Ruby on Rails: Saving two things to the database in controller - ruby-on-rails

My 'create' function in my 'Message' controller is something like this:
def create
#message = Message.new(params[:message])
#message2 = Message.new(params[:message])
#message.sender_deleted = false
#message2.sender_deleted = true
if #message2.save
...
else
logger.debug("SAVE DIDN'T WORK")
For whatever reason, message2 cannot be saved, but #message can. I believe this is because you need to save only a variable named #message, but I can't figure out how to get around this. I need to, on this save, save multiple things to the database - is there some other way to do this or am I doing this completely wrong?
Thanks for your help

There's no reason you can't save more than once in an action, though why you'd want to do such a thing is debatable. You'll want to put the saves in a transaction so you only save when both records are valid. save! will raise an exception when the save fails.
def create
#message = Message.new(params[:message].merge(:sender_deleted=>false))
#message2 = Message.new(params[:message].merge(:sender_deleted=>true))
Message.transaction do
#message.save!
#message2.save!
end
redirect_to .... # handle success here
rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
# do what you need to deal with failed save here,
# e.g., set flash, log, etc.
render :action => :new
end
end

Related

Correct syntax for rescue and next in a each loop

I have a fairly straightforward if else statement in a controller as follows:
if citation_array.blank?
flash.now[:error] = "There was a problem saving the publications selected!"
#user = current_user
render 'pubmed_search'
else
citation_array.each do |user_publication|
begin
publication = Publication.new
render_publication(user_publication)
publication.citation = user_publication
publication.user_id = current_user.id
publication.title = #title
publication.authors = #authors
publication.journal = #journal
publication.year = #year
publication.volume = #volume
publication.pages = #pages
if publication.save
next
end
rescue
next
end
end
#user = current_user
redirect_to current_user
return false
end
It is served an array of id's in citation_array and if there are values present it loops throught them saving each publication found by the id's in the array. The render_publication method instantiates the instance variables so don't be concerned with that.
My issue is this. Very rarely an id is fake or wrong and so this block fails at that point. I want to simple move on to the next id in the array and forget about the failed id. I don't even need to save an exception. I'm new to Ruby (coming from a PHP background).
I want to check if this syntax is correct. I am having trouble checking it in the rails console.
Syntax errors are easier to spot if the code is indented correctly.
if citation_array.blank?
flash.now[:error] = "There was a problem saving the publications selected!"
#user = current_user
render 'pubmed_search'
else
citation_array.each do |user_publication|
begin
publication = Publication.new
render_publication(user_publication)
publication.citation = user_publication
publication.user_id = current_user.id
publication.title = #title
publication.authors = #authors
publication.journal = #journal
publication.year = #year
publication.volume = #volume
publication.pages = #pages
if publication.save
next
end
rescue
next
end
end
#user = current_user
redirect_to current_user
return false
end
The syntax seems correct. Though an easier way to find out would have been just to run the code.
Some things in the code are not necessary though. After cleaning up your code a bit, it would look something like this with the same functionality.
#user = current_user
if citation_array.blank?
flash.now[:error] = 'There was a problem saving the publications selected!'
render 'pubmed_search'
else
citation_array.each do |user_publication|
begin
render_publication(user_publication)
Publication.create!( # create! here so that if something does go wrong, then you're not just ignoring it, but you can log it in your rescue block.
citation: user_publication,
user_id: current_user.id,
title: #title,
authors: #authors,
journal: #journal,
year: #year,
volume: #volume,
pages: #pages
# This hash should be extracted to a method.
)
rescue
# Just doing nothing here is valid syntax, but you should at least log your error.
end
end
redirect_to current_user
false # This can most likely be omitted as well since not many places care about the return value of a controller action.
end
Syntax for begin-rescue,
begin
your code...
rescue => e
Rails.logger.debug 'Exception is #{e}'
end

Better way to do a few save in one tranaction in rails 3.2

Here is our code to save quite a few objects all at once within one transaction. What the code does is to create a new checkout record (for warehouse) and update each item (there may be a few of them) in stock. Since all save has to be either all or none, we put all the save within Rails transaction:
#checkout = RequisitionCheckoutx::Checkout.new(params[:checkout])
#checkout.last_updated_by_id = session[:user_id]
#checkout.checkout_by_id = session[:user_id]
#checkout.transaction do
params['ids'].each do |id|
params['out_qtys'].each do |qty| #ids passed in as a string of 'id'
stock_item = RequisitionCheckoutx.warehouse_class.find_by_id(id.to_i)
qty = qty.to_i
if stock_item.stock_qty >= qty
stock_item.stock_qty = stock_item.stock_qty - qty
stock_item.last_updated_by_id = session[:user_id]
begin
stock_item.save
rescue => e
flash[:notice] = t('Stock Item#=') + id.to_s + ',' + e.message
end
end
end unless params['out_qtys'].blank?
end unless params['ids'].blank?
if #checkout.save
redirect_to URI.escape(SUBURI + "/authentify/view_handler?index=0&msg=Successfully Saved!")
else
flash[:notice] = t('Data Error. Not Saved!')
render 'new'
end
end
We haven't run the test yet and the code looks not pretty. Is there a better way to handle this kind of batch save? Also should the rescue loop be removed for transaction?
The transaction block should be performed first and then you should deal with action response. Besides that, catching exception here is pointless, cause using save returns simply true or false. Your transaction should look like:
RequisitionCheckoutx::Checkout.transaction do
begin
#...
#...
stock_item.save! # it will raise RecordInvalid or RecordNotSaved if something goes wrong
#...
#...
#checkout.save!
rescue Exception => e
raise ActiveRecord::Rollback # it calls Rollback to the database
end
end
Now, using ActiveModel::Dirty you need to check if #checkout has been saved:
if !#checkout.changed?
redirect_to "/something"
else
flash[:notice] = t('Data Error. Not Saved!')
render 'new'
end

Duplicating a record with a Paperclip attachment

I'm creating an action which duplicates an item and then allows the user to edit it and save it back to the database.
I've written the following method in my controller and it mostly works apart from the Paperclip attachment which won't move across for some reason.
def duplicate
existing_event = Event.find(params[:id])
#event = Event.new(existing_event.attributes)
render action: 'new'
end
I've seen this question where the person is using .dup but I can't seem to get that working in a situation where the user edits the new item before saving.
I also tried using something like #event.image = existing_event.image but that didn't have any effect either.
This is what my create method looks like:
def create
#event = Event.create(event_params)
if #event.save
redirect_to events_path, notice: "Event was successfully created."
else
render action: 'new'
end
end
If it makes any difference I'm using S3 for the image uploads too and it doesn't really matter to me if there are multiple copies of the image up there.
Can anyone help? Thanks!
Passing the attachment params does just that: pass the params.
You need to pass the file itself.
Below you get the idea how to do it, not tested it, but you can play around it and make it work, it shouldn't be that hard.
On new action:
existing_event = Event.find(params[:id])
#event = Event.new(existing_event.attributes)
#event.image = File.open(existing_event.image.path,'rb')
render :action => 'new'
Also:
Check in your create action, you have a slight mistake, calling create and save for the same record - this is redundant. You should call #event=Event.new(event_params) and then if #event.save.
Here's a little snippet I use in an initialiser:
module Paperclip
class HasAttachedFile
def define_with_extensions
define_without_extensions
define_dup_override
end
alias_method_chain :define, :extensions
private
def define_dup_override
name = #name
#klass.send :define_method, "dup" do
copy = super()
self.class.attachment_definitions.each do |name, options|
ivar = "#attachment_#{name}"
copy.instance_variable_set(ivar, nil)
copy.send(name).assign send(name)
end
copy
end
end
end
end
This will assign the files from the old record to the new record programatically without knowing what the actual attachment definitions are.

Rails: clean up messy controller methods

I've got a long controller method with a lot of redirect conditions:
def show
get_param_user
if params[:id].match(/\D/)
#document = Document.where(:user_id => #user.id, :issue => params[:id]).first
else
#document = Document.find(params[:id])
end
unless #document.blank?
unless #document.template.name == "Media"
unless #document.retired?
#creator = User.find(#document.user)
if #creator == #user # if document exists, based on name and id
#document.components.each do |a|
redirect_to share_error_url, :flash => { :error => "#{#document.title} contains retired content and is now unavailable." } if a.retired? and return
end
render #document.template.name.downcase.parameterize.underscore
end
else # if retired
redirect_to share_error_url, :flash => { :error => "That document has expired." } and return
end
else # if media
redirect_to share_error_url, :flash => { :error => "Media has no public link." } and return
end
else # if document doesn't exist
redirect_to share_error_url, :flash => { :error => "Can't find that document. Maybe check your link. Or maybe it was deleted. Ask #{#user.name}." } and return
end
end
As you might guess, it's prone to errors under certain conditions. Is there a neater way to rewrite it to make it more robust? I know methods should have only one render or redirect_to each, but I'm not sure how else to achieve what I need.
Thanks!
A few specific and little things.
First, in general, better don't use until with an else condition, and even less if you can use an if:
unless #document.blank?
is the same as
if #document.present?
Second, you use
#creator = User.find(#document.user)
when usually you can simply use:
#creator = #document.user
The semantics is a bit different (in the first case if #document.user is nil you'll immediatelly get an exception, in the secnond case not), but the second is what you commonly need.
Third, if it is sensible, you can move code to the model away from the controller, and use some nice enumerators:
def has_retired_components?
#document.components.any?(&:retired?)
end
Also, your controller method is not that complex. It is just
if #document.present? and #document.showable? # also #document.try(:showable?)
render whatever
else
redirect_to error_url, flash: { error: error_message }
end
error_message may be the result of a method call (on the object itself if it makes sense). That way you move the logic to verify if the oject is showable somewhere else where it is less muddled with rendering logic.
The problem is that if you have a showable? method and another to show error messages, you have to be sure that the business logic is always correct in both. An option is to treat it similarly to how validations work: have a method (lets call it with the horrible name showable_validation here just to go on) that returns a hash with the errors and messages (the reasons for the object not being showable, like {title: 'this is an error message'}. The showable? method would then be:
def showable?
showable_validation.empty?
end
and then you would also have in the model something like:
def showable_error
showable_validation.values.first
end
And that would be the error_message (#document.showable_error). That way, the logic is only in one method.

Rails - Getting an error message from the model that is not a Validation error

So I have a method in a reservation model called add_equip. This method does some checking to make sure the added piece of equipment is valid (not conflicting with another reservation).
The checks work. If a added piece of equipment shouldn't be added it isn't, and if it should it is.
The problem is I can't figure out how to send the messages back up to the controller to be put in the flash message? I know I must be missing something here, but I've googled for a few hours now and can't really find any clear explanations how how to pass errors back up the the controller, unless they are validation errors.
add_equip in reservations_controller
def add_equip
#reservation = Reservation.find(params[:id])
#addedEquip = Equip.find(params[:equip_id])
respond_to do |format|
if #reservation.add_equip(#addedEquip)
flash[:notice] = "Equipment was added"
format.html { redirect_to(edit_reservation_path(#reservation)) }
else
flash[:notice] = #reservation.errors
format.html { redirect_to(edit_reservation_path(#reservation)) }
end
end
end
add_equip in reservation model
def add_equip equip
if self.reserved.find_by_equip_id(equip.id)
self.errors.add_to_base("Equipment Already Added")
return false
elsif !equip.is_available?(self.start, self.end)
self.errors.add_to_base("Equipment Already Reserved")
return false
else
r = Reserved.new
r.reservation = self
r.equip = equip
r.save
end
end
Any help would be greatly appreciated. I know I'm missing something basic here.
Using add_to_base to store the error message seems fine to me, you just need to figure out how to get it into the view.
How about:
flash[:notice] = #reservation.errors.full_messages.to_sentence
Assuming you're going to re-display a form, you could also probably use:
<%= f.error_messages %>
Or possibly:
<%= error_messages_for :reservation %>
Also, you might want to use flash[:error], then you can color it differently with a CSS class in your view.
I think I can see why errors are not being passed back to the user.
The problem is that you are sending a redirect to the user when the action fails instead of just doing a render, that means you lose any variables you set up to use within the request. Instead of adding errors to the flash, just render the edit page and set the flash to a normal message and everything should be fine.
For example:
def add_equip
#reservation = Reservation.find(params[:id])
#addedEquip = Equip.find(params[:equip_id])
respond_to do |format|
if #reservation.add_equip(#addedEquip)
flash[:notice] = "Equipment was added"
format.html { redirect_to(edit_reservation_path(#reservation)) }
else
flash[:error] = 'Error adding equipment'
format.html { render :action => :edit }
end
end
end
Now you can continue to use the normal form helpers for displaying error messages.
Also, just a little suggestion for the model code, try to use i18n when possible (including for flash messages in the controller). Although this is mostly a personal preference, it gives a logical home to all your messages and specific text, and alos allows you to create general or default messages which can be changed in one place instead of duplicating the change in multiple models and controllers.
eg.
def add_equip equip
if self.reserved.find_by_equip_id(equip.id)
self.errors.add_to_base(:already_added)
return false
elsif !equip.is_available?(self.start, self.end)
self.errors.add_to_base(:already_reserved)
return false
else
r = Reserved.new
r.reservation = self
r.equip = equip
r.save
end
end

Resources