Passing a block into a class_eval

C

Clifford Heath

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
 
A

Austin Ziegler

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
 
C

Clifford Heath

Austin said:
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?
 
D

dblack

Hi --

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

--
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)
 
C

Clifford Heath

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

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

dblack

Hi --

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

--
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)
 
D

dblack

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

:)


David

--
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)
 
C

Clifford Heath

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

dblack

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


David

--
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)
 
C

Clifford Heath

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

David Chelimsky

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
 
D

David Chelimsky

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
 
C

Clifford Heath

David said:
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.
 
T

Tom Copeland

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,772
Messages
2,569,591
Members
45,103
Latest member
VinaykumarnNevatia
Top