0
0
Fork 0
* Add polls

Fix #1629

* Add tests

* Fixes

* Change API for creating polls

* Use name instead of content for votes

* Remove poll validation for remote polls

* Add polls to public pages

* When updating the poll, update options just in case they were changed

* Fix public pages showing both poll and other media
This commit is contained in:
Eugen Rochko 2019-03-03 22:18:23 +01:00 committed by GitHub
parent 99dc212ae5
commit 230a012f00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1038 additions and 19 deletions

View file

@ -26,6 +26,7 @@ module AccountAssociations
# Media
has_many :media_attachments, dependent: :destroy
has_many :polls, dependent: :destroy
# PuSH subscriptions
has_many :subscriptions, dependent: :destroy

90
app/models/poll.rb Normal file
View file

@ -0,0 +1,90 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: polls
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# status_id :bigint(8)
# expires_at :datetime
# options :string default([]), not null, is an Array
# cached_tallies :bigint(8) default([]), not null, is an Array
# multiple :boolean default(FALSE), not null
# hide_totals :boolean default(FALSE), not null
# votes_count :bigint(8) default(0), not null
# last_fetched_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
#
class Poll < ApplicationRecord
include Expireable
belongs_to :account
belongs_to :status
has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :destroy
validates :options, presence: true
validates :expires_at, presence: true, if: :local?
validates_with PollValidator, if: :local?
scope :attached, -> { where.not(status_id: nil) }
scope :unattached, -> { where(status_id: nil) }
before_validation :prepare_votes_count
after_initialize :prepare_cached_tallies
after_commit :reset_parent_cache, on: :update
def loaded_options
options.map.with_index { |title, key| Option.new(self, key.to_s, title, cached_tallies[key]) }
end
def unloaded_options
options.map.with_index { |title, key| Option.new(self, key.to_s, title, nil) }
end
def possibly_stale?
remote? && last_fetched_before_expiration? && time_passed_since_last_fetch?
end
delegate :local?, to: :account
def remote?
!local?
end
class Option < ActiveModelSerializers::Model
attributes :id, :title, :votes_count, :poll
def initialize(poll, id, title, votes_count)
@poll = poll
@id = id
@title = title
@votes_count = votes_count
end
end
private
def prepare_cached_tallies
self.cached_tallies = options.map { 0 } if cached_tallies.empty?
end
def prepare_votes_count
self.votes_count = cached_tallies.sum unless cached_tallies.empty?
end
def reset_parent_cache
return if status_id.nil?
Rails.cache.delete("statuses/#{status_id}")
end
def last_fetched_before_expiration?
last_fetched_at.nil? || expires_at.nil? || last_fetched_at < expires_at
end
def time_passed_since_last_fetch?
last_fetched_at.nil? || last_fetched_at < 1.minute.ago
end
end

29
app/models/poll_vote.rb Normal file
View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: poll_votes
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# poll_id :bigint(8)
# choice :integer default(0), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class PollVote < ApplicationRecord
belongs_to :account
belongs_to :poll, inverse_of: :votes
validates :choice, presence: true
validates_with VoteValidator
after_create_commit :increment_counter_cache
private
def increment_counter_cache
poll.cached_tallies[choice] = (poll.cached_tallies[choice] || 0) + 1
poll.save
end
end

View file

@ -21,6 +21,7 @@
# account_id :bigint(8) not null
# application_id :bigint(8)
# in_reply_to_account_id :bigint(8)
# poll_id :bigint(8)
#
class Status < ApplicationRecord
@ -44,6 +45,7 @@ class Status < ApplicationRecord
belongs_to :account, inverse_of: :statuses
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account', optional: true
belongs_to :conversation, optional: true
belongs_to :poll, optional: true
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true
@ -61,6 +63,7 @@ class Status < ApplicationRecord
has_one :notification, as: :activity, dependent: :destroy
has_one :stream_entry, as: :activity, inverse_of: :status
has_one :status_stat, inverse_of: :status
has_one :owned_poll, class_name: 'Poll', inverse_of: :status, dependent: :destroy
validates :uri, uniqueness: true, presence: true, unless: :local?
validates :text, presence: true, unless: -> { with_media? || reblog? }
@ -101,6 +104,7 @@ class Status < ApplicationRecord
:tags,
:preview_cards,
:stream_entry,
:poll,
account: :account_stat,
active_mentions: { account: :account_stat },
reblog: [
@ -111,6 +115,7 @@ class Status < ApplicationRecord
:media_attachments,
:conversation,
:status_stat,
:poll,
account: :account_stat,
active_mentions: { account: :account_stat },
],
@ -250,6 +255,8 @@ class Status < ApplicationRecord
before_validation :set_conversation
before_validation :set_local
before_save :set_poll_id
class << self
def selectable_visibilities
visibilities.keys - %w(direct limited)
@ -438,6 +445,10 @@ class Status < ApplicationRecord
self.reblog = reblog.reblog if reblog? && reblog.reblog?
end
def set_poll_id
self.poll_id = owned_poll.id unless owned_poll.nil?
end
def set_visibility
self.visibility = (account.locked? ? :private : :public) if visibility.nil?
self.visibility = reblog.visibility if reblog?