Jens
Jens

Reputation: 1397

Rails 3: Use 'before_create' within a module (for ActiveRecord models)

I am writing an application where many (but not all) ActiveRecord models have a hash column. This is populated on creation with a random MD5 hash, and used for referencing a single object instead of its ID. To achieve this, I have included the following module in the respective models, and use find_by_id_or_hash!() in all controllers instead of find:

module IdOrHashFindable

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

  module ClassMethods

    before_create :create_hash             ## <-- THIS FAILS

    # legacy. in use only until find by ID is phased out altogether
    def find_by_id_or_hash!(id_or_hash)
      id_or_hash.to_s.size >= 32 ? find_by_hash!(id_or_hash) : find(id_or_hash)
    end
  end

  def to_param; self.hash end
  def create_hash; self.hash = Support.create_hash  end

end

To keep things DRY, I would like to have the before_create call also inside the module. However, I keep getting either

undefined method `before_create' for IdOrHashFindable:Module

or

undefined method `before_create' for IdOrHashFindable::ClassMethods:Module

depending where I put it. This makes sense (after all, I am calling a function, not defining it), but I'd still like to know how this can be done. (I cannot overwrite before_create since there are other before_create invocations).

Also, for all models which have this module included, quite similar tests apply. How do I consistently test this feature? Do I write a custom describe ... end block and require it into each model_spec.rb where it applies? How do I pass the correct model to use without resorting to global variables?

Any ideas would be appreciated!

Upvotes: 2

Views: 2022

Answers (1)

ShiningRay
ShiningRay

Reputation: 1008

You must place class method invokation in class_eval or just directly invoke it like:

module IdOrHashFindable

  def self.included(base)
    base.extend(ClassMethods)
    base.before_create :create_hash
    # or
    base.class_eval do
      before_create :create_hash
    end
  end

end

because when you place the method in the module, it will directy invoke it as the module's method

Upvotes: 6

Related Questions