228 lines
7.4 KiB
Ruby
228 lines
7.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# A module used for classes that can appear as the "creation" in a Creatorship
|
|
# (i.e. Work, Series, and Chapter).
|
|
module Creatable
|
|
extend ActiveSupport::Concern
|
|
|
|
included do
|
|
has_many :creatorships,
|
|
autosave: true,
|
|
as: :creation,
|
|
inverse_of: :creation
|
|
|
|
has_many :approved_creatorships,
|
|
-> { Creatorship.approved },
|
|
class_name: "Creatorship",
|
|
as: :creation,
|
|
inverse_of: :creation
|
|
|
|
has_many :pseuds,
|
|
through: :approved_creatorships,
|
|
before_add: :disallow_pseud_changes,
|
|
before_remove: :disallow_pseud_changes
|
|
|
|
has_many :users,
|
|
-> { distinct },
|
|
through: :pseuds
|
|
|
|
attr_reader :current_user_pseuds
|
|
|
|
validate :check_no_creators
|
|
validate :check_current_user_pseuds
|
|
after_save :update_current_user_pseuds
|
|
after_destroy :destroy_creatorships
|
|
end
|
|
|
|
########################################
|
|
# CALLBACKS & VALIDATIONS
|
|
########################################
|
|
|
|
# Updating pseuds directly goes through the approved_creatorships relation,
|
|
# so it will automatically approve any pseuds added in this way. So we want
|
|
# to make sure that this is a read-only relation.
|
|
def disallow_pseud_changes(*)
|
|
raise "Cannot add or remove pseuds through the pseuds association!"
|
|
end
|
|
|
|
# Make sure that there will be at least one approved creator after saving:
|
|
def check_no_creators
|
|
return if @current_user_pseuds.present? || pseuds_after_saving.any?
|
|
|
|
errors.add(:base, ts("%{type} must have at least one creator.",
|
|
type: model_name.human))
|
|
end
|
|
|
|
# Make sure that if @current_user_pseuds is not nil, then the user has
|
|
# selected at least one pseud, and that all of the pseuds they've selected
|
|
# are their own.
|
|
def check_current_user_pseuds
|
|
return unless @current_user_pseuds && User.current_user.is_a?(User)
|
|
|
|
if @current_user_pseuds.empty?
|
|
errors.add(:base, ts("You haven't selected any pseuds for this %{type}.",
|
|
type: model_name.human.downcase))
|
|
end
|
|
|
|
if @current_user_pseuds.any? { |p| p.user_id != User.current_user.id }
|
|
errors.add(:base, ts("You're not allowed to use that pseud."))
|
|
end
|
|
end
|
|
|
|
# The variable @current_user_pseuds stores which pseuds the current editor
|
|
# wants to use on this work. The pseuds should contain only pseuds owned by
|
|
# User.current_user.
|
|
def update_current_user_pseuds
|
|
return unless @current_user_pseuds
|
|
|
|
set_current_user_pseuds(@current_user_pseuds)
|
|
@current_user_pseuds = nil
|
|
end
|
|
|
|
# Clean up all creatorships associated with this item.
|
|
def destroy_creatorships
|
|
creatorships.destroy_all
|
|
end
|
|
|
|
########################################
|
|
# VIRTUAL ATTRIBUTES
|
|
########################################
|
|
|
|
# Update all creator-related attributes.
|
|
def author_attributes=(attributes)
|
|
self.new_bylines = attributes[:byline] if attributes[:byline].present?
|
|
self.new_co_creator_ids = attributes[:coauthors] if attributes[:coauthors].present?
|
|
self.current_user_pseud_ids = attributes[:ids] if attributes[:ids].present?
|
|
end
|
|
|
|
# Invite new co-creators by passing in their byline.
|
|
def new_bylines=(bylines)
|
|
bylines.split(",").reject(&:blank?).map(&:strip).each do |byline|
|
|
self.creatorships.build(byline: byline, enable_notifications: true)
|
|
end
|
|
end
|
|
|
|
# Invite new co-creators by ID.
|
|
def new_co_creator_ids=(ids)
|
|
new_pseuds = Pseud.where(id: ids).to_a
|
|
|
|
creatorships.each do |creatorship|
|
|
if new_pseuds.include?(creatorship.pseud)
|
|
new_pseuds.delete(creatorship.pseud)
|
|
end
|
|
end
|
|
|
|
new_pseuds.each do |pseud|
|
|
self.creatorships.build(pseud: pseud, enable_notifications: true)
|
|
end
|
|
end
|
|
|
|
# Update which of User.current_user's pseuds should be listed on the byline
|
|
# after saving.
|
|
def current_user_pseud_ids=(ids)
|
|
return unless User.current_user.is_a?(User)
|
|
|
|
@current_user_pseuds = Pseud.where(id: ids).to_a
|
|
end
|
|
|
|
# This behaves very similarly to new_bylines=, but because it's designed to
|
|
# be used for bulk editing works, it doesn't handle ambiguous pseuds well. So
|
|
# we need to manually refine our guess as much as possible.
|
|
def pseuds_to_add=(pseud_names)
|
|
names = pseud_names.split(",").reject(&:blank?).map(&:strip)
|
|
|
|
names.each do |name|
|
|
possible_pseuds = Pseud.parse_byline_ambiguous(name)
|
|
|
|
pseud = if possible_pseuds.size > 1
|
|
Pseud.parse_byline(name)
|
|
else
|
|
possible_pseuds.first
|
|
end
|
|
|
|
if pseud
|
|
creatorship = creatorships.find_or_initialize_by(pseud: pseud)
|
|
creatorship.enable_notifications = true
|
|
end
|
|
end
|
|
end
|
|
|
|
########################################
|
|
# USEFUL FUNCTIONS
|
|
########################################
|
|
|
|
# Update the pseuds on this item so that User.current_user's pseuds are
|
|
# replaced by the passed-in array of pseuds new_pseuds. If it's a Series, we
|
|
# also update the user's byline on any owned works in the series. If it's a
|
|
# Work, we also update the user's byline on any owned chapters in the series.
|
|
def set_current_user_pseuds(new_pseuds)
|
|
return unless User.current_user.is_a?(User)
|
|
|
|
user_id = User.current_user.id
|
|
|
|
children = if is_a?(Work)
|
|
chapters.to_a
|
|
elsif is_a?(Series)
|
|
works.to_a
|
|
else
|
|
[]
|
|
end
|
|
|
|
transaction do
|
|
children.each do |child|
|
|
next unless child.users.include?(User.current_user)
|
|
child.set_current_user_pseuds(new_pseuds)
|
|
end
|
|
|
|
# Create before destroying, so that we don't run into issues with
|
|
# deleting the very last creator.
|
|
new_pseuds.each do |pseud|
|
|
creatorships.approve_or_create_by(pseud: pseud)
|
|
end
|
|
|
|
creatorships.each do |creatorship|
|
|
creatorship.destroy unless new_pseuds.include?(creatorship.pseud) ||
|
|
creatorship.pseud&.user_id != user_id
|
|
end
|
|
end
|
|
end
|
|
|
|
# Figure out which creatorships will exist after saving.
|
|
#
|
|
# Excludes creatorships with a missing pseud, because those orphaned
|
|
# creatorships can break various bits of code if they're considered valid.
|
|
def creatorships_after_saving
|
|
creatorships.select(&:valid?).reject(&:marked_for_destruction?).
|
|
reject { |creatorship| creatorship.pseud.nil? }
|
|
end
|
|
|
|
# Calculate what the pseuds on this work will be after saving, taking into
|
|
# account validity, approval, and @current_user_pseuds.
|
|
def pseuds_after_saving
|
|
pseuds = creatorships_after_saving.select(&:approved?).map(&:pseud)
|
|
|
|
if @current_user_pseuds
|
|
pseuds = (pseuds - User.current_user.pseuds) + @current_user_pseuds
|
|
end
|
|
|
|
pseuds.uniq
|
|
end
|
|
|
|
# Check whether the passed-in user has been invited to become a creator.
|
|
def user_has_creator_invite?(user)
|
|
return false unless user.is_a?(User)
|
|
creatorships.unapproved.for_user(user).exists?
|
|
end
|
|
|
|
# Check whether the given user has some kind of creatorship (approved or
|
|
# unapproved) associated with this item.
|
|
def user_is_owner_or_invited?(user)
|
|
return false unless user.is_a?(User)
|
|
creatorships.for_user(user).exists?
|
|
end
|
|
|
|
# Get all orphan_account pseuds that (co-)created this creatable, excluding the orphan_account's default_pseud
|
|
def orphan_pseuds
|
|
self.pseuds.where(user_id: User.orphan_account.id, is_default: false)
|
|
end
|
|
end
|