# encoding: utf-8 module ActiveMerge # Service Object responds for merging given ActiveRecord instances into the # first one # # Object initializes either with ActiveRecord::Association or array of objects. # If the argument is an array, then only those items taken into account, that: # # * inherited from the ActiveRecord::Base # * persisted # * are objects of one class # # == Merging algorithm # # * The item with minimal id selected from the service argument. It will remaine intact. # * All objects, that the other items links to via "has_many" assotiation, are selected... # * ... and are rebound to the first item # * After rebinding any item from the service argument, except the first one, to be deleted. # # As a result it is only the first object from the argument remains intact, # and all the instances that belonged to the other ones are now belongs to it. # # == Example: # # # Let any kingdom has many shires and men # class Kingdom < ActiveRecord::Base # has_many :shires # has_many :men # end # # class Shire < ActiveRecord::Base # belongs_to :kingdom # end # # class Man < ActiveRecord::Base # belongs_to :kingdom # end # # # Britain has 10 shires and 100 thousands men lives there # britain = Kingdom.create! # 100000.times.each{ britain.men.create! } # 10.times.each{ britain.shires.create! } # # # Scotland has 5 shires and 30 thousands living men # scotland = Kingdom.create! # 30000.times.each{ scotland.men.create! } # 5.times.each{ scotland.shires.create! } # # # Lets merge all the kingdoms: # Service.new(Kingdom.all).provide # # # Now union Britain (because it is Britain that was created first) # # has all those 15 shires and 130 thousand men # # from both the old good Britain and the old good Scotland. # britain.reload.men.count # => 130000 # britain.reload.shires.count # => 15 # # # And, alas, the Scotland Kingdom doesn't exists any more # Kingdom.find_by(scotland.id) # => nil # # == Warning! # # The merge provided as a whole transaction. In case of any error, all # changes roll back. # # Let (see the example above) the shire cannot be rebount to another kingdom: # # class Shire # attr_readonly :kingdom_id # end # # In this case the merge won't be finished. Not only shires, but also # scots remain living in their old good Scotland! # # == Skipping activerecord validations # # You can use the validate: false option. # # With this option set any activerecord validation and callback are skipped. # # Service.new(Kingdom.all).provide validate: false # class Service < ActivePatterns::BaseService include ActiveModel::Validations def initialize(list = nil) list = Items.new list @item, @items = list.first, Array(list[1..-1]) end attr_reader :item, :items, :klass, :klasses validates :items, presence: true # Merges all the #items to the #item as a whole transaction def provide(options = {}) transaction do items.each do |item| service = SimpleService.new(self.item, item) change(service) { service.provide(options) } end end end private # Returns only persisted items of the same ActiveRecord model class Items < Array def initialize(items) items = Array(items).map{ |item| Item.new item }.compact items = [] if items.map{ |item| item.class }.uniq.count > 1 super items.sort_by{ |item| item.id } end end # Initializes a persisted item of an ActiveRecord model class Item def self.new(item) return unless item.class.ancestors.include? ActiveRecord::Base return unless item.persisted? item end end end end