Class: RailsArchiver::Archiver

Inherits:
Object
  • Object
show all
Defined in:
lib/rails-archiver/archiver.rb

Defined Under Namespace

Classes: IDHash

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, options = {}) ⇒ Archiver

Create a new Archiver with the given model.

Parameters:

  • model (ActiveRecord::Base)

    the model to archive or unarchive.

  • options (Hash) (defaults to: {})
    • logger [Logger]

    • transport [Sybmol] :in_memory or :s3 right now

    • delete_records [Boolean] whether or not we should delete existing records



27
28
29
30
31
32
33
34
35
# 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

Instance Attribute Details

#archive_locationObject

Returns the value of attribute archive_location.



18
19
20
# File 'lib/rails-archiver/archiver.rb', line 18

def archive_location
  @archive_location
end

#transportObject

Returns the value of attribute transport.



18
19
20
# File 'lib/rails-archiver/archiver.rb', line 18

def transport
  @transport
end

Instance Method Details

#archiveHash

Archive a model.

Returns:

  • (Hash)

    the hash that was archived.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 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) ⇒ Object

Delete rows from a table. Can be used in #delete_records.

Parameters:

  • table (String)

    the table name.

  • ids (Array<Integer>)

    the IDs to delete.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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_dataObject

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.



60
61
# File 'lib/rails-archiver/archiver.rb', line 60

def save_additional_data
end

#unarchiverRailsArchiver::Unarchiver



132
133
134
135
136
# 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) ⇒ Hash

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.

Parameters:

  • node (ActiveRecord::Base)

    an object that inherits from AR::Base

Returns:

  • (Hash)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 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) ⇒ Object

Used to visit an association, and recursively calls down to all child objects through all other allowed associations.

Parameters:

  • node (ActiveRecord::Base|Array<ActiveRecord::Base>)

    any object(s) that inherits from ActiveRecord::Base



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 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