I am trying to development an application that can present the same resource to different users and where the resource may have different validation behavior based on the user.
I have tried to use Ruby metaprogramming to solve this in an easy way but it looks like I am missing some key knowledge of the matter.
I can be examplified by a model such as
class Profile < ActiveRecord::Base
# validates_presence_of :string1
end
The model has a property 'string1' that sometimes are required and sometimes not. I would like to create sub-classes for each user (for reasons not apparent in this simplification) and have created a module that I would like to include:
module ExtendProfile
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def configure_validation(required)
if required
class_eval("ActiveRecord::Base.validates_presence_of :string1")
end
end
end
end
Its only purpose is to add a method that add the conditional validation based on the arguments given.
It does add the validation when called with an argument that is true but it does not do it cleanly. It appears that it does not separate the s开发者_StackOverflow社区ubclasses as I thought it would.
It can be illustrated by the following tests:
profile = Profile.new
profile.save; profile.errors
=> []
A profile can by default be saved without errors.
Object::const_set('FirstExtendedProfile'.intern, Class::new(Profile) { include ExtendProfile })
FirstExtendedProfile.configure_validation(true)
fep = FirstExtendedProfile.new; fep.save; fep.errors
=> {:string1=>["skal udfyldes", "skal udfyldes"]}
Creating a new subclass and calling configuring_validation
adds validation but for some reason it is called twice during validation ("skal udfyldes" - is Danish and means that it is required).
Object::const_set('SecondExtendedProfile'.intern, Class::new(Profile) { include ExtendProfile })
sep = SecondExtendedProfile.new; sep.save; sep.errors
=> {:string1=>["skal udfyldes"]}
Another descendant is created and even though configure_validation
isn't called it still validates the string1
property (but now only once).
Adding yet another descendant and calling configure_validation
adds the validation once more...
Why am I not able to add the validation to the specific Profile descendant?
I am using Ruby 1.9.2 and Rails 3.06. Please understand that I would like to understand how to make this dynamic class creation to work - I am aware of "standard" custom validation.
For reasons that become clear when working with Single Table Inheritance, validations are stored in a variable in the "root" class, that is the class that directly inherits for ActiveRecord::Base. There is quite a bit of work behind the scenes to make sure that what your trying to do does not work.
My suggestion is to store some configuration data in each class then write a single dynamic validation that checks the configuration and validates based on what it finds there. This may however be what you mean by "I am aware of "standard" custom validation."
with_options :if => lambda { |obj| obj.class.validation_configuration[:string1]} do |t|
t.validates_presence_of :string1
end
This is a different way to solve the problem... rather than use meta-programming to extend or not-extend a class:
class Profile
with_options :if => lambda { |o| o.required? } do |on_required|
on_required.validates_presence_of :string1
end
end
精彩评论