Class: ARDatabaseDuplicator

Inherits:
Object
  • Object
show all
Extended by:
SingleForwardable
Defined in:
lib/ar_database_duplicator.rb

Defined Under Namespace

Classes: CapturedSchema, TableSchema

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ ARDatabaseDuplicator

Returns a new instance of ARDatabaseDuplicator.



125
126
127
128
129
130
131
132
# File 'lib/ar_database_duplicator.rb', line 125

def initialize(options={})
  @source = options[:source] || 'development'
  @destination = options[:destination] || 'dev_data'
  @schema_file = options[:schema_file] || 'db/schema.rb'
  @force = options.fetch(:force) { false }
  @test = options.fetch(:test) { false }
  @split_data = options.fetch(:split_data) { true }
end

Instance Attribute Details

#destinationObject

Returns the value of attribute destination.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def destination
  @destination
end

#forceObject

Returns the value of attribute force.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def force
  @force
end

#schema_fileObject

Returns the value of attribute schema_file.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def schema_file
  @schema_file
end

#silentObject

Returns the value of attribute silent.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def silent
  @silent
end

#sourceObject

Returns the value of attribute source.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def source
  @source
end

#split_dataObject

Returns the value of attribute split_data.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def split_data
  @split_data
end

#testObject

Returns the value of attribute test.



123
124
125
# File 'lib/ar_database_duplicator.rb', line 123

def test
  @test
end

Class Method Details

.instance(options = {}) ⇒ Object



257
258
259
260
261
262
263
264
265
# File 'lib/ar_database_duplicator.rb', line 257

def self.instance(options={})
  options[:source] ||= 'development'
  options[:destination] ||= 'dev_data'
  options[:schema] ||= 'db/schema.rb'
  options[:force] = false unless options.has_key?(:force)
  options[:test] = true unless options.has_key?(:test)
  options[:split_data] = true unless options.has_key?(:split_data)
  @duplicator ||= new(options)
end

.reset!Object



267
268
269
# File 'lib/ar_database_duplicator.rb', line 267

def self.reset!
  @duplicator = nil
end

Instance Method Details

#define_class(name) ⇒ Object



167
168
169
170
171
# File 'lib/ar_database_duplicator.rb', line 167

def define_class(name)
  name = name.camelize.to_sym
  Object.const_set(name, Class.new(ActiveRecord::Base)) unless Object.const_defined?(name)
  Object.const_get(name)
end

#duplicate(klass, replacements = {}, *additional_replacements, &block) ⇒ Object

Duplicate each record, via ActiveRecord, from the source to the destination database. Field replacements can be given via a hash in the form of :original_field => :pseudo_person_field If a block is passed, the record will be passed for inspection/alteration before it is saved into the destination database.

Raises:

  • (ArgumentError)


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/ar_database_duplicator.rb', line 177

def duplicate(klass, replacements={}, *additional_replacements, &block)
  klass = define_class(klass) unless klass.is_a?(Class)

  plural = plural(klass)

  automatic_replacements = [replacements] + additional_replacements
  raise(ArgumentError, "Each group of replacements must be given as a Hash") unless automatic_replacements.all? { |x| x.is_a?(Hash) }

  sti_klasses = []
  set_temporary_vetted_attributes(klass, automatic_replacements)

  # If we aren't guaranteed to fail on vetting
  if block_given? || !block_required?(klass)
    # If we have potential duplication to do
    if force || !already_duplicated?(klass)
      # Connect to the source database
      with_source do
        # Grab a quick count to see if there is anything we need to do.
        estimated_total = klass.count
        if estimated_total > 0
          inform(test ? "Extracting first 1,000 #{plural} for testing" : "Extracting all #{plural}")
          # Pull in all records. Perhaps later we can enhance this to do it in batches.
          unless singleton?(klass)
            records = test ? klass.find(:all, :limit => 1000) : klass.find(:all)
          else
            records = [klass.instance]
          end

          # Handle any single table inheritance that may have shown up
          records.map(&:class).uniq.each { |k| sti_klasses << k if k != klass }
          sti_klasses.each { |k| set_temporary_vetted_attributes(k, automatic_replacements) }

          # Record the size so we can give some progress indication.
          inform "#{records.size} #{plural} read"

          transfer(klass, records, automatic_replacements, &block)
        else
          inform "Skipping #{plural}. No records exist."
        end
      end
    else
      inform "Skipping #{plural}. Records already exist."
    end
  else
    inform "Skipping #{plural}. The following field(s) were not checked: #{klass.unvetted_attributes.join(', ')}"
  end

  # Clean things up for the next bit of code that might use this class.
  klass.clear_temporary_safe_attributes
  sti_klasses.each { |k| k.clear_temporary_safe_attributes }
end

#load_duplication(klass) ⇒ Object

Raises:

  • (ArgumentError)


153
154
155
156
157
158
159
# File 'lib/ar_database_duplicator.rb', line 153

def load_duplication(klass)
  raise ArgumentError, "Production must be duplicated, not loaded from." if source.downcase == "production"
  klass = define_class(klass) unless klass.is_a?(Class)
  records = with_source(klass) { klass.all }
  puts "#{records.size} #{plural(klass)} read."
  klass.without_field_vetting { transfer(klass, records) }
end

#load_schemaObject



161
162
163
164
165
# File 'lib/ar_database_duplicator.rb', line 161

def load_schema
  # Adding this class just so we can check if a schema has already been loaded
  Object.const_set(:SchemaMigration, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:SchemaMigration)
  split_data ? load_schema_split : load_schema_combined
end

#use_destination(subname = nil) ⇒ Object



138
139
140
# File 'lib/ar_database_duplicator.rb', line 138

def use_destination(subname=nil)
  use_connection destination, subname
end

#use_source(subname = nil) ⇒ Object



134
135
136
# File 'lib/ar_database_duplicator.rb', line 134

def use_source(subname=nil)
  use_connection source, subname
end

#while_not_silent(&block) ⇒ Object



233
234
235
# File 'lib/ar_database_duplicator.rb', line 233

def while_not_silent(&block)
  with_silence_at(false, &block)
end

#while_silent(&block) ⇒ Object



229
230
231
# File 'lib/ar_database_duplicator.rb', line 229

def while_silent(&block)
  with_silence_at(true, &block)
end

#with_connection(name, subname = nil, silent_change = false, &block) ⇒ Object

With a specified connection, connect, execute a block, then restore the connection to it’s previous state (if any).



246
247
248
249
250
251
252
253
254
255
# File 'lib/ar_database_duplicator.rb', line 246

def with_connection(name, subname=nil, silent_change=false, &block)
  old_connection = connection
  begin
    use_connection(name, subname, silent_change)
    result = yield
  ensure
    use_spec(old_connection)
  end
  result
end

#with_destination(subname = nil, silent_change = false, &block) ⇒ Object



241
242
243
# File 'lib/ar_database_duplicator.rb', line 241

def with_destination(subname=nil, silent_change=false, &block)
  with_connection(destination, subname, silent_change, &block)
end

#with_source(subname = nil, silent_change = false, &block) ⇒ Object



237
238
239
# File 'lib/ar_database_duplicator.rb', line 237

def with_source(subname=nil, silent_change=false, &block)
  with_connection(source, subname, silent_change, &block)
end