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