Confirming that a model instance was correctly created from POST params

I’m wondering whether I’m wasting my time trying to verify that an
addition I’ve made to legacy code is in fact setting a new attribute on
a model.

Substruct’s (http://dev.subimage.com/projects/substruct) OrderHelper
contains this method:

 1    def create_order_from_post
 2      @use_separate_shipping_address = 

params[:use_separate_shipping_address]
3
4 @order_user = OrderUser.find_or_create_by_email_address(
5 params[:order_user][:email_address]
6 )
7 @order_user.valid?
8
9 @order = Order.new(params[:order])
10 @order.valid?
11
12 # Look up billing address and update if customer is logged
in.
13 if @customer
14 @billing_address = @customer.billing_address
15 @billing_address.attributes = params[:billing_address]
16 @shipping_address = @customer.billing_address
17 @shipping_address.attributes = params[:shipping_address]
18 else
19 @billing_address =
OrderAddress.new(params[:billing_address])
20 @shipping_address =
OrderAddress.new(params[:shipping_address])
21 end
22 @billing_address.valid?
23
24 if @use_separate_shipping_address
25 @shipping_address.valid?
26 end
27
28 @order_account = OrderAccount.new(params[:order_account])
29 @order_account.valid?
30
31 OrderUser.transaction do
32 @order_user.save!
33 Order.transaction do
34 @order = @order_user.orders.build(params[:order])
35 @order.order_number = Order.generate_order_number
36 @order.save!
37 end
38 OrderAddress.transaction do
39 # Addresses
40 @billing_address =
@order_user.order_addresses.create(params[:billing_address])
41 @billing_address.order_id = @order.id
42 @billing_address.is_shipping = true
43 @billing_address.save!
44 if @use_separate_shipping_address then
45 @shipping_address =
@order_user.order_addresses.create(params[:shipping_address])
46 @shipping_address.is_shipping = true
47 @billing_address.is_shipping = false
48 @billing_address.save!
49 @shipping_address.order_id = @order.id
50 @shipping_address.save!
51 end
52 end
53 OrderAccount.transaction do
54 @order_account =
OrderAccount.new(params[:order_account])
55 @order_account.order_id = @order.id
56 @order_account.order_user_id = @order_user.id
57 @order_account.order_address_id = @billing_address.id
58 @order_account.save!
59 end
60 end
61 end

I am adding to the OrderAddress model the concept of whether an address
is a business address (as opposed to a home/residential address):

describe OrderHelper do
it ‘should record the fact that the address is a business address’ do
1 order = mock_model( Order, :null_object => TRUE )
2 order.stub!( :valid? ).and_return( TRUE )
3 Order.stub!( :new ).and_return( order )
4 order.stub!( :order_number= )
5 order.stub!( :save! )
6
7 params[:order_user] = Hash.new
8 params[:order_user][:email_address] = ‘’
9 order_user = mock_model( OrderUser )
10 order_user.stub!( :valid? ).and_return( TRUE )
11 order_user.stub!( :save! )
12 orders = mock( ‘orders’ )
13 orders.stub!( :build ).and_return( order )
14 order_user.stub!( :orders ).and_return( orders )
15 OrderUser.stub!( :find_or_create_by_email_address
).and_return( order_user )
16 order_user.stub!( :order_addresses ).and_return(
OrderAddress )
17
18 order_address = mock_model( OrderAddress ) #, :null_object
=> TRUE )
19 order_address.stub!( :valid? ).and_return( TRUE )
20 OrderAddress.stub!( :new ).and_return( order_address )
21 order_address.stub!( :order_id= )
22 order_address.stub!( :is_shipping= )
23 order_address.stub!( :save! )
24 order_address.should_receive( :save )
25
26 order_account = mock_model( OrderAccount )
27 order_account.stub!( :valid? ).and_return( TRUE )
28 order_account.stub!( :order_id= )
29 order_account.stub!( :order_user_id= )
30 order_account.stub!( :order_address_id= )
31 order_account.stub!( :save! )
32 OrderAccount.stub!( :new ).and_return( order_account )
33
34 assigns[:use_separate_shipping_address] = FALSE
35 params[:is_business_address] = TRUE
36 create_order_from_post
37
38 assigns[:billing_address].is_business_address.should be_true
end
end

What I am trying to demonstrate to myself is that the addition of a
boolean “is_business_address” column to the order_addresses table via a
migration allows line 40 of the create_order_from_post method to set
this property when an OrderAddress instance is created. But my example
as shown tells me that assigns[:billing_address] is nil when I run it.
Also, I realize that line 16 of the example isn’t returning the right
thing; it should be an AssociationCollection of OrderAddress. That’s
where I’m starting to wonder if I’m spending too much time on something
that is probably simple enough that it’s already working but
disproportionately hard to write an RSpec example for (because the
existing create_order_from_post method has no tests or examples and thus
isn’t easy to test – witness the 32 lines, modulo blank lines, of
stubbing just to get the example to run).

Suggestions on how to write the example, or advice on whether to
continue to try, are requested.

Al

  ____________________________________________________________________________________

Never miss a thing. Make Yahoo your home page.
http://www.yahoo.com/r/hs

On 20.11.2007, at 8.00, Al Chou wrote:

example for (because the existing create_order_from_post method has
no tests or examples and thus isn’t easy to test – witness the 32
lines, modulo blank lines, of stubbing just to get the example to
run).

Suggestions on how to write the example, or advice on whether to
continue to try, are requested.

A couple of points:

  • I have a similar situation with shipping and billing addresses. I
    solved it by adding attr_accessor :single_address to the customer
    model. That gives you the option of using “check_box :single_address”
    in the form for the customer and it will automatically be passed to
    the model when you update its attributes. I’ve noticed it cleans up
    the controller code a lot.
  • for line 16 in the spec, you need to do something like this:

order_addresses = []
order_addresses.stub!(:create).and_return(order_address) #
order_address must be created before this
order_user.stub!( :order_addresses ).and_return( order_addresses )

  • You should put all the stubbing and mocking in a before(:each)
    block, they don’t really belong to the actual spec.
  • What you have in your helper is basically business domain stuff.
    Helpers in Rails are meant for streamlining view code. I just
    refactored some code that does similar things that yours does, and
    noticed that it fits perfectly in the model. So in your case, you
    could add e.g. a class method Order.create_with_addresses_and_user
    (params[:order], params[:order_user], params[:billing_address], params
    [:shipping_address], params[:order_account]). In that case, all you’d
    have to spec in the controller is that the class method is called
    with correct params. All the other spec’ing would go into the model’s
    specs.
  • Unless your db supports nested transactions (very few do), there’s
    no point in putting nested transaction blocks in your code.
  • You can shorten your mocks a bit by defining the stubbed methods
    already in the mock_model call:

order_account = mock_model( OrderAccount, :valid? => true,
:order_id= => true,
:save! => true )

and so on. Also, once your specs are more than just the one, you
start finding patterns there and you can easily refactor the stubbing
into spec helpers so that you only need a line or two to create a
comprehensive mock model in your specs.

HTH,
//jarkko

P.S. in Ruby true and false are normally written in lower case.


Jarkko L.

http://www.railsecommerce.com
http://odesign.fi