开发者

Dynamic validation and metaprogramming in Ruby

开发者 https://www.devze.com 2023-02-23 04:36 出处:网络
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 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
0

精彩评论

暂无评论...
验证码 换一张
取 消