Class: ActiveRecordArchiver

Inherits:
Object
  • Object
show all
Defined in:
lib/activerecord-archiver/export.rb,
lib/activerecord-archiver/import.rb,
lib/activerecord-archiver/archiver.rb

Overview

method ActiveRecordArchiver::import

Arguments:

A string of JSON generated by ActiveRecordArchiver::export

Returns:

true on success, raises an Exception and rolls back the db on failure

Effects:

Attempts to create and save a new record for each item in the input JSON structure and
connects the specified belongs_to relations

Class Method Summary collapse

Class Method Details

.assert_instance_of(instance, klass) ⇒ Object



68
69
70
71
72
# File 'lib/activerecord-archiver/archiver.rb', line 68

def self.assert_instance_of instance, klass
  unless instance.class <= klass
    raise "Object #{instance} is not an instance of the #{klass} model"
  end
end

.assert_model(model) ⇒ Object



74
75
76
77
78
# File 'lib/activerecord-archiver/archiver.rb', line 74

def self.assert_model model
  unless model < ActiveRecord::Base
    raise "#{model} is not an activerecord model"
  end
end

.belongs_to?(model, attribute) ⇒ Boolean

Returns:

  • (Boolean)


14
15
16
# File 'lib/activerecord-archiver/archiver.rb', line 14

def self.belongs_to? model, attribute
  !!relation(model, attribute).try(:belongs_to?)
end

.cols_for_model(model, all_models, options = {}) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/activerecord-archiver/archiver.rb', line 99

def self.cols_for_model model, all_models, options={}
  model.columns.map do |col|
    if (col.primary or
        parse_array_option(options, :exclude).include? col.name)
      # omit primary keys and excluded columns
      nil
    elsif (relation = relation_from_key(model, col.name)) and relation.belongs_to?
      # include belongs_to relations to included models
      if all_models.include? relation.klass
        relation.name
      else nil end
    else
      # warn before adding an attribute ending in '_id'
      warn id_warning(model, col) if col.name =~ /_id$/
      col.name
    end
  end.compact
end

.column(model, attribute) ⇒ Object



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

def self.column model, attribute
  model.columns_hash.with_indifferent_access[attribute]
end

.column_required(model, attribute) ⇒ Object



22
23
24
# File 'lib/activerecord-archiver/archiver.rb', line 22

def self.column_required model, attribute
  column(model, attribute).null == false
end

.export(*args) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/activerecord-archiver/export.rb', line 39

def self.export *args
  
  @models_hash = models_hash_from_args(args)
  
  result = {}
  
  # serialize
  @models_hash.each_pair do |model, pair|
    records, attributes = pair
    
    result[model.to_s] = []
    records.each do |record|
      
      assert_instance_of record, model
      
      rec = {}
      attributes.each do |attribute|
        attribute, placeholder = if attribute.is_a? Array
                                 then attribute
                                 else [attribute, nil] end
        
        if has_attribute? model, attribute
          # store attribute
          rec[attribute] = if placeholder.nil?
                           then record.send attribute
                           else placeholder end
        elsif belongs_to?(model, attribute)
          # store relation
          if (index = relation_index(record, attribute))
            rec[attribute] = index
            if placeholder
              rec[relation_foreign_key(model, attribute)] = placeholder
            end
          end
        else
          raise "#{attribute} is not an attribute or belongs_to relation of #{model}"
        end
      end
      result[model.to_s] << rec
    end
  end
  
  # encode
  JSON.dump result
end

.has_attribute?(model, attribute) ⇒ Boolean

Returns:

  • (Boolean)


2
3
4
# File 'lib/activerecord-archiver/archiver.rb', line 2

def self.has_attribute? model, attribute
  model.columns_hash.with_indifferent_access.has_key? attribute
end

.id_warning(model, col) ⇒ Object



93
94
95
96
97
# File 'lib/activerecord-archiver/archiver.rb', line 93

def self.id_warning model, col
  "Warning: #{model}##{col.name} included in export." +
    "  To exclude it use ActiveRecordArchiver.export(#{model.name.downcase.pluralize} => " +
    "{:exclude => [:#{col.name}]})"
end

.import(json) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/activerecord-archiver/import.rb', line 18

def self.import json
  
  @data = JSON.parse json
  
  ActiveRecord::Base.transaction do
    
    # insert records
    @data.each_pair do |model_name, records|
      model = model_name.constantize
      
      assert_model model
      
      records.each do |record|
        record[:id] = model.all.insert(insertable_hash(model, record))
      end
    end
    
    # add relations
    @data.each_pair do |model_name, records|
      model = model_name.constantize
      
      records.each do |record|
        if (update_hash = relations_update_hash(model, record))
          instance = model.find(record[:id])
          instance.update update_hash
        end
      end
    end
    
  end
  
  true
end

.insertable_hash(model, hash) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/activerecord-archiver/archiver.rb', line 40

def self.insertable_hash model, hash
  ret = {}
  hash.each_pair do |key, value|
    if column(model, key)
      ret[column(model, key)] = value
    elsif belongs_to?(model, key)
      foreign_key = relation_foreign_key(model, key)
      if !hash.include?(foreign_key) and column_required(model, foreign_key)
        # if the foreign key is required connect it to the first available record temporarily
        ret[column(model, foreign_key)] = relation_model(model, key).first.try(:id) || 0
      end
    else
      raise "#{key} is not an attribute or belongs_to relation of #{model}"
    end
  end
  ret
end

.models_hash_from_args(args) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/activerecord-archiver/export.rb', line 85

def self.models_hash_from_args args
  options = args.extract_options!
  args.each do |arg|
    options[arg] = :all
  end
  
  options.keys.each do |collection|
    if collection.is_a? ActiveRecord::Base
      options[[collection]] = options[collection]
      options.delete(collection)
    end
  end
  
  models = options.each_pair.map do |collection, _|
    collection.first.class
  end
  
  models_hash = {}
  
  options.each_pair do |collection, cols|
    models_hash[collection.first.class] =
      [collection,
       if cols.is_a? Array then cols
       elsif cols == :all or cols.is_a? Hash
         cols_for_model(collection.first.class, models, cols)
       else
         raise "unknown column specification: #{cols}"
       end]
  end
  
  models_hash
end

.parse_array_option(options, key) ⇒ Object



84
85
86
87
88
89
90
91
# File 'lib/activerecord-archiver/archiver.rb', line 84

def self.parse_array_option options, key
  if options.is_a? Hash
    case options[key]
    when Array then options[key].map(&:to_s)
    when nil then []
    else [options[key].to_s] end
  else [] end
end

.relation(model, attribute) ⇒ Object



6
7
8
# File 'lib/activerecord-archiver/archiver.rb', line 6

def self.relation model, attribute
  model.reflections.with_indifferent_access[attribute]
end

.relation_foreign_key(model, key) ⇒ Object



36
37
38
# File 'lib/activerecord-archiver/archiver.rb', line 36

def self.relation_foreign_key model, key
  relation(model, key).foreign_key
end

.relation_from_key(model, attribute) ⇒ Object



80
81
82
# File 'lib/activerecord-archiver/archiver.rb', line 80

def self.relation_from_key model, attribute
  model.reflections.values.detect{|ref| ref.foreign_key == attribute}
end

.relation_id(model, key, value) ⇒ Object



31
32
33
34
# File 'lib/activerecord-archiver/archiver.rb', line 31

def self.relation_id model, key, value
  relation_model_name = relation(model, key).class_name
  @data[relation_model_name][value][:id]
end

.relation_index(record, attribute) ⇒ Object



26
27
28
29
# File 'lib/activerecord-archiver/archiver.rb', line 26

def self.relation_index record, attribute
  relevant_records = @models_hash[relation_model(record.class, attribute)].first
  relevant_records.present? && relevant_records.map(&:id).index(record.send(attribute).id)
end

.relation_model(model, attribute) ⇒ Object



10
11
12
# File 'lib/activerecord-archiver/archiver.rb', line 10

def self.relation_model model, attribute
  relation(model, attribute).class_name.constantize
end

.relations_update_hash(model, record) ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/activerecord-archiver/archiver.rb', line 58

def self.relations_update_hash(model, record)
  ret = {}
  record.each_pair do |key, value|
    if belongs_to?(model, key)
      ret[relation_foreign_key(model, key)] = relation_id(model, key, value)
    end
  end
  ret.presence
end