How to validate :created_at in model (RAILS 3) - ruby-on-rails

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

Related

factory_bot build_stubbed strategy

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 # => []

Check if DateTime value is today, tomorrow or later

I have an object attribute of the DateTime class.
How would I understand if the saved date is today, tomorrow or else later?
Here are some useful ways to achieve it:
datetime = DateTime.now => Sun, 26 Oct 2014 21:00:00
datetime.today? # => true
datetime.to_date.past? # => false (only based on date)
datetime.to_date.future? # => false (only based on date)
datetime.to_date == Date.tomorrow # => false
datetime.to_date == Date.yesterday # => false
Something like...
datetime = Time.now.to_datetime
=> Sun, 26 Oct 2014 16:24:55 -0600
datetime >= Date.today
=> true
datetime < Date.tomorrow
=> true
datetime += 1.day
=> Mon, 27 Oct 2014 16:25:12 -0600
datetime >= Date.today
=> true
datetime >= Date.tomorrow
=> true
datetime < (Date.tomorrow + 1.day)
=> false
?
yesterday? & tomorrow? (Rails 6.1+)
Rails 6.1 adds new #yesterday? and #tomorrow? methods to Date and Time classes.
As a result, now, your problem can be solved as:
datetime = DateTime.current
# => Mon, 16 Nov 2020 20:50:16 +0000
datetime.today?
# => true
datetime.yesterday?
# => false
datetime.tomorrow?
# => false
It is also worth to mention that #yesterday? and #tomorrow? are aliased to #prev_day? and #next_day?.
Here is a link to the corresponding PR.

Validation doesn't work (Rails 3)

In model/item.rb I have custom validation method
validate :double_dates
after_save :double_check
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
Validation method does work in "rails c". But, when I run two task at the same time (rails runner "ApplicationController.rotate" I have Item.save in rotate method) - validation stops working.
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
irb(main):048:0> i1.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):049:0> i2.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):050:0> i1.created_at.usec => 311714
irb(main):051:0> i2.created_at.usec => 312779
Any hints or advice would be appreciated.
I had a similar problem once, and guess you could actually solve this problem adding indexes to the table. Add an index to the created_at column in Items.
Here you've got a full explanation about indexing in rails.
Hope it helps!
Thanks to #FabKremer I was able to solve it :)
by adding #i.created_at = Time.now.change(usec: 0) before Item.save :)
Nevertheless I'm accepting his answer, cuz it'll work for most cases.

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

Why does Date.yesterday counts as Date.today also?

I have the following model and methods:
class UserPrice < ActiveRecord::Base
attr_accessible :price, :purchase_date,
def self.today
where(:purchase_date => Date.today)
end
def self.yesterday
where(:purchase_date => Date.yesterday)
end
Why on my form if I give my date_select field :purchase_date 1/4/2012(yesterday method) it also counts as today(today method) and if I give it 1/5/2012 (today) it is nil?
P.S. I am using Rails 3.0.10 with PostgreSQL.
Update
This is my console returns:
$ rails console --s
Loading development environment in sandbox (Rails 3.0.10)
Any modifications you make will be rolled back on exit
irb(main):001:0> Date.today
=> Wed, 04 Jan 2012
irb(main):002:0> Time.now
=> 2012-01-04 21:28:07 -0500
irb(main):003:0> Time.zone.now
=> Thu, 05 Jan 2012 02:28:18 UTC +00:00
irb(main):004:0> Date.yesterday
=> Wed, 04 Jan 2012
irb(main):005:0>
Now yesterday is screwed up, makes no sense....
This is happening because calculations.rb is calling the "current" method of the Date class for the configured timezone (Defaults to UTC).
If you open the rails console you can see the date at which "Date.yesterday" is calculating on by doing:
Time.zone.today
This will probably show you tomorrow's date. So Date.yesterday for what rails sees as today, is today. ;)
You can work with the Date library more directly by doing:
Date.today.advance(:days => -1)
This will give you yesterday's date like you expect whereas active_support is returning:
Date.current.advance(:days => -1)
If you use the Time class, you'll have access to UTC (or "zulu") time zone rather than using the environment's time zone in Date class.
class UserPrice < ActiveRecord::Base
attr_accessible :price, :purchase_date,
def self.today
where(purchase_date: today_utc_date)
end
def self.yesterday
where(purchase_date: today_utc_date.yesterday)
end
private
def today_utc_date
#today ||= Time.zone.today
end
end
Also, if you need to process an outside date, for example params[:purchase_date]:
Time.parse(params[:purchase_date]).utc.to_date

Resources