I have a field in a model that is serialized and when I attempted to validates the uniqueness of it, it doesn't work. (Still on Rails 2.3 on this app)
app/models/foo.rb
class foo < ActiveRecord::Base
serialize :rules
validates_uniqueness_of :rules
end
I have attempted to store the content in a hash field instead and validate the content hash's uniqueness. Then I ran to another problem of the order of the callbacks.
require 'digest/md5'
class foo < ActiveRecord::Base
before_save :update_content_hash
validates_uniqueness_of :content_hash
def update_content_hash
self.content_hash = OpenSSL::Digest::SHA1.hexdigest(self.rules.flatten)
end
end
However, having looked at the Active Record callbacks order, before_save is executed after validation, so it will always be valid because the default value is nil and after that it's updated to the new content hash.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Maybe I'm not thinking straight, any solution to this?
Many thanks in advance.
Try this:
before_validation :update_content_hash
Related
Say, if we have
class SomeData < ActiveRecord::Base
validates :foo, inclusion: 33..99
and now, we say, we will make the range narrower, to 66..99, but there are many values in the Database that are still between 33 and 65, can we change the above code to
class SomeData < ActiveRecord::Base
validates :foo, inclusion: 66..99
immediately? Is it a problem if the data is just read into the system? (even in earlier version of Rails such as 3.2?)
Changing a validation does not actually directly effect the existing data in your database in any way. Validations are only run when #valid? is called on the model.
This happens implicitly when you call:
.create and .create!
#save and #save!
#update and #update!
And it causes these methods to bail so that either a rollback occurs or no db query is fired in the first place.
Can we change the validation immediately?
Yes. The validation will only really kick in if you try to update an existing record. In which case a previously valid record may now be invalid. This will not permit an update unless the value is updated to be in the new permitted range.
Dealing with legacy data
If you want to let users still update existing records with what is invalid data according to the new rules there are serveral tricks.
The if: and unless: options
class SomeData < ActiveRecord::Base
validates :foo, inclusion: 33..99, if: :legacy_record?
validates :foo, inclusion: 66..99, unless: :legacy_record?
end
There are several ways to implement :legacy_record? like a boolean flag in the database. Note that these should not be confused with the ruby keywords. They are just hash options.
custom validation methods:
class SomeData < ActiveRecord::Base
validate :my_validation_method
def my_validation_method
rng = legacy_record? ? 33..99 : 66..99
errors.add(:foo, "out of range") unless rng.cover?(foo)
end
end
Single Table Inheritance
class Thing < ActiveRecord::Base
validates :foo, inclusion: 66..99
end
class LegacyThing < ActiveRecord::Base
self.table_name = "things"
validates :foo, inclusion: 33..99
end
In this example you would add a things.type varchar column and update the existing rows with things.type = "LegacyThing". This isn't really STI - it just uses the mechanism built into ActiveRecord.
I've one small problem, I can't get solved. I want to validate that there is at least one associated model. Like in the following
class User < ActiveRecord::Base
has_many :things
validates_presence_of :things
end
class Thing < ActiveRecord::Base
belongs_to :user
end
This works fine when I update my model via #update_attributes, but when I simply set #user.things = [], I am able to get invalid data in the database. My workaroud to solve this is to overwrite the setter method
def things=(val)
begin
if val.blank?
errors.add(:things, "not valid")
raise SomeError
end
super
rescue SomeError
false
end
end
But somehow this doesn't feel right. Isn't there a way to archive the same result via validations and/or callbacks, preferably so that #things= return false (and not val) and so that #user.things is not changed (I mean the cached #user.things, #user.things(true) should work fine anyway).
You can create a custom validator that will check the presence of things.
Instead of
validates_presence_of :things
You could do
validate :user_has_things
def user_has_things
if self.things.size == 0
errors.add("user has no thingies")
end
end
I have a model with an serialized attribute (array). I would like to validate the model only if each member of the array is included within the pre-determined options.
Example:
I have a Person model which has a "mood" attribute. Users can have more than one mood, but each mood must be either 'happy', 'sad', 'tired' or 'angry'.
The model would be something like this:
class Person < ActiveRecord::Base
MOODS = %w[happy sad tired angry]
# validates :inclusion => { :in => MOODS }
attr_accessible :mood
serialize :mood
end
The commented validation doesn't work. Is there any way to make it work or do I need a custom validation?
(Note: I don't want to create a separate Mood model.)
class Person < ActiveRecord::Base
MOODS = %w[happy sad tired angry]
validate :mood_check
attr_accessible :mood
serialize :mood
protected
def mood_check
mood.each do |m|
errors.add(:mood, "#{m} is no a valid mood") unless MOODS.include? m
end
end
end
Leaving this here in case it helps anyone in the future - I've written a gem to better handle validating serialized attributes. You can just put those validations in a block syntax, the ways you might expect to:
class Person < ActiveRecord::Base
MOODS = %w[happy sad tired angry]
attr_accessible :mood
serialize :mood
validates_array_values :mood, inclusion: { in: MOODS }
end
https://github.com/brycesenz/validates_serialized
class Parent
has_one :child
accepts_nested_attributes_for :child
end
class Child
belongs_to :parent
end
Using a nested object form, I need to add some extra validations to the child model. These are not always run on Child, so I cannot put them in a validate method in Child. It seems sensible to do the checking in a validate method in Parent, but I'm having trouble adding error messages correctly.
This does work:
class Parent
...
def validate
errors[ :"child.fieldname" ] = "Don't be blank!"
end
But we lose nice things like I18n and CSS highlighting on the error field.
This doesn't work:
def validate
errors.add :"child.fieldname", :blank
end
You should keep them in the child model since that's the one validated, however, you can set conditionals with if: and unless:
class Order < ActiveRecord::Base
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
You can do several variations on this, read more in the rails documentation http://edgeguides.rubyonrails.org/active_record_validations.html#conditional-validation
I guess you could add an attribute, created_by to child, and make Child select which validations to use depending on that one. You can do that like they do in this answer: Rails how to set a temporary variable that's not a database field
Given a model
class BaseModel < ActiveRecord::Base
validates_presence_of :parent_id
before_save :frobnicate_widgets
end
and a derived model (the underlying database table has a type field - this is simple rails STI)
class DerivedModel < BaseModel
end
DerivedModel will in good OO fashion inherit all the behaviour from BaseModel, including the validates_presence_of :parent_id. I would like to turn the validation off for DerivedModel, and prevent the callback methods from firing, preferably without modifying or otherwise breaking BaseModel
What's the easiest and most robust way to do this?
I like to use the following pattern:
class Parent < ActiveRecord::Base
validate_uniqueness_of :column_name, :if => :validate_uniqueness_of_column_name?
def validate_uniqueness_of_column_name?
true
end
end
class Child < Parent
def validate_uniqueness_of_column_name?
false
end
end
It would be nice if rails provided a skip_validation method to get around this, but this pattern works and handles complex interactions well.
As a variation of the answer by #Jacob Rothstein, you can create a method in parent:
class Parent < ActiveRecord::Base
validate_uniqueness_of :column_name, :unless => :child?
def child?
is_a? Child
end
end
class Child < Parent
end
The benefit of this approach is you not need to create multiple methods for each column name you need to disable validation for in Child class.
From poking around in the source (I'm currently on rails 1.2.6), the callbacks are relatively straightforward.
It turns out that the before_validation_on_create, before_save etc methods, if not invoked with any arguments, will return the array which holds all the current callbacks assigned to that 'callback site'
To clear the before_save ones, you can simply do
before_save.clear
and it seems to work
A cleaner way is this one:
class Parent < ActiveRecord::Base
validate :column_name, uniqueness: true, if: 'self.class == Parent'
end
class Child < Parent
end
Or you can use it also in this way:
class Parent < ActiveRecord::Base
validate :column_name, uniqueness: true, if: :check_base
private
def check_base
self.class == Parent
end
end
class Child < Parent
end
So, uniqueness validation is done if the instance class of model is Parent.
Instance class of Child is Child and differs from Parent.
Instance class of Parent is Parent and is the same as Parent.
Since rails 3.0 you can also access the validators class method to manipulate get a list of all validations. However, you can not manipulate the set of validations via this Array.
At least as of rails 5.0 you however seem to be able to manipulate the _validators (undocumented) method.
Using this method you can modify the validations in the subclass like e.g.:
class Child < Parent
# add additional conditions if necessary
_validators.reject! { |attribute, _| attribute == :parent_id }
end
While this uses an undocumented method, is has the benefit of not requiring the superclass to know anything about the child's implementation.
Again poking around in the source, it seems that validations can be run either on every save, or updates/creates only. This maps to
:validate => all saves
:validate_on_create => creations only
:validate_on_update => updates only
To clear them, you can use write_inheritable_attribute, like this:
write_inheritable_attribute :validate, nil
Here is a slight variation of RubyDev's that I've been using in mongoid 3.
class Parent
include Mongoid::Document
validates :column_name , uniqueness: true, unless: Proc.new {|r| r._type == "Child"}
end
class Child < Parent
end
It has been working pretty good for me so far.