views:

49

answers:

2

i've got a module that wants to use data provided by the class that included it - but at the class level, not the instance level.

the goal is to have class 'metadata' provided to a module that the class includes, so that the module can use the metadata during the included call.

this works:

module Bar
  def value
    @value
  end

  def baz
    puts "the value is: #{value}"
  end
end

module Foo
  def self.included(mod)
    mod.extend(Bar)
    mod.baz
  end
end

class MyClass
  @value = "my class defined this"
  include Foo
end

the output of this code is

the value is: my class defined this

i'm not sure if the use of @value is good or not... it seems odd to me that i require this to be set before the include Foo happens, not from a technical perspective (i know why it's required to be done in this order) but from an idiomatic or usability perspective.

... is there a better way / more idiomatic way of accomplishing this?

+2  A: 

If you really want to use the class metadata in the moment you're including a module, given the 'included' method runs on its own scope, it's best to have a class method providing the metadata to it.

Also, if the metadata is not going to be manipulated, its better to declare it as a constant.

module Bar
  def self.included(base)
    puts "the value is: #{base.metadata}"
  end
end

class MyClass
  VALUE = "MyClass metadata"

  def self.metadata
    VALUE
  end

  include Bar
end

class OtherClass
  VALUE = "OtherClass metadata"

  def self.metadata
    VALUE
  end

  include Bar
end

Of course you can declare the metadata anyway you want, as long as its accessible by a class method to your Module.

Also, its not common to do these kind of metadata manipulation in the module's 'included' method and the necessity of ordering your statements on the class level is a bit brittle, so you might want to try to find a different solution to your original problem instead.

Marcos Toledo
If you use a constant anyway, you don't need the accessor. Just use `base::VALUE` in your `included` hook.
Jörg W Mittag
+1  A: 

If you want to the class to pass an argument to the mixin, then why not use one of the Ruby constructs that actually does allow passing an argument?

class Object
  private

  def Bar(metadata)
    Module.new do
      include Bar

      define_singleton_method(:included) do |base|
        puts "the value is: #{metadata}"
      end
    end
  end
end

module Bar
  # put common behavior here
end

class MyClass
  include Bar 'MyClass metadata'
end

class OtherClass
  include Bar 'OtherClass metadata'
end

This is a pretty common idiom that is for example used by the delegate library in the stdlib.

Jörg W Mittag