问题
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!
来源:https://stackoverflow.com/questions/40509943/how-to-make-a-factorygirl-create-affect-another-records-attribute