RUBY: defined?(klass) seeing a local-variable instead of constant - ruby-on-rails

I am running into an issue, where I need to check if a class exists. However, I am passing the class to a variable and trying to check it from there.
My issue is I need to pass the actual constant for defined?() to work, but I'm passing a variable, so instead of seeing a constant, it sees a method or variable.
obj is a rails model instance, for example, a specific User, or a specific Car.
def present(obj, presenter_class=nil, view_context=nil)
klass = presenter_class || "#{obj.class}Presenter".constantize
if defined?(klass) == 'constant' && klass.class == Class
klass.new(obj, view_context)
else
warn("#{self}: #{klass} is not a defined class, no presenter used")
obj
end
end
Pry Output:
[1] pry(ApplicationPresenter)> defined?(klass)
=> "local-variable"
I tried the below, but I get a method back...
[18] pry(ApplicationPresenter)> defined?("UserPresenter".constantize)
=> "method"
How can I fix this issue?

Well, apparently Object#defined? does not the thing that you hoped it would do.
tests whether or not expression refers to anything recognizable (literal object, local variable that has been initialized, method name visible from the current scope, etc.). The return value is nil if the expression cannot be resolved. Otherwise, the return value provides information about the expression.
Your goal looks like you are rebuilding what the draper gem is doing with .decorate... Don't forget that most of the gems are open source and you can use that for trying things on your own. See for example the decorator_class method from them
decorator_name = "#{prefix}Decorator"
decorator_name_constant = decorator_name.safe_constantize
return decorator_name_constant unless decorator_name_constant.nil?
They use the method safe_constantize and this apparently returns nil when the constant is not available.
2.6.5 :007 > class UserPresenter; end;
=> nil
2.6.5 :008 > 'UserPresenter'.safe_constantize
=> UserPresenter
2.6.5 :009 > 'ForgottenPresenter'.safe_constantize
=> nil
To me that looks exactly like what you need, and it also safer than using constantize
def present(obj, presenter_class=nil, view_context=nil)
klass = presenter_class || "#{obj.class}Presenter".safe_constantize
if klass != nil
klass.new(obj, view_context)
else
warn("#{self}: #{klass} is not a defined class, no presenter used")
obj
end
end

Related

How to avoid nilClass errors

If I try to check for values in my database, I sometimes get errors related to the fact that there were no hits from my query.
So I started using .present? to check if the query returned any result, before doing the original check.
Is there a smoother way to avoid nilClass errors than this?
temp = Event.where(basket_id: basket.basket_id).where(event_type: ["operatorJoined", "operatorDisconnected"]).last
if temp.present? && temp.event_type == "operatorJoined"
You could be writing:
if temp && temp.event_type == "operatorJoined"
... or you might like to look at the Sandy Metz presentation "Nothing is Something" to learn more about avoiding this problem by using the Null Object pattern.
I would really hope this code would be something like:
temp = Basket.events.operator_join_or_disc.last
if temp && temp.operator_joined?
Ruby 2.3 introduced a safe call operator (I've seen it called safe navigation operator quite a lot, as well) &. which is similar to the try! method in rails:
class A
attr_accessor :the_other_a
def do_something
puts "doing something on #{object_id}"
end
end
a = A.new
a.do_something # => shows 'doing something on 70306921519040'
a.the_other_a.do_something # => NoMethodError: undefined method `do_something' for nil:NilClass
a.the_other_a&.do_something # => nil
a.the_other_a = A.new
a.the_other_a&.do_something # => shows 'doing something on 70306910376860'
a.the_other_a&.do_something_else # => NoMethodError: undefined method `do_something_else' for #<A:0x007fe334d62738>
a.the_other_a.try(:do_something_else) # => nil
a.the_other_a.try!(:do_something_else) # => NoMethodError: undefined method `do_something_else' for #<A:0x007fe334d62738>
So, in your example something like this should work:
temp = Event.where(basket_id: basket.basket_id).where(event_type: ["operatorJoined", "operatorDisconnected"]).last
if temp&.event_type == "operatorJoined"
However, present? just checks for !blank?, so if the variable (temp in this example) could be false, '', ' ', [], or {} (among any other things that would return true for blank?) then temp.present? && temp.something_else would not be the same as temp&.something_else. Doesn't apply in this situation, since it's the result of the ActiveRecord query, but something to keep in mind.

Rails authentication from scratch

Actually I am new to rails I was trying to build authentication from scratch but didn't have any idea. So I was reading some code on internet and I am struck at a function.What does !! means over here. Is it first making it false then true? What it is and why we are using it?
def logged_in?
!!current_user
end
! means "not", so !! means "not-not". ! is often called "bang" (because 'exclamation mark' is too long, i assume) and so !! is often called "double bang". See also "hashbang" or "shebang" which mean #!.
It has the effect of converting all "truthy"* things into the boolean true and all "falsy" things into the boolean false.
Note that in Ruby, unlike some languages, the only things that are "falsy" are false and nil. 0, "", [], {}, and any other object that isn't false or nil, such as a User object, are "truthy".
*"truthy" means it will pass an "if" test.
We use double bang to get value as Boolean (true/false). Here is demonstration:
2.1.2 :001 > my_var = "string"
=> "string"
2.1.2 :002 > !my_var
=> false
2.1.2 :003 > !!my_var
=> true
2.1.2 :005 > null_var = nil
=> nil
2.1.2 :006 > !!null_var
=> false
So, as you can see that, if we want to get corresponding boolean value for an object then we use double bang (!!).
In your case, I assume current_user can have user object OR nil value. Instead of getting user object or nil, it's using !! to convert it into corresponding boolean.
Hope it helps. Happy Learning!
EDIT:
I would like to add that in Ruby, it's convention that method name ending with ? will return boolean value. So, your mentioned code us using !! in logged_in? to return boolean instead of User or NULL class objects.
You are trying to do something like this
def logged_in?
current_user
you are asking here if it is a current user
def logged_in?
!current_user
while you are asking here is the logged in user is not current_user.

Get the referred object of a Ruby variable

I am working on a task where I get a set of classes( ActiveRecord classes to be more precise) and I want to get their names. I do this by iterating over this set and applying methods like #name to the elements of the set.
Something like this:
array_of_classes.map {|class| class.name}
The problem is that some of the elements in this array are not initialized so when I call #name on them I get:
NameError: uninitialized constant ClassName
To find out if a variable or a constant has been initilized I can use the defined?keyword like this:
> defined? UnInitializedClass
nil
> defined? InitializedClass
"constant"
The problem with this is that things get more complicated when we have an array of both initialized and uninitialized elements and we iterate over the array to get their elements. The defined? keyword will be applied to the iterators themselves and not to the classes( or constants or variables) that these variables point to.
> [UnInitializedClass, InitializedClass ].map {|x| defined? x}
["local-variable", "local-variable"]
Is there a way to apply the defined? keyword on the constant the variable x holds?
If not, do you have any idea on how I can find out the uninitialized elements of an Ruby array?
Another idea might be to access each element using the square brackets method [] but defined? is still not applied on the referred constant
> defined? array[0]
"method"
> defined? array[1]
"method"
Or a shorter version of pdobb's code:
array_of_classes.map {|klass| klass.name rescue nil}.compact
I'm not sure how you can have an array of constants, some of which aren't defined... but, you could use a rescue block to filter out the valid constants.
array_of_classes =
array_of_classes.each_with_object([]) { |klass, acc|
begin
klass
rescue NameError
# Do nothing
else
acc << klass
end
}
After that you should be safe to use the constants as per usual.
The only way this question makes sense is that the array contains not constants and uninitialized constants (the latter would immediately raise a syntax error), but strings containing the names of initialized uninitialized classes. In that case you could do this:
class A
end
B = 'cat'
['A','B','C'].select do |s|
Object.const_defined?(s) ? Object.const_get(s).is_a?(Class) : false
end
#=> ["A"]
Note that this concerns classes generally, not just particular ones.

Ruby === not acting as it when left-hand argument is a class

I'm using Ruby 1.8.7 with Rails 3.0.1 and am having a problem whose root cause appears to be the "Array === object" operation. I saw the same behavior before in a class of my own creation, and programmed around it by not using the "===" operator (I assumed that there was some flaw in my knowledge of Ruby, which is still rather limited). But now that it is happening inside ActionPack, I need to do something about it.
This surfaced when the FormHelper "fields_for" was not acting the way it should. The following view code snippet ("<% %>" removed to improve readability):
form_for #coupon do |f|
...
f.fields_for #coupon.participants do |cp|
...
end
end
gave the error "ActionView::Template::Error (undefined method `model_name' for Array:Class):"
inside the form_for helper method. I determined that it was executing the wrong branch of a "case" command, set a breakpoint and started testing. Here are the results:
/Library/Ruby/Gems/1.8/gems/actionpack-3.0.1/lib/action_view/helpers/form_helper.rb:1152
case record_or_name_or_array
(rdb:1) pp record_or_name_or_array.instance_of? Array
true
(rdb:1) pp Array === record_or_name_or_array
false
(rdb:1) pp Array.object_id
2148267660
(rdb:1) pp record_or_name_or_array.class.object_id
2148267660
This shows pretty definitively that, while "record_or_name_or_array" is definitely an array, "Array === record_or_name_or_array" is returning false.
BTW, in case you're suspecting that "#f.fields_for" is the wrong syntax, I tried it both with and without the "#f." and got the same result. I have also restarted RoR and my machine and the results remain unchanged.
Try this:
#coupon = Coupon.last
Array === #coupon.participants #=> false
Array === #coupon.participants.find(:all) #=> true
Association #coupon.participants is not an array, it is a proxy. The reason why #coupon.participants.class == Array is true is described in activerecord-3.0.9/lib/active_record/associations/association_proxy.rb:25
Added: Another interesting experiment would be #coupon.participants.superclass.
From the console (rails c) try running:
#coupon = Coupon.last
Array == #coupon.participants
If that call returns false, it is most likely that your associations are incorrectly setup (i.e. has_many :participants and belongs_to :coupon).
#coupon.is_a? Array should return true, #coupon === Array would mean #coupon was equal to the singleton instance of Array

Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]

This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]

Resources