I have this code
args = x.lead_details.last.leads.last.country_id rescue nil
Function(args)
I have used rescue keyword to make sure I don't get errors like
undefined method `country_id' for nil:NilClass,
undefined method `leads' for nil:NilClass
Is there any better way I can achieve this?
Other approach is
Function(x.lead_details.last.leads.last.country_id if x.present? && x.lead_details.present? && x.lead_details.last? && so on)
rescue nil is evil. It does not just rescue nil errors. It eats everything you throw at it. For example:
class Foo
def bar
end
def woo
raise ArgumentError
end
end
Foo.new.baz rescue nil # nil!
Foo.new.woo rescue nil # nil!
This applies to any inline rescue.
Ruby 2.3.0 introduced the safe navigation operator that lets you do:
x&.lead_details&.last&.leads&.last&.country_id
ActiveSupport also has a Object#try! method that can be used in older versions of Ruby:
x.try!(:lead_details).try!(:last).try!(:leads).try!(:last).try!(:country_id)
But you're really just pissing on the Law of Demeter here and you should refactor the code so you don't have to traverse down that insane method chain as you're really just burying tons of potential bugs and making debugging impossible.
See:
5 reasons not to use safe navigation operators
You can use the safe navigation operator (&.) that was introduced in Ruby 2.3:
Function(x&.lead_details&.last&.leads&.last&.country_id)
It returns nil when you try to call a method on nil.
Related
What is the use of ! in rails?
Especially in this line: From HArtl tutorial
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
user.each { |user| user.microposts.create!( content: content )}
end
Basically this is creating tweets/microposts for 6 users.
I am really wondering why need to use !
The important thing to remember is that in Ruby a trailing ! or ? are allowed on method names and become part of the method name, not a modifier added on. x and x! and x? are three completely different methods.
In Ruby the convention is to add ! to methods that make in-place modifications, that is they modify the object in fundamental ways. An example of this is String#gsub which returns a copy, and String#gsub! which modifies the string in-place.
In Rails this has been ported over to mean that as well as situations where the method will raise an exception on failure instead of returning nil. This is best illustrated here:
Record.find_by(id: 10) # => Can return nil if not found
Record.find_by!(id: 10) # => Can raise ActiveRecord::RecordNotFound
Note that this is not always the case, as methods like find will raise exceptions even without the !. It's purely an informational component built into the method name and does not guarantee that it will or won't raise exceptions.
Update:
The reason for using exceptions is to make flow-control easier. If you're constantly testing for nil, you end up with highly paranoid code that looks like this:
def update
if (user.save)
if (purchase.save)
if (email.sent?)
redirect_to(success_path)
else
render(template: 'invalid_email')
end
else
render(template: 'edit')
end
else
render(template: 'edit')
end
end
In other words, you always need to be looking over your shoulder to be sure nothing bad is happening. With exceptions it looks like this:
def update
user.save!
purchase.save!
email.send!
redirect_to(success_path)
rescue ActiveRecord::RecordNotFound
render(template: 'edit')
rescue SomeMailer::EmailNotSent
render(template: 'invalid_email')
end
Where you can see the flow is a lot easier to understand. It describes "exceptional situations" as being less likely to occur so they don't clutter up the main code.
I am trying to convert the string into classname using constantize. But it throws error if the classname is not present or the string is empty or nil.
Is there any way to convert the string into classname without throwing error. Or returning false only if not able to convert.
I hate Rails, it brings a ton of redundant so-called helpers, having zero value in general. This might be easily done with pure Ruby:
Kernel.const_get(string) if Kernel.const_defined?(string)
The above effectively returns the class or nil (which is falsey in Ruby) if the class is not defined.
Firstly, I want to point out something from the answers above. rescue false means that all kinds of exceptions thrown from that expression are rescued as false. This means that even if you have an exception object of class NoMethodError or RuntimeError you will return false and your expectation would be that the constantized string does not match a constant in your app. This can cause you hours of debugging if you don't know how the error handling system in ruby is working. It is also a place to introduce a lot of bugs in your code in the future.
I see the ruby-on-rails tag so I assume you are running a problem in a rails app. You can use a helper method coming from the ActiveSupport::Inflector module. Instead of rescuing the constantize method you would probably want to use the safe_constantize. It will return nil in case the constant is not present in your project.
Example usage (note I haven't defined a Foo constant in my project):
# with constantize
irb(main) > 'foo'.constantize
=> NameError (wrong constant name foo)
# with safe_contantize
irb(main) > 'foo'.safe_constantize
=> nil
What's the error? if it's an exception you can do something like:
the_class = your_string.constantize rescue false
The rescue catches de exception and returns false in that case.
You can achieve it by following,
string.constantize rescue false
Additional
If you have have downcase string or table name for that, it can also be converted into class name by using classify method as follows,
string.classify.constantize rescue false
If you have string 'event_managers', it will return class EventManager
You can provide a default value or return early in case of nil and then rescue a NameError:
def to_class(name)
# default to empty string
(name || "").constantize
rescue NameError
false
end
def to_class(name)
# guard against invalid input
return false if name.nil?
name.constantize
rescue NameError
false
end
EDIT: It's longer than simple
string.constantize rescue false
but IMO you should rescue the most specific error you can. rescue false is OK for simple scripts or some test cases. In production code it's quite risky - you could hide some exceptions that should be verbose.
What a surprise when I defined a method the set my ActiveRecord User attribute currency_code to Turkish lira (TRY).
So in User I defined:
define_method("try!"){ update! currency_code: :try }.
And it apparently defined both try() and try!() with the same block
and the result for User.first.try is updating my user instance.
I investigate but I don't understand where the try() (Active support that prevent you call to a method to raise if it's nil) is override and now execute an update.
To be sure I defined the same one for USD
define_method("usd!"){ update! currency_code: :usd } and this one is working the right way. No usd() method defined here.
I'm not looking for a fix but an explication to this magic :)
Thanks
It's because ActiveSupport's try method also uses try! internally, at least in Rails 5.1:
# File 'lib/active_support/core_ext/object/try.rb', line 5
def try(*a, &b)
try!(*a, &b) if a.empty? || respond_to?(a.first)
end
I'm upgrading a Rails application I've inherited from 3.2 to 4.0.1. I followed and finished the edge guide here:
http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0
I've gotten everything fixed except for a single error that I can't seem to find the root cause of. When I attempt to save a User model object, I'm met with the following error:
[1] pry(main)> User.create(name: "test user", email: "testuser#frobnitz.com", password: "testPassword123", password_confirmation: "testPassword123")
(0.6ms) BEGIN
(0.9ms) ROLLBACK
NoMethodError: undefined method `to_datetime' for false:FalseClass
from /home/cmhobbs/src/serve2perform/.gem/ruby/2.3.0/gems/activesupport-4.0.1/lib/active_support/core_ext/date_time/calculations.rb:161:in `<=>'
activesupport 4.0.1 and rals 4.0.1 are installed. I use chgems and I purged my .gem/ directory and Gemfile.lock before bundling again.
Here is a Gist of the User model.
And here is all of the backtrace output I could get from pry.
Here is a link to the User table schema.
Once you've found the offending callback to be this one:
before_create :activate_license
def activate_license
self.active_license = true
self.licensed_date = Time.now
end
things begin to be clearer. The activate_licence is a before callback. Before callbacks can halt the whole callbacks chain by returning false (or raising an exception).
If we look carefully in the debug output that you provided by manually adding some puts lines into the Rails callbacks code, we can indeed find the comparison of this callback result with false (here - I removed some unimportant parts of the code):
result = activate_license
halted = (result == false)
if halted
halted_callback_hook(":activate_license")
end
Because the support for halting before callbacks by returning false (i.e. the Rails code shown above) practically has not changed from Rails 3.2 to Rails 4.0.1, the issue must lie in the comparison itself.
The callback returns a DateTime object (it's the last assignment in the method which is also returned). And, indeed, the comparison of DateTimes changed significantly between the two Rails versions (also note that the == operator is normally evaluated using the <=> operator):
in Rails 3.2 it was this:
def <=>(other)
if other.kind_of?(Infinity)
super
elsif other.respond_to? :to_datetime
super other.to_datetime
else
nil
end
end
notice especially the respond_to? check if the other object is also a date or time object while otherwise returning nil.
whereas in Rails 4.0.1 this changed to the bare code below:
def <=>(other)
super other.to_datetime
end
→ all sanity checks are gone!
Now, everything is clear: the result of the callback (a DateTime object) is compared using the <=> operator with false and under Rails 4.0, the comparison tries to convert the false object to DateTime without any sanity checks, which of course fails and throws the exception.
To fix this issue, simply ensure that your callback returns something that Rails can compare with false without any problems, e.g. true, as your callback is never supposed to halt the chain:
def activate_license
self.active_license = true
self.licensed_date = Time.now
true
end
Now everything should work as expected again.
You can bind even in core classes, please do something like this and check what the other is, from where it came from.
/home/cmhobbs/src/serve2perform/.gem/ruby/2.3.0/gems/activesupport-4.0.1/lib/active_support/core_ext/date_time/calculations.rb
def <=>(other)
binding.pry
if other.kind_of?(Infinity)
super
elsif other.respond_to? :to_datetime
super other.to_datetime rescue nil
else
nil
end
end
Is there a built-in way of specifying asserts in Rails that will throw an exception if an invariant is broken during development and testing?
Edit: Just to be clear, I'm looking for asserts that can be placed in models or controllers as opposed to asserts that you would use for unit tests.
There are many assert functions if you are writing tests. But for assertiona in the main code, there aren't any and you can roll your own easily.
Add something like this to environment.rb:
class AssertFailure < Exception
end
def assert(message = 'assertion failed')
unless block_given? and yield
raise message
end
end
and make it a no-op in your environments/production.rb so there is minimal overhead
def assert(message = 'assertion failed')
end
Then, in your code, you can assert to your heart's content:
assert { value == expected_value }
assert('value was not what was expected') { value == expected_value }
If value does not equal expected_value and you aren't running in production, an exception will be raised.
Beyond these, you mean?
Raise exceptions, and use rescue_from.
As #Gordon said, no there isn't. I ended up using the solid_assert gem by Jorge Manrubia, as mentioned in this question: Is it idiomatic Ruby to add an assert( ) method to Ruby's Kernel class?