Rails fixture associations - ruby-on-rails

Struggling to get fixtures to associate. We are (finally!) writing tests for an existing app. We are using Minitest as the framework.
mail_queue_item.rb:
class MailQueueItem < ApplicationRecord
belongs_to :order
...
end
mail_queue_items.yml:
one:
run_at: 2015-01-04 10:22:19
mail_to: test#test.com
order: seven_days_ago
email_template: with_content
customer: basic_customer
status: waiting
orders.yml:
seven_days_ago:
tenant: basic_tenant
ecom_store: basic_store
ecom_order_id: 123-123456-123456
purchase_date: <%= 7.days.ago %>
set_to_shipped_at: <%= 6.days.ago %>
ecom_order_status: shipped
fulfillment_channel: XYZ
customer: basic_customer
In a test:
require 'test_helper'
class MailQueueItemDenormalizerTest < ActiveSupport::TestCase
fixtures :mail_queue_items, :customers, :email_templates, :orders
test 'should make hash' do
#mqi = mail_queue_items(:one)
puts #mqi.order_id.inspect
puts #mqi.order.inspect
order = orders(:seven_days_ago)
puts order.inspect
assert #mqi.order.ecom_order_status == 'shipped'
end
end
The output looks like this:
MailQueueItemDenormalizerTest
447558226
nil
#<Order id: 447558226, tenant_id: 926560165, customer_id: 604023446, ecom_order_id: "123-123456-123456", purchase_date: "2022-08-13 19:18:02.000000000 -0700", last_update_date: nil, ecom_order_status: "shipped", fulfillment_channel: "XYZ", ....>
test_should_make_hash ERROR (5.96s)
Minitest::UnexpectedError: NoMethodError: undefined method `ecom_order_status' for nil:NilClass
test/denormalizers/mail_queue_item_denormalizer_test.rb:26:in `block in <class:MailQueueItemDenormalizerTest>'
So even though the order_id on the mail_queue_item is correct (it matches the id from the object loaded from the fixture) the association does not work.
I have tried the suggestions in Nil Associations with Rails Fixtures... how to fix? of putting ids in everything and the result is the same.
Project is in Rails 6 (long project that started life in Rails 3.1).

The issue turned out to be that the fixtures were creating invalid objects. The objects were valid enough to get written to the database, but were not passing the Rails validations.
The resulting behavior is quite odd I think, but I don't know of a better way to do it.
I discovered this by adding:
puts "#mqi.order.valid? = #{#mqi.order.valid?}"
puts "#mqi.customer.valid? = #{#mqi.customer.valid?}"
puts "#mqi.email_template.valid? = #{#mqi.email_template.valid?}"
puts #mqi.email_template.errors.full_messages
code in there. Yes, it's disgusting.

Related

Factorygirl , I can't express the association of 1 on 1 (has_one) with rspec

I want to test #create in Decidingscontroller , and Deciding model has undertaking_id column in association with Undertaking model , and has asking_id column in association with Asking model.
So, My factories/decidings.rb is below.
factory :deciding do
after(:build) do |deciding|
deciding.asking ||=build(:asking, deciding: deciding)
deciding.undertaking ||=build(:undertaking, deciding: deciding)
end
end
and My spec/controllers/decidings_controller_spec.rb is below.
RSpec.describe DecidingsController, type: :controller do
describe '#create' do
before do
#deciding=build(:deciding)
end
context 'correct_user login' do
before do
login_user(#deciding.asking.user)
end
it 'creates with deciding +1' do
expect{post :create , undertaking_id: #deciding.undertaking_id , asking_id: #deciding.asking_id}.to change(Deciding , :count).by(1)
end
end
end
end
but #deciding in this case is below.
#<Deciding id: nil, asking_id: nil, undertaking_id: nil, created_at: nil, updated_at: nil>
so I can't create test because undertaking_id and asking_id is nil.
Why is undertaking_id and asking_id nil? Please help me...
Anyway , My factories/asking.rb is below.
FactoryGirl.define do
factory :asking do
association :user
sequence(:content){|i| "お願いします#{i}"}
end
end
The ids are nil because the records are not persisted. When you use build method, it does not save a record in the database. If a record is not saved, it cannot have ID. Use create method instead.
Try this:
factory :deciding do
after(:build) do |deciding|
deciding.asking ||= create(:asking)
deciding.undertaking ||= create(:undertaking)
end
end

Rails: List Required Attributes For Create

I am manually creating objects in the rails console using Model.new(<attributes here>). Is there an easy way to list out which attributes a model will require me to include in order for the .save call to succeed?
I am running rails 4.2.3
You can get an array of validators using Model.validators. You'll have to parse this in some way to extract those validations for presence, something like:
presence_validated_attributes = Model.validators.map do |validator|
validator.attributes if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
end.compact.flatten
I found a simpler way to accomplish the same thing:
When you do a failed create you can check the error message on the object.
# app/models/price.rb
class Price < ActiveRecord::Base
validates_presence_of :value
end
# in console
p = Price.new()
=> #<Price id: nil, created_at: nil, updated_at: nil, value: nil>
p.save
=> false
p.errors.messages
=> {:value=>["can't be blank"]}
In case you the mandatory attributes with error messages
book = Book.new
book.valid?
book.errors.messages
In case you just want the name of attributes without an error message
book = Book.new
book.valid?
book.errors.messages.keys

Why Does A Unit Test For destroy_all fail with let?

So I have this model code:
def self.cleanup
Transaction.where("created_at < ?", 30.days.ago).destroy_all
end
and this rspec unit test:
describe 'self.cleanup' do
before(:each) do
#transaction = Transaction.create(seller:item.user, buyer:user, item:item, created_at:6.weeks.ago)
end
it 'destroys all transactions more than 30 days' do
Transaction.cleanup
expect(#transaction).not_to exist_in_database
end
end
with these factories:
FactoryGirl.define do
factory :transaction do
association :seller, factory: :user, username: 'IAMSeller'
association :buyer, factory: :user, username: 'IAmBuyer'
association :item
end
factory :old_transaction, parent: :transaction do
created_at 6.weeks.ago
end
end
using this rspec custom matcher:
RSpec::Matchers.define :exist_in_database do
match do |actual|
actual.class.exists?(actual.id)
end
end
When I change the spec to this:
describe 'self.cleanup' do
let(:old_transaction){FactoryGirl.create(:old_transaction)}
it 'destroys all transactions more than 30 days' do
Transaction.cleanup
expect(old_transaction).not_to exist_in_database
end
end
the test fails. I also tried manually creating a transaction and assigning it to :old_transaction with let() but that makes the test fail too.
Why is it that it only passes when I use an instance variable in the before(:each) block?
Thanks in advance!
EDIT: FAILED OUTPUT
1) Transaction self.cleanup destroys all transactions more than 30 days
Failure/Error: expect(old_transaction).not_to exist_in_database
expected #<Transaction id: 2, seller_id: 3, buyer_id: 4, item_id: 2, transaction_date: nil, created_at: "2014-02-26 10:06:30", updated_at: "2014-04-09 10:06:32", buyer_confirmed: false, seller_confirmed: false, cancelled: false> not to exist in database
# ./spec/models/transaction_spec.rb:40:in `block (3 levels) in <top (required)>'
let is lazy loaded. So in your failing spec this is the order of events:
Transaction.cleanup
old_transaction = FactoryGirl.create(:old_transaction)
expect(old_transaction).not_to exist_in_database
So the transaction is created after you attempt to clean up.
There are multiple options for you:
Don't use let for this
Unless you have other specs that you want to tell other devs:
I fully intend for all of these specs to reference what should be the exact same object
I personally feel, that you're better off inlining the transaction.
it do
transaction = FactoryGirl.create(:old_transaction)
Transaction.cleanup
expect(transaction).not_to exist_in_database
end
Use the change matcher
This is my personal choice as it clearly demonstrates the intended behavior:
it do
expect{
Transaction.cleanup
}.to change{ Transaction.exists?(old_transaction.id) }.to false
end
This works with let as the change block is run before AND after the expect block. So on the first pass the old_transaction is instantiated so it's id can be checked.
Use before or reference old_transaction before your cleanup
IMO this seems odd:
before do
old_transaction
end
it do
old_transaction # if you don't use the before
Transaction.clean
# ...
end
Use let!
The let! is not lazy loaded. Essentially, it's an alias for doing a normal let, then calling it in a before. I'm not a fan of this method (see The bang is for surprise for details why).
I think you've just accidentally typoed in a ":"
Try this spec:
describe 'self.cleanup' do
let(:old_transaction){FactoryGirl.create(:old_transaction)}
it 'destroys all transactions more than 30 days' do
Transaction.cleanup
expect(old_transaction).not_to exist_in_database
end
end

FactoryGirl: attributes_for not giving me associated attributes

I have a Code model factory like this:
Factory.define :code do |f|
f.value "code"
f.association :code_type
f.association(:codeable, :factory => :portfolio)
end
But when I test my controller with a simple test_should_create_code like this:
test "should create code" do
assert_difference('Code.count') do
post :create, :code => Factory.attributes_for(:code)
end
assert_redirected_to code_path(assigns(:code))
end
... the test fails. The new record is not created.
In the console, it seems that attributes_for does not return all required attributes like the create does.
rob#compy:~/dev/my_rails_app$ rails console test
Loading test environment (Rails 3.0.3)
irb(main):001:0> Factory.create(:code)
=> #<Code id: 1, code_type_id: 1, value: "code", codeable_id: 1, codeable_type: "Portfolio", created_at: "2011-02-24 10:42:20", updated_at: "2011-02-24 10:42:20">
irb(main):002:0> Factory.attributes_for(:code)
=> {:value=>"code"}
Any ideas?
Thanks,
You can try something like this:
(Factory.build :code).attributes.symbolize_keys
Check this: http://groups.google.com/group/factory_girl/browse_thread/thread/a95071d66d97987e)
This one doesn't return timestamps etc., only attributes that are accessible for mass assignment:
(FactoryGirl.build :position).attributes.symbolize_keys.reject { |key, value| !Position.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Still, it's quite ugly. I think FactoryGirl should provide something like this out of the box.
I opened a request for this here.
I'd suggest yet an other approach, which I think is clearer:
attr = attributes_for(:code).merge(code_type: create(:code_type))
heres what I end up doing...
conf = FactoryGirl.build(:conference)
post :create, {:conference => conf.attributes.slice(*conf.class.accessible_attributes) }
I've synthesized what others have said, in case it helps anyone else. To be consistent with the version of FactoryGirl in question, I've used Factory.build() instead of FactoryGirl.build(). Update as necessary.
def build_attributes_for(*args)
build_object = Factory.build(*args)
build_object.attributes.slice(*build_object.class.accessible_attributes).symbolize_keys
end
Simply call this method in place of Factory.attributes_for:
post :create, :code => build_attributes_for(:code)
The full gist (within a helper module) is here: https://gist.github.com/jlberglund/5207078
In my APP/spec/controllers/pages_controllers_spec.rb I set:
let(:valid_attributes) { FactoryGirl.attributes_for(:page).merge(subject: FactoryGirl.create(:theme), user: FactoryGirl.create(:user)) }
Because I have two models associated. This works too:
FactoryGirl.define do
factory :page do
title { Faker::Lorem.characters 12 }
body { Faker::Lorem.characters 38 }
discution false
published true
tags "linux, education, elearning"
section { FactoryGirl.create(:section) }
user { FactoryGirl.create(:user) }
end
end
Here's another way. You probably want to omit the id, created_at and updated_at attributes.
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.

Problem on Rails fixtures creation sequence

I am reading the book Simply Rails by Sitepoint and given these models:
story.rb
class Story < ActiveRecord::Base
validates_presence_of :name, :link
has_many :votes do
def latest
find :all, :order => 'id DESC', :limit => 3
end
end
def to_param
"#{id}-#{name.gsub(/\W/, '-').downcase}"
end
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :story
end
and given this fixtures
stories.yml
one:
name: MyString
link: MyString
two:
name: MyString2
link: MyString2
votes.yml
one:
story: one
two:
story: one
these tests fail:
story_test.rb
def test_should_have_a_votes_association
assert_equal [votes(:one),votes(:two)], stories(:one).votes
end
def test_should_return_highest_vote_id_first
assert_equal votes(:two), stories(:one).votes.latest.first
end
however, if I reverse the order of the stories, for the first assertion and provide the first vote for the first assertion, it passes
story_test.rb
def test_should_have_a_votes_association
assert_equal [votes(:two),votes(:one)], stories(:one).votes
end
def test_should_return_highest_vote_id_first
assert_equal votes(:one), stories(:one).votes.latest.first
end
I copied everything as it is in the book and have not seen an errata about this. My first conclusion was that the fixture is creating the records from bottom to top as it was declared, but that doesn't make any point
any ideas?
EDIT: I am using Rails 2.9 running in an RVM
Your fixtures aren't getting IDs 1, 2, 3, etc. like you'd expect - when you add fixtures, they get IDs based (I think) on a hash of the table name and the fixture name. To us humans, they just look like random numbers.
Rails does this so you can refer to other fixtures by name easily. For example, the fixtures
#parents.yml
vladimir:
name: Vladimir Ilyich Lenin
#children.yml
joseph:
name: Joseph Vissarionovich Stalin
parent: vladimir
actually show up in your database like
#parents.yml
vladimir:
id: <%= fixture_hash('parents', 'vladimir') %>
name: Vladimir Ilyich Lenin
#children.yml
joseph:
id: <%= fixture_hash('children', 'joseph') %>
name: Joseph Vissarionovich Stalin
parent_id: <%= fixture_hash('parents', 'vladimir') %>
Note in particular the expansion from parent: vladimir to parent_id: <%= ... %> in the child model - this is how Rails handles relations between fixtures.
Moral of the story: Don't count on your fixtures being in any particular order, and don't count on :order => :id giving you meaningful results in tests. Use results.member? objX repeatedly instead of results == [obj1, obj2, ...]. And if you need fixed IDs, hard-code them in yourself.
Hope this helps!
PS: Lenin and Stalin weren't actually related.
Xavier Holt already gave the main answer, but wanted to also mention that it is possible to force rails to read in fixtures in a certain order.
By default rails assigns its own IDs, but you can leverage the YAML omap specification to specify an ordered mapping
# countries.yml
--- !omap
- netherlands:
id: 1
title: Kingdom of Netherlands
- canada:
id: 2
title: Canada
Since you are forcing the order, you have to also specify the ID yourself manually, as shown above.
Also I'm not sure about this part, but I think once you commit to overriding the default rails generated ID and use your own, you have to do the same for all downstream references.
In the above example, suppose each country can have multiple leaders, you would have do something like
# leaders.yml
netherlands-leader:
country_id: 1 #you have to specify this now!
name: Willem-Alexander
You need to manually specify the id that refers to the previous Model (Countries)

Resources