Skip to content

Instantly share code, notes, and snippets.

@abachman
Created April 16, 2009 20:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save abachman/96624 to your computer and use it in GitHub Desktop.
Save abachman/96624 to your computer and use it in GitHub Desktop.
Demonstration of a handful of the metaprogramming features of Ruby.
# 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