From: "David A. Black" Date: 2005-12-04T13:34:54+09:00 Subject: Re: injecting dynamic methods into a class Hi -- On Sun, 4 Dec 2005, johanatan wrote: > hi All, > > I posted this message to the Ruby on Rails group yesterday, but I > suppose it belongs here instead. > > I've been trying to wrestle dynamic code generation into place for a > week now with no success. What I want is a module or class (started > trying to use module but finished up trying to use a class) which can > inject a method into its consumer which will allow: > > user.image = # > > as you would get from a file upload. (This eliminates any need for > special image handling code in the view or controller and lets the image > field act like any other). It uses the RMagick library to convert all > images to jpg's and resize them to a consumer-defined size. What I was > hoping the group could help with was moving all the code out of the > model and into my ImageBlob class (which will inject necessary code into > the model (i.e., consumer)). Here's what I currently have (which > works): > > model.rb > ---------- > def image=( file ) > write_attribute( 'image', ImageBlob.getBlob( file, 225 )) > end > def thumbnailImage=( file ) > write_attribute( 'thumbnailImage', ImageBlob.getBlob( file, 125 > )) > end > > ImageBlob.rb > ------------ > require 'RMagick' > include Magick > > class ImageBlob > def ImageBlob.getBlob( file, maxImageSize ) > #omitted code which resizes and reformats image and returns blob > end > end > > I was hoping to have an initialize function in ImageBlob which would > take the consumer class and inject the necessary methods into it (image= > and thumbnailImage=). Here's my best attempt at that (which fails for > an unknown reason): > > def initialize( attributesAndSizesHash, consumerClass ) > attributesAndSizesHash.each_pair {|attributeName, maxImageSize| > code = "class #{consumerClass} > def #{attributeName}=( file ) > write_attribute( '#{attributeName}', ImageBlob.getBlob( > file, #{maxImageSize} )) > end > end" > eval( code ) } > end > > I also tried making 'code' define a module and calling > consumerClass.extend( moduleName ). Neither of these approaches are > working. > > Of course, if this implemention could work, then consumers of the > ImageBlob class could simply call: > > ImageBlob.new( { "image" => 225, "thumbnailImage" => 125 }, self ) > > in their class definition to have the functionality added (with > any number of image fields each having their own size). > > Can anyone out there provide the last piece of this puzzle? I don't know if this is an exact fit, but I've tried to model it on what your example. The basic idea here is to call class_eval on the class that's passed in, and proceed from there (defining the methods). class ImageBlob def self.get_blob(*args) puts "In ImageBlob.get_blob" end end class MyClass def initialize(attrs_and_sizes, klass) attrs_and_sizes.each_pair do |attr_name,max_image_size| klass.class_eval do define_method("#{attr_name}=") do |file| write_attribute(attr_name, ImageBlob.get_blob(file,max_image_size)) end end end end end class OtherClass def write_attribute(*args) puts "In write_attribute" end end m = MyClass.new({"a" => 10, "b" => 20}, OtherClass) OtherClass.new.a = 2 (If you're passing in a classname string, rather than a class, you'd also want to do some variant of const_get to get the class itself before class_eval'ing from it.) David Black