Extending a Ruby class with a standalone piece of

2019-05-01 11:59发布

问题:

I have a Rails app with several models with the same structure:

class Item1 < ActiveRecord::Base
  WIDTH = 100
  HEIGHT = 100
  has_attached_file :image, styles: {original: "#{WIDTH}x#{HEIGHT}"}
  validates_attachment :image, :presence => true
end


class Item2 < ActiveRecord::Base
  WIDTH = 200
  HEIGHT = 200
  has_attached_file :image, styles: {original: "#{WIDTH}x#{HEIGHT}"}
  validates_attachment :image, :presence => true
end

The actual code is more complicated but that's enough for simplicity.

I think I can put the common part of the code in one place and then use it in all models.

Here is what comes to my mind:

class Item1 < ActiveRecord::Base
  WIDTH = 100
  HEIGHT = 100
  extend CommonItem
end

module CommonItem
  has_attached_file :image, styles: {original: "#{WIDTH}x#{HEIGHT}"}
  validates_attachment :image, :presence => true
end

Obviously it doesn't work for two reasons:

  1. CommonItem has no idea about class methods I invoke.
  2. WIDTH and HEIGHT constants are looked up in CommonItem instead of Item1.

I tried to use include instead of extend, some ways of class_eval and class inheritance, but none work.

It seems I am missing something obvious. Please tell me what.

回答1:

Here's how I would do it:

class Model
  def self.model_method
    puts "model_method"
  end
end

module Item
  def self.included(base)
    base.class_eval do
      p base::WIDTH, base::HEIGHT
      model_method
    end
  end
end

class Item1 < Model
  WIDTH = 100
  HEIGHT = 100
  include Item
end

class Item2 < Model
  WIDTH = 200
  HEIGHT = 200
  include Item
end

The included method is called on a module when it's included.

I think I've managed to create a similar structure that your problem has. The module is calling the method inherited by the items classes from Model class.

Output:

100
100
model_method
200
200
model_method


回答2:

In Ruby, the construct to use to extract repeated code into a single unit is a method:

class Model
  def self.model_method
    p __method__
  end

  private

  def self.item
    p self::WIDTH, self::HEIGHT
    model_method
  end
end

class Item1 < Model
  WIDTH = 100
  HEIGHT = 100
  item
end

class Item2 < Model
  WIDTH = 200
  HEIGHT = 200
  item
end