user3868832
user3868832

Reputation: 620

Ruby on Rails, including a module with arguments

Is there a way to use arguments when including a ruby module? I have a module Assetable which is included across many classes. I want to be able to generate attr_accessor's on the fly.

module Assetable
  extend ActiveSupport::Concern

  included do 
    (argument).times do |i| 
      attr_accessor "asset_#{i}".to_sym
      attr_accessible "asset_#{i}".to_sym
    end
  end
end 

Upvotes: 21

Views: 14223

Answers (4)

inopinatus
inopinatus

Reputation: 3780

You can generate and include an anonymous module without polluting global namespaces:

module Assetable
  def self.[](argument)
    Module.new do
      extend ActiveSupport::Concern

      included do 
        (argument).times do |i| 
          attr_accessor :"asset_#{i}"
          attr_accessible :"asset_#{i}"
        end
      end
    end
  end
end

class Foo
  include Assetable[5]
end

Upvotes: 9

kinopyo
kinopyo

Reputation: 1687

There is a trick: making a class that's inheriting from a module so that you could pass any arguments to the module like class.

class Assetable < Module
  def initialize(num)
    @num = num
  end

  def included(base)
    num = @num

    base.class_eval do
      num.times do |i|
        attr_accessor "asset_#{i}"
      end
    end
  end
end

class A
  include Assetable.new(3)
end

a = A.new
a.asset_0 = 123
a.asset_0 # => 123

The details are blogged at http://kinopyo.com/en/blog/ruby-include-module-with-arguments, hope you'll find it useful.

Upvotes: 35

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369594

You can't pass arguments to a module. In fact, you can't pass arguments to anything except a message send.

So, you have to use a message send:

module Kernel
  private def Assetable(num)
    @__assetable_cache__ ||= []
    @__assetable_cache__[num] ||= Module.new do
      num.times do |i|
        attr_accessor   :"asset_#{i}"
        attr_accessible :"asset_#{i}"
      end
    end
  end
end

class Foo
  include Assetable 3
end

Note: I didn't see why you would need ActiveSupport::Concern here at all, but it's easy to add back in.

Upvotes: 6

Gabriel de Oliveira
Gabriel de Oliveira

Reputation: 1044

There is no way of passing arguments when including the module. The best next thing would be to define a class method that lets you create what you need afterwards:

module Assetable
  extend ActiveSupport::Concern
  module ClassMethods
    def total_assets(number)
      number.times do |i|
        attr_accessor "asset_#{i}"
        attr_accessible "asset_#{i}"
      end
    end
  end
end

class C
  include Assetable
  total_assets 3
end

o = C.new
o.asset_2 = "Some value."
o.asset_2  #=> "Some value."

Also be careful when overriding the included method within a concern because it's also used by ActiveSupport::Concern. You should call super within the overriden method in order to ensure proper initialization.

Upvotes: 16

Related Questions