Note we're excluding a few files: * Configuration files that weren't generated by us * Migration files that weren't generated by us * The Gemfile, since it includes an important comment that must be on the same line as the gem declaration * The Budget::Stats class, since the heading statistics are a mess and having shorter lines would require a lot of refactoring
92 lines
3.0 KiB
Ruby
92 lines
3.0 KiB
Ruby
class RelatedContent < ApplicationRecord
|
|
RELATED_CONTENT_SCORE_THRESHOLD = Setting["related_content_score_threshold"].to_f
|
|
RELATIONABLE_MODELS = %w[proposals debates budgets investments].freeze
|
|
|
|
acts_as_paranoid column: :hidden_at
|
|
include ActsAsParanoidAliases
|
|
|
|
belongs_to :author, class_name: "User"
|
|
belongs_to :parent_relationable, polymorphic: true, optional: false, touch: true
|
|
belongs_to :child_relationable, polymorphic: true, optional: false, touch: true
|
|
has_one :opposite_related_content, class_name: name, foreign_key: :related_content_id
|
|
has_many :related_content_scores, dependent: :destroy
|
|
|
|
validates :parent_relationable_id,
|
|
uniqueness: {
|
|
scope: [:parent_relationable_type, :child_relationable_id, :child_relationable_type]
|
|
}
|
|
validate :different_parent_and_child
|
|
|
|
after_create :create_opposite_related_content, unless: proc { opposite_related_content.present? }
|
|
after_create :create_author_score
|
|
|
|
scope :not_hidden, -> { where(hidden_at: nil) }
|
|
scope :from_users, -> { where(machine_learning: false) }
|
|
scope :from_machine_learning, -> { where(machine_learning: true) }
|
|
scope :for_proposals, -> do
|
|
where(parent_relationable_type: "Proposal", child_relationable_type: "Proposal")
|
|
end
|
|
scope :for_investments, -> do
|
|
where(parent_relationable_type: "Budget::Investment", child_relationable_type: "Budget::Investment")
|
|
end
|
|
|
|
def score_positive(user)
|
|
score(RelatedContentScore::SCORES[:POSITIVE], user)
|
|
end
|
|
|
|
def score_negative(user)
|
|
score(RelatedContentScore::SCORES[:NEGATIVE], user)
|
|
end
|
|
|
|
def scored_by_user?(user)
|
|
related_content_scores.exists?(user: user)
|
|
end
|
|
|
|
def same_parent_and_child?
|
|
parent_relationable == child_relationable
|
|
end
|
|
|
|
def duplicate?
|
|
parent_relationable.relationed_contents.include?(child_relationable)
|
|
end
|
|
|
|
private
|
|
|
|
def different_parent_and_child
|
|
if same_parent_and_child?
|
|
errors.add(:parent_relationable, :itself)
|
|
end
|
|
end
|
|
|
|
def create_opposite_related_content
|
|
related_content = RelatedContent.create!(opposite_related_content: self,
|
|
parent_relationable: child_relationable,
|
|
child_relationable: parent_relationable,
|
|
machine_learning: machine_learning,
|
|
author: author)
|
|
self.opposite_related_content = related_content
|
|
end
|
|
|
|
def score(value, user)
|
|
score_with_opposite(value, user)
|
|
|
|
if (related_content_scores.sum(:value) / related_content_scores_count) < RELATED_CONTENT_SCORE_THRESHOLD
|
|
hide_with_opposite
|
|
end
|
|
end
|
|
|
|
def hide_with_opposite
|
|
hide
|
|
opposite_related_content.hide
|
|
end
|
|
|
|
def create_author_score
|
|
score_positive(author)
|
|
end
|
|
|
|
def score_with_opposite(value, user)
|
|
RelatedContentScore.create(user: user, related_content: self, value: value)
|
|
RelatedContentScore.create(user: user, related_content: opposite_related_content, value: value)
|
|
end
|
|
end
|