10

I am trying to abstract some of the logic required for cropping images into a module so that its not messing up my models. The code is based on http://railscasts.com/episodes/182-cropping-images

module CroppableImage
  def croppable_image(*image_names)
    image_names.each do |image_name|

      define_method "#{image_name}_sizes" do
        { :cropped => read_attribute("#{image_name}_size").to_s, :large => "800x800>" }
      end

      define_method "#{image_name}_geometry" do |style|
        style ||= :original
        @geometry ||= {}
        @geometry[style] ||= Paperclip::Geometry.from_file(eval "#{image_name}.path(style)")
      end

      define_method "#{image_name}_aspect_ratio" do
        width, height = read_attribute("#{image_name}_size").split("x")
        width.to_f / height.to_f
      end

      private

        define_method "reprocess_#{image_name}" do
          eval "#{image_name}.reprocess!"
        end

    end
  end

end

To include this into my model it seems that I have to use extend. I thought extend was for including class methods. I am from a java background - I thought using extend basically created static method on the class.

class Website < ActiveRecord::Base
  extend CroppableImage
  croppable_image :logo, :footer_image

-- this works

It seems then that what I am really wanting is to create instance methods.

class Website < ActiveRecord::Base
  include CroppableImage
  croppable_image :logo, :footer_image

-- This throws the error "undefined method `croppable_image' for #"

Can anyone please explain whats going on and if I should be using include or extend in this case. Thanks guys

Aliaksei Kliuchnikau
  • 13,589
  • 4
  • 59
  • 72
Nick
  • 2,715
  • 4
  • 24
  • 35
  • 1
    `extend` adds the methods as class methods, while `include` adds the methods as instance methods. – zeacuss May 03 '12 at 12:13

2 Answers2

14

extend M internally is similar to class << self; include M; end - extend includes module into singleton class of an object (and makes instance methods of the module to be a singleton methods of a class you extend).

In your case you call croppable_image in the context of a class definition and thus croppable_image should be an instance method of a Class class or a singleton method of Website class.

This is why you should extend Website class with a module CroppableImage by using extend CroppableImage - it adds instance method croppable_image as a singleton method of Website class.

Aliaksei Kliuchnikau
  • 13,589
  • 4
  • 59
  • 72
  • I think I finally get it. croppable_image is a class method but the methods that are defined with define_method become instance methods. I just read http://stackoverflow.com/questions/302789/can-i-define-method-in-rails-models and it all makes a lot more sense now. Thanks for your help – Nick Mar 15 '12 at 08:31
  • @Nick, Yes, `define_method` is executed on the class `Website` and `define_method` adds *instance methods* of the class `Website`. – Aliaksei Kliuchnikau Mar 15 '12 at 08:35
7

you can use both logic together. Ruby has callbacks for extend and include Example of using included callback

module CroppableImage
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def bar
      puts 'class method'
    end
  end

  def foo
    puts 'instance method'
  end
end
Fivell
  • 11,829
  • 3
  • 61
  • 99