Forum: Ruby on Rails injecting image= method into model classes

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Jonathan L. (Guest)
on 2005-12-03 02:15
hi All,

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 = #Ruby:File

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.assignAttrib( 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 )

anywhere in their class definition to have the functionality added (with
any number of image fields each with their own size).

Can anyone out there provide the last piece of this puzzle?  If it is
provided, I will post the entire source for this module and it should
prove to be useful.

Regards,
Jonathan
Jonathan L. (Guest)
on 2005-12-03 02:23
Oops.  The thumbnailImage= method should have been defined as this:

def thumbnailImage=( file )
  write_attribute( 'thumbnailImage', ImageBlob.getBlob( file, 125 ))
end
Jonathan <> <. (Guest)
on 2005-12-04 03:39
Anyone have an idea?  A simple example of how to inject code into a
generic class would probably suffice.  Can you possibly do a mixin
dynamically and, if so, how?

Much thanks.
francois.beausoleil (Guest)
on 2005-12-04 06:28
(Received via mailing list)
Hi !

2005/12/3, Jonathan <removed_email_address@domain.invalid> 
<removed_email_address@domain.invalid.
<removed_email_address@domain.invalid>:
> Anyone have an idea?  A simple example of how to inject code into a
> generic class would probably suffice.  Can you possibly do a mixin
> dynamically and, if so, how?

Have you looked at FileColumn ?  It does what you want, except it uses
the file system.  If you were to create a way to store in the DB
instead, I'm sure Sebastian would include that in a future release.

See http://www.kanthak.net/opensource/file_column/

Bye !
skanthak (Guest)
on 2005-12-04 15:03
(Received via mailing list)
On 12/4/05, Jonathan <removed_email_address@domain.invalid> 
<removed_email_address@domain.invalid.
<removed_email_address@domain.invalid> wrote:
> Anyone have an idea?  A simple example of how to inject code into a
> generic class would probably suffice.  Can you possibly do a mixin
> dynamically and, if so, how?

well, I don't have to plug my file_column plugin anymore since
Francois was already so nice to do so :) However, you might want to
take a look at the plugin's source code as it does exactly this:
adding methods to a model dynamically.

Instead of creating strings and evaluating them, I would suggest you
take a look at Module#define_method that let's you dynamically define
methods using ruby's nice block syntax. A small example from
file_column, that defines the "image=" method. (where the name of the
attribute is stored in the local variable attr).

     define_method "#{attr}=" do |file|
        state = send(state_method).assign(file)
        instance_variable_set state_attr, state
        if state.options[:after_upload] and state.just_uploaded?
          state.options[:after_upload].each do |sym|
            self.send sym
          end
        end
      end

"state_method" is a Symbol that contains the name of another generated
method that will return an object that does all the heavy lifting for
file uploads/image manipulation.

As a last note I wanted to give the reason why I decided to store
files in the filesystem and not in the database: Simply put, rails'
support for large binary columns is very poor and inefficient
(somebody correct me please if that has changed since I last checked)
and since the filesystem is very good at storing files I decided to
leverage this.

Sebastian
johanatan <> (Guest)
on 2005-12-05 03:34
skanthak wrote:
> On 12/4/05, Jonathan <removed_email_address@domain.invalid> 
<removed_email_address@domain.invalid.
> <removed_email_address@domain.invalid> wrote:
>> Anyone have an idea?  A simple example of how to inject code into a
>> generic class would probably suffice.  Can you possibly do a mixin
>> dynamically and, if so, how?
>
> well, I don't have to plug my file_column plugin anymore since
> Francois was already so nice to do so :) However, you might want to
> take a look at the plugin's source code as it does exactly this:
> adding methods to a model dynamically.
>
> Instead of creating strings and evaluating them, I would suggest you
> take a look at Module#define_method that let's you dynamically define
> methods using ruby's nice block syntax. A small example from
> file_column, that defines the "image=" method. (where the name of the
> attribute is stored in the local variable attr).
>
>      define_method "#{attr}=" do |file|
>         state = send(state_method).assign(file)
>         instance_variable_set state_attr, state
>         if state.options[:after_upload] and state.just_uploaded?
>           state.options[:after_upload].each do |sym|
>             self.send sym
>           end
>         end
>       end
>
> "state_method" is a Symbol that contains the name of another generated
> method that will return an object that does all the heavy lifting for
> file uploads/image manipulation.
>
> As a last note I wanted to give the reason why I decided to store
> files in the filesystem and not in the database: Simply put, rails'
> support for large binary columns is very poor and inefficient
> (somebody correct me please if that has changed since I last checked)
> and since the filesystem is very good at storing files I decided to
> leverage this.
>
> Sebastian

Thank you very much. :)  define_method should do the trick.  In my case,
the client has requested that files be stored in the database to
eliminate file permission complexities.  How does your plugin work with
file permissions (i.e., which user owns the files and where are they
stored)?
johanatan <> < (Guest)
on 2005-12-05 03:59
I actually decided try class_eval first (which David Black suggested on
the Ruby list).  It worked, so I had no need to investigate further.  I
figured I'd share the results with everyone in case someone else can
find a use for this (it actually should be a fairly common task).

Here it is:

ImageBlob.rb
------------
require 'RMagick'
include Magick

class ImageBlob

  def ImageBlob.inject_attrib_assign_methods( attr_and_sizes_hash,
consumer_class )
    attr_and_sizes_hash.each {|attr_name, max_size|
      code = %{
        def #{attr_name}=( file )
          write_attribute( '#{attr_name}', ImageBlob.getBlob( file,
#{max_size} ))
        end
      }
      consumer_class.class_eval code }
  end

  def ImageBlob.getBlob( file, max_image_size )
    begin
      img = Image.from_blob( file.read ).first
      if not img
        raise
      end

      img.format = "JPG"

      if img.rows > max_image_size or img.columns > max_image_size
        if img.rows > img.columns
          factor = max_image_size / img.rows.to_f
        else
          factor = max_image_size / img.columns.to_f
        end

        img = img.scale( factor )
      end

      retVal = img.to_blob
      GC.start
      return retVal
    rescue
      return nil
    end
  end
end

To use the class, from your model do:

ImageBlob.inject_attrib_assign_methods( { "image" => 225,
"thumbnailImage" => 125 }, self )

--Jonathan
This topic is locked and can not be replied to.