otwarchive-symphonyarchive/lib/collectible.rb

151 lines
5.9 KiB
Ruby
Raw Permalink Normal View History

2026-03-11 22:22:11 +00:00
module Collectible
def self.included(collectible)
collectible.class_eval do
has_many :collection_items, as: :item, inverse_of: :item
accepts_nested_attributes_for :collection_items, allow_destroy: true
has_many :approved_collection_items, -> { approved_by_both }, class_name: "CollectionItem", as: :item
has_many :user_approved_collection_items, -> { approved_by_user }, class_name: "CollectionItem", as: :item
has_many :collections,
through: :collection_items,
after_add: :set_visibility,
after_remove: :set_visibility,
before_remove: :destroy_collection_item
has_many :approved_collections,
through: :approved_collection_items,
source: :collection
has_many :user_approved_collections,
through: :user_approved_collection_items,
source: :collection
has_many :rejected_collections,
-> { CollectionItem.rejected_by_user },
through: :collection_items,
source: :collection
# Note: this scope includes the items in the children of the specified collection
scope :in_collection, lambda { |collection|
distinct.joins(:approved_collection_items).merge(collection.all_items)
}
after_destroy :clean_up_collection_items
end
end
# add collections based on a comma-separated list of names
def collections_to_add=(collection_names)
old_collections = self.collection_items.collect(&:collection_id)
names = trim_collection_names(collection_names)
names.each do |name|
c = Collection.find_by(name: name)
errors.add(:base, ts("We couldn't find the collection %{name}.", name: name)) and return if c.nil?
if c.closed?
errors.add(:base, ts("The collection %{name} is not currently open.", name: name)) and return unless c.user_is_maintainer?(User.current_user) || old_collections.include?(c.id)
end
add_to_collection(c)
end
end
# remove collections based on an array of ids
def collections_to_remove=(collection_ids)
collection_ids.reject {|id| id.blank?}.map {|id| id.is_a?(String) ? id.strip : id}.each do |id|
c = Collection.find(id) || nil
remove_from_collection(c)
end
end
def collections_to_add; nil; end
def collections_to_remove; nil; end
def add_to_collection(collection)
if collection && !self.collections.include?(collection)
self.collections << collection
end
end
def remove_from_collection(collection)
if collection && self.collections.include?(collection)
self.collections -= [collection]
end
end
private
def trim_collection_names(names)
names.split(',').map{ |name| name.strip }.reject {|name| name.blank?}
end
public
# Set ALL of an item's collections based on a list of collection names
# Refactored to use collections_to_(add,remove) above so we only have one set of code
# performing the actual add/remove actions
# This method now just does the convenience work of getting the removed ids -- any missing collections
# will be identified
# IMPORTANT: cannot delete all existing collections, or else items in closed collections
# can't be edited
def collection_names=(new_collection_names)
new_names = trim_collection_names(new_collection_names)
remove_ids = self.collections.reject {|c| new_names.include?(c.name)}.collect(&:id)
self.collections_to_add = new_names.join(",")
self.collections_to_remove = remove_ids
end
# NOTE: better to use collections_to_add/remove above instead for more consistency
def collection_names
@collection_names ? @collection_names : self.collections.collect(&:name).uniq.join(",")
end
#### UNREVEALED/ANONYMOUS
# Set the anonymous/unrevealed status of the collectible based on its collections
# We can't check for user approval because the collection item doesn't exist
# and don't need to because this only gets called when the work is a new record and
# therefore being created by its author
def set_anon_unrevealed
if self.respond_to?(:in_anon_collection) && self.respond_to?(:in_unrevealed_collection)
# if we have collection items saved here then the collectible is not a new object
if self.id.nil? || self.collection_items.empty?
self.in_anon_collection = !self.collections.select(&:anonymous?).empty?
self.in_unrevealed_collection = !self.collections.select(&:unrevealed?).empty?
else
update_anon_unrevealed
end
end
return true
end
# TODO: need a better, DRY, long-term fix
# Collection items can be revealed independently of a collection, so we don't want
# to check the collection status when those are updated
# Only include collections approved by the user
def update_anon_unrevealed
if self.respond_to?(:in_anon_collection) && self.respond_to?(:in_unrevealed_collection)
self.in_anon_collection = self.user_approved_collection_items.anonymous.any?
self.in_unrevealed_collection = self.user_approved_collection_items.unrevealed.any?
end
end
#### CALLBACKS
# Calculate (but don't save) whether this work should be anonymous and/or
# unrevealed. Saving the results of this will be handled when the work saves,
# or by the collection item's callbacks.
def set_visibility(collection)
set_anon_unrevealed
end
# We want to do this after the work is deleted to avoid issues with
# accidentally trying to reveal the work during deletion (which wouldn't
# successfully reveal the work because it'd fail while trying to save the
# partially invalid work, but would cause an error).
def clean_up_collection_items
self.collection_items.destroy_all
end
# Destroy the collection item before the collection is deleted, so that we
# trigger the CollectionItem's after_destroy callbacks.
def destroy_collection_item(collection)
self.collection_items.find_by(collection: collection).try(:destroy)
end
end