101 lines
3.0 KiB
Ruby
101 lines
3.0 KiB
Ruby
class Work < ApplicationRecord
|
|
# 1. Includes/Concerns
|
|
include Searchable
|
|
|
|
# 2. JSON Store for flexible metadata
|
|
store :metadata, accessors: [:tmdb_data, :imdb_data, :custom_fields]
|
|
store :tmdb_data, accessors: [:overview, :poster_path, :backdrop_path, :release_date, :genres]
|
|
store :imdb_data, accessors: [:plot, :rating, :votes, :runtime, :director]
|
|
store :custom_fields
|
|
|
|
# 3. Associations
|
|
has_many :videos, dependent: :nullify
|
|
has_many :external_ids, dependent: :destroy
|
|
has_one :primary_video, -> { order("(video_metadata->>'height')::int DESC") }, class_name: "Video"
|
|
|
|
# 4. Validations
|
|
validates :title, presence: true
|
|
validates :year, numericality: { only_integer: true, greater_than: 1800 }, allow_nil: true
|
|
validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 10 }, allow_nil: true
|
|
|
|
# 5. Scopes
|
|
scope :organized, -> { where(organized: true) }
|
|
scope :unorganized, -> { where(organized: false) }
|
|
scope :recent, -> { order(created_at: :desc) }
|
|
scope :by_title, -> { order(:title) }
|
|
scope :with_year, -> { where.not(year: nil) }
|
|
|
|
# 6. Delegations
|
|
delegate :resolution_label, :duration, to: :primary_video, prefix: true, allow_nil: true
|
|
|
|
# 7. Class methods
|
|
def self.search(query)
|
|
where("title LIKE ? OR director LIKE ?", "%#{sanitize_sql_like(query)}%", "%#{sanitize_sql_like(query)}%")
|
|
end
|
|
|
|
def self.find_by_external_id(source, value)
|
|
joins(:external_ids).find_by(external_ids: { source: source, value: value })
|
|
end
|
|
|
|
# 8. Instance methods
|
|
def display_title
|
|
year ? "#{title} (#{year})" : title
|
|
end
|
|
|
|
def video_count
|
|
videos.count
|
|
end
|
|
|
|
def total_duration
|
|
videos.sum("(video_metadata->>'duration')::float")
|
|
end
|
|
|
|
def available_versions
|
|
videos.group_by(&:resolution_label)
|
|
end
|
|
|
|
def has_external_ids?
|
|
external_ids.exists?
|
|
end
|
|
|
|
def poster_url
|
|
poster_path || tmdb_data['poster_path']
|
|
end
|
|
|
|
def backdrop_url
|
|
backdrop_path || tmdb_data['backdrop_path']
|
|
end
|
|
|
|
def description
|
|
return read_attribute(:description) if read_attribute(:description).present?
|
|
tmdb_data['overview'] || imdb_data['plot']
|
|
end
|
|
|
|
def effective_director
|
|
return read_attribute(:director) if read_attribute(:director).present?
|
|
imdb_data['director']
|
|
end
|
|
|
|
def effective_rating
|
|
return read_attribute(:rating) if read_attribute(:rating).present?
|
|
imdb_data['rating']&.to_f
|
|
end
|
|
|
|
# Convenience accessors for common external IDs
|
|
# Auto-generated for all sources (will be implemented when we add ExternalId model logic)
|
|
# ExternalId.sources.keys.each do |source_name|
|
|
# define_method("#{source_name}_id") do
|
|
# external_ids.find_by(source: source_name)&.value
|
|
# end
|
|
#
|
|
# define_method("#{source_name}_id=") do |value|
|
|
# return if value.blank?
|
|
# external_ids.find_or_initialize_by(source: source_name).update!(value: value)
|
|
# end
|
|
#
|
|
# define_method("#{source_name}_url") do
|
|
# external_ids.find_by(source: source_name)&.url
|
|
# end
|
|
# end
|
|
end
|