Rails ActiveSupport::Concern and method evaluation - ruby-on-rails

I have this application which uses Devise with current_user helper. When I create a module, current_user becomes nil after I mention an attribution to it even though it never happens.
class PagesController < ApplicationController
include ModuleTest
def index
a_test_method
end
end
And the ModuleTest:
module ModuleTest
extend ActiveSupport::Concern
def a_test_method
puts "(BEFORE)===========> #{current_user.inspect}"
current_user = nil if false
puts "(AFTER) ===========> #{current_user.inspect}"
end
end
Output:
(BEFORE)===========> #<User id: 1>
(AFTER) ===========> nil
However, if I delete/comment out this line # current_user = nil if false, current_user remains valid:
(BEFORE)===========> #<User id: 1>
(AFTER) ===========> #<User id: 1>
Would this be related to lazy evaluation somewhat?
EDIT
The whole problem relies on how Ruby defines variables when a statement is not evaluated:
2.3.4 (main):0 > defined? this_never_seen_variable_before
=> nil
2.3.4 (main):0 > this_never_seen_variable_before = "value" if false
=> nil
2.3.4 (main):0 > defined? this_never_seen_variable_before
=> "local-variable"
2.3.4 (main):0 >
2.3.4 (main):0 > this_never_seen_variable_before_2
NameError: undefined local variable or method `this_never_seen_variable_before_2' for main:Object
from (pry):119:in `<main>'
2.3.4 (main):0 > this_never_seen_variable_before_2 = "value" if false
=> nil
2.3.4 (main):0 > this_never_seen_variable_before_2
=> nil
2.3.4 (main):0 >
How does this work underneath?

current_user is a helper method provided by Devise, not a local variable.
There is no such helper method named current_user=. You can prove this by changing current_user = nil to self.current_user = nil and see it crash. But this is irrelevant to your issue.
So the result is, you defined a local variable current_user between the 2 puts, which shadows the helper method with the same name.
The weird thing is, although current_user = nil is not executed because of the if false, the local variable still gets defined, and its value is implicitly set to nil. This is why your second puts shows nil. Even if you change your current_user = nil to current_user = :someone, your second puts should still show nil.

Related

undefined method `set' for nil:NilClass in Rails even though similar code works in irb

The following code works fine in IRB (Interactive Ruby Shell):
require 'prometheus/client'
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
puts 'output: ' + test.to_s
2.4.0 :018 > load 'test.rb'
output: 123.0
=> true
2.4.0 :019 >
However, when I put the same code into my Ruby on Rails controller, the second time the user uses the application, the following error is returned:
undefined method `set' for nil:NilClass
Can someone tell me when I'm doing wrong? Thank you.
require 'prometheus/client'
class RandomnumbersController < ApplicationController
def index
#randomnumbers = Randomnumber.order('number DESC').limit(8)
#counter = 0
end
def show
#randomnumber = Randomnumber.find(params[:id])
end
def new
end
def create
#randomnumber = Randomnumber.new(randomnumber_params)
prometheus = Prometheus::Client.registry
begin
#requests = prometheus.gauge(:demo, 'Random number selected for this users turn.')
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
end
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
#randomnumber.save
redirect_to #randomnumber
end
private
def randomnumber_params
params.require(:randomnumber).permit(:name, :number)
end
end
Because there is no #requests for :demo argument.
When ORM cannot find any info in db it returns nil (NilClass)
and You're trying to do:
#requests.set({name: "test"}, 123)
it's interpreted like:
nil.set({name: "test"}, 123)
why it's causes this issue in second time?
cuz Your code changes #requests name attribute to be test and seems like :demo is not test or maybe in another part of Your app You're replacing/deleting data in database that makes: #requests = prometheus.gauge(:demo, 'Random number selected for this users turn.') to return nil
Solution:
in code level add this fixes to avoid such unpredictable situations (check for nil) :
unless #requests.nil?
#requests.set({name: "test"}, 123)
test = #requests.get name: "test"
end

Rails Cookie Issue

I have the following new method in a ruby on rails app:
def new
if cookies[:owner].empty?
cookies[:owner] = SecureRandom.hex
end
#movie = Movie.new
#movie.owner = cookies[:owner]
end
Basically, each new user is supposed to be issued a code which identifies them (though just by the cookie). So when the user creates a movie, the cookie that was created is stored in the owner field.
So two problems:
Using the .empty? method when I delete the cookie from the browser, returns a undefined methodempty?' for nil:NilClass`
When I do have a cookie already set in the browser, and then create a movie, the cookies[:owner] value is different from the #movie.owner code?
cookies[:owner] will either be nil (when it hasn't been set), or a String (when it's been set). The method you're looking for is blank?, instead of empty?
2.1.0 :003 > nil.blank?
=> true
2.1.0 :005 > "i'm not blank".blank?
=> false
2.1.0 :006 > " ".blank?
=> true
As for your second problem: where do you call the save method? Do you have any callback on the Movie model that could rewrite the owner attribute?
You could also use this.
def new
if !cookies[:owner]
cookies[:owner] = SecureRandom.hex
end
#movie = Movie.new
#movie.owner = cookies[:owner]
end

Ruby on rails, multiple check for nil attributes

I'm trying to check multiple attributes for nil, I've found this post simplify... but I'm not getting the results I want. I have a user whom I want to update their profile if needed. This user however has all the data I want.
#user.try(:age_id).nil?
#returns false
#user.try(:customer).nil?
#returns false
#user.try(:country).nil?
#returns false
#user.try(:age_id).try(:customer).try(:country).nil?
#returns true
Why is it responding with true here when all the other single instances of tries responds with false?
You are chaining the .try(), which fails after the try(:age_id):
It tries to call age_id on the #user object
if #user.nil? # => returns nil
if #user.age_id != nil # => returns a Fixnum
Then you call the method try(:customer) on a Fixnum which obviously fails # => returns nil
etc.
An example from the IRB console:
1.9.3p448 :049 > nil.try(:nothing).try(:whatever).try(:try_this_also).nil?
=> true
If you want to test that all of these attributes are not nil, use this:
if #user.present?
if #user.age_id.presence && #user.customer.presence && #user.country.presence
# they are all present (!= nil)
else
# there is at least one attribute missing
end
end

Ruby: return value or nil

I have this method in Rails and it trips up when passed in parameter, date_string, is nil. Specifically when GetFileInfo -d returns nil.
What's a Rails-y way to handle this?
def calling_method
...
m = MyModel.create(creation_date: cdate_for(path))
...
end
def cdate_for(path)
local_datetime_for(`GetFileInfo -d "#{path}"`.chomp!)
end
def local_datetime_for(date_string)
t = Time.strptime(date_string, '%m/%d/%Y %H:%M:%S')
DateTime.parse(t.to_s)
end
It's OK to return nil from this, assuming Model.create(creation_date: return_value_here) can handle nil values.
Edit: added some other methods to illustrate call chain.
You can use .blank?:
def local_datetime_for(date_string)
return nil if date_string.blank?
t = Time.strptime(date_string, '%m/%d/%Y %H:%M:%S')
DateTime.parse(t.to_s)
end
Some use cases for .blank?:
1.9.3p448 :042 > false.blank?
=> true
1.9.3p448 :043 > true.blank?
=> false
1.9.3p448 :044 > [].blank?
=> true
1.9.3p448 :045 > ''.blank?
=> true
1.9.3p448 :046 > '1'.blank?
=> false
1.9.3p448 :047 > nil.blank?
=> true
(For the record, .present? is the exact opposite of .blank?)
I think idiomatic Ruby calls for this:
def local_datetime_for(date_string)
unless date_string.blank?
t = Time.strptime(date_string, '%m/%d/%Y %H:%M:%S')
DateTime.parse(t.to_s)
end
end
Or if you want to be fancy:
def local_datetime_for(date_string)
DateTime.parse(Time.strptime(date_string, '%m/%d/%Y %H:%M:%S').to_s) unless date_string.blank?
end
Using return early in the method is very Ruby too. I just have trouble getting used to it coming from a Java background where I avoided early returns like the plague.
Use File.stat instead of calling out to the system for GetFileInfo. Then handle any exceptions with the stat in Ruby that might have caused a nil result.
def cdate_for(path)
File.stat(path).ctime.to_datetime
end
You need a condition to check if the date_string is nil or not. I would use a simple If statement,
def local_datetime_for(date_string)
if date_string === nil
t = Time.strptime(date_string, '%m/%d/%Y %H:%M:%S')
t = DateTime.parse(t.to_s)
else
t = 'Unknown'
end
return t
end
You can check the date_string value immediately. Returning immediately is the best way to express your intent – and use the nil? method to express what you're looking for in a Ruby-ish way:
def local_datetime_for(date_string)
return if date_string.nil?
t = Time.strptime(date_string, '%m/%d/%Y %H:%M:%S')
DateTime.parse(t.to_s)
end
Expressing the return case in a single "guard clause" is good practice – it gets the edge cases out of the way as quickly as possible, rather than leaving them hanging around like a bad smell, hovering over the meat of the method.

How can I test if a variable is defined?

Here is the pseudo-code of what I want to do:
if #current_user is defined then puts #current_user.name
Use the operator defined? then.
x = 10
defined? x # => "local-variable"
defined? y # => nil
#x = 10
defined? #x # => "instance-variable"
!!defined? x # => true
!!defined? y # => false
write your code as below:
puts #current_user.name if !!defined?(#current_user)
Do you really need to know whether the variable is defined or is it enough to know whether it contains a valid User object?
Instance variables will never raise a NameError, even when they are not defined. They just evaluate to nil, so you can just check for that:
puts #current_user.name unless #current_user.nil?
Since your question is tagged ruby-on-rails, I'll assume that you have ActiveSupport loaded anyway, so you can also use the Object#try extension method:
puts #current_user.try(:name)
puts #current_user.name if instance_variable_defined?(:#current_user)

Resources