Rspec let variable producing weird result - ruby-on-rails

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.

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.

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.

How to correctly use Devise + Confirmable + fixtures

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

Most proper way to use inherited classes with shared relations?

I have the TestVisual class that is inherited by the Game class :
class TestVisual < Game
include MongoMapper::Document
end
class Game
include MongoMapper::Document
belongs_to :maestra
key :incorrect, Integer
key :correct, Integer
key :time_to_complete, Integer
key :maestra_id, ObjectId
timestamps!
end
As you can see it belongs to Maestra.
So I can do Maestra.first.games which returns []
But I can not to Maestra.first.test_visuals because it returns undefined method test_visuals
Since I'm working specifically with TestVisuals, that is ideally what I would like to pull, but still have it share the attributes of its parent Game class.
Is this possible with Mongo. If it isn't or if it isn't necessary, is there any other better way to reach the TestVisual object from Maestra and still have it inherit Game ?
Single Collection Inheritance (SCI) in MongoMapper auto-generates selection,
ex., the following produce the same results.
p Game.where(_type: 'TestVisual').all
p TestVisual.all
See also mongomapper/lib/mongo_mapper/plugins/sci.rb - MongoMapper::Plugins::Sci::ClassMethods#query
However, MongoMapper does not auto-generate associations for subclasses based on the base class' associations,
and I don't think that this should be expected.
Note that SCI places subclasses and base classes in the same MongoDB collection.
If this is not what you want, you should consider other mechanisms for modularity.
You can define the following method yourself for an association accessor method, perhaps this is sufficient for your purposes?
For other association methods like append or push, the parent methods are probably workable.
class Maestra
include MongoMapper::Document
key :name, String
many :games
def test_visuals
games.where(_type: 'TestVisual')
end
end
test/unit/test_visual_test.rb
require 'test_helper'
def ppp(obj)
puts obj.inspect.gsub(/, ([^#])/, ",\n\t\\1").gsub(/, #/, ",\n #")
end
class TestVisualTest < ActiveSupport::TestCase
def setup
Maestra.delete_all
Game.delete_all
end
test "inheritance" do
maestra = Maestra.create(name: 'Fiona')
maestra.games << Game.create(incorrect: 1, correct: 9, time_to_complete: 60)
maestra.games << TestVisual.create(incorrect: 2, correct: 8, time_to_complete: 61)
ppp maestra.games.to_a
ppp maestra.test_visuals.to_a
end
end
output
Run options: --name=test_inheritance
# Running tests:
[#<Game _id: BSON::ObjectId('4ff7029a7f11ba6e43000002'),
_type: "Game",
correct: 9,
created_at: Fri,
06 Jul 2012 15:22:02 UTC +00:00,
incorrect: 1,
maestra_id: BSON::ObjectId('4ff7029a7f11ba6e43000001'),
time_to_complete: 60,
updated_at: Fri,
06 Jul 2012 15:22:02 UTC +00:00>,
#<TestVisual _id: BSON::ObjectId('4ff7029a7f11ba6e43000003'),
_type: "TestVisual",
correct: 8,
created_at: Fri,
06 Jul 2012 15:22:02 UTC +00:00,
incorrect: 2,
maestra_id: BSON::ObjectId('4ff7029a7f11ba6e43000001'),
time_to_complete: 61,
updated_at: Fri,
06 Jul 2012 15:22:02 UTC +00:00>]
[#<TestVisual _id: BSON::ObjectId('4ff7029a7f11ba6e43000003'),
_type: "TestVisual",
correct: 8,
created_at: Fri,
06 Jul 2012 15:22:02 UTC +00:00,
incorrect: 2,
maestra_id: BSON::ObjectId('4ff7029a7f11ba6e43000001'),
time_to_complete: 61,
updated_at: Fri,
06 Jul 2012 15:22:02 UTC +00:00>]
.
Finished tests in 0.026661s, 37.5080 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips

Resources