# 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