REFACTOR: use tables instead of custom fields for polls (#6359)

Co-authored-by: Guo Xiang Tan <tgx_world@hotmail.com>
This commit is contained in:
Régis Hanol
2018-11-19 14:50:00 +01:00
committed by GitHub
parent 86dafc1f25
commit 4459665dee
37 changed files with 1912 additions and 1573 deletions

View File

@@ -0,0 +1,76 @@
class Poll < ActiveRecord::Base
# because we want to use the 'type' column and don't want to use STI
self.inheritance_column = nil
belongs_to :post
has_many :poll_options, dependent: :destroy
has_many :poll_votes
enum type: {
regular: 0,
multiple: 1,
number: 2,
}
enum status: {
open: 0,
closed: 1,
}
enum results: {
always: 0,
on_vote: 1,
on_close: 2,
}
enum visibility: {
secret: 0,
everyone: 1,
}
validates :min, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
validates :max, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
validates :step, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
def is_closed?
closed? || (close_at && close_at <= Time.zone.now)
end
def can_see_results?(user)
always? || is_closed? || (on_vote? && has_voted?(user))
end
def has_voted?(user)
user&.id && poll_votes.any? { |v| v.user_id == user.id }
end
def can_see_voters?(user)
everyone? && can_see_results?(user)
end
end
# == Schema Information
#
# Table name: polls
#
# id :bigint(8) not null, primary key
# post_id :bigint(8)
# name :string default("poll"), not null
# close_at :datetime
# type :integer default("regular"), not null
# status :integer default("open"), not null
# results :integer default("always"), not null
# visibility :integer default("secret"), not null
# min :integer
# max :integer
# step :integer
# anonymous_voters :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_polls_on_post_id (post_id)
# index_polls_on_post_id_and_name (post_id,name) UNIQUE
#

View File

@@ -0,0 +1,24 @@
class PollOption < ActiveRecord::Base
belongs_to :poll
has_many :poll_votes, dependent: :delete_all
default_scope { order(created_at: :asc) }
end
# == Schema Information
#
# Table name: poll_options
#
# id :bigint(8) not null, primary key
# poll_id :bigint(8)
# digest :string not null
# html :text not null
# anonymous_votes :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_poll_options_on_poll_id (poll_id)
# index_poll_options_on_poll_id_and_digest (poll_id,digest) UNIQUE
#

View File

@@ -0,0 +1,23 @@
class PollVote < ActiveRecord::Base
belongs_to :poll
belongs_to :poll_option
belongs_to :user
end
# == Schema Information
#
# Table name: poll_votes
#
# poll_id :bigint(8)
# poll_option_id :bigint(8)
# user_id :bigint(8)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_poll_votes_on_poll_id (poll_id)
# index_poll_votes_on_poll_id_and_poll_option_id_and_user_id (poll_id,poll_option_id,user_id) UNIQUE
# index_poll_votes_on_poll_option_id (poll_option_id)
# index_poll_votes_on_user_id (user_id)
#

View File

@@ -0,0 +1,14 @@
class PollOptionSerializer < ApplicationSerializer
attributes :id, :html, :votes
def id
object.digest
end
def votes
# `size` instead of `count` to prevent N+1
object.poll_votes.size + object.anonymous_votes.to_i
end
end

View File

@@ -0,0 +1,50 @@
class PollSerializer < ApplicationSerializer
attributes :name,
:type,
:status,
:public,
:results,
:min,
:max,
:step,
:options,
:voters,
:close
def public
true
end
def include_public?
object.everyone?
end
def include_min?
object.min.present? && (object.number? || object.multiple?)
end
def include_max?
object.max.present? && (object.number? || object.multiple?)
end
def include_step?
object.step.present? && object.number?
end
def options
object.poll_options.map { |o| PollOptionSerializer.new(o, root: false).as_json }
end
def voters
object.poll_votes.map { |v| v.user_id }.uniq.count + object.anonymous_voters.to_i
end
def close
object.close_at
end
def include_close?
object.close_at.present?
end
end