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.