How to make a FactoryGirl.create affect another re

2019-07-08 06:57发布

问题:

In a webshop application normally after a booking of a product the bookings_controller create action does an order.save which in turn activates the necessary before_save method order.sum_of_all_bookings.

When building a RSpec test for an admin viewing a list of orders, making a booking doesn't change the total of an order. The indexing does work in the browser.

require 'rails_helper'

RSpec.feature 'Admin can oversee orders' do
  let(:admin) { FactoryGirl.create(:user, :admin) }
  let(:customer) { FactoryGirl.create( :user ) }
  let!(:order_1) { FactoryGirl.create( :order, customer: customer ) }
  let!(:booking_1) { FactoryGirl.create( :booking,  product_name: 'Honingpot', 
                                                    product_quantity: 1, 
                                                    product_price: '5,00', 
                                                    order: order_1 ) }
  let!(:order_2) { FactoryGirl.create( :order, customer: customer ) }
  let!(:booking_2) { FactoryGirl.create( :booking,  product_name: 'Streekpakket',
                                                    product_quantity: 2, 
                                                    product_price: '10,00', 
                                                    order: order_2 ) }


  before do
    order_1.sum_all_bookings
    order_2.sum_all_bookings
    login_as(admin)
    visit orders_path
  end

  scenario 'with success' do
    within('table#paid') do
      expect(page).to have_content '5,00'
      expect(page).to have_content '10,00'
    end
  end
end

Before block doesn't work.

Neither does the factory girl after create:

FactoryGirl.define do
  factory :booking do
    product_name 'Honingpot'
    product_quantity '1'
    product_price '3,99'
    order
    after(:create) { |booking| booking.order.save } # booking.order.sum_all_bookings, also not working
  end
end

RSpec prints the bookings being in the order, yet the order total being unchanged.

How to make the order sum the bookings?

Or more generally:

How to make a FactoryGirl.create affect another record's attribute?

回答1:

Cleaning up your specs

First off, you have too much factory configuration in your spec. FactoryGirl is here to move the creation and setup away from your specs.

This will not directly solve your problem, but these steps will make your testing proccess cleaner, simpler and faster.

1. Faker Gem

To get started with the revamp go ahead and add the faker gem so you can easily generate useful, random data for your factories.

FactoryGirl.define do
  factory :booking do
    product_name { Faker::Commerce.product_name }
    product_quantity { Faker::Number.number(2) }
    product_price Faker::Number.decimal(2)

    order
  end
end

There you go, some solid booking factories ready to be used.

2. Traits

They are a great way to adapt your factories for certain scenarios. Definitely read more about them in the docs.

FactoryGirl.define do
  factory :order do 
    customer

    trait :with_bookings do 
      after(:create) do |order|
        order.bookings << create_list(:booking, 2)
      end
    end
  end
end

So now you can easily create an order with or without bookings:

let(:order_with_bookings) { create(:order, :with_bookings) }

3. Cleaner syntax

You can leave out the FactoryGirl syntax, simply create(:order) will do. After these steps you should have a much cleaner test environment.

Solving the callback problem

As I understand it, you set up your callback like this:

class Order
  has_many :bookings

  before_save :update_order_cache

  def update_order_cache
    # calculation of the total bookings sum 
  end
end

class Booking
  belongs_to :order
end

But you should do it the other way round, after the product has been created or updated, the callback on the order should be triggered:

class Order
  has_many :bookings

  def update_order_cache
    # calculation of the total bookings sum 
  end
end

class Booking
  belongs_to :order

  after_save do
    order.update_order_cache
  end
end

You can also add a default value to the sum if that field should never be blank.

validates :sum_field, presence: true, default: '0.0'

This should wrap this up for you. Cheers!