hummingbird-me/kitsu-server

View on GitHub
app/resources/media_resource.rb

Summary

Maintainability
A
0 mins
Test Coverage
D
61%
class MediaResource < BaseResource
  include SluggableResource

  abstract

  # This regex accepts a numerical range or single number
  # $1 = start, $2 = dot representing closed/open, $3 = end
  NUMBER = /(\d+(?:\.\d+)?)/
  NUMERIC_RANGE = /\A#{NUMBER}?(\.{2,3})?#{NUMBER}?\z/
  NUMERIC_QUERY = {
    valid: ->(value, _ctx) do
      matches = NUMERIC_RANGE.match(value)
      # You gotta provide at least *one* number
      matches && (matches[1].present? || matches[3].present?)
    end,
    apply: ->(values, _ctx) do
      # We only accept the first value
      values.map do |value|
        matches = NUMERIC_RANGE.match(value)
        inclusive = matches[2] == '..'

        if matches[2] # Range
          if matches[1] && matches[3] # Double-ended
            Range.new(matches[1].to_f, matches[3].to_f, !inclusive)
          elsif matches[1] # start...
            key = inclusive ? 'gte' : 'gt'
            { range: { '$field' => { key => matches[1] } } }
          else # ...end
            key = inclusive ? 'lte' : 'lt'
            { range: { '$field' => { key => matches[3] } } }
          end
        else # Scalar
          matches[1]
        end
      end
    end
  }.freeze

  caching

  attribute :synopsis # DEPRECATED
  def synopsis
    description
  end

  attribute :description
  def description
    _model.description['en']
  end

  # Cover image location
  attributes :cover_image_top_offset,
    # Titles
    :titles, :canonical_title, :abbreviated_titles,
    # Ratings
    :average_rating, :rating_frequencies, :user_count, :favorites_count,
    # Dates
    :start_date, :end_date, :next_release,
    # Rankings
    :popularity_rank, :rating_rank,
    # Age Ratings
    :age_rating, :age_rating_guide,
    # Subtype
    :subtype,
    # Airing/Publishing Status
    :status, :tba
  # Images
  attribute :poster_image, format: :shrine_attachment, delegate: :poster_image_attacher
  attribute :cover_image, format: :shrine_attachment, delegate: :cover_image_attacher

  has_many :genres
  has_many :categories
  has_many :castings
  has_many :installments
  has_many :mappings
  has_many :reviews
  has_many :media_relationships
  has_many :characters
  has_many :staff
  has_many :productions
  has_many :quotes

  filter :subtype, apply: ->(records, values, _opts) {
    values = values.map { |v| records.subtypes[v] || v }
    records.where(subtype: values)
  }
  filter :status, apply: ->(records, values, _opts) {
    values.inject(records.none) do |query, value|
      if %w[tba unreleased upcoming current finished].include? value
        query.or(records.send(value))
      else
        query
      end
    end
  }
  filter :since, apply: ->(records, values, _options) {
    time = values.join.to_time
    records.where('updated_at >= ?', time)
  }

  # Common ElasticSearch stuff
  query :year, NUMERIC_QUERY
  query :average_rating, NUMERIC_QUERY
  query :user_count, NUMERIC_QUERY
  query :subtype
  query :status
  query :genres,
    apply: ->(values, _ctx) {
      { match: { genres: { query: values.join(' '), operator: 'and' } } }
    }
  query :categories,
    apply: ->(values, _ctx) {
      { match: { categories: { query: values.join(' '), operator: 'and' } } }
    }
  query :text,
    mode: :query,
    apply: ->(values, _ctx) {
      {
        function_score: {
          script_score: {
            lang: 'expression',
            script: "max(log10(doc['user_count'].value), 1) * _score"
          },
          query: {
            bool: {
              should: [
                { multi_match: {
                  fields: %w[
                    titles.* abbreviated_titles description people characters
                  ],
                  query: values.join(' '),
                  fuzziness: 2,
                  max_expansions: 15,
                  prefix_length: 2
                } },
                { multi_match: {
                  fields: %w[titles.* abbreviated_titles],
                  query: values.join(' '),
                  boost: 1.2
                } }
              ]
            }
          }
        }
      }
    }

  def self.updatable_fields(context)
    super - [:status]
  end

  def self.creatable_fields(context)
    super - [:status]
  end
end