Skip to content

Commit

Permalink
Namespace AssemblyLine when used in irb
Browse files Browse the repository at this point in the history
When using AssemblyLine in the console, you now must prefix your 'let'
definitions with AssemblyLine. The methods are no longer defined at the
global level.

Old:
>> Assemble(:drinks)
>> drinks
=> [:gin, :vodka]

New:
>> Assemble(:drinks)
>> AssemblyLine.drinks
=> [:gin, :vodka]

Apparently, defining an arbitrary set of methods on Kernel can wreak
havoc on your software. While testing AssemblyLine in irb I encountered a
recursive loop caused by method_missing. A NameError should have been
raised but instead one of my 'let' definitions was being called because
it was defined globally.

Heres an example:
>> def foo
>>   :bar
>>   end
=> nil
>> class A
>>   def baz
>>     foo
>>     end
>>   end
=> nil
>> A.new.baz
=> :bar

A#baz should raise a NameError but instead, #foo gets called because it
is defined globally.
  • Loading branch information
sandro committed Feb 10, 2010
1 parent 77e39aa commit e26a903
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 63 deletions.
21 changes: 9 additions & 12 deletions lib/assembly_line.rb
@@ -1,17 +1,22 @@
require 'forwardable'
require 'assembly_line/registry'
require 'assembly_line/constructor'
require 'assembly_line/global_context'
require 'assembly_line/generic_context'

module AssemblyLine
VERSION = "0.2.0".freeze
extend SingleForwardable

def self.assemble(name, context, options={})
Registry.locate(name).assemble(context, options)
end

def self.define(name, &block)
Registry.add(name, block)
end

def self.assemble(name, rspec_context, options={})
Registry.locate(name).assemble(rspec_context, options)
def self.generic_context
@generic_context ||= GenericContext.new
end

def Assemble(name, options={})
Expand All @@ -20,15 +25,7 @@ def Assemble(name, options={})
end

module Kernel
extend Forwardable

def Assemble(name, options={})
AssemblyLine.assemble(name, assembly_line_global_context, options)
end

protected

def assembly_line_global_context
AssemblyLine::GlobalContext
AssemblyLine.assemble(name, AssemblyLine.generic_context, options)
end
end
@@ -1,12 +1,11 @@
module AssemblyLine
module GlobalContext
extend self
class GenericContext

def let(name, &block)
define_method name do
let_values[name] ||= instance_eval(&block)
end
::Kernel.def_delegator :assembly_line_global_context, name
AssemblyLine.def_delegator :generic_context, name
end

# there are no tests so just run the block
Expand All @@ -26,6 +25,8 @@ def let_values
@let_values ||= {}
end

attr_writer :context
def define_method(name, &block)
self.class.send(:define_method, name, &block)
end
end
end
2 changes: 1 addition & 1 deletion spec/assembly_line/constructor_spec.rb
Expand Up @@ -19,7 +19,7 @@
end
it "persists context" do
constructor.assemble(rspec_context, options)
constructor.rspec_context.should == rspec_context
constructor.context.should == rspec_context
end

it "persists context" do
Expand Down
34 changes: 34 additions & 0 deletions spec/assembly_line/generic_context_spec.rb
@@ -0,0 +1,34 @@
require 'spec_helper'

describe AssemblyLine::GenericContext do
let(:generic_context) { AssemblyLine.generic_context }

after { generic_context.clear }

describe "#let" do
it "defines the method on the GenericContext instance" do
generic_context.let(:foobarish) { :bar }
generic_context.foobarish.should == :bar
end

it "memoizes the defined method" do
generic_context.instance_variable_set(:@count, 0)
generic_context.let(:my_count) { @count += 1 }
5.times { generic_context.my_count }
generic_context.my_count.should == 1
end

it "adds a delegating method to AssemblyLine" do
generic_context.let(:global_method) { :global }
AssemblyLine.global_method.should == :global
end
end

describe "#before" do
it "runs the code block" do
expect do
generic_context.before { @done = true }
end.to change { generic_context.instance_variable_get(:@done) }.from(nil).to(true)
end
end
end
33 changes: 0 additions & 33 deletions spec/assembly_line/global_context_spec.rb

This file was deleted.

22 changes: 9 additions & 13 deletions spec/functional/global_context_spec.rb
@@ -1,30 +1,26 @@
require 'spec_helper'

global_assembly_set_up = lambda do
set_up_assembly = lambda do
AssemblyLine.define(:global_assembly) do
let(:foo) { @bar +=1 }
let(:foo) { @bar += 1 }
before do
@bar = 0
end
end
Assemble(:global_assembly)
end

def call_global_assembly(count = 5)
(count - 1).times { foo }
foo
end

describe "in the global context" do
before(:all) do
global_assembly_set_up.call
before do
set_up_assembly.call
end

it "defined the method within AssemblyLine::GlobalContext" do
AssemblyLine::GlobalContext.should respond_to(:foo)
it "defines the method on AssemblyLine.generic_context" do
AssemblyLine.generic_context.should respond_to(:foo)
end

it "does not increase the value because the method is memoized" do
call_global_assembly.should == 1
it "memoizes the value" do
5.times { AssemblyLine.foo }
AssemblyLine.foo.should == 1
end
end

0 comments on commit e26a903

Please sign in to comment.