Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass Arguments to Included Module in Ruby?

I'm hoping to implement something like all of the great plugins out there for ruby, so that you can do this:

acts_as_commentable
has_attached_file :avatar

But I have one constraint:

That helper method can only include a module; it can't define any variables or methods.

The reason for this is because, I want the options hash to define something like type, and that could be converted into one of say 20 different 'workhorse' modules, all of which I could sum up in a line like this:

def dynamic_method(options = {})
 include ("My::Helpers::#{options[:type].to_s.camelize}").constantize(options)
end

Then those 'workhorses' would handle the options, doing things like:

has_many "#{options[:something]}"

Here's what the structure looks like, and I'm wondering if you know the missing piece in the puzzle:

# 1 - The workhorse, encapsuling all dynamic variables
module My::Module
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      include InstanceMethods
    end
  end

  module InstanceMethods
    self.instance_eval %Q?
      def #{options[:my_method]}
        "world!"
      end
    ?
  end

  module ClassMethods
  end
end

# 2 - all this does is define that helper method
module HelperModule
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods

    def dynamic_method(options = {})
      # don't know how to get options through!
      include My::Module(options)
    end

  end
end

# 3 - send it to active_record
ActiveRecord::Base.send(:include, HelperModule)

# 4 - what it looks like
class TestClass < ActiveRecord::Base
  dynamic_method :my_method => "hello"
end

puts TestClass.new.hello #=> "world!"

That %Q? I'm not totally sure how to use, but I'm basically just wanting to somehow be able to pass the options hash from that helper method into the workhorse module. Is that possible? That way, the workhorse module could define all sorts of functionality, but I could name the variables whatever I wanted at runtime.

like image 241
Lance Avatar asked Mar 22 '10 08:03

Lance


People also ask

How do you pass an argument in Ruby?

Method arguments These can be called as the default argument type. When you don't know which type of argument to use, use the required arguments. They are order dependent and as the name suggests, required. If you don't pass them during method invocation Ruby will throw an ArgumentError .

Can a module include another module Ruby?

Actually, Ruby facilitates the use of composition by using the mixin facility. Indeed, a module can be included in another module or class by using the include , prepend and extend keywords.


1 Answers

I'm working on something similar, based on some clever ruby code I saw once but now can't find again. I have things almost working, but it messes up things like self.included and self.extended in your Module, for somewhat obvious reasons that could be worked around, but I don't like how complicated it would get. I think there's one trick I haven't thought of yet that would make this work more perfectly.

So this may or may not work for you, but what I'm trying is the idea of an anonymous module created dynamically based on your options. (I think 'type' may be a reserved word but I'm not sure, so let's say "options" instead). Check out this idea:


Module HelperModule
   def[](options)
      Module.new do 
        include HelperModule
        define_method(:options) { options }
      end
   end

   def options
     raise TypeError.new("You need to instantiate this module with [], man!")
   end
end

obj = Object.new
obj.extend(HelperModule)
obj.options => raises TypeError

obj = Object.new
obj.extend(HelperModule[ :my_option => "my_option" ]
obj.options => { my_option => "my_option }

Kind of neat, huh? But isn't quite good enough for me yet, because the actual Module you get from HelperModule[options] doesn't have the self.included and self.extended from the original HelperModule. I guess I could just define self.included and self.extended in the anon module, but I don't like the code confusion. I also don't like having to explicitly say "include HelperModule" in the anonymous module, I'd rather it be, like "self", except "self" doesn't mean the right thing there, so that doesn't work.

Ah, wait, guess what I just discovered: If this general approach seems like it should work for you, it can be supplied by the paramix gem: http://rubyworks.github.com/paramix/

like image 148
jrochkind Avatar answered Oct 14 '22 12:10

jrochkind