diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/account.rb | 1 | ||||
-rw-r--r-- | app/models/concerns/account_associations.rb | 1 | ||||
-rw-r--r-- | app/models/export.rb | 2 | ||||
-rw-r--r-- | app/models/featured_tag.rb | 5 | ||||
-rw-r--r-- | app/models/poll.rb | 97 | ||||
-rw-r--r-- | app/models/poll_vote.rb | 36 | ||||
-rw-r--r-- | app/models/status.rb | 12 |
7 files changed, 151 insertions, 3 deletions
diff --git a/app/models/account.rb b/app/models/account.rb index 0c08c0991..bf2a17a7f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -245,6 +245,7 @@ class Account < ApplicationRecord def fields_attributes=(attributes) fields = [] old_fields = self[:fields] || [] + old_fields = [] if old_fields.is_a?(Hash) if attributes.is_a?(Hash) attributes.each_value do |attr| diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 3ab8a0daa..1b22f750c 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -27,6 +27,7 @@ module AccountAssociations # Media has_many :media_attachments, dependent: :destroy + has_many :polls, dependent: :destroy # PuSH subscriptions has_many :subscriptions, dependent: :destroy diff --git a/app/models/export.rb b/app/models/export.rb index fc4bb6964..9bf866d35 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -23,7 +23,7 @@ class Export def to_lists_csv CSV.generate do |csv| - account.owned_lists.select(:title).each do |list| + account.owned_lists.select(:title, :id).each do |list| list.accounts.select(:username, :domain).each do |account| csv << [list.title, acct(account)] end diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index b5a10ad2d..d06ae26a8 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -18,11 +18,12 @@ class FeaturedTag < ApplicationRecord delegate :name, to: :tag, allow_nil: true - validates :name, presence: true + validates_associated :tag, on: :create + validates :name, presence: true, on: :create validate :validate_featured_tags_limit, on: :create def name=(str) - self.tag = Tag.find_or_initialize_by(name: str.delete('#').mb_chars.downcase.to_s) + self.tag = Tag.find_or_initialize_by(name: str.strip.delete('#').mb_chars.downcase.to_s) end def increment(timestamp) diff --git a/app/models/poll.rb b/app/models/poll.rb new file mode 100644 index 000000000..ab7236d45 --- /dev/null +++ b/app/models/poll.rb @@ -0,0 +1,97 @@ +# 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_options + 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 prepare_options + self.options = options.map(&:strip).reject(&:blank?) + 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 diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb new file mode 100644 index 000000000..9ad66bbf8 --- /dev/null +++ b/app/models/poll_vote.rb @@ -0,0 +1,36 @@ +# 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 +# uri :string +# + +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 + + delegate :local?, to: :account + + def object_type + :vote + end + + private + + def increment_counter_cache + poll.cached_tallies[choice] = (poll.cached_tallies[choice] || 0) + 1 + poll.save + end +end diff --git a/app/models/status.rb b/app/models/status.rb index 4566c0d20..f576489b4 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -23,6 +23,7 @@ # in_reply_to_account_id :bigint(8) # local_only :boolean # full_status_text :text default(""), not null +# poll_id :bigint(8) # class Status < ApplicationRecord @@ -46,6 +47,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 @@ -64,12 +66,14 @@ 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? } validates_with StatusLengthValidator validates_with DisallowedHashtagsValidator validates :reblog, uniqueness: { scope: :account }, if: :reblog? + validates_associated :owned_poll default_scope { recent } @@ -106,6 +110,7 @@ class Status < ApplicationRecord :tags, :preview_cards, :stream_entry, + :poll, account: :account_stat, active_mentions: { account: :account_stat }, reblog: [ @@ -116,6 +121,7 @@ class Status < ApplicationRecord :media_attachments, :conversation, :status_stat, + :poll, account: :account_stat, active_mentions: { account: :account_stat }, ], @@ -257,6 +263,8 @@ class Status < ApplicationRecord before_validation :set_conversation before_validation :set_local + after_create :set_poll_id + class << self def selectable_visibilities visibilities.keys - %w(direct limited) @@ -458,6 +466,10 @@ class Status < ApplicationRecord self.reblog = reblog.reblog if reblog? && reblog.reblog? end + def set_poll_id + update_column(: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? |