Browse files

. #append and #set

  • Loading branch information...
1 parent 1bedeb9 commit a9dc459d64885249ef47969e1d715c6f139445a9 @kschiess committed Jun 3, 2010
Showing with 152 additions and 16 deletions.
  1. +103 −13 lib/floor_manager/employee.rb
  2. +10 −0 spec/support/model/spy.rb
  3. +39 −3 spec/unit/floor_manager_spec.rb
View
116 lib/floor_manager/employee.rb
@@ -3,26 +3,108 @@
module FloorManager::Employee
class DSL
+ # A proxy that is the receiver of #set and #append in a construct like this:
+ #
+ # one :spy do
+ # relationship.set :gun
+ # end
+ #
+ class AssocProxy < Struct.new(:employee, :field)
+ def set(*create_args)
+ employee.add_attribute AttributeAction::AssocSet.new(field, create_args)
+ end
+ def append(*create_args)
+ employee.add_attribute AttributeAction::AssocAppend.new(field, create_args)
+ end
+ end
+
def initialize(employee, &block)
@employee = employee
instance_eval(&block)
end
-
+
def method_missing(sym, *args, &block)
if args.size == 1
# Immediate attribute
value = args.first
- @employee.add_attribute sym, Proc.new { value }
+ @employee.add_attribute AttributeAction::Immediate.new(sym, value)
elsif block
# Lazy attribute
- @employee.add_attribute sym, block
+ @employee.add_attribute AttributeAction::Block.new(sym, block)
+ elsif args.empty?
+ # Maybe: #set / #append proxy?
+ AssocProxy.new(@employee, sym)
else
super
end
end
end
+
+ module AttributeAction
+ # Base class for stored actions.
+ class Base
+ def initialize(name)
+ @name = name
+ end
+ def set(obj, value)
+ obj.send("#{@name}=", value)
+ end
+ def get(obj)
+ obj.send(@name)
+ end
+ end
+
+ # Stores the action of producing another employee via a collection proxy
+ # stored in field.
+ class AssocAppend < Base
+ def initialize(field, create_args)
+ super field
+ @create_args = create_args
+ end
+ def apply(obj, floor)
+ assoc_obj = floor.build(*@create_args)
+ get(obj) << assoc_obj
+ assoc_obj.save!
+ end
+ end
+
+ # Stores the action of producing another employee via the floor and then
+ # storing that as a value.
+ class AssocSet < Base
+ def initialize(field, create_args)
+ super field
+ @create_args = create_args
+ end
+ def apply(obj, floor)
+ set(obj, floor.create(*@create_args))
+ end
+ end
+
+ # Stores the action of setting an attribute to an immediate value.
+ class Immediate < Base
+ def initialize(name, value)
+ super(name)
+ @value = value
+ end
+ def apply(obj, floor)
+ set(obj, @value)
+ end
+ end
+
+ # Stores the action of setting an attribute to the result of a block.
+ class Block < Base
+ def initialize(name, block)
+ super(name)
+ @block = block
+ end
+ def apply(obj, floor)
+ set(obj, @block.call(obj, floor))
+ end
+ end
+ end
+ # Base class for employees. No instances of this should be created.
class Base
def self.from_dsl(klass_name, &block)
new(klass_name).tap { |emp| DSL.new(emp, &block) }
@@ -33,21 +115,28 @@ def initialize(klass_name)
@attributes = []
end
- def add_attribute(name, callable)
- @attributes << [name, callable]
- end
-
+ # Build this employee in memory.
+ #
def build(floor, overrides)
produce_instance.tap { |i| apply_attributes(i, overrides, floor) }
end
+ # Create this employee in the database.
+ #
def create(floor, overrides)
produce_instance.tap { |i|
apply_attributes(i, overrides, floor)
i.save! }
end
+ # Reset this employee between test runs.
+ #
def reset
+ # Empty, but subclasses will override this.
+ end
+
+ def add_attribute action
+ @attributes << action
end
protected
def produce_instance
@@ -58,12 +147,12 @@ def produce_instance
end
def apply_attributes(instance, overrides, floor)
- (@attributes + overrides.to_a).each do |name, value_producer|
- if value_producer.respond_to? :call
- instance.send("#{name}=", value_producer.call(instance, floor))
- else
- instance.send("#{name}=", value_producer)
- end
+ @attributes.each do |action|
+ action.apply(instance, floor)
+ end
+
+ overrides.each do |name, value|
+ AttributeAction::Immediate.new(name, value).apply(instance, floor)
end
end
end
@@ -96,5 +185,6 @@ def reset
# A template for employees, you can call build/create many times.
class Template < Base
+ # Currently empty, see base class
end
end
View
10 spec/support/model/spy.rb
@@ -18,4 +18,14 @@ def initialize
end
def save!; @saved = true; end
def saved?; @saved; end
+
+ class Builder < Array
+ def create!(attrs)
+ self << Spy.build(attrs)
+ end
+ end
+
+ def enemies
+ @builder ||= Builder.new
+ end
end
View
42 spec/unit/floor_manager_spec.rb
@@ -101,9 +101,45 @@
FloorManager.get(:any)
}
- let(:russian_spy) { env.spy(:name => 'russian') }
- subject { russian_spy }
+ let(:blue_spy) { env.spy(:name => 'blue') }
+ subject { blue_spy }
- its(:name) { should == 'russian' }
+ its(:name) { should == 'blue' }
+ end
+ context "environment with association" do
+ let(:env) {
+ FloorManager.define :any do |m|
+ one :green, :class => Spy do
+ name 'green'
+ end
+ one :white, :class => Spy do
+ name 'white'
+ end
+ one :black, :class => Spy do
+ name 'black'
+ end
+ any :spy do
+ # A shortcut for association sets
+ # (like { |inst, floor| floor.create(...) })
+ opposite.set :green
+
+ # has and belongs to many (uses association.create)
+ enemies.append :black
+ enemies.append :white
+ end
+ end
+
+ FloorManager.get(:any)
+ }
+
+ context "produced spy (from any rule)" do
+ let(:any_spy) { env.spy }
+ subject { any_spy }
+
+ its(:enemies) { should have(2).entries }
+ it "should have green in #opposite" do
+ any_spy.opposite.should == env.green
+ end
+ end
end
end

0 comments on commit a9dc459

Please sign in to comment.