3scale/porta

View on GitHub
app/models/topic.rb

Summary

Maintainability
A
25 mins
Test Coverage
class Topic < ApplicationRecord
  include PermalinkFu

  acts_as_taggable
  include Tagging

  include Indices::TopicIndex

  ORDER_BY = [['Newest post', ''], ["Most views", "hits"], ["Number of posts", "posts_count"]]

  before_validation(:on => :create) { set_default_attributes }

  after_create   :create_initial_post
  before_update  :check_for_moved_forum
  after_update   :set_post_forum_id
  before_destroy :count_user_posts_for_counter_cache
  after_destroy  :update_cached_forum_and_user_counts

  belongs_to :category, :class_name => 'TopicCategory'

  # creator of forum topic
  belongs_to :user

  # creator of recent post
  belongs_to :last_user, :class_name => "User"

  belongs_to :forum, :counter_cache => true
  delegate :account, :account_id, :to => :forum

  has_many :posts, -> { oldest_first }, :dependent => :delete_all
  has_one  :recent_post, -> { latest_first }, :class_name => 'Post'

  has_many :voices, -> { distinct }, :through => :posts, :source => :user

  has_many :user_topics, :dependent => :destroy
  has_many :subscribers, :through => :user_topics, :source => :user

  validates :user_id, presence: { :unless => :anonymous_user }
  validates :forum_id, :title, presence: true
  validates :body, presence: { :on => :create }
  validates :title, :permalink, length: { maximum: 255 }

  scope :order_by, ->(param) {
                     joins('INNER JOIN posts ON topics.last_post_id = posts.id')
                                   .order((param && ['hits', 'posts_count'].include?(param)) ? "#{param} desc" : 'posts.created_at desc')
                   }
  scope :for_category, ->(*args) { where(['topic_category_id = ?', args.first || nil]) }

  scope :with_latest_post, -> { joins('INNER JOIN posts ON topics.last_post_id = posts.id')}


  scope :sticky_first, -> { order(sticky: :desc) }
  scope :last_updated_first, -> { order(last_updated_at: :desc) }
  scope :latest_first, -> { order(created_at: :desc) }
  scope :of_active_user, -> { joins(:user).references(:user).merge(User.active) }

  # Topics that have at least one post by the given user.
  def self.with_post_by(user)
    where({:posts => {:user_id => user.to_param}})
           .joins(:posts).group("topics.id")
  end

  attr_accessor :body, :quiz, :quiz_id, :first_name, :last_name, :email, :human, :markup_type, :anonymous_user
  attr_accessible :markup_type, :title, :body, :quiz, :quiz_id, :first_name, :last_name, :email, :tag_list, :anonymous_user, :tag_list

  attr_readonly :posts_count, :hits

  permalink :title, scope: :forum_id

  module Search
    def smart_search(*args)
      # If no search query given, bypass sphinx and return just normal scope.
      # This is not strictly necessary, beacause sphinx would return the same
      # result, but it simplifies testing. We don't have to care about sphinx
      # in tests that don't have searching as their main focus.

      options = args.extract_options!
      query   = args.first

      options[:page]     ||= 1
      options[:per_page] ||= 20

      if query
        # with sphinx
        options = options.reverse_merge(
          star: true,
          sort_mode: :extended,
          order: 'sticky DESC, weight() DESC, last_updated_at DESC'
        )

        search(ThinkingSphinx::Query.escape(query), options)
      else
        # without sphinx
        results = where({})
        results = results.where(options[:with]) if options[:with]
        results = results.order('sticky DESC, last_updated_at DESC')
        results.paginate(options.slice(:page, :per_page))
      end
    end
  end

  def self.find_for_category(category_id, forum, params = {})
    self.for_forum(forum.id).for_category(category_id).order_by(params[:s])
  end

  def within_range?
  end

  def hit!
    self.class.increment_counter :hits, id
  end

  def paged?
    posts_count > Post.per_page
  end

  def last_page
    [(posts_count.to_f / Post.per_page.to_f).ceil.to_i, 1].max
  end

  def update_cached_post_fields(post)
    # these fields are not accessible to mass assignment
    if remaining_post = post.frozen? ? recent_post : post
          update_columns(last_updated_at: remaining_post.created_at, last_user_id: remaining_post.user_id, last_post_id: remaining_post.id)
    else
      self.destroy
    end
  end

  def to_param
    permalink
  end

  protected

  def create_initial_post
    posts.build.tap do |post|
      post.anonymous_user = anonymous_user
      post.body           = body
      post.user           = user
    end.save!
  end

  def set_default_attributes
    self.sticky          ||= 0
    self.last_updated_at ||= Time.now.utc
  end

  def check_for_moved_forum
    old = Topic.find(id)
    @old_forum_id = old.forum_id if old.forum_id != forum_id
    true
  end

  def set_post_forum_id
    return unless @old_forum_id
    posts.update_all :forum_id => forum_id
    Forum.update_all "posts_count = posts_count - #{posts_count}", ['id = ?', @old_forum_id]
    Forum.update_all "posts_count = posts_count + #{posts_count}", ['id = ?', forum_id]
  end

  def count_user_posts_for_counter_cache
    @user_posts = posts.unscope(:order).where.not(user_id: nil).group(:user_id).count
  end

  def update_cached_forum_and_user_counts
    forum.update(posts_count: "posts_count - #{posts_count}")

    @user_posts.each do |user_id, posts_size|
      User.find(user_id).update(posts_count: "posts_count - #{posts_size}")
    end
  end

end