3

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 subclasses 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.

HakonB
  • 6,977
  • 1
  • 26
  • 27
  • Not certain yet but it might be because the list of validators are kept in a class variable in the class that is a direct descendant of `ActiveRecord::base` and that this variable is shared between its descendants. Is that the case? – HakonB Apr 10 '11 at 21:33

2 Answers2

2

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
Jesse Wolgamott
  • 40,197
  • 4
  • 83
  • 109
  • Thanks, however, I am aware of this customization done this way - I was looking for a solution to my metaprogramming issue – HakonB Apr 10 '11 at 22:03
2

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 
John F. Miller
  • 26,961
  • 10
  • 71
  • 121
  • Yes, I am aware of this way of doing custom validation but my issue is that figuring out how it should be validated is actually quite complex and I would like to do that only once and then create a "template" class where everything is set up static. – HakonB Apr 10 '11 at 22:01