How do you temporarily "class_eval" for a test? - ruby

Is it possible to temporarily apply certain methods to a class for tests? I want to be able to run specs depending on many ways to apply it. While I could make a bunch of fixtures with different settings, I find it easier to just class_eval the model in the tests. For example:
describe "some context"
before do
Page.class_eval do
my_applying_method :some => :option
end
end
it "should..."
end
Then in another context block:
describe "another context without the gem applied"
before do
Page.class_eval do
# nothing here since I want to page to be as is
end
end
it "should do something else..."
end
But the problem with the last context block is that it has a modified class (modified in the context block above). Is it possible to reset a class after class_eval? How?
Thanks!

I hope there is a better way to do that but you can use this(and it's with a warning at the Foo = Foo_old line):
module Bar
def baz
end
end
class Foo
end
puts Foo.method_defined? :baz #=> false
Foo_old = Foo.dup # create a copy of our class
Foo.class_eval do
include Bar
end
puts Foo.method_defined? :baz #=> true
Foo = Foo_old
puts Foo.method_defined? :baz #=> false

You haven't clarified how you are modifying the class.
The remix library allows you to temporarily include a module and properly uninclude it later.
In general, it's probably safest to just duplicate the class and test the duplicate:
irb(main):001:0> class Foo; end
#=> nil
irb(main):002:0> Foo.dup.class_eval{ #x = 42 }
#=> 42
irb(main):003:0> Foo.class_eval{ #x }
#=> nil

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)...

Dynamically adding statements to a method

I have a class Foo:
class Foo
def a
"something" if true
end
end
I want an add_statement method to add new statements to the method, keeping the old implementation. Is it possible?
I want to do something like this:
foo = Foo.new
foo.extend_method(:a, &block)
so now the source of my a method should be something like this:
def a
"something" if true
&block
end
where &block is the code I passed as argument in extend_method.
You want to use alias_method_chain, or Module#prepend.
There are many tutorials / documentation about them on the net, for example "AVOIDING ALIAS_METHOD_CHAIN IN RUBY" or "Module.prepend: a super story".
For your specific example (when you want to extend the function outside of the class), you have to use alias_method_chain, added in a new module, and included in your original class using Foo.send.
You can find more details and examples in "When to use alias_method_chain".
Here is an aspect oriented example:
require 'aspector'
class Foo
def a
puts "in Foo.a"
end
end
aspect = Aspector do
before :a do
puts 'this should print first'
end
after :a do |result_of_a|
puts 'this should print last'
end
end
puts "\n\nplain instance"
foo = Foo.new
foo.a
puts "\n\napply to an instance"
aspect.apply(foo)
foo.a
puts "\n\napply to all instances of Foo"
aspect.apply(Foo)
Foo.new.a
You will need to 'gem install aspector'.

Ruby: what's the difference with these self extensions?

So I have three different ways I can have class level methods on a module:
I usually do it this way if I have 3 or fewer:
module Foo
def self.speak
"Foo"
end
end
If I have more, I've traditionally done it this way:
module Bar
class << self
def speak
"Bar"
end
end
end
But recently, I came across this nifty way and have started using this more often:
module FooBar
extend self
def speak
"FooBar"
end
end
They all work:
Foo.speak => "Foo"
Bar.speak => "Bar"
FooBar.speak => "FooBar"
So, is there any material differences or gotchas to be aware of, esp. with the last form? The only real gotcha I can think of is that once you "extend self" all method defs following are class-level.
I've tried to think of some edge cases where one form works, but the other doesn't. Are there any?
The third form, unlike the first two, creates both an instance method :speak and a module method :speak:
module Foo
def self.speak
"Foo"
end
end
Foo.methods.include?(:speak) #=> true
Foo.instance_methods.include?(:speak) #=> false
class A
include FooBar
end
A.instance_methods.include?(:speak) #=> false
module Bar
class << self
def speak
"Bar"
end
end
end
Bar.methods.include?(:speak) #=> true
Bar.instance_methods.include?(:speak) #=> false
class A
include Bar
end
A.instance_methods.include?(:speak) #=> false
module FooBar
extend self
def speak
"FooBar"
end
end
FooBar.methods.include?(:speak) #=> true
FooBar.instance_methods.include?(:speak) #=> true
class A
include FooBar
end
A.instance_methods.include?(:speak) #=> true

Preventing methods from being re-defined

So, I need to make a method within a class protected from re-definition. I am not really sure how else to explain it so here's the code:
module Foo
def bar
p "lol"
end
end
Now, that's the original Foo#bar method and I need it to be like a constant. So, I did come up with a solution. That was to save the method in a Constant and detect when someone tried changing it it would simply re-load it from that constant (it was a Proc object):
module Foo
Original_bar = Proc.new { p "lol" }
def bar
Original_bar.call
end
def self.method_added(method_name)
if method_name == :bar
def Foo::bar
Original_bar.call
end
end
end
end
But this isn't completely safe since once could use the same "trick" I did to bypass method_added and I am not really fond of this, since it's not very ruby-like.
Tests
A normal test:
module Foo
def bar
p "lmao"
end
end
Foo::bar # => "lol"
And another test using the trick:
def Foo::bar
p "rofl"
end
Foo::bar # => "rofl"
tl;dr
How to make a method in Ruby "unredefinable" (if that's even a word)?
If you freeze the module that should prevent method being added to it.
Note that a c extension can unfreeze variables (see evil.rb for example.
You can make your code warn you that a method has been over-ridden by turning on warnings:
$VERBOSE = true
module Foo
def self.bar
p "lmao"
end
end
def Foo::bar
p "rofl"
end
(irb):9: warning: method redefined; discarding old bar
It may be possible, but not practical, to raise an exception when warnings are issued.
This won't warn you if you first undefine Foo.bar, however.

ruby - override method and then revert

I am trying to find a way that I can override a method, do something, and then revert without leaving any artifacts around.
I have implemented this using mocha but obviously this is not going to fly in a production app. Notice the new method has parameters and the old one does not.
Example as follows
require 'rubygems'
require 'mocha'
class Example
def to_something
self.stubs(:attribs => other(1))
r = attribs_caller
self.unstub(:attribs)
r
end
def other(int)
{"other" => int }
end
def attribs_caller
attribs
end
def attribs
{"this" => 1 }
end
end
a1 = Example.new
puts a1.attribs_caller #=> this1
puts a1.to_something #=> other1
puts a1.attribs_caller #=> this1
class String
alias orig_reverse reverse
def reverse(n)
'fooled you. '*n
end
end
puts "ab".reverse(2)
#=> fooled you fooled you
# clean up:
class String
alias reverse orig_reverse
remove_method(:orig_reverse)
end
puts "ab".reverse #=> ba
Another way to do that, without creating an extra method, is this:
class Foo
def bar
:old_method
end
end
Foo.new.bar # => :old_method
$old_method = Foo.new.method(:bar)
class Foo
def bar
:new_method
end
end
Foo.new.bar # => :new_method
class Foo
define_method($old_method.name, &$old_method)
end
Foo.new.bar # => :old_method
I think that this is better than using an alias method. In Ruby methods are, also, objects. I just take the reference of the object before destructing the association of the object (the method) with the class. After I add the same method. It also works if you use the undef keyword to remove the method from the class. The bad point is that you have to have an object of the class to take the reference of the method.

Resources