Created
April 16, 2009 20:08
-
-
Save abachman/96624 to your computer and use it in GitHub Desktop.
Demonstration of a handful of the metaprogramming features of Ruby.
This file contains hidden or 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
# Demonstration of a handful of the metaprogramming features of Ruby. | |
# | |
# This code uses the same class/module configuration as Rails' standard | |
# acts_as plugins. I was looking for a way to extend the behavior of an | |
# ActiveRecord model in practice, and this came out of that exploration. | |
# | |
# Each "method" of extending the target class is tested and evaluated | |
# for functionality and a report is printed. | |
# | |
# Each method is tested as a class method and an instance method, | |
# `Class.method` and `Class.new.method`, respectively. The results are | |
# printed out as the method calls are executed. | |
# | |
# The "method_[1-6]" methods rely data local to the method, while the | |
# "method_[1-6]a" methods try to access a variable defined in the scope | |
# of the initial eval call that created the method. | |
# | |
# The piece of information I was actually looking for was, how can I | |
# define a variable local to the acts_as_extension method and later | |
# access that data from my Model's class-level scope. | |
# (i.e., `Model.local_data`) | |
# | |
# It appears the only ways to do so are method_5a and method_6a. I'm | |
# partial to 5a, just because the .inspect method and string eval-ing | |
# make me nervous. | |
# | |
module Extension | |
def self.included(base) | |
base.extend(ClassMethods) | |
end | |
module ClassMethods | |
# | |
# Toy "acts_as" plugin for demonstrating Ruby language features. | |
# | |
def acts_as_extension | |
# Model.new.method | |
class_eval do | |
define_method(:method_1) do | |
return 'method_1' | |
end | |
end | |
# Model.new.method | |
var_method_1a = 'method_1a' | |
class_eval do | |
define_method(:method_1a) do | |
return var_method_1a | |
end | |
end | |
# Model.new.method | |
instance_eval do | |
define_method(:method_2) do | |
return 'method_2' | |
end | |
end | |
# Model.new.method | |
var_method_2a = 'method_2a' | |
instance_eval do | |
define_method(:method_2a) do | |
return var_method_2a | |
end | |
end | |
# Model.new.method | |
class_eval do | |
def method_3 | |
return 'method_3' | |
end | |
end | |
# Model.new.method (no access to local variables) | |
var_method_3a = 'method_3a' | |
class_eval do | |
def method_3a | |
return var_method_3a # throws "NameError: undefined local variable or method" | |
end | |
end | |
# Model.method | |
instance_eval do | |
def method_4 | |
return 'method_4' | |
end | |
end | |
# Model.method (no access to local variables) | |
var_method_4a = 'method_4a' | |
instance_eval do | |
def method_4a | |
return var_method_4a # throws "NameError: undefined local variable or method" | |
end | |
end | |
# Model.method | |
instance_eval do | |
self.class.send(:define_method, :method_5) do | |
return 'method_5' | |
end | |
end | |
# Model.method | |
val_method_5a = "method_5a" | |
instance_eval do | |
self.class.send(:define_method, :method_5a) do | |
return val_method_5a | |
end | |
end | |
# Model.method | |
instance_eval <<-EOS | |
def method_6 | |
return 'method_6' | |
end | |
EOS | |
# Model.method | |
val_method_6a = 'method_6a' | |
instance_eval <<-EOS | |
def method_6a | |
return #{ val_method_6a.inspect } | |
end | |
EOS | |
end | |
end | |
end | |
# "Superclass" represents ActiveRecord::Base | |
class Superclass; end | |
# ActiveRecord::Base.send :include, ActiveRecord::Acts::Extension | |
Superclass.send :include, Extension | |
# Normal Rails-style model, would normally be `class Ext < ActiveRecord::Base; end` | |
class Ext < Superclass | |
acts_as_extension | |
end | |
(1..6).each do |n| | |
['', 'a'].each do |mode| | |
begin | |
val = Ext.send("method_#{ n }#{ mode }".to_sym) | |
puts "Ext.method_#{ n }#{ mode } works, got `#{ val }`" | |
rescue NoMethodError | |
puts "Ext.method_#{ n }#{ mode } fails (NoMethodError: not the appropriate scope)" | |
rescue NameError | |
puts "Ext.method_#{ n }#{ mode } fails (NameError: no access to local variables)" | |
end | |
begin | |
val = Ext.new.send("method_#{ n }#{ mode }".to_sym) | |
puts "Ext.new.method_#{ n }#{ mode } works, got `#{ val }`" | |
rescue NoMethodError | |
puts "Ext.new.method_#{ n }#{ mode } fails (NoMethodError: not the appropriate scope)" | |
rescue NameError | |
puts "Ext.new.method_#{ n }#{ mode } fails (NameError: no access to local variables)" | |
end | |
end | |
end | |
# Output: | |
# | |
# Ext.method_1 fails (NoMethodError: not the appropriate scope) | |
# Ext.new.method_1 works, got `method_1` | |
# Ext.method_1a fails (NoMethodError: not the appropriate scope) | |
# Ext.new.method_1a works, got `method_1a` | |
# Ext.method_2 fails (NoMethodError: not the appropriate scope) | |
# Ext.new.method_2 works, got `method_2` | |
# Ext.method_2a fails (NoMethodError: not the appropriate scope) | |
# Ext.new.method_2a works, got `method_2a` | |
# Ext.method_3 fails (NoMethodError: not the appropriate scope) | |
# Ext.new.method_3 works, got `method_3` | |
# Ext.method_3a fails (NoMethodError: not the appropriate scope) | |
# Ext.new.method_3a fails (NameError: no access to local variables) | |
# Ext.method_4 works, got `method_4` | |
# Ext.new.method_4 fails (NoMethodError: not the appropriate scope) | |
# Ext.method_4a fails (NameError: no access to local variables) | |
# Ext.new.method_4a fails (NoMethodError: not the appropriate scope) | |
# Ext.method_5 works, got `method_5` | |
# Ext.new.method_5 fails (NoMethodError: not the appropriate scope) | |
# Ext.method_5a works, got `method_5a` | |
# Ext.new.method_5a fails (NoMethodError: not the appropriate scope) | |
# Ext.method_6 works, got `method_6` | |
# Ext.new.method_6 fails (NoMethodError: not the appropriate scope) | |
# Ext.method_6a works, got `method_6a` | |
# Ext.new.method_6a fails (NoMethodError: not the appropriate scope) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment