in rspec, why do I need the '[]' in expect().to eq([])? - ruby-on-rails

Rails app, writing specs:
RSpec.describe AdvertisementsController, :type => :controller do
let(:my_ad) { Advertisement.create!(title: 'title', copy: 'copy text', price: 10)}
describe 'GET #index' do
...
...
it 'renders my_ad' do
get :index, {id: my_ad.id}
expect(assigns[:advertisements]).to eq(my_ad)
end
end
...
...
end
I wrote the above, which gave the error below.
1) AdvertisementsController GET #index renders my_ad
Failure/Error: expect(assigns[:advertisements]).to eq(my_ad)
expected: #<Advertisement id: 1, title: "title", copy: "copy text", price: 10, created_at: "2016-02-26 02:39:20", updated_at: "2016-02-26 02:39:20">
got: #<ActiveRecord::Relation [#<Advertisement id: 1, title: "title", copy: "copy text", price: 10, created_at: "2016-02-26 02:39:20", updated_at: "2016-02-26 02:39:20">]>
(compared using ==)
Diff:
## -1,8 +1,8 ##
-#<Advertisement:0x007ff6995e19f0
- id: 1,
- title: "title",
- copy: "copy text",
- price: 10,
- created_at: Fri, 26 Feb 2016 02:39:20 UTC +00:00,
- updated_at: Fri, 26 Feb 2016 02:39:20 UTC +00:00>
+[#<Advertisement:0x007ff6994f9560
+ id: 1,
+ title: "title",
+ copy: "copy text",
+ price: 10,
+ created_at: Fri, 26 Feb 2016 02:39:20 UTC +00:00,
+ updated_at: Fri, 26 Feb 2016 02:39:20 UTC +00:00>]
With this code, the test will pass. All it seems to do is add [..] around the variable, as such (| & ^ to emphasize location):
Why does that work?
RSpec.describe AdvertisementsController, :type => :controller do
let(:my_ad) { Advertisement.create!(title: 'title', copy: 'copy text', price: 10)}
describe 'GET #index' do
...
...
it 'renders my_ad' do
get :index, {id: my_ad.id}
expect(assigns[:advertisements]).to eq([my_ad])
end ^ ^
end | |
...
...
end
of note I can see that the objects have different identifying id's, so I think that's part of the reason and the [..]'s either ignore the mismatch or something, but I'd like to understand it.
-#<Advertisement:0x007ff6995e19f0 vs . +[#<Advertisement:0x007ff6994f9560

[] is Ruby syntax for an array. So your test is expecting to assign an array of advertisements (or in this case, something that behaves like an array, such as an ActiveRecord::Relation), containing just one element, my_ad.
Your code also sounds quite strange in that you're providing an ID to the index action, expecting only that record to be returned. Index actions are for listing out groups of records - a show action is for showing the details of a single record.

Related

rails PaperTrail gem causing duplicate version entries starting with version 6

Here is a piece of rspec code defining a risk_action :
let(:risk_action) {
risk_action = risk.risk_actions.new({name: 'action 1', user: user, action_status_id: created.id, due_date: '2018-02-10', owner_email: user.email, org: org})
risk_action.org = org
risk_action.assigned_to = user
risk_action.save!
risk_action
}
Both in rails 4 & 5, this behaves as I expect (it creates and persists the risk_action without error). Later in the rspec, I have a test case which runs a controller method and eventually calls this risk_action. I can confirm nothing has manipulated the object in the meantime (largely because my test case which is checking it via papertrail is working correctly) :
it "correctly assigns the Paper_Trail whodunnit type", versioning: true, focus: true do
session = RiskActionSession.create({risk_action: risk_action})
expect(session).to be_valid
expect(session.status).to eq('CREATED')
jwt = session.generate_jwt
#request.env["HTTP_AUTHORIZATION"] = "JWT #{jwt}" # <-- DOES NOT WORK
get :update_action, id: session.id, risk_action: {action_status_id: closed.id}, token: jwt
expect(response.status).to eq(200)
# there should be 2 versions [create, update]
versions = risk_action.versions
expect(versions.length).to eq(2)
# 1 update event
ver = versions.where(event: "update")
expect(ver.length).to eq(1)
#whodunnit_type should be set to 'User'
expect(ver[0].whodunnit_type).to eq("User")
end
end #describe "update_action [PUT]" do
Then, after retrieving the risk_action and calling versions on it, I would expect to see a single create entry. Instead, I see two, precise duplicated except for the id (of the version object) :
[#<PaperTrail::Version:0x0000000006b1a930
id: 24328,
item_type: "RiskAction",
item_id: 44074,
event: "create",
whodunnit: "543076",
object: nil,
created_at: Mon, 15 Aug 2022 16:14:45 UTC +00:00,
object_changes:
"---\nname:\n- \n- action 1\nuser_id:\n- \n- 543076\naction_status_id:\n- \n- 66607\ndue_date:\n- \n- '2018-02-10 00:00:00'\nowner_email:\n- ''\n- foo1#example.com\norg_id:\n- \n- 642273\nassigned_to_id:\n- \n- 543076\nassigned_to_type:\n- \n- User\nid:\n- \n- 44074\n",
whodunnit_type: nil,
comments: nil,
transaction_id: 24322>,
#<PaperTrail::Version:0x0000000006b1a430
id: 24329,
item_type: "RiskAction",
item_id: 44074,
event: "create",
whodunnit: "543076",
object: nil,
created_at: Mon, 15 Aug 2022 16:14:45 UTC +00:00,
object_changes:
"---\nname:\n- \n- action 1\nuser_id:\n- \n- 543076\naction_status_id:\n- \n- 66607\ndue_date:\n- \n- '2018-02-10 00:00:00'\nowner_email:\n- ''\n- foo1#example.com\norg_id:\n- \n- 642273\nassigned_to_id:\n- \n- 543076\nassigned_to_type:\n- \n- User\nid:\n- \n- 44074\n",
whodunnit_type: nil,
comments: nil,
transaction_id: 24322>]
This behaviour starts occurring with the papertrail gem version 6 on rails 4.2.10. The behaviour continues into rails 5.2.8 and papertrail gem versions 9 thru 11, the latter of which is where I'm trying to end up.
I am not sure why the behaviour begins here, there is nothing in the changelog (at least, that's obvious to me) why this would be the case. Any help is appreciated -- thank you.

Rails, how to sort a collection keeping a specific value at the end

I have an active record collection of some objects...
#<CoverElement:0x00007f87a4d78718
id: 312,
title: "Title 1",
link: "",
coverable_id: 35001,
coverable_type: "Article",
created_at: Thu, 07 May 2020 16:55:00 CEST +02:00,
updated_at: Thu, 07 May 2020 16:55:00 CEST +02:00,
cover_id: 4,
format: "small",
custom_image: nil,
position: 2,
second_title: nil,
second_title_url: nil>,
#<CoverElement:0x00007f87a4d6fde8
id: 313,
title: "Title 2",
link: "",
coverable_id: 35010,
coverable_type: "Article",
created_at: Thu, 07 May 2020 16:55:00 CEST +02:00,
updated_at: Sun, 22 Nov 2020 19:33:39 CET +01:00,
cover_id: 4,
format: "horizontal",
custom_image: nil,
position: 3,
second_title: nil,
second_title_url: nil>
For the desktop version of the website I use the position as order, but I need another order for the mobile version.
I need to sort by format and "small" must be the last.
Basically formats can be: "large", "vertical", "horizontal" and "small".
I try with
#elements.reorder('format ASC')
and it works but of course it keep the order "horizontal", "large", "vertical" and "small"
Every object with "small" as format must be at the end. Is there a way to do this or I have to write a custom sort method?
you will need another sort method for the mobile version. However if you sort by format, it will do an alphanumeric sort, which will not give you the results you want ("small" is last).
There are a couple of ways to get the order you want:
first way is to recast the format column as an enum, so that the database contains integer values iso strings:
class CoverElement < ActiveRecord::Base
enum format: [ :large, :vertical, :horizontal, :small ]
end
if this is not possible, you can do it with an sql snippet something like this:
order = <<-ORD.squish
CASE
WHEN 'format'='large' THEN 4
WHEN 'format'='vertical' THEN 3
WHEN 'format'='horizontal' THEN 2
WHEN 'format'='small' THEN 1
END
ORD
CoverElement.order(order)

Rails 5: How do I point fixtures to other fixtures?

I three models Comment, User and Project. Project and Comment need to point to other objects in order to be valid. For example, a comment needs to point to an author (user) and a project.
The associated fixture files look like this:
# comments.yml
test_comment:
author: users(:test_user)
project: projects(:test_project)
# users.yml
test_user:
name: 'test user'
# projects.yml
test_project:
name: 'Test'
description: 'This is a test'
owner: users(:test_user)
However, I've found that my fixtures are probably set up incorrectly. Rails returns false if I try to save the comment:
assert_equal true, comments(:test_comment)
#=> false
I can see that there are foreign keys for a project and author:
=> #<Comment:0x00007f9b1661f3d8
id: 137725605,
body: "",
project_id: 745075726,
author_id: "31ceee04-5307-5059-91db-0dc2068a780c",
created_at: Fri, 22 Feb 2019 13:17:58 UTC +00:00,
updated_at: Fri, 22 Feb 2019 13:17:58 UTC +00:00>
But when I interrogate them, Rails returns nil.
> comments(:test_comment).author
=> nil
> comments(:test_comment).project
=> nil
I expected that one would return users(:test_user) and the other would return projects(:test_project). I thought perhaps I needed to use ERB in my yaml:
test_comment:
author: <%= users(:test_user) %>
project: <%= projects(:test_project) %>
But that results is a stream of errors when I run my tests:
NoMethodError: undefined method `users' for #<#<Class:0x00007f9b17692ff8>:0x00007f9b17692dc8>
What do I need to do to point fixtures to other fixtures? Can it be done? What have I done wrong?
In the Rails guide on Testing with YAML fixtures, you can see that you don't need users(:test_user) to refer to some other object. Instead, you can simply write test_user:
# comments.yml
test_comment:
author: test_user
project: test_project
# users.yml
test_user:
name: 'test user'
# projects.yml
test_project:
name: 'Test'
description: 'This is a test'
owner: test_user
Hope this helps!

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 can I print each element of an array on its own line in the Rails console?

When I run the Rails console, how can I display each item on its own line? Instead of
> Post.all
=> #<ActiveRecord::Relation [#<Post id: 1, title: "Post #0", comment: nil, link: "http://yahoo.com", user_id: 1, created_at: "2013-09-30 02:29:28", updated_at: "2013-09-30 02:29:28">, #<Post id: 2, title: "Post #1", comment: nil,...
it would display as
> Post.all
=> #<ActiveRecord::Relation [
#<Post id: 1, title: "Post #0", comment: nil, link: "http://yahoo.com", user_id: 1, created_at: "2013-09-30 02:29:28", updated_at: "2013-09-30 02:29:28">,
#<Post id: 2, title: "Post #1", comment: nil,...
Similar to x in Perl debugger. I tried
Post.all.each{|e| e.inspect + "\n"}
But that only made it worse, and wasn't very convenient.
I saw Ruby on Rails: pretty print for variable.hash_set.inspect ... is there a way to pretty print .inpsect in the console? and https://github.com/michaeldv/awesome_print
but that doesn't seem to work
irb(main):005:0> require "awesome_print"
=> false
irb(main):006:0> ap Post.all
#<ActiveRecord::Relation [#<Post id: 1, title: "Post #0",
Try:
Post.all.each {|e| puts e.inspect }
Thing to notice here is that puts function automatically adds a newline character after the statement, and if you instead use print it will function in a similar manner as puts without the newline character at the end.
If you are using awesome_print, try:
ap Post.all.to_a
Further, when you issue the first command, the output will be repeated at the end (as per your comment) to show the output of the current expression. You can suppress it by appending a ; (semi-colon) at the end of the command, like this:
Post.all.each { |e| puts e.inspect };
Try:
> puts Post.all.map(&:inspect).join("\n")

Resources