class RailsArchiver::Archiver

Attributes

archive_location[RW]
transport[RW]

Public Class Methods

new(model, options={}) click to toggle source

Create a new Archiver with the given model. @param model [ActiveRecord::Base] the model to archive or unarchive. @param options [Hash]

* logger [Logger]
* transport [Sybmol] :in_memory or :s3 right now
* delete_records [Boolean] whether or not we should delete existing
  records
# File lib/rails-archiver/archiver.rb, line 27
def initialize(model, options={})
  @model = model
  @logger = options.delete(:logger) || ::Logger.new(STDOUT)
  @hash = {}
  self.transport = _get_transport(options.delete(:transport) || :in_memory)
  @options = options
  # hash of table name -> IDs to delete in that table
  @ids_to_delete = {}
end

Public Instance Methods

archive() click to toggle source

Archive a model. @return [Hash] the hash that was archived.

# File lib/rails-archiver/archiver.rb, line 39
def archive
  @logger.info("Starting archive of #{@model.class.name} #{@model.id}")
  @hash = {}
  visit_nodes(@model)
  save_additional_data
  @logger.info('Completed loading data')
  @archive_location = @transport.store_archive(@hash)
  if @model.attribute_names.include?('archived')
    @model.update_attribute(:archived, true)
  end
  if @options[:delete_records]
    @logger.info('Deleting rows')
    _delete_records
    @logger.info('All records deleted')
  end
  @hash
end
delete_from_table(table, ids) click to toggle source

Delete rows from a table. Can be used in delete_records. @param table [String] the table name. @param ids [Array<Integer>] the IDs to delete.

# File lib/rails-archiver/archiver.rb, line 116
    def delete_from_table(table, ids)
      return if ids.blank?
      @logger.info("Deleting #{ids.size} records from #{table}")
      groups = ids.to_a.in_groups_of(10000)
      groups.each_with_index do |group, i|
        sleep(0.5) if i > 0 # throttle so we don't kill the DB
        delete_query = <<-SQL
          DELETE FROM `#{table}` WHERE `id` IN (#{group.compact.join(',')})
        SQL
        ActiveRecord::Base.connection.delete(delete_query)
      end

      @logger.info("Finished deleting from #{table}")
    end
save_additional_data() click to toggle source

Use this method to add more data into the archive hash which isn't directly accessible from the parent model. Call `visit_nodes` on each set of data you want to add. To be overridden by subclasses.

# File lib/rails-archiver/archiver.rb, line 60
def save_additional_data
end
unarchiver() click to toggle source

@return [RailsArchiver::Unarchiver]

# File lib/rails-archiver/archiver.rb, line 132
def unarchiver
  unarchiver = RailsArchiver::Unarchiver.new(@model, :logger => @logger)
  unarchiver.transport = self.transport
  unarchiver
end
visit(node) click to toggle source

Returns a single object in the database represented as a hash. Does not account for any associations, only prints out the columns associated with the object as they relate to the current schema. Can be extended but should not be overridden or called explicitly. @param node [ActiveRecord::Base] an object that inherits from AR::Base @return [Hash]

# File lib/rails-archiver/archiver.rb, line 96
def visit(node)
  return {} unless node.class.respond_to?(:column_names)
   if @options[:delete_records] && node != @model
    @ids_to_delete[node.class.table_name] ||= Set.new
    @ids_to_delete[node.class.table_name] << node.id
  end
  IDHash[
    node.class.column_names.select do |cn|
      next unless node.respond_to?(cn)
      # Only export columns that we actually have data for
      !node[cn].nil?
    end.map do |cn|
      [cn.to_sym, node[cn]]
    end
  ]
end
visit_nodes(node) click to toggle source

Used to visit an association, and recursively calls down to all child objects through all other allowed associations. @param node [ActiveRecord::Base|Array<ActiveRecord::Base>]

any object(s) that inherits from ActiveRecord::Base
# File lib/rails-archiver/archiver.rb, line 67
def visit_nodes(node)
  return if node.blank?
  if node.respond_to?(:each) # e.g. a list of nodes from a has_many association
    node.each { |n| visit_nodes(n) }
  else
    class_name = node.class.name
    @hash[class_name] ||= Set.new
    @hash[class_name] << visit(node)
    get_associations(node).each do |assoc|
      @logger.debug("Visiting #{assoc.name}")
      new_nodes = node.send(assoc.name)
      next if new_nodes.blank?

      if new_nodes.respond_to?(:find_each)
        new_nodes.find_each { |n| visit_nodes(n) }
      else
        visit_nodes(new_nodes)
      end
    end

  end
end

Protected Instance Methods

after_delete() click to toggle source

Callback that runs after deletion is finished.

# File lib/rails-archiver/archiver.rb, line 141
def after_delete
end
get_associations(node) click to toggle source

Indicate which associations to retrieve from the given model. @param node [ActiveRecord::Base]

# File lib/rails-archiver/archiver.rb, line 146
def get_associations(node)
  node.class.reflect_on_all_associations.select do |assoc|
    [:destroy, :delete_all].include?(assoc.options[:dependent]) &&
      [:has_many, :has_one].include?(assoc.macro)
  end
end

Private Instance Methods

_delete_records() click to toggle source

Delete the records corresponding to the model.

# File lib/rails-archiver/archiver.rb, line 156
def _delete_records
  @ids_to_delete.each do |table, ids|
    delete_from_table(table, ids)
  end
end
_get_transport(symbol_or_object) click to toggle source

@param symbol_or_object [Symbol|RailsArchiver::Transport::Base] @return [RailsArchiver::Transport::Base]

# File lib/rails-archiver/archiver.rb, line 164
def _get_transport(symbol_or_object)
  if symbol_or_object.is_a?(Symbol)
    klass = if symbol_or_object.present?
              "RailsArchiver::Transport::#{symbol_or_object.to_s.classify}".constantize
              else
                Transport::InMemory
            end
    klass.new(@model, @logger)
  else
    symbol_or_object
  end
end