Protecting against an undefined chained method - ruby-on-rails

I have a long loop that results in this:
csv_code = CSV.generate do |csv|
csv << ["Product ID","Name", "Url"]
#all_products.each do |product|
if product.page_url("en_US") != nil
turl = product.page_url("en_US")
end
csv << [product.name,product.d_id, turl]
end
end
The method uses products 1-17 works great resulting in a url printed. When I get to my 18th record I have problems
Product.find(18) // product found!
product.find(18).page_url("en_US")
NoMethodError: undefined method `page_url' for nil:NilClass
How can I protect against these undefined events?
url = product.page_url("en_US")

The issue is that a product is nil:
undefined method 'page_url' for nil:NilClass". Solution:
(It has nothing to do with page_url maybe returning nil.)
Make sure product can't be nil: but be wary that this may be a deeper issue. In any case, "fixing" this issue is easy to deal with.
Consider either using a collection restriction (such as Enumerable#reject):
#all_products.reject(&:nil?).each do {
...
}
The above uses the Symbol#to_proc "Rails magic", but could just as easily have been {|x| x.nil?} as the restriction. The downside is it's not practical to use this for a "no URL" condition per-product although Enumerable#partition could help with that: use the right tool for the job.
Another solution is to expand the conditional check itself:
if product && product.page_url("en_US")
# yay
else
# uhm
end
The short-circuit nature of && will ensure page_url is only invoked upon a truthy value (which excludes nil).
I also took the liberty of assuming page_url can't return false as I find this makes the intent more clear.
Happy coding.

Try this:
product.find(18).try(:page_url, "en_US")
But it's a perf killer.
Are you sure Product.find(18) doesn't return nil ?
Anyway, you could do:
url = product.nil? ? "no_url" : product.page_url("en_US")

Related

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.

method.to_proc doesn't return from enclosed function

I was trying to DRY up a Rails controller by extracting a method that includes a guard clause to return prematurely from the controller method in the event of an error. I thought this may be possible using a to_proc, like this pure Ruby snippet:
def foo(string)
processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
My thinking was that having called to_proc on the breaker method, calling the early return statement in the rescue clause should escape the execution of foo. However, it didn't work:
2.4.0 :033 > foo('bar')
This step should not be executed in the event of an error
=> "BAR"
2.4.0 :034 > foo(2)
Well you messed that up, didn't you?
This step should not be executed in the event of an error
=> nil
Can anyone please
Explain why this doesn't work
Suggest a way of achieving this effect?
Thanks in advance.
EDIT: as people are wondering why the hell I would want to do this, the context is that I'm trying to DRY up the create and update methods in a Rails controller. (I'm trying to be agressive about it as both methods are about 60 LoC. Yuck.) Both methods feature a block like this:
some_var = nil
if (some complicated condition)
# do some stuff
some_var = computed_value
elsif (some marginally less complicated condition)
#error_msg = 'This message is the same in both actions.'
render partial: "show_user_the_error" and return
# rest of controller actions ...
Hence, I wanted to extract this as a block, including the premature return from the controller action. I thought this might be achievable using a Proc, and when that didn't work I wanted to understand why (which I now do thanks to Marek Lipa).
What about
def foo(string)
processed = breaker(string)
puts "This step should not be executed in the event of an error"
processed
rescue ArgumentError
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
raise ArgumentError.new("could not call upcase! on #{string.inspect}")
end
string
end
After all this is arguably a pretty good use case for an exception.
It seems part of the confusion is that a Proc or lambda for that matter are distinctly different than a closure (block).
Even if you could convert Method#to_proc to a standard Proc e.g. Proc.new this would simply result in a LocalJumpError because the return would be invalid in this context.
You can use next to break out of a standard Proc but the result would be identical to the lambda that you have now.
The reason Method#to_proc returns a lambda is because a lambda is far more representative of a method call than a standard Proc
For Example:
def foo(string)
string
end
bar = ->(string) { string } #lambda
baz = Proc.new {|string| string }
foo
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
bar.()
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
baz.()
#=> nil
Since you are converting a method to a proc object I am not sure why you would also want the behavior to change as this could cause ambiguity and confusion. Please note that for this reason you can not go in the other direction either e.g. lambda(&baz) does not result in a lambda either as metioned Here.
Now that we have explained all of this and why it shouldn't really be done, it is time to remember that nothing is impossible in ruby so this would technically work:
def foo(string)
# place assignment in the guard clause
# because the empty return will result in `nil` a falsey value
return unless processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
Example

What is the best practice for handling nil objects and properties?

Say I have a User object, which has an email property, and I need the upper cased last letter of their email:
u = User.find(1)
letter = u.email.upcase.last
If u or email is nil in this chain, then I get a NoMethodError: undefined method 'blah' for nil:Nilclass. I should be able to work around it in most cases, but sometimes, a nil gets where it shouldn't or its hard to contain. One way would be verbose:
u = User.find(1)
letter = nil
if u && u.email
letter = u.email.upcase.last
end
But this gets annoying and hazardous in a view, or in a long chain of a.bunch.of.properties.down.a.hierarchy. I read about try method in Rails:
u = User.find(1)
letter = u.try(:email).try(:upcase).try(:last)
This is less verbose, but I feel icky writing all those tries. And once I put try in the chain, I have to use them all the way down. Is there a better way?
I like to use the Null Object Pattern. Avdi has a great post explaining this, but the basic idea is you have a little class that can stand in for an object and respond reasonably to the messages you might pass the original object. I've found these are useful not only for avoiding NoMethodErrors but also for setting default values/nice messages.
For instance, you could do:
class NilUser
def email
"(no email address)"
end
end
u = User.find(1) || NilUser.new
u.email.upcase.last # => No errors!
I just wanted to update this thread with one more option: Ruby now (as of 2.3) gives us a safe navigation operator, the &. syntax.
So:
u.email.upcase
Would become:
u.email&.upcase
Similarly to Rail's try method, the whole chain will return nil if it encounters NoMethodError on a nil.
User.find(1)
Will raise exception if user with id 1 not exist so you don't need to worry about nil here
u.email
If you have in your model
validates :email, presence: true
You don't need to worry about nil because User without email cant be in database
But I think you are asking about general way of handling nils in ruby code. Lately I'm using Null Object pattern
http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/
http://robots.thoughtbot.com/post/20907555103/rails-refactoring-example-introduce-null-object
Rails: replacing try with the Null Object Pattern
https://github.com/martinciu/nullobject
You could also map the result of find
[User.find(1)].map{|u| (u != nil ? u.mail : "no mail")}[0]

Rails 3 Error Checking for Existence of Association

I has a model "modela" that has a has_many_through relationship with model "submodelb". In a controller I want to check if modela has any submodelb associated with it. I have tried the two code examples below; however, the both throw the error "undefined method `submodelbs'" if modela does not have any submodelbs. Please help me see what I am doing wrong.
Sample 1:
if !#modela.submodelbs.nil?
#submodelbs = #modela.submodelbs
else
#submodelbs = []
end
Sample 2:
if !#modela.submodelbs.empty?
#submodelbs = #modela.submodelbs
else
#submodelbs = []
end
You can use .present? which is the opposite of blank?
#submodelbs = #modela.submodelbs.present? ? #modela.submodelbs : []
But I think your problem is that #modela may be nil or you may have not defined associations correctly in the model.
The reader method produced by has_many_through always returns something that looks like an Array, so it should never return nil. So, can't you just return #modela.submodelbs always?
I use blank?
unless #modela.submodelbs.blank?
#modela has submodelbs
end
but error messege suggests that you may have something wrong with association definition...
Also what you are trying to achieve can be done with one-liner
#modela.submodelbs ||= []
if submodelbs are nil empty array will be assigned.
Why not just put your checks in a begin...rescue...end block?

debugging a ruby on rails error

I'm some what new with ruby on rails, so I'm attempting to debug this error that I'm getting but, from my understanding, is working on the prod code.
The error:
NoMethodError in JoinController#index
undefined method `join_template' for nil:NilClass
/app/controllers/join_controller.rb:5:in `index'
Okay, so line 5 in index is:
elsif current_brand.join_template == 'tms'
So clearly current_brand is nil. The controller is a child class of AppController, so checking that out I see that current_brand is a method:
def current_brand
return #current_brand if defined?(#current_brand)
url_array = request.env['HTTP_HOST'].split('.').reverse
url = url_array[1] << "." << url_array[0]
#current_brand = Brand.find(:first, :conditions => ["url LIKE ?", "%" << url << "%"])
end
It seems that #current_brand is always returned, yet it's continuing to be Nil. What could the problem be?
It may be your query is not returning anything. You can use a debugger, but it's pretty easy to just output #current_brand and see what it evaluates to.
logger.debug(#current_brand)
You must check two things:
Does Rails build the SQL query properly with the url passed in on the last line of that method?
Does the record exist in the brands table? you're not actually checking for that.
Also, passing in the url like that opens you up to a potential SQL injection attack.
You need to rewrite your definition with a rescue or if/else so if you do get a nil element then it won't be a fatal error.
This is your problem:
#current_brand = Brand.find(:first, :conditions => ["url LIKE ?", "%" << url << "%"])
#current_brand is not finding anything so make sure you find something.
#current_brand = Brand.find(:first)
If this fixes the problem then you know it is not finding anything and you will need to change your code that if it returns nil then it doesn't provide the function or it finds a default brand such as what I suggested.

Resources