Skip to content

Instantly share code, notes, and snippets.

@saxxi
Created November 9, 2014 19:04
Show Gist options
  • Save saxxi/965b6f3f9562432e62c8 to your computer and use it in GitHub Desktop.
Save saxxi/965b6f3f9562432e62c8 to your computer and use it in GitHub Desktop.
Metaprogramming Ruby - Access anything
# 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