[rspec-users] How to write a correct RSpec for Polymorphic Association

David Chelimsky dchelimsky at gmail.com
Sat Oct 17 13:24:51 EDT 2009


On Fri, Oct 16, 2009 at 7:21 AM, Sergey Rogachev <lists at ruby-forum.com> wrote:
> I don't know what is need to set in the variable ADDRESSES:
> Organization.stub!(:find_by_id).with("100").and_return(mock_organization(:id
> => "100", :addresses => ??? ))
>
> Somebody help me. Any ideas? I tried to find the solution of problem but
> I can't.
>
> --- rspec - controllers
> def mock_organization(stubs={})
>  @parent_object ||= mock_model(Organization, stubs)
> end
>
> it "assigns a newly created address as @address" do
>  Organization.stub!(:find_by_id).with("100").and_return(
>       mock_organization(:id => "100", :addresses => ??? )) <<!--- here
>  @parent_object.stub(:addresses).stub(:new).with({:these => 'params'})
>
>  post :create, :address => {:these => 'params'}, :organization_id =>
> @parent_object.id
>  assigns[:address].should equal(mock_address)
> end
>
> ---- models ----
> class Organization < ActiveRecord::Base
>  has_many :addresses, :as => :addressable
> end
>
> class Address < ActiveRecord::Base
>  belongs_to :addressable, :polymorphic => true
> end
>
> ----- controllers -----
> class AddressesController < ApplicationController
>  before_filter :load_parent_object
>
>  def create
>    @address = @parent_object.addresses.new(params[:address])
>
>    respond_to do |format|
>      if @address.save
>        flash[:notice] = 'Address was successfully created.'
>        format.html { redirect_to(@parent_object) }
>        format.xml  { render :xml => @address, :status => :created,
> :location => @address }
>      else
>        format.html { render :action => "new" }
>        format.xml  { render :xml => @address.errors, :status =>
> :unprocessable_entity }
>      end
>    end
>  end
>
>  private
>
>  def load_parent_object
>    if params[:organization_id]
>      @parent_object = Organization.find_by_id(params[:organization_id])
>    end
>  end
>
> end

Hi Sergey,

This is a great example of why it's helpful to write the examples
first. There is no need for this controller to know anything about
organizations - that can be handled by the model, where the
association is defined. Here's how I might have done this:

it "assigns a newly created address as @address" do
  address = stub_model(Address)
  Address.stub(:new).
    with({:these => 'params'}).
    and_return(address)

  post :create, :address => {:these => 'params'}

  assigns[:address].should equal(address)
end

class AddressesController < ApplicationController
  def create
    @address = Address.new(params[:address])

    respond_to do |format|
      if @address.save
        flash[:notice] = 'Address was successfully created.'
        format.html { redirect_to(@address.addressable) }
        format.xml  { render :xml => @address, :status => :created,
:location => @address }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @address.errors, :status =>
:unprocessable_entity }
      end
    end
  end
end

Now the post to the create action can include :organization_id within
params[:address] instead of as a separate hash key, and all is well.

That said, the following _should_ work w/ your current design:

it "assigns a newly created address as @address" do
  address     = mock('address')
  addresses   = mock('addresses')
  addressable = mock_model(Organization, :id => "100", :addresses => addresses)

  Organization.stub!(:find_by_id).with("100").and_return(addressable)
  addressable.stub(:addresses).and_return(addresses)
  addresses.stub(:new).with({:these => 'params'}).and_return(address)

  post :create, :address => {:these => 'params'}, :organization_id => "100"

  assigns[:address].should equal(address)
end

class AddressesController < ApplicationController
  before_filter :load_addressable

  def create
    @address = @addressable.addresses.new(params[:address])

    respond_to do |format|
      if @address.save
        flash[:notice] = 'Address was successfully created.'
        format.html { redirect_to(@addressable) }
        format.xml  { render :xml => @address, :status => :created,
:location => @address }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @address.errors, :status =>
:unprocessable_entity }
      end
    end
  end

private

  def load_addressable
    if params[:organization_id]
      @addressable = Organization.find_by_id(params[:organization_id])
    end
  end

end

Notes:

* I changed @parent_object to @addressable, since that's how the
association is labeled in the code.
* The only object in the code example that uses mock_model is the one
that is actually needs to behave like an AR model in the context of
this example

Let me know if you have any questions.

Cheers,
David


More information about the rspec-users mailing list