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