DataMapper case-insensitive unique validation - ruby

I have something like:
class User
include DataMapper::Resource
property :id, Serial
property :username, String, :unique => true
end
post '/signup' do
user = User.create(username: params['username'])
if user.save
puts "New user was created"
else
puts user.errors
end
end
Parameter :unique => true is case-sensitive. It does not prevent to create users with usernames 'admin' and 'Admin'. How can I validate case-insensitive username unique, with out downcased username property, so users can make usernames as they choose.

You can provide your own custom validation:
class User
include DataMapper::Resource
property :id, Serial
property :username, String
validates_with_method :username,
:method => :case_insensitive_unique_username
def case_insensitive_unique_username
User.first(conditions: ["username ILIKE ?", self.username]).nil?
end
end
Note that ILIKE will only work with PostgreSQL, you will have to find out how to find records case insensitively with your specific adapter for yourself.

Related

Mongoid : The Validation "validates_uniqueness_of" is only triggered when the specific field changes

I have a unique constraint defined using a condition. But the following test does not pass :
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_uniqueness_of :name, if: :status
end
describe "UniquenessValidator" do
let!(:d1) { Dummy.create!(name: 'NAME_1', status: true) }
let!(:d2) { Dummy.create!(name: 'NAME_1', status: false) }
it "should raise an error" do
expect {
d2.status = true
d2.save!
}.to raise_error
end
end
Since name_changed? is false, no validation seems to happen and therefore the uniqueness condition is not checked.
Is it a bug ? Or have I forgotten something ? I guess it's an optimization to avoid to run the validation each time the element is modified.
In that case what is the good way to trigger the validation when the status is changed ?
Thanks!
As you case is a edge case, I would advice you to create your own validator class for this, something like this should work:
class NameUniquenessValidator < Mongoid::Validatable::UniquenessValidator
private
def validation_required?(document, attribute)
return true "name" == attribute.to_s
super
end
end
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_with(NameUniquenessValidator, :name, if: :status)
end
You are updating the status field so, you need to validate this field. You can do something like this:
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_uniqueness_of :name, if: :status
validates_uniqueness_of :status, scope: :name, if: :status
end
I don't know if you can force mongoid to validate all fields when you update a single field.

Datamapper does not save/update entry

I'm currently developing a quick little sinatra app, and I've managed to conquer authentication quite easily. However I cannot for the life of me get password changing to work. I'm using the code below with Datamapper, and although it reaches the redirect, the password does not change.
user = User.first(:token => session[:user])
if params[:newpassword] == params[:newpasswordconfirm]
if BCrypt::Engine.hash_secret(params[:oldpassword], user.salt) == user.password_hash
user.password_hash = BCrypt::Engine.hash_secret(params[:newpassword], user.salt)
user.save
redirect '/'
I've also tried
user = User.first(:token => session[:user])
if params[:newpassword] == params[:newpasswordconfirm]
if BCrypt::Engine.hash_secret(params[:oldpassword], user.salt) == user.password_hash
user.update(:password_hash = BCrypt::Engine.hash_secret(params[:newpassword], user.salt)
redirect '/'
however this also fails to update the value. Unsure what I've done wrong.
class User
include DataMapper::Resource
attr_accessor :password, :password_confirmation
property :id, Serial
property :username, String, :required => true, :unique => true
property :password_hash, Text
property :salt, Text
property :token, String
validates_presence_of :password
validates_confirmation_of :password
validates_length_of :password, :min => 6
end

"Retweets" with Datamapper

I want to impliment something which is similar to Twitter Repost System, therefore I will use this as an example. So let's say I have a Tweet Model and I want to allow other user to repost a certian tweet of another user, how do I impliment something like this?
I thought I would be a cool idea to put the retweet class into the tweet to be able to acess the repost too when I use Tweet.all to recive all tweets stored in the database, but somehow I didn't worked as expected...
The following Code is just an example which should show how to impliment this even if it is not working...
Any ideas how I could build a working repost model which also allows me to access both tweets and retweet by using Tweet.all?
class Tweet
class Retweet
include DataMapper::Resource
belongs_to :user, key => true
belongs_to :tweet, key => true
end
include DataMapper::Resource
property :text, String
property :timestamp, String
belongs_to :user
end
Important: I should be carrierwave compatible.
class Tweet
include DataMapper::Resource
property :id, Serial
has n, :retweets, 'Tweet', :child_key => :parent_id
belongs_to :parent, 'Tweet', :required => false
belongs_to :user
def is_retweet?
self.parent_id ? true : false
end
end
original = Tweet.create :user => user1
retweet = Tweet.create :parent => original, :user => user2
retweet.is_retweet? # => true

Associating one class to belong to two different classes in DataMapper using Sinatra

I am working with DataMapper and Sinatra to create a simple app. Here's the structure:
The app has Accounts. Each account has users and campaigns. Each user has comments that should be related to a specific campaign.
Comments should ideally have a user_id and a campaign_id to relate them both.
How can I relate the 2 together? Here's the code that I have which does not work:
class Account
include DataMapper::Resource
property :id, Serial
property :mc_username, String, :required => true
property :mc_user_id, String, :required => true
property :mc_api_key, String, :required => true
property :created_at, DateTime
property :updated_at, DateTime
has n, :users
has n, :campaigns
end
class User
include DataMapper::Resource
property :id, Serial
property :name, String, :required => true
property :email, String, :required => true
property :is_organizer, Integer
property :created_at, DateTime
property :updated_at, DateTime
belongs_to :account, :key => true
has n, :comments
end
class Campaign
include DataMapper::Resource
belongs_to :mailchimpaccount, :key => true
has n, :comments
property :id, Serial
property :cid, String
property :name, String
property :current_revision, Integer
property :share_url, Text, :required => true
property :password, String
property :created_at, DateTime
property :updated_at, DateTime
end
class Comment
include DataMapper::Resource
belongs_to :campaign, :key => true
belongs_to :user, :key => true
property :id, Serial
property :at_revision, Integer
property :content, Text
property :created_at, DateTime
end
With this code, I can't save a comment since I can't figure out how to associate it to a campaign and a user at the same time. I can't really get my head around wether I should even try to relate them at all using DataMapper.
I would love to know if this code is correct, how I can go about creating a comment that is related to both. If not, what structure and associations would be optimal for this scenario?
Thanks so much for the help!
What you're doing seems reasonable, I think you just need to get rid of the :key => true options since you don't really want those associations to be part of the comment's primary key.
You should probably start by looking at these datamapper docs on properties.
Alex is right, what you have there is a composite primary key. This would be ok if you only wanted each user to have one comment per campaign, but this is probably not the case but you do want to make sure that the comment is associated to a user and a campaign so use required => true, like so:
class Comment
include DataMapper::Resource
property :id, Serial
belongs_to :campaign, :required => true
belongs_to :user, :required => true
property :at_revision, Integer
property :content, Text
property :created_at, DateTime
end
Also your key in the campaign model may be problematic:
class Campaign
include DataMapper::Resource
belongs_to :mailchimpaccount, :key => true
#......
You probably just want to make that required too.
So it seems that my thinking was correct. I can relate a comment to both a user and a campaign in this way:
# Get a user and a campaign first that we can relate to the comment
user = User.get(user_id)
campaign = Campaign.get(campaign_id)
comment = Comment.new
comment.content = "The comment's content"
user.comments << comment # This relates the comment to a specific user
campaign.comments << comment # This now relates the comment to a specific campaign
comment.save # Save the comment
Dangermouse's suggestion to replace the :key => true option with :required => true also helped clean up the schema. Thanks!

Cannot find the child_model (NameError)

I am unable to determine why I am getting a name error here. I'm new to DataMapper, but trying to associations down. Any help is appreciated.
User:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :first_name, String
property :last_name, String
property :company, String
property :city, String
property :country, String
property :mobile_number, Integer
property :email_address, String
property :shahash, String
property :isRegistered, Boolean
belongs_to :event, :required => true
end
DataMapper.auto_upgrade!
Event:
class Event
include DataMapper::Resource
property :id, Serial, :key => true
property :name, String
property :occuring, DateTime
has n, :user
end
DataMapper.auto_upgrade!
I think the problem is you're calling DataMapper.auto_upgrade! after each model definition. When you call it after just defining one model, there's no child model there. Instead, you should define and/or require all your models and then do:
DataMapper.finalize # set up all relationships properly
# and do a basic model sanity check
DataMapper.auto_upgrade! # create database table if it doesn't exist
Add an init file in your models directory and move all of your your DataMapper.finalize statements to it (i.e. remove the finalize statement from your individual model files)
app/models/init.rb
require_relative 'file_name'
require_relative 'another_model_file_name'
DataMapper.finalize
Then in your application file require the init file
require_relative 'models/init'

Resources