class_eval and using a block instead of a String?

M

Mariano Kamp

Hi,

given the following contrived example I am wondering how to use a
block with class_eval instead of using the string like in the code
below?!

Any ideas?

Cheers,
Mariano

require 'test/unit'
include Test::Unit::Assertions

class Base
class << self
def enhance(*attributes)
class_eval do
attr_reader *attributes
end

initializer = "def initialize(#{attributes.join(", ")})"
attributes.each {|a| initializer << "@#{a}=#{a}\n"}
initializer << "end"

# ------
class_eval initializer # <-------
# ------
end
end
end
class Derived < Base; end

Derived.enhance:)a, :b, :c)
d = Derived.new("a", "b", "c")

assert_equal "a", d.a
assert_equal "b", d.b
assert_equal "c", d.c
 
L

Luke Kanies

Hi,

given the following contrived example I am wondering how to use a
block with class_eval instead of using the string like in the code
below?!

Any ideas?

Cheers,
Mariano

require 'test/unit'
include Test::Unit::Assertions

class Base
class << self
def enhance(*attributes)
class_eval do
attr_reader *attributes
end

initializer = "def initialize(#{attributes.join(", ")})"
attributes.each {|a| initializer << "@#{a}=#{a}\n"}
initializer << "end"

# ------
class_eval initializer # <-------
# ------
end
end
end
class Derived < Base; end

Derived.enhance:)a, :b, :c)
d = Derived.new("a", "b", "c")

assert_equal "a", d.a
assert_equal "b", d.b
assert_equal "c", d.c

Something like thie following should work:

require 'test/unit'
include Test::Unit::Assertions

class Base
class << self
def enhance(*attributes)
attr_accessor *attributes

define_method:)initialize) do |*ary|
attributes.each do |a|
send(a.to_s + "=", ary.shift)
end
end
end
end
end
class Derived < Base; end

class Testing < Test::Unit::TestCase
def test_initialize
Derived.enhance:)a, :b, :c)
d = Derived.new("a", "b", "c")

assert_equal "a", d.a
assert_equal "b", d.b
assert_equal "c", d.c
end
end

I didn't really do what you want, because I'm not actually using
class_eval here at all, but the assertions pass, anyway, which I
assume is the real goal.

Note that I'm using attr_accessor here instead of attr_reader,
because it's much easier to set the attributes this way. You could
just about as easily use instance_variable_set or whatever the method
is, but I prefer this for my own code.

Note also that if you call enhance() multiple times on the same
class, you'll recreate the initialize() method each time, which is
probaby a bad thing; as long as this is just for testing, you should
be fine, but I'd be wary of putting this into production anywhere.

One of the things I love about ruby is I can usually use a block
instead of a string with the eval methods, but what I love even more
is being able to skip the eval methods entirely.
 
M

Mariano Kamp

Something like thie following should work:

require 'test/unit'
include Test::Unit::Assertions

class Base
class << self
def enhance(*attributes)
attr_accessor *attributes

define_method:)initialize) do |*ary|
attributes.each do |a|
send(a.to_s + "=", ary.shift)
end
end
end
end
end
class Derived < Base; end

class Testing < Test::Unit::TestCase
def test_initialize
Derived.enhance:)a, :b, :c)
d = Derived.new("a", "b", "c")

assert_equal "a", d.a
assert_equal "b", d.b
assert_equal "c", d.c
end
end

I didn't really do what you want, because I'm not actually using
class_eval here at all, but the assertions pass, anyway, which I
assume is the real goal.

Note that I'm using attr_accessor here instead of attr_reader,
because it's much easier to set the attributes this way. You could
just about as easily use instance_variable_set or whatever the
method is, but I prefer this for my own code.

Note also that if you call enhance() multiple times on the same
class, you'll recreate the initialize() method each time, which is
probaby a bad thing; as long as this is just for testing, you
should be fine, but I'd be wary of putting this into production
anywhere.

One of the things I love about ruby is I can usually use a block
instead of a string with the eval methods, but what I love even
more is being able to skip the eval methods entirely.

Hi Luke,

thanks. That should work for my example.

Well, generally speaking I love the idea that you can transform
one concept into another and if this works nicely it most of the time
looks elegant to me. So I was hoping that I just didn't get how to do
that with code in strings as opposed to blocks.

So it seems, that there are things that you can do with one of the
concepts but not with the other.

I am currently trying to figure out what the hard rules are on
when to use a String and when to use a block.

I like the String approach as it is pretty close to how you would
write the method by hand, but I hate about it that it is just heap of
characters. But the latter might be just me as my thoughts are often
still in the Java world, where you like to do things explicitly and
give the compiler/editor a chance to help you.

Coming back to the example it seems that the two approaches do not
provide the same functionality. When using the String approach the
parameters to the initialize method are explicit. On consequence is
that if I would call Derived.new ("a", "b") I would get an error
saying that I just provided two of three parameters. This doesn't
happen with the block approach. Of course I could always explicitly
code to check for the ary length, but this is kind of redundant.


Cheers,
Mariano
 
D

dblack

Hi --

Hi,

given the following contrived example I am wondering how to use a block with
class_eval instead of using the string like in the code below?!

Any ideas?

Cheers,
Mariano

require 'test/unit'
include Test::Unit::Assertions

class Base
class << self
def enhance(*attributes)
class_eval do
attr_reader *attributes
end

initializer = "def initialize(#{attributes.join(", ")})"
attributes.each {|a| initializer << "@#{a}=#{a}\n"}
initializer << "end"

# ------
class_eval initializer # <-------
# ------
end
end
end
class Derived < Base; end

Derived.enhance:)a, :b, :c)
d = Derived.new("a", "b", "c")

assert_equal "a", d.a
assert_equal "b", d.b
assert_equal "c", d.c

Here's one way:

class Base
class << self
def enhance(*attributes)
attr_accessor *attributes
define_method:)initialize) do |*args|
args.each_with_index {|a,i| send("#{attributes}=", a) }
end
end
end
end


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 

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

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top