3

I have an Invoice model that may contain a number of Items:

class Invoice < ActiveRecord::Base

  attr_accessible :number, :date, :recipient, :items_attributes

  belongs_to :user

  has_many :items

  accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true

end

I am trying to test this using RSpec:

describe InvoicesController do

  describe 'user access' do

    before :each do
      @user = FactoryGirl.create(:user)
      @invoice = @user.invoices.create(FactoryGirl.attributes_for(:invoice))
      sign_in(@user)
    end

    it "renders the :show view" do
      get :show
      expect(response).to render_template :show
    end

  end

end

Unfortunately, this test (and all the others) fail with this error message from RSpec:

Failure/Error: @invoice = @user.invoices.create(FactoryGirl.attributes_for(:invoice))
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: items

How can I create an invoice with items that will pass my tests?

I am using FactoryGirl to fabricate objects like this:

factory :invoice do
  number { Random.new.rand(0..1000000) }
  recipient { Faker::Name.name }
  date { Time.now.to_date }
  association :user
  items { |i| [i.association(:item)] } 
end

factory :item do
  date { Time.now.to_date }
  description { Faker::Lorem.sentences(1) }
  price 50
  quantity 2
end

2 Answers 2

1

-To use nested attributes in your example you need to pass in "item_attributes" and not "items" like you are currently doing.

I'm not fluent in FactoryGirl, but maybe something along these lines would work? :

invoice_attributes = FactoryGirl.attributes_for(:invoice)
invoice_attributes["item_attributes"] = invoice_attributes["items"]
invoice_attributes["items"] = nil
@invoice = @user.invoices.create(invoice_attributes)

That s should hopefully simulate what parameters get passed in from your form.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, but that results in the same Can't mass-assign protected attributes: items error. I even changed your code to ["items_attributes"] which should be the correct version but to no avail :-(
1

Edit: Misunderstood the question. Apologies.

Instead of

before :each do
  @user = FactoryGirl.create(:user)
  @invoice = @user.invoices.create(FactoryGirl.attributes_for(:invoice))
  sign_in(@user)
end

Simply create the factories for invoice passed with a user param, like so:

before :each do
  @user = FactoryGirl.create(:user)
  FactoryGirl.create :invoice, user: @user
  sign_in(@user)
end

Also, this a minor style suggestion, but instead of instance variables, you can use lets, like so:

let(:user) { FactoryGirl.create :user }

before :each do
  FactoryGirl.create :invoice, user: user
  sign_in(user)
end

Passing 'user' in to the invoice create will also create the user (and it is callable as simply 'user').

Minor caveat: I've been doing this for about 6 months, so there might be someone more knowledgeable who disagrees with my style suggestion.

1 Comment

OK, thanks, but what's the difference between your code and mine? I think Rails' create method will also save the @user.id into the new invoice, no? At least it does so in all my controllers. I will heed your advice on let, but it doesn't make my tests pass for now :-(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.