about summary refs log tree commit diff
path: root/app/models/poll.rb
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-03 22:18:23 +0100
committerGitHub <noreply@github.com>2019-03-03 22:18:23 +0100
commit230a012f0090c496fc5cdb011bcc8ed732fd0f5c (patch)
treeca589b040b2d5c440e75e53d528f908cafe65d3a /app/models/poll.rb
parent99dc212ae5d7b2527d835744bf903293398ce946 (diff)
Add polls (#10111)
* 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
Diffstat (limited to 'app/models/poll.rb')
-rw-r--r--app/models/poll.rb90
1 files changed, 90 insertions, 0 deletions
diff --git a/app/models/poll.rb b/app/models/poll.rb
new file mode 100644
index 000000000..ba0b17f91
--- /dev/null
+++ b/app/models/poll.rb
@@ -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