Class_eval and using a block instead of a String?

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.

···

On Dec 19, 2006, at 2:47 PM, Mariano Kamp wrote:

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

  --
  Some people are afraid of heights. I'm afraid of widths.
                  -- Stephen Wright
  ---------------------------------------------------------------------
  Luke Kanies | http://reductivelabs.com | http://madstop.com

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
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[i]}=", a) }
         end
       end
     end
   end

David

···

On Wed, 20 Dec 2006, Mariano Kamp wrote:

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

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

···

On Dec 19, 2006, at 10:36 PM, Luke Kanies wrote:

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.