Ruby: Include a dynamic module name - ruby

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?

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.

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.

Related

How to disable rspec's checking for access to double outside of original example?

A class double is created in a let which is accessed by multiple examples. For example:
let(:foo_klass) do
class_double('A::B::C::Foo')
end
Other unit tests use ObjectSpace.each_object(Class) to examine the classes to select, for example, all modules including another module:
def find_modules_including_module(target_module)
ObjectSpace.each_object(Module).select do |object|
# All Class'es are modules, but we are not interested in them, so we exclude them.
!object.is_a?(Class) \
&& \
object.ancestors.include?(target_module) \
&& \
!object.equal?(target_module)
end
end
It is only the unit tests that call find_modules_including_module that fail. The error looks something like this:
Failure/Error: object.ancestors.include?(target_module) \
#<ClassDouble(MyModule::MyClass) (anonymous)> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
I understand why failing on any access on the double class is almost always the right thing to do, but mine is a special case in which that behavior is problematic and IMO inappropriate.
Is there an option to suppress this temporarily? Or do you have another suggestion?
In response to #engineersmnky's comments, and to provide context, I am adding the following:
I am doing this whole exercise because I am using the SemanticLogger logging gem, which provides a Loggable module which provides a class method and an instance method named 'logger'. The instance method calls the class method (self.class.logger).
If a module (let's call it M) includes Loggable, and a class (let's call it C) includes M, then C will assimilate M's instance method as if it were its own. When it is called, it will try to get self.class.logger, but self is the C instance and not the module, and since there is no C class method logger, a NoMethodError is raised.
My solution is to add this to every module that includes Loggable:
def self.included(klass)
klass.class_exec do
include SemanticLogger::Loggable
end
end
This works, but there is a risk that in the future a new module will include Loggable but not this included implementation. I want to be able to detect this situation programmatically in a test. My current test looks like this:
specify 'all classes including modules that include Loggable include Loggable themselves' do
modules_including_loggable = modules_including_module(SemanticLogger::Loggable)
classes_including_those_modules = classes_including_modules(modules_including_loggable)
suspicious_classes = classes_not_having_ancestor(classes_including_those_modules, SemanticLogger::Loggable)
expect(suspicious_classes.map(&:name).sort.join("\n")).to eq('')
end
The methods used are defined as follows:
def modules_including_module(target_module)
ObjectSpace.each_object(Module).select do |object|
# All Class'es are modules, but we are not interested in them, so we exclude them.
!object.is_a?(Class) \
&& \
object.ancestors.include?(target_module) \
&& \
!object.equal?(target_module)
end
end
def classes_including_modules(modules)
ObjectSpace.each_object(Class).select do |klass|
(klass.ancestors & modules).any?
end
end
def classes_not_having_ancestor(classes, ancestor)
classes.select do |klass|
!klass.ancestors.include?(ancestor)
end
end

Ruby reflection composition: call original method from redefined method

A bit of context first
I have a class Phone that defines a method advertise like this:
class Phone
def advertise(phone_call)
'ringtone'
end
end
I would like to have some adaptations for this method.
For example when the user is in a quiet environment, the phone should vibrate and not ring.
To do so, I define modules like
module DiscreetPhone
def advertise_quietly (phone_call)
'vibrator'
end
end
Then my program can do
# add the module to the class so that we can redefine the method
Phone.include(DiscreetPhone)
# redefine the method with its adaptation
Phone.send(:define_method, :advertise, DiscreetPhone.instance_method(:advertise_quietly ))
Of course for this example I hardcoded the class and module's name but they should be parameters of a function.
And so, an execution example would give:
phone = Phone.new
phone.advertise(a_call) # -> 'ringtone'
# do some adaptation stuff to redefine the method
...
phone.advertise(a_call) # -> 'vibrator'
Finally coming to my question
I want to have an adaptation that call the original function and append something to its result. I would like to write it like
module ScreeningPhone
def advertise_with_screening (phone_call)
proceed + ' with screening'
end
end
But I don't know what the proceed call should do or even where should I define it.
I'm using Ruby 2.3.0 on Windows.
proceed could be replaced by something else but I'd like to keep it as clean as possible in the module that defines the adaptation.
You can do this by prepending your module instead of including it.
Instead of using define_method as a sort of ersatz alias_method, just call the method advertise in your modules too.
Within your advertise method, you can call super to call up the inheritance hierarchy.
In my opinion, this approach is way too complex, and an inappropriate use of Modules.
I recommend thinking about a simpler way to implement this.
One simple way is to just include all the methods in the Phone class.
Or, you could use a hash as a lookup table for ring strategies:
class Phone
attr_accessor :ring_strategy
RING_STRATEGIES = {
ringtone: -> { ring_with_tone },
discreet: -> { ring_quietly },
screening: -> { ring_with_tone; ring_screening_too }
# ...
}
def initialize(ring_strategy = :ringtone)
#ring_strategy = ring_strategy
end
def ring
RING_STRATEGIES[:ring_strategy].()
end
end

Calling a Volt Framework Task method from another Task

I have a Volt Framework Task that checks and stores information on a directory, e.g.
class DirectoryHelperTask < Volt::Task
def list_contents()
contents = []
Dir.glob("/path/to/files").each do |f|
contents << f
end
return contents
end
end
I would like to call this from a different task, e.g.
class DirectoryRearrangerTask < Volt::Task
dir_contents = DirectoryHelperTask.list_contents()
end
The code above (DirectoryRearranger) throws an error, as does a promise call
DirectoryHelperTask.list_contents().then do |r|
dir_conents = r
end.fail do |e|
puts "Error: #{e}"
end
Could not find a way to call a task from another task in the Volt Framework documentation.
Thanks a lot!
From what I gather, tasks are meant to be run on the server side and then called on the client side, hence the use of the promise object. The promise object comes from OpalRb, so trying to call it from MRI won't work. If you have a "task" that will only be used on the server side, then it doesn't really fit with Volt's concept of a task.
Your first approach to the problem actually does work, except that DirectoryRearrangerTask can't inherit from Volt::Task.
directory_helper_task.rb
require_relative "directory_rearranger_task"
class DirectoryHelperTask < Volt::Task
def list_contents
contents = []
Dir.glob("*").each do |file|
contents << file
end
DirectoryRearrangerTask.rearrange(contents)
contents
end
end
directory_rearranger_task.rb
class DirectoryRearrangerTask
def self.rearrange(contents)
contents.reverse!
end
end
Here is a GitHub repo with my solution to this problem.
You can call tasks from the client or server, but keep in mind that you call instance methods on the class. (So they get treated like singletons) And all methods return a Promise. I think your issue here is that your doing dir_contents = DirectoryHelperTask.list_contents() inside of the class. While you could do this in ruby, I'm not sure its what you want.
Also, where you do dir_contents = r, unless dir_contents was defined before the block, its going to get defined just in the block.

How to load file in object context

I'm playing with some meta-programming concepts and wonder if something I want to do is simply possible.
There's simple DLS for events,
//test_events.rb
event 'monthly events are suspiciously high' do
true
end
and the script should shout out when event returns true, I try to do this without polluting global namespace with method event, and any instance variables. So I try something like this:
Dir.glob('*_events.rb').each do |file|
MyClass = Class.new do
define_method :event do |name, &block|
#events[name] = block
end
end
env = MyClass.new
env.instance_eval{#events = {}}
env.instance_eval{load(file)}
end
So for each *_events.rb file I would like to load it in context of MyClass (i know that with 2nd loop of Dir.glob#each it will complain about already defined const - not important now).
The problem is with env.instance_eval{load(file)} code in test_events.rb is run in Object context, because I get
undefined method `event' for main:Object (NoMethodError)
Is there a way to do it? ( I try now in 1.9.3 but changing version up is not a problem since it's just exercise)
instance_eval can take a String as its argument instead of a block, so rather than load (which as you suggest will load the file in the top level) you need to read the file contents into a string to pass in, something like:
env.instance_eval(File.read(file))

Alternative initialize for a Class to avoid processing already known information

I have a class, Autodrop, that contains several methods , a.o. 'metadata', that call an external API (dropbox). They are slow.
However, I already often have that metadata around when initializing the AutodropImage, so I should make the methods smarter.
What I have in mind is this:
class Autodrop
include Dropbox
attr_reader :path
def initialize(path)
#path = path
end
def self.from_entry(drop_entry)
#drop_entry = drop_entry
self.initialize(#drop_entry.path)
end
def metadata
if #drop_entry = nil
return heavy_lifting_and_network_traffic
else
return #drop_entry.metadata
end
end
#...
end
Now, I would expect to call
entry = BarEntry.new()
foo = Autodrop.from_entry(entry)
foo.metadata
In order to avoid that heavy lifting and network traffic call.
But this does not work. And somehow, in all my newbieness, I am sure I am goind at this all wrong.
Is there a term I should look for and read about first? How would you go for this?
Note, that the examples are simplified: in my code, I inherit AutodropImage < Autodrop for example, which is called from withing AutodropGallery < Autodrop. The latter already knows all metadata for the AutodropImage, so I mostly want to avoid AutodropImage going over the heavy lifting again.
You are creating an instance variable #drop_entry in your class method from_entry and obviously it wont be available to your object that you are creating in this method. One workaround is to pass it as a parameter when you are initializing the class. It should work if you do the following modifications:
In your from_entry class method change
self.initialize(#drop_entry)
to
new(#drop_entry)
Modify initialize method to:
def initialize(drop_entry)
#drop_entry = drop_entry
#path = #drop_entry.path
end
Or if your class is tied up to pass only the path parameter, ie. you dont want to change the other existing code then you can use an optional parameter drop entry like so
def initialize(path, drop_entry=nil)
You would need to cache the metadata in a class variable.
Edit: Or in a class level instance variable.
Maybe this read will help: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

Resources