FactoryGirl rspec, while creating multiple factories at once - ruby-on-rails

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.

Related

Nil Associations with Rails Fixtures... how to fix?

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

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.

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...

Deleting records from HABTM association

I'm trying to do something fairly simple. I have two models, User and Group. For simplicity's sake, let's say they look like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
and
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, for some reason, I have a user that has the same group twice. In the Rails Console:
user = User.find(1000)
=> #<User id: 1000, first_name: "John", last_name: "Doe", active: true, created_at:
"2013-01-02 16:52:36", updated_at: "2013-06-17 16:21:09">
groups = user.groups
=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">, #<Group id: 1, name: "student", is_active: true,
created_at: "2012-12-24 15:08:59", updated_at: "2012-12-24 15:08:59">]
user.groups = groups.uniq
=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">]
user.save
=> true
And there is some SQL output that I've silenced. I would think that everything should be all set, but it's not. The groups aren't updated, and that user still has both. I could go into the join table and manually remove the duplicates, but that seems cludgy and gross and unnecessary. What am I doing wrong here?
I'm running Rails 3.2.11 and Ruby 1.9.3p392
Additional note: I've tried this many different ways, including using user.update_attributes, and using group_ids instead of the groups themselves, to no avail.
The reason this doesn't work is because ActiveRecord isn't handling the invalid state of duplicates in the habtm association (or any CollectionAssociation for that matter). Any ids not included in the newly assigned array are deleted - but there aren't any in this case. The relevant code:
# From lib/active_record/associations/collection_association.rb
def replace_records(new_target, original_target)
delete(target - new_target)
unless concat(new_target - target)
#target = original_target
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
target
end
The 'targets' being passed around are Arrays of assigned records. Note the call to delete(target - new_target) is equivalent in your case to delete(user.groups - user.groups.uniq) which results in an empty Array passed (since comparison is based on the id attribute of each record).
Instead, you'll need to clear out the association and then reassign the single group again:
group = user.groups.first
user.groups.clear
user.groups << group
This might be a way to cleanup those duplicates (it handles any number of groups of duplicate associations):
user = User.find(1000)
user.groups << user.groups.group_by(&:id).values.find_all {|v| v.size > 1}.each {|duplicates| duplicates.uniq_by! {|obj| obj.id}}.flatten.each {|duplicate| user.groups.delete(duplicate)}

Get ID of Rails Model before saving...?

How do you get the id of a rails model before it is saved?
For example, if I create a new model instance, how can I get its ID before it is saved?
I know that the id is created onsave and according to the database but is there a workaround?
I was looking for this too, and I found an answer:
Let's suppose model name is "Model" and table name is "models"
model.rb
before_save {
next_id=Model.connection.select_value("Select nextval('models_id_seq')")
}
This will output the value your record will take for id IF it gets saved
Usually when people think they need to do this they actually do not need to do it. Like John says, explain what you are trying to do and someone can probably suggest a way to do it without having to know the id in advance.
This is less a Rails question and more a database question. This is a problem that will present itself in any web application framework, and the solution is the same in all places. You have to use a database transaction.
The basic flow will work like this.
Open a transaction
Save your model
Use the ID assigned by the database
If it turns out you actually don't want to keep this model in the database, roll back the transaction.
If it turns out you want to keep the model in the database, commit the transaction.
The main thing you will notice from this approach is that there will be gaps in your IDs where you rolled back the transaction.
Using the default Rails convention of an auto-incrementing integer primary key, there's no way to get the ID of a model before it's saved because it's generated by the RDBMS when the new row is inserted in the relevant table.
What problem are you actually trying to solve?
Most of the time when I needed an id can be grouped into a short list.
When creating nested associations or connectin of the associations through.
Let's assume we have: :user that have :pets through :user_pets association, where we will save their type.
If we have a properly configured "has_many: through Association" we can just
User.pets.create(name: "Rex") but this is too simplistic, as we want to creat :pet type in :user_pets.
u = User.create(name: "Cesar")
u.id # => 1 # works fine
p = u.pets.create(name: 'Rex')
# => rails will create UserPets => {id: 1, user_id: 1, pet_id: 1} for us
# But now we have a problem, how do we find :user_pets of our :pet?
# remember we still need to update the :type, the ugly (wrong) way:
up = p.user_pets.first
up.type = 'dog'
up.save! # working but wrong
# Do you see the problems here? We could use an id
P = Pet.new( name: "Destroyer" )
p.id # will not work, as the pet not saved yet to receive an id
up = UserPet.new( user_id: U.id, pet_id: p.id )
# => UserPet {id: 2, user_id: 1, pet_id: nil} # as we expected there is no id.
# What solutions do we have? Use nested creation!
# Good
up = UserPet.new(user_id: u.id, type: "dog")
# even better
up = u.user_pets.new(type: "dog")
# it's just a shortcut for the code above,
# it will add :user_id for us, so let's just remember it.
# Now lets add another 'new' from our creatd 'user_pet'
p = up.pets.new(name: "Millan")
user.save!
# => UserPet: {id: 3, user_id: 1, pet_id: 2, type: 'dog'} # => Pet: {id: 2, name: "Sam"}
# everything is working! YEY!
# we can even better, than writing in the beginning "User.create",
# we can write "User.new" and save it with all the nested elements.
You saw how this created all the ids for us? Let's move to something even more complex!
Now we have an additional table :shampoo that exactly as :user_pet, belongs to a :pet and a :user
We need to create it without knowing the id of the :user and :pet
u = User.new('Mike')
up = u.user_pets.new(type: "cat")
p = up.pets.new(name: "Rowe")
# But what are we doing now?
# If we do:
s = u.shampoos.new(name: "Dirty Job")
# => Shampoo: {user_id: 2, pet_id: nil, name: "..."}
# We get "pet_id: nil", not what we want.
# Or if we do:
s = p.shampoos.new(name: "Schneewittchen")
# => Shampoo: {user_id: nil, pet_id: 3, name: "..."}
# We get "user_id: nil", in both cases we do not get what we want.
# So we need to get the id of not created record, again.
# For this we can just do as in the first example (order is not important)
s = u.shampoos.new(name: "Mission Impossible")
# => Shampoo: {id: 3, user_id: 2, pet_id: nil, name: "..."}
s.pet = p # this will give the missing id, to the shampoo on save.
# Finish with save of the object:
u.save! #=> Shampoo: {id: 3, user_id: 2, pet_id: 3, name: '...'} # => Pet: ...
# Done!
I tried to cover most common causes when you need element id, and how to overcome this. I hope it will be useful.
I don't believe there are any workarounds since the id is actually generated by the database itself. The id should not be available until after the object has been saved to the database.
Consider doing what you want right after the instance is created.
after_create do
print self.id
end
First understand the structure of database.
Id is generated using sequence
increment done by 1 (specified while creating sequence)
Last entry to database will have highest value of id
If you wanted to get id of record which is going to be saved,
Then you can use following:
1. id = Model.last.id + 1
model = Model.new(id: id)
model.save
# But if data can be delete from that dataabse this will not work correctly.
2. id = Model.connection.select_value("Select nextval('models_id_seq')")
model = Model.new(id: id)
model.save
# Here in this case if you did not specified 'id' while creating new object, record will saved with next id.
e.g.
id
=> 2234
model = Model.new(id: id)
model.save
# Record will be created using 'id' as 2234
model = Model.new()
model.save
# Record will be created using next value of 'id' as 2235
Hope this will help you.
I just ran into a similar situation when creating a data importer. I was creating a bunch of records of different types and associating them before saving. When saving, some of the records threw validation errors because they had validate_presence_of a record that was not yet saved.
If you are using postgres, active record increments the id it assigns to a Model by keeping a sequence named models_id_seq (sales_id_seq for Sale etc.) in the database. You can get the next id in this sequence and increment it with the following function.
def next_model_id
ActiveRecord::Base.connection.execute("SELECT NEXTVAL('models_id_seq')").first["nextval"].to_i
end
However, this solution is not good practice as there is no guarantee that active record will keep id sequences in this way in the future. I would only use this if it was used only once in my project, saved me a lot of work and was well documented in terms of why it should not be used frequently.
I know it's an old question, but might as well throw my answer in case anyone needs to reference it
UserModel
class User < ActiveRecord::Base
before_create :set_default_value
def set_default_value
self.value ||= "#{User.last.id+1}"
end

Resources