How do I handle nil in models properly? - ruby-on-rails

I have a chain of methods and at any time one of them could return nil based on what they're returning (a collection) and then a method on that collection will blow up because it is nil.
I'm not sure how to properly handle these cases? Should I do a if statement in each and explicitly return the method to stop the execution or what do I do?
undefined method `created_at' for nil:NilClass
# ./app/models/exercise.rb:132:in `last_placeholder_log_date'
# ./app/models/exercise.rb:136:in `placeholder_log_entries'
# ./app/models/exercise.rb:140:in `placeholder_log_entries!'
def last_placeholder_log_date(user)
self.last_log_entry(user, true).created_at.beginning_of_day
end
How do I gracefully handle the possibility if there is no last log entry?

You can use try:
def last_placeholder_log_date(user)
self.last_log_entry(user, true).try(:created_at).try(:beginning_of_day)
end
With try, it's ensured that an exception is not raised if receiver does not respond. If the receiver does not respond then the call returns nil.

Write code as :
def last_placeholder_log_date(user)
val = last_log_entry(user, true)
val.created_at.beginning_of_day unless val.nil?
end

Related

in `_main': undefined method `run' for nil:NilClass (NoMethodError)

I can't call the run method in a class called MySqliteRequest. When I call the method,the error is going out.
in `main': undefined method `run' for nil:NilClass (NoMethodError)
Here some methods of MySqliteRequest
class MySqliteRequest
def print
puts "Type Of Request #{#type_of_request}"
end
def run
print
end
def _setTypeOfRequest(new_type)
if(#type_of_request == :none or #type_of_request == new_type)
#type_of_request = new_type
else
raise "Invalid: type of request already set #{#type_of_request} (new type => #{new_type}"
end
end
end
And main method ↓
def _main()
request = MySqliteRequest.new
request = request.from('nba_player_data.csv')
request = request.select('name')
request = request.where('birth_state', 'Indiana')
request.run
end
_main()
At the point where you call request.run the value for request is nil. This is why you are seeing the error you're given.
This is happening because the line right above it assigns the nil value to the request variable.
You are clearly coming from another language that is not Ruby (some type of C maybe?), by how you've formatted things. It would help for you to get more familiar with Ruby and its idioms. However, best I can tell, you want to do something like this instead:
def _main
request = MySqliteRequest.new
request.from('nba_player_data.csv')
request.select('name')
request.where('birth_state', 'Indiana')
request.run
end
_main()
This assumes you've also defined (and in some cases probably overridden) these methods on your MySqliteRequest Object or Model:
from
select
where
However, please note that the way you're going about this is just completely against how Ruby and Ruby on Rails is designed to work.

Example for deprecation warning

I get the following deprecation warning:
DEPRECATION WARNING: The behavior of `changed?`
inside of after callbacks will be changing
in the next version of Rails.
The new return value will reflect the behavior
of calling the method after `save` returned
(e.g. the opposite of what it returns now).
To maintain the current behavior, use `saved_changes?` instead.
for this code:
def send_devise_notification(notification, *args)
# If the record is new or changed then delay the
# delivery until the after_commit callback otherwise
# send now because after_commit will not be called.
if new_record? || changed?
pending_notifications << [notification, args]
else
devise_mailer.send(notification, self, *args).deliver_later
end
end
Can somebody explain me the Deprecation Warning with an example? I'm not sure if I understand correctly what's meant with The new return value will reflect the behavior of calling the method after "save" returned
Can I now simply replace changed? with saved_changes?? Thanks
As I understand it works like this. Now #changed? returns false in after callbacks until you change an attribute. So #changed? on a record behave in after callbacks as if you've just fetched a record by doing #find (or, as the message says, if you have a record after calling #save). #saved_changes? answers the question: did the last call to #save have any changes to change? So in the cases I can think of you can safely switch to this method in after callbacks.

update_attributes() vs. update_all () & on deleted object

I'm trying to update the attributes of an Object, but often the Object i try to update doesn't exist anymore.
E.g.: I'm post-processing a CSV file to get the attributes:
array.each do |a|
player = Player.find_by_grepo_id(a[:grepo_id])
player.update_attributes(a)
end
That will throw an error when a Player is not found.
What i learned from previous experiences:
ActiveRecord::Base.find always throws an exception if it does not find a record, this is intentional.
I should only use find if I'm absolutely expect to have whatever it is I'm looking for.
If I'm rending a show action and can't find the article, I should rescue that exception and render a 404 (Not found)
instead of redirecting to index (technically).
If I want to find something by it's id attribute without forcing an exception, I should use the dynamic finder
find_by_id (In my case find_by_grepo_id) which will return false if it doesn't find a record with that id.
But upon running the task which contains the above code i get
NoMethodError: undefined method `update_attributes' for nil:NilClass
That's because a Player with that specific id doesn't exist anymore. If i wrap the update_attributes call in a .present? method it works.
What am i missing? Shouldn't the find_by_id method NOT throw an error and just skip it ?
If you want to do it in one call instead of two, you can use the update_all method like this:
Player.where(:grepo_id => a[:grepo_id]).update_all(a)
This will result in the following SQL:
UPDATE players SET ... = ..., ... = ... WHERE players.grepo_id = ...
Also works if the grepo_id doesn't exist: nothing will get updated. Note however that this just runs the SQL; any validations or callbacks on your model are ignored.
This is due to you are doing update_attributes even if it does not find the record by grepo_id. find_by_grepo_id returns nil if it does not find any record. So you need to add a condition to get rid of this error.
array.each do |a|
player = Player.find_by_grepo_id(a[:grepo_id])
player.update_attributes(a) if player.present?
end
Rails has a try method (check the docs) and you can use it here:
array.each do |a|
player = Player.find_by_grepo_id(a[:grepo_id])
player.try do |p|
p.update_attributes(a)
end
end
This should work fine and updating attribute or silently failing (without throwing exceptions) when no record is found.

How to transform a string into a variable/field?

I'm new to Ruby and I would like to find out what the best way of doing things is.
Assume the following scenario:
I have a text field where the user can input strings. Based on what the user inputs (after validation) I would like to access different fields of an instance variable.
Example: #zoo is an instance variable. The user inputs "monkey" and I would like to access #zoo.monkey. How can I do that in Ruby?
One idea that crossed my mind is to have a hash:
zoo_hash = { "monkey" => #zoo.monkey, ... }
but I was wondering if there is a better way to do this?
Thanks!
#zoo.attributes gives you a hash of the object attributes. So you can access them like
#zoo.attributes['monkey']
This will give nil if the attribute is not present. Calling a method which doesn't exist will throw NoMethodError
In your controller you could use the public_send (or even send) method like this:
def your_action
#zoo.public_send(params[:your_field])
end
Obviously this is no good, since someone can post somehing like delete_all as the method name, so you must sanitize the value you get from the form. As a simple example:
ALLOWED_METHODS = [:monkey, :tiger]
def your_action
raise unless ALLOWED_METHODS.include?(params[:your_field])
#zoo.public_send(params[:your_field])
end
There is much better way to do this - you should use Object#send or (even better, because it raises error if you try to call private or protected method) Object#public_send, like this:
message = 'monkey'
#zoo.public_send( message )
You could implement method_missing in your class and have it interrogate #zoo for a matching method. Documentation: http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-method_missing
require 'ostruct' # only necessary for my example
class ZooKeeper
def initialize
#zoo = OpenStruct.new(monkey: 'chimp')
end
def method_missing(method, *args)
if #zoo.respond_to?(method)
return #zoo.send(method)
else
super
end
end
end
keeper = ZooKeeper.new
keeper.monkey #=> "chimp"
keeper.lion #=> NoMethodError: undefined method `lion'

Is it possible to use the 'request' method in a model?

I am using Ruby on Rails 3 and I am tring to do some like this in my model file
if request.headers["CONTENT_LENGTH"]
...
end
but I get this error:
NameError (undefined local variable or method `request' for #<User:0x00...>):
So, is it possible to use the 'request' method in a model? If so, how?
No, because the request is only available in controllers and view code. From a design point of view you're ill advised to use the request within model code, but let's say you want to do something with the request on a particular instance of your User, just create a method for it:
class User
...
def has_a_request?(request)
raise ArgumentError if(request.blank? || !request.respond_to(:headers))
if(request.headers["CONTENT_LENGTH"] # Does it really have CONTENT_LENGTH btw?
puts "#{self.username} got a request with content length"
return true
else
puts "#{self.username} didn't get a request with content length"
return false
end
end
And then elsewhere in your application:
User.has_a_request?(request)
Is not good app design to use the request object in the method, but if you do absolutely need it you can do :
class User
def a_method request
end
end
class UserController
def index
User.find(params[:user]).a_method request
end
end
Extract relevant data from the request object in your controller, then pass them on to the model -- either by setting attributes on an instance of it or by passing them as parameters to a method call.

Resources