class Syncify::Sync

Attributes

has_and_belongs_to_many_associations[RW]
identified_records[RW]

Public Instance Methods

execute() click to toggle source
# File lib/syncify/sync.rb, line 18
def execute
  puts 'Identifying records to sync...'
  @identified_records = Set[]
  @has_and_belongs_to_many_associations = {}

  remote do
    associations = normalized_associations(association)
    initial_query.each do |root_record|
      identify_associated_records(root_record, associations)
    end
  end

  puts "\nIdentified #{identified_records.size} records to sync."

  callback.call(identified_records) if callback.present?

  sync_records
end

Private Instance Methods

cache_has_and_belongs_to_many_association(record, association, associated_records) click to toggle source
# File lib/syncify/sync.rb, line 136
def cache_has_and_belongs_to_many_association(record, association, associated_records)
  has_and_belongs_to_many_associations[record] ||= {}
  has_and_belongs_to_many_associations[record][association] = Array(associated_records)
end
classify_identified_instances() click to toggle source
# File lib/syncify/sync.rb, line 178
def classify_identified_instances
  puts "Classifying #{identified_records.size} records for bulk import..."

  identified_records.each_with_object({}) do |instance, memo|
    clazz = instance.class
    class_name = clazz.name
    memo[class_name] ||= []
    memo[class_name] << clone_instance(instance)
  end
end
clone_instance(instance) click to toggle source
# File lib/syncify/sync.rb, line 189
def clone_instance(instance)
  clazz = instance.class
  new_instance = clazz.new

  instance.attributes.each do |attribute, value|
    new_instance[attribute.to_s] = value
  end

  new_instance
end
has_and_belongs_to_many_association?(record, association) click to toggle source
# File lib/syncify/sync.rb, line 141
def has_and_belongs_to_many_association?(record, association)
  record.class.reflect_on_association(association).class ==
    ActiveRecord::Reflection::HasAndBelongsToManyReflection
end
id_xor_where_present?() click to toggle source
# File lib/syncify/sync.rb, line 47
def id_xor_where_present?
  unless id? ^ where?
    errors.add(:id,
               'Please provide either the id argument or the where argument, but not both.')
  end
end
identify_associated_records(root, associations) click to toggle source
# File lib/syncify/sync.rb, line 62
def identify_associated_records(root, associations)
  identified_records << root
  print_status

  standard_associations = associations.reject(&method(:includes_polymorphic_association))
  polymorphic_associations = associations.select(&method(:includes_polymorphic_association))

  standard_associations.each do |association|
    # begin
    traverse_associations(root.class.eager_load(association).find(root.id), association)
    # rescue StandardError => e
    #   binding.pry
    #   x = 1231231231
    # end
  end

  identify_polymorphic_associated_records(root, polymorphic_associations)
end
identify_polymorphic_associated_records(root, polymorphic_associations) click to toggle source
# File lib/syncify/sync.rb, line 81
def identify_polymorphic_associated_records(root, polymorphic_associations)
  polymorphic_associations.each do |polymorphic_association|
    property = polymorphic_association.keys.first
    nested_associations = polymorphic_association[property]

    referenced_objects = Array.wrap(root.__send__(property))
    next if referenced_objects.empty?

    referenced_objects.each do |referenced_object|
      # TODO: there's got to be a better way to express this
      if polymorphic_association.values.first.keys.all? { |key| key.is_a? Class }
        # TODO: please, Doug. Fix the names!!!
        associated_types = nested_associations.keys
        referenced_type = associated_types.find { |type| referenced_object.is_a?(type) }

        identify_associated_records(
          referenced_object,
          normalized_associations(nested_associations[referenced_type])
        )
      else
        identify_associated_records(referenced_object, normalized_associations(nested_associations))
      end
    end
  end
end
includes_polymorphic_association(association) click to toggle source
# File lib/syncify/sync.rb, line 200
def includes_polymorphic_association(association)
  return false if association.nil?
  return false if association == {}
  return true if association.keys.all? { |key| key.is_a? Class }

  includes_polymorphic_association(association.values.first)
end
initial_query() click to toggle source
# File lib/syncify/sync.rb, line 39
def initial_query
  if id?
    klass.where(id: id)
  else
    klass.where(where)
  end
end
normalized_associations(association) click to toggle source
# File lib/syncify/sync.rb, line 208
def normalized_associations(association)
  Syncify::NormalizeAssociations.run!(association: association)
end
print_status() click to toggle source
remote() { || ... } click to toggle source
# File lib/syncify/sync.rb, line 212
def remote
  run_in_environment(remote_database) { yield }
end
run_in_environment(environment) { || ... } click to toggle source
# File lib/syncify/sync.rb, line 216
def run_in_environment(environment)
  initial_config = ActiveRecord::Base.connection_config
  ActiveRecord::Base.establish_connection environment
  yield
ensure
  ActiveRecord::Base.establish_connection initial_config
end
sync_records() click to toggle source
# File lib/syncify/sync.rb, line 151
def sync_records
  ActiveRecord::Base.connection.disable_referential_integrity do
    classify_identified_instances.each do |class_name, new_instances|
      puts "Syncing #{new_instances.size} #{class_name} objects"
      clazz = Object.const_get(class_name)
      clazz.where(id: new_instances.map(&:id)).delete_all
      clazz.import(new_instances, validate: false)
    end

    has_and_belongs_to_many_associations.each do |record, associations|
      associations.each do |association, associated_records|
        # begin
        local_record = record.class.find(record.id)
        local_associated_records = associated_records.map do |associated_record|
          associated_record.class.find(associated_record.id)
        end
        local_record.__send__(association) << local_associated_records
        local_record.save
        # rescue StandardError => e
        #   binding.pry
        #   x = 123
        # end
      end
    end
  end
end
through_association?(record, association) click to toggle source
# File lib/syncify/sync.rb, line 146
def through_association?(record, association)
  record.class.reflect_on_association(association).class ==
    ActiveRecord::Reflection::ThroughReflection
end
traverse_associations(records, associations) click to toggle source
# File lib/syncify/sync.rb, line 107
def traverse_associations(records, associations)
  records = Array(records)

  identified_records.merge records
  print_status

  records.each do |record|
    associations.each do |association, nested_associations|
      puts "Traversing #{record.class.name}##{association}"
      if through_association?(record, association)
        traverse_associations(
          record.__send__(
            record.class.reflect_on_association(association).through_reflection.name
          ),
          associations
        )
      else
        associated_records = record.__send__(association)

        if has_and_belongs_to_many_association?(record, association)
          cache_has_and_belongs_to_many_association(record, association, associated_records)
        end

        traverse_associations(associated_records, nested_associations)
      end
    end
  end
end