module Formol class Poll < ActiveRecord::Base #Exceptions class PollException < Exception; end class TooManyVotesError < PollException; end class UserAlreadyVoter < PollException; end class NoOptionProvided < PollException; end class TopicLockedError < PollException; end class PollExpiredError < PollException; end #Security attr_protected :topic_id attr_readonly :topic_id #Virtual attributes attr_accessor :poll_vote_ids #to not conflict with has_many :votes #Default values default_value_for :duration, 1.week.to_i default_value_for :max_options, 1 #default to single mode #Associations belongs_to :topic has_many :options has_many :votes, :through => :options has_many :voters, :through => :votes, :class_name => Formol.config.user_class, :uniq => true #Nested attributes accepts_nested_attributes_for :options, :limit => 20 #Validations validates :topic, :presence => true validates :question, :presence => true, :length => { :in => 5..64, :allow_blank => true } validates :duration, :presence => true, :numericality => { :only_integer => true, :greater_than => 0, :less_than => 3.months.to_i, :allow_blank => true } validates :max_options, :presence => true, :numericality => { :only_integer => true, :greater_than => 0, :less_than_or_equal_to => Proc.new{ |p| p.options.length }, :allow_blank => true } validates :options, :length => { :in => 1..20 } #Normalization normalize_attribute :question, :with => [:squish, :blank] #Callbacks before_validation :setup_options_assoc, :on => :create #Business methods def multiple? max_options > 1 end def single? !multiple? end def expired? expires_at < Time.now end def expires_at created_at + duration.to_i end def voted?(user) voters.include?(user) end def vote(user, option_ids) option_ids = [option_ids] unless option_ids.respond_to?(:each) option_ids.reject!(&:blank?) option_ids.compact! raise NoOptionProvided.new if option_ids.empty? raise TooManyVotesError.new if option_ids.length > max_options raise UserAlreadyVoter.new if voted?(user) raise TopicLockedError.new if topic.locked? raise PollExpiredError.new if expired? options.find(option_ids).to_a.each do |option| #? option.votes.create!(:voter => user) end end private #Callback methods # set poll for each options # AR doesn't do it by itself and it ruins validations def setup_options_assoc options.each do |o| o.poll = self end end end end