mongar 0.0.7 → 0.0.8
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/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"
|