Rails 3 rateable model - How to create ajax rating? - ruby-on-rails

How do I create some simple ajax rating like there is on this page http://watir.com/documentation/ ? Every visitor should be able to rate, I dont need to set permissions. I want to store the ratings in a column. So the user can sort by ratings.
Please make an detailled example. I am not a javascript expert.
I have found an example to create ratings from scratch. But it authorizes a user.
Can someone show me a guidance to create ratings without a Rater (user)? It should not only store the values but also count the votes.
http://eighty-b.tumblr.com/post/1569674815/creating-an-ajaxified-star-rating-system-in-rails-3

What I did recently to add a simple rating mechanism to an existing project was the following:
I added two fields to an existing table (which contained the items to be rated). Those were:
rating_score => The current score
ratings => The number of ratings which led to the score
For example, if five users would've voted "5" for the current item, rating_score would be 25, and ratings would be 5. The current rating would be computed as rating_score / ratings.
Then I added a new method to the controller of the items to be rated, called "rate", which looked something like:
def rate
#item = Item.find(params[:id])
#container = "item"+#item.id.to_s
#item.rating_score += params[:rating].to_i
#item.ratings += 1
#item.save
respond_to do |format|
format.js
end
end
My view for that method, called rate.js.erb, would look something like
$('#<%= #container %>').html('<%= escape_javascript(render(partial: 'rating', locals: { item: #item })) %>');
This code works only if you've got jQuery installed, but it should be easily translatable to Prototype or whatever JS framework you may be using.
And the partial for the rating, called _rating.html.erb, was something like:
<%= form_tag url_for(controller: 'items', action: 'rate', id: item.id), remote: true %>
<%= rating_stars(item.rating_score, item.ratings) %>
<%= item.ratings %> Votes
</form>
In this partial, the rating_stars() helper method generated some kind of star-like representation for the rating, but you can do that however you like.
By setting "remote: true" in the form_tag helper, your Rails installation should automatically transmit the request via the installed Javascript framework. This magic is part of the whole unobtrusive javascript thing going on in Rails lately, which is actually pretty cool.
Hope this gives you an idea of how to realize a very simple rating system with no IP lock feature whatsoever in Rails.

Looks like the Watir documentation rating system is set up through polldaddy.
For this particular case, it appears they include the polldaddy javascript which populates the rating div container with their star rating widget. Looks like there's a corresponding javascript object which you can inspect:
console.log( PDRTJS_101132_page_2.avg_rating ); //=> 4
If you had numerous rating widgets like these on a page, and you were able to make a collection of the javascript objects paired with their divs, presumably you could sort them based on that average rating property.

Related

How to compare two items within Ruby on Rails?

So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id

using rails how do i only show the id submitted via a text box from a table

I've got a table full of information at the moment, Ideally i need the information from a database table to be viewed via a link.
I only have the controller and some standard html (the html is just a h1 tag at the moment)
The HTML will be standard throughout like a template.
The way i'm seeing what i want in my head is the users will get a link which would be events_prev/{{id from DB here}} and depending on the ID the information on the page will be populated from the corrisponsing DB Row
Heres my controller
class Events::EventsPrevController < ApplicationController
def index
#events = Event.where(id: id)
end
def show
render :nothing => true
end
end
Sorry if its super confusing.
Welcome to rails.
Ok, there's a couple of things that will get you in the right directions. Firstly, you REALLY need to do a little reading to understand how the controller and the routes and the views are linked together in rails, that'll help you tons.
But moving on to your specific issues:
Parameters:
All data passed via a url (get, post, put, doesn't matter the method) is available in the controller in an array object called params - So that means when want to access the data the user submitted, you'll use something like
#event = Event.where(id: params[:id])
Routes:
It looks like you're trying to use the index page. In rails index is a RESTful route which generally points to a collection of model objects. The show route will point to an individual object so you should instead make your link point to the show path instead of the index path.
You can view the routes available on a model on a command line using:
bundle exec rake routes
An example of what your routes might look like:
prev_events GET /prev_events(.:format) prev_events#index
POST /prev_events(.:format) prev_events#create
new_prev_event GET /prev_events/new(.:format) prev_events#new
edit_prev_event GET /prev_events/:id/edit(.:format) prev_events#edit
prev_event GET /prev_events/:id(.:format) prev_events#show
PATCH /prev_events/:id(.:format) prev_events#update
PUT /prev_events/:id(.:format) prev_events#update
DELETE /prev_events/:id(.:format) prev_events#destroy
Link
Based on the routing table, you now should see that the link you need your users to click on might look like this (given that event is your object:
<%= link_to event.title, event_path(event.id) %>
or shortcutted
<%= link_to event.title, event %>
View
For the view this is entirely dependent on the data in the Event model. Since the data is stored in #event you'll simple use the attributes on the event model to render the html however use like, e.g.
<h3><%= #event.title %></h3>
<span><%= #event.start_time %></span>
You should read up on Rails controllers: by default the action index is used to show all of the records and what you're talking about should belong to the show action. The default routes take care of the id passing to your show action.
Index action is mean to show list of items in view and Show action is used to show a single item.
what you are doing in index is actually mean to be in show action.
Reason:
#events = Event.where(id: id)
this line will give u a single record in every case it means it should be in Show action.
your code should look like:
def show
#event = Event.find(params[:id])
[your logic: what you want to do with that #event]
end

How to organize business logic and js logic, for voting in Rails?

In rails, I want to handle the visual vote icons and the database record upon clicking on a vote button. It seems like it would be a good idea to have both of these in one authoritative location (so that they don't go out of sync), rather than duplicating the control flow logic for the front and back ends in different files.
There is some logic as follows, and I need to handle the front end aspect and the back end aspects.
What's he best way to do this in rails?
There is a poll, and there are different options. Each option is given a scope id.
The following code handles intuitive voting behavior for an option that allows only one option.
model method called by controller
if current_user voted_for? #votable
#votable.unvote_by current_user
#votable.vote_by current_user, scope: params[:vote_type] unless current_user voted_for? #votable, scope: params[:vote_type]
else
#votable.vote_by current_user, scope: params[:vote_type]
end
Now this is fine for the back end, and I need front-end.
asset javascript file
// Detect if record was voted for by current user, obtain #id
// add class 'voted' to child with matching #id
$('#poll .option').on(click, ->
if ($('this').first().class('voted') ) {
$('this').first().removeClass('voted');
} else if ( $('this').siblings('option').first().class('voted') ){
$('this').siblings('option').first().removeClass('voted');
} else {
$('this').first().addClass('voted');
}
Will this work properly with a rails remote: true link?
html.haml
#poll
= link_to (content_tag :div, class: #voted), vote_path(:vote_type)
= link_to "", vote_path(vote_type: "2"), class=
Using acts_as_votable API to conditionally set class in view. Use CSS to style 'voted'
controller
def show
# can be moved to model
if current_user.voted_on? #votable
#voted = 'voted'
else
#voted = ''
end
end
I have not used ajax calls in the above. Do I need to?
The above javascript seems like it would get very messy very quickly, if I use ajax.
It also doesn't prevent multiple voting, or the visual votes going out of sync with what's actually in the database.
Now the above so far duplicates the if/else control flow in the back and front ends.
But is it better to combine them in a js.erb file where it does both?
That's why I was thinking it might be better to combine things into one js.erb that handles both front and back. But that doesn't seem like good design either. Perhaps there is a way using ajax to put in more validations and increase robustness? Anyway, this is all just nice-to-haves. As long as it works, that's good.
It seems having a custom js.erb file is not good for using responds_with... I am confused. on how to proceed.
Sorry for the multiple questions within this question. I am just trying to implement an intuitive voting system, and it's not very easy to get bits and pieces of information from different sources.
Thanks in advance.
This is a very generic question. Aw as for the backend you can use some of the gems to do that as it would make your life easier something like acts_as_votable gem for rails.
Then include it at your ActiveRecord model:
class Record < ActiveRecord::Base
acts_as_votable
end
Now you will be able to use a lot of helper methods like:
#record = Record.create!(attributes)
#record.liked_by #user1
#record.downvote_from #user2
#record.vote_by :voter => #user4, :vote => 'bad'
#record.vote_by :voter => #user5, :vote => 'like'
As for your fronend, you can send some Ajax requests with the like of link_to remote: true or form_for remote:true. Then you can use RJS or even doing that manually on the client side using jQuery and change the state of the divs.
I'll demonstrate with code here:
In Controller you'll have an action something like:
def vote
#record = Record.find(params[:post_id])
#record.liked_by current_user
end
Html
<%= link_to 'like', vote_path(#record), class: 'vote', remote: true %>
JS
$('.vote')
.on('ajax:send', function () { $(this).addClass('loading'); })
.on('ajax:complete', function () { $(this).removeClass('loading'); })
.on('ajax:success', function (data) { $(this).html(data.count); });

Parent-Child form

How do I show a parent-child relationship on a single page in Rails? I don't need a form as I simply want to show the information (no edits or updates required). The parent is customer and the child is orders. I want to able to select a customer, display some customer information such as address, and list all orders placed in row format. Thanks.
It isn't entirely clear what you want here. I am assuming you want to have a list of customers, and when you click on one of them, other page is being populated with its details. If so, the nicest solution is to use Ajax event which will get the details from a server and place it in given div. Action in customers_controller would be sth like:
def details
#customer = Customer.find(params[:id])
render 'details', :layout => false # remember to add view 'details'
# and appropriate route in routes.
end
and javascript (jQuery) (note it is not perfect ajax call, it doesn;t handle multiple clicks and other problems, but shoud get you started)
$('.customer-row').click(function() {
customer_id = $(this).data('id');
$.ajax({
url: "/customers/" + customer_id + "/details",
type: "get",
success: function(details) {
$('#customer_details').html(details)
}
});
});
Then in your basic view you need to have div with id 'customer_details' somewhere and each of customer row should look like class="customer-row">.
In terms on how to access child objects inside details view it is as simple as:
<% #customer.orders.each do |order|>
# display order
<% end %>
Assuming you have your model associations set up properly..
In the controller,
#customer = Customer.find(params[:id])
#orders = #customer.orders.all
Then in the view, use the orders variable.

Get save checkbox values in haml even if submit button not selected

This is SaaS ("Rotten Potatoes") training question that has been discussed many times last year but I don't seem to find answer to this.
I have a form with checkboxes for ratings that generate hash named ratings. Then I uses params[:ratings] to pass latest checkbox selections to different pages. My code works correctly when user submits the form. But if they wander around without clicking the submit button then the latest values are lost.
I think that the latest values needs to saved whenever user checks/unchecks a checkbox, but don't know how.
I need to do this using haml and ruby, no javascript etc.
In you're movies controller you have the instance variable #selected_ratings where you save all the current ratings.
if params[:ratings] != session[:ratings] and #selected_ratings != {}
session[:ratings] = #selected_ratings
redirect_to :sort => sort, :ratings => #selected_ratings and return
end
#movies = Movie.find_all_by_rating(#selected_ratings.keys, ordering)
And in the movies index, in the form_tag you have
= check_box_tag "ratings[#{rating}]", 1, #selected_ratings.include?(rating)
For the sake of completeness.
Requirement was that if the user closes the web page and comes back later then the previous selections should be available. So I did it with cookies and passed the test case.

Resources