Forum: Ruby Passing a block into a class_eval

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Clifford H. (Guest)
on 2007-02-17 04:25
(Received via mailing list)
Here's a function similar to attr_accessor, except it takes a block,
which is used to validate assignments to the attribute, and substitute
in a string value to be used as a key to the hash inside class_eval.

It's just ugly having to use a hash to store the block. class_eval and
friends should support saving parameters into global variables for the
duration? Or is there an obvious way of doing this that I've missed?
I removed some checking for simplicity.

Clifford H..

def validated_attr(*names, &block)
    begin
        Thread.critical = true
        $__VALIDATOR_BLOCK ||= {}
        while $__VALIDATOR_BLOCK.has_key?([r = (rand*16777216).to_i]);
end
        $__VALIDATOR_BLOCK[r] = block
    ensure
        Thread.critical = false
    end

    names.each{|name|
        class_eval <<-END
            def #{name}
                @#{name}
            end
            def #{name}=(__val)
                if !$__VALIDATOR_BLOCK[#{r.to_s}.to_i].call(__val)
                    throw "Invalid assignment of \#{__val.inspect} to
#{name}"
                end
                @#{name} = __val
            end
        END
    }
end

class Foo
    validated_attr(:bar) {|v| v.kind_of?(Integer) &&
(0..14).include?(v)}
end

f = Foo.new

begin
    f.bar = 2       # Ok
rescue => e
    puts e.to_s
end

begin
    f.bar = 15      # Not ok
rescue => e
    puts e.to_s
end

begin
    f.bar = "Hi there"  # Not ok
rescue => e
    puts e.to_s
end
Austin Z. (Guest)
on 2007-02-17 04:50
(Received via mailing list)
On 2/16/07, Clifford H. <removed_email_address@domain.invalid> wrote:
> Here's a function similar to attr_accessor, except it takes a block,
> which is used to validate assignments to the attribute, and substitute
> in a string value to be used as a key to the hash inside class_eval.

Ruby 1.9 accepts blocks as parameters to blocks.

-austin
Clifford H. (Guest)
on 2007-02-17 04:55
(Received via mailing list)
Austin Z. wrote:
> Ruby 1.9 accepts blocks as parameters to blocks.

I can't immediately see how that solves this problem...?
I need to create a string to eval so I can fabricate the
method names, and I want a block to be available within
that eval. Can you show a quick example?
unknown (Guest)
on 2007-02-17 05:16
(Received via mailing list)
Hi --

On Sat, 17 Feb 2007, Clifford H. wrote:

> Austin Z. wrote:
>> Ruby 1.9 accepts blocks as parameters to blocks.
>
> I can't immediately see how that solves this problem...?
> I need to create a string to eval so I can fabricate the
> method names, and I want a block to be available within
> that eval. Can you show a quick example?

I guess my first take on it would be this:

   class Module
     def validated_attr(*names, &block)
       names.each do |name|
         define_method(name) { instance_variable_get("@#{name}") }
         define_method("#{name}=") {|val|
           unless block.call(val)
             raise "Invalid assignment of #{val.inspect} to #{name}."
           end
         }
       end
     end
   end

(using define_method rather than def so that the local variable block
will still be in scope).


David
Clifford H. (Guest)
on 2007-02-17 05:35
(Received via mailing list)
removed_email_address@domain.invalid wrote:
> I guess my first take on it would be this:
> (using define_method rather than def so that the local variable block
> will still be in scope).

Excellent! I added the missing instance_variable_set and I'm away.
My full example has type checking, and whether nil is allowed.
Oh, and type-checked array attributes in the same fashion.
Will post soon... or is there an existing package to add this to?

class Foo
    MAX = 14
    typed_attr Integer, nil, :foo {|v| v <= MAX }
    array_attr Range, :bar {|v| v.end+(v.exclude_end? ? 0 : 1) < MAX }
end

Clifford H..
Clifford H. (Guest)
on 2007-02-17 05:55
(Received via mailing list)
Of course, that should be:

class Foo
   MAX = 14
   typed_attr(Integer, nil, :foo) {|v| v <= MAX }
   array_attr(Range, :bar) {|v| v.end+(v.exclude_end? ? 0 : 1) < MAX }
end

Can't use a block without parenthesizing the params...
unknown (Guest)
on 2007-02-17 18:16
(Received via mailing list)
Hi --

On Sat, 17 Feb 2007, Clifford H. wrote:

> removed_email_address@domain.invalid wrote:
>> I guess my first take on it would be this:
>> (using define_method rather than def so that the local variable block
>> will still be in scope).
>
> Excellent! I added the missing instance_variable_set and I'm away.

Whoops, I knew it looked too short :-)

> My full example has type checking, and whether nil is allowed.

I'll bet you mean class checking :-)  But that's another (long)
story....


David
unknown (Guest)
on 2007-02-18 01:43
(Received via mailing list)
Hi --

On Sat, 17 Feb 2007, Clifford H. wrote:

> Of course, that should be:
>
> class Foo
>  MAX = 14
>  typed_attr(Integer, nil, :foo) {|v| v <= MAX }
>  array_attr(Range, :bar) {|v| v.end+(v.exclude_end? ? 0 : 1) < MAX }
> end
>
> Can't use a block without parenthesizing the params...

You can, actually:

   irb(main):013:0> def x(*args); p args; yield; end
   => nil
   irb(main):014:0> x 1,2 do puts "hi" end
   [1, 2]
   hi

:-)


David
Clifford H. (Guest)
on 2007-02-18 06:10
(Received via mailing list)
removed_email_address@domain.invalid wrote:
>> My full example has type checking, and whether nil is allowed.
> I'll bet you mean class checking :-)  But that's another (long)
> story....

It is. class checking is a limited form of type checking however, and
you can omit class checking and/or let the block can do whatever extra
type checking is needed. :-) It's nice to automatically allow/disallow
assignment of nil also.

Thanks for the note on do...end, I almost never use it, but this is a
rare case where having the same feature at a different precedence is
a good idea.

This is ready to be made a gem when I've done the unit tests. I think
I'll call it "checked". I use the "method as superclass" magic to
create checked subclasses of Array, and support both simple attributes
(with a default value) and array attributes. Hmm, perhaps I should do
hashes also... later :-).

Clifford H..
unknown (Guest)
on 2007-02-18 12:25
(Received via mailing list)
Hi --

On Sun, 18 Feb 2007, Clifford H. wrote:

> Thanks for the note on do...end, I almost never use it, but this is a
> rare case where having the same feature at a different precedence is
> a good idea.

I need to stash that example away as it's rare enough that I never
seem to be able to remember an example when people ask me....

> This is ready to be made a gem when I've done the unit tests. I think
> I'll call it "checked".

How about "chattr"? :-)


David
Clifford H. (Guest)
on 2007-03-01 20:01
(Received via mailing list)
removed_email_address@domain.invalid wrote:
>> This is ready to be made a gem when I've done the unit tests. I think
>> I'll call it "checked".
> How about "chattr"? :-)

Thanks for the suggestion.

Chattr is in Rubyforge now, with RDoc, (new) RSpec tests, all tied up
with a nice Rakefile to build the gem and test it. Only SVN access,
I'll get a proper release file and announcement up in the next few
days. You might like a sneak peek though :-).

Clifford H..
David C. (Guest)
on 2007-03-01 21:44
(Received via mailing list)
On 3/1/07, Clifford H. <removed_email_address@domain.invalid> wrote:
> removed_email_address@domain.invalid wrote:
> >> This is ready to be made a gem when I've done the unit tests. I think
> >> I'll call it "checked".
> > How about "chattr"? :-)
>
> Thanks for the suggestion.
>
> Chattr is in Rubyforge now, with RDoc, (new) RSpec tests

Right on! Nice to see someone using the rspec-0.8 syntax only a day
after its release. And, if I might say so, "nice specs". Take it as
you will.

Cheers,
David
David C. (Guest)
on 2007-03-01 21:48
(Received via mailing list)
On 3/1/07, David C. <removed_email_address@domain.invalid> wrote:
> Right on! Nice to see someone using the rspec-0.8 syntax only a day
> after its release. And, if I might say so, "nice specs". Take it as
> you will.
>
> Cheers,
> David

Actually - I see that chattr_spec.rb is in the test directory. If you
make that a spec directory instead, then tinderbox will, in theory,
know what to do with it.

Also - the syntax you're using won't work with anything before 0.8.0,
so I'd make the gem statements reflect that instead of "> 0".

gem 'rspec', ">= 0.8.0"

Cheers,
David
Clifford H. (Guest)
on 2007-03-02 06:25
(Received via mailing list)
David C. wrote:
>> Chattr is in Rubyforge now, with RDoc, (new) RSpec tests
> Right on! Nice to see someone using the rspec-0.8 syntax only a day
> after its release. And, if I might say so, "nice specs".

Thanks! The new Rspec makes nice tests easier :-).

I've done the gem version change, thanks, it'll get checked in after I
resolve the following things:

Do still need the runtest.rb at all if I rename the directory "test"?

What about the Rakefile entries for runtest?

How does this get into the gem repository - automatically? Otherwise...?

What about rubyforge "released files" - I should upload a pre-built gem
and release notes I assume?

Any comments on the RDoc?

I'm still getting the hang of this. I have some much bigger things in
the
offing, so I need to get it right. I'm just grabbing examples from
anywhere
and hacking 'til it seems to work... which isn't great. There should be
a
quick-start guide with an example that stays up-to-date with the
latest...

Clifford H..
Tom C. (Guest)
on 2007-03-03 01:14
(Received via mailing list)
On Fri, 2007-03-02 at 13:25 +0900, Clifford H. wrote:
> How does this get into the gem repository - automatically? Otherwise...?
>
> What about rubyforge "released files" - I should upload a pre-built gem
> and release notes I assume?

Yup, it'll get deployed to the main gem index when you release the gem
via the RubyForge "released files".

Yours,

Tom
This topic is locked and can not be replied to.