I've a strange situation with my rails application (2.3.8)
I want to assign a no nil value to some variable in my controller, so I wrote this code:
#myValue = session[:value] or []
In my view I wrote:
<%= #myvalue.size %>
When I display my page, I have a nil error.
I've tried the nil or [] in the IRB and I get the []. So my question is if anyone know why is working different in rails?
P.S.: The only gem that I'm using is cell version 3.3.3.
Thanks!
The 'or' keyword has too low a precedence for this code to work. Use '||' instead. To demonstrate the point:
ruby-1.9.2-p0 > a = nil or true; a
=> nil
ruby-1.9.2-p0 > a = nil || true; a
=> true
It's an order of operations thing.
#myValue = (session[:value] or []) will work. I believe #myValue = session[:value] or [] is interpreted as (#myValue = session[:value]) or []. But #myValue = session[:value] || [] worked too without parens. || has higher precedence than or.
The idiom is to do session[:value] || [], using ||, not or. You can also try session[:value].to_a. This works because nil.to_a becomes [].
Related
I wonder if there’s a way to tighten this up:
def find_by_recordtype
e = EvLk.find_by_sql(<SQL QUERY>)
return (e.size > 0 ? e : nil)
end
I could do something like below but, prefer not to query twice.
return ( EvLk.find_by_sql().size > 0 ? EvLk.find_by_sql() : nil)
You're using find_by_sql so presumably you're in Rails or have ActiveSupport available. In that case, you can use presence:
presence()
Returns the receiver if it's present otherwise returns nil. object.presence is equivalent to
object.present? ? object : nil
So you could do this:
def find_by_recordtype
EvLk.find_by_sql(<SQL QUERY>).presence
end
One thing you can do is memoize the result.
Whether this makes sense depends on the context of the object and the app.
def find_by_recordtype
return #records if #records
#records = EvLk.find_by_sql(<SQL QUERY>)
end
+1 for ruby nuby.
BTW, why do you want to return nil? Usually it's better to the same type for all cases.
EvLk.find_by_sql(<SQL QUERY>).tap{|e| break if e.empty?}
can any body tell me what command is used to clear all variables in rails console?
e.g.
1.9.1 :001 > permissions = {:show => true}
=> {:show=>true}
1.9.1 :001 > foo = "bar"
=> "bar"
I need a command that can get all variables reset to nil without a restart of rails console itself.
Any advice would be very much appreciated.
local_variables.each { |e| eval("#{e} = nil") }
local_variables returns the list of symbols of all local variables in the current scope
a, b = 5, 10
local_variables # => [:b, :a]
Using each you can iterate over this list an use eval to assign their values to nil.
You can also do the same thing with instance_variables and global_variables. For example
(local_variables + instance_variables).each { |e| eval("#{e} = nil") }
By the way, if you are going to use it more than once, it might be helpful to define such method in ~/.irbrc file to make it accessible for all irb sessions (didn't test it in rails console).
class Binding
def clear
eval %q{ local_variables.each { |e| eval("#{e} = nil") } }
end
end
Then, inside irb session
a = 5
binding.clear
a # => nil
Do one thing, type
irb 'another'
and then press ctrl+l
now check the values of your variables.
It works.
How do i reduce the following code to one line in Ruby?
unless(current_facebook_user.nil?)
unless(current_facebook_user.client.nil?)
unless(current_facebook_user.client.default_params.nil?)
val = current_facebook_user.client.default_params
end
end
end
You can using the try() method from active_support/core_ext/object/try.rb introduced since Rails 2.3.2
val = current_facebook_user.try(:client).try(:default_params)
Method Try
Or directly using Safe navigation operator introduced since Ruby 2.3.0
val = current_facebook_user&.client&.default_params
documented here
more on Safe navigation operator
Use || operator in one unless: val = ... unless cond1 || cond2 || cond3
Or use && on negations of the conditions in if: val = ... if !cond1 && !cond2
You may not need the third check, as if .default_params is nil, val will be evaluated nil too (if it started nil, this is no problem; if it was already populated, you might want to keep the third guard there :-)
val = current_facebook_user.client.default_params if current_facebook_user && current_facebook_user.client && current_facebook_user.client.default_params
val = current_facebook_user.client.default_params unless current_facebook_user.nil? or current_facebook_user.client.nil? or current_facebook_user.client.default_params.nil?
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]
if args.size == 5
value_for,alt_currency_id,amount,exchange_rate_code,tran_dt = args
else
value_for,alt_currency_id,amount,exchange_rate_code,year_no,period_no = args
end
Any Better way to write this condition ??
I would just skip the condition entirely. If you don't have the fifth argument, period_no will simply be nil.
If period_no needed to be set to some default you could follow up with:
period_no ||= sane_default
Definitely is a code smell, specially since the variable is called args. If you're passing all these arguments as optional values, the best approach is make the variable arguments into a hash.
def whatever(value_for, alt_currency_id, amount, options = {})
tran_dt = options[:tran_dt]
year_no = options[:year_no]
period_no = options[:period_no]
...
end
To strictly meet your requirements, I'd do this:
value_for, alt_currency_id, amount, exchange_rate_code = args.shift(4)
tran_dt, year_no, period_no = [nil, nil, nil] # or some sensible defaults
case args.size
when 1 then tran_dt = args.shift
when 2 then year_no, period_no = args.shift(2)
end
But this code has a smell to it. I'd look at redesigning how that method gets called.
Perhaps assign period_no to nil by default, and use that to determine which argument set you are working with:
def process_record(value_for, alt_currency_id, amount, exchange_rate_code, tran_dt, period_no=nil)
year_no = period_no ? tran_dt : nil
puts "tran_dt: #{tran_dt.inspect}"
puts "year_no: #{year_no.inspect}"
puts "period_no: #{period_no.inspect}"
end
process_record(:foo, :bar, :baz, :buz, Time.now)
# Output:
#
# tran_dt: Mon Sep 13 15:52:54 -0400 2010
# year_no: nil
# period_no: nil
process_record(:foo, :bar, :baz, :buz, 2010, 1)
# Output:
#
# tran_dt: 2010
# year_no: 2010
# period_no: 1
Here's one way of DRYing up your code a bit:
value_for, alt_currency_id, amount, exchange_rate_code, year_no, period_no = args
if period_no.nil?
tran_dt = year_no
year_no = nil # May or may not be needed, depending on later code
end
Ruby has two ternary operators as well that I'm aware of
a = true ? 'a' : 'b' #=> "a"
b = false ? 'a' : 'b' #=> "b"
or
a = (true && 'a') || b #=> "a"
b = (false && 'a') || b #=> "b"
Are you processing commandline? Just leave as it is, for me it is most readable at first look :) Otherwise it may smell perlish.
You simply see what is required set for 5 arguments or else.
If these are not command line args I suggest introducing hash.