I'm trying to write a method that tells me every class that includes a particular Module. It looks like this -
def Rating.rateable_objects
rateable_objects = []
ObjectSpace.each_object(Class) do |c|
next unless c.include? Rateable
rateable_objects << c
end
rateable_objects
end
Where "Rateable" is my module that I'm including in several models.
What i'm finding is that this method return [] if i call it immediately after booting rails console or running the server. But if i first instantiate an instance of one of the consuming models it will return that model in the result.
So when do modules get included? I'm guessing later in the process than when he app starts up. If i can't get this information this way early in the process, is there anyway to accomplish this?
They are included when the include statement is executed when the class is defined. Rails autoloads modules/classes when you first use them. Also, you might try something like this:
module Rateable
#rateable_object = []
def self.included(klass)
#rateable_objects << klass
end
def rateable_objects
#rateable_objects
end
end
This will build the list as classes include the module.
Related
I want to be able to do this:
class IncludingClass
include IncludedModule
end
module IncludedModule
self.parent_class # => IncludingClass, I wish
end
Any ideas? Sorry for the brevity. Typing this on a phone. At any rate, I've looked around and haven't been able to find this, which seems surprising for such a met aprogrammable language.
I don't believe modules keep track of what included them. But they do fire a MyModule.included(SomeClass) method as a callback when they get included, so you can keep track yourself.
module IncludedModule
# Array to store included classes
##included_classes = []
# Called when this module is included.
# The including class is passed as an argument.
def self.included(base)
##included_classes << base
end
# Getter for class variable
def self.included_classes
##included_classes
end
end
# Include the module
class IncludingClass
include IncludedModule
end
# Ask the module what included it.
puts IncludedModule.included_classes #=> [IncludingClass]
There's probably also a way to crawl all Classes declared and ask them what they included via SomeClass.included_modules but that's kind of hairy, and would be much slower.
I have a class which I want other developers to be able to create modules to extend my class and give it more instance variables.
How can I automatically include modules in my class based on files in a particular folder? i.e. load all files and include them as modules for my class
How can I do this and somehow avoid method naming collisions?
I was thinking I could pass the name of the plugin in the initialize method, and do this:
class MyClass
def initialize(plugin_name=nil)
end
def method_missing(method_name)
"#{plugin_name}".send(method_name)
end
end
Would something like this work in theory, please help out in the method_missing as I have never written one before. I'm trying to basically specify a particular plugin name to avoid any name collisions.
Or should I, when including the plugins, output an error if there is a method collision?
Allowing external code to be melted in your class sounds dangerous and drives into names collisions as you guessed. It cans also drive into unexpected behavior, since you allow external code to mess with your class internals.
The best way to handle a plugin would be to encaspulate the behavior that would be replaced.
Let's take an example :
Given you're building a Logger, we can imagine that you can create plugins to specify where the logs will be wrote.
You can then create the following class :
class Logger
def initialize(store)
#store = store
end
def info(message)
#store.write(:info, message)
end
end
class StandardOutputStore
def write(level, message)
puts "#{level}: #{message}"
end
end
class FileStore
def initalize(filename)
#filename = filename
end
def write(level, message)
File.open(#filename, "a") do |file|
file.write("#{level}: #{message}")
end
end
end
Then you can you instanciate the right plugin this way :
logger = Logger.new(StandardOutputStore.new)
logger.warn "hello"
logger = Logger.new(FileStore.new("/tmp/log"))
logger.warn "hello"
This way of doing things provides modularity, more flexibility and is more solid than overloading things in your class with external code.
Loading modules as they are found in a directory is a matter of toying with Dir.glob and require. To explore what had been found under your plugin directory, you can force your users to write plugins in a module, like Foo::Plugins::FileStore and inspect which constants are present in the Foo::Plugins module after requiring the files. See http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-constants for more informations.
Edit: As you don't have any interface to map to since you provides the methods to an ERB template, you could do the following :
Plugins are in the following form :
module Plugins
module Emoticons
def smile
":-)"
end
end
end
You can then load them with the following :
Dir["/plugins/*.rb"].each {|file| require file }
And include them, given TemplateMethods is a module included in the context of your ERB template.
Plugins.constants.each do |constant|
mod = Plugins.const_get constant
TemplateMethods.send(:include, mod)
end
Using send isn't very elegant, I'd suggest building a class method on TemplateMethods that encapsulate the whole thing, like TemplateMethods.load_plugins_method! The bang here would warn about the intrusive action done by the dynamic modules inclusion.
That should do the job :)
I'm having some difficulty with referring to module-level variables in ruby. Say I have a situation like this, where I'm referring to M.a internally:
module M
##a=1
def self.a
##a
end
class A
def x
M.a
end
end
end
Now, this example works fine for me but it is failing in a slightly more complicated context (where the module is spread over a number of files installed in a local gem - but my understanding is that that should not effect the way the code is executed) with an error like this: undefined method `a' for M::M (NoMethodError).
So, is this the correct way to refer to module level variables in context? is there a simpler/more idiomatic way?
If the module is spread out over other files, you need to ensure that your initialization is run before the method is called. If they are in the same file, this should be as much as guaranteed, but if you somehow split them there could be trouble.
I've found you can usually get away with this:
module M
def self.a
#a ||= 1
end
end
If this variable is subject to change, you will need a mutator method. Rails provides mattr_accessor that basically does what you want, part of ActiveSupport.
UPDATE TO QUESTION
Here is what I have done based on some research and findings.
STEP 1 - I have this module in my Rails 3 project and place it in my lib folder
# lib/enumerable.rb
module Enumerable
def sum
return self.inject(0){|acc,i|acc +i}
end
def average
return self.sum/self.length.to_f
end
def sample_variance
avg=self.average
sum=self.inject(0){|acc,i|acc +(i-avg)**2}
return(1/self.length.to_f*sum)
end
def standard_deviation
return Math.sqrt(self.sample_variance)
end
end
STEP 2 - According to this blog article, in Rails 3 your lib folder will not get loaded automatically. In order to load this module you need to go to your config / application.rb and type this in:
config.autoload_paths += %W(#{config.root}/lib)
STEP 3 - Then in your model my understanding is you type this in to get the module picked up.
class MyModel < ActiveRecord::Base
include Enumerable
end
STEP 4 - I then try restart the rails server and try this out and I get false when I would expect it to be true.
MyModel.respond_to?('sample_variance')
# false, when it should be true
What am I doing wrong? Should I not be getting true?
Your inclusion of the main Enumerable module (not your extension) undoubtedly worked, and you can test it by simply checking for any of the methods that were mixed in. The problem is, your 'Include Enumerable' may not have included your file, but rather the main module.
One suggestion is to rename the file name for your extension, and have it loaded through an initializer with a
require 'my_enumerable.rb'
That way you for sure get both Enumerable and your extension to Enumerable loaded.
If I understand what you're driving at, you're trying to use Enumerable's sum method in ActiveRecord. You can do that by converting the current object to an array, then calling Enumerable's sum method on that array.
One more thing: you don't need to use return like you are using it. Ruby will return the last calculated thing from your method. You don't need to use self like that either -- in Ruby, self is the current object.
So if you have a method:
def charlie
inject{|i, j| i + j + 1}
end
and you call it like this:
(1..2).charlie
self is the current object (1..2).
The output will be 4, with no self or return.
I highly recommend Dave Thomas' lecture on Ruby metaprogramming, I tried to find it, but I could not, it's out there on the web somewhere.
You might want to take a look at this:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
You can include a module in a class, and thereby make that module's methods available to that class.
If you include Enumerable into a Rails model, then its methods would be available to that model. But since Enumerable's methods are already available to certain types of objects inside your Rails project, and those objects are available to be instantiated from inside your model, I don't see why you might do that, because Enumerable's methods are working just fine for the purposes they were designed.
Anyway, you might find that one of the following might work for you:
-- use Activerecord's sum method
-- convert your object to an array, and use Enumerable's sum method
-- write your own method, but don't call it sum, because you don't want to confuse yourself.
Try commenting out the second occurrence of module Neuone in the following snippet, and see what happens. Then try commenting out the Charlie.one method, and see what happens.
module Neuone
def one
'neuone one'
end
def two
'neuone two'
end
end
module Neuone
def two
'neuone two two'
end
end
class Charlie
include Neuone
def one
'charlie one'
end
end
c = Charlie.new
p c.one
p c.two
I encountered a problem when trying to test a module with Test::Unit. What I used to do is this:
my_module.rb:
class MyModule
def my_func
5 # return some value
end
end
test_my_module.rb:
require 'test/unit'
require 'my_module'
class TestMyModule < Unit::Test::TestCase
include MyModule
def test_my_func
assert_equal(5, my_func) # test the output value given the input params
end
end
Now the problem is, if my_module declares an initialize method, it gets included in the test class and this causes a bunch of problems since Test::Unit seems to override/generate an initialize method. So I'm wondering what is the best way to test a module?
I'm also wondering wether my module should become a class at this point since the initialize method is made for initializing the state of something. Opinions?
Thanks in advance !
Including an initialize method in a module feels very wrong to me, so I'd rethink that at the very least.
To answer your question about testing this as a module more directly, though, I would create a new, empty class, include your module in it, create an instance of that class, and then test against that instance:
class TestClass
include MyModule
end
class TestMyModule < Unit::Test::TestCase
def setup
#instance = TestClass.new
end
def test_my_func
assert_equal(5, #instance.my_func) # test the output value given the input params
end
end
Yeah, your initialize should definitely suggest that you're going towards a class. A module in ruby often feels like an interface in other languages, as long as you implement some basic things when you include the module you'll get a lot for free.
Enumerable is a great example, as long as you define [] and each when you include Enumerable you suddenly get pop, push, etc.
So my gut feeling about testing modules is that you should probably be testing classes that include the module rather than testing the module itself unless the module is designed to not be included in anything, it's simply a code storage mechanism.