lib/saviour/integrator.rb
module Saviour
class Integrator
def initialize(klass, persistence_klass)
@klass = klass
@persistence_klass = persistence_klass
end
def setup!
raise(ConfigurationError, "You cannot include Saviour::Model twice in the same class") if @klass.respond_to?(:attached_files)
@klass.class_attribute :attached_files
@klass.attached_files = []
@klass.class_attribute :followers_per_leader_config
@klass.followers_per_leader_config = {}
@klass.class_attribute :uploader_classes
@klass.uploader_classes = {}
persistence_klass = @persistence_klass
@klass.define_singleton_method "attach_file" do |attach_as, *maybe_uploader_klass, **opts, &block|
self.attached_files += [attach_as]
uploader_klass = maybe_uploader_klass[0]
if opts[:follow]
dependent = opts[:dependent]
if dependent.nil? || ![:destroy, :ignore].include?(dependent)
raise(ConfigurationError, "You must specify a :dependent option when using :follow. Use either :destroy or :ignore")
end
self.followers_per_leader_config = self.followers_per_leader_config.dup
self.followers_per_leader_config[opts[:follow]] ||= []
self.followers_per_leader_config[opts[:follow]].push({ attachment: attach_as, dependent: dependent })
end
if uploader_klass.nil? && block.nil?
raise ConfigurationError, "you must provide either an UploaderClass or a block to define it."
end
uploader_klass = Class.new(Saviour::BaseUploader, &block) if block
self.uploader_classes[attach_as] = uploader_klass
mod = Module.new do
define_method(attach_as) do
instance_variable_get("@__uploader_#{attach_as}") || begin
layer = persistence_klass.new(self)
new_file = ::Saviour::File.new(uploader_klass, self, attach_as, layer.read(attach_as))
instance_variable_set("@__uploader_#{attach_as}", new_file)
end
end
define_method("#{attach_as}=") do |value|
send(attach_as).assign(value)
end
define_method("#{attach_as}?") do
send(attach_as).present?
end
define_method("#{attach_as}_was") do
instance_variable_get("@__uploader_#{attach_as}_was")
end
define_method("#{attach_as}_changed?") do
send(attach_as).changed?
end
define_method(:changed_attributes) do
if send("#{attach_as}_changed?")
super().merge(attach_as => send("#{attach_as}_was"))
else
super()
end
end
define_method(:changes) do
if send("#{attach_as}_changed?")
super().merge(attach_as => send("#{attach_as}_change"))
else
super()
end
end
define_method(:changed) do
if ActiveRecord::VERSION::MAJOR == 6 && send("#{attach_as}_changed?")
super() + [attach_as.to_s]
else
super()
end
end
define_method(:changed?) do
if ActiveRecord::VERSION::MAJOR == 6
send("#{attach_as}_changed?") || super()
else
super()
end
end
define_method("#{attach_as}_change") do
[send("#{attach_as}_was"), send(attach_as)]
end
define_method("remove_#{attach_as}!") do |dependent: nil|
if !dependent.nil? && ![:destroy, :ignore].include?(dependent)
raise ArgumentError, ":dependent option must be either :destroy or :ignore"
end
layer = persistence_klass.new(self)
attachment_remover = proc do |attach_as|
layer.write(attach_as, nil)
deletion_path = send(attach_as).persisted_path
send(attach_as).delete
work = proc do
file = send(attach_as)
file.uploader.storage.delete(deletion_path) if deletion_path && file.persisted_path.nil?
end
if ActiveRecord::Base.connection.current_transaction.open?
DbHelpers.run_after_commit &work
else
work.call
end
end
attachment_remover.call(attach_as)
(self.class.followers_per_leader_config[attach_as] || []).each do |follower|
dependent_option = dependent || follower[:dependent]
next if dependent_option == :ignore || send(follower[:attachment]).changed?
attachment_remover.call(follower[:attachment])
end
end
end
self.include mod
end
@klass.define_singleton_method("attached_followers_per_leader") do
self.followers_per_leader_config.map do |leader, followers|
[leader, followers.map { |data| data[:attachment] }]
end.to_h
end
@klass.class_attribute :__saviour_validations
@klass.__saviour_validations = Hash.new { [] }
@klass.define_singleton_method("attach_validation") do |attach_as, method_name = nil, &block|
self.__saviour_validations = self.__saviour_validations.dup
self.__saviour_validations[attach_as] += [{ method_or_block: method_name || block, type: :memory }]
end
@klass.define_singleton_method("attach_validation_with_file") do |attach_as, method_name = nil, &block|
self.__saviour_validations = self.__saviour_validations.dup
self.__saviour_validations[attach_as] += [{ method_or_block: method_name || block, type: :file }]
end
end
end
end