Nil Associations with Rails Fixtures... how to fix? - ruby-on-rails

I have a Rails 5.1 project using rspec/fixtures and I am having trouble getting fixtures to load objects associated with belongs_to/has_one/has_many: the object I requested the fixture for comes back with its _id columns filled with a seemingly-random number and ActiveRecord sees the association as nil. This occurs on large classes with many associations as well as small data classes with only a few fields.
If, in my test code, I assign those associations with normal Ruby code, objects behave as normal and my tests pass. However when loading the same data through fixtures, associated records are not available and tests that require data spanning across associations fail.
As an example, here are two affected classes:
#app/models/location.rb
class Location < ActiveRecord::Base
has_many :orders
has_many :end_user
belongs_to :retailer
belongs_to :depot
end
#app/models/retailer.rb
class Retailer < ActiveRecord::Base
has_many :locations
end
And here are two corresponding fixtures files:
#spec/fixtures/locations.yml
loc_paris:
retailer: ret_europe (Retailer)
name: "Paris"
nickname: "paris"
loc_washington:
retailer: ret_usa (Retailer)
name: "Washington"
nickname: "washington"
#spec/fixtures/retailers.yml
ret_europe:
name: "AcmeCo France"
nickname: "acmecofr"
currency_type: "EUR"
ret_usa:
name: "AcmeCo USA"
nickname: "acmecousa"
currency_type: "USD"
With the above data, running pp locations(:loc_paris) results in:
#<Location:0x0000000006eee1d8
id: 35456173,
name: "Paris",
nickname: "paris",
retailer_id: 399879241,
created_at: Wed, 23 May 2018 22:39:56 UTC +00:00,
updated_at: Wed, 23 May 2018 22:39:56 UTC +00:00>
Those id numbers are consistent through multiple calls, at least in the same RSpec context. (I put pp locations(:loc_paris) in a let block.) Yet pp locations(:loc_paris).retailer returns nil.
I tried using FactoryBot however we had to switch away from it. I am trying to give fixtures an honest shake but it seems like we are best off simply building data objects in the actual test code... because that solutions works without complaining :/
Am I doing something wrong here? Are we asking too much of fixtures?
Thank you!
Tom

Problem with fixtures
Looking at what you've done, locations(:loc_paris) will find the record described in locations.yml, but locations(:loc_paris).retailer won't.
Rails Associations work like this:
locations(:loc_paris).retailer will look for the retailer with retailer_id mentioned in locations(:loc_paris) record. In your case retailer_id: 399879241 and there is no reseller with this id that's why it returns Nil.
Solution:
Describe fixtures like this:
#spec/fixtures/locations.yml
loc_paris:
retailer_id: 1
name: "Paris"
nickname: "paris"
loc_washington:
retailer_id: 2
name: "Washington"
nickname: "washington"
#spec/fixtures/retailers.yml
ret_europe:
id: 1
name: "AcmeCo France"
nickname: "acmecofr"
currency_type: "EUR"
ret_usa:
id: 2
name: "AcmeCo USA"
nickname: "acmecousa"
currency_type: "USD"
Now, locations(:loc_paris).retailer will look for the retailer with retailer_id mentioned in locations(:loc_paris) record i.e. retailer_id: 1 and there is a reseller ret_europe with this id. Problem Solved
When you run rspec, at first rspec saves these fixtures into your database with some auto-generated id values (if id not provided explicitly), that's why id and reseller_id are some random values. If you don't want the id of locations.yml record to be some random value, you can provide it yourself like this:
loc_paris:
id: 1
retailer_id: 1
name: "Paris"
nickname: "paris"
Tips:
As rspec runs in test environment (mentioned in app/spec/rails_helper.rb) and as I mentioned earlier whenever you run rspec, at first it saves the fixtures into your database. If your local and test database are same, fixtures will replace the actual database records of your database. In your case, records in locations and resellers table record will be completely erased and replaced with these fixtures. So, make different database for test environment.
Hope this answer is helpful

Related

Return name in ActiveRecord relation along with foreign key id

I have a Sub-Component model which can belong to other sub-components. My Model looks like this:
class SubComponent < ApplicationRecord
belongs_to :parent, class_name: "SubComponent", foreign_key: "parent_sub_component_id", optional: true
has_many :child_sub_components, class_name: "SubComponent", foreign_key: "parent_sub_component_id"
validates_presence_of :name
end
This model is fairly simple, it has a name field and a parent_sub_component_id which as the name suggests is an id of another SubComponent.
I'd like to generate a query that returns all of the SubComponents (with their id, name, and parent_sub_component_id) but also includes the actual name of it's parent_sub_component.
This seems like it should be pretty simple but for the life of me I can't figure out how to do it. I'd like for this query to be done in the database rather than doing an each loop in Ruby or something like that.
EDIT:
I'd like for the output to look something like this:
#<ActiveRecord::Relation [#<SubComponent id: 1, name: "Parent Sub", parent_sub_component_id: nil, parent_sub_component_name: nil created_at: "2017-07-07 00:29:37", updated_at: "2017-07-07 00:29:37">, #<SubComponent id: 2, name: "Child Sub", parent_sub_component_id: 1, parent_sub_component_name: "Parent Sub" created_at: "2017-07-07 00:29:37", updated_at: "2017-07-07 00:29:37">]>
You can do this efficiently using an each loop if you use includes:
SubComponent.all.includes(:parent).each do |comp|
comp.parent.name # this gives you the name of the parent
end
What includes does is it pre-fetches the specified association. That is, ActiveRecord will query all subcomponents, and then in a single query also pull down all the parents of those subcomponents. When you subsequently access comp.parent in the loop, the associated parent will already be loaded, so this will not result in a so-called N+1 query.
The queries that AR will generate for you automatically will look something like this:
SELECT `subcomponents`.* FROM `subcomponents`
SELECT `subcomponents`.* FROM `subcomponents` WHERE `subcomponents`.`id` IN (1, 3, 9, 14)
If you need to use the name of the parent in a where condition, includes will not work and you will have to use joins instead to actually generate an SQL JOIN.
This is untested, but should get you started in the right direction, you can do this in Arel by doing something like
def self.execute_query
parent_table = Arel::Table.new(:sub_component).alias
child_table = Arel::Table.new(:sub_component)
child_table.join(parent_table, Arel::Nodes::OuterJoin).on(child_table[:parent_sub_component_id].eq(parent_table[:id]).project(child_table[:id], child_table[:name], parent_table[:id], parent_table[:name])
end
This results in a query like
SELECT "sub_component"."id", "sub_component"."name", "sub_component_2"."id", "sub_component_2"."name" FROM "sub_component" LEFT OUTER JOIN "sub_component" "sub_component_2" ON "sub_component"."parent_sub_component_id" = "sub_component_2"."id"
this is just off the top of my head by looking at Rails/Arel and probably needs a some work, but the query looks about what I would expect and this should get you going.

Rails app not recognizing active record entries?

I am building a rails app and have create a basic and pro plan in database, here is output of Plan.all from rails console:
=> #<ActiveRecord::Relation [#<Plan id: 1, name: "basic", price: #<BigDecimal:7f9aad0bf578,'0.0',9(27)>, created_at: "2016-11-03 04:21:55", updated_at: "2016-11-03 04:21:55">, #<Plan id: 2, name: "pro", price: #<BigDecimal:7f9aad0be448,'0.1E2',9(27)>, created_at: "2016-11-03 04:22:21", updated_at: "2016-11-03 04:22:21">]>
When I create a user under either plan i get this error:
1 error prohibited this user from being saved: Plan must exist
Not sure why it is not picking up the plans.
The error "Plan must exist" when you create a user means that whatever plan_id you gave to the user does not match up with the id of an actual plan in the database. Check the plans you have created, look a their ids, and make sure the plan_id you give to your user matches the id of one of those plans.
Note: it is not enough to create database tables for each of the 2 types of plans, you must create actual instances of the plans.
Also: check to make sure you has_many / belongs_to relationship is set up correctly.

How to assign fixed data to database by ruby on rails

In ruby on rails, we usually assign data in model by console.
like #model = Model.attribute (data). Like #user = User.create("John")
My question is, if I want to have the data fixed, can I do it through model or controller? SO that if I want to change the detail, I do not have to check and find in console.
For example, I need 5 users and 5 users only, so I set user_id = 1 is John user_id = 2 is David...etc in model. Can I do that? How?
You can create "fixed" data by seeding data via the seeds.rb file in your Rails application.
Let's suppose you want to create a user with specific data. Go into your app's db directory and open seeds.rb where you can add code that writes entries in your database.
User.create( email: 'patron#patron.com',
system_id: 2,
subdomain: 'foo',
external_id: '',
first_name: 'Joe',
last_name: 'Patron',
phone_number: '213 555 1212',
role: 'default',
user_status_id: 1,
password: 'password',
password_confirmation: 'password',
terms_accepted_at: datetime,
created_at: datetime,
updated_at: datetime)
You can see that you're calling the create method from whatever model you're working with and passing along all required parameters -- in other words, you're just using Ruby to build objects.
Once you have all your objects created, remember to run rake db:seed any time you reset your database in order to run the seeds.rb file to populate your database.

FactoryGirl rspec, while creating multiple factories at once

When I am creating multiple object of Factory using method create_list, the order and where method don't work on it because create_list creates the array of factory objects.
users = FactoryGirl.create_list(:user, 5)
users.order('name ASC')
It gives undefined method order for Array
So, what should I do to create the multiple factory objects inside the ActiveRecord Collection?
The order and where methods are not defined on Array but on ActiveRecord::Relation. It's the kind of thing that gets passed around by the ActiveRecord query interface. You simply cannot run a query on an array. To run a query you need to start from scratch:
create_list, :user, 5
users = User.where(...).order(name: :asc)
But you might as well directly pass your arguments to create_list so that the condition is satisfied without needing to re-select the users. I also often find myself writing arrays explicitly when I need to alter values in each row e. g.
users = [
create(:user, name: 'Alfred'),
create(:user, name: 'Bob'),
create(:user, name: 'Charlie'),
create(:user, name: 'David'),
create(:user, name: 'Egon')
]
The users are already ordered by name and you're good to go. If you have some condition in the where clause that for example separates the users in two groups, you can just go ahead and separate these users directly. For example imagine we have an admin flag in the users table and you want to create many users, some being admins and some not. Then you'd do
admins = [
create(:user, name: 'Alfred', admin: true),
create(:user, name: 'Charlie', admin: true),
create(:user, name: 'Egon', admin: true)
]
visitors = [
create(:user, name: 'Bob'),
create(:user, name: 'David')
]
No need to query at all. Of course you can also do that in let syntax if you like.

Custom scope not returning any results

Basic association setup (note, Customer is an extension of a Person model):
Customer has_many :orders
Order belongs_to :customer
Inside of Customer.rb, I have the following class method:
# SCOPE
def self.ordered_in_90_days
joins(:orders).where('orders.created_at > ?', 90.days.ago)
end
In my test, I have the following code which creates a new Order (automatically creating a customer thanks for FactoryGirl), then uses the Customer model's self method defined above:
it "finds customers who have ordered within the last 90 days" do
#order = FactoryGirl.create(:order, created_at: 50.days.ago)
#customer = #order.customer
Customer.count.should == 1 #passes
Order.count.should == 1 #passes
puts Customer.all.to_yaml #for debugging, see below
puts Order.all.to_yaml #for debugging, see below
Customer.ordered_in_90_days.should == [#customer] #fails! returns: []
end
Both the customer and order are being created, but nothing is returning in the method call (empty array). What am I missing?
Here is some additional information regarding the factories:
FactoryGirl.define do
factory :customer do
first_name "Wes"
last_name "Foster"
type "Customer"
end
factory :order do
customer
end
end
And here is the debugging output for Customer and Order (remember, Customer is an extension of Person, so that's why you see person_id instead of customer_id):
---
- !ruby/object:Customer
attributes:
id: 1
first_name: Wes
last_name: Foster
type: Customer
created_at: 2013-09-16 21:54:26.162851000 Z
updated_at: 2013-09-16 21:54:26.162851000 Z
middle_name:
---
- !ruby/object:Order
attributes:
id: 1
person_id:
created_at: 2013-07-28 21:54:26.135748000 Z
updated_at: 2013-09-16 21:54:26.192877000 Z
(Customer
The debug output indicates the problem, indeed! Take a look at the Order inspect: you have person_id blank.
First, even if a Customer is a subclass/extension of Person, the Order belongs_to :customer tells ActiveRecord to look for customer_id, not person_id. Are you indicating that the association should be configured in a non-default way on the Order model?
Otherwise, I think you might be mishandling the aliased association reference in the Order factory. I've not used factory_girl association alias references in my project—I try to keep associations out of my factories—but I would verify your methodology with the factory_girl documentation: Association Aliases.
I would, personally, try this in your test example:
it "finds customers who have ordered within the last 90 days" do
#customer = FactoryGirl.create(:customer)
#order = FactoryGirl.create(:order, created_at: 50.days.ago, customer: #customer)
Customer.count.should == 1
Order.count.should == 1
Customer.ordered_in_90_days.should == [#customer]
end
Setting the #order.customer explicitly in your examples allows you to eliminate the factory dependency and complexity.
Sidenote
If you want to keep the association alias method in your factory, and rely on that association in other tests, I would suggest writing a separate test to verify that factory relationship instantiating correctly:
#order = create(:order)
expect(#order.customer).to be_a(Customer)
Or something like that...

Resources