How to correctly use Devise + Confirmable + fixtures - ruby-on-rails

I'm using Devise + Confirmable for user authentication and Minitest + Capybara + fixtures for testing. I can make working logged in tests for users as long as I included some form of login (either with the helper login_as(#user) or going to the login page with Capybara) and the line#user.confirm before running.
How can I confirm users in the fixture itself though so I don't have to include this line every time.
Right now I have:
confirmed_at: Time.now
in the yml, but it doesn't seem to make a difference.
Here is a working sample test, if it's useful for illustration:
def setup
#user = users(:user)
end
test 'user should be redirected to profile edit on first login' do
#user.confirm
visit(new_user_session_path)
fill_in('user_email', :with => #user.email)
fill_in('user_password', :with => 'foobar')
click_button('Log in')
assert_current_path(edit_user_registration_path)
end
and the user fixture:
user:
email: test1#example.com
confirmed_at: Time.now
encrypted_password: <%= Devise::Encryptor.digest(User, 'foobar') %>
sign_in_count: 0

I updated my answer. The solution of the problem is found here. You need to configure it as in the guide and call the method user.confirm! inside the module ControllerMacros method def login_user
https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-(and-RSpec)#controller-specs
Open source github page of a complete Devise Rspec Project including testing
https://github.com/RailsApps/rails-devise/blob/master/spec/features/users/sign_in_spec.rb
you are trying to set
User.confirmed_at = Time.now
but confirmed_at has datetime datatype at least in my postegresql db, so this may depend on the db you are using.
confirmed_at: datetime
So this is the Time.now format
pry(main)> Time.now
=> 2017-03-14 11:14:06 +0100
While this is is the User.confirmed_at format
user.confirmed_at
=> Sun, 05 Mar 2017 15:05:03 UTC +00:00
So you should use a compatible format/variable, try to search the DateTime class for a compatible format that includes the UTC as DateTime.now returns:
[40] pry(main)> DateTime.now
=> Tue, 14 Mar 2017 11:19:25 +0100
DateTime has a utc() method. If I run this it is almost what is needed.
DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
=> 2005-02-21 16:11:12 UTC
Check the DateTime api and if needed you can check the utc() method on github
DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
# File activesupport/lib/active_support/core_ext/date_time/calculations.rb, line 168
def utc
utc = new_offset(0)
Time.utc(
utc.year, utc.month, utc.day,
utc.hour, utc.min, utc.sec + utc.sec_fraction
)
end
http://api.rubyonrails.org/classes/DateTime.html#method-i-utc

Related

Rspec let variable producing weird result

I am having a weird issue with RSpec that I don't quite understand.
This is my port_stock_spec.rb file:
# == Schema Information
#
# Table name: port_stocks
#
# id :bigint(8) not null, primary key
# portfolio_id :integer
# stock_id :integer
# volume :integer
# transaction_price :float
# current_price :float
# percent_change :float
# created_at :datetime not null
# updated_at :datetime not null
# current_value :float
# dollar_change :float
# total_spend :float
# transaction_date :datetime
# action :integer
# position :integer default("open")
# ticker :string
# slug :string
#
require 'rails_helper'
RSpec.describe PortStock, type: :model do
let(:stock) { create(:stock, price: 10.00) }
let(:portfolio) { create(:portfolio) }
let(:port_stock_1) { create(:port_stock, stock: stock, portfolio: portfolio, transaction_price: stock.price, action: :buy, volume: 100) }
context "associations" do
it { should belong_to(:portfolio) }
it { should belong_to (:stock) }
end
context "methods" do
it "should accurately calculate the positive percent_change of the current PortStock" do
port_stock_1.current_price = 20.00
expect(port_stock_1.calculate_percent_change).to eql 100.00
end
it "should accurately calculate the negative percent_change of the current PortStock" do
port_stock_1.current_price = 5.00
expect(port_stock_1.calculate_percent_change).to eql(-50.00)
end
it "should accurately calculate the negative dollar_change of the current PortStock" do
port_stock_1.current_price = 5.00
port_stock_1.volume = 1000
expect(port_stock_1.calculate_dollar_change).to eql (-5000.00)
end
# more specs that may or may no interact with the let variables.
it "should accurately calculate the portfolio's initial_dollar_value" do
expect(portfolio.initial_dollar_value).to eql 1000.00
end
end
Then I have the following method on my portfolio.rb model:
def calculate_portfolio_initial_dollar_value
if self.portfolio.initial_dollar_value.nil?
self.portfolio.initial_dollar_value = 0.0
end
self.portfolio.initial_dollar_value += (self.transaction_price * self.volume)
self.portfolio.save!
end
When I run my test suite, that last test keeps failing, when it shouldn't:
Failures:
1) PortStock methods should accurately calculate the portfolio's initial_dollar_value
Failure/Error: expect(portfolio.initial_dollar_value).to eql 1000.00
expected: 1000.0
got: 798229.0
(compared using eql?)
# ./spec/models/port_stock_spec.rb:77:in `block (3 levels) in <top (required)>'
Finished in 5.05 seconds (files took 3.68 seconds to load)
29 examples, 1 failure, 19 pending
So I put a binding.pry within the it blocks of the last few tests and when I check the portfolio.initial_dollar_value it repeatedly changes the value.
[1] pry(#<RSpec::ExampleGroups::PortStock::Methods>)> portfolio
=> #<Portfolio:0x00007fcdc5c5db28
id: 14,
user_id: 7,
current_dollar_value: 2864770.0,
percent_change: 75.02,
created_at: Sat, 13 Apr 2019 00:36:24 UTC +00:00,
updated_at: Sat, 13 Apr 2019 00:36:24 UTC +00:00,
num_winners: 2,
num_losers: 7,
initial_dollar_value: 860679.0,
dollar_change: 92865.0>
[2] pry(#<RSpec::ExampleGroups::PortStock::Methods>)> port_stock_1.portfolio
=> #<Portfolio:0x00007fcdc5c5db28
id: 14,
user_id: 7,
current_dollar_value: 150.0,
percent_change: -85.0,
created_at: Sat, 13 Apr 2019 00:36:24 UTC +00:00,
updated_at: Sat, 13 Apr 2019 00:36:42 UTC +00:00,
num_winners: 0,
num_losers: 1,
initial_dollar_value: 1000.0,
dollar_change: -850.0>
[3] pry(#<RSpec::ExampleGroups::PortStock::Methods>)> portfolio
=> #<Portfolio:0x00007fcdc5c5db28
id: 14,
user_id: 7,
current_dollar_value: 150.0,
percent_change: -85.0,
created_at: Sat, 13 Apr 2019 00:36:24 UTC +00:00,
updated_at: Sat, 13 Apr 2019 00:36:42 UTC +00:00,
num_winners: 0,
num_losers: 1,
initial_dollar_value: 1000.0,
dollar_change: -850.0>
[4] pry(#<RSpec::ExampleGroups::PortStock::Methods>)> portfolio
=> #<Portfolio:0x00007fcdc5c5db28
id: 14,
user_id: 7,
current_dollar_value: 150.0,
percent_change: -85.0,
created_at: Sat, 13 Apr 2019 00:36:24 UTC +00:00,
updated_at: Sat, 13 Apr 2019 00:36:42 UTC +00:00,
num_winners: 0,
num_losers: 1,
initial_dollar_value: 1000.0,
dollar_change: -850.0>
[5] pry(#<RSpec::ExampleGroups::PortStock::Methods>)>
I don't understand why.
Thoughts?
Edit 1
This is portfolio.rb Factory:
FactoryBot.define do
factory :portfolio do
user
current_dollar_value { Faker::Number.number(7) }
percent_change { Faker::Number.decimal(2) }
num_winners { Faker::Number.number(1) }
num_losers { Faker::Number.number(1) }
initial_dollar_value { Faker::Number.number(6) }
dollar_change { Faker::Number.number(5) }
end
end
Edit 2
There is a callback on my port_stock.rb model that triggers methods related to portfolio_initial_dollar_value:
after_save :calculate_portfolio_initial_dollar_value
Also other callbacks that impact other aspects of the portfolio:
after_save :update_portfolio_current_dollar_value
after_save :update_portfolio_initial_dollar_value, if: (:total_spend_previously_changed? || :volume_previously_changed?)
def update_portfolio_current_dollar_value
self.portfolio.current_dollar_value = self.portfolio.port_stocks.open.map(&:current_value).sum
self.portfolio.save!
end
def update_portfolio_initial_dollar_value
self.portfolio.initial_dollar_value = self.portfolio.port_stocks.open.map { |ps| ps.volume * ps.transaction_price }.sum
self.portfolio.save!
end
Edit 3
For the full version both the model (port_stock.rb) & spec (port_stock_spec.rb) files, check out this gist. I didn't want to pollute SO with that full dump.
As #grzekos point out you never call stock or port_stock_1 during the execution of it "should accurately calculate the portfolio's initial_dollar_value" test.
Why? Because you used let to setup/create the test objects.
If you want to always setup/create stock, portfolio and port_stock_1 you can either use let! (RSpec documentation) or use a before block like this:
let(:stock) { create(:stock, price: 10.00) }
let(:portfolio) { create(:portfolio) }
let(:port_stock_1) { create(:port_stock, stock: stock, portfolio: portfolio, transaction_price: stock.price, action: :buy, volume: 100) }
before do
stock
portfolio
port_stock_1
end
Why do you see different numbers during debuging with pry?
In your first test you called the portfolio object, which was created with FactoryBot. The Factory asinged a random 6 digit number to the initial_dollar_value atttribute via Faker::Number.number(6).
In your second test you called port_stock_1.portfolio. Now the block of let(:port_stock_1) gets evaluated. This creates a PortStock object, which in its after_save method updates the initial_dollar_value of portfolio.
All subsequet calls of portfolio or port_stock_1.portfolio do not change the value of initial_dollar_value anymore.
Ok, so the failing test is:
it "should accurately calculate the portfolio's initial_dollar_value" do
expect(portfolio.initial_dollar_value).to eql 1000.00
end
Here I can see that you create the portfolio object and the initial_dollar_value is set (in the factory) to Faker::Number.number(6). Why do you expect it to be equal to 1000.00?
The stock or the port_stock_1 objects are never created when you run this particular test. Quoting the Rspec let documentation
Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked.

Rails app not following the timezone configured in config/application.rb

I'm new to configuring timezones and confused about a few points. Any advice on the correct configuration would be appreciated.
My understanding is that it's best practice to store all timestamps as UTC in the DB (I'm using PostgreSQL). On the other hand, in the actual app, I'd like to see the timestamps in my local timezone (JST +9:00).
I've configured config/application.rb like this:
module MyApp
class Application < Rails::Application
config.load_defaults 5.2
config.time_zone = 'Tokyo'
end
end
However, I'm not sure it's configured correctly, because I'm getting mixed results in the rails console. Time.zone and Time.now tell me the timezone is set to JST, but the created_at and updated_at timestamps are rendered as UTC when I do User.first.
User.first
#=> #<User id: 1, first_name: "Bob", last_name: "Smith", created_at: "2019-04-09 08:54:30", updated_at: "2019-04-09 08:54:30">
But then, the time is rendered as JST if I specifically ask for the created_at time:
User.first.created_at
#=> Tue, 09 Apr 2019 17:54:30 JST +09:00
Why are the timestamps being rendered as UTC unless I specifically ask for the time itself? Is this normal? The same phenomenon is happening for DateTime columns in my other tables as well.
All your dates seems to be the same, it's just how they are represented on different contexts.
This:
User.first
#=> #<User id: 1, first_name: "Bob", last_name: "Smith", created_at: "2019-04-09 08:54:30", updated_at: "2019-04-09 08:54:30">
renders the result of .inspect
This:
User.first.created_at
#=> Tue, 09 Apr 2019 17:54:30 JST +09:00
is the console guessing you want the date formated with the current time zone.
You could force some representation being explicit
User.first.created_at.to_formatted_s(:db) #should print the same as you see on the inspect
I18n.localize(User.first.created_at) #should localize the date with the default date format
I18n.localize(USer.first.created_at, format: :something) #should localize the date as the format you defined as ":something" on your locale file

Rails/Rspec - objects not existing until interacted with

I have the following code where I create some instances of records using FactoryBot:
describe "#force_recalculation_of_lab_container_labs" do
(1..5).each do |n|
let("lab_#{n}".to_sym) { create(:lab) }
let("lab_container_#{n}".to_sym) { create(:skill_path) }
let("lab_collection_#{n}".to_sym) do
create(:lab_collection, lab_container: eval("lab_container_#{n}"))
end
end
context 'when adding labs' do
it "starts with 0 labs" do
expect(lab_collection_1.labs.count).to eq(0)
end
(1..3).each do |n|
let("lab_collection_inclusion_#{n}") do
create(:lab_collection_inclusion,
included_item_id: eval("lab_#{n}").id,
included_item_type: 'Lab',
lab_collection_id: eval("lab_collection_1").id,
subscribed: 0)
end
end
it "updates the lab total correctly after adding labs" do
binding.pry
end
end
end
From my pry point, I receive the following:
LabCollectionInclusion.count
=> 0
lab_collection_1.lab_collection_inclusions.count
=> 0
When I then call a record individually, it appears to then exist:
lab_collection_inclusion_1
<LabCollectionInclusion:0x000055a45c985b10
id: 1,
included_item_id: 1,
included_item_type: "Lab",
lab_collection_id: 4,
subscribed: false,
created_at: Thu, 01 Nov 2018 10:48:00 UTC +00:00,
updated_at: Thu, 01 Nov 2018 10:48:00 UTC +00:00>
After which point it exists when searching:
LabCollectionInclusion.count
=> 1
lab_collection_1.lab_collection_inclusions.count
=> 1
Obviously I don't want to have to do this for every record so my 2 questions are first of all why is this happening, and second of all how to correct it?
Thanks in advance
This is expected behavior because let is designed to lazy-evaluated. Quote from the docs of let:
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.
As described in the documentation: Just let! (note the !) instead of let when you need the records to be created without calling them by their name first.

is it possible to override built-in Ruby methods?

I am working on a problem where I have to pass an rpsec test. The problem is that the method is using the same name as a built in ruby method .count
given that I cannot change the rspec test, is it possible to override .count to behave differently? if not, is there a better way to get around this?
here is the rspec test I am trying to pass
subject = FinancialSummary.one_day(user: user, currency: :usd)
expect(subject.count(:deposit)).to eq(2)
my code:
class FinancialSummary
def self.one_day(user: user, currency: currency)
one_day_range = Date.today.beginning_of_day..Date.today.end_of_day
find_transaction(user.id, currency).where(created_at: one_day_range)
end
def self.find_transaction(user_id, currency)
Transaction.where(user_id: user_id,
amount_currency: currency.to_s.upcase
)
end
end
output:
[#<Transaction:0x00007f9b39c2e9b8
id: 1,
user_id: 1,
amount_cents: 1,
amount_currency: "USD",
category: "deposit",
created_at: Sat, 10 Mar 2018 18:46:53 UTC +00:00,
updated_at: Sat, 10 Mar 2018 18:46:53 UTC +00:00>,
#<Transaction:0x00007f9b3d0dbc38
id: 2,
user_id: 1,
amount_cents: 2000,
amount_currency: "USD",
category: "deposit",
created_at: Sat, 10 Mar 2018 18:47:43 UTC +00:00,
updated_at: Sat, 10 Mar 2018 18:47:43 UTC +00:00>,
#<Transaction:0x00007f9b3d0b3fa8
id: 7,
user_id: 1,
amount_cents: 1200,
amount_currency: "USD",
category: "withdraw",
created_at: Mon, 05 Mar 2018 02:22:42 UTC +00:00,
updated_at: Tue, 06 Mar 2018 18:48:20 UTC +00:00>]
it is printing out, what I believe to be the correct information, up until the test attempts to count the transactions by their category: 'deposit'. Then I get this error message:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: deposit: SELECT COUNT(deposit) FROM "transactions" WHERE "transactions"."user_id" = ? AND "transactions"."amount_currency" = ?
EDITED FOR MORE INFO
Some Assumptions Were Made in the Writing of this answer and modifications may be made based on updated specifications
Overriding count is a bad idea because others who view or use your code will have no idea that this is not the count they know and understand.
Instead consider creating a scope for this like
class FinancialSummary < ApplicationRecord
scope :one_day, ->(user:,currency:) { where(user: user, currency: currency) } #clearly already a scope
scope :transaction_type, ->(transaction_type:) { where(category: transaction_type) }
end
then the test becomes
subject = FinancialSummary.one_day(user: user, currency: :usd)
expect(subject.transaction_type(:deposit).count).to eq(2)
SQL now becomes:
SELECT COUNT(*)
FROM
"transactions"
WHERE
"transactions"."user_id" = ?
AND "transactions"."amount_currency" = "usd"
AND "transactions"."category" = "deposit"
Still very understandable and easy to read without the need to destroy the count method we clearly just used.
It's not clear what object the count message is being sent to because I don't know what FinancialSummary.one_day(user: user, currency: :usd) returns, but it seems like you are saying count is a method on whatever it returns, that you can't change. What does FinancialSummary.one_day(user: user, currency: :usd).class return?
Perhaps one solution would be to alias it on that object by adding alias_method :count, :account_count and then in your test calling expect(subject.account_count(:deposit)).to eq(2)
It would be easier if you could post the FinancialSummary#one_day method in your question.

Weird created_at behavior

I've set config.time_zone = 'UTC' in environment.rb, and yet still I get some weird behavior with Rails' built-in datetime fields:
>> Time.now
=> Sun Jun 21 17:05:59 -0700 2009
>> Feedback.create(:body => "testing")
=> #<Feedback id: 23, body: "testing", email_address: nil, name: nil, created_at: "2009-06-22 00:06:09", updated_at: "2009-06-22 00:06:09">
>> Time.parse(Feedback.last.created_at.to_s)
=> Mon Jun 22 00:06:09 UTC 2009
Any thoughts?
It looks like it's properly setting the timezone in the ActiveRecord object, so I don't think you need to worry too much. If you want to force your timestamp from Rails to use UTC, you can use Time.utc.
Time.now.utc
=> Mon Jun 22 00:54:21 UTC 2009

Resources