Rails 3 - Increment Model Data on click to a link_to - ruby-on-rails

I'm working on a small picture application. That I'm trying to do is build a counter to track how many times each image is clicked.
Right now I have in my view:
<% #galleries.each do |g| %>
<% for image in g.images %>
<div id="picture">
<%= render 'top_nav'%>
<%= link_to g.source, :target => true do %>
<%= image_tag image.file_url(:preview) %>
<% g.vote %>
<% end %>
<%= will_paginate(#galleries, :next_label => "Forward", :previous_label => "Previous") %>
</div>
Obviously this doesn't work, as the g.vote executes every time it's rendered, not clicked. Here's the vote method in my model:
def vote
self.increment!(:score)
end
I'm looking for a solution to run the vote method only when the image above is clicked. The links are to external resources only, not to a show action. Should I be building a controller action that's accepts a post, executes the vote, then redirects to the source?
Anyway, looking for some ideas, thanks.

I've done something similar, but keeping a count of how many times a Download link was clicked. This was awhile ago and I didn't know about Ajax at the time, but now I would recommend using jQuery (a great library in my opinion, but you could use something else) and do an Ajax call when the image is clicked that would execute some controller action which would increment that vote.
The other way, which is what I did in my scenario, and is what you talked about there, is creating a custom action in the controller that accepts a post. But I have to ask as well, does clicking on the image do something else in the behaviour of your website? For example, if when you click the picture, another random image is supposed to come up, that means you'll already have an action to load a new image and it be easy to stick the vote up in there before showing a new image. Otherwise you'd have to create the new controller action. If that's the case, the Ajax would be more efficient as the user wouldn't see a momentary flash as the page was refreshed (especially bad if the refresh time is long).

Related

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.

Navigation Indicator for Current Page and Sub-Pages

I have seen similar questions answered but I cannot find exactly my problem and I can't work out how to change the others to make them fit.
I am writing a simple rails 3 app that has 2 main sections. One called Students and one called iConnects with multiple pages for each. I have a simple navigation bar to allow navigation to either by navigating to the first record of either of these sections when clicked. This click also sets a class which has a colour set on it in CSS to indicate which section you are on. This however only works if you click that link once. If you navigate to any of the sub-pages e.g. students/2 the class is obviously not present there and there is no visual indication.
My question is how do I have a persistent class added to the navigation to indicate whether the user is on either students or iConnects?
I have a separate partial in my shared which I am pulling in with this code..
<div id="section_navigation">
<%= link_to 'iConnect', #iconnects.first, :class => current_page?(#iconnects.first) ? "current" : "" %>
<%= link_to 'Students', #pages.first, :class => current_page?(#pages.first) ? "current" : "" %>
</div>
And from my students show.html.erb
<%= render :partial => 'shared/navigation' %>
I am fairly new to rails and so can't think how to do this myself and I am resisting the urge to do it in a Javascript way as I know backend output is the correct approach for this.
Any hints or tips are more than welcome. Thank you for your time...
Use something like simple-navigation

How to pass a lambda to link_to

I would like to use lambda as a parameter for link_to for the code below: edit.html.erb
<h2>Edit customer info</h2>
<%= render 'form' %>
<%= link_to(#return_to) do %>
Back
step_back()
<% end %>
Here is the def for step_back:
#return link for previous page in page step
def step_back
session[:page_step] -= 1
end
The problem with the code above is that the step_back() is executed as soon as the edit.html.erb is loaded. Actually the step_back should only be executed when the user clicks the Back link. I figure that only lambda can accomplish this.
Any thoughts?
Your options are limited since you're interacting with the session.
You're getting that #return_to from somewhere; it'd probably be easiest to call an action that gets the same data and redirects to it, and does the same session manipulation.
See about writing your step_back() function in javascript and attaching it to an onClick html attribute on an tag instead of using the link_to rails helper
Then also .preventDefault() the event with javascript if you don't want the link to go anywhere, or to '#'
This will allow the code to execute on the click event in the browser and not during asset compilation before the page is served.

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