Help How to create DSL for conditional validations

Hi All,

I am new to Ruby and ROR I were trying to create small DSL for conditional validations

valid_with_cond :bypass_validation do if self.addresses > 3 errors[:base] << “Can not have more than 3 addresses”.

end

end

By this I wanted to create array of method and call them all in custom validation method.

this above code I wanted to do

attr_accessor :bypass_validation

def svalid_cond

unless bypass_validation
  if self.addresses > 3
     errors[:base] << "Can not have more than 3 addresses".
  end
end

end

        This way I want in new and edit form I can ask user to click

        :bypass_validation attribute and I will bypass this validation.

I tried to create

def valid_with_cond(name, &block) attr_accessor name define_method “svalidator_#{name}” do |*arg|

  if send name
    yield *arg
  end
end

end

But after trying it in

Class Person < ActibeRecord::Base valid_with_cond :test do if self.addresses > 3 errors[:base] << “Can not have more than 3 addresses”.

end

end end

But when I check it on console

Person.test

it throw me error test is private method.

Anybody could let me know how to correct it.

Your call to attr_accessor will create instance methods called test and test=, but you tried to call test on the class (and so you end up falling through to Kernel#test). Have you seen that validations take :if and :unless options ?

Fred

Hi

Your call to attr_accessor will create instance methods called test and test=, but you tried to call test on the class (and so you end up falling through to Kernel#test).

No I have tested this is not the case, yes I have given wrong example but with object also it is not working, I have given all details below I guess I got it working by hit and trial way, so not sure what exactly is happening around.

Have you seen that validations take :if and :unless options ?

I need to use coustom validation, for them I did not see any :if, :unless option

But as I got it working I have lot of question in all stages that I want to put else I will never able to know what is happening.

test 1:     >     > def cvalidation(name, &block)     > attr_accessor name     > define_method "svalidator_#{name}" do |*arg|     > if send name     > yield *arg     > end     > end     > end      =>     >     > class Person     > cvalidation :zen do     > puts "Yes"     > end     > end            => #<Proc:0x95e73fc@(irb):8 (lambda)>     > x = Person.newq           NoMethodError: undefined method `newq' for Person:Class

So this is not working now in test 2, I now test only attr_accessor

test 2:

    > def k(name)     > attr_accessor name     > end             => nil     > class Personq     > k :test     > end            => nil     > x = Personq.new            => #<Personq:0x917a4e0>     > x.test            NoMethodError: private method `test' called for #<Personq: 0x917a4e0>

it again will not work.

test 3:     > def k(name)     > class_eval do     > attr_accessor name     > end     > end              => nil     > class Pers     > k :u     > end     > x = Pers.new            => #<Pers:0x8f9bf0c>     > x.u            => nil     > x.u = "sfdsg"           => "sfdsg"     > x.u           => "sfdsg

Q1: Now it is working, but why it did not work in test 2 ?

test 4:     > def cvalidation(name, &block)     > class_eval do     > attr_accessor name     > define_method "svalidator_#{name}" do |*arg|     > if name     > yield *arg     > end     > end     > end     > end               => nil     >     > class Work     > cvalidation :done do     > puts "Yes"     > end     > end             => #<Proc:0xa337638@(irb):64 (lambda)>     > x = Work.new            => #<Work:0xa32d200>     > x.svalidator_done           Yes            => nil     > x.done           => nil

Here define_method is working but it is not using argument `name' in if condition, it uses some other name method, than provided argument `name'

Q2: Why it is like that and what `name' method or variable it is     actually using in both lines ?     > define_method "svalidator_#{name}" do |*arg|     > if name

test 5:     > def cvalidation(name, &block)     > class_eval do     > attr_accessor name     > define_method "svalidator_#{name}" do |*arg|     > if send "#{name}"     > yield *arg     > end     > end     > end     > end               => nil     >     > class QQ     > cvalidation :xx do     > puts "Yes"     > end     > end             => #<Proc:0xa0aaf28@(irb):105 (lambda)>     > x = QQ.new           => #<QQ:0xa0a09ec>     > x.svalidator_xx           => nil     > x.xx           => nil     > x.xx = "sfddsaf"           => "sfddsaf"     > x.svalidator_xx           Yes           => nil     > x.xx = nil           => nil     > x.svalidator_xx           => nil     > class NN     > cvalidation :ww do |w|     > puts w, " Hi"     > end     > end             => #<Proc:0x9fbe830@(irb):105 (lambda)>     > x = NN.new           => #<NN:0x9fabb40>     > x.ww           => nil     > x.svalidator_ww           => nil     > x.ww = "Sharad"           => "Sharad"     > x.svalidator_ww

          Hi           => nil     > x.svalidator_ww "Sharad"           Sharad           Hi           => nil     > puts "Sharad", "Hi"           Sharad           Hi           => nil     >

Here I guess as I see all is working, but I really like to know about `name' used in

    > if send "#{name}"

in test 5, uses the `name' arguemnt provided.

But     > if name in test 4 it do not able to use same `name' argument

Why ?

> Have you seen that validations take :if and :unless options ?

I need to use coustom validation, for them I did not see any :if, :unless option

I think you can still do

validate :something, :if => :something+else?

But as I got it working I have lot of question in all stages that I want to put else I will never able to know what is happening.

[snip

&gt;
&gt;   class Person
&gt;   cvalidation :zen do
&gt;       puts &quot;Yes&quot;
&gt;     end
&gt;   end
       =&gt; \#&lt;Proc:0x95e73fc@\(irb\):8 \(lambda\)&gt;
&gt; x = Person\.newq
      NoMethodError: undefined method \`newq&#39; for Person:Class

Not sure what this is showing - you haven't attempted to define newq anywhere

So this is not working now in test 2, I now test only attr_accessor

test 2:

&gt;   def k\(name\)
&gt;    attr\_accessor name
&gt;   end
        =&gt; nil
&gt;  class Personq
&gt;   k :test
&gt;   end
       =&gt; nil
&gt;  x = Personq\.new
       =&gt; \#&lt;Personq:0x917a4e0&gt;
&gt;  x\.test
       NoMethodError: private method \`test&#39; called for \#&lt;Personq:

0x917a4e0>

I ran this in irb and this worked fine.

test 4: > def cvalidation(name, &block) > class_eval do > attr_accessor name > define_method "svalidator_#{name}" do |*arg| > if name > yield *arg > end > end > end > end => nil > > class Work > cvalidation :done do > puts "Yes" > end > end => #<Proc:0xa337638@(irb):64 (lambda)> > x = Work.new => #<Work:0xa32d200> > x.svalidator_done Yes => nil > x.done => nil

Here define_method is working but it is not using argument `name' in if condition, it uses some other name method, than provided argument `name'

Q2: Why it is like that and what `name' method or variable it is actually using in both lines ?

blocks are closures, so it sees the same local variables as those that were existent when define_method were called, so it picks up the name that was first argument to cvalidation. self is special though - self will be the value of the object the method is called on.

&gt;           define\_method &quot;svalidator\_\#\{name\}&quot; do |\*arg|
&gt;                 if name

test 5: > def cvalidation(name, &block) > class_eval do > attr_accessor name > define_method "svalidator_#{name}" do |*arg| > if send "#{name}" > yield *arg > end > end > end > end => nil > > class QQ > cvalidation :xx do > puts "Yes" > end > end => #<Proc:0xa0aaf28@(irb):105 (lambda)> > x = QQ.new => #<QQ:0xa0a09ec> > x.svalidator_xx => nil > x.xx => nil > x.xx = "sfddsaf" => "sfddsaf" > x.svalidator_xx Yes => nil > x.xx = nil => nil > x.svalidator_xx => nil > class NN > cvalidation :ww do |w| > puts w, " Hi" > end > end => #<Proc:0x9fbe830@(irb):105 (lambda)> > x = NN.new => #<NN:0x9fabb40> > x.ww => nil > x.svalidator_ww => nil > x.ww = "Sharad" => "Sharad" > x.svalidator_ww

      Hi
      =&gt; nil
&gt; x\.svalidator\_ww &quot;Sharad&quot;
      Sharad
      Hi
      =&gt; nil
&gt; puts &quot;Sharad&quot;, &quot;Hi&quot;
      Sharad
      Hi
      =&gt; nil
&gt;

Here I guess as I see all is working, but I really like to know about `name' used in

&gt;                 if send &quot;\#\{name\}&quot;

in test 5, uses the `name' arguemnt provided.

But > if name in test 4 it do not able to use same `name' argument

When you just write name, it picks up the local variable from the closure (it's ambiguous whether you mean the local variable or the method, and in these cases ruby picks the local variable), but by using send you're forcing it to call the accessor method. You could also have disambiguated by writing self.name or name()

Fred

Hi Frederick!

I think you can still do validate :something, :if => :something+else?

I hope it could be used multiple time with different methods

> > x = Person.newq > NoMethodError: undefined method `newq' for Person:Class

Not sure what this is showing - you haven't attempted to define newq anywhere

Sorry It was my typing mistake.

> test 2: > .... I ran this in irb and this worked fine.

But it has not work fine, I again tested it now. my ruby version    ruby 1.8.7 (2010-06-23 patchlevel 299) [i686-linux] installed using Ruby Version Manager.

For all I got why this is happening.

Great thanks for all explanations.