Rails 4 update inside for loop - ruby-on-rails

I have a view show.html.eb that displays details for an Order and displays all available Delivery Slots for the date of the order.
Eg:
Order 1
12/12/14
Delivery slots:
6.30 - 7.00
7.30 - 8.00
8.00 - 8.30
I then have a link next to each delivery slot which when clicked updates the field delivery_slot on the Order to the Id of the delivery slot that was clicked.
View Code
<% #slots = DeliverySlot.all.select {|slot| slot.day == #order.date} %>
<% #slots.each do |slot| %>
<%=slot.start_time.strftime("%I:%M%p") %> - <%=slot.end_time.strftime("%I:%M%p") %>
<%= link_to "Order", Order.update(#order,:delivery_slot => slot) %>
<br>
<% end %>
The issue is, when you click one Order link, all the order links are clicked (I can see this through the SQL in the terminal) so the end result is the delivery_slot field is always populated with the last delivery slot of the loop.
I appreciate that I am missing something here so could anyone explain:
1) Why all Order links are "clicked" when only 1 is clicked in practice.
2) Is there a better way to update the delivery_slot attribute on the Order?
Thanks

You are issuing an update when rendering the view.
<%= link_to "Order", Order.update(#order,:delivery_slot => slot) %>
You need to provide a path (or url_options which can be used to generate a path) as the second argument to link_to. Instead you are calling the update method. So when your view is rendered, you iterate over every delivery slot and issue an update for each one of them and as you have noted, the last one overwrites everything else.
If you observe your logs you will see that the SQL statements are seen in logs lines corresponding to the show request. You are updating the order even before any link is clicked.
Now since the link_to does not get a valid path, the actual HTML link is not pointing to anything. Clicking on it would be a no-op.
ri link_to is what you need to read.

So I finally arrived at the answer:
<%= link_to "Order", order_path(order:{:delivery_slot_id => slot}), :method => :put %>
I think what tripped me up in the end was that my strong parameters specific :delivery_slot_id and I was specifying :delivery_slot so I assumed I was doing something wrong when the answer was very simple.

Related

rails: link_to to pass list of selected checkboxes into params

I am not sure what the correct approach is for my situation:
I want to create a link_to pushing all checkboxes with value="1" into an array, or individually if array is not possible, but I am at a loss of how to express that?
<% #cards.each do |card| %>
<%= check_box("#{card.name}", card.id, {checked: true}) %><%= "#{card.name}" %>
<% end %>
(Rails 4.2)
After a long time of dead ends, trying to make it a 'clean' solution I ended up with this very dirty approach. But as they say, done is better than perfect:
Create a link_to that would include all the cards, but add one additional params: user_selected_cards = "".
Create a javascript that listens to for checking/unchecking of the checkboxes and reads the id associated with that specific checkbox. Then take that incoming info and add or remove it to the actual url that the link_to generates by finding the user_seletected_cards= portion in the url and add or remove the id depending on if isn't or already is added to the list after the equal sign.

Ruby on Rails: Create action seems to fail

I've been stuck on this problem for days. First off, I now know this code is horribly wrong. I've been trying to fix it, but it's way more important in the short term that this link is created. In my view (I'm so sorry), I call the create method like this, if a certain condition is met:
index.html.erb (controller: subjects_controller)
<%= Baseline.create(subject_id: sub.subject_id) %>
I do this several times on the page, from several controllers (i.e., FollowUp3Week.create(subject_id: sub.subject_id) works). All of the other controllers work. I've checked, and double checked, every controller action and compared them to each other, and they appear the same.
So instead of creating the record, it leaves something like this instead:
#<Baseline:0x007f944c4f7f80>
I'm at a bit of a trouble shooting loss. Once again, I know how wrong it is to have these database actions in the view. But I didn't know that when I made the page, and I really need this to function before I can take the time to learn how to rearrange everything through the MVC.
Any advice would be greatly appreciated. Let me know what other code you might want to look at.
EDIT 1.
link Creation:
<% if Baseline.where(subject_id: sub.subject_id).first != nil %>
<%= link_to "edit", baseline_path(Baseline.where(subject_id: sub.subject_id).first) %>
<% else %>
<%= Baseline.create(subject_id: sub.subject_id) %>
<% end %>
First of all, making DB calls in views is a big NO! NO!
Secondly, to answer why you see the output as
#<Baseline:0x007f944c4f7f80>
for
<%= Baseline.create(subject_id: sub.subject_id) %>
You are trying to render an instance of Baseline model. Its just how the instance would be displayed. If you want to display a particular attribute's value in view then just do
<%= Baseline.create(subject_id: sub.subject_id).subject_id %>
Also, this code will not create a link. To create a link you would have to call link_to helper in your view.
What you need to do is, move the Baseline.create call in the controller. Set an instance variable in the action which renders this particular view as below:
def action_name
#baseline = Baseline.create(subject_id: sub.subject_id)
end
After this in you view you can easily access all the attributes of #baseline instance.
For example:
To access subject_id
<%= #baseline.subject_id %>
To create a link for show page of #baseline, provided you have a RESTful route to show action for baselines
<%= link_to "Some Link", #baseline %>

How do I create a button that increments an attribute of a model and can be pressed once per browser?

An apartment_listing has many reviews, and a review belongs to an apartment_listing.
In the file views/apartment_listings/show.html.erb, I show a list of reviews for that particular apartment_listing. These reviews are generated with the partial view apartment_listings/_review.html.erb like so:
<%= render :partial => "review", :collection => #apartment_listing.reviews %>
In _review, I want to have a button that, when pressed:
Increments that review's helpful_count attribute.
Makes it so that it cannot be pressed again while in the same browser - probably using cookies.
I feel like the former shouldn't be too hard to figure out, but it's got me beat. I'm really not sure where to start with the second goal.
EDIT: I managed to update the review's helpful_count attribute with this code in apartment_listings/_review.html.erb:
<%= form_for review, :method => :put, :remote => true do |f| %>
<%= f.hidden_field :helpful_count, value: (review.helpful_count + 1) % >
<%= f.submit 'Helpful?' %>
<% end %>
However, I'm not sure if this is the best way to do it, and I'd like to be able to disable the button after it is clicked.
Your code for updating helpful_count has the potential for problems. Imagine two users have loaded an apartment on their web page. One of them marks it helpful, and the next one does as well. Since when they initially loaded the page, helpful_count was the same, after both of them click helpful, the count will only be incremented by one: it would be updated twice to the same value.
Really, you want to create a new action, probably under the reviews resource for an apartment. That action could use ActiveRecord's increment method to update the helpful_count (technically there's still a race condition in increment!, you'd encounter it much less often) http://apidock.com/rails/ActiveRecord/Persistence/increment%21
Cookies seem like a reasonable solution for the latter problem. Simply bind to submit on the form with jQuery, and create the cookie in the handler.
What does the code look like in your reviews controller? More experienced RESTful coders might be able to speak more coherently on this, but the way I see it, incrementing the helpful_count attribute should be an action sent to the reviews controller. That way, you can create a link that performs the action asynchronously.
For example, inside _review.html.erb:
<% collection.each do |review| %>
<%= link_to "Mark as Helpful", "/apartment_listing/#{#apartment_listing.id}/reviews/#{#review.id}/incHelpful?nonce=#{SecureRandom.rand(16)}", :remote => true, :method => :put %>
# ... Do something cool with your review content ...
<% end %>
Inside your ReviewsController class:
def incHelpful
unless params[:nonce] == session[:nonce][params[:id]]
#review = Review.find(params[:id])
#review.helpful_count += 1
#review.update_attributes(:helpful_count)
session[:nonce][params[:id]] = params[:nonce]
end
render :nothing
# Optionally return some javascript or JSON back to the browser on success/error
end
Inside /config/routes.rb:
put "apartment_listing/:apart_id/reviews/:id/incHelpful" => "reviews#incHelpful"
The main idea here is that actions that edit a resource should use the PUT http method, and that change should be handled by that resource's controller. Rails' built-in AJAX functions are engaged by setting :remote => true inside the link_to helper. The second concept is that of a nonce, a random value that is only valid once. Once this value is set in the user's session, subsequent requests to incHelpful will do nothing.

How to setup delete selected messages feature in ruby on rails

I'm about to finish the final part of my thread messaging system for users. All deletion works great however before I move on to my next feature I'd like to give users the ability to delete selected messages.
Here's a way I've thought of doing it so far.
Add a check box tag to the each loop that loops through each message.
Have a "delete selected" link that goes to my messages controller "destroy_selected_messages" action.
What I need to do is some how grab an array of all the selected messages id's. Then pass it to the path as an argument. The delete all links path.
<%= link_to 'Delete Selected', messages_destroy_selected_messages_path(ARRAY_WITH_IDS), :method => :delete, :confirm => "Are you sure?" if #current_thread_messages.any? %>
This delete selected link won't be part of the loop because I don't want it showing for every message but at the top of the thread instead.
I need to figure out how to pass the array with all the selected messages ideas into that argument. How do I get them from the each loop without going into my messages helper and writing some funky method.?
I have the checkbox tag e.g. check_box_tag ... how do I setup an empty array and then so I can pass in the messages id? e.g.:
<%= check_box_tag ......., :value => message.id &>
Help would be appreciated. I looked at an old screencast in railscasts but it's from 2007 I think.
Kind regards
You can make name="message_ids[]" for multi select inputs. It will get passed as an array through HTTP server to your params[:message_ids].
From the HTML side of the problem, I think that form helper <%= check_box_tag "message_ids[]", :value => message.id %> should suffice.
In the controller action, log the params[:message_ids] and look it up, it should be an Array.

Need help modifying multiple models example from advanced rails recipes book

I'm developing a simple rails app for my own use for learning purposes and I'm trying to handle 2 models in 1 form. I've followed the example in chapter 13 of Advanced Rails Recipes and have got it working with a few simple modifications for my own purposes.
The 2 models I have are Invoice and InvoicePhoneNumber. Each Invoice can have several InvoicePhoneNumbers. What I want to do is make sure that each invoice has at least 1 phone number associated with it. The example in the book puts a 'remove' link next to each phone number (tasks in the book). I want to make sure that the top-most phone number doesn't have a remove link next to it but I cannot figure out how to do this. The partial template that produces each line of the list of phone numbers in the invoice is as follows;
<div class="invoice_phone_number">
<% new_or_existing = invoice_phone_number.new_record? ? 'new' : 'existing' %>
<% prefix = "invoice[#{new_or_existing}_invoice_phone_number_attributes][]" %>
<% fields_for prefix, invoice_phone_number do |invoice_form| -%>
<%= invoice_form.select :phone_type, %w{ home work mobile fax } %>
<%= invoice_form.text_field :phone_number %>
<%= link_to_function "remove", "$(this).up('.invoice_phone_number').remove()" %>
<% end -%>
</div>
Now, if I could detect when the first phone number is being generated I could place a condition on the link_to_function so it is not executed. This would half solve my problem and would be satisfactory, although it would mean that if I actually wanted to, say, delete the first phone number and keep the second, I would have to do some manual shuffling.
The ideal way to do this is presumably in the browser with javascript but I have no idea how to approach this. I would need to hide the 'remove' link when there was only one and show all 'remove' links when there is more than one. The functionality in the .insert_html method that is being used in the 'add phone number' link doesn't seem adequate for this.
I'm not asking for a step-by-step how-to for this (in fact I'd prefer not to get one - I want to understand this), but does anyone have some suggestions about where to begin with this problem?
There is a counter for partial-collections:
<%= render :partial => "ad", :collection => #advertisements %>
This
will render "advertiser/_ad.erb" and
pass the local variable ad to the
template for display. An iteration
counter will automatically be made
available to the template with a name
of the form partial_name_counter. In
the case of the example above, the
template would be fed ad_counter.
For your problem of detecting whether a row is the first one or not, you could add a local variable when calling the partial:
<%= render :partial => 'mypartial', :locals => {:first => true} %>
As it would be much easier to detect in the main file, whether a row is the first or not I guess.
Instead of detecting whether a phone number is the first, you could also detect whether a phone number is the only one. If not, add remove links next to all numbers otherwise, do not display the remove link. Note that besides showing/hiding the link, you also need to add code, to prevent removing of the last number by (mis)using an URL to directly delete the number instead of using your form.

Resources