Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: Include a dynamic module name

I have a situation in my Rails application where I need to include arbitrary modules depending on the current runtime state. The module provides custom application code that is only needed when certain conditions are true. Basically, I'm pulling the name of a company from the current context and using that as the filename for the module and its definition:

p =  self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
include self.class.const_get(self.user.company.subdomain.capitalize.to_sym)
    self.custom_add_url
end

My test module looks like this:

module Companyx
    def custom_add_url
    puts "Calling custom_add_url"   
end 
end

Now in the console, this actually works fine. I can pull a user and include the module like so:

[1] pry(main)> c = Card.find_by_personal_url("username")
[2] pry(main)> include c.class.const_get(c.user.company.subdomain.capitalize)=> Object
[3] pry(main)> c.custom_add_url

Calling custom_add_url

If I try to run the include line from my model, I get

NoMethodError: undefined method `include' for #<Card:0x007f91f9094fb0>

Can anyone suggest why the include statement would work on the console, but not in my model code?

like image 709
Aaron Vegh Avatar asked Apr 27 '12 13:04

Aaron Vegh


2 Answers

I'm doing a similar thing. I found this answer useful: How to convert a string to a constant in Ruby?

Turns out I was looking for the constantize method. This is the line I'm using in my code:

include "ModuleName::#{var.attr}".constantize

Edit:

So ack, I ran into various problems with actually using that line myself. Partially because I was trying to call it inside a method in a class. But since I'm only calling one method in the class (which calls/runs everything else) the final working version I have now is

"ModuleName::#{var.attr}".constantize.new.methodname

Obviously methodname is an instance method, so you could get rid of the new if yours is a class method.

like image 105
undefinedvariable Avatar answered Nov 17 '22 17:11

undefinedvariable


Include is a method on a class.

If you want to call it inside a model, you need to execute the code in the context of its singleton class.

p =  self.user.company.subdomain + ".rb"
if File.exists?(Rails.root + "lib/" + p)
myself = self
class_eval do
  include self.const_get(myself.user.company.subdomain.capitalize.to_sym)
end
self.custom_add_url

EDIT:

class << self doesn't accept a block; class_eval does, hence it preserves the state of local variables. I've modified my solution to use it.

like image 31
Joe Pym Avatar answered Nov 17 '22 16:11

Joe Pym