I have two different kind of user (devise) models in my Rails application. Doctor and Patient. It was my fault I haven't defined an upper class User and inherit attributes from it. They have a mutual attribute - a personal identification number and I want two check uniqueness of this attribute in both of tables. I've searched a bit and saw this answer.
I've applied things as it written there but it had no effect.
#patient.rb
class Patient < ActiveRecord::Base
...
validates_uniqueness_of :pin
validate :pin_not_on_doctors
private
def pin_not_on_doctors
Doctor.where(:pin => self.pin).first.nil?
end
end
#doctor.rb
class Doctor < ActiveRecord::Base
...
validates_uniqueness_of :pin
validate :pin_not_on_patients
private
def pin_not_on_patients
Patient.where(:pin => self.pin).first.nil?
end
end
First I created a patient instance, then a doctor instance with the same pin I used in my first (patient) case. Rails unexpectedly didn't spit out an error message and created doctor instance, and more interestingly devise also turned a blind eye for duplicate email.
How can I overcome this problem?
You should add the error on the validation function:
http://api.rubyonrails.org/classes/ActiveModel/Errors.html
def pin_not_on_doctors
errors.add :pin, "already exists" if Doctor.exists?(:pin => self.pin)
end
In addition to adding the error, try adding a line to return true/false,
Something like,
def pin_not_on_doctors
errors.add :pin, "already exits" if Doctor.exists?(:pin => self.pin)
Doctor.exists?(:pin => self.pin)
end
I don't know the details of your app, but in depending on how you're actually creating the object in this case, it might need that.
EDIT: Misread something in the original, looks like your current version is to just return true/false, so this probably doesn't help. Sorry.
Related
I have two tables: admin_users and users. I want all the created names to be unique (a regular user can't create a name already taken by an admin user and vice-versa). I'm having trouble writing a validates_uniqueness_of validation that is able to analyze information in a different table. I think I have to use a scope of some sort, but I tried all sorts of combinations and couldn't get it to work. To be clear: I'm looking for the correct code to replace the question marks below.
validates_uniqueness_of :user_name, :scope => #???Look in admin users
#table and check to make that this name is not taken.
Any help would be very much appreciated. Thanks!
You can create a custom validator for this.
class UserNameValidator < ActiveModel::Validator
def validate(record)
if AdminUser.exists?(user_name: record.user_name)
record.errors[:base] << "An admin user have this username!"
end
end
end
class User < ActiveRecord::Base
validates_with UserNameValidator
end
Rails convention of adding a bang to the end of a method makes it throw an exception if it fails. But my experience isn't matching that behavior.
My controller for a many-to-many relationship (an audit trail). The relationship object cannot be created, only updated by posting events to the audit trail object. (Which means you create by updating...)
I have a User object and a Foo object I'll call the relationship Bar.
bar=Bar.where(:user_id=>params[:user_id]).where(:foo_id=>params[:foo_id]).first
if bar
authorize! :update, bar
else
user=User.find(params[:user_id])
authorize! :bar_create, user
foo=Foo.find(params[:foo_id])
bar=Bar.create!(:user_id=>user.id, :foo_id=>foo.id)
end
The create method does not work. I debugged, and bar.save worked fine, but the entire point of create is to avoid having to make that second call to save. I experimented, and discovered that create! works just fine.
Edit:
As I continued on, I discovered that create! did not, in fact, always save. No errors in the underlying object, just mysteriously not saved.
I've had to do a create call followed by a save call, which... honestly, I just don't understand.
Edit: Per request, adding model code -- simplified to the relevant statements by removing unnecessary methods, validation calls, and the like. (While writing this, I noticed that I haven't yet added the has_many :through calls, but... doesn't seem like those should be relevant to the issue at hand.
class User < ActiveRecord::Base
has_secure_password
has_many :progresses
end
class Bar < ActiveRecord::Base
belongs_to :user
belongs_to :foo
has_many :bar_events
validates :user, :presence=>true
validates :foo, :presence=>true
scope :user_id, -> (user_id){ where user_id: user_id}
scope :foo_id, -> (foo_id){ where foo_id: foo_id}
end
class Foo < ActiveRecord::Base
end
There is a validation error or more in one of the associations in Bar foo or user. Try inspecting those objects:
bar=Bar.create!(:user_id=>user.id, :foo_id=>foo.id)
puts bar.errors.inspect, bar.user.errors.inspect, bar.foo.errors.inspect
That will print the errors of all those objects to the terminal running rails server. The only reason create would not save is due to validation errors in itself or nested associations.
I wanted some advice about how to handle to_param in regards to permalinks
Basically this is what happens.
Create a new company
The company :name is then parameterized and saved as a :permalink in the db
Updating an existing company enables you to change the :permalink
There are validations to ensure user updated :permalink is unique
The problem I'm having is occurring when updating the company's :permalink to something that already exists. The uniqueness validation works which is great, but it changes the params[:id] to the invalid permalink instead of reseting and using the existing params[:id]
When I try to edit the permalink to something else I get a flash validation error of "Name already taken" because it thinks I'm editing the company of the already existing :permalink (company). The URL reflects the change in permalink since my companies_controller.rb is using #company = Company.find_by_permalink[:id])
I wanted to know the best way to handle this issue?
class Companies < ActiveRecord::Base
before_create :set_permalink
before_update :update_permalink
attr_accessible :name, :permalink
validates :name, :permalink, uniqueness: { message: 'already taken' }
def to_param
permalink
end
private
def set_permalink_url
self.permalink = name.parameterize
end
def update_permalink_url
self.permalink = permalink.parameterize
end
end
Apologies if I'm not making too much sense.
Thanks in advance.
you could try to handle this with an after_rollback callback.
after_rollback :restore_permalink
def restore_permalink
self.permalink = permalink_was if permalink_changed?
end
here's how it works : every update / destroy in Rails is wrapped in a transaction. If the save fails, the transaction rollbacks and triggers the callback.
The callback then restores the old value (permalink_was) if it was changed since the record has been loaded.
See ActiveModel::Dirty and ActiveRecord::Transactions for more info.
EDIT
On the other hand, there may be another solution (untested) - just define your accessor like this :
def permalink=( value )
permalink_will_change! unless #permalink == value
#permalink = value
end
This way, the permalink will not be marked as dirty if the new value is identical to the old one, and so AR will not try to update the column.
Explanation:
i don't know on which version of rails it was implemented (it is relatively recent), but here's how "dirtyness" works :
your "standard" (automagically generated) attribute setters basicly call
#{your_attribute}_will_change! before setting the associated
instance variable (even if you set the exact same value than before)
when you call save, ActiveRecords looks for attributes that have changed ("dirty") and builds the SQL UPDATE query using ONLY these attributes (for performance reasons, mostly)
so if you want to avoid your permalink to appear in the query when it is unchanged, i think you have to override the standard setter - or avoid mass-assignment and only set permalink if it has changed
Let's say a model catches a validation error, usually this is handled by the controller, but is it possible to handle it automatically by the model?
Practically I want to generate a unique id uid for each Note, the model looks like this:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
end
The closest I got is:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
after_rollback :repair
protected
def repair
if self.errors[:uid].size > 0
self.uid = generate_uid
end
self.save # Try again
end
end
Some immediate problems with my solution: (1) The model instance still has errors that the controller can see, I'm not sure how to clear the errors. (2) The repair method is recursive.
While I'm sure there is a way to catch and handle the errors in the model (maybe the after_validation callback could be of use), perhaps you can avoid the issue in this case by ensuring that the uid you generate is unique when you create it.
Ryan Bates offered this method for generating unique tokens in a RailsCast:
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
With the use of a before_create callback, i.e. before_create { generate_token(:uid) }, each model will have a unique id.
All this said, #Beerlington raises a really good point about UUIDs.
Update: Note that the method given is expecting to be defined in a User model. For your example, you'd want to change it to ...while Note.exists?....
I would use a true UUID that is guaranteed to be unique, and not add the overhead to your model. Having a uniqueness validation in the model adds some overhead because it has to hit the database to figure out if something exists, and it's still not even guaranteed.
Check out this Ruby project to generate UUIDs: https://github.com/assaf/uuid/
I have models like this:
class Person
has_many :phones
...
end
class Phone
belongs_to :person
end
I want to forbid changing phones associated to person when some condition is met. Forbidden field is set to disabled in html form. When I added a custom validation to check it, it caused save error even when phone doesn't change. I think it is because a hash with attributes is passed to
#person.update_attributes(params[:person])
and there is some data with phone number (because form include fields for phone). How to update only attributes that changed? Or how to create validation that ignore saves when a field isn't changing? Or maybe I'm doing something wrong?
You might be able to use the
changed # => []
changed? # => true|false
changes # => {}
methods that are provided.
The changed method will return an array of changed attributes which you might be able to do an include?(...) against to build the functionality you are looking for.
Maybe something like
validate :check_for_changes
def check_for_changes
errors.add_to_base("Field is not changed") unless changed.include?("field")
end
def validate
errors.add :phone_number, "can't be updated" if phone_number_changed?
end
-- don't know if this works with associations though
Other way would be to override update_attributes, find values that haven't changed and remove them from params hash and finally call original update_attributes.
Why don't you use before_create, before_save callbacks in model to restrict create/update/save/delete or virtually any such operation. I think hooking up observers to decide whether you want to restrict the create or allow; would be a good approach. Following is a short example.
class Person < ActiveRecord::Base
#These callbacks are run every time a save/create is done.
before_save :ensure_my_condition_is_met
before_create :some_other_condition_check
protected
def some_other_condition_check
#checks here
end
def ensure_my_condition_is_met
# checks here
end
end
More information for callbacks can be obtained here:
http://guides.rubyonrails.org/activerecord_validations_callbacks.html#callbacks-overview
Hope it helps.