How to detect if Rails / Devise current_user is a guest user - ruby-on-rails

I've got a Rails app running with "real" users and "guest" users via devise (https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user).
Most of the time they can be used indistinguishably, but occasionally I want to be able to tell a "real" logged in user from a "guest" user who didn't log in or create an account yet.
Is there a standard way to do this? (There must be, right?)
Here's a sample of what I'm seeing in current_user:
=> #<User:0x007fb011620c78
id: 30,
email: "guest_143829190430#example.com",
encrypted_password:
"$2a$10$MJr8M3JA6/wYCoOz0oBTseoA3zVnLUceY7BeJLRnLgeXfKNMW/k/S",
reset_password_token: nil,
reset_password_sent_at: nil,
remember_created_at: nil,
sign_in_count: 1,
current_sign_in_at: Thu, 30 Jul 2015 16:31:44 CDT -05:00,
last_sign_in_at: Thu, 30 Jul 2015 16:31:44 CDT -05:00,
current_sign_in_ip: "127.0.0.1",
last_sign_in_ip: "127.0.0.1",
created_at: Thu, 30 Jul 2015 16:31:44 CDT -05:00,
updated_at: Thu, 30 Jul 2015 16:31:44 CDT -05:00,
first_name: "guest",
last_name: "guest",
I could look at first_name / last_name / email, but that feels flimsy, and I could look for session[:guest_user_id] in the session, and that seems like perhaps the way to go, but it just feels like there should be a built in .is_guest? or something, and I'd rather use that than write my own.
(My Google-ing and exploration of their codebase has not yet turned up anything... Might have more once I can pull their repo and grep...)
Thanks all!
Best I've got so far would be something like:
def is_guest?
current_user.id == session[:guest_user_id] if current_user && session[:guest_user_id]
end

Without delving into all of the available code, I would suggest that the beginnings of thecurrent_or_guest_user method describe the checks that are required. I would create a method from these along the lines of:
def is_guest_user?
# Someone is logged in AND (we have a guest id AND that id matches the current user)
current_user && (session[:guest_user_id] && session[:guest_user_id] == current_user.id)
end
I would add this to the User model. Based on that current_or_guest_user method I don't expect you'll find anything else built in (as surely they would have used it in that method).

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.

Using Acts As Votable for multiple database tables with Rails 5

I have been using https://github.com/ryanto/acts_as_votable gem as a Save button for Posts. It has been all good so far. 👍
However now I created a separate scaffold (Articles) and wanted to add the same Save button. So users can save Posts and Articles, then view in their profiles.
Now I got problem as some Article records has same id as Post records. Plus how do I even display Saved records now as I dont know what id comes from Article or Post. 🤔
Is there any way to solve this with Acts As Votable Gem?
Thank you! 🙏
The current version (0.12.0) of acts_as_voteable does this out of the box. The Vote model has a column votable_type which can be a reference to multiple models.
#<ActsAsVotable::Vote:0x00007f9f6558a9b0
id: 4,
votable_type: "Post",
votable_id: 1,
voter_type: "User",
voter_id: 2,
vote_flag: true,
vote_scope: "save",
vote_weight: 1,
created_at: Mon, 31 Dec 2018 13:39:34 UTC +00:00,
updated_at: Mon, 31 Dec 2018 13:39:34 UTC +00:00>,
#<ActsAsVotable::Vote:0x00007f9f6558a4d8
id: 5,
votable_type: "Article",
votable_id: 3,
voter_type: "User",
voter_id: 2,
vote_flag: true,
vote_scope: "article",
vote_weight: 1,
created_at: Tue, 01 Jan 2019 15:15:27 UTC +00:00,
updated_at: Tue, 01 Jan 2019 15:15:27 UTC +00:00>
To display saved records you can use a scope like
#user.votes.for_type(Post)
#user.votes.for_type(Article)
I hope this answers your question.

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

delayed_job missing method

I add a job to delayed job but when I run it I have a NoMethod error
Delayed::Job.last
=> #Delayed::Backend::MongoMapper::Job _id: BSON::ObjectId('5266f28aa1cba6257b000001'), attempts: 0, created_at: Tue, 22 Oct 2013 23:47:54 CEST +02:00, failed_at: nil, handler: "--- !ruby/struct:Delayed::PerformableMethod \nobject: !ruby/object:Module {}\n\nmethod: :notify_all_with_review_reminder_due!\nargs: []\n\n", last_error: nil, locked_at: nil, locked_by: nil, priority: 75, run_at: Tue, 22 Oct 2013 23:47:54 CEST +02:00, updated_at: Tue, 22 Oct 2013 23:47:54 CEST +02:00>
Delayed::Job.last.invoke_job
NoMethodError: undefined method `notify_all_with_review_reminder_due!' for #Module:0x124781cf0>
Looks like he is not able to serialize the correct object (it is a module).
Googling around I found the suggestion to require the module in config/inizializers but this is not working for me.
Any idea?
I had hit similar problem, and at time of writing this I have not found any other solution besides the "require" but in my case I was trying to queue the module class, so I just wrapped it with class and it wroks now.
before:
module GlobalModule
def self.some_method
end
end
Delayed::Job failing with similar error when calling GlobalModule::delay.some_method
after:
module GlobalModule
class Wrapper
def self.some_method
end
end
end
GlobalModule::Wrapper.delay.some_method

Resources