2

I have several models that share a concern. Each model passes in a hash, which is meant to handle minor differences in the way they use the concern. I pass the hash in through a class method like so:

add_update_to :group, :user

The full code for the concern is:

module Updateable
  extend ActiveSupport::Concern

  attr_accessor :streams

  module ClassMethods
    def add_updates_to(*streams)
      @streams = streams
    end
  end

  module InstanceMethods
    def update_streams
      @streams.collect{|stream| self.public_send(stream)}
    end
  end

  included do
    has_one :update, :as => :updatable

    after_create :create_update_and_history
  end

  private
    def create_update_and_history
      update = self.create_update(:user_id => User.current.id)
      self.update_streams.each do |stream|
        stream.histories.create(:update_id => update.id)
      end
    end
end

Most of this code works, but I'm having trouble passing the hash from the class to an instance. At the moment, I'm trying to achieve this effect by creating a virtual attribute, passing the hash to the attribute, and then retrieving it in the instance. Not only does this feel hacky, it doesn't work. I'm assuming it doesn't work because @streams is an instance variable, so the class method add_update_to can't actually set it?

Whatever the case, is there a better way to approach this problem?

1 Answer 1

4

You could probably use class variables here, but those are pretty reviled in the Ruby community due to their unpredictable nature. The thing to remember is that classes in Ruby are actually also instances of classes, and can have their own instance variables that are only accessible to themselves, and not accessible to their instances (if that is in any way clear).

In this case, you are defining behavior, and not data, so I think neither instance nor class variables are appropriate. Instead, I think your best bet is to define the instance methods directly within the class method, like this:

module Updateable
  extend ActiveSupport::Concern

  module ClassMethods
    def add_updates_to(*streams)
      define_method :update_streams do
        streams.collect {|stream| public_send(stream) }
      end
    end
  end
end

BTW, there is no hash involved here, so I'm not sure what you were referring to. *streams collects your arguments into an Array.

Sign up to request clarification or add additional context in comments.

2 Comments

Excellent response. It works, and thank you for taking the time to explain your answer. One thing I don't understand, though, is why you don't have to call self on public_send(stream). Is self somehow implied?
Yes, the self is often optional. In an instance method, any method call not explicitly sent to another object goes to self.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.