replicate 1.0 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HACKING +61 -0
- data/README.md +108 -23
- data/Rakefile +66 -1
- data/bin/replicate +5 -1
- data/lib/replicate/active_record.rb +48 -5
- data/test/active_record_test.rb +102 -3
- data/test/dumper_test.rb +1 -0
- metadata +25 -10
data/HACKING
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Grab a local clone of rtomayko/replicate:
|
2
|
+
|
3
|
+
git clone git://github.com/rtomayko/replicate.git
|
4
|
+
cd replicate
|
5
|
+
|
6
|
+
The default rake task installs the latest supported activerecord
|
7
|
+
version to a project local GEM_HOME=./vendor and runs the unit
|
8
|
+
tests:
|
9
|
+
|
10
|
+
$ rake
|
11
|
+
installing activerecord-3.1.0 to ./vendor/1.8
|
12
|
+
installing sqlite3 to ./vendor/1.8
|
13
|
+
Using activerecord 3.1.0
|
14
|
+
Loaded suite ...
|
15
|
+
Started
|
16
|
+
....................
|
17
|
+
Finished in 0.150186 seconds.
|
18
|
+
|
19
|
+
20 tests, 106 assertions, 0 failures, 0 errors
|
20
|
+
|
21
|
+
Use `rake test:all' to run tests under all activerecord versions:
|
22
|
+
|
23
|
+
$ rake test:all
|
24
|
+
installing activerecord ~> 2.2.3 to ./vendor
|
25
|
+
installing activerecord ~> 2.3.14 to ./vendor
|
26
|
+
installing activerecord ~> 3.0.10 to ./vendor
|
27
|
+
installing activerecord ~> 3.1.0 to ./vendor
|
28
|
+
==> testing activerecord ~> 2.2.3
|
29
|
+
Started
|
30
|
+
....................
|
31
|
+
Finished in 0.119517 seconds.
|
32
|
+
|
33
|
+
20 tests, 106 assertions, 0 failures, 0 errors
|
34
|
+
==> testing activerecord ~> 2.3.14
|
35
|
+
Started
|
36
|
+
....................
|
37
|
+
Finished in 0.119517 seconds.
|
38
|
+
|
39
|
+
20 tests, 106 assertions, 0 failures, 0 errors
|
40
|
+
<snip>
|
41
|
+
|
42
|
+
rake test:all should always be passing under latest stable MRI
|
43
|
+
1.8.7 and MRI 1.9.x.
|
44
|
+
|
45
|
+
Running individual test files directly requires setting the
|
46
|
+
GEM_HOME environment variable and ensuring ./lib is on the load
|
47
|
+
path:
|
48
|
+
|
49
|
+
export GEM_HOME=vendor/1.9 # or 1.8.7
|
50
|
+
ruby -Ilib test/active_record_test.rb
|
51
|
+
|
52
|
+
You can also control which activerecord version is used in the
|
53
|
+
test with the AR_VERSION environment variable:
|
54
|
+
|
55
|
+
rake setup:all
|
56
|
+
export GEM_HOME=vendor/1.8.7
|
57
|
+
AR_VERSION=3.1.0 ruby -rubygems -Ilib test/active_record_test.rb
|
58
|
+
|
59
|
+
If you have something worth sharing, please send a pull request:
|
60
|
+
|
61
|
+
https://github.com/rtomayko/replicate
|
data/README.md
CHANGED
@@ -1,15 +1,20 @@
|
|
1
|
-
# Replicate
|
2
|
-
|
3
1
|
Dump and load relational objects between Ruby environments.
|
2
|
+
===========================================================
|
3
|
+
|
4
|
+
The project started at GitHub to simplify the process of getting real production
|
5
|
+
data into development and staging environments. We use it to replicate entire
|
6
|
+
repository data (including associated issue, pull request, commit comment, etc.
|
7
|
+
records) from production to our development environments with a single command.
|
8
|
+
It's excessively useful for troubleshooting issues, support requests, and
|
9
|
+
exception reports as well as for establishing real data for evaluating design
|
10
|
+
concepts.
|
4
11
|
|
5
|
-
|
6
|
-
|
7
|
-
uses the replicate machinery to dump entire repository data (including
|
8
|
-
associated objects like issues, pull requests, commit comments, etc.) from
|
9
|
-
production and load it into the current environment. This is excessively useful
|
10
|
-
for troubleshooting issues, support requests, and exception reports.
|
12
|
+
Synopsis
|
13
|
+
--------
|
11
14
|
|
12
|
-
|
15
|
+
Installing:
|
16
|
+
|
17
|
+
$ gem install replicate
|
13
18
|
|
14
19
|
Dumping objects:
|
15
20
|
|
@@ -32,16 +37,34 @@ Dumping and loading over SSH:
|
|
32
37
|
$ remote_command="replicate -r /app/config/environment -d 'User.find(1234)'"
|
33
38
|
$ ssh example.org "$remote_command" |replicate -r config/environment -l
|
34
39
|
|
35
|
-
|
40
|
+
ActiveRecord
|
41
|
+
------------
|
42
|
+
|
43
|
+
Basic support for dumping and loading ActiveRecord objects is included. The
|
44
|
+
tests pass under ActiveRecord versions 2.2.3, 2.3.14, 3.0.10, and 3.1.0 under
|
45
|
+
MRI 1.8.7 as well as under MRI 1.9.2.
|
46
|
+
|
47
|
+
To use customization macros in your models, require the replicate library after
|
48
|
+
ActiveRecord (in e.g., `config/initializers/libraries.rb`):
|
49
|
+
|
50
|
+
require 'active_record'
|
51
|
+
require 'replicate'
|
52
|
+
|
53
|
+
ActiveRecord support works sensibly without customization so this isn't strictly
|
54
|
+
necessary to use the `replicate` command. The following sections document the
|
55
|
+
available customization macros.
|
36
56
|
|
37
|
-
|
38
|
-
ActiveRecord 3.x is planned.*
|
57
|
+
### Association Dumping
|
39
58
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
`
|
59
|
+
The baked in support adds some more or less sensible default behavior for all
|
60
|
+
subclasses of `ActiveRecord::Base` such that dumping an object will bring in
|
61
|
+
objects related via `belongs_to` and `has_one` associations.
|
62
|
+
|
63
|
+
Unlike 1:1 associations, `has_many` and `has_and_belongs_to_many` associations
|
64
|
+
are not automatically included. Doing so would quickly lead to the entire
|
65
|
+
database being sucked in. It can be useful to mark specific associations for
|
66
|
+
automatic inclusion using the `replicate_associations` macro. For instance,
|
67
|
+
to always include `EmailAddress` records belonging to a `User`:
|
45
68
|
|
46
69
|
class User < ActiveRecord::Base
|
47
70
|
belongs_to :profile
|
@@ -50,10 +73,13 @@ followed and included in the dump. You can mark `has_many` and
|
|
50
73
|
replicate_associations :email_addresses
|
51
74
|
end
|
52
75
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
76
|
+
### Natural Keys
|
77
|
+
|
78
|
+
By default, the loader attempts to create a new record with a new primary key id
|
79
|
+
for all objects. This can lead to unique constraint errors when a record already
|
80
|
+
exists with matching attributes. To update existing records instead of
|
81
|
+
creating new ones, define a natural key for the model using the `replicate_natural_key`
|
82
|
+
macro:
|
57
83
|
|
58
84
|
class User < ActiveRecord::Base
|
59
85
|
belongs_to :profile
|
@@ -63,13 +89,45 @@ define a natural key for the model using the `replicate_natural_key` macro:
|
|
63
89
|
replicate_associations :email_addresses
|
64
90
|
end
|
65
91
|
|
66
|
-
|
92
|
+
class EmailAddress < ActiveRecord::Base
|
93
|
+
belongs_to :user
|
94
|
+
replicate_natural_key :user_id, :email
|
95
|
+
end
|
96
|
+
|
97
|
+
Multiple attribute names may be specified to define a compound key. Foreign key
|
98
|
+
column attributes (`user_id`) are often included in natural keys.
|
99
|
+
|
100
|
+
### Validations and Callbacks
|
101
|
+
|
102
|
+
__IMPORTANT:__ All ActiveRecord validations and callbacks are disabled on the
|
103
|
+
loading side. While replicate piggybacks on AR for relationship information and
|
104
|
+
uses `ActiveRecord::Base#save` to write objects to the database, it's designed
|
105
|
+
to act as a simple dump / load tool.
|
106
|
+
|
107
|
+
It's sometimes useful to run certain types of callbacks on replicate. For
|
108
|
+
instance, you might want to create files on disk or load information into a
|
109
|
+
separate data store any time an object enters the database. The best way to go
|
110
|
+
about this currently is to override the model's `load_replicant` class method:
|
111
|
+
|
112
|
+
class User < ActiveRecord::Base
|
113
|
+
def self.load_replicant(type, id, attrs)
|
114
|
+
id, object = super
|
115
|
+
object.register_in_redis
|
116
|
+
object.some_other_callback
|
117
|
+
[id, object]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
This interface will be improved in future versions.
|
67
122
|
|
68
|
-
|
123
|
+
Custom Objects
|
124
|
+
--------------
|
69
125
|
|
70
126
|
Other object types may be included in the dump stream so long as they implement
|
71
127
|
the `dump_replicant` and `load_replicant` methods.
|
72
128
|
|
129
|
+
### dump_replicant
|
130
|
+
|
73
131
|
The dump side calls `#dump_replicant(dumper)` on each object. The method must
|
74
132
|
call `dumper.write()` with the class name, id, and hash of primitively typed
|
75
133
|
attributes for the object:
|
@@ -84,6 +142,8 @@ attributes for the object:
|
|
84
142
|
end
|
85
143
|
end
|
86
144
|
|
145
|
+
### load_replicant
|
146
|
+
|
87
147
|
The load side calls `::load_replicant(type, id, attributes)` on the class to
|
88
148
|
load each object into the current environment. The method must return an
|
89
149
|
`[id, object]` tuple:
|
@@ -97,3 +157,28 @@ load each object into the current environment. The method must return an
|
|
97
157
|
[user.id, user]
|
98
158
|
end
|
99
159
|
end
|
160
|
+
|
161
|
+
How it works
|
162
|
+
------------
|
163
|
+
|
164
|
+
The dump format is designed for streaming relational data. Each object is
|
165
|
+
encoded as a `[type, id, attributes]` tuple and marshalled directly onto the
|
166
|
+
stream. The `type` (class name string) and `id` must form a distinct key when
|
167
|
+
combined, `attributes` must consist of only string keys and simply typed values.
|
168
|
+
|
169
|
+
Relationships between objects in the stream are managed as follows:
|
170
|
+
|
171
|
+
- An object's attributes may encode references to objects that precede it
|
172
|
+
in the stream using a simple tuple format: [:id, 'User', 1234].
|
173
|
+
|
174
|
+
- The dump side ensures that objects are written to the dump stream in
|
175
|
+
"reference order" such that when an object A includes a reference attribute
|
176
|
+
to an object B, B is guaranteed to arrive before A.
|
177
|
+
|
178
|
+
- The load side maintains a mapping of ids from the dumping system to the newly
|
179
|
+
replicated objects on the loading system. When the loader encounters a
|
180
|
+
reference value [:id, 'User', 1234] in an object's attributes, it converts it
|
181
|
+
to the load side id value.
|
182
|
+
|
183
|
+
Dumping and loading happens in a streaming fashion. There is no limit on the
|
184
|
+
number of objects included in the stream.
|
data/Rakefile
CHANGED
@@ -1,6 +1,71 @@
|
|
1
|
-
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'rake/clean'
|
3
|
+
task :default => [:setup, :test]
|
4
|
+
|
5
|
+
vendor_dir = './vendor'
|
6
|
+
ruby_version = RbConfig::CONFIG['ruby_version']
|
7
|
+
ENV['GEM_HOME'] = "#{vendor_dir}/#{ruby_version}"
|
8
|
+
|
9
|
+
desc "Install gem dependencies for development"
|
10
|
+
task :setup => 'setup:latest' do
|
11
|
+
verbose(false) { gem_install 'sqlite3' }
|
12
|
+
end
|
2
13
|
|
3
14
|
desc "Run tests"
|
4
15
|
task :test do
|
16
|
+
ENV['RUBYOPT'] = [ENV['RUBYOPT'], 'rubygems'].compact.join(' ')
|
17
|
+
ENV['RUBYLIB'] = ['lib', ENV['RUBYLIB']].compact.join(':')
|
5
18
|
sh "testrb test/*_test.rb", :verbose => false
|
6
19
|
end
|
20
|
+
CLEAN.include 'test/db'
|
21
|
+
|
22
|
+
desc "Build gem"
|
23
|
+
task :build do
|
24
|
+
sh "gem build replicate.gemspec"
|
25
|
+
end
|
26
|
+
|
27
|
+
# supported activerecord gem versions
|
28
|
+
AR_VERSIONS = []
|
29
|
+
AR_VERSIONS << '2.2.3' if RUBY_VERSION < '1.9'
|
30
|
+
AR_VERSIONS.concat %w[2.3.14 3.0.10 3.1.0]
|
31
|
+
|
32
|
+
desc "Run unit tests under all supported AR versions"
|
33
|
+
task 'test:all' => 'setup:all' do
|
34
|
+
failures = []
|
35
|
+
AR_VERSIONS.each do |vers|
|
36
|
+
warn "==> testing activerecord ~> #{vers}"
|
37
|
+
ENV['AR_VERSION'] = vers
|
38
|
+
ok = system("rake -s test")
|
39
|
+
failures << vers if !ok
|
40
|
+
warn ''
|
41
|
+
end
|
42
|
+
fail "activerecord version failures: #{failures.join(', ')}" if failures.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
# file tasks for installing each AR version
|
46
|
+
desc 'Install gem dependencies for all supported AR versions'
|
47
|
+
task 'setup:all' => 'setup'
|
48
|
+
AR_VERSIONS.each do |vers|
|
49
|
+
version_file = "#{ENV['GEM_HOME']}/versions/activerecord-#{vers}"
|
50
|
+
file version_file do |f|
|
51
|
+
verbose(false) { gem_install 'activerecord', vers }
|
52
|
+
end
|
53
|
+
task "setup:#{vers}" => version_file
|
54
|
+
task "setup:all" => "setup:#{vers}"
|
55
|
+
end
|
56
|
+
task "setup:latest" => "setup:#{AR_VERSIONS.last}"
|
57
|
+
CLEAN.include 'vendor'
|
58
|
+
|
59
|
+
# Install a gem to the local GEM_HOME but only if it isn't already installed
|
60
|
+
def gem_install(name, version = nil)
|
61
|
+
version_name = [name, version].compact.join('-')
|
62
|
+
version_file = "#{ENV['GEM_HOME']}/versions/#{version_name}"
|
63
|
+
return if File.exist?(version_file)
|
64
|
+
warn "installing #{version_name} to #{ENV['GEM_HOME']}"
|
65
|
+
command = "gem install --no-rdoc --no-ri #{name}"
|
66
|
+
command += " -v '~> #{version}'" if version
|
67
|
+
command += " >/dev/null"
|
68
|
+
sh command
|
69
|
+
mkdir_p File.dirname(version_file)
|
70
|
+
File.open(version_file, 'wb') { }
|
71
|
+
end
|
data/bin/replicate
CHANGED
@@ -10,6 +10,7 @@
|
|
10
10
|
#/ -r, --require Require the library. Often used with 'config/environment'.
|
11
11
|
#/ -d, --dump Dump the repository and all related objects to stdout.
|
12
12
|
#/ -l, --load Load dump file data from stdin.
|
13
|
+
#/ -i, --keep-id Use replicated ids when loading dump file.
|
13
14
|
#/
|
14
15
|
#/ -v, --verbose Write more status output.
|
15
16
|
#/ -q, --quiet Write less status output.
|
@@ -20,6 +21,7 @@ require 'optparse'
|
|
20
21
|
mode = nil
|
21
22
|
verbose = false
|
22
23
|
quiet = false
|
24
|
+
keep_id = false
|
23
25
|
out = $stdout
|
24
26
|
|
25
27
|
# parse arguments
|
@@ -31,6 +33,7 @@ ARGV.options do |opts|
|
|
31
33
|
opts.on("-r", "--require=f") { |file| require file }
|
32
34
|
opts.on("-v", "--verbose") { verbose = true }
|
33
35
|
opts.on("-q", "--quiet") { quiet = true }
|
36
|
+
opts.on("-i", "--keep-id") { keep_id = true }
|
34
37
|
opts.on_tail("-h", "--help", &usage)
|
35
38
|
opts.parse!
|
36
39
|
end
|
@@ -38,8 +41,9 @@ end
|
|
38
41
|
# load rails environment and replicator lib.
|
39
42
|
require 'replicate'
|
40
43
|
|
41
|
-
# hack to enable AR query cache
|
42
44
|
if defined?(ActiveRecord::Base)
|
45
|
+
ActiveRecord::Base.replicate_id = keep_id
|
46
|
+
# hack to enable AR query cache
|
43
47
|
ActiveRecord::ConnectionAdapters::QueryCache.
|
44
48
|
send :attr_writer, :query_cache, :query_cache_enabled
|
45
49
|
ActiveRecord::Base.connection.send(:query_cache=, {})
|
@@ -126,6 +126,18 @@ module Replicate
|
|
126
126
|
@replicate_natural_key = attribute_names
|
127
127
|
end
|
128
128
|
|
129
|
+
# Set or retrieve whether replicated object should keep its original id.
|
130
|
+
# When not set, replicated objects will be created with new id.
|
131
|
+
def replicate_id(boolean=nil)
|
132
|
+
self.replicate_id = boolean unless boolean.nil?
|
133
|
+
@replicate_id.nil? ? superclass.replicate_id : @replicate_id
|
134
|
+
end
|
135
|
+
|
136
|
+
# Set flag for replicating original id.
|
137
|
+
def replicate_id=(boolean)
|
138
|
+
@replicate_id = boolean
|
139
|
+
end
|
140
|
+
|
129
141
|
# Load an individual record into the database. If the models defines a
|
130
142
|
# replicate_natural_key then an existing record will be updated if found
|
131
143
|
# instead of a new record being created.
|
@@ -156,17 +168,47 @@ module Replicate
|
|
156
168
|
|
157
169
|
# Update an AR object's attributes and persist to the database without
|
158
170
|
# running validations or callbacks.
|
171
|
+
#
|
172
|
+
# Returns the [id, object] tuple for the newly replicated objected.
|
159
173
|
def create_or_update_replicant(instance, attributes)
|
160
|
-
|
161
|
-
|
174
|
+
# write replicated attributes to the instance
|
162
175
|
attributes.each do |key, value|
|
163
|
-
next if key == primary_key
|
164
|
-
instance.write_attribute key, value
|
176
|
+
next if key == primary_key and not replicate_id
|
177
|
+
instance.send :write_attribute, key, value
|
178
|
+
end
|
179
|
+
|
180
|
+
# save the instance bypassing all callbacks and validations
|
181
|
+
replicate_disable_callbacks instance
|
182
|
+
if ::ActiveRecord::VERSION::MAJOR >= 3
|
183
|
+
instance.save :validate => false
|
184
|
+
else
|
185
|
+
instance.save false
|
165
186
|
end
|
166
187
|
|
167
|
-
instance.save false
|
168
188
|
[instance.id, instance]
|
169
189
|
end
|
190
|
+
|
191
|
+
# Disable all callbacks on an ActiveRecord::Base instance. Only the
|
192
|
+
# instance is effected. There is no way to re-enable callbacks once
|
193
|
+
# they've been disabled on an object.
|
194
|
+
def replicate_disable_callbacks(instance)
|
195
|
+
if ::ActiveRecord::VERSION::MAJOR >= 3
|
196
|
+
# AR 3.1.x
|
197
|
+
def instance.run_callbacks(*args); yield; end
|
198
|
+
|
199
|
+
# AR 3.0.x
|
200
|
+
def instance._run_save_callbacks(*args); yield; end
|
201
|
+
def instance._run_create_callbacks(*args); yield; end
|
202
|
+
def instance._run_update_callbacks(*args); yield; end
|
203
|
+
else
|
204
|
+
# AR 2.x
|
205
|
+
def instance.callback(*args)
|
206
|
+
end
|
207
|
+
def instance.record_timestamps
|
208
|
+
false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
170
212
|
end
|
171
213
|
|
172
214
|
# Special object used to dump the list of associated ids for a
|
@@ -213,5 +255,6 @@ module Replicate
|
|
213
255
|
::ActiveRecord::Base.send :extend, ClassMethods
|
214
256
|
::ActiveRecord::Base.replicate_associations = []
|
215
257
|
::ActiveRecord::Base.replicate_natural_key = []
|
258
|
+
::ActiveRecord::Base.replicate_id = false
|
216
259
|
end
|
217
260
|
end
|
data/test/active_record_test.rb
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'stringio'
|
3
|
+
|
4
|
+
# require a specific AR version.
|
5
|
+
version = ENV['AR_VERSION']
|
6
|
+
gem 'activerecord', "~> #{version}" if version
|
3
7
|
require 'active_record'
|
8
|
+
require 'active_record/version'
|
9
|
+
warn "Using activerecord #{ActiveRecord::VERSION::STRING}"
|
10
|
+
|
11
|
+
# replicate must be loaded after AR
|
4
12
|
require 'replicate'
|
5
13
|
|
14
|
+
# create the sqlite db on disk
|
6
15
|
dbfile = File.expand_path('../db', __FILE__)
|
7
16
|
File.unlink dbfile if File.exist?(dbfile)
|
8
17
|
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => dbfile)
|
9
18
|
|
19
|
+
# load schema
|
10
20
|
ActiveRecord::Migration.verbose = false
|
11
21
|
ActiveRecord::Schema.define do
|
12
22
|
create_table "users", :force => true do |t|
|
@@ -28,25 +38,25 @@ ActiveRecord::Schema.define do
|
|
28
38
|
end
|
29
39
|
end
|
30
40
|
|
41
|
+
# models
|
31
42
|
class User < ActiveRecord::Base
|
32
43
|
has_one :profile, :dependent => :destroy
|
33
44
|
has_many :emails, :dependent => :destroy, :order => 'id'
|
34
|
-
|
35
45
|
replicate_natural_key :login
|
36
46
|
end
|
37
47
|
|
38
48
|
class Profile < ActiveRecord::Base
|
39
49
|
belongs_to :user
|
40
|
-
|
41
50
|
replicate_natural_key :user_id
|
42
51
|
end
|
43
52
|
|
44
53
|
class Email < ActiveRecord::Base
|
45
54
|
belongs_to :user
|
46
|
-
|
47
55
|
replicate_natural_key :user_id, :email
|
48
56
|
end
|
49
57
|
|
58
|
+
# The test case loads some fixture data once and uses transaction rollback to
|
59
|
+
# reset fixture state for each test's setup.
|
50
60
|
class ActiveRecordTest < Test::Unit::TestCase
|
51
61
|
def setup
|
52
62
|
self.class.fixtures
|
@@ -250,4 +260,93 @@ class ActiveRecordTest < Test::Unit::TestCase
|
|
250
260
|
assert !user.emails.empty?, "#{login} has no emails" if login != 'tmm1'
|
251
261
|
end
|
252
262
|
end
|
263
|
+
|
264
|
+
def test_loading_with_replicating_id
|
265
|
+
objects = []
|
266
|
+
@dumper.listen do |type, id, attrs, obj|
|
267
|
+
objects << [type, id, attrs, obj] if type == 'User'
|
268
|
+
end
|
269
|
+
|
270
|
+
dumped_users = {}
|
271
|
+
%w[rtomayko kneath tmm1].each do |login|
|
272
|
+
user = User.find_by_login(login)
|
273
|
+
@dumper.dump user
|
274
|
+
dumped_users[login] = user
|
275
|
+
end
|
276
|
+
assert_equal 3, objects.size
|
277
|
+
|
278
|
+
User.destroy_all
|
279
|
+
User.replicate_id = false
|
280
|
+
|
281
|
+
# load everything back up
|
282
|
+
objects.each { |type, id, attrs, obj| User.load_replicant type, id, attrs }
|
283
|
+
|
284
|
+
user = User.find_by_login('rtomayko')
|
285
|
+
assert_not_equal dumped_users['rtomayko'].id, user.id
|
286
|
+
|
287
|
+
User.destroy_all
|
288
|
+
User.replicate_id = true
|
289
|
+
|
290
|
+
# load everything back up
|
291
|
+
objects.each { |type, id, attrs, obj| User.load_replicant type, id, attrs }
|
292
|
+
|
293
|
+
user = User.find_by_login('rtomayko')
|
294
|
+
assert_equal dumped_users['rtomayko'].id, user.id
|
295
|
+
end
|
296
|
+
|
297
|
+
def test_loader_saves_without_validations
|
298
|
+
# note when a record is saved with validations
|
299
|
+
ran_validations = false
|
300
|
+
User.class_eval { validate { ran_validations = true } }
|
301
|
+
|
302
|
+
# check our assumptions
|
303
|
+
user = User.create(:login => 'defunkt')
|
304
|
+
assert ran_validations, "should run validations here"
|
305
|
+
ran_validations = false
|
306
|
+
|
307
|
+
# load one and verify validations are not run
|
308
|
+
user = nil
|
309
|
+
@loader.listen { |type, id, attrs, obj| user = obj }
|
310
|
+
@loader.feed 'User', 1, 'login' => 'rtomayko'
|
311
|
+
assert_not_nil user
|
312
|
+
assert !ran_validations, 'validations should not run on save'
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_loader_saves_without_callbacks
|
316
|
+
# note when a record is saved with callbacks
|
317
|
+
callbacks = false
|
318
|
+
User.class_eval { after_save { callbacks = true } }
|
319
|
+
|
320
|
+
# check our assumptions
|
321
|
+
user = User.create(:login => 'defunkt')
|
322
|
+
assert callbacks, "should run callbacks here"
|
323
|
+
callbacks = false
|
324
|
+
|
325
|
+
# load one and verify validations are not run
|
326
|
+
user = nil
|
327
|
+
@loader.listen { |type, id, attrs, obj| user = obj }
|
328
|
+
@loader.feed 'User', 1, 'login' => 'rtomayko'
|
329
|
+
assert_not_nil user
|
330
|
+
assert !callbacks, 'callbacks should not run on save'
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_loader_saves_without_updating_created_at_timestamp
|
334
|
+
timestamp = Time.at((Time.now - (24 * 60 * 60)).to_i)
|
335
|
+
user = nil
|
336
|
+
@loader.listen { |type, id, attrs, obj| user = obj }
|
337
|
+
@loader.feed 'User', 23, 'login' => 'brianmario', 'created_at' => timestamp
|
338
|
+
assert_equal timestamp, user.created_at
|
339
|
+
user = User.find(user.id)
|
340
|
+
assert_equal timestamp, user.created_at
|
341
|
+
end
|
342
|
+
|
343
|
+
def test_loader_saves_without_updating_updated_at_timestamp
|
344
|
+
timestamp = Time.at((Time.now - (24 * 60 * 60)).to_i)
|
345
|
+
user = nil
|
346
|
+
@loader.listen { |type, id, attrs, obj| user = obj }
|
347
|
+
@loader.feed 'User', 29, 'login' => 'rtomayko', 'updated_at' => timestamp
|
348
|
+
assert_equal timestamp, user.updated_at
|
349
|
+
user = User.find(user.id)
|
350
|
+
assert_equal timestamp, user.updated_at
|
351
|
+
end
|
253
352
|
end
|
data/test/dumper_test.rb
CHANGED
@@ -46,6 +46,7 @@ class DumperTest < Test::Unit::TestCase
|
|
46
46
|
|
47
47
|
def test_writing_to_io
|
48
48
|
io = StringIO.new
|
49
|
+
io.set_encoding 'BINARY' if io.respond_to?(:set_encoding)
|
49
50
|
@dumper.marshal_to io
|
50
51
|
@dumper.dump object = thing
|
51
52
|
data = Marshal.dump(['Replicate::Object', object.id, object.attributes])
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: replicate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: "1.
|
8
|
+
- 1
|
9
|
+
version: "1.1"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ryan Tomayko
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-09-
|
17
|
+
date: 2011-09-09 00:00:00 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activerecord
|
@@ -24,15 +24,29 @@ dependencies:
|
|
24
24
|
requirements:
|
25
25
|
- - ~>
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
hash:
|
27
|
+
hash: 5
|
28
28
|
segments:
|
29
|
-
-
|
30
|
-
-
|
31
|
-
version: "
|
29
|
+
- 3
|
30
|
+
- 1
|
31
|
+
version: "3.1"
|
32
32
|
type: :development
|
33
33
|
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: sqlite3
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
34
48
|
description: Dump and load relational objects between Ruby environments.
|
35
|
-
email:
|
49
|
+
email: ryan@github.com
|
36
50
|
executables:
|
37
51
|
- replicate
|
38
52
|
extensions: []
|
@@ -41,6 +55,7 @@ extra_rdoc_files: []
|
|
41
55
|
|
42
56
|
files:
|
43
57
|
- COPYING
|
58
|
+
- HACKING
|
44
59
|
- README.md
|
45
60
|
- Rakefile
|
46
61
|
- bin/replicate
|
@@ -55,7 +70,7 @@ files:
|
|
55
70
|
- test/dumper_test.rb
|
56
71
|
- test/loader_test.rb
|
57
72
|
- test/replicate_test.rb
|
58
|
-
homepage: http://github.com/rtomayko/replicate
|
73
|
+
homepage: http://github.com/rtomayko/replicate
|
59
74
|
licenses: []
|
60
75
|
|
61
76
|
post_install_message:
|