Multiple model.save's in 1 if condition with an unless - ruby-on-rails

I'm trying to save a response and also save an issue if it's not nil in one condition so i don't have multiple if/else conditions complicating this logic.
For the use case where #response exists and issue is nil, this does not get into the if block.
Is there something obvious that i'm not seeing or can I not write my logic in one line like this?
Note: I know a transaction should be used, but i'm just trying to get a working prototype up right now.
if #response.save && (issue.save unless issue.nil?) # Does not get into the if block when #response exists and issue is nil
p 'in save'
format.html { redirect_to issue_path(params[:issue_id]), notice: success_message }
else
p 'not in save'
format.html { render action: 'new' }
end
This is what I have working now and I was hoping there was an easier 1 liner rather than this.
success = false
if issue.nil?
if #response.save
success = true
end
else
if #response.save && issue.save
success = true
end
end
if success
p 'in save'
format.html { redirect_to issue_path(params[:issue_id]), notice: success_message }
else
p 'not in save'
format.html { render action: 'new' }
end

You want to execute the "success" condition when:
#response.save succeeded AND
issue is nil OR issue is not nil and it saved successfully.
Thus, you can just do;
if #response.save and (issue.nil? or issue.save)
# Success
else
# Fail
end

Since you are using && after #response.save, in case of issue.nil? the issue is not saved and returns a nil, which always causes it to give you a nil. I hope this will get you on the right track.

Related

How to check the result of status's within a loop after it completes?

I have a loop that creates refunds for any purchase under an order.
An order can have multiple payments on it (if user upgrades shipping, adds items to cart after initial payment and resubmits, etc.) This isn't conventonial for ecommerce but it's needed in this case.
I have this loop:
orders_payments = Payment.where()
orders_payments.each do |btp|
transaction = gateway.transaction.find("#{btp.transaction_token}")
if transaction.status == "submitted_for_settlement"
void = gateway.transaction.void("#{btp.transaction_token}")
if void.success?
format.html { redirect_to user_orders_path, notice: 'Order refund was successfully refunded.' }
else
format.html { redirect user_orders_path, notice: 'Order refund unsuccessful.' }
end
elsif transaction.status == "settled"
refund = gateway.transaction.refund("#{btp.transaction_token}")
if refund.success?
format.html { redirect_to user_orders_path, notice: 'Order refund was successfully refunded.' }
else
format.html { redirect user_orders_path, notice: 'Order refund unsuccessful.' }
end
end
end
Of course, this doesn't work due to the redirects. But aside from not creating a redirect... and it's important I have a redirect with a message of the status... How can I check the status of the voids or refunds AFTER the loop is created.
I would like to be able to check which ones worked and which ones didn't work.
Any ideas on how I can do this?
Rather than do a single loop, you can use multiple loops to split up your data into categories.
The first step in your iteration is to retrieve the transaction object. This can also be done with map:
transactions = orders_payments.map do |btp|
gateway.transaction.find("#{btp.transaction_token}")
end
The next step is the if statement that splits the data into 2 groups according to their status. You can do use select to construct these 2 lists:
submitted = transactions.select do |transaction|
transaction.status == "submitted_for_settlement"
end
settled = transactions.select do |transaction|
transaction.status == "settled"
end
The next step is to process both of the lists and track which ones succeeded or failed. It sounds like at the end of this you want a "successful" and "failure" list for each of the transaction groups, leaving you with 4 lists. partition is a good method for this use-case:
successful_submitted, failed_submitted = submitted.partition do |transaction|
gateway.transaction.void("#{btp.transaction_token}").success?
end
successful_settled, failed_settled = settled.partition do |transaction|
gateway.transaction.refund("#{btp.transaction_token}").success?
end
You can use each_with_object to store the statuses. Here I'm storing them in a Hash by transaction_token.
Note that I'm using find_each rather than each to avoid pulling all the records into memory at once.
transaction_statuses = orders_payments.find_each.each_with_object({}) do |btp,status|
transaction = gateway.transaction.find("#{btp.transaction_token}")
status[btp.transaction_token] = case
when transaction.status == "submitted_for_settlement"
{ voided: gateway.transaction.void("#{btp.transaction_token}") }
when transaction.status == "settled"
{ refunded: gateway.transaction.refund("#{btp.transaction_token}") }
end
end
And then do something later with them.
transaction_statuses.each do |token,status|
case
when status[:voided]
case
when status[:voided].success?
puts "Order #{token} was successfully voided."
else
puts "Order #{token} void failed."
end
when status[:refunded]
case
when status[:refunded].success?
puts "Order #{token} was successfully refunded."
else
puts "Order #{token} refund failed."
end
end
end
If you are coming here because you are having the same or similar issue, before reading this, I suggest reading max pleaner's and Schwern's answers on this question.
The following works but may be a very beginner way I doing what I wanted to do.
I create a blank array, and then store the messages in it with flash.keep and then redirect after the loop completes with all messages, whether successful or declined refunds.
orders_payments = Payment.where()
messages = []
orders_payments.each do |btp|
transaction = gateway.transaction.find("#{btp.transaction_token}")
if transaction.status == "submitted_for_settlement"
void = gateway.transaction.void("#{btp.transaction_token}")
if void.success?
messages << "Refund Successful message"
else
messages << "Refund unSuccessful message"
end
elsif transaction.status == "settled"
refund = gateway.transaction.refund("#{btp.transaction_token}")
if refund.success?
messages << "Refund successful message"
else
messages << "Refund unSuccessful message"
end
end
end
flash.keep[:notice] = messages.join if messages.any?
respond_to do |format|
format.html { redirect_to route }
end
...

Improving ruby code

people said to me that I have an wrong code at this point. The main idea of this method is to create 1 event if user pressed once, and a row of events if user pressed daily or weekly. Everything works well, but code is so bulky.
def create
#event = Event.new(event_params
#event.start_time = DateTime.parse(params[:start_time], "%Y-%m-%d %H:%i")
#event.end_time = DateTime.parse(params[:end_time], "%Y-%m-%d %H:%i")
#event.user_id = current_user.id
#event.update_attributes(:repeat_id => #event.id) if #event.save
respond_to do |format|
if #event.save
format.html { redirect_to persons_profile_path }
else
format.html { render :new }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
interval = 60 if #event.repeat =='daily'
interval = 20 if #event.repeat =='weekly'
#creating row of events
if #event.repeat != 'once'
(1..interval).each do |i|
#event = Event.new(event_params)
#event.start_time = DateTime.parse(params[:start_time], "%Y-%m-%d %H:%i")
#event.end_time = DateTime.parse(params[:end_time], "%Y-%m-%d %H:%i")
#event.user_id = current_user.id
if #event.repeat =='daily'
#event.start_time = DateTime.parse(#event.start_time.to_s) + i.day
#event.end_time = DateTime.parse(#event.end_time.to_s) + i.day
end
if #event.repeat =='weekly'
#event.start_time = DateTime.parse(#event.start_time.to_s) + i.week
#event.end_time = DateTime.parse(#event.end_time.to_s) + i.week
end
#event.update_attributes(:repeat_id => k) if #event.save
#event.save
end
end
I can't find another solutions. Any ideas? Dont pay attention on parse methods on date, its needed to JS.
I think the issue is that you are using the controllers to do too much work and it's hard to read. Typically controllers look like:
def create
#client = Client.new(params[:client])
if #client.save
redirect_to #client
else
render "new"
end
end
Take a moment and think about your data. You create events. You want your events to appear in a row if the create button is pressed daily or weekly. So we are thinking about how to display it, that's a css/html issue, not something relating to controllers.
The reason people are saying your code as wrong is because you are doing something very atypical. If you submitted this code for school or a project you would get a low score because you are making everyone else work hard to understand your work, instead of following the same basic style that everyone was trained in.

data using a method is not being saved in database

I wanted to use #reload.status = boolean (true or false) but i could not get the boolean to be save into the database.
Also, i had been doing this all the time but it doesn't work now.
def return
ret = ActiveMerchant::Billing::Integrations::Ipay88::Notification.new(request.raw_post)
if ret.success?
#reload = Reload.find(ret.item_id)
#reload.status = true
#reload.save
redirect_to new_reload_path, notice: "Success"
else
#reload = Reload.find(ret.item_id)
#reload.status = false
redirect_to new_reload_path, notice: 'Reload unsuccessful'
end
end
I forgot to add #reload.save for the 'false' statement. (just like you see in "ret.success?" method call)

.where returning nil?

def new
#host = Host.find(params[:id])
#lastreview = Review.where("user_id = ? AND host_id = ?", current_user[:id], #host.id)
if #lastreview == nil
#review = Review.new
else
redirect_to #host, notice: "You already posted a review for this host!"
end
end
For some reason it is not returning nil even though their are no reviews? Am I misunderstanding the usage?
where returns an array - if there are no reviews in your table that match your conditions, the array that it returns will be empty, but it will never be nil.
Try this instead:
if #lastreview.empty?
#review = Review.new
else
redirect_to #host, notice: "You already posted a review for this host!"
end
If you don't actually need the last records, you can use exists?:
Returns true if a record exists in the table that matches the id or conditions given, or false otherwise.
This should work (Rails can handle the _id suffixes if everything is set up correctly):
def new
#host = Host.find(params[:id])
if Review.exists?(user: current_user, host: #host)
#review = Review.new
else
redirect_to #host, notice: "You already posted a review for this host!"
end
end

Help me refactor this nasty Ruby if/else statement

so I have this big method in my application for newsletter distribution. Method is for updating rayons and I need to assign a user to rayon. I have relation n:n through table colporteur_in_rayons which has attributes since_date and until_date.
I am a junior programmer and I know this code is pretty dummy :)
I appreciate every suggestion.
def update
rayon = Rayon.find(params[:id])
if rayon.update_attributes(params[:rayon])
if params[:user_id] != ""
unless rayon.users.empty?
unless rayon.users.last.id.eql?(params[:user_id])
rayon.colporteur_in_rayons.last.update_attributes(:until_date => Time.now)
Rayon.assign_user(rayon.id,params[:user_id])
flash[:success] = "Rayon #{rayon.name} has been succesuly assigned to #{rayon.actual_user.name}."
return redirect_to rayons_path
end
else
Rayon.assign_user(rayon.id,params[:user_id])
flash[:success] = "Rayon #{rayon.name} has been successfully assigned to #{rayon.actual_user.name}."
return redirect_to rayons_path
end
end
flash[:success] = "Rayon has been successfully updated."
return redirect_to rayons_path
else
flash[:error] = "Rayon has not been updated."
return redirect_to :back
end
end
def update
rayon = Rayon.find(params[:id])
unless rayon.update_attributes(params[:rayon])
flash[:error] = "Rayon not updated."
return redirect_to :back
end
puid = params[:user_id]
empty = rayon.users.empty?
if puid == "" or (not empty and rayon.users.last.id.eql?(puid))
msg = "Rayon updated.",
else
msg = "Rayon #{rayon.name} assigned to #{rayon.actual_user.name}.",
rayon.colporteur_in_rayons.last.update_attributes(
:until_date => Time.now) unless empty
Rayon.assign_user(rayon.id, puid)
end
flash[:success] = msg[msg_i]
return redirect_to rayons_path
end
there was a some double code. So remove that. But also, there is some business code in the controller, which should not be there. So i should refactor the controller code as follows:
def update
rayon = Rayon.find(params[:id])
if rayon.update_attributes(params[:rayon])
if params[:user_id] != ""
rayon.handle_update_user(params[:user_id]
flash[:success] = "Rayon #{rayon.name} has been succesuly assigned to #{rayon.actual_user.name}."
else
flash[:success] = "Rayon has been successfully updated."
end
return redirect_to rayons_path
else
flash[:error] = "Rayon has not been updated."
return redirect_to :back
end
end
The controller method now clearly deals with the actions that need to be taken, and sets a flash method accordingly if needed.
In the Rayon model you write the code what needs to be done when an update is done by a user:
class Rayon
def handle_update_user(user_id)
if (!users.empty? && users.last.id.eql?(params[:user_id]))
# do nothing!
else
colporteur_in_rayons.last.update_attributes(:until_date => Time.now) unless users.empty?
Rayon.assign_user(rayon.id,params[:user_id])
end
end
end
This clearly seperates the concerns. A rayon should know what happens when a user updates it (the name of the function could be improved to what you actually want it to mean, as that is not entirely clear to me).
It could be shorter, but i like to write explicitely that nothing needs to be done if the last user is the same as the current. Otherwise, actions need to be taken. If i understood correctly.
Here's my take at it. I put a bunch of comments inline describing why I changed what I did.
def update
rayon = Rayon.find(params[:id])
if rayon.update_attributes(params[:rayon])
user_id = params[:user_id] # using same value a couple times
# if-else strongly preferred over unless-else...
# so replace `unless foo.empty?` with `if foo.length > 0`
if user_id.length > 0 # then string != ""
# `if A && B` more readable than `unless X || Y` in my opinion
if rayon.users.length > 0 && rayon.users.last.id != user_id
rayon.colporteur_in_rayons.last.update_attributes(:until_date => Time.now)
end
# same line in if-else; pull outside
Rayon.assign_user(rayon.id, user_id)
flash[:success] = "Rayon #{rayon.name} has been successfully assigned to #{rayon.actual_user.name}."
else
flash[:success] = "Rayon has been successfully updated."
end
# all branches in here return this value, pull outside
return redirect_to rayons_path
else
flash[:error] = "Rayon has not been updated."
return redirect_to :back
end
end
The largest issue I noticed with your code is that you had a lot of duplication in your first if-block. All the branches in the true section reached return redirect_to rayons_path, so that got pulled to the end of the block. You had the same flash[:success] = ... twice, so that got pulled out as well.
if-else is much more readable than unless-else, so try to avoid using the latter. I'm not a fan of one unless immediately nested inside another, so I changed that to a compound if-statement.
One of the primary idioms in Rails is DRY (Don't Repeat Yourself), so try to identify spots where you do have repetitive code.
(Also note that I haven't tested this refactoring -- I'm pretty sure that the logic is still the same, but I can't prove it.)
def update
rayon = Rayon.find(params[:id])
unless rayon.update_attributes(params[:rayon])
flash[:error] = "Rayon has not been updated."
return redirect_to :back
end
if params[:user_id].empty?
msg = "Rayon has been successfully updated."
else
if rayon.users.empty?
Rayon.assign_user(rayon.id,params[:user_id])
msg = "Rayon #{rayon.name} has been successfully assigned to #{rayon.actual_user.name}."
elsif not rayon.users.last.id.eql?(params[:user_id])
rayon.colporteur_in_rayons.last.update_attributes(:until_date => Time.now)
Rayon.assign_user(rayon.id,params[:user_id])
msg = "Rayon #{rayon.name} has been succesuly assigned to #{rayon.actual_user.name}."
end
end
flash[:success] = msg
return redirect_to rayons_path
end

Resources