rokumatsumoto/boyutluseyler

View on GitHub
app/models/design.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

# == Schema Information
#
# Table name: designs
#
#  id                        :bigint(8)        not null, primary key
#  name                      :string(120)      not null
#  description               :text             not null
#  printing_settings         :text
#  model_file_format         :string
#  license_type              :string           not null
#  allow_comments            :boolean          default(TRUE), not null
#  created_at                :datetime         not null
#  updated_at                :datetime         not null
#  user_id                   :bigint(8)        not null
#  category_id               :bigint(8)        not null
#  slug                      :string
#  downloads_count           :integer          default(0), not null
#  hourly_downloads_count    :float            default(0.0), not null
#  popularity_score          :float            default(0.0), not null
#  home_popular_at           :datetime
#  likes_count               :integer          default(0), not null
#  hourly_downloads_count_at :datetime
#  cached_tag_names          :text
#

class Design < ApplicationRecord
  extend FriendlyId

  include Taggable
  include Sortable

  friendly_id :name, use: %i[slugged history]
  resourcify

  HOURLY_DOWNLOAD_INTERVAL = 1.hour
  POPULAR_INTERVAL = 1.hour
  MOST_DOWNLOADED_LIMIT = 8
  POPULAR_LIMIT = 12
  POPULARITY_EFFECT = 0.02

  has_many :design_illustrations, -> { order(position: :asc) }, inverse_of: :design
  has_many :illustrations, through: :design_illustrations
  has_many :design_blueprints, -> { order(position: :asc) }, inverse_of: :design
  has_many :blueprints, through: :design_blueprints
  has_one :design_download, dependent: :destroy

  belongs_to :user
  belongs_to :category

  validates :name, presence: true, length: { maximum: 120 }
  validates :description, presence: true
  validates :license_type, presence: true
  validates :design_illustrations, presence: true
  validates :design_blueprints, presence: true

  scope :downloaded, -> { where('downloads_count > 0') }
  scope :eligible_for_hourly_download_calculation, (lambda do
    where('designs.created_at < ?', Time.current - HOURLY_DOWNLOAD_INTERVAL)
  end)

  enum license_type: {
    license_none: 'license_none',
    cc_by: 'cc_by',
    cc_by_sa: 'cc_by_sa',
    cc_by_nd: 'cc_by_nd',
    cc_by_nc: 'cc_by_nc',
    cc_by_nc_sa: 'cc_by_nc_sa',
    cc_by_nc_nd: 'cc_by_nc_nd'
  }

  def preview_blueprints
    blueprints.where(preview: true).select(:url, :thumb_url)
  end

  def preview_illustrations
    illustrations.select(:id, :thumb_url, 'large_url as url')
  end

  def cached_category_name
    Rails.cache.fetch(['Design', id, 'Category', category_id], expires_in: 1.month) do
      category.name
    end
  end

  def cached_user
    Rails.cache.fetch(['Design', id, 'User', user_id], expires_in: 1.month) do
      OpenStruct.new(username: user.username, avatar_url: user.avatar_url)
    end
  end

  class << self
    delegate :with_illustration, to: :most_downloaded, prefix: true
    delegate :with_illustration, to: :most_popular, prefix: true

    def sort_by_attribute(method)
      case method.to_s
      when 'downloads_count_desc' then reorder(downloads_count: :desc)
      when 'downloads_count_asc'  then reorder(downloads_count: :asc)
      when 'likes_count_desc'     then reorder(likes_count: :desc)
      when 'likes_count_asc'      then reorder(likes_count: :asc)
      when 'home_popular_at_desc' then reorder(home_popular_at: :desc)
      when 'home_popular_at_asc'  then reorder(home_popular_at: :asc)
      else order_by(method)
      end
    end

    def most_downloaded
      downloaded.eligible_for_hourly_download_calculation
                .order(hourly_downloads_count: :desc, created_at: :desc)
                .limit(MOST_DOWNLOADED_LIMIT)
    end

    def most_popular
      order(popularity_score: :desc).limit(POPULAR_LIMIT)
    end

    def popular
      where.not(home_popular_at: nil)
    end

    def with_illustration
      first_illustration.joins(:category).select('designs.id, designs.name, designs.slug,
        illustrations.medium_url, categories.slug as category_slug')
    end

    def cached_most_downloaded
      design_list = Rails.cache.fetch('most_downloaded',
                                      expires_in: HOURLY_DOWNLOAD_INTERVAL) do
        hourly_downloads_count_service.execute

        # TODO: use fast_jsonapi serializer
        most_downloaded_with_illustration.to_json(except: :tag_names)
      end

      JSON.parse(design_list)
    end

    def cached_popular_designs
      design_list = Rails.cache.fetch('popular_designs', expires_in: POPULAR_INTERVAL) do
        become_popular_service.execute

        # TODO: use fast_jsonapi serializer
        most_popular_with_illustration.to_json(except: :tag_names)
      end

      JSON.parse(design_list)
    end

    def invalidate_most_downloaded_cache
      Rails.cache.delete('most_downloaded')
    end

    def invalidate_popular_designs_cache
      Rails.cache.delete('popular_designs')
    end

    private

    def first_illustration
      joins(:illustrations).where('design_illustrations.position = ?', 1)
    end

    def hourly_downloads_count_service
      Designs::Downloads::HourlyDownloadsCountService.new
    end

    def become_popular_service
      Designs::BecomePopularService.new
    end
  end

  private

  # https://github.com/norman/friendly_id/blob/984dac788d106faf60313cd0e51593474a513078/lib/friendly_id/slugged.rb#L191
  def should_generate_new_friendly_id?
    name_changed? || super
  end
end