app/models/commontator/comment.rb in commontator-5.1.0 vs app/models/commontator/comment.rb in commontator-6.0.0.pre.1

- old
+ new

@@ -1,116 +1,233 @@ -module Commontator - class Comment < ActiveRecord::Base - belongs_to :creator, polymorphic: true - belongs_to :editor, polymorphic: true, optional: true - belongs_to :thread +class Commontator::Comment < ActiveRecord::Base + belongs_to :creator, polymorphic: true + belongs_to :editor, polymorphic: true, optional: true + belongs_to :thread, inverse_of: :comments + belongs_to :parent, optional: true, class_name: name, inverse_of: :children - validates_presence_of :creator, on: :create - validates_presence_of :editor, on: :update - validates_presence_of :thread - validates_presence_of :body + has_many :children, class_name: name, inverse_of: :parent - validates_uniqueness_of :body, - scope: [:creator_type, :creator_id, :thread_id, :deleted_at], - message: I18n.t('commontator.comment.errors.double_posted') + serialize :ancestor_ids, Commontator::JsonArrayCoder + serialize :descendant_ids, Commontator::JsonArrayCoder - protected + validates :editor, presence: true, on: :update + validates :body, presence: true, uniqueness: { + scope: [ :creator_type, :creator_id, :thread_id, :deleted_at ], + message: I18n.t('commontator.comment.errors.double_posted') + } + validate :parent_is_not_self, :parent_belongs_to_the_same_thread, if: :parent - cattr_accessor :acts_as_votable_initialized + before_save :set_ancestor_ids_and_ancestor_descendant_ids + before_destroy :remove_ancestor_descendant_ids - public + cattr_accessor :will_paginate + self.will_paginate = begin + require 'will_paginate' - def is_modified? - !editor.nil? - end + true + rescue LoadError + false + end - def is_latest? - thread.comments.last == self - end + cattr_accessor :is_votable + self.is_votable = begin + require 'acts_as_votable' + acts_as_votable - def is_votable? - return true if acts_as_votable_initialized - return false unless self.class.respond_to?(:acts_as_votable) - self.class.acts_as_votable - self.class.acts_as_votable_initialized = true - end + true + rescue LoadError + false + end - def get_vote_by(user) - return nil unless is_votable? && !user.nil? && user.is_commontator - votes_for.where(voter_type: user.class.name, voter_id: user.id).first - end + def self.will_paginate? + will_paginate + end - def update_cached_votes(vote_scope = nil) - self.update_column(:cached_votes_up, count_votes_up(true)) - self.update_column(:cached_votes_down, count_votes_down(true)) - end + def self.is_votable? + is_votable + end - def is_deleted? - !deleted_at.blank? - end + def is_modified? + !editor.nil? + end - def delete_by(user) - return false if is_deleted? - self.deleted_at = Time.now - self.editor = user - self.save - end + def is_latest? + thread.comments.last == self + end - def undelete_by(user) - return false unless is_deleted? - self.deleted_at = nil - self.editor = user - self.save - end + def get_vote_by(user) + return nil unless self.class.is_votable? && !user.nil? && user.is_commontator - def created_timestamp - I18n.t 'commontator.comment.status.created_at', - created_at: I18n.l(created_at, format: :commontator) - end + votes_for.find_by(voter_type: user.class.name, voter_id: user.id) + end - def updated_timestamp - I18n.t 'commontator.comment.status.updated_at', - editor_name: Commontator.commontator_name(editor || creator), - updated_at: I18n.l(updated_at, format: :commontator) - end + def update_cached_votes(vote_scope = nil) + self.update_column(:cached_votes_up, count_votes_up(true)) + self.update_column(:cached_votes_down, count_votes_down(true)) + end - ################## - # Access Control # - ################## + def is_deleted? + !deleted_at.nil? + end - def can_be_created_by?(user) - user == creator && !user.nil? && user.is_commontator &&\ - !thread.is_closed? && thread.can_be_read_by?(user) - end + def delete_by(user) + return false if is_deleted? - def can_be_edited_by?(user) - return true if thread.can_be_edited_by?(user) &&\ - thread.config.moderator_permissions.to_sym == :e - comment_edit = thread.config.comment_editing.to_sym - !thread.is_closed? && !is_deleted? && user == creator &&\ - comment_edit != :n && (is_latest? || comment_edit == :a) &&\ - thread.can_be_read_by?(user) - end + self.deleted_at = Time.now + self.editor = user + self.save + end - def can_be_deleted_by?(user) - mod_perm = thread.config.moderator_permissions.to_sym - return true if thread.can_be_edited_by?(user) &&\ - (mod_perm == :e ||\ - mod_perm == :d) - comment_del = thread.config.comment_deletion.to_sym - !thread.is_closed? && (!is_deleted? || editor == user) &&\ - user == creator && comment_del != :n &&\ - (is_latest? || comment_del == :a) &&\ - thread.can_be_read_by?(user) - end + def undelete_by(user) + return false unless is_deleted? - def can_be_voted_on? - !thread.is_closed? && !is_deleted? &&\ - thread.config.comment_voting.to_sym != :n && is_votable? + self.deleted_at = nil + self.editor = user + self.save + end + + def created_timestamp + I18n.t 'commontator.comment.status.created_at', + created_at: I18n.l(created_at, format: :commontator) + end + + def updated_timestamp + I18n.t 'commontator.comment.status.updated_at', + editor_name: Commontator.commontator_name(editor || creator), + updated_at: I18n.l(updated_at, format: :commontator) + end + + ################## + # Access Control # + ################## + + def can_be_created_by?(user) + user == creator && !user.nil? && user.is_commontator && + !thread.is_closed? && thread.can_be_read_by?(user) + end + + def can_be_edited_by?(user) + return true if thread.can_be_edited_by?(user) && + thread.config.moderator_permissions.to_sym == :e + + comment_edit = thread.config.comment_editing.to_sym + !thread.is_closed? && !is_deleted? && user == creator && (editor.nil? || user == editor) && + comment_edit != :n && (is_latest? || comment_edit == :a) && thread.can_be_read_by?(user) + end + + def can_be_deleted_by?(user) + mod_perm = thread.config.moderator_permissions.to_sym + return true if thread.can_be_edited_by?(user) && (mod_perm == :e || mod_perm == :d) + + comment_del = thread.config.comment_deletion.to_sym + !thread.is_closed? && user == creator && (!is_deleted? || editor == user) && + comment_del != :n && (is_latest? || comment_del == :a) && thread.can_be_read_by?(user) + end + + def can_be_voted_on? + !thread.is_closed? && !is_deleted? && + thread.config.comment_voting.to_sym != :n && self.class.is_votable? + end + + def can_be_voted_on_by?(user) + !user.nil? && user.is_commontator && user != creator && + thread.can_be_read_by?(user) && can_be_voted_on? + end + + protected + + def parent_is_not_self + return if parent != self + errors.add :parent, 'must be a different comment' + throw :abort + end + + def parent_belongs_to_the_same_thread + return if parent.thread_id == thread_id + errors.add :parent, 'must belong to the same thread' + throw :abort + end + + def remove_ancestor_descendant_ids + return if ancestor_ids.empty? + + # Remove id and descendant_ids from ancestors + self.class.where(id: ancestor_ids).order(:id).update_all("descendant_ids = #{ + ([ id ] + descendant_ids).reduce(self.class.arel_table[:descendant_ids]) do |memo, descendant_id| + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + memo, Arel::Nodes.build_quoted("[#{descendant_id}]"), Arel::Nodes.build_quoted('[]') + ]), Arel::Nodes.build_quoted("[#{descendant_id},"), Arel::Nodes.build_quoted('[') + ]), Arel::Nodes.build_quoted(",#{descendant_id},"), Arel::Nodes.build_quoted(',') + ]), Arel::Nodes.build_quoted(",#{descendant_id}]"), Arel::Nodes.build_quoted(']') + ]) + end.to_sql + }") + + association(:parent).reset + end + + def set_ancestor_ids_and_ancestor_descendant_ids + return if ancestor_ids.first == parent_id + + pa = parent + + remove_ancestor_descendant_ids + + # Remove ancestor_ids from descendants + unless ancestor_ids.empty? || descendant_ids.empty? + self.class.where(id: descendant_ids).order(:id).update_all("ancestor_ids = #{ + ancestor_ids.reduce(self.class.arel_table[:ancestor_ids]) do |memo, ancestor_id| + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + memo, Arel::Nodes.build_quoted("[#{ancestor_id}]"), Arel::Nodes.build_quoted('[]') + ]), Arel::Nodes.build_quoted("[#{ancestor_id},"), Arel::Nodes.build_quoted('[') + ]), Arel::Nodes.build_quoted(",#{ancestor_id},"), Arel::Nodes.build_quoted(',') + ]), Arel::Nodes.build_quoted(",#{ancestor_id}]"), Arel::Nodes.build_quoted(']') + ]) + end.to_sql + }") + + children.reset end - def can_be_voted_on_by?(user) - !user.nil? && user.is_commontator && user != creator &&\ - thread.can_be_read_by?(user) && can_be_voted_on? + if pa.nil? + self.ancestor_ids = [] + else + self.ancestor_ids = [ pa.id ] + pa.ancestor_ids + + # Add id and descendant_ids to ancestors + descendant_ids_str = ([ id ] + descendant_ids).to_json[1..-2] + self.class.where(id: ancestor_ids).order(:id).update_all("descendant_ids = #{ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('COALESCE', [ + self.class.arel_table[:descendant_ids], Arel::Nodes.build_quoted('[]') + ]), Arel::Nodes.build_quoted(']'), Arel::Nodes.build_quoted(",#{descendant_ids_str}]") + ]), Arel::Nodes.build_quoted('[,'), Arel::Nodes.build_quoted('[') + ]).to_sql + }") + + association(:parent).reset + + # Add ancestor_ids to descendants + unless descendant_ids.empty? + ancestor_ids_str = ancestor_ids.to_json[1..-2] + self.class.where(id: descendant_ids).order(:id).update_all("ancestor_ids = #{ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('REPLACE', [ + Arel::Nodes::NamedFunction.new('COALESCE', [ + self.class.arel_table[:ancestor_ids], Arel::Nodes.build_quoted('[]') + ]), Arel::Nodes.build_quoted(']'), Arel::Nodes.build_quoted(",#{ancestor_ids_str}]") + ]), Arel::Nodes.build_quoted('[,'), Arel::Nodes.build_quoted('[') + ]).to_sql + }") + + children.reset + end end end end