Ruby on Rails 5 Star Rating with Star Rating Widget

Description

With countless rating systems out there I decided to go with JQuery’s Star Rating Widget Plugin for one of my pet sites: www.recisphere.com. The only problem with this is a complete lack of documentation on how to go about using it. I did a dirty hack but it seems to work great so I am going to show you how to set up a rating system with ruby on rails and then use the star rating widget.

Step 1:

I won’t go through this in detail but you will need to have Rails <= 3.09 installed (possibly works in earlier versions but it's not tested) and include the most recent Jquery and plugin javascript and css files. Just follow the Star Rating Widget’s install and required documents section. Also note I use the gem ‘simple_form’, but this can easily be edited to your specific needs.

Step 2:

The plugin pretty much puts makeup on an already existing rating system. You will need to determine the best way to store your ratings and work with that data. Here I will simply show the input. The Star Rating Widget can take input from a few different types. I prefer to use radio buttons. Thus what I did was

rails g scaffold user_rating user_id:integer recipe_id:integer user_rating:decimal

This gives us a scaffold and controller to work with. The next step was to edit the controller.

def create 
    @user_rating = UserRating.find_or_initialize_by_user_id_and_recipe_id(params[:user_rating])
    @user_rating.update_attributes(params[:user_rating])
    respond_to do |format|
      if @user_rating.save
        format.html { redirect_to(@user_rating, 
          :notice => 'Recipe was successfully created.') }
        format.js
        format.xml  { render :xml => @user_rating, 
          :status => :created, :location => @user_rating }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @user_rating.errors, 
          :status => :unprocessable_entity }
      end
    end
  end 
 def edit
    @user_rating = UserRating.find_by_user_id_and_recipe_id(params[:user_rating])
  end

  def update
    @user_rating = UserRating.find_by_user_id_and_recipe_id(params[:user_rating])
    respond_to do |format|
      if @user_rating.update_attributes(params[:user_rating])
        format.html { redirect_to(@user_rating, :notice => 'Recipe was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @user_rating.errors, :status => :unprocessable_entity }
      end
    end
  end
end

UserRating.find_or_initialize_by_user_id_and_recipe_id(params[:user_rating]) allows us to create if it doesn’t exist and edit later if it does exist.

Step 3:

Edit wherever you want your form to be kept. The Star Rating Widget is going to change the input from 10 (or how ever many radio buttons you want) into 5 stars. This will have a hidden input that will need to get submitted. The widget has a callback function that will allow any number of functions to be called everytime a rating is changed. By using the callback function with the widget we can successfully change the rating at any time, which is similar to how Netflix operates.


<%= simple_form_for :user_rating, :url => user_ratings_path, :remote => true do |f| %>
    <div id="stars-wrapper<%= @recipe.id.to_s %>">
      <% (1...11).each do |rates| %>
       <%= f.radio_button :user_rating, rates, :class => ["star", @recipe], :checked => rates == (checked)? true : false %>
      <% end %>
    <% if user_signed_in? %>
    <%= f.input :user_id, :as => :hidden, :input_html => { :value => current_user.id } %>
    <% end %>
    <%= f.input :recipe_id, :as => :hidden, :input_html => { :value => @recipe.id } %>
    <%= f.button :submit, :id => "starbutton" %>
</div>
<% end %>
<script type="text/javascript">
  $j("<%= "#stars-wrapper" << @recipe.id.to_s %>").stars({
    callback: function(value, link){
      document.getElementById("starbutton").click();
    }
  });
</script>

Step 4:

Now that we have a form submitting and storing values we need to add a little bit of ajax into this so that the page doesn’t get redirected continually. The secret to getting the ajax to work is in the format.js in the controller. Since format.js is a respond call in the create definition it will look for a create.js in the views directory.

Open “ROOTDIR”/app/views/user_ratings/create.js.rjs

page.replace_html('stars_show', render("recipes/star_fields"))

This uses the prototype library which comes with Rails. Be warned Prototype and JQuery do not work together very well. I had to make a hack to allow me to call jquery calls by making a javascript file called ‘noconflict.js’ and putting the following code in. This is why you see a $j in my javascript calls earlier.

jQuery.noConflict();
var $j = jQuery;

Conclusion

We just made a rating system for a rails site. Made the controller which will allow us to make a form and submit data to our database. We then made sure we had a working form and covered it with some pretty stars from the Jquery Star Rating Widget Plugin. Then we added a touch of ajax to allow us to not have to redirect or refresh an entire page each time a rating is made or changed.

Comments are closed.