Passing a block into a class_eval

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

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

Ruby 1.9 accepts blocks as parameters to blocks.

-austin

···

On 2/16/07, Clifford Heath <no@spam.please.net> 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.

--
Austin Ziegler * halostatue@gmail.com * http://www.halostatue.ca/
               * austin@halostatue.ca * You are in a maze of twisty little passages, all alike. // halo • statue
               * austin@zieglers.ca

Austin Ziegler 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?

Hi --

Austin Ziegler 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

···

On Sat, 17 Feb 2007, Clifford Heath wrote:

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf\)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

dblack@wobblini.net 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 Heath.

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

Hi --

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 :slight_smile:

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

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

David

···

On Sat, 17 Feb 2007, Clifford Heath wrote:

dblack@wobblini.net wrote:

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf\)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

Hi --

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

:slight_smile:

David

···

On Sat, 17 Feb 2007, Clifford Heath wrote:

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf\)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

dblack@wobblini.net wrote:

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

I'll bet you mean class checking :slight_smile: 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. :slight_smile: 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 Heath.

Hi --

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"? :slight_smile:

David

···

On Sun, 18 Feb 2007, Clifford Heath wrote:

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf\)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

dblack@wobblini.net 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"? :slight_smile:

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

>> 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"? :slight_smile:

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

···

On 3/1/07, Clifford Heath <no@spam.please.net> wrote:

dblack@wobblini.net wrote:

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

> >> 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"? :slight_smile:
>
> 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

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

···

On 3/1/07, David Chelimsky <dchelimsky@gmail.com> wrote:

On 3/1/07, Clifford Heath <no@spam.please.net> wrote:
> dblack@wobblini.net wrote:

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

David Chelimsky 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 Heath.

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

Yours,

Tom

···

On Fri, 2007-03-02 at 13:25 +0900, Clifford Heath 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?