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 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 => 'customers' do
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.7
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
- destination = what.values.first
41
+ destinations = what.values.first
42
42
  else
43
43
  source = what
44
- destination = what.to_s.downcase.en.plural
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
- replica = Replica.new(:source => source, :destination => destination, :log_level => log_level)
51
- replica.instance_eval(&block)
52
- self.replicas << replica
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
@@ -47,5 +47,9 @@ class Mongar
47
47
  def status_collection_accessor
48
48
  db[status_collection]
49
49
  end
50
+
51
+ def time_on_server
52
+ db.eval("return new Date()")
53
+ end
50
54
  end
51
55
  end
@@ -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 })
@@ -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.7"
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-16}
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 => 'customers' do
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
@@ -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
@@ -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
- it "should make a new Mongar::Replica and pass it the block" do
27
- @mock_replica.should_receive(:instance_eval).with(&@block)
28
- @mongar.replicate({ Client => 'clients' }, &@block)
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
- it "should populate Mongar.replicas with one Replica instance for Clients" do
32
- @mongar.replicate({ Client => 'clients' }, &@block)
33
- @mongar.replicas.should == [@mock_replica]
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
- it "should initialize a new replica with the source and destination objects" do
37
- Mongar::Replica.should_receive(:new).with(:source => Client, :destination => @collection, :log_level => nil)
38
- @mongar.replicate({ Client => 'clients' }, &@block)
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.7
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-16 00:00:00 -05:00
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: -627046195
142
+ hash: -176625357
143
143
  segments:
144
144
  - 0
145
145
  version: "0"