Star Ratings in Rails Using CSS

Star ratings are one of the most common features in the front-end of a system that allows users to provide feedback.  You see it on Amazon, IMDB, App Store, the list goes on.  I wanted to incorporate this functionality in a Rails application, so I was looking around the web.

My preferences are:

  • No JavaScript if possible.
    Most browsers have JavaScript turned on and the chances are with mostly Web 2.0 applications, they wouldn’t function without JavaScript.  However, there has been growing unpopularity of shipping a lot of JavaScript libraries (even if mimified) mainly due to increasing number of mobile users and the limited bandwidth available on mobile devices.  The less JavaScript the better for your web app.
  • No instant communication to the server necessary for the rating to be recorded.
    In some implementations, every time a star is selected, jQuery sends the rating to the back-end immediately.  Again, bandwidth does not come free.  Not just on mobile devices but on any connection.  In some parts of the world, high speed internet is still a scarce commodity.  If your app takes too long to load, the user experience will be off-putting.  Therefore, it is much preferable to have the rating sent to the back-end as part of the form submission.

In my attempt to find the most suitable one for my Rails app, I will document my findings here, in the hope that any person who is new to this arena like myself finds it of use.

The first one that I came across was called Star Ratings With Very Little CSS by Chris Coyier.  Before I stumbled upon this, I did not know that it was possible to do this without JavaScript.  As the author has advised, there still needs to be a way to register what the user has selected.  Otherwise, how would you send to the back-end what was selected?  His post referred to Accessible star rating widget with pure CSS by Lea Verou which I went on to explore further.

The trick involves using radio buttons to be displayed as stars.  As the title says, it involves no JavaScript.  I tried to integrate this to my Rails app and have listed below the steps involved in a tutorial on submitting book reviews (the code for this sample project is available on GitHub).  A book review has a summary, comments, star ratings and of course, the book being reviewed.

  1. Place the CSS code in app/assets/stylesheets/.  In our case, the CSS goes into bookreviews.css.
  2. The HTML code goes into _form.html.erb under app/views/bookreviews.
  3. We also make changes to the model, the controller and the view of bookreview so that ratings can be received, stored and displayed accordingly.

Using the HTML code as-is, we find that the ‘rating’ attribute will not be part of the bookreview hash of the params hash in the request sent to the server:

Started POST "/bookreviews" for at 2015-05-12 19:05:50 +0000
Processing by BookreviewsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"PQqwM/AQG5aUKDjqU8yK/tu5PeQeQvxgYqfkoxnZoa8=", "rating"=>"4", "bookreview"=>{"summary"=>"Fantastic Book", "details"=>"Full of excitement and adventure.", "book_id"=>"2"}, "commit"=>"Create Bookreview"}

If that works for you, you need not read further.  However, we want ‘rating’ to be part of ‘bookreview,’ since it is an attribute of the bookreview model.  In order for this to tie up with Rails’ form_for generator, we need to make a few changes.  Essentially, the aim is to generate the similar HTML code using Rails’ form helpers.  Take a look at the radio_button and label methods to understand how the tag attributes are generated.  For the star ratings CSS to work, the value of the for attribute in the label tag must be the same as the value of the id attribute in the input tag.  In the original HTML code, they are “star5” and “star5,” “star4,” “star4” and so on.  To remind ourselves, this is what the original HTML code looks like:

<fieldset class="rating">
    <legend>Please rate:</legend>
    <input type="radio" id="star5" name="rating" value="5" /><label for="star5" title="Rocks!">5 stars</label>
    <input type="radio" id="star4" name="rating" value="4" /><label for="star4" title="Pretty good">4 stars</label>
    <input type="radio" id="star3" name="rating" value="3" /><label for="star3" title="Meh">3 stars</label>
    <input type="radio" id="star2" name="rating" value="2" /><label for="star2" title="Kinda bad">2 stars</label>
    <input type="radio" id="star1" name="rating" value="1" /><label for="star1" title="Sucks big time">1 star</label>

The first part we need to write is the radio button:

<%= f.radio_button(:rating, "5") %>

which generates the following HTML code:

 <input id="bookreview_rating_5" name="bookreview[rating]" type="radio" value="5" />

So, id is concatenation of the current model name, the (humanised?) string generated from the symbol (rating) and the value (5) passed to the radio_button method of the FormBuilder object (f).  As mentioned above, for the star rating to work, the value of id in the input tag must be the same as that of for in the label tag.  Using the label method, we write the following after the input_tag:

<%= f.label(:rating.to_s + "_5", "Rocks!") %>

which generates the following HTML code:

<label for="bookreview_rating_5">Rocks!</label>

There you have it.  The code in Rails is:

<fieldset class="rating>
  <legend>Please rate:</legend>
  <%= f.radio_button(:rating, "5") %> <%= f.label(:rating.to_s + "_5", "Rocks") %> 
  <%= f.radio_button(:rating, "4") %> <%= f.label(:rating.to_s + "_4", "Pretty good") %> 
  <%= f.radio_button(:rating, "3") %> <%= f.label(:rating.to_s + "_3", "Meh") %> 
  <%= f.radio_button(:rating, "2") %> <%= f.label(:rating.to_s + "_2", "Kinda bad") %> 
  <%= f.radio_button(:rating, "1") %> <%= f.label(:rating.to_s + "_1", "Sucks big time") %>

And the generated HTML code is:

<fieldset class="rating">
 <legend>Please rate:</legend>
 <input id="bookreview_rating_5" name="bookreview[rating]" type="radio" value="5" /><label for="bookreview_rating_5">Rocks!</label>
 <input checked="checked" id="bookreview_rating_4" name="bookreview[rating]" type="radio" value="4" /><label for="bookreview_rating_4">Pretty good</label>
 <input id="bookreview_rating_3" name="bookreview[rating]" type="radio" value="3" /><label for="bookreview_rating_3">Meh</label>
 <input id="bookreview_rating_2" name="bookreview[rating]" type="radio" value="2" /><label for="bookreview_rating_2">Kinda bad</label> 
 <input id="bookreview_rating_1" name="bookreview[rating]" type="radio" value="1" /><label for="bookreview_rating_1">Sucks big time</label>

The request sent to the server now shows that ‘rating’ is now part of the bookreview hash:

Started POST "/bookreviews" for at 2014-05-12 20:32:24 +0000
Processing by BookreviewsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"PQqwM/AQG5aUKDjqU8yK/tu5PeQeQvxgYqfkoxnZoa8=", "bookreview"=>{"rating"=>"4", "summary"=>"Fantastic Book", "details"=>"Full of excitement and adventure.", "book_id"=>"2"}, "commit"=>"Create Bookreview"}

Depending on your needs, you might want to style the form (e.g., removing the legend tag), play with the CSS code for different star colours and sizes.  Be sure to adjust the CSS parameters and test it in different browsers because I had a bit of a surprise when I opened the page in Firefox, though stars look pretty good on Chrome and Safari.  Who knows what it looks like in Internet Explorer.  In fact, I was pretty happy with this approach until I saw in Firefox how different the stars looked (they are smaller) and a slightly different ‘feel’ (e.g. when you click the third star, it is the only one that looks pressed).

To add icing on the cake, in the read-only mode, stars can be displayed using Font Awesome.  Using the logic described in Adding a 5 star ratings feature to a Rails 4 model by Pawel Janiak (which I will also explore), we define a method in the Book model to calculate the average rating, a helper method to determine how many full stars, open stars and half stars to be displayed based on the average rating and call them from View accordingly.

Does this suit my needs?  I’m sure there is a better solution which makes the look of the stars consistent but I will settle with this for the time being.  I will share what I find next or what I settle with.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s