mongar 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +18 -21
- data/VERSION +1 -1
- data/lib/mongar.rb +18 -8
- data/lib/mongar/mongo.rb +4 -0
- data/lib/mongar/mongo/collection.rb +43 -1
- data/lib/mongar/replica.rb +14 -2
- data/mongar.gemspec +2 -2
- data/spec/fixtures/full_configure.rb +3 -6
- data/spec/mongar/mongo/collection_spec.rb +46 -0
- data/spec/mongar/mongo_spec.rb +10 -0
- data/spec/mongar/replica_spec.rb +37 -0
- data/spec/mongar_spec.rb +29 -10
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -63,44 +63,41 @@ Given an ActiveRecord model call Domain with two attributes name and client_id:
|
|
63
63
|
# plans to use a configurable logger.
|
64
64
|
# Valid log levels are :info and :debug
|
65
65
|
log_level :debug
|
66
|
-
|
66
|
+
|
67
67
|
mongo :default do
|
68
68
|
database 'mydb'
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
mongo :otherdb do
|
72
72
|
database 'myotherdb'
|
73
73
|
user 'mongouser'
|
74
74
|
password 'password'
|
75
75
|
host '127.0.0.1'
|
76
76
|
port 27017
|
77
|
-
|
77
|
+
|
78
78
|
# By default Mongar uses the 'statuses' collection to
|
79
79
|
# keep track of the last replicated times, you can specify
|
80
80
|
# a custom collection name
|
81
81
|
status_collection :stati
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
# Minimal replica config to replicate the Domain model
|
85
85
|
# to the 'domains' mongo collection on the mongo db defined
|
86
86
|
# in the :default config above
|
87
|
-
|
87
|
+
|
88
88
|
replicate Domain do
|
89
89
|
column :name do
|
90
90
|
primary_index
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
column :client_id
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
# Customized replica config to replicate the Client
|
97
97
|
# model to the 'customers' mongo collection on the db
|
98
|
-
# defined in the :otherdb config above
|
99
|
-
|
100
|
-
replicate Client => '
|
101
|
-
# Use the other mongo database
|
102
|
-
use_mongodb :otherdb
|
103
|
-
|
98
|
+
# defined in the :otherdb config above as well as the
|
99
|
+
# default database
|
100
|
+
replicate Client => [{ :otherdb => 'clients' }, 'clients'] do
|
104
101
|
# By default, Mongar will try to determine the time on the
|
105
102
|
# backend database. The supported ActiveRecord database adapters
|
106
103
|
# are MysqlAdapter, Mysql2Adapter, and SQLServerAdapter.
|
@@ -110,42 +107,42 @@ Given an ActiveRecord model call Domain with two attributes name and client_id:
|
|
110
107
|
# this will run Client#get_db_time
|
111
108
|
get_db_time
|
112
109
|
end
|
113
|
-
|
110
|
+
|
114
111
|
# Items are never deleted
|
115
112
|
no_deleted_finder
|
116
|
-
|
113
|
+
|
117
114
|
# Customize your updated record finder. The block is
|
118
115
|
# run in the source class context
|
119
116
|
set_updated_finder do |last_replicated_date|
|
120
117
|
find(:all, :conditions => ['something > ?', last_replicated_date])
|
121
118
|
end
|
122
|
-
|
119
|
+
|
123
120
|
# Custom created item finder
|
124
121
|
set_created_finder do |last_replicated_date|
|
125
122
|
created_scope(last_replicated_date)
|
126
123
|
end
|
127
|
-
|
124
|
+
|
128
125
|
# Run a full refresh of all items in the database based
|
129
126
|
# a condition returned by the Proc
|
130
127
|
full_refresh :if => Proc.new do |last_replicated_date|
|
131
128
|
# class eval'ed code
|
132
129
|
any_changes_since?(last_replicated_date)
|
133
130
|
end
|
134
|
-
|
131
|
+
|
135
132
|
# You can also refresh every N seconds
|
136
133
|
# full_refresh :every => 3600
|
137
|
-
|
134
|
+
|
138
135
|
# Define your columns
|
139
136
|
column :id do
|
140
137
|
# The 'primary index' column is used to find items in the mongo collection
|
141
138
|
primary_index
|
142
139
|
end
|
143
|
-
|
140
|
+
|
144
141
|
column :tag do
|
145
142
|
# Run the procedure :downcase on the value of Client#tag
|
146
143
|
transform :downcase
|
147
144
|
end
|
148
|
-
|
145
|
+
|
149
146
|
column :employee_count do
|
150
147
|
# Run a block transformation on the value of Client#employee_count
|
151
148
|
transform do |value|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.8
|
data/lib/mongar.rb
CHANGED
@@ -38,18 +38,28 @@ class Mongar
|
|
38
38
|
def replicate(what, &block)
|
39
39
|
if what.is_a?(Hash)
|
40
40
|
source = what.keys.first
|
41
|
-
|
41
|
+
destinations = what.values.first
|
42
42
|
else
|
43
43
|
source = what
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
destination = Mongar::Mongo::Collection.new(:name => destination, :log_level => log_level)
|
44
|
+
destinations = what.to_s.downcase.en.plural
|
45
|
+
end
|
46
|
+
destinations = [destinations] unless destinations.is_a?(Array)
|
48
47
|
|
49
48
|
self.replicas ||= []
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
|
50
|
+
destinations.each do |destination|
|
51
|
+
database = nil
|
52
|
+
collection = if destination.is_a?(Hash)
|
53
|
+
database = destination.keys.first
|
54
|
+
Mongar::Mongo::Collection.new(:name => destination.values.first, :log_level => log_level)
|
55
|
+
else
|
56
|
+
Mongar::Mongo::Collection.new(:name => destination, :log_level => log_level)
|
57
|
+
end
|
58
|
+
|
59
|
+
replica = Replica.new(:source => source, :destination => collection, :mongodb_name => database, :log_level => log_level)
|
60
|
+
replica.instance_eval(&block)
|
61
|
+
self.replicas << replica
|
62
|
+
end
|
53
63
|
end
|
54
64
|
|
55
65
|
def mongo(name, &block)
|
data/lib/mongar/mongo.rb
CHANGED
@@ -9,6 +9,7 @@ class Mongar::Mongo
|
|
9
9
|
@name = args[:name]
|
10
10
|
@replica = args[:replica]
|
11
11
|
@log_level = args[:log_level]
|
12
|
+
@last_logged_activity = nil
|
12
13
|
end
|
13
14
|
|
14
15
|
def mongodb
|
@@ -40,43 +41,84 @@ class Mongar::Mongo
|
|
40
41
|
def last_replicated_at=(date)
|
41
42
|
info " * Updating #{name}.last_replicated_at to #{date}"
|
42
43
|
status_collection.update({ :collection_name => name },
|
43
|
-
{ :collection_name => name, :last_replicated_at => date },
|
44
|
+
{ '$set' => { :collection_name => name, :last_replicated_at => date } },
|
44
45
|
{ :upsert => true })
|
45
46
|
end
|
46
47
|
|
48
|
+
def last_activity_at=(date)
|
49
|
+
debug "Saving #{name} last_activity_at to #{date}"
|
50
|
+
status_collection.update({ :collection_name => name },
|
51
|
+
{ '$set' => { :collection_name => name, :last_activity_at => date } },
|
52
|
+
{ :upsert => true })
|
53
|
+
end
|
54
|
+
|
55
|
+
def log_activity
|
56
|
+
return unless should_log_activity?
|
57
|
+
debug "Logging activity for #{name}"
|
58
|
+
|
59
|
+
# should be able to set last_activity_at = new Date() but I can't figure out how to get
|
60
|
+
# ruby's mongo library to evaluate it instead of setting it to the string "new Date()"
|
61
|
+
status_collection.update({ :collection_name => name },
|
62
|
+
{ '$set' => { :collection_name => name, :last_activity_at => mongodb.time_on_server } },
|
63
|
+
{ :upsert => true })
|
64
|
+
@last_logged_activity = Time.now
|
65
|
+
end
|
66
|
+
|
67
|
+
def should_log_activity?
|
68
|
+
@last_logged_activity.nil? || Time.now - @last_logged_activity > 5
|
69
|
+
end
|
70
|
+
|
71
|
+
def last_activity_at
|
72
|
+
status = status_collection.find_one({ :collection_name => name })
|
73
|
+
return nil unless status && status['last_activity_at']
|
74
|
+
status['last_activity_at']
|
75
|
+
end
|
76
|
+
|
47
77
|
def find(key)
|
48
78
|
debug "#{name}.find #{key.inspect}"
|
49
79
|
collection.find_one(key)
|
50
80
|
end
|
51
81
|
|
52
82
|
def create(document)
|
83
|
+
log_activity
|
84
|
+
|
53
85
|
debug "#{name}.create #{document.inspect}"
|
54
86
|
!collection.insert(document).nil?
|
55
87
|
end
|
56
88
|
|
57
89
|
def delete(key)
|
90
|
+
log_activity
|
91
|
+
|
58
92
|
debug "#{name}.delete #{key.inspect}"
|
59
93
|
collection.remove(key, { :safe => true })
|
60
94
|
end
|
61
95
|
|
62
96
|
def update(key, document)
|
97
|
+
log_activity
|
98
|
+
|
63
99
|
debug "#{name}.update #{key.inspect} with #{document.inspect}"
|
64
100
|
collection.update(key, document, { :safe => true })
|
65
101
|
end
|
66
102
|
|
67
103
|
def create_or_update(key, document)
|
104
|
+
log_activity
|
105
|
+
|
68
106
|
debug "#{name}.create_or_update #{key.inspect} with #{document.inspect}"
|
69
107
|
|
70
108
|
collection.update(key, document, {:upsert => true, :safe => true})
|
71
109
|
end
|
72
110
|
|
73
111
|
def mark_all_items_pending_deletion
|
112
|
+
log_activity
|
113
|
+
|
74
114
|
info " * Marking all items in #{name} for pending deletion"
|
75
115
|
|
76
116
|
collection.update({ '_id' => { '$exists' => true } }, { "$set" => { :pending_deletion => true } }, { :multi => true, :safe => true })
|
77
117
|
end
|
78
118
|
|
79
119
|
def delete_all_items_pending_deletion
|
120
|
+
log_activity
|
121
|
+
|
80
122
|
info " * Deleting all items in #{name} that are pending deletion"
|
81
123
|
|
82
124
|
collection.remove({ :pending_deletion => true }, { :safe => true })
|
data/lib/mongar/replica.rb
CHANGED
@@ -28,13 +28,18 @@ class Mongar
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def run
|
31
|
+
info "Replicating #{source.to_s} to #{mongodb.name}.#{destination.name}"
|
32
|
+
|
33
|
+
if locked?
|
34
|
+
info " * Skipping locked replica"
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
31
38
|
time = current_time_on_database_server
|
32
39
|
|
33
40
|
# Set the time back 1 second to make sure we don't miss any changes made mid-second
|
34
41
|
time -= 1 unless time.nil?
|
35
42
|
|
36
|
-
info "Replicating #{source.to_s} to #{destination.name}"
|
37
|
-
|
38
43
|
if do_full_refresh?
|
39
44
|
info " * Full refresh"
|
40
45
|
|
@@ -51,6 +56,7 @@ class Mongar
|
|
51
56
|
run_sync_for([:deleted, :created_or_updated, :updated], last_replicated_at)
|
52
57
|
end
|
53
58
|
destination.last_replicated_at = time
|
59
|
+
destination.last_activity_at = nil
|
54
60
|
end
|
55
61
|
|
56
62
|
def run_sync_for(types, last_replicated_at)
|
@@ -184,5 +190,11 @@ class Mongar
|
|
184
190
|
def current_time_on_database_server
|
185
191
|
@db_time_selector.nil? ? default_time_selector(source) : source.instance_exec(&@db_time_selector)
|
186
192
|
end
|
193
|
+
|
194
|
+
def locked?
|
195
|
+
last_replicated_at = destination.last_activity_at
|
196
|
+
return false if last_replicated_at.nil?
|
197
|
+
Time.now - last_replicated_at < 300
|
198
|
+
end
|
187
199
|
end
|
188
200
|
end
|
data/mongar.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mongar}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.8"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Philippe Green"]
|
12
|
-
s.date = %q{2011-12-
|
12
|
+
s.date = %q{2011-12-19}
|
13
13
|
s.description = %q{Replicates data from ActiveRecord (or other Ruby data mapping class) to MongoDB}
|
14
14
|
s.email = %q{phil@greenviewdata.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -35,12 +35,9 @@ Mongar.configure do
|
|
35
35
|
|
36
36
|
# Customized replica config to replicate the Client
|
37
37
|
# model to the 'customers' mongo collection on the db
|
38
|
-
# defined in the :otherdb config above
|
39
|
-
|
40
|
-
replicate Client => '
|
41
|
-
# Use the other mongo database
|
42
|
-
use_mongodb :otherdb
|
43
|
-
|
38
|
+
# defined in the :otherdb config above as well as the
|
39
|
+
# default database
|
40
|
+
replicate Client => [{ :otherdb => 'clients' }, 'clients'] do
|
44
41
|
# By default, Mongar will try to determine the time on the
|
45
42
|
# backend database. The supported ActiveRecord database adapters
|
46
43
|
# are MysqlAdapter, Mysql2Adapter, and SQLServerAdapter.
|
@@ -198,4 +198,50 @@ describe "Mongar::Mongo::Collection" do
|
|
198
198
|
@collection.last_replicated_at.should == Time.parse("1/1/1902 00:00:00")
|
199
199
|
end
|
200
200
|
end
|
201
|
+
|
202
|
+
describe "#last_activity_at" do
|
203
|
+
it "should return the last activity date" do
|
204
|
+
@collection.last_activity_at = Time.parse("1/1/2012 1:01:00")
|
205
|
+
@collection.last_activity_at.should == Time.parse("1/1/2012 1:01:00")
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should return nil if the last activity date doesn't exist" do
|
209
|
+
@collection.last_activity_at.should be_nil
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe "#log_activity" do
|
214
|
+
it "should log the current mongodb time in the statuses collection" do
|
215
|
+
@collection.log_activity
|
216
|
+
@collection.last_activity_at.should be_a_kind_of(Time)
|
217
|
+
@collection.last_activity_at.should be_within(10).of(Time.now)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe "#last_activity_at=" do
|
222
|
+
context "with no status collection record" do
|
223
|
+
before do
|
224
|
+
@time1 = @collection.last_activity_at = Time.parse("1/1/2012 1:01:00")
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should save the last activity date" do
|
228
|
+
@collection.last_activity_at.should == @time1
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context "with an existing status collection record" do
|
233
|
+
before do
|
234
|
+
@time1 = @collection.last_replicated_at = Time.parse("1/1/2012 1:00:00")
|
235
|
+
@time2 = @collection.last_activity_at = Time.parse("1/1/2012 1:01:00")
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should save the last activity date" do
|
239
|
+
@collection.last_activity_at.should == @time2
|
240
|
+
end
|
241
|
+
|
242
|
+
it "should not overwrite the last_replicated_at" do
|
243
|
+
@collection.last_replicated_at.should == @time1
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
201
247
|
end
|
data/spec/mongar/mongo_spec.rb
CHANGED
@@ -88,4 +88,14 @@ describe "Mongar::Mongo" do
|
|
88
88
|
Mongar::Mongo.new.status_collection.should == 'statuses'
|
89
89
|
end
|
90
90
|
end
|
91
|
+
|
92
|
+
describe "#time_on_server" do
|
93
|
+
before do
|
94
|
+
@mongo = Mongar::Mongo.new(:name => 'rspec_tests',
|
95
|
+
:database => :rspec_tests)
|
96
|
+
end
|
97
|
+
it "should return a date/time" do
|
98
|
+
@mongo.time_on_server.should be_a_kind_of(Time)
|
99
|
+
end
|
100
|
+
end
|
91
101
|
end
|
data/spec/mongar/replica_spec.rb
CHANGED
@@ -18,6 +18,7 @@ describe "Mongar::Replica" do
|
|
18
18
|
|
19
19
|
@collection = Mongar::Mongo::Collection.new(:name => "clients")
|
20
20
|
@replica = Mongar::Replica.new(:source => Client, :destination => @collection)
|
21
|
+
@replica.stub!(:locked?).and_return(false)
|
21
22
|
@replica.column :name do
|
22
23
|
primary_index
|
23
24
|
end
|
@@ -31,9 +32,22 @@ describe "Mongar::Replica" do
|
|
31
32
|
@last_replicated_time = Time.now - 86400
|
32
33
|
@collection.stub!(:last_replicated_at).and_return(@last_replicated_time)
|
33
34
|
|
35
|
+
@collection.stub!(:last_activity_at=)
|
36
|
+
|
34
37
|
@created_client1 = Client.new(:name => "Otis Co", :employee_count => 600)
|
35
38
|
end
|
36
39
|
|
40
|
+
context "with a locked replica" do
|
41
|
+
before do
|
42
|
+
@replica.stub!(:locked?).and_return(true)
|
43
|
+
end
|
44
|
+
it "should return without doing anything" do
|
45
|
+
@replica.should_not_receive(:do_full_refresh?)
|
46
|
+
@replica.should_not_receive(:find)
|
47
|
+
@replica.run
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
37
51
|
context "requiring a full refresh" do
|
38
52
|
before do
|
39
53
|
@replica.stub!(:find).with(:created, Time.parse("1/1/1902 00:00:00")).and_return([@created_client1])
|
@@ -472,4 +486,27 @@ describe "Mongar::Replica" do
|
|
472
486
|
end
|
473
487
|
end
|
474
488
|
end
|
489
|
+
|
490
|
+
describe "#locked?" do
|
491
|
+
before do
|
492
|
+
@collection = Mongar::Mongo::Collection.new(:name => "clients")
|
493
|
+
@replica = Mongar::Replica.new(:destination => @collection)
|
494
|
+
|
495
|
+
@ten_min_ago = Time.now - 600
|
496
|
+
@two_min_ago = Time.now - 120
|
497
|
+
@collection.stub!(:last_activity_at).and_return(nil)
|
498
|
+
end
|
499
|
+
|
500
|
+
it "should return false if the last_activity is null" do
|
501
|
+
@replica.locked?.should be_false
|
502
|
+
end
|
503
|
+
it "should return false if the last_activity is more than 5 minutes ago" do
|
504
|
+
@collection.stub!(:last_activity_at).and_return(@ten_min_ago)
|
505
|
+
@replica.locked?.should be_false
|
506
|
+
end
|
507
|
+
it "should return true if the last_activity is not null and within 5 minutes of the current time" do
|
508
|
+
@collection.stub!(:last_activity_at).and_return(@two_min_ago)
|
509
|
+
@replica.locked?.should be_true
|
510
|
+
end
|
511
|
+
end
|
475
512
|
end
|
data/spec/mongar_spec.rb
CHANGED
@@ -16,26 +16,45 @@ describe "Mongar" do
|
|
16
16
|
|
17
17
|
@block = lambda {}
|
18
18
|
@mock_replica = mock(Mongar::Replica)
|
19
|
-
Mongar::Replica.stub!(:new).and_return(@mock_replica)
|
20
19
|
@mock_replica.stub!(:instance_eval)
|
20
|
+
Mongar::Replica.stub!(:new).and_return(@mock_replica)
|
21
21
|
|
22
22
|
@collection = Mongar::Mongo::Collection.new
|
23
23
|
Mongar::Mongo::Collection.stub!(:new).and_return(@collection)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
context "given a string" do
|
27
|
+
it "should make a new Mongar::Replica and pass it the block" do
|
28
|
+
@mock_replica.should_receive(:instance_eval).with(&@block)
|
29
|
+
@mongar.replicate({ Client => 'clients' }, &@block)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should populate Mongar.replicas with one Replica instance for Clients" do
|
33
|
+
@mongar.replicate({ Client => 'clients' }, &@block)
|
34
|
+
@mongar.replicas.should == [@mock_replica]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should initialize a new replica with the source and destination objects" do
|
38
|
+
Mongar::Replica.should_receive(:new).with(:source => Client, :destination => @collection, :mongodb_name => nil, :log_level => nil)
|
39
|
+
@mongar.replicate({ Client => 'clients' }, &@block)
|
40
|
+
end
|
29
41
|
end
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
43
|
+
context 'given an array of destinations' do
|
44
|
+
before do
|
45
|
+
@mongar.replicate({ Client => ['clients', { :someotherdb => 'clients' }]})
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should create 2 replicas" do
|
49
|
+
@mongar.replicas.length.should == 2
|
50
|
+
end
|
34
51
|
end
|
35
52
|
|
36
|
-
|
37
|
-
|
38
|
-
|
53
|
+
context "given a hash" do
|
54
|
+
it "should initialize a new replica" do
|
55
|
+
Mongar::Replica.should_receive(:new).with(:source => Client, :destination => @collection, :mongodb_name => :someotherdb, :log_level => nil)
|
56
|
+
@mongar.replicate(Client => { :someotherdb => 'clients'})
|
57
|
+
end
|
39
58
|
end
|
40
59
|
end
|
41
60
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: mongar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.8
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Philippe Green
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-12-
|
13
|
+
date: 2011-12-19 00:00:00 -05:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -139,7 +139,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
139
|
requirements:
|
140
140
|
- - ">="
|
141
141
|
- !ruby/object:Gem::Version
|
142
|
-
hash: -
|
142
|
+
hash: -176625357
|
143
143
|
segments:
|
144
144
|
- 0
|
145
145
|
version: "0"
|