From 29a5ab1a89932887f0eab34d35a6c3bd63a887a7 Mon Sep 17 00:00:00 2001 From: Joshua Clayton Date: Thu, 19 Apr 2012 21:34:47 -0400 Subject: [PATCH] Implicitly call FactoryGirl's syntax methods from dynamic attributes --- lib/factory_girl.rb | 1 + lib/factory_girl/attribute_assigner.rb | 13 +++++- lib/factory_girl/evaluator.rb | 6 ++- lib/factory_girl/factory.rb | 2 +- lib/factory_girl/null_object.rb | 16 +++++++- lib/factory_girl/syntax_runner.rb | 5 +++ ..._methods_within_dynamic_attributes_spec.rb | 41 +++++++++++++++++++ spec/factory_girl/null_object_spec.rb | 22 ++++++++-- 8 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 lib/factory_girl/syntax_runner.rb create mode 100644 spec/acceptance/syntax_methods_within_dynamic_attributes_spec.rb diff --git a/lib/factory_girl.rb b/lib/factory_girl.rb index adf2e4f..529a14a 100644 --- a/lib/factory_girl.rb +++ b/lib/factory_girl.rb @@ -28,6 +28,7 @@ require 'factory_girl/aliases' require 'factory_girl/definition' require 'factory_girl/definition_proxy' require 'factory_girl/syntax' +require 'factory_girl/syntax_runner' require 'factory_girl/find_definitions' require 'factory_girl/reload' require 'factory_girl/version' diff --git a/lib/factory_girl/attribute_assigner.rb b/lib/factory_girl/attribute_assigner.rb index 4eae168..4067356 100644 --- a/lib/factory_girl/attribute_assigner.rb +++ b/lib/factory_girl/attribute_assigner.rb @@ -1,6 +1,7 @@ module FactoryGirl class AttributeAssigner - def initialize(evaluator, &instance_builder) + def initialize(evaluator, build_class, &instance_builder) + @build_class = build_class @instance_builder = instance_builder @evaluator = evaluator @attribute_list = evaluator.class.attribute_list @@ -18,7 +19,7 @@ module FactoryGirl end def hash - @evaluator.instance = NullObject.new + @evaluator.instance = build_hash attributes_to_set_on_hash.inject({}) do |result, attribute| result[attribute] = get(attribute) @@ -32,6 +33,10 @@ module FactoryGirl @build_class_instance ||= @evaluator.instance_exec(&@instance_builder) end + def build_hash + @build_hash ||= NullObject.new(hash_instance_methods_to_respond_to) + end + def get(attribute_name) @evaluator.send(attribute_name) end @@ -64,6 +69,10 @@ module FactoryGirl @evaluator.__overrides.keys end + def hash_instance_methods_to_respond_to + @attribute_list.map(&:name) + override_names + @build_class.instance_methods + end + def alias_names_to_ignore @attribute_list.reject(&:ignored).map do |attribute| override_names.map {|override| attribute.name if attribute.alias_for?(override) && attribute.name != override && !ignored_attribute_names.include?(override) } diff --git a/lib/factory_girl/evaluator.rb b/lib/factory_girl/evaluator.rb index 42e7f3d..d4d2c55 100644 --- a/lib/factory_girl/evaluator.rb +++ b/lib/factory_girl/evaluator.rb @@ -47,7 +47,11 @@ module FactoryGirl if @cached_attributes.key?(method_name) @cached_attributes[method_name] else - @instance.send(method_name, *args, &block) + if @instance.respond_to?(method_name) + @instance.send(method_name, *args, &block) + else + SyntaxRunner.new.send(method_name, *args, &block) + end end end diff --git a/lib/factory_girl/factory.rb b/lib/factory_girl/factory.rb index d4d8ec6..2b585a7 100644 --- a/lib/factory_girl/factory.rb +++ b/lib/factory_girl/factory.rb @@ -33,7 +33,7 @@ module FactoryGirl strategy = strategy_class.new evaluator = evaluator_class.new(strategy, overrides.symbolize_keys) - attribute_assigner = AttributeAssigner.new(evaluator, &instance_builder) + attribute_assigner = AttributeAssigner.new(evaluator, build_class, &instance_builder) evaluation = Evaluation.new(attribute_assigner, to_create) evaluation.add_observer(CallbackRunner.new(callbacks, evaluator)) diff --git a/lib/factory_girl/null_object.rb b/lib/factory_girl/null_object.rb index 374f8ee..c7a3075 100644 --- a/lib/factory_girl/null_object.rb +++ b/lib/factory_girl/null_object.rb @@ -1,7 +1,19 @@ module FactoryGirl class NullObject < ::BasicObject - def method_missing(*args) - nil + def initialize(methods_to_respond_to) + @methods_to_respond_to = methods_to_respond_to.map(&:to_s) + end + + def method_missing(name, *args, &block) + if respond_to?(name) + nil + else + super + end + end + + def respond_to?(method, include_private=false) + @methods_to_respond_to.include? method.to_s end end end diff --git a/lib/factory_girl/syntax_runner.rb b/lib/factory_girl/syntax_runner.rb new file mode 100644 index 0000000..ebc07b5 --- /dev/null +++ b/lib/factory_girl/syntax_runner.rb @@ -0,0 +1,5 @@ +module FactoryGirl + class SyntaxRunner + include Syntax::Methods + end +end diff --git a/spec/acceptance/syntax_methods_within_dynamic_attributes_spec.rb b/spec/acceptance/syntax_methods_within_dynamic_attributes_spec.rb new file mode 100644 index 0000000..fc02573 --- /dev/null +++ b/spec/acceptance/syntax_methods_within_dynamic_attributes_spec.rb @@ -0,0 +1,41 @@ +require "spec_helper" + +describe "syntax methods within dynamic attributes" do + before do + define_model("Post", title: :string, user_id: :integer) do + belongs_to :user + + def generate + "generate result" + end + end + define_model("User", email: :string) + + FactoryGirl.define do + sequence(:email_address) {|n| "person-#{n}@example.com" } + + factory :user do + email { generate(:email_address) } + end + + factory :post do + title { generate } + user { build(:user) } + end + end + end + + it "can access syntax methods from dynamic attributes" do + FactoryGirl.build(:user).email.should == "person-1@example.com" + FactoryGirl.attributes_for(:user)[:email].should == "person-2@example.com" + end + + it "can access syntax methods from dynamic attributes" do + FactoryGirl.build(:post).user.should be_instance_of(User) + end + + it "can access methods already existing on the class" do + FactoryGirl.build(:post).title.should == "generate result" + FactoryGirl.attributes_for(:post)[:title].should be_nil + end +end diff --git a/spec/factory_girl/null_object_spec.rb b/spec/factory_girl/null_object_spec.rb index 1ef8182..9d44f53 100644 --- a/spec/factory_girl/null_object_spec.rb +++ b/spec/factory_girl/null_object_spec.rb @@ -1,8 +1,22 @@ require "spec_helper" describe FactoryGirl::NullObject do - its(:id) { should be_nil } - its(:age) { should be_nil } - its(:name) { should be_nil } - its(:admin?) { should be_nil } + let(:methods_to_respond_to) { %w[id age name admin?] } + let(:methods_to_not_respond_to) { %w[email date_of_birth title] } + + subject { FactoryGirl::NullObject.new(methods_to_respond_to) } + + it "responds to the given methods" do + methods_to_respond_to.each do |method_name| + subject.__send__(method_name).should be_nil + subject.respond_to?(method_name).should be_true + end + end + + it "does not respond to other methods" do + methods_to_not_respond_to.each do |method_name| + expect { subject.__send__(method_name) }.to raise_error(NoMethodError) + subject.respond_to?(method_name).should be_false + end + end end