Do refinements apply only to instance methods? - ruby

I'm trying to understand Ruby's refinements feature, and I've encountered a scenario I don't understand.
Take this example code:
class Traveller
def what_are_you
puts "I'm a Backpacker"
end
def self.preferred_accommodation
puts "Hostels"
end
end
module Refinements
module Money
def what_are_you
puts "I'm a cashed-up hedonist!"
end
module ClassMethods
def preferred_accommodation
puts "Expensive Hotels"
end
end
def self.included(base)
base.extend ClassMethods
end
end
refine Traveller do
include Money
end
end
Now, when I do this in the REPL:
Traveller.new.what_are_you # => I'm a Backpacker
Traveller.preferred_accommodation # => Hostels
using Refinements
Traveller.new.what_are_you # => I'm a cashed-up hedonist!
Traveller.preferred_accommodation # => Hostels (???)
Why is #what_are_you refined, but .preferred_accommodation is not?

As #MasashiMiyazaki explained, you have to refine two classes: Traveller and Traveller's singleton class. That actually allows you to simplify your code quite a bit:
module Money
refine Traveller do
def what_are_you
puts "I'm a cashed-up hedonist!"
end
end
refine Traveller.singleton_class do
def preferred_accommodation
puts "Expensive Hotels"
end
end
end
Traveller.new.what_are_you #=> I'm a Backpacker
Traveller.preferred_accommodation #=> Hostels
using Money
Traveller.new.what_are_you #=> I'm a cashed-up hedonist!
Traveller.preferred_accommodation #=> Expensive Hotels
Moreover, by putting the above three statements in a module, the refined versions of the two methods are confined to that module:
module M
using Money
Traveller.new.what_are_you #=> I'm a cashed-up hedonist!
Traveller.preferred_accommodation #=> Expensive Hotels
end
Traveller.new.what_are_you #=> I'm a Backpacker
Traveller.preferred_accommodation #=> Hostels

You need to call refine Traveller with singleton_class scope to overwrite class methods. By adding the following code to your Refinements module instead of self.included, you can get the expected result.
module Refinements
refine Traveller.singleton_class do
include Money::ClassMethods
end
end
This article(http://timelessrepo.com/refinements-in-ruby) will help you to understand Refinements more.

Related

Ruby: how does constant lookup works when using the shortcut notation `class A::B`

When working in a class defined in a module, I'd like to access another class defined in the same module.
When using this code
module SomeModule
class Foo
def self.get_bar
Bar
end
end
end
module SomeModule
class Bar
end
end
# works and returns SomeModule::Bar
SomeModule::Foo.get_bar
Looking up Bar from SomeModule::Foo works. It is not found in local scope SomeModule::Foo, so it looks one level up to SomeModule and finds SomeModule::Bar.
But when using the shortcut notation class A::B to define the classes, the lookup does not work anymore:
module SomeModule
end
class SomeModule::Foo
def self.get_bar
Bar
end
end
class SomeModule::Bar
end
# does not work and raises a NameError
SomeModule::Foo.get_bar
It produces the error NameError: uninitialized constant SomeModule::Foo::Bar. But for me both codes look identical and should produce the same output. I'm obvisouly missing a key concept here.
Can someone explain why the lookup works in one case and not the other? And is it possible to know in advance if the lookup will work or fail by introspecting the class?
There is an awesome post explaining how it works in detail. The summary will be that Ruby's constant lookup is based on lexical scope.
There is a method Module.nesting which returns an array of constants where Ruby is looking for a required const first.
module SomeModule
class Buzz
def self.get_bar
p Module.nesting #=> [SomeModule::Buzz, SomeModule]
Bar
end
end
end
class SomeModule::Foo
def self.get_bar
p Module.nesting #=> [SomeModule::Foo]
Bar rescue puts "Oops, can not find it"
end
end
class SomeModule::Bar
end
SomeModule::Buzz.get_bar
SomeModule::Foo.get_bar
Ruby constant lookup is based on the lexical scope, and these two examples are quite different with regards to that. Take a look:
module SomeModule
puts Module.nesting.inspect #=> [SomeModule]
puts Module.nesting.map(&:constants).inspect # => [[]], we didn't define Foo yet
class Foo
puts Module.nesting.inspect #=> [SomeModule::Foo, SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[], [:Foo]], at this point SomeModule is already "aware" of Foo
def self.get_bar
Bar
end
end
end
module SomeModule
puts Module.nesting.inspect #=> [SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[:Foo]], we didn't define Bar yet
class Bar
puts Module.nesting.inspect #=> [SomeModule::Bar, SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[], [:Foo, :Bar]]
end
end
and the second one
module SomeModule
puts Module.nesting.inspect #=> [SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[]]
end
class SomeModule::Foo
puts Module.nesting.inspect #=> [SomeModule:Foo]
puts Module.nesting.map(&:constants).inspect #=> [[]]
def self.get_bar
Bar
end
end
class SomeModule::Bar
puts Module.nesting.inspect #=> [SomeModule:Bar]
puts Module.nesting.map(&:constants).inspect #=> [[]]
end
As you see, Bar in 1st and 2nd case is being resolved in quite different lexical scopes which in turn leads to quite different result.
Regarding your question
is it possible to know in advance if the lookup will work or fail by introspecting the class
It is possible for isolated piece of code, but application-wide I wouldn't rely on that. When in doubt just specify the nesting explicitly starting from the outermost context (::SomeModule::Bar)...

How to open class with alias_method

I've been reading through Metaprogrammin Ruby 2nd edition recently, and in the end of Chapter 5, they provide a little quiz that
Your task is to change Fixnum class so that the answer to 1+1 becomes 3, instead of 2.
I understand the solution in the textbook without much trouble (They reopen Fixnum class directly). But I wanted to give a try isolating methods to an independent Module as much as possible. something like below.
But running below result in infinite lop inside newly defined +. Could you point out what's wrong with this code? Thank you in advance.
module PlusOneMore
def self.prepended(base)
base.class_eval{
alias_method :original_plus, :+
}
end
def +(n)
original_plus(n).original_plus(1)
end
end
Fixnum.class_eval do
prepend PlusOneMore
end
puts 1.+(1)
The problem is that your override has already occurred when the prepended hook is executed. I.e. when you run alias_method :original_plus, :+, + already points to your override.
You need to either make sure the new method is defined after the alias_method call or use some other approach.
module Foo
def self.included(klass)
klass.class_eval do
alias_method :original_plus, :+
define_method(:+) do |n|
original_plus(n).original_plus(1)
end
end
end
end
Fixnum.include(Foo)
puts 1.+(1)
# => 3
You could also use prepend with super and succ:
module Foo
def +(n)
super(n.succ)
end
end
Fixnum.prepend(Foo)
puts 1.+(1)
# => 3
You can also use Ruby refinements to achieve similar effect.
module Foo
refine Fixnum do
alias_method :original_plus, :+
def +(n)
(self.original_plus(n)).next
end
end
end
using Foo
puts 1 + 1
#=> 3

How can I define self.included in a module so that it passes through its included/extended module to subsequent include/extends

I have a number of modules that when they are included I want them to call an 'included' function.. this function would be the same for all of them.. I'd like to be able to therefore have this included function in a module and have all these sub modules include that.. This is a contrived example but I need something like....
module Humanable
def self.extended(klass)
klass.instance_eval do
define_method :included do |base|
puts 'I was just included into the Person Class'
end
end
end
end
module Personable
extend Human
end
class Person
include Personable
end
Where when Person includes Personable..it puts the string from the Humanable module.. This doesn't work but hopefully you get the idea how can I achieve this?
The answer seems to be:
module Humanable
def self.extended(klass)
klass.define_singleton_method :included do |base|
puts 'hello'
end
end
end
module Personable
extend Humanable # does **not** puts 'hello'
end
class Person
include Personable # puts 'hello'
end

Executing code for every method call in a Ruby module

I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.
module MyModule
def go_forth
a re-used statement
# code particular to this method follows ...
end
def and_multiply
a re-used statement
# then something completely different ...
end
end
But I want to avoid putting that a re-used statement code explicitly in every single method. Is there a way to do so?
(If it matters, a re-used statement will have each method, when called, print its own name. It will do so via some variant of puts __method__.)
Like this:
module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end
module M
def hello
puts "yo"
end
def bye
puts "bum"
end
before(*instance_methods) { puts "start" }
end
class C
include M
end
C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"
This is exactly what aspector is created for.
With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.
require 'aspector'
module MyModule
aspector do
before :go_forth, :add_multiply do
...
end
end
def go_forth
# code particular to this method follows ...
end
def and_multiply
# then something completely different ...
end
end
You can implement it with method_missing through proxy Module, like this:
module MyModule
module MyRealModule
def self.go_forth
puts "it works!"
# code particular to this method follows ...
end
def self.and_multiply
puts "it works!"
# then something completely different ...
end
end
def self.method_missing(m, *args, &block)
reused_statement
if MyModule::MyRealModule.methods.include?( m.to_s )
MyModule::MyRealModule.send(m)
else
super
end
end
def self.reused_statement
puts "reused statement"
end
end
MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...
You can do this by metaprogramming technique, here's an example:
module YourModule
def included(mod)
def mod.method_added(name)
return if #added
#added = true
original_method = "original #{name}"
alias_method original_method, name
define_method(name) do |*args|
reused_statement
result = send original_method, *args
puts "The method #{name} called!"
result
end
#added = false
end
end
def reused_statement
end
end
module MyModule
include YourModule
def go_forth
end
def and_multiply
end
end
works only in ruby 1.9 and higher
UPDATE: and also can't use block, i.e. no yield in instance methods
I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Another Solution could be:
module Aop
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_filter(method_name, options = {})
aop_methods = Array(options[:only]).compact
return if aop_methods.empty?
aop_methods.each do |m|
alias_method "#{m}_old", m
class_eval <<-RUBY,__FILE__,__LINE__ + 1
def #{m}
#{method_name}
#{m}_old
end
RUBY
end
end
end
end
module Bar
def hello
puts "Running hello world"
end
end
class Foo
include Bar
def find_hello
puts "Running find hello"
end
include Aop
before_filter :find_hello, :only => :hello
end
a = Foo.new()
a.hello()
It is possible with meta-programming.
Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.

instance_eval vs class_eval in module

class Foo
include Module.new { class_eval "def lab; puts 'm' end" }
def lab
super
puts 'c'
end
end
Foo.new.lab #=> m c
========================================================================
class Foo
include Module.new { instance_eval "def lab; puts 'm' end" }
def lab
super
puts 'c'
end
end
Notice here I changed class_eval to instance_eval
Foo.new.lab rescue nil#=> no super class method lab
Foo.lab #=> undefined method lab for Foo class
So it seems that including the module neither defined an instance method nor a class method.
Any explanation what's going on here?
This code was tested on ruby 1.8.7 on mac.
First, think of what include does. it makes the instance methods of the module being included into instance methods on the including class. i.e. apart from the fact that your working example uses an anonymous module it is equivalent to:
module M1
def lab
puts 'm'
end
end
class Foo
include M1
def lab
super
puts 'c'
end
end
Next, think of what class_eval does. It evaluates the given code in the context of the class or module. i.e. it's exactly like you reopened the module and typed the code passed to class_eval. So MyModule = Module.new { class_eval "def lab; puts 'm' end" } is equivalent to
module MyModule
def lab
puts 'm'
end
end
Hopefully this explains the case that works.
When you use instance_eval you are evaluating the code within the context of the receiving object (in this case the instance of module) so MyMod2 = Module.new { instance_eval "def lab; puts 'm' end" } is equivalent to
module MyMod2
def MyMod2.lab
puts 'm'
end
end
i.e. it creates a module method which you'd call via MyMod2.lab and such methods are not added as instance methods by include.
Please note: this answer borrows a bit of its explanation from an answer I wrote to a previous question asking about instance_eval vs. class_eval relating to an example from The Ruby Programming Language book. You might find that answer helpful too.
including a module just takes the instance methods - you are looking for extend. luckily to get the best of both worlds, you can simply do:
module Something
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def blah
puts "lol"
end
end
end
class Test
include Something
end
irb:
>> Test.blah
lol
=> nil

Resources