Module Tutorial: User Polling / Rating: Remembering Users Choices

We’ve pretty much come to the end of the tutorial, but there’s one more thing that’s worth doing, to prevent ballot stuffing by remembering users’ choices. This is also a good example of iterating on a module, as we’ll need to do some fairly wide ranging changes across the breadth of the PollDemo module. Let’s break down the necessary steps before we get started:

  1. We need to add and run a new migration on the module to add a user_id field to the database (and probably an index on the user as well).
  2. We need to modify our PollDemoPoll class to pass the user_id through.
  3. We need to modify our renderer to store submitted polls in the session and check the database by id.

Let’s get started.

Step 1: Adding the new migration

Let’s add the new migration using the Webiva migration generator

  $ ./script/generate webiva_module_migration PollDemo user_responses

Open up the migration file it created and add the following:

 class UserResponses < ActiveRecord::Migration
   def self.up
     add_column :poll_demo_responses, :end_user_id, :integer
     add_index :poll_demo_responses, :end_user_id
   end
 
   def self.down
     remove_column :poll_demo_responses, :end_user_id
     remove_index :poll_demo_responses, :end_user_id
   end
 end

Now let’s run the migration:

 $ rake cms:migrate_domain_components COMPONENT=poll_demo

Step 2: Modifying the model

Let’s open up poll_demo_poll.rb and modify the add_response method to handle the user:

vendor/modules/poll_demo/app/models/poll_demo_poll.rb

    def add_response(response,user=nil,ip=nil)
      response = response.to_i
      if response > 0 && response <= self.answers.length
        if self.poll_demo_responses.create(:response => response, 
                                           :ip_address => ip, 
                                           :end_user => user)
          recalculate_results!
        end
      else
        false
      end
    end

We’ve added another argument for the user. We’ve also added a check on the return value of the create, in case there was a validation error.

Now we need to add a relation and validation to the EndUser model inside of poll_demo_response.rb:

vendor/modules/poll_demo/app/models/poll_demo_response.rb

   belongs_to :end_user
   validates_uniqueness_of :poll_demo_poll_id, :scope => :end_user_id

Step 3: Modifying the renderer

We need to do a couple of things:

Keep track of what polls a user has responded to in their session.

Check if a user is logged in and check the database if they have already responded.

We’ll accomplish these by adding a couple of protected methods into the renderer called already_responded? and add_saved_response, and modify the view method to use them. We need to add these to the renderer because only the renderer has access to the session and the myself object which represents the current user. Here’s some code that does the trick; you’ll want to replace the entire view method:

vendor/modules/poll_demo/app/controllers/poll_demo/page_renderer.rb

       def view
         
         @options = paragraph_options(:view)

         if !params[:poll]
           @poll = PollDemoPoll.random_poll
           @response = @poll.poll_demo_responses.build if @poll

           @state = already_responded?(@poll) ? 'responded' : 'question'
         else
           @poll = PollDemoPoll.find_by_id(params[:poll][:poll_demo_poll_id])
           if already_responded?(@poll)
             @state = 'responded'
           elsif @poll && @poll.add_response(params[:poll][:response],myself,request.remote_ip)
             @state = 'responded'
             add_saved_response(@poll)
           end
         end
       
         require_js('prototype')

         render_paragraph :feature => :poll_demo_page_view
       end
     
       protected

       def add_saved_response(poll)
         session[:polls] ||= []
         session[:polls] << poll.id unless session[:polls].include?(poll.id)
       end

       def already_responded?(poll)
         session[:polls] ||= []
         included = session[:polls].include?(poll.id)
         if !included && myself.id
           response = 
              PollDemoResponse.find_by_end_user_id_and_poll_demo_poll_id(myself.id,poll.id)
           if response
             included = true
             session[:polls] << poll.id
           end
         end

         included
       end

Now give this a shot and see how it works. When you load the page, you shouldn’t see the question, only the response graph.