Passing a block into a class_eval


#1

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


#2

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


#3

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?


#4

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…


#5

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


#6

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


#7

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…


#8

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

:slight_smile:

David


#9

removed_email_address@domain.invalid 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 H…


#10

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”? :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 H…


#11

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

David


#12

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”? :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


#13

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…


#14

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


#15

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