Get the referred object of a Ruby variable - ruby-on-rails

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.

Related

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

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

Ruby documentation over Symbol objects is misleading

I'm a newbie to Ruby and like others I had trouble wrapping my head around Ruby symbols. I know this subject has been brought up many times but I believe this post might slightly differ from the others. If not, my apologies. Take for instance this piece of code from the documentation.
module One
class Fred
end
$f1 = :Fred
end
module Two
Fred = 1
$f2 = :Fred
end
def Fred()
end
$f3 = :Fred
$f1.object_id #=> 2514190
$f2.object_id #=> 2514190
$f3.object_id #=> 2514190
My gripe is that it makes us think there is a link between the class, the module or the function and the :Fred symbol. No wonder people ask things like "can I assign a value to a symbol" or is the symbol a reference to another thing.
This code adds to the confusion :
class TestController < ApplicationController
layout :which_layout
def index
...
end
private
def which_layout
if condition
"layout1"
else
"layout2"
end
end
end
At first, I thought there was a reference to the function but in fact it's just that the behavior of the layout method will vary base on whether we'll pass a String (the template name) or a Symbol (call the method specified by the symbol) as stated by the documentation. (Does it look for a method.to_sym that is equivalent to the symbol we passed as argument?)
What I believe I've read however is that when creating a class, his symbol counterpart will be automatically created, that is :Fred will already exist in subsequent calls. But that's just it?
My question is : why did they have to include a class, a variable and a function to illustrate this? The context? Then why having the same name? Why not just do :
$f1 = :Fred
$f2 = :Fred
$f3 = :Fred
$f1.object_id #=> 2514190
$f2.object_id #=> 2514190
$f3.object_id #=> 2514190
When you use a symbol Ruby looks at list of existing symbols so when you reuse a symbol you are not creating a separate object in memory.
irb(main):006:0> :foo.object_id == :foo.object_id
=> true
You can contrast this with a string:
irb(main):007:0> "foo".object_id == "foo".object_id
=> false
This combined with the fact that they are so cheap to compare is what makes symbols so effective as hash keys.
What that pretty confusing example demonstrates is that symbols are not private to a scope - the table of symbols is global. It would be a bit less confusing if an instance variable was used instead of a global variable. I think it also attempts to demonstrate that module and class names are constants.
irb(main):016:0> Fred = Module.new do; end # this is the same as using the module keyword
irb(main):017:0> Fred.object_id != :Fred.object_id
=> true
Which means that Fred is a reference to the module while :Fred is a value (a symbol).
Symbols like strings are a value and thus cannot be a used as a reference. This is very much like true, false and nil which are singleton objects.
irb(main):008:0> true.class
=> TrueClass
irb(main):09:0> true.object_id == true.object_id
=> true
# you can even use the singletons as hash keys
irb(main):010:0> { true => 1, false => 2, nil => 3 }[true]
=> 1
The Rails example is not really that complicated. :which_layout is just an argument passed to the layout method. The layout method has a conditional which uses Object#send to dynamically call the :which_layout method if it exists. A string argument is instead used straight away to construct a glob.
My understanding is it's meant to illustrate the fact that all of the other "Fred's" do nothing to alter the fact that the symbol :Fred is unchanged in every context. Maybe changing the object_id list at the bottom of the example to something like this would make it more clear:
p One::Fred.object_id #=> 70222371662500
p Two::Fred.object_id #=> 3
p Fred().object_id #=> 8
p $f1.object_id #=> 2514190
p $f2.object_id #=> 2514190
p $f3.object_id #=> 2514190

Can't figure out what the error is with this method?

I'm passing a hash to this function that either a) has keys that are strings along with values that are ints OR b) it is an empty hash.
The point of the function is to return nil if the hash is empty and return the key associated with the lowest int.
def key_for_min_value(name_hash)
if name_hash == nil
return nil
else
lowest_value = nil
lowest_value_name = nil
name_hash.collect do |name, value|
if lowest_value > value
lowest_value = value
lowest_value_name = name
end
end
return lowest_value_name
end
end
The error I'm receiving is:
1) smallest hash value does not call the `#keys` method
Failure/Error: key_for_min_value(hash)
NoMethodError:
undefined method `>' for nil:NilClass`
You can't compare nil to anything using >, it's not allowed, so you either have to avoid that test or use tools like min_by to get the right value instead of this collect approach.
One way to make your unit test happy might be:
def key_for_min_value(name_hash)
return unless (name_hash)
name_hash.keys.min_by do |key|
name_hash[key]
end
end
Ruby leans very heavily on the Enumerable library, there's a tool in there for nearly every job, so when you have some free time have a look around there, lots of things to discover.
Now Ruby is very strict about comparisons, and in particular a nil value can't be "compared" (e.g. > or < and such) to other values. You'll need to populate that minimum with the first value by default, not nil, then the comparisons work out, but doing that completely is pretty ugly:
def key_for_min_value(name_hash)
return unless (name_hash)
min_key, min_value = name_hash.first
name_hash.each do |key, value|
next unless (value < min_value)
min_key = key
min_value = value
end
min_key
end
So that approach is really not worth it. Enumerable makes it way easier and as a bonus your intent is clear. One thing you'll come to appreciate is that in Ruby if your code looks like code then you're probably going about it the wrong way, over-complicating things.
Ruby is an unusually expressive language, and often there's a very minimal form to express just about anything.

How to uninitialize/undefine a variable in Ruby so I don't get warnings in console?

I keep getting this warning in console, it's cluttering my puts output, and I want it gone:
d.rb:24: warning: previous definition of VariableA was here
d.rb:86: warning: already initialized constant VariableA
Tried doing this after using them:
VariableA = nil
VariableB = nil
Important note: These variables are re-used in a loop.
As zhon points out, you are actually creating a constant. The defined? keyword demonstrates this:
local_var = "foo"
some_constant = "bar"
Local_var = "foo"
SOME_CONSTAT = "baz"
puts defined? local_var #=> local-variable
puts defined? some_constant #=> local-variable
puts defined? Local_var #=> constant
puts defined? SOME_CONSTAT #=> constant
However, you shouldn't create initially-capitalized constants. The convention is to use SCREAMING_SNAKE_CASE.
In ruby whenever you use a capital letter first letter you are creating a constant. You will be warned when it is redefined.
For example
class
THIS_IS_A_CONSTANT = 42
ThisIsAlsoAConstant = "The answer!"
##this_is_a_class_variable
def a_method
#this_is_a_member_variable = true
this_is_a_local_variable = true
end
end
If you create a constant inside a method, you will get re-definition error the second time you call the method.

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