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.
Related
I do a first_or_create statement, followed by a update_attributes:
hangout = Hangout.where(tour: tour, guide: current_user).first_or_create
hangout.update_attributes(priority: current_user.priority)
If the record already existed, it updates the priority. If it doesn't exist previously, there is no update. Why?
Thanks!
update_attributes (aka update) returns a boolean indicating if there was an error, if you do not check it - use bang-version update! so that exception will not be ignored.
Most probably record does not get created due to validation. Also when you're updating new record just after create - better use first_or_create(priority: current_user.priority) or first_or_initialize(with subsequent update) to spare extra DB write.
def update_attributes!(attributes)
self.attributes = attributes
save!
end
update attribute with bang call the save with bang.
def save!(*args, &block)
create_or_update(*args, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
end
Inside the save! RecordNotSave error will be raise if it cannot save the record.
so you can customize the error handing from your code.
begin
hangout.update_attributes!(priority: current_user.priority)
rescue RecordNotSaved => e
#do the exception handling here
end
I have an app structure with nested routes in which a proposal belongs to a request and a request has many proposals.
When I execute a send_proposal method, I am trying to get it to update the status of the request to which that proposal belongs, but I am getting an error that says undefined method 'request' for true:TrueClass.
My route for this method (not that I think it matters) is:
put "proposal/:id/send_proposal" => "proposals#send_proposal", as: "send_proposal"
Here is my send_proposal method, as found in my proposals_controller:
def send_proposal
#proposal = Proposal.find(params[:id])
ProposalMailer.send_proposal_to_client(#proposal, #proposal.request.user).deliver_now
#proposal = #proposal.update_attributes(status: "Sent to Client")
#proposal.request = #proposal.request.update_attributes(archived: "Proposal Sent to Client") <<<<<<<<<ERROR CALLED ON THIS LINE
flash[:notice] = "Your proposal has been sent to the client!"
end
I have looked at many SO posts for other TrueClass errors, but can't seem to find one with a problem like this. Can anyone see what I'm doing wrong or help me conceptualize what TrueClass errors are generally about?
update_attributes is an alias for update:
update(attributes)
Updates the attributes of the model from the passed-in hash and saves the record, all wrapped in a transaction. If the object is invalid, the saving will fail and false will be returned.
and update returns true or false (the documentation could be a lot more explicit about this) not the updated model instance. So this:
#proposal = #proposal.update_attributes(status: "Sent to Client")
will leave #proposal as true or false and neither of those have an update_attributes method.
Your controller method should look more like this:
def send_proposal
#...
#proposal.update(status: "Sent to Client"))
#proposal.request.update(archived: "Proposal Sent to Client")
#...
end
You probably want to do some error checking on those two update calls too.
I'm quite new to RoR. At first I used a sqlite3 database but migrated it to mysql. Everything worked fine with my application until I added a record to my database.
I can update my new records using the "irb" console.
I have a function in my controller that is called from a python script which updates a record.
Here's the function:
# GET /dd_bots/update
# param name, att
def update
obj = Object.all
obj = obj.select{ |o| o.name == params[:name] }
obj[0].update_attribute(:att, params[:att])
head :ok
end
However this function doesn't update the newly added records returning this error: NoMethodError (undefined method update_attribute' for nil:NilClass)
Obviously the record doesn't seem to be found...
Do you have any clue why is this happening?
Firstly, don't call your model Object: this could have disastrous consequences, as Object is the base class for practically every object in Ruby.
Secondly, the update_attribute is being called on the first element of obj, which is an array. You will get that error if the array contains no elements, i.e. if there are no objects where the name is the same as the name parameter being passed to the controller.
You can do this in a way that's less error prone:
def update
obj = Object.find_by_name(params[:name])
obj.update_attribute(:att, params[:att]) if obj
head :ok
end
If a record should exist, you might like to throw an exception if it doesn't. You can do this by adding a ! to the find_by_name method:
obj = Object.find_by_name!(params[:name]) # Could throw an ActiveRecord::RecordNotFound
This has the benefit of giving a HTTP 404 status code if the record doesn't exist, but acts in the same way otherwise.
This is the conventional way of doing that. Do substitute Object with your model name.
def update
obj = Object.find_by_name(params[:name]
if obj
obj.update_attribute(:att, params[:att])
end
head :ok
end
Does that work for you?
If not, try this in erb - Object.find_by_name('whatever_name') and see if your record actually exists or not.
I've got a helper in my ApplicationHelper file that works just fine:
def latest_issue
#issue = Issue.find(:all, :order => "id DESC", :limit => 1)
return #issue
end
After that, using #issue works just fine in any view, but considering that #issue has a column named message using #issue.message returns a No Method Error.
Any help would be fantastic! Cheers.
The issue instance variable is returning an array of objects not an instance. If you would like to select an attribute of an Issue object you need to return an instance of the object.
#issue = Issue.find(:last)
#issue.message
You may be trying to output all the message attributes of the Issue object, if that is the case you need to pass the #issue to an block
#issue.each do |issue|
issue.message
end
As Kyle C says, you're attempting to access the member message of an array of Issues, when you should be returning a single issue from your helper.
Assuming you're using Rails 3, A vastly improved version of your helper would be written this way:
def latest_issue
Issue.order(:id).last
end
A few notes on writing idomatic Ruby:
Avoid explicit return statements, and let the last statement be the return value of a method
Use the chainable methods like order(), where(), and limit() instead of passing arguments to find
I have very newbie question. How can i check that object of model is valid with new params BEFORE updating it?
I want transform that:
def update
#obj = SomeModel.find( params[:id] )
if #obj.update_attributes( params[:obj] )
# That have been updated
else
# Ups, errors!
end
end
To something like that:
def update
#obj = SomeModel.find( params[:id] )
if #obj.valid_with_new_params( params[:obj] )
#obj.update_attributes( params[:obj] )
else
# Ups, errors!
end
end
To update the attributes without saving them, you can use
#obj.assign_attributes( params[:obj] )
Then to check if the object is valid, you can call
#obj.valid?
If the object is not valid, you can see the errors (only after calling .valid?) by calling
#obj.errors
If the object is valid, you can save it by calling
#obj.save
However, all of this usually isn't necessary. If the object isn't valid, then ActiveRecord won't save the object to the database, so all of the attribute changes are forgotten when you leave the controller action.
Also, since an invalid record won't be saved to the database, you can always just call Object.find() again to get the original object back.
You can call the valid? method to run the validations.
This doesn't guarantee that a subsequent save will succeed if some of your validations depend on the state of other objects in the database. Te save could also fail for reasons unconnected to validations (eg a foreign key constraint)
I'm not sure why you'd want this pattern
The object won't be saved if the passed argument doesn't produce a valid object, so you can use your way just fine. You can see the errors (if any) using the #obj.errors array.
update_attributes method validate object and return false if object is invalid. So, you can just write:
if #obj.update_attributes( params[:obj] )
# That have been update
else
# Ups, errors!
end
The answer is that you can define a method
def valid_with_new_params(hash)
self.attributes = hash
valid?
end
But that would be unnecessary because #obj.update_attributes(params[:obj]) returns true if the obj was successfully updated and false otherwise. Note also that internally the update_attributes method runs all validations on the #obj so that you have #obj.errors available if the update failed.
To update the attributes without saving them
#obj.attributes = params[:obj] 0r
#obj.attributes = {:name => “Rob”}
To then check if the object is valid
#obj.valid?
To check if there is any error
#obj.errors