replicate 1.0 → 1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|