Files
growstuff/app/models/job.rb
T

96 lines
3.1 KiB
Ruby

# frozen_string_literal: true
class Job < ApplicationRecord
extend FriendlyId
include Ownable
include Finishable
include PhotoCapable
belongs_to :garden, optional: true
belongs_to :jobbable, polymorphic: true, optional: true
# The template step this job was made from, if it was.
belongs_to :crop_job_step, optional: true
JOBBABLE_TYPES = %w(Crop Planting Seed).freeze
# What each action can be about: plant a crop or the seeds of it, harvest a crop
# or a planting of it.
ACTION_JOBBABLES = { 'plant' => %w(Crop Seed), 'harvest' => %w(Crop Planting) }.freeze
enum :action, ACTION_JOBBABLES.keys.index_with(&:itself), prefix: true, validate: { allow_nil: true }
scope :active, -> { where(finished: [false, nil]) }
scope :finished_this_week, -> { where(finished: true, finished_at: Time.zone.now.beginning_of_week..) }
friendly_id :job_slug, use: %i(slugged finders)
validates :name, presence: true
validates :slug, uniqueness: true
# The add form sends these empty when the job is not linked to anything.
normalizes :jobbable_type, with: ->(type) { type.presence }
validates :jobbable_type, inclusion: { in: JOBBABLE_TYPES }, allow_nil: true
validate :action_goes_with_jobbable, if: :action
validate :jobbable_is_the_owners, if: -> { jobbable_id_changed? || jobbable_type_changed? }
before_save :stamp_finished_at, if: :finished_changed?
delegate :login_name, :slug, to: :owner, prefix: true
delegate :name, to: :garden, prefix: true, allow_nil: true
def job_slug
"#{owner.login_name}-#{name}-#{id}".downcase.tr(' ', '-')
end
def to_s
name
end
# The planting a job is about, if it is about one. The form, Copy and the API
# speak of plantings; the link itself is the jobbable.
def planting
jobbable if jobbable_type == 'Planting'
end
def planting=(planting)
self.jobbable = planting
end
def planting_id
jobbable_id if jobbable_type == 'Planting'
end
# Nothing chosen on the form leaves the job about whatever it was about.
def planting_id=(id)
self.jobbable = Planting.find_by(id:) if id.present?
end
# The photo taken before or after the job was done, if there is one. Of those
# with the label, the first added. Uses the loaded photos, so a list of jobs
# can load them all at once.
def photo_at(moment)
photo_associations.select { |association| association.label == moment }.min_by(&:id)&.photo
end
def planting_name
planting&.crop_name
end
private
def stamp_finished_at
self.finished_at = (Time.zone.now if finished)
end
def action_goes_with_jobbable
errors.add(:action, :cannot_be_about_that) unless ACTION_JOBBABLES.fetch(action, []).include?(jobbable_type)
end
# A job about something is about something that is there. Plantings and seeds are a member's own: a job can't
# be about someone else's.
def jobbable_is_the_owners
return unless JOBBABLE_TYPES.include?(jobbable_type)
return errors.add(:jobbable, :required) if jobbable.nil?
errors.add(:jobbable, :invalid) if jobbable.respond_to?(:owner_id) && jobbable.owner_id != owner_id
end
end