# encoding: utf-8
module ActiveMerge
# Сервисный объект (паттерн Service Object), отвечающий за объединение
# двух записей ActiveRecord
#
# В ходе объединения все ссылки на вторую запись перепривязываются к первой,
# затем вторая запись удаляется
#
# В случае любой ошибки вызывается исключение, а ошибка добавляется в массив
# errors
#
class SimpleService
include ActiveModel::Validations
def initialize(first, second)
if first.class.ancestors.include?(ActiveRecord::Base) && first.persisted?
@first = first
end
if @first && (second.class == @first.class) && second.persisted?
@second = second
end
end
attr_reader :first, :second
validates :second, presence: true
def provide
ActiveRecord::Base.transaction requires_new: true do
raise unless valid?
refs.each{ |item, key| rebind(item, key) }
destroy
end
end
private
# Возвращает хэш, в котором ключами выступают связи has_many,
# а значениями - foreign keys
def klasses
second.class.reflect_on_all_associations(:has_many).
inject({}){ |hash, item| hash.merge(item.name => item.foreign_key) }
end
# Возвращает хэш, в котором ключами выступают объекты, ссылающихся на
# #second, а значениями - методы присвоения ссылки на #first.
def refs
@refs = {}
klasses.each do |list, key|
second.send(list).each{ |item| @refs[item] = "#{ key }=" }
end
return @refs
end
# Выполняет перепривязку указанного объекта к первому аргументу
# Ошибки накапливаются в массиве errors
def rebind(item, key)
begin
item.send key, first.id
item.save!
rescue
item.errors.each{ |key, val| errors.add key, val } and raise
end
end
# Удаляет объединяемый объект
def destroy
begin
second.destroy!
rescue
second.errors.each{ |key, val| errors.add key, val } and raise
end
end
end
end