Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to meta program methods from a Class Constant array which is being referenced in a module?

I wrote a basic Eventable module that allows other Classes to create events for itself. All the Models have their own set of events such as a Message being sent or a User having a sign_in event. I would like to create an array of events as a constant at the class level and access that array inside the module to build helper methods for creating events like so:

class User
  EVENT_TYPES = ['sign_in', 'sign_out']
  include Eventable
end

class Message
  EVENT_TYPES = ['sent', 'opened']
  include Eventable
end

module Eventable
  extend ActiveSupport::Concern

  included do
    has_many :events, as: :eventable
    attr_accessor :metadata

    self::EVENT_TYPES.each do |event_type|
      define_method "create_#{event_type}_event" do |args={}|
        Event.create(eventable: self, event_type: event_type)
      end
    end
  end
end

The issue with this is: the methods are not being defined properly. It cannot reference the respective Model's constant EVENT_TYPES properly.

How can I access a class constant from insude a module?

like image 618
Robbie Guilfoyle Avatar asked Jul 28 '14 14:07

Robbie Guilfoyle


2 Answers

Using constant like that is not very rubyesque, I'd say. This is much more idiomatic (in rails, at least).

class Message
  include Eventable

  event_types :sent, :opened
end

And here's the implementation

module Eventable
  extend ActiveSupport::Concern

  module ClassMethods
    def event_types(*names)
      names.each do |event_type|
        define_method "create_#{event_type}_event" do |args={}|
          Event.create(eventable: self, event_type: event_type)
        end
      end
    end
  end
end
like image 152
Sergio Tulentsev Avatar answered Nov 16 '22 19:11

Sergio Tulentsev


Constants can be obtained by Module#const_get method. But I would not present the event types for individual classes as constants, but as either class methods or instance variables belonging to the class:

class User
  def self.event_types
    [:sign_in, :sign_out]
  end
end

# or

class User
  @event_types = [:sign_in, :sign_out]

  class << self
    attr_reader :event_types
  end
end

It seems that another answer expressed most of the remaining things I wanted to say. But one more thing: I would not name the module Eventable, but eg. HavingEvents or simply Events.

like image 44
Boris Stitnicky Avatar answered Nov 16 '22 20:11

Boris Stitnicky