diff --git a/lib/model_stubbing.rb b/lib/model_stubbing.rb index 22c55d4..6b47ef7 100644 --- a/lib/model_stubbing.rb +++ b/lib/model_stubbing.rb @@ -2,6 +2,7 @@ require 'model_stubbing/definition' require 'model_stubbing/model' require 'model_stubbing/stub' +require 'model_stubbing/stub_proxy' module ModelStubbing # Gets a hash of all current definitions. diff --git a/lib/model_stubbing/model.rb b/lib/model_stubbing/model.rb index 79c2ccc..7e5d11b 100644 --- a/lib/model_stubbing/model.rb +++ b/lib/model_stubbing/model.rb @@ -112,16 +112,7 @@ def connection protected def method_missing(model_name, stub_name, *args) - named_model = @definition.models[model_name] - if named_model.nil? - raise "No #{model_name.inspect} model found when calling #{model_name}(#{stub_name})" - end - stub = named_model.stubs[stub_name] - if stub.nil? - raise "No #{stub_name.inspect} stub found in the #{model_name.inspect} model when calling #{model_name}(#{stub_name})" - else - stub - end + StubProxy.new(@definition, model_name, stub_name) end end end \ No newline at end of file diff --git a/lib/model_stubbing/stub.rb b/lib/model_stubbing/stub.rb index 85b36ca..328b687 100644 --- a/lib/model_stubbing/stub.rb +++ b/lib/model_stubbing/stub.rb @@ -47,7 +47,13 @@ def record(attributes = {}) ModelStubbing.records[this_record_key] = instantiate(this_record_key, attributes) end end - + + # Same as #record, but does not instantiate stubs. + def record_without_stubs + this_record_key = record_key(attributes) + instantiate(this_record_key, attributes, false) + end + def inspect "(ModelStubbing::Stub(#{@name.inspect} => #{attributes.inspect}))" end @@ -69,7 +75,7 @@ def insert(attributes = {}) def with(attributes) @attributes.inject({}) do |attr, (key, value)| attr_value = attributes[key] || value - attr_value = attr_value.record if attr_value.is_a?(Stub) + attr_value = attr_value.record if (attr_value.is_a?(Stub) || attr_value.is_a?(StubProxy)) attr.update key => attr_value end end @@ -78,7 +84,7 @@ def only(*keys) keys = Set.new Array(keys) @attributes.inject({}) do |attr, (key, value)| if keys.include?(key) - attr.update key => (value.is_a?(Stub) ? value.record : value) + attr.update key => ((value.is_a?(Stub) || value.is_a?(StubProxy)) ? value.record : value) else attr end @@ -91,7 +97,7 @@ def except(*keys) if keys.include?(key) attr else - attr.update key => (value.is_a?(Stub) ? value.record : value) + attr.update key => ((value.is_a?(Stub) || value.is_a?(StubProxy)) ? value.record : value) end end end @@ -99,9 +105,14 @@ def except(*keys) def connection @connection ||= @model.connection end - + + # duck typing with StubProxy + def method_name + nil + end + private - def instantiate(this_record_key, attributes) + def instantiate(this_record_key, attributes, with_stubs = true) case attributes[:id] when :new is_new_record = true @@ -128,26 +139,35 @@ def instantiate(this_record_key, attributes) record.stubbed_attributes = stubbed_attributes.merge(:id => record.id) stubbed_attributes.each do |key, value| meta.send :attr_accessor, key unless record.respond_to?("#{key}=") - if value.is_a? Stub - # set foreign key - record.send("#{stubbed_attributes.column_name_for(key)}=", value.record.id) - # set association - record.send("#{key}=", value.record) - elsif value.is_a? Array - records = value.map { |v| v.is_a?(Stub) ? v.record : v } - records.compact! - - # when assigning has_many instantiated stubs, temporarily act as new - # otherwise AR inserts rows - nr, record.new_record = record.new_record?, true - record.send("#{key}=", records) - record.new_record = nr - else - duped_value = case value - when TrueClass, FalseClass, Fixnum, Float, NilClass, Symbol then value - else value.dup - end - record.send("#{key}=", duped_value) + case value + when StubProxy, Stub + if with_stubs + if value.method_name + record.send("#{key}=", value.record_without_stubs) + else + # set foreign key + record.send("#{stubbed_attributes.column_name_for(key)}=", value.record_without_stubs.id) + # set association + record.send("#{key}=", value.record) + end + end + when Array + if with_stubs + records = value.map { |v| (v.is_a?(Stub) || v.is_a?(StubProxy)) ? v.record : v } + records.compact! + + # when assigning has_many instantiated stubs, temporarily act as new + # otherwise AR inserts rows + nr, record.new_record = record.new_record?, true + record.send("#{key}=", records) + record.new_record = nr + end + else + duped_value = case value + when TrueClass, FalseClass, Fixnum, Float, NilClass, Symbol then value + else value.dup + end + record.send("#{key}=", duped_value) end end record @@ -182,7 +202,11 @@ def value_list list = inject([]) do |fixtures, (key, value)| column_name = column_name_for key column = column_for column_name - value = value.record.id if value.is_a?(Stub) + case value + when Stub then value = value.record_without_stubs.id + when StubProxy + value = value.method_name ? value.record : value.record_without_stubs.id + end quoted = @stub.connection ? @stub.connection.quote(value, column) : %("#{value.to_s}") fixtures << quoted.gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r") end.join(", ") @@ -191,12 +215,12 @@ def value_list def column_name_for(key) (@keys ||= {})[key] ||= begin value = self[key] - if value.is_a? Stub + if value.is_a?(Stub) || value.is_a?(StubProxy) if defined?(ActiveRecord) if reflection = model_class.reflect_on_association(key) reflection.primary_key_name else - raise "No reflection '#{key}' found for #{model_class.name} while guessing column_name" + "#{key}_id".sub(/_id_id/, '_id') end else "#{key}_id" diff --git a/lib/model_stubbing/stub_proxy.rb b/lib/model_stubbing/stub_proxy.rb new file mode 100644 index 0000000..026887f --- /dev/null +++ b/lib/model_stubbing/stub_proxy.rb @@ -0,0 +1,74 @@ +module ModelStubbing + # Used when attaching stubs to other stubs in definitions. The stub doesn't + # actually have to exist until the Definition is being inserted into the database. + # + # model Post do + # stub :title => 'foo', :author => users(:default) + # end + # + # You can also call specific methods from the stub. + # + # model Blog do + # stub :latest_post => posts(:default), :latest_post_title => posts(:default).title + # end + # + class StubProxy + attr_reader :method_name, :proxy_definition, :proxy_model_name, :proxy_stub_name + + def initialize(definition, model_name, stub_name, method_name = nil) + @proxy_definition = definition + @proxy_model_name = model_name + @proxy_stub_name = stub_name + @method_name = method_name + @model = @stub = nil + end + + def record + @stub ||= fetch_stub + @method_name ? @stub.record_without_stubs.send(@method_name) : @stub.record_without_stubs + end + + alias_method :record_without_stubs, :record + + def ==(other) + if other.is_a?(StubProxy) + other.proxy_definition == @proxy_definition && other.proxy_model_name == @proxy_model_name && other.proxy_stub_name == @proxy_stub_name && other.method_name == @method_name + elsif !@method_name && other.is_a?(Stub) + other.model.definition == @proxy_definition && other.model.name == @proxy_model_name && @proxy_stub_name == other.name + else + super + end + end + + def id + method_missing(:id) + end + + def inspect + "(ModelStubbing::StubProxy[#{@proxy_model_name}(#{@proxy_stub_name.inspect})#{".#{@method_name}" if @method_name}]" + end + + protected + def fetch_stub + @model = @proxy_definition.models[@proxy_model_name] + if @model.nil? + raise "No #{@proxy_model_name.inspect} model found when calling #{@proxy_model_name}(#{@proxy_stub_name.inspect})" + end + @stub = @model.stubs[@proxy_stub_name] + if @stub.nil? + raise "No #{@proxy_stub_name.inspect} stub found in the #{@proxy_model_name.inspect} model when calling #{@proxy_model_name}(#{@proxy_stub_name})" + else + @stub + end + end + + def method_missing(name, *args) + if args.empty? + @method_name = name.to_sym + self + else + super + end + end + end +end \ No newline at end of file diff --git a/spec/definition_spec.rb b/spec/definition_spec.rb index 40cfc89..8076940 100644 --- a/spec/definition_spec.rb +++ b/spec/definition_spec.rb @@ -81,11 +81,11 @@ module ModelStubbing it "dups each model" do @defn.models.each do |name, model| - duped_model = @copy.models[name] + duped_model = @copy.models[name] model.should == duped_model model.should_not be_equal(duped_model) model.stubs.each do |key, stub| - duped_stub = @copy.models[name].stubs[key] + duped_stub = @copy.models[name].stubs[key] stub.should == duped_stub stub.should_not be_equal(duped_stub) end @@ -94,7 +94,7 @@ module ModelStubbing it "dups each stub" do @defn.stubs.each do |name, stub| - duped_stub = @copy.stubs[name] + duped_stub = @copy.stubs[name] stub.should == duped_stub stub.should_not be_equal(duped_stub) end diff --git a/spec/models.rb b/spec/models.rb index 7c77e36..1023cdd 100644 --- a/spec/models.rb +++ b/spec/models.rb @@ -135,13 +135,13 @@ def User.table_name end model User do - stub :admin, :admin => true # inherits from default fixture + stub :admin, :admin => true, :edited_post => model_stubbing_posts(:default) # inherits from default fixture end model Post do # uses admin user fixture above - stub :title => 'initial', :user => all_stubs(:admin_model_stubbing_user), :published_at => current_time + 5.days - stub :nice_one, :title => 'nice one', :tags => [all_stubs(:foo_model_stubbing_tag), all_stubs(:bar_model_stubbing_tag)] + stub :title => 'initial', :user => model_stubbing_users(:admin), :published_at => current_time + 5.days, :editor_id => model_stubbing_users(:admin).id + stub :nice_one, :title => 'nice one', :tags => [model_stubbing_tags(:foo), model_stubbing_tags(:bar)] end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b098274..0210ef4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,4 +12,11 @@ require 'spec' end +begin + require 'ruby-debug' + Debugger.start +rescue LoadError + # no debugger +end + require File.join(File.dirname(__FILE__), 'models') \ No newline at end of file diff --git a/spec/stub_proxy_spec.rb b/spec/stub_proxy_spec.rb new file mode 100644 index 0000000..5502f75 --- /dev/null +++ b/spec/stub_proxy_spec.rb @@ -0,0 +1,68 @@ +require File.join(File.dirname(__FILE__), 'spec_helper') + +module ModelStubbing + describe StubProxy do + before :all do + @definition = ModelStubbing.definitions[:default] + @model = @definition.models[:model_stubbing_tags] + @stub = @model.stubs[:foo] + end + + describe "with model name and stub name" do + before :all do + @stub_proxy = StubProxy.new(@definition, @model.name, @stub.name) + end + + it "has no #method_name" do + @stub_proxy.method_name.should be_nil + end + + it "returns @stub.record for #record" do + @stub_proxy.record.should == @stub.record + end + + it "equals the stub" do + @stub_proxy.should == @stub + end + end + + describe "with model name, stub name, and method name" do + before :all do + @stub_proxy = StubProxy.new(@definition, @model.name, @stub.name) + @stub_proxy.name + end + + it "has #method_name" do + @stub_proxy.method_name.should == :name + end + + it "returns @stub.record.name for #record" do + @stub_proxy.record.should == @stub.record.name + end + + it "does not equal the stub" do + @stub_proxy.should_not == @stub + end + end + + describe "missing valid stub name" do + before :all do + @stub_proxy = StubProxy.new(@definition, @model.name, :abc) + end + + it "raises exception with #record" do + lambda { @stub_proxy.record }.should raise_error + end + end + + describe "missing valid model name" do + before :all do + @stub_proxy = StubProxy.new(@definition, :abc, @stub.name) + end + + it "raises exception with #record" do + lambda { @stub_proxy.record }.should raise_error + end + end + end +end \ No newline at end of file diff --git a/spec/stub_spec.rb b/spec/stub_spec.rb index 7e231b2..770eea1 100644 --- a/spec/stub_spec.rb +++ b/spec/stub_spec.rb @@ -9,6 +9,7 @@ module ModelStubbing @posts = @definition.models[:model_stubbing_posts] @user = @users.default @post = @posts.default + @editor_id = StubProxy.new(@definition, @users.name, :admin, :id) end it "is defined in stub file" do @@ -17,11 +18,11 @@ module ModelStubbing it "has the default stub's attributes" do @user.attributes.should == {:name => 'bob', :admin => false} - @post.attributes.should == {:title => 'initial', :user => @users.stubs[:admin], :published_at => @definition.current_time + 5.days} + @post.attributes.should == {:title => 'initial', :user => @users.stubs[:admin], :editor_id => @editor_id, :published_at => @definition.current_time + 5.days} end it "#with returns merged attributes" do - @post.with(:title => 'fred').should == {:title => 'fred', :user => @users.stubs[:admin].record, :published_at => @definition.current_time + 5.days} + @post.with(:title => 'fred').should == {:title => 'fred', :user => @users.stubs[:admin].record, :editor_id => @editor_id.record, :published_at => @definition.current_time + 5.days} end it "#only returns only given keys" do @@ -29,11 +30,11 @@ module ModelStubbing end it "#except returns other keys" do - @post.except(:published_at).should == {:title => 'initial', :user => @users.stubs[:admin].record} + @post.except(:published_at).should == {:title => 'initial', :user => @users.stubs[:admin].record, :editor_id => @editor_id.record} end it "merges named stub attributes with default attributes" do - @users.stubs[:admin].attributes.should == {:name => 'bob', :admin => true} + @users.stubs[:admin].attributes.should == {:name => 'bob', :admin => true, :edited_post => StubProxy.new(@definition, @posts.name, :default)} end it "sets default model stubs in the definition's global stubs" do