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 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"