Skip to content

Instantly share code, notes, and snippets.

@arches
Created April 1, 2012 03:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arches/2270996 to your computer and use it in GitHub Desktop.
Save arches/2270996 to your computer and use it in GitHub Desktop.
Create and manipulate arbitrary classes with attributes and inheritance inside the safety of a module namespace
module Sandbox
def self.add_class(klass)
const_set_from_string(klass)
end
def self.add_attributes(klass, *attrs)
self.const_get_from_string(klass).class_eval do
attr_accessor *attrs
# set up the 'initialize' method to assign the attributes
define_method(:initialize) do |value_hash|
attrs.each_with_index do |attr|
instance_variable_set("@"+attr, value_hash[attr.to_sym])
end
end
end
end
def self.add_method(klass, method_name, &blk)
self.const_get_from_string(klass).send(:define_method, method_name, &blk)
end
def self.add_class_method(klass, method_name, &blk)
singleton = (class << self.const_get_from_string(klass); self; end)
singleton.send(:define_method, method_name, &blk)
end
def self.cleanup!
constants.each do |klass|
remove_const klass
end
end
def self.const_get_from_string(klass)
klass.split("::").inject(self){|const, klass| const.const_get(klass) }
end
def self.const_set_from_string(klass)
# TODO: try to get before we set, so you can add One::Two::Three,
# then later, One::Dos::Tres without an 'already initialized' warning on 'One'
klass.split("::").inject(self){|const, klass| const.const_set(klass, Class.new) }
end
end
Feature: Sensible defaults
Scenario: A simple object
Given a class named Foo
Given Foo has attributes herp
Given Foo has a method named derp with lambda{"hurrrrr"}
Given a class named Foo::Blog
Given Foo::Blog has attributes title, author
Given Foo::Blog has a class method named foo with lambda{"just testing!"}
Given Foo::Blog has a method named two_args with lambda{|a, b| "Called with #{a}, #{b}"}
When I instantiate a Foo::Blog with {title: "First post!", author: 'Ryan'}
And table_print Foo::Blog
Then the output should contain
"""
TITLE | AUTHOR
--------------------
First post! | Ryan
"""
Given /^a class named ([A-Za-z]*)$/ do |klass|
Sandbox.add_class(klass)
end
Given /^([A-Za-z]*) has attributes (.*)$/ do |klass, attributes|
attrs = attributes.split(",").map { |attr| attr.strip }
Sandbox.add_attributes(klass, *attrs)
end
Given /^([A-Za-z]*) has a class method named ([A-Za-z]*) with (.*)$/ do |klass, method_name, blk|
Sandbox.add_class_method(klass, method_name, &eval(blk))
end
Given /^([A-Za-z]*) has a method named (\w*) with (.*)$/ do |klass, method_name, blk|
Sandbox.add_method(klass, method_name, &eval(blk))
end
When /^I instantiate a ([A-Za-z]*) with (\{.*\})$/ do |klass, args|
@objs ||= OpenStruct.new
@objs.send("#{klass.downcase}=", Sandbox.const_get(klass).new(eval(args)))
end
When /^I instantiate a ([A-Za-z]*) with (\{.*\}) and add it to (.*)$/ do |klass, args, target|
# the thing we're instantiating
child = Sandbox.const_get(klass).new(eval(args))
# the place we're going to add it
target_array = target.split(".").inject(@objs) { |target_obj, target_part| target_obj.send(target_part) }
target_array << child
end
When /table_print$/ do
@r, w = IO.pipe
# TODO: actually table_print :)
w.puts ["TITLE | AUTHOR", "--------------------", "First post! | Ryan"].join("\n")
w.close
end
Then /^the output should contain$/ do |string|
output = []
while line = @r.gets
output << line
end
@r.close
output.join.strip.should == string
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment