pierallard
pierallard

Reputation: 3371

Define dynamic class methods

I know this question has been asked several times, but I think never in this conditions.

I try to create an "helper" for my models (an acts_as), for automatically set a status from a status_id.

acts_as_statusable :status, [ :in_progress, closed ]

This helper creates methods status, status=(sym), in_progress?, closed? and named scopes in_progress and closed.

My helper work, this is its code put in lib/

class ActiveRecord::Base
  def self.acts_as_statusable(*args)
    field_name = args.first
    field_name_id = :"#{field_name}_id"
    statuses = args.second

    # Define getter
    define_method(field_name) do
      return nil if self.send(field_name_id).nil?
      return statuses[self.send(field_name_id)]
    end

    # Define setter
    define_method(:"#{field_name}=") do |v|
      return self.send(:"#{field_name_id}=", statuses.index(v))
    end

    # Define a status? for each status,
    # and a named scope .status
    statuses.each do |status|
      define_method(:"#{status}?") do
        return self.send(field_name) == status
      end
      scope status, -> { where(["#{table_name}.#{field_name_id} = ?", statuses.index(status)]) }
    end

    validates field_name_id, :inclusion => { :in => (0...statuses.length).to_a }
  end
end

Now, the problem

I need to have a Class method, for every class using acts_as_statusable, named status_index(sym) which returns the status_id from a status.

The problem is that every solutions I found for defining a class models are using Module, extend or intend, but I can not insert the statuses variable of the acts_as_statusable line into this modules...

How can I do this ? I use Rails 4.

Upvotes: 1

Views: 380

Answers (2)

xlembouras
xlembouras

Reputation: 8295

you can use class_eval, and class_variable_set

class_variable_set :"@@#{field_name}_options", statuses

class_eval <<-RUBY
  def self.status_index(sym)
    @@#{field_name}_options.index(sym)
  end
RUBY

which in your case will give a

@@status_options class variable
and a
status_index(sym)
method that returns the offset of the given status

Upvotes: 4

Vitalyp
Vitalyp

Reputation: 1089

xlembouras - your solution works great! I have two models:

class User < ActiveRecord::Base
  acts_as_statusable :status, [ :little, :big ]
end 

class Price < ActiveRecord::Base
  acts_as_statusable :status, [ :in_progress, :closed ]
end

'rails c' execution:

> User.status_index(:little)
=> 0 
User.status_index(:big)
=> 1 
> Price.status_index(:closed)
=> 1 
> Price.status_index(:in_progress)
=> 0 

I modified the structure, and used code written by ForgetTheNorm. Rails 4, ActiveSupport::Concern used

lib/active_record_extension.rb :

module ActiveRecordExtension
  extend ActiveSupport::Concern  
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)

config/initializers/extensions.rb :

require "active_record_extension"

config/initializers/active_record_monkey_patch.rb

class ActiveRecord::Base     
  def self.acts_as_statusable(*args)
    ...
    # ForgetTheNorm's CODE..
    ...
    # xlembouras CODE:
    class_variable_set :"@@#{field_name}_options", statuses

    class_eval <<-RUBY
      def self.status_index(sym)
        @@#{field_name}_options.index(sym)
      end
    RUBY
  end
end

Upvotes: 2

Related Questions