Search code examples
ruby-on-railswindowqueuebackground-processvoting

What is the best approach to build a voting system for an item held in a queue, which will expire? Background task required?


I am building an app in Rails 3 (and Ruby 1.9.2) which takes submissions from multiple users. The app closes the submissions window after X amount of time. Then, everyone is to vote (thumbs up/thumbs down) on the submissions. After Y amount of time, the one with the most votes "wins", and the process repeats.

I'm a noob, but I have the basic models built housing the submissions, RESTful routes, user authentication with Devise, etc. But now my experience has run out and I'm not quite sure what the best approach is to building the "holding" system that will countdown the time until no votes are accepted and a winner is chosen. I think I will create a vote object, so that each submission can have many votes. But it seems like maybe something is needed in the background to wake up after the timer runs down for voting and say, "okay, this voting period has ended, now move the winner into the DB and start taking new submissions before the next voting round". Is this right or can it be simpler than this?

Any suggestions on how I should think about this concept?

Thanks!!


Solution

  • So you've got some kind of Ballot model. Ballot has many Submissions, submission has many Votes. There's a WinningSubmission model as well. Ballot has_one WinningSubmission.

    I think it's probably easiest to have Ballot contain the timestamps for X and Y.

    If you don't want to have some cron job that polls for completed-voted-ballots, you can run a general before_filter in ApplicationController (or, preferably, create a blank controller just for this filter, and have the other controllers that manage the voting inherit from it) that checks on each request to see if any ballots are closed, but also have no winners; then calculate the winners on those, before continuing with the request. It's just like a job worker, except it's within the webserver, so it adds some small amount of response delay.

    As far as the Controller/View architecture of how submissions and votes are created, I'd probably have thrice nested controllers:

    resources :ballots do       # These blocks may need to pass in their object 
                                # depending on your rails version.
    
      resources :submissions do 
        # POST to ballot_submissions_path(@ballot) creates subs's.
    
        resources :votes # POST to ballot_submission_votes_path(@b, @s) creates votes.
      end 
    end 
    

    I'd have to know more about your interaction design to help with any view/ajax level interaction beyond the 'create' actions, but I'm guessing you'd either go with a one-page index style design at the ballot-submission level, or with a series of views, one for each state a ballot can be in. These would probably get sprinkled across the #index actions of the various controllers for simplicity's sake.

    As above, if you didn't want to use a cronjob or job worker, I'd have BallotsController, SubmissionsController, and VotesController all < BallotCompleterController like so:

    class VotesController < BallotCompleterController
    #your vote handling actions would go here.
    end
    
    class BallotCompleterController < ApplicationController
      before_filter :complete_unfinished_ballots
      protected
      def complete_unfinished_ballots
         Ballot.expectant.calculate_all!
      end
      #and that's all that's in here
    end
    
    class Ballot < ActiveRecord::Base
      #...has_many etc's
      named_scope :expectant, lambda{ 
       {:select => "ballots.*",
        :conditions => ['votes_until < ? and winning_submissions.id is null', Time.current],
        :joins => 'left outer join winning_submissions 
                   on winning_submissions.ballot_id = ballots.id', 
        :readonly => false} }
    
     def self.calculate_all!
       self.each(&:'calculate_winning_submission!') 
     end
    
     def calculate_winning_submission!
       #calc and save the winning_submission for this ballot
     end
    
    end