factory_bot build_stubbed strategy - ruby-on-rails

The factory_bot documentation for build strategies says:
factory_bot supports several different build strategies: build, create, attributes_for and build_stubbed
And continues with some examples of usage. However, it doesn't clearly state what the result of each one is. I've been using create and build for a while now. attributes_for seems straightforward from the description and I see some uses for it. However, what is build_stubbed? The description says
Returns an object with all defined attributes stubbed out
What does "stubbed out" mean? How is this different from either create or build?

Let's consider the difference on the example of these factories:
FactoryBot.define do
factory :post do
user
title { 'Post title' }
body { 'Post body' }
end
end
FactoryBot.define do
factory :user do
first_name { 'John' }
last_name { 'Doe' }
end
end
build
With build method everything is easy. It returns a Post instance that's not saved
# initialization
post = FactoryBot.build(:post)
# call
p post
p post.user
# output
#<Post:0x00007fd10f824168> {
:id => nil,
:user_id => nil,
:title => "Post title",
:body => "Post body",
:created_at => nil,
:updated_at => nil
}
#<User:0x00007f8792ed9290> {
:id => nil,
:first_name => "Post title",
:last_name => "Post body",
:created_at => nil,
:updated_at => nil
}
Post.all # => []
User.all # => []
create
With create everything is also quite obvious. It saves and returns a Post instance. But it calls all validations and callbacks and also creates associated instance of User
# initialization
post = FactoryBot.create(:post)
# call
p post
p post.user
# output
#<Post:0x00007fd10f824168> {
:id => 1,
:user_id => 1,
:title => "Post title",
:body => "Post body",
:created_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00,
:updated_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00
}
#<User:0x00007f8792ed9290> {
:id => 1,
:first_name => "John",
:last_name => "Joe",
:created_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00,
:updated_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00
}
Post record and associated user record were created in the database:
Post.all # => [<Post:0x00007fd10f824168> {...}]
# User also created in the database
User.all # => [<User:0x00007f91af405b30> {...}]
build_stubbed
build_stubbed imitates creating. It slubs id, created_at, updated_at and user_id attributes. Also it skips all validations and callbacks.
Stubs means that FactoryBot just initialize object and assigns values to the id created_at and updated_at attributes so that it just looks like created. For id it assign integer number 1001 (1001 is just default number what FactoryBot uses to assign to id), for created_at and updated_at assigns current datetime. And for every other record created with build_stubbed is will increment number to be assigned to id by 1.
First FactoryBot initialize user record and assign 1001 to id attribute but not save it to the database than it initialize post record and assing 1002 to the id attribute and 1001 to user_id attribute to make association, but also doesn't save record to the database.
See example below.
#initialization
post = FactoryBot.build_stubbed(:post)
# call
p post
p post.user
# output
# It looks like persisted instance
#<Post:0x00007fd10f824168> {
:id => 1002,
:user_id => 1001,
:title => "Post title",
:body => "Post body",
:created_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00,
:updated_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00
}
#<User:0x00007f8792ed9290> {
:id => 1001,
:first_name => "John",
:last_name => "Joe",
:created_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00,
:updated_at => Sat, 18 Jun 2022 05:32:17.122906000 UTC +00:00
}
Post and user records were not created in the database!!!
# it is not persisted in the database
Post.all # => []
# Association was also just stubbed(initialized) and there are no users in the database.
User.all # => []

Related

Rails class method used as scope with complex logic

In the system there are employees with login information in the User model and
other information about them in the Profile model.
We want to be able to display a list of employees who have an anniversary
this month (the month of hire is the same as the current one) and it is
their 1st, 2nd, or a multiple of 5 years on the job.
We want to use it like a scope, but since the logic is complex, we are making
a Class method. Trying to split the logic into small chunks is becoming messy.
I am sure that the code can be simplified.
The biggest issue is that instead of getting a list of only the employees with
an anniversary as a scope would do, I am getting a list of all the employees
as nil or their user info if it is their anniversary month.
An example:
irb_001 >> Profile.anniversary?
[
[0] nil,
[1] nil,
[2] #<User:0x007fd17c883740> {
:id => 3,
:first_name => "Sally",
:last_name => "Brown",
:email => "sally#peanuts.com",
:password_digest => "[redacted]",
:created_at => Tue, 21 Feb 2018 11:12:42 EST -05:00,
:updated_at => Sat, 25 Feb 2018 12:28:45 EST -05:00,
},
[3] nil,
[4] nil,
[5] #<User:0x007fd17a2eaf38> {
:id => 6,
:first_name => "Lucy",
:last_name => "Van Pelt",
:email => "lucy#peanuts.com",
:password_digest => "[redacted]",
:created_at => Tue, 20 Nov 2018 21:01:04 EST -05:00,
:updated_at => Tue, 20 Nov 2018 21:02:36 EST -05:00,
},
[6] nil
]
irb_002 >>
What is the best way to achieve the desired result and clean up this code?
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile, allow_destroy: true
after_create :create_matching_profile
delegate :active, to: :profile, prefix: true
private
def create_matching_profile
profile = build_profile
profile.save
end
end
class Profile < ActiveRecord::Base
belongs_to :user
def self.years_employed(profile)
# calculate how many years employed
#profile = profile
if #profile.employed_since?
(( Date.today.to_time - #profile.employed_since.to_time )/1.year.second).to_i
else
0
end
end
def self.anniversary_month(profile)
# get the month of hire
#profile = profile
#profile.employed_since? ? #profile.employed_since.month : 0
end
def self.anniversary?
# first, second, or multiple of five year anniversary month
#profiles = Profile.where("employed_since is not null")
#profiles.map do |profile|
if ( Date.today.month == anniversary_month(profile) )
#years_working = years_employed(profile)
if ( #years_working> 0 &&
( #years_working == 1 || #years_working == 2 || ( #years_working % 5 == 0 )))
result = true
else
result = false
end
else
result = false
end
profile.user if result
end
end
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# first_name :string
# last_name :string
# email :string
# password_digest :string
# created_at :datetime not null
# updated_at :datetime not null
#
# Table name: profiles
#
# id :integer not null, primary key
# user_id :integer
# active :boolean
# employed_since :date
# ...other attributes...
# created_at :datetime not null
# updated_at :datetime not null
#
employed since data from Profiles
[
[0] Sun, 01 Dec 1991,
[1] Thu, 01 May 2018,
[2] Wed, 01 Nov 2017,
[3] Wed, 01 Feb 2017,
[4] Thu, 01 Aug 2018,
[5] Fri, 01 Nov 2013,
[6] Fri, 01 Nov 1991
]
This can be done in a much simpler and more efficient way by using the date functions in the database and doing the comparison there.
class User < ApplicationRecord
has_one :profile
def self.anniversary
self.joins(:profile)
.where("EXTRACT(MONTH FROM profiles.employed_since) = EXTRACT(MONTH FROM now())")
.where("profiles.employed_since < ?", 1.year.ago)
.where(%q{
EXTRACT(year FROM now()) - EXTRACT(year FROM profiles.employed_since BETWEEN 1 AND 2
OR
CAST(EXTRACT(year FROM now()) - EXTRACT(year FROM profiles.employed_since) AS INTEGER) % 5 = 0
})
end
end
This example is written for Postgres and you might need to adapt it to your RDBMS.
Using SQLITE the where clause looks like:
where "strftime('%m',employed_since) = strftime('%m', date('now'))
AND employed_since < date('now','-1 year','+1 day')
AND ( (strftime('%Y','now') - strftime('%Y', employed_since)) BETWEEN 1 AND 2
OR (strftime('%Y','now') - strftime('%Y', employed_since)) % 5 = 0 )"
This actually works as a scope, no need for a class method as I originally thought.

Rspec rails : check if object.value is false

I'm testing a cron job in rails and I want to test if an attribute of object to be false by default, this the portion of code that do the job :
describe ".perform()" do
before :each do
SendReportCronJob.perform()
end
context "automatically_send_report" do
it "should be false by default" do
binding.pry()
expect( automatically_send_report.value ).to be "false"
end
end
context "time_limit" do
it "should not be nil" do
expect( time_limit_for_sending_report.value ).to_not be_nil
end
end
end
the problem is the automatically_send_report.value return a "false" value !
here the output of rails console when I do binding.pry() :
#<Setting:0x00000009990878> {
:id => "69617295-4209-4092-80cf-5934d1cf7d38",
:related_id => "cd830ace-933a-4230-ad54-bd94e63d5d7b",
:key => "automatically_send_report",
:value => "false",
:data_type => "boolean",
:is_archived => false,
:updated_by_id => nil,
:device_id => nil,
:created_at => Wed, 18 Apr 2018 18:34:35 +03 +03:00,
:updated_at => Wed, 18 Apr 2018 18:34:35 +03 +03:00
}
can I convert this value from string to boolean in the rspec file ?
You don't need to convert the string to boolean, you can compare strings as well, but in that case you need to use eq instead of be, as they are different objects with the same value:
expect(automatically_send_report.value).to eq "false"

How to validate :created_at in model (RAILS 3)

irb(main):044:0> i1.created_at
=> Thu, 24 Apr 2014 02:41:15 UTC +00:00
irb(main):045:0> i2.created_at
=> Thu, 24 Apr 2014 02:41:15 UTC +00:00
irb(main):046:0> i1.created_at == i2.created_at
=> false
irb(main):047:0> i1.created_at.to_time.to_i == i2.created_at.to_time.to_i
=> true
Seems not to work validates_uniqueness_of :created_at
because
irb(main):046:0> i1.created_at == i2.created_at
=> false
How to validate created_at? Don't want to save with the same date.
+++ UPDATE +++
irb(main):048:0> i1.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):049:0> i2.created_at.class
=> ActiveSupport::TimeWithZone
Since they might have different precision milliseconds.
Refer to the post: Testing ActiveSupport::TimeWithZone objects for equality
Chances are the millisecond values would be unequal.
puts i1.created_at.usec
puts i2.created_at.usec
I think, if you are getting concurrent requests, there are chances that you may have multiple entries in the table which are created at same time and will have same time stamps.
As you said, if you don't want to save with the same date, you can put a lock while saving the entries, removing the possibility of creating two entries at same time. In that case validates_uniqueness_of :created_at should also work.
Just in case
class Item < ActiveRecord::Base
attr_accessible :asin, :domain, :formatted_price, :user_id, :created_at
validate :double_dates
private
def double_dates
if Item.where(:user_id => self.user_id, :asin => self.asin, :domain => self.domain).where("DATE(created_at) = ?", Date.today).length >= 1
errors.add(:created_at, "no double dates")
end
end
end

Wrong boolean result comparing Date objects

I'm trying to write test that compare some dates. So far i have 2 tests, one of them works as intended, but the other one fails because doesnt/not correctly compare dates.
Here is my code:
def self.has_expired?(card, start_month, start_year, annually)
card_date = Date.new(card.year, card.month, -1)
billing_date = Date.new(start_year, start_month, -1)
if !annually
p '--------'
p card_date
p billing_date
card_date > billing_date
else
#return false
end
end
creditcard object
creditcard = ActiveMerchant::Billing::CreditCard.new(
:number => 1234567890123456
:month => 01,
:year => 13,
:first_name => 'John',
:last_name => 'Doe',
:verification_value => 132,
:brand => 'visa'
)
Here is output of p's
First block works as intended.
"--------"
Tue, 31 Jan 0013
Thu, 28 Feb 2013
false
Second block fails, expecting true, but got false
."--------"
Tue, 31 Jan 0013
Fri, 30 Nov 2012
false
Here is my rspec code
describe CreditCard do
context 'card_expired' do
it 'should return false with args passed to method (02month, 13 year, annually==false)' do
CreditCard.has_expired?(creditcard, 02, 2013, false).should == false
end
it 'should return true with args passed to method (11month, 12 year, annually==false)' do
CreditCard.has_expired?(creditcard, 11, 2012, false).should == true
end
end
end
in irb it works as charm, returning correct value(true/false)
I think the problem is in your logic. A card is expired when the expiration date is before the billing date, thus when
card_date < billing_date # expired
and not when
card_date > billing_date # valid
Also try puting in the full 2013 and see if that helps if it keeps breaking
:year => 2013,
You're also missing a comma after this line (probably a copy/paste error) :number => 1234567890123456

Rails 3: How to display an object in a readable format?

Displaying an object like this:
Job.find(4).inspect
results in:
#<Job id: 4, job_date: "2010-10-22", address: "some address", invoice_number: "b432", client_name: "Fred", client_email: "fred#yahoo.com", client_mobile_number: "12345678", ...>
which is not very readable.
Is there a simple way to display the object like this:
id: 4
job_date: "2010-10-22"
address: "some address"
invoice_number: "b432"
client_name: "Fred"
client_email: "fred#yahoo.com"
client_mobile_number: "12345678"
or some other readable way ?
Not sure where you are looking to display this, but for the console/debugger I recommend the awesome_print gem.
https://github.com/michaeldv/awesome_print
Printing a rails object example from gem readme:
ap Account.all(:limit => 2)
[
[0] #<Account:0x1033220b8> {
:id => 1,
:user_id => 5,
:assigned_to => 7,
:name => "Hayes-DuBuque",
:access => "Public",
:website => "http://www.hayesdubuque.com",
:toll_free_phone => "1-800-932-6571",
:phone => "(111)549-5002",
:fax => "(349)415-2266",
:deleted_at => nil,
:created_at => Sat, 06 Mar 2010 09:46:10 UTC +00:00,
:updated_at => Sat, 06 Mar 2010 16:33:10 UTC +00:00,
:email => "info#hayesdubuque.com",
:background_info => nil
},
[1] #<Account:0x103321ff0> {
:id => 2,
:user_id => 4,
:assigned_to => 4,
:name => "Ziemann-Streich",
:access => "Public",
:website => "http://www.ziemannstreich.com",
:toll_free_phone => "1-800-871-0619",
:phone => "(042)056-1534",
:fax => "(106)017-8792",
:deleted_at => nil,
:created_at => Tue, 09 Feb 2010 13:32:10 UTC +00:00,
:updated_at => Tue, 09 Feb 2010 20:05:01 UTC +00:00,
:email => "info#ziemannstreich.com",
:background_info => nil
}
]
The formatting is similar if you do raise Job.find(4).to_yaml

Resources