Much base work started
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2025-10-31 14:36:14 +11:00
parent 4a35bf6758
commit 88a906064f
97 changed files with 5333 additions and 2774 deletions

100
app/models/work.rb Normal file
View File

@@ -0,0 +1,100 @@
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