Changing ruby method context / calling a method with instance_exec - ruby

First, for the short version:
Isn't a method definition just a block? Why can't I do something like:
obj.instance_exec(&other_obj.method(:my_method))
with the goal of running some module method in the context of an instance of a separate class? The method is called, but it doesn't seem to be executed in the context of 'obj', despite the 'instance_exec' call.
The only way I can figure out how to accomplish this is to wrap all of the code of 'my_method' in a proc, then call in the following manner instead:
obj.instance_eval(&other_obj.my_method)
but I'd like to avoid encapsulating all of my module methods in procs.
Now, for the long version:
I'm attempting to create a modularized external provider system, where for any given class/method (generally controller methods,) I can call a corresponding method for a given provider (e.g. facebook).
Since there could be multiple providers, the provider methods need to be namespaced, but instead of simply including a bunch of methods like, for example, 'facebook_invitation_create', I'd like my InvitationsController instance to have a facebook member containing a create method - e.g.
class InvitationsController < ApplicationController
def create
...
# e.g. self.facebook.create
self.send(params[:provider]).create
...
end
end
Furthermore, I'd like the provider methods to not only function as if they were part of the controller itself - meaning they should have access to things like controller instance variables, params, session, etc. - but also to be (mostly) written as if they were part of the controller itself - meaning without any complex additional code as a result of being modularized.
I've created a simplified example below, in which MyClass has a greet method, which if called with a valid provider name (:facebook in this case), will call that providers greet method instead. In turn, the provider greet method accesses the message method of the including class, as if it were part of the class itself.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
class << self
def greet
proc {
"#{message} from facebook!"
}
end
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
(provider.nil? or !self.respond_to?(provider)) ? message : instance_exec(&self.send(provider).greet)
end
end
This actually accomplishes almost everything I've previously stated, but I'm hung up on the fact that my provider functions need to be encapsulated in procs. I thought maybe I could simply call instance_exec on the method instead (after removing the proc encapsulation):
instance_exec(&self.send(provider).method(:greet))
...but then it seems like the instance_exec is ignored, as I get the error:
NameError: undefined local variable or method `message' for Providers::FacebookProvider:Module
Is there any way to call instance_exec on a defined method?
(I'm open to suggestions on how to better implement this as well...)

I think this is simpler than you might expect (and I realize that my answer is 2 years after you asked)
You can use instance methods from modules and bind them to any object.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
def greet
"#{message} from facebook!"
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
if provider
provider.instance_method(:greet).bind(self).call
else
message
end
end
end
If your provider is a module, you can user instance_method to create an UnboundMethod and bind it to the current self.
This is delegation.
It's the basis for the casting gem which would work like this:
delegate(:greet, provider)
Or, if you opt-in to using method_missing from casting, your code could just look like this:
greet
But you'd need to set your delegate first:
class MyClass
include Providers
include Casting::Client
delegate_missing_methods
attr_accessor :message
def initialize(message="hello", provider=facebook)
cast_as(provider)
self.message = message
end
end
MyClass.new.greet # => "hello from facebook!"
I wrote about what delegation is and is not on my blog which is relevant to understanding DCI and what I wrote about in Clean Ruby

Maybe I'm not following along, but it seems like you are making this harder than it needs to be.
Why not implement a "dispatch" pattern in your class, where you have a hash of provider names and provider methods {:facebook=>"facebook_greet"} and then just "send" the incoming call to the correct handler via "Object#send" (http://ruby-doc.org/core-1.9.3/Object.html#method-i-send)? Send is very fast for dispatching methods, so unlike eval, you should get great performance.
Here's some code to demonstrate the way I'd solve it (assuming I am following along with what you're trying to accomplish):
module TwitterProvider
def providerInit(providers)
#providers[:twitter]="twitter_greet"
super(providers) if defined?(super)
end
def twitter_greet
"Hello Twitter User"
end
end
module FacebookProvider
def providerInit(providers)
providers[:facebook]="facebook_greet"
super(providers) if defined?(super)
end
def facebook_greet
"Hello Facebook User"
end
end
class MyClass
include FacebookProvider
include TwitterProvider
attr_accessor :message
def providerInit(providers)
super(providers) if defined?(super)
end
def initialize(message="hello")
#providers = {}
self.message = message
providerInit(#providers)
end
def greet(provider=nil)
if provider.nil? or !self.respond_to?(#providers[provider])
self.message
else
self.send(#providers[provider])
end
end
end
my_class = MyClass.new
puts my_class.greet
puts my_class.greet(:twitter)
puts my_class.greet(:facebook)
# Output:
# hello
# Hello Twitter User
# Hello Facebook User

Related

Testing ruby superclass inherited functionality with rspec

Given that I have an abstract class which provides inherited functionality to subclasses:
class Superclass
class_attribute :_configuration_parameter
def self.configuration_parameter config
self._configuration_parameter = config
end
def results
unless #queried
execute
#queried = true
end
#results
end
private
# Execute uses the class instance config
def execute
#rows = DataSource.fetch self.class._configuration_parameter
#results = Results.new #rows, count
post_process
end
def post_process
#results.each do |row|
# mutate results
end
end
end
Which might be used by a subclass like this:
class Subclass < Superclass
configuration_parameter :foo
def subclass_method
end
end
I'm having a hard time writing RSpec to test the inherited and configured functionality without abusing the global namespace:
RSpec.describe Superclass do
let(:config_parameter) { :bar }
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
let(:test_instance) do
test_subclass.new
end
describe 'config parameter' do
it 'sets the class attribute' do
expect(test_subclass._configuration_parameter).to be(config_parameter)
end
end
describe 'execute' do
it 'fetches the data from the right place' do
expect(DataSource).to receive(:fetch).with(config_parameter)
instance.results
end
end
end
The real world superclass I'm mocking here has a few more configuration parameters and several other pieces of functionality which test reasonably well with this pattern.
Am I missing something obviously bad about the class or test design?
Thanks
I'm just going to jump to the most concrete part of your question, about how to avoid using a global variable to pass a local parameter to the dummy class instantiated in your spec.
Here's your spec code:
let(:test_subclass) do
# this feels like an anti-pattern, but the Class.new block scope
# doesn't contain config_parameter from the Rspec describe
$config_parameter = config_parameter
Class.new(Superclass) do
configuration_parameter $config_parameter
end
end
If you take the value returned from Class.new you can call configuration_parameter on that with the local value and avoid the global. Using tap does this with only a minor change to your existing code:
let(:test_subclass) do
Class.new(SuperClass).tap do |klass|
klass.configuration_parameter config_parameter
end
end
As to the more general question of how to test functionality inherited from a superclass, I think the general approach of creating a stub subclass and writing specs for that subclass is fine. I personally would make your _configuration_parameter class attribute private, and rather than testing that the configuration_parameter method actually sets the value, I'd instead focus on checking that the value is different from the superclass value. But I'm not sure that's in the scope of this question.

Using super with class_eval

I have an app that includes modules into core Classes for adding client customizations.
I'm finding that class_eval is a good way to override methods in the core Class, but sometimes I would like to avoid re-writing the entire method, and just defer to the original method.
For example, if I have a method called account_balance, it would be nice to do something like this in my module (i.e. the module that gets included into the Class):
module CustomClient
def self.included base
base.class_eval do
def account_balance
send_alert_email if balance < min
super # Then this would just defer the rest of the logic defined in the original class
end
end
end
end
But using class_eval seems to take the super method out of the lookup path.
Does anyone know how to work around this?
Thanks!
I think there are several ways to do what you're wanting to do. One is to open the class and alias the old implementation:
class MyClass
def method1
1
end
end
class MyClass
alias_method :old_method1, :method1
def method1
old_method1 + 1
end
end
MyClass.new.method1
=> 2
This is a form of monkey patching, so probably best to make use of the idiom in moderation. Also, sometimes what is wanted is a separate helper method that holds the common functionality.
EDIT: See Jörg W Mittag's answer for a more comprehensive set of options.
I'm finding that instance_eval is a good way to override methods in the core Class,
You are not overriding. You are overwriting aka monkeypatching.
but sometimes I would like to avoid re-writing the entire method, and just defer to the original method.
You can't defer to the original method. There is no original method. You overwrote it.
But using instance_eval seems to take the super method out of the lookup path.
There is no inheritance in your example. super doesn't even come into play.
See this answer for possible solutions and alternatives: When monkey patching a method, can you call the overridden method from the new implementation?
As you say, alias_method must be used carefully. Given this contrived example :
module CustomClient
...
host.class_eval do
alias :old_account_balance :account_balance
def account_balance ...
old_account_balance
end
...
class CoreClass
def old_account_balance ... defined here or in a superclass or
in another included module
def account_balance
# some new stuff ...
old_account_balance # some old stuff ...
end
include CustomClient
end
you end up with an infinite loop because, after alias, old_account_balance is a copy of account_balance, which now calls itself :
$ ruby -w t4.rb
t4.rb:21: warning: method redefined; discarding old old_account_balance
t4.rb:2: warning: previous definition of old_account_balance was here
[ output of puts removed ]
t4.rb:6: stack level too deep (SystemStackError)
[from the Pickaxe] The problem with this technique [alias_method] is that you’re relying on there not being an existing method called old_xxx. A better alternative is to make use of method objects, which are effectively anonymous.
Having said that, if you own the source code, a simple alias is good enough. But for a more general case, i'll use Jörg's Method Wrapping technique.
class CoreClass
def account_balance
puts 'CoreClass#account_balance, stuff deferred to the original method.'
end
end
module CustomClient
def self.included host
#is_defined_account_balance = host.new.respond_to? :account_balance
puts "is_defined_account_balance=#{#is_defined_account_balance}"
# pass this flag from CustomClient to host :
host.instance_variable_set(:#is_defined_account_balance,
#is_defined_account_balance)
host.class_eval do
old_account_balance = instance_method(:account_balance) if
#is_defined_account_balance
define_method(:account_balance) do |*args|
puts 'CustomClient#account_balance, additional stuff'
# like super :
old_account_balance.bind(self).call(*args) if
self.class.instance_variable_get(:#is_defined_account_balance)
end
end
end
end
class CoreClass
include CustomClient
end
print 'CoreClass.new.account_balance : '
CoreClass.new.account_balance
Output :
$ ruby -w t5.rb
is_defined_account_balance=true
CoreClass.new.account_balance : CustomClient#account_balance, additional stuff
CoreClass#account_balance, stuff deferred to the original method.
Why not a class variable ##is_defined_account_balance ? [from the Pickaxe] The module or class definition containing the include gains access to the constants, class variables, and instance methods of the module it includes.
It would avoid passing it from CustomClient to host and simplify the test :
old_account_balance if ##is_defined_account_balance # = super
But some dislike class variables as much as global variables.
[from the Pickaxe] The method Object#instance_eval lets you set self to be some arbitrary object, evaluates the code in a block with, and then resets self.
module CustomClient
def self.included base
base.instance_eval do
puts "about to def account_balance in #{self}"
def account_balance
super
end
end
end
end
class Client
include CustomClient #=> about to def account_balance in Client
end
As you can see, def account_balance is evaluated in the context of class Client, the host class which includes the module, hence account_balance becomes a singleton method (aka class method) of Client :
print 'Client.singleton_methods : '
p Client.singleton_methods #=> Client.singleton_methods : [:account_balance]
Client.new.account_balance won't work because it's not an instance method.
"I have an app that includes modules into core Classes"
As you don't give much details, I have imagined the following infrastructure :
class SuperClient
def account_balance
puts 'SuperClient#account_balance'
end
end
class Client < SuperClient
include CustomClient
end
Now replace instance_eval by class_eval. [from the Pickaxe] class_eval sets things up as if you were in the body of a class definition, so method definitions will define instance methods.
module CustomClient
...
base.class_eval do
...
print 'Client.new.account_balance : '
Client.new.account_balance
Output :
#=> from include CustomClient :
about to def account_balance in Client #=> as class Client, in the body of Client
Client.singleton_methods : []
Client.new.account_balance : SuperClient#account_balance #=> from super
"But using instance_eval seems to take the super method out of the lookup path."
super has worked. The problem was instance_eval.

ruby method_alias in inherited class

I'm deeping into ruby metaprogramming and have next question.
Example:
module ExampleAliaser
def do_example_alias(prefix=:origin)
class_eval <<-EOS
class << self
alias_method :#{prefix}_example, :example
def example
puts "in aliase will call :#{prefix}_example"
#{prefix}_example
end
end
EOS
end
end
class Example1
def self.example
puts "Example"
end
end
Example1.extend(ExampleAliaser)
class Example1
do_example_alias(:origin)
end
class Example2 < Example1
do_example_alias(:origin)
end
Example1.example
in aliase will call :origin_example
Example
=> nil
Example2.example
in aliase will call :origin_example
in aliase will call :origin_example
in aliase will call :origin_example
SystemStackError: stack level too deep
from /Users/igorfedoronchuk/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/irb/workspace.rb:80
Maybe IRB bug!!
So when mixin used 2 times it causes error.
What is the best way to fix such things? How to determine that mixing exists and remove it before new mixing
Follow the definition of methods to see why this is happening.
You first define Example1::example in the class definition of Example1. It writes a string to the console.
Then you extend ExampleAliaser. When you call Example1::do_example_alias, you then alias the method example to origin_example and redefine the method example to write a different string to the console and call origin_example.
Then you define the class Example2 to inherit from Example1, which now has two methods defined on it: origin_example and example. When you call Example2::do_example_alias, you alias the method example to origin_example. But remember that example was already redefined to call origin_example. So effectively, Example2::example will call itself until you run out of room on the stack.
If you want to avoid double-aliasing, you could include some kind of guard in do_example_alias:
def do_example_alias(prefix = :origin)
unless methods.include?("#{prefix}_example")
# do the aliasing
end
end
You can also undef :method_name in subclasses to remove methods that you no longer want defined.

How do I wrap the invocation of a Ruby method by including a module?

I want to be notified when certain things happen in some of my classes. I want to set this up in such a way that the implementation of my methods in those classes doesn't change.
I was thinking I'd have something like the following module:
module Notifications
extend ActiveSupport::Concern
module ClassMethods
def notify_when(method)
puts "the #{method} method was called!"
# additional suitable notification code
# now, run the method indicated by the `method` argument
end
end
end
Then I can mix it into my classes like so:
class Foo
include Notifications
# notify that we're running :bar, then run bar
notify_when :bar
def bar(...) # bar may have any arbitrary signature
# ...
end
end
My key desire is that I don't want to have to modify :bar to get notifications working correctly. Can this be done? If so, how would I write the notify_when implementation?
Also, I'm using Rails 3, so if there are ActiveSupport or other techniques I can use, please feel free to share. (I looked at ActiveSupport::Notifications, but that would require me to modify the bar method.)
It has come to my attention that I might want to use "the Module+super trick". I'm not sure what this is -- perhaps someone can enlighten me?
It has been quite a while since this question here has been active, but there is another possibility to wrap methods by an included (or extended) Module.
Since 2.0 you can prepend a Module, effectively making it a proxy for the prepending class.
In the example below, a method of an extended module module is called, passing the names of the methods you want to be wrapped. For each of the method names, a new Module is created and prepended. This is for code simplicity. You can also append multiple methods to a single proxy.
An important difference to the solutions using alias_method and instance_method which is later bound on self is that you can define the methods to be wrapped before the methods themselves are defined.
module Prepender
def wrap_me(*method_names)
method_names.each do |m|
proxy = Module.new do
define_method(m) do |*args|
puts "the method '#{m}' is about to be called"
super *args
end
end
self.prepend proxy
end
end
end
Use:
class Dogbert
extend Prepender
wrap_me :bark, :deny
def bark
puts 'Bah!'
end
def deny
puts 'You have no proof!'
end
end
Dogbert.new.deny
# => the method 'deny' is about to be called
# => You have no proof!
I imagine you could use an alias method chain.
Something like this:
def notify_when(method)
alias_method "#{method}_without_notification", method
define_method method do |*args|
puts "#{method} called"
send "#{method}_without_notification", args
end
end
You do not have to modify methods yourself with this approach.
I can think of two approaches:
(1) Decorate the Foo methods to include a notification.
(2) Use a proxy object that intercepts method calls to Foo and notifies you when they happen
The first solution is the approach taken by Jakub, though the alias_method solution is not the best way to achieve this, use this instead:
def notify_when(meth)
orig_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts "#{meth} called"
orig_meth.bind(self).call *args, &block
end
end
The second solution requires you to use method_missing in combination with a proxy:
class Interceptor
def initialize(target)
#target = target
end
def method_missing(name, *args, &block)
if #target.respond_to?(name)
puts "about to run #{name}"
#target.send(name, *args, &block)
else
super
end
end
end
class Hello; def hello; puts "hello!"; end; end
i = Interceptor.new(Hello.new)
i.hello #=> "about to run hello"
#=> "hello!"
The first method requires modifying the methods (something you said you didn't want) and the second method requires using a proxy, maybe something you do not want. There is no easy solution I'm sorry.

Ruby design pattern: How to make an extensible factory class?

Ok, suppose I have Ruby program to read version control log files and do something with the data. (I don't, but the situation is analogous, and I have fun with these analogies). Let's suppose right now I want to support Bazaar and Git. Let's suppose the program will be executed with some kind of argument indicating which version control software is being used.
Given this, I want to make a LogFileReaderFactory which given the name of a version control program will return an appropriate log file reader (subclassed from a generic) to read the log file and spit out a canonical internal representation. So, of course, I can make BazaarLogFileReader and GitLogFileReader and hard-code them into the program, but I want it to be set up in such a way that adding support for a new version control program is as simple as plopping a new class file in the directory with the Bazaar and Git readers.
So, right now you can call "do-something-with-the-log --software git" and "do-something-with-the-log --software bazaar" because there are log readers for those. What I want is for it to be possible to simply add a SVNLogFileReader class and file to the same directory and automatically be able to call "do-something-with-the-log --software svn" without ANY changes to the rest of the program. (The files can of course be named with a specific pattern and globbed in the require call.)
I know this can be done in Ruby... I just don't how I should do it... or if I should do it at all.
You don't need a LogFileReaderFactory; just teach your LogFileReader class how to instantiate its subclasses:
class LogFileReader
def self.create type
case type
when :git
GitLogFileReader.new
when :bzr
BzrLogFileReader.new
else
raise "Bad log file type: #{type}"
end
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
end
As you can see, the superclass can act as its own factory. Now, how about automatic registration? Well, why don't we just keep a hash of our registered subclasses, and register each one when we define them:
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
And there you have it. Just split that up into a few files, and require them appropriately.
You should read Peter Norvig's Design Patterns in Dynamic Languages if you're interested in this sort of thing. He demonstrates how many design patterns are actually working around restrictions or inadequacies in your programming language; and with a sufficiently powerful and flexible language, you don't really need a design pattern, you just implement what you want to do. He uses Dylan and Common Lisp for examples, but many of his points are relevant to Ruby as well.
You might also want to take a look at Why's Poignant Guide to Ruby, particularly chapters 5 and 6, though only if you can deal with surrealist technical writing.
edit: Riffing of off Jörg's answer now; I do like reducing repetition, and so not repeating the name of the version control system in both the class and the registration. Adding the following to my second example will allow you to write much simpler class definitions while still being pretty simple and easy to understand.
def log_file_reader name, superclass=LogFileReader, &block
Class.new(superclass, &block).register_reader(name)
end
log_file_reader :git do
def display
puts "I'm a git log file reader!"
end
end
log_file_reader :bzr do
def display
puts "A bzr log file reader..."
end
end
Of course, in production code, you may want to actually name those classes, by generating a constant definition based on the name passed in, for better error messages.
def log_file_reader name, superclass=LogFileReader, &block
c = Class.new(superclass, &block)
c.register_reader(name)
Object.const_set("#{name.to_s.capitalize}LogFileReader", c)
end
This is really just riffing off Brian Campbell's solution. If you like this, please upvote his answer, too: he did all the work.
#!/usr/bin/env ruby
class Object; def eigenclass; class << self; self end end end
module LogFileReader
class LogFileReaderNotFoundError < NameError; end
class << self
def create type
(self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
rescue NameError => e
raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
raise
end
def []=(type, klass)
#readers ||= {type => klass}
def []=(type, klass)
#readers[type] = klass
end
klass
end
def [](type)
#readers ||= {}
def [](type)
#readers[type]
end
nil
end
def included klass
self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
end
end
end
def LogFileReader type
Here, we create a global method (more like a procedure, actually) called LogFileReader, which is the same name as our module LogFileReader. This is legal in Ruby. The ambiguity is resolved like this: the module will always be preferred, except when it's obviously a method call, i.e. you either put parentheses at the end (Foo()) or pass an argument (Foo :bar).
This is a trick that is used in a few places in the stdlib, and also in Camping and other frameworks. Because things like include or extend aren't actually keywords, but ordinary methods that take ordinary parameters, you don't have to pass them an actual Module as an argument, you can also pass anything that evaluates to a Module. In fact, this even works for inheritance, it is perfectly legal to write class Foo < some_method_that_returns_a_class(:some, :params).
With this trick, you can make it look like you are inheriting from a generic class, even though Ruby doesn't have generics. It's used for example in the delegation library, where you do something like class MyFoo < SimpleDelegator(Foo), and what happens, is that the SimpleDelegator method dynamically creates and returns an anonymous subclass of the SimpleDelegator class, which delegates all method calls to an instance of the Foo class.
We use a similar trick here: we are going to dynamically create a Module, which, when it is mixed into a class, will automatically register that class with the LogFileReader registry.
LogFileReader.const_set type.to_s.capitalize, Module.new {
There's a lot going on in just this line. Let's start from the right: Module.new creates a new anonymous module. The block passed to it, becomes the body of the module – it's basically the same as using the module keyword.
Now, on to const_set. It's a method for setting a constant. So, it's the same as saying FOO = :bar, except that we can pass in the name of the constant as a parameter, instead of having to know it in advance. Since we are calling the method on the LogFileReader module, the constant will be defined inside that namespace, IOW it will be named LogFileReader::Something.
So, what is the name of the constant? Well, it's the type argument passed into the method, capitalized. So, when I pass in :cvs, the resulting constant will be LogFileParser::Cvs.
And what do we set the constant to? To our newly created anonymous module, which is now no longer anonymous!
All of this is really just a longwinded way of saying module LogFileReader::Cvs, except that we didn't know the "Cvs" part in advance, and thus couldn't have written it that way.
eigenclass.send :define_method, :included do |klass|
This is the body of our module. Here, we use define_method to dynamically define a method called included. And we don't actually define the method on the module itself, but on the module's eigenclass (via a small helper method that we defined above), which means that the method will not become an instance method, but rather a "static" method (in Java/.NET terms).
included is actually a special hook method, that gets called by the Ruby runtime, everytime a module gets included into a class, and the class gets passed in as an argument. So, our newly created module now has a hook method that will inform it whenever it gets included somewhere.
LogFileReader[type] = klass
And this is what our hook method does: it registers the class that gets passed into the hook method into the LogFileReader registry. And the key that it registers it under, is the type argument from the LogFileReader method way above, which, thanks to the magic of closures, is actually accessible inside the included method.
end
include LogFileReader
And last but not least, we include the LogFileReader module in the anonymous module. [Note: I forgot this line in the original example.]
}
end
class GitLogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrFrobnicator
include LogFileReader
def display
puts "A bzr log file reader..."
end
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class NameThatDoesntFitThePattern
include LogFileReader(:darcs)
def display
puts "Darcs reader, lazily evaluating your pure functions."
end
end
LogFileReader.create(:darcs).display
puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors
puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :#readers
puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)
This new expanded version allows three different ways of defining LogFileReaders:
All classes whose name matches the pattern <Name>LogFileReader will automatically be found and registered as a LogFileReader for :name (see: GitLogFileReader),
All classes that mix in the LogFileReader module and whose name matches the pattern <Name>Whatever will be registered for the :name handler (see: BzrFrobnicator) and
All classes that mix in the LogFileReader(:name) module, will be registered for the :name handler, regardless of their name (see: NameThatDoesntFitThePattern).
Please note that this is just a very contrived demonstration. It is, for example, definitely not thread-safe. It might also leak memory. Use with caution!
One more minor suggestion for Brian Cambell's answer -
In you can actually auto-register the subclasses with an inherited callback. I.e.
class LogFileReader
cattr_accessor :subclasses; self.subclasses = {}
def self.inherited(klass)
# turns SvnLogFileReader in to :svn
key = klass.to_s.gsub(Regexp.new(Regexp.new(self.to_s)),'').underscore.to_sym
# self in this context is always LogFileReader
self.subclasses[key] = klass
end
def self.create(type)
return self.subclasses[type.to_sym].new if self.subclasses[type.to_sym]
raise "No such type #{type}"
end
end
Now we have
class SvnLogFileReader < LogFileReader
def display
# do stuff here
end
end
With no need to register it
This should work too, without the need for registering class names
class LogFileReader
def self.create(name)
classified_name = name.to_s.split('_').collect!{ |w| w.capitalize }.join
Object.const_get(classified_name).new
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
and now
LogFileReader.create(:git_log_file_reader).display
This is how I would make an extensible factory class.
module Factory
class Error < RuntimeError
end
class Base
##registry = {}
class << self
def inherited(klass)
type = klass.name.downcase.to_sym
##registry[type] = klass
end
def create(type, *args, **kwargs)
klass = ##registry[type]
return klass.new(*args, **kwargs) if klass
raise Factory::Error.new "#{type} is unknown"
end
end
end
end
class Animal < Factory::Base
attr_accessor :name
def initialize(name)
#name = name
end
def walk?
raise NotImplementedError
end
end
class Cat < Animal
def walk?; true; end
end
class Fish < Animal
def walk?; false; end
end
class Salmon < Fish
end
duck = Animal.create(:cat, "Garfield")
salmon = Animal.create(:salmon, "Alfredo")
pixou = Animal.create(:duck, "Pixou") # duck is unknown (Factory::Error)

Resources