Created
November 9, 2014 19:04
-
-
Save saxxi/965b6f3f9562432e62c8 to your computer and use it in GitHub Desktop.
Metaprogramming Ruby - Access anything
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Ruby as a particularly flexible language which provides developers a rich toolbox | |
# to express any source code in several different ways. | |
# Reading "Metaprogramming Ruby - Program Like the Ruby Pros" (The Pragmatic Programmers) | |
# I wanted to try my self many of the different `spells` (as Paolo calls them) | |
# I learned or learned its formal definition. | |
# Knowing how to use these tools becomes crucially useful as | |
# we'd often like a slight modification of the behaviour of an external gem. | |
# In this case we can either send a pull request to the owner | |
# and wait until it's been patched or temporarily . | |
# Please pay caution in the use of following metaprogramming `spells`, | |
# as they may lead to unexpected errors while trying to port | |
# your code from one project to another ("where this `{...}.to_nice_json` come from?") | |
# - method_missing | |
# - monkey patching external source code | |
# - expecially monkey patching of core libraries (eg. String, Array, ...) | |
# Consider `refinements` (stable in ruby 2.1, http://rkh.im/ruby-2.1) | |
# Cheers, | |
# Adit Saxena | |
# [access_anything_spec.rb] | |
require 'rspec' | |
describe "Access anything", focus: true do | |
# Per convention the `subject` definition | |
# should preceed the `before` and `after` blocks. | |
# | |
# I had to specify the class as a stub constant | |
# instead of the classic `class MyClass ; end` | |
# to avoid many tests to `poison` each other. | |
before do | |
stub_const "MyClass", Class.new | |
end | |
subject(:my_class_intance) { MyClass.new } | |
context "From its instances" do | |
before do | |
subject.instance_variable_set("@x" , 10) | |
end | |
it "allows reopen the class" do | |
expect { subject.x }.to raise_error(NoMethodError) | |
class MyClass | |
def x | |
@x | |
end | |
end | |
expect(subject.x).to eq 10 | |
end | |
it "allows ghost methods" do | |
expect { subject.reader_x }.to raise_error(NoMethodError) | |
class MyClass | |
def method_missing(name, *args) | |
return super unless name.to_s =~ /^reader_/ | |
_, var = name.to_s.split('reader_', 2) | |
instance_variable_get :"@#{var}" | |
end | |
def respond_to?(method) | |
method.to_s =~ /^reader_/ || super | |
end | |
end | |
expect(subject.reader_x).to eq 10 | |
end | |
context "allows singleton methods definition" do | |
before do | |
expect { subject.reader_x }.to raise_error(NoMethodError) | |
end | |
after do | |
expect(subject.singleton_methods).to eq [:x] | |
expect(subject.x).to eq 10 | |
end | |
it "through `def`" do | |
def subject.x | |
@x | |
end | |
end | |
it "through define_singleton_method block" do | |
subject.define_singleton_method :x do | |
@x | |
end | |
end | |
it "through context probe (instance eval)" do | |
subject.instance_eval do | |
def x | |
@x | |
end | |
end | |
end | |
end | |
end | |
context "At class level" do | |
subject(:my_class) { MyClass } | |
before do | |
expect { my_class.get_twenty }.to raise_error(NoMethodError) | |
end | |
after do | |
expect(my_class.get_twenty).to eq 20 | |
expect(subject.singleton_methods).to eq [:get_twenty] | |
end | |
it "allows class singleton method definition" do | |
def MyClass.get_twenty | |
20 | |
end | |
end | |
it "allows method definition from its eigenclass" do | |
class MyClass | |
class << self | |
def get_twenty | |
20 | |
end | |
end | |
end | |
end | |
it "allows class evaluation" do | |
expect { my_class.new.x }.to raise_error(NoMethodError) | |
MyClass.class_eval do | |
def self.get_twenty | |
20 | |
end | |
def x | |
self.class.get_twenty | |
end | |
end | |
expect(my_class.new.x).to eq 20 | |
another_tester = MyClass.new | |
another_tester.instance_variable_set("@x" , 20) | |
expect(another_tester.x).to eq 20 | |
end | |
end | |
end | |
# $ rspec access_anything_spec.rb --format doc | |
# Access anything | |
# From its instances | |
# allows reopen the class | |
# allows ghost methods | |
# allows singleton methods definition | |
# through `def` | |
# through define_singleton_method block | |
# through context probe (instance eval) | |
# At class level | |
# allows class singleton method definition | |
# allows method definition from its eigenclass | |
# allows class evaluation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment