mongify 0.1.2 → 0.1.3

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.
@@ -1,25 +1,58 @@
1
1
  module Mongify
2
2
  # This class is responsible for generating progress bars with status of mongify
3
3
  class Status
4
+ # List of known notifications
5
+ NOTIFICATIONS = ['copy_data', 'copy_embedded', 'copy_polymorphic', 'update_references', 'remove_pre_mongified']
6
+
4
7
  class << self
8
+ #List of all the progress bars.
9
+ def bars
10
+ @bars ||= {}
11
+ end
12
+ #Add a new bar to the list of progress bars
13
+ def add_bar(name, bar)
14
+ self.bars[name] = bar
15
+ end
16
+
5
17
  # Registers the ActiveSupport::Notifications for Mongify
6
18
  def register
7
- @bars = {}
8
19
  ActiveSupport::Notifications.subscribe(/^mongify\./) do |*args|
9
20
  event = ActiveSupport::Notifications::Event.new(*args)
10
- # puts event.name
11
- mongify, name, action = event.name.split('.', 3)
12
- case action
13
- when 'start'
14
- @bars[name] = ProgressBar.new(name.humanize, event.payload[:size] || 0)
15
- when 'inc'
16
- @bars[name].try(:inc)
17
- when 'finish'
18
- @bars[name].try(:finish)
21
+
22
+ action_name = event.name.split('.', 2)[1]
23
+
24
+ if Status::NOTIFICATIONS.include?(action_name)
25
+ case event.payload[:action]
26
+ when 'add'
27
+ self.add_bar(action_name, ProgressBar.new(event.payload[:name], event.payload[:size]))
28
+ when 'inc'
29
+ self.bars[action_name].try(:inc)
30
+ when 'finish'
31
+ self.bars[action_name].try(:finish)
32
+ else
33
+ UI.warn("Unknown Notification Action #{event.payload[:action]}")
34
+ end
35
+ #puts event.payload.inspect
19
36
  else
20
- UI.warn("Unknown Notification Event #{event.name}")
37
+ UI.warn("Unknown Notification Event #{action_name}")
21
38
  end
22
39
  end
40
+
41
+ # Unregisters from {ActiveSupport::Notifications}
42
+ def unregister
43
+ ActiveSupport::Notifications.unsubscribe(/^mongify\./)
44
+ end
45
+
46
+ # Publish an notification event
47
+ # This will publish the event as an mongify.[name]
48
+ # @param [String] name Name of the notification
49
+ # @param [Hash] payload to be sent with the notification
50
+ # @return [nil]
51
+ def publish(name, payload={})
52
+ payload.reverse_merge!(:name => name.humanize, :action => 'inc')
53
+ ActiveSupport::Notifications.instrument("mongify.#{name}", payload)
54
+ nil
55
+ end
23
56
  end
24
57
  end
25
58
  end
@@ -5,7 +5,6 @@ module Mongify
5
5
  #
6
6
  module Process
7
7
  attr_accessor :sql_connection, :no_sql_connection
8
-
9
8
  # Does the actual act of processing the translation.
10
9
  # Takes in boht a sql connection and a no sql connection
11
10
  def process(sql_connection, no_sql_connection)
@@ -13,12 +12,13 @@ module Mongify
13
12
  raise Mongify::NoSqlConnectionRequired, "Can only write to Mongify::Database::NoSqlConnection" unless no_sql_connection.is_a?(Mongify::Database::NoSqlConnection)
14
13
 
15
14
  self.sql_connection = sql_connection
16
- raise "SQL Connection is not valid" unless self.sql_connection.valid?
15
+ raise SqlConnectionInvalid, "SQL Connection is not valid" unless self.sql_connection.valid?
17
16
  self.no_sql_connection = no_sql_connection
18
- raise "noSql Connection is not valid" unless self.no_sql_connection.valid?
17
+ raise NoSqlConnectionInvalid, "noSql Connection is not valid" unless self.no_sql_connection.valid?
19
18
 
20
19
  no_sql_connection.ask_to_drop_database if no_sql_connection.forced?
21
20
 
21
+ setup_db_index
22
22
  copy_data
23
23
  copy_embedded_tables
24
24
  update_reference_ids
@@ -31,23 +31,32 @@ module Mongify
31
31
  private
32
32
  #######
33
33
 
34
+ # Setups up pre_mongifed_id as an index to speed up lookup performance
35
+ def setup_db_index
36
+ self.copy_tables.each do |t|
37
+ no_sql_connection.create_pre_mongified_id_index(t.name)
38
+ end
39
+ end
40
+
34
41
  # Does the straight copy (of tables)
35
42
  def copy_data
36
- ActiveSupport::Notifications.instrument('mongify.copy_data.start', :size => self.copy_tables.count)
37
43
  self.copy_tables.each do |t|
38
- sql_connection.select_rows(t.sql_name).each do |row|
44
+ rows = sql_connection.select_rows(t.sql_name)
45
+ Mongify::Status.publish('copy_data', :size => rows.count, :name => "Copying #{t.name}", :action => 'add')
46
+ rows.each do |row|
39
47
  no_sql_connection.insert_into(t.name, t.translate(row))
48
+ Mongify::Status.publish('copy_data')
40
49
  end
41
- ActiveSupport::Notifications.instrument('mongify.copy_data.inc')
50
+ Mongify::Status.publish('copy_data', :action => 'finish')
42
51
  end
43
- ActiveSupport::Notifications.instrument('mongify.copy_data.finish')
44
52
  end
45
53
 
46
54
  # Does a copy of the embedded tables
47
55
  def copy_embedded_tables
48
- ActiveSupport::Notifications.instrument('mongify.copy_embedded_tables.start', :size => self.embed_tables.count)
49
56
  self.embed_tables.each do |t|
50
- sql_connection.select_rows(t.sql_name).each do |row|
57
+ rows = sql_connection.select_rows(t.sql_name)
58
+ Mongify::Status.publish('copy_embedded', :size => rows.count, :name => "Embedding #{t.name}", :action => 'add')
59
+ rows.each do |row|
51
60
  target_row = no_sql_connection.find_one(t.embed_in, {:pre_mongified_id => row[t.embed_on]})
52
61
  next unless target_row.present?
53
62
  row = t.translate(row)
@@ -56,54 +65,60 @@ module Mongify
56
65
  row.delete('pre_mongified_id')
57
66
  save_function_call = t.embedded_as_object? ? '$set' : '$addToSet'
58
67
  no_sql_connection.update(t.embed_in, target_row['_id'], {save_function_call => {t.name => row}})
68
+ Mongify::Status.publish('copy_embedded')
59
69
  end
60
- ActiveSupport::Notifications.instrument('mongify.copy_embedded_tables.inc')
70
+ Mongify::Status.publish('copy_embedded', :action => 'finish')
61
71
  end
62
- ActiveSupport::Notifications.instrument('mongify.copy_embedded_tables.finish')
63
72
  end
64
73
 
65
74
  # Moves over polymorphic data
66
75
  def copy_polymorphic_tables
67
- ActiveSupport::Notifications.instrument('mongify.copy_polymorphic_tables.start', :size => self.polymorphic_tables.count)
68
76
  self.polymorphic_tables.each do |t|
69
77
  polymorphic_id_col, polymorphic_type_col = "#{t.polymorphic_as}_id", "#{t.polymorphic_as}_type"
70
- sql_connection.select_rows(t.sql_name).each do |row|
71
- table_name = row[polymorphic_type_col].tableize
72
- new_id = no_sql_connection.get_id_using_pre_mongified_id(table_name, row[polymorphic_id_col])
73
- if new_id
74
- row = t.translate(row)
75
- row.merge!(fetch_reference_ids(t, row))
76
- row[polymorphic_id_col] = new_id
77
- row.delete('pre_mongified_id')
78
- if t.embedded?
79
- row.delete(polymorphic_id_col)
80
- row.delete(polymorphic_type_col)
81
- save_function_call = t.embedded_as_object? ? '$set' : '$addToSet'
82
- no_sql_connection.update(table_name, new_id, {save_function_call => {t.name => row}})
83
- else
84
- no_sql_connection.insert_into(t.name, row)
85
- end
78
+ rows = sql_connection.select_rows(t.sql_name)
79
+ Mongify::Status.publish('copy_polymorphic', :size => rows.count, :name => "Polymorphicizing #{t.name}", :action => 'add')
80
+ rows.each do |row|
81
+
82
+ #If no data is in the column, skip importing
83
+
84
+ if (row[polymorphic_type_col])
85
+ table_name = row[polymorphic_type_col].tableize
86
+ new_id = no_sql_connection.get_id_using_pre_mongified_id(table_name, row[polymorphic_id_col])
87
+ end
88
+
89
+ row = t.translate(row)
90
+ row[polymorphic_id_col] = new_id if new_id
91
+ row.merge!(fetch_reference_ids(t, row))
92
+ row.delete('pre_mongified_id')
93
+
94
+ if t.embedded? && table_name
95
+ row.delete(polymorphic_id_col)
96
+ row.delete(polymorphic_type_col)
97
+ save_function_call = t.embedded_as_object? ? '$set' : '$addToSet'
98
+ no_sql_connection.update(table_name, new_id, {save_function_call => {t.name => row}})
86
99
  else
87
- UI.warn "#{table_name} table not found on #{t.sql_name} polymorphic import"
100
+ no_sql_connection.insert_into(t.name, row)
88
101
  end
102
+
103
+ Mongify::Status.publish('copy_polymorphic')
89
104
  end
90
- ActiveSupport::Notifications.instrument('mongify.copy_polymorphic_tables.inc')
105
+ Mongify::Status.publish('copy_polymorphic', :action => 'finish')
91
106
  end
92
- ActiveSupport::Notifications.instrument('mongify.copy_polymorphic_tables.finish')
93
107
  end
94
108
 
95
109
  # Updates the reference ids in the no sql database
96
110
  def update_reference_ids
97
- ActiveSupport::Notifications.instrument('mongify.update_reference_ids.start', :size => self.tables.size)
98
- self.tables.each do |t|
99
- no_sql_connection.select_rows(t.name).each do |row|
111
+ self.copy_tables.each do |t|
112
+ rows = no_sql_connection.select_rows(t.name)
113
+ Mongify::Status.publish('update_references', :size => rows.count, :name => "Updating References #{t.name}", :action => 'add')
114
+ rows.each do |row|
100
115
  id = row["_id"]
101
116
  attributes = fetch_reference_ids(t, row)
102
117
  no_sql_connection.update(t.name, id, {"$set" => attributes}) unless attributes.blank?
118
+ Mongify::Status.publish('update_references')
103
119
  end
104
- ActiveSupport::Notifications.instrument('mongify.update_reference_ids.inc')
120
+ Mongify::Status.publish('update_references', :action => 'finish')
105
121
  end
106
- ActiveSupport::Notifications.instrument('mongify.update_reference_ids.finish')
107
122
  end
108
123
 
109
124
  # Fetches the new _id from a collection
@@ -118,13 +133,12 @@ module Mongify
118
133
 
119
134
  # Removes 'pre_mongiifed_id's from all collection
120
135
  def remove_pre_mongified_ids
121
- ActiveSupport::Notifications.instrument('mongify.remove_pre_mongified_ids.start', :size => self.tables.size)
122
- p = ProgressBar.new("Removed pre_mongified_ids", self.copy_tables.count)
123
136
  self.copy_tables.each do |t|
137
+ Mongify::Status.publish('remove_pre_mongified', :size => 1, :name => "Removing pre_mongified_id #{t.name}", :action => 'add')
124
138
  no_sql_connection.remove_pre_mongified_ids(t.name)
125
- ActiveSupport::Notifications.instrument('mongify.remove_pre_mongified_ids.inc')
139
+ Mongify::Status.publish('remove_pre_mongified', :action => 'finish')
140
+ # Mongify::Status.publish('remove_pre_mongified')
126
141
  end
127
- ActiveSupport::Notifications.instrument('mongify.remove_pre_mongified_ids.finish')
128
142
  end
129
143
 
130
144
  end
data/lib/mongify/ui.rb CHANGED
@@ -49,6 +49,14 @@ module Mongify
49
49
  def out_stream
50
50
  Configuration.out_stream
51
51
  end
52
+
53
+ # Creates an instance of HighLine
54
+ # which lets us figure out width of console
55
+ # plus a whole lot more
56
+ # @reutrn [HighLine] instance
57
+ def terminal_helper
58
+ @terminal_helper ||= HighLine.new
59
+ end
52
60
  end
53
61
  end
54
62
  end
@@ -1,4 +1,4 @@
1
1
  module Mongify
2
2
  # Mongify's Current Version Number
3
- VERSION = "0.1.2"
3
+ VERSION = "0.1.3"
4
4
  end
data/mongify.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency('mongo', ">= 1.1.5")
20
20
  s.add_dependency('bson_ext', ">= 1.1.5")
21
21
  s.add_dependency('net-ssh', ">= 2.0")
22
- s.add_dependency('progressbar', ">= 0.9")
22
+ s.add_dependency('highline', '>= 1.6.1')
23
23
 
24
24
  s.add_development_dependency('rspec', '>= 2.0')
25
25
  s.add_development_dependency('rcov', '>= 0.9.9')
@@ -26,10 +26,10 @@ describe Mongify::Database::BaseConnection do
26
26
  end
27
27
 
28
28
  it "should raise error when trying to call has_connection?" do
29
- lambda { @base_connection.has_connection? }.should raise_error(NotImplementedError)
29
+ lambda { @base_connection.has_connection? }.should raise_error(Mongify::NotImplementedMongifyError)
30
30
  end
31
31
  it "should raise error when trying to call setup_connection_adapter" do
32
- lambda { @base_connection.setup_connection_adapter}.should raise_error(NotImplementedError)
32
+ lambda { @base_connection.setup_connection_adapter}.should raise_error(Mongify::NotImplementedMongifyError)
33
33
  end
34
34
 
35
35
  it "should raise error on setting unknown variable setting" do
@@ -122,6 +122,35 @@ describe Mongify::Database::Column do
122
122
  end
123
123
  end
124
124
 
125
+ context "as" do
126
+ subject {Mongify::Database::Column.new('total', :decimal)}
127
+ it "should default to string" do
128
+ subject.as.should == 'string'
129
+ end
130
+ it "should allow it to be set to integer" do
131
+ subject.as = 'integer'
132
+ subject.should be_as_integer
133
+ end
134
+ it "should not allow other values" do
135
+ subject.as = "zuza"
136
+ subject.as.should == 'string'
137
+ end
138
+ end
139
+ context "scale" do
140
+ subject {Mongify::Database::Column.new('total', :decimal, :as => 'integer')}
141
+ it "should be defaulted to 0" do
142
+ subject.scale.should be_zero
143
+ end
144
+ it "should let you set the scale" do
145
+ subject.scale = 3
146
+ subject.scale.should == 3
147
+ end
148
+ it "should return 0 on invalid input" do
149
+ subject.scale = 'zuza'
150
+ subject.scale.should be_zero
151
+ end
152
+ end
153
+
125
154
  context :to_print do
126
155
  before(:each) do
127
156
  @column = Mongify::Database::Column.new('first_name', :string)
@@ -160,6 +189,10 @@ describe Mongify::Database::Column do
160
189
  @column = Mongify::Database::Column.new('id', :key)
161
190
  @column.translate(123123).should == {"pre_mongified_id" => 123123}
162
191
  end
192
+ it "should return an integer for pre_mongified_id" do
193
+ @column = Mongify::Database::Column.new('id', :key)
194
+ @column.translate('123123').should == {"pre_mongified_id" => 123123}
195
+ end
163
196
  end
164
197
  context :type_cast do
165
198
  it "should return value if unknown type" do
@@ -212,14 +245,42 @@ describe Mongify::Database::Column do
212
245
  before(:each) do
213
246
  @column = Mongify::Database::Column.new('price', :decimal)
214
247
  end
215
- it "should convert numbers to decimal" do
216
- @column.send(:type_cast, 101.43).should == BigDecimal.new("101.43")
248
+ it "should convert numbers to decimal string" do
249
+ @column.send(:type_cast, 101.43).should == "101.43"
217
250
  end
218
- it "should convert integers to decimal" do
219
- @column.send(:type_cast, 101).should == BigDecimal.new("101.0")
251
+ it "should convert integers to decimal string" do
252
+ @column.send(:type_cast, 101).should == "101.0"
220
253
  end
221
- it "should convert strings to 0.0" do
222
- @column.send(:type_cast, 'zuza').should == BigDecimal.new("0")
254
+ it "should convert strings to 0.0 (string)" do
255
+ @column.send(:type_cast, 'zuza').should == "0.0"
256
+ end
257
+ it "should return a string value" do
258
+ @column.send(:type_cast, 101.43).should be_a_kind_of String
259
+ end
260
+
261
+ context "as integer" do
262
+ before(:each) do
263
+ @column = Mongify::Database::Column.new('price', :decimal, :as => 'integer')
264
+ @value = 101.123455
265
+ end
266
+ it "should be as_integer" do
267
+ @column.should be_as_integer
268
+ end
269
+ it "should convert number to integer" do
270
+ @column.send(:type_cast, @value).should == 101
271
+ end
272
+ it "should let you specify scale" do
273
+ @column.scale = 3
274
+ @column.send(:type_cast, @value).should == 101123
275
+ end
276
+ it "should round correctly to specified scale" do
277
+ @column.scale = 4
278
+ @column.send(:type_cast, @value).should == 1011235
279
+ end
280
+ it "should return an integer value" do
281
+ @column.scale = 3
282
+ @column.send(:type_cast, @value).should be_an_integer
283
+ end
223
284
  end
224
285
  end
225
286
  context :timestamp do
@@ -116,9 +116,23 @@ describe Mongify::Database::NoSqlConnection do
116
116
  end
117
117
  end
118
118
 
119
+ it "should create index for pre_mongified_id" do
120
+ @collection.should_receive(:create_index).with([["pre_mongified_id", Mongo::ASCENDING]]).and_return(true)
121
+ @mongodb_connection.create_pre_mongified_id_index('users')
122
+ end
123
+
119
124
  context "remove_pre_mongified_ids" do
125
+ before(:each) do
126
+ @collection.stub(:index_information).and_return('pre_mongified_id_1' => 'something')
127
+ end
120
128
  it "should call update with unset" do
121
129
  @collection.should_receive(:update).with({},{'$unset' => {'pre_mongified_id' => 1}}, {:multi=>true})
130
+ @collection.stub(:drop_index)
131
+ @mongodb_connection.remove_pre_mongified_ids('users')
132
+ end
133
+ it "should drop the index" do
134
+ @collection.should_receive(:drop_index).with('pre_mongified_id_1')
135
+ @collection.stub(:update)
122
136
  @mongodb_connection.remove_pre_mongified_ids('users')
123
137
  end
124
138
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongify::Status do
4
+ it "should warn if it's an unknown notification" do
5
+ Mongify::UI.should_receive(:warn).at_least(1)
6
+ Mongify::Status.publish('unknwon')
7
+ end
8
+ context "publish" do
9
+ before(:each) do
10
+ @bar = stub(:title= => '')
11
+ Mongify::Status.bars.stub(:[]).and_return(@bar)
12
+ end
13
+ it "should create a new progress bar" do
14
+ Mongify::ProgressBar.should_receive(:new)
15
+ Mongify::Status.publish('copy_data', :size => 100, :action => 'add')
16
+ end
17
+ context "on inc" do
18
+ before(:each) do
19
+ @bar.stub(:inc)
20
+ end
21
+ it "should inc a already created progress bar" do
22
+ @bar.should_receive(:inc)
23
+ Mongify::Status.publish('copy_data', :action => 'inc')
24
+ end
25
+ it "should inc by default action" do
26
+ @bar.should_receive(:inc)
27
+ Mongify::Status.publish('copy_data')
28
+ end
29
+ end
30
+ it "should finish the progress bar" do
31
+ @bar.should_receive(:finish)
32
+ Mongify::Status.publish('copy_data', :action => 'finish')
33
+ end
34
+ end
35
+ end
@@ -23,6 +23,10 @@ describe Mongify::Translation::Process do
23
23
  @translation.stub(:copy_data)
24
24
  @translation.stub(:copy_embedded_tables)
25
25
  end
26
+ it "should setup index on pre_mongify_id" do
27
+ @translation.should_receive(:setup_db_index)
28
+ @translation.process(@sql_connection, @no_sql_connection)
29
+ end
26
30
  it "should call copy_data" do
27
31
  @translation.should_receive(:copy_data)
28
32
  @translation.process(@sql_connection, @no_sql_connection)
@@ -39,6 +43,13 @@ describe Mongify::Translation::Process do
39
43
  @translation.should_receive(:remove_pre_mongified_ids)
40
44
  @translation.process(@sql_connection, @no_sql_connection)
41
45
  end
46
+
47
+ it "should add pre_mongified_id index to database" do
48
+ tables = [stub(:name => 'users')]
49
+ @translation.stub(:copy_tables).and_return(tables)
50
+ @no_sql_connection.should_receive(:create_pre_mongified_id_index).with('users')
51
+ @translation.process(@sql_connection, @no_sql_connection)
52
+ end
42
53
  end
43
54
 
44
55
  it "should ask_to_drop_database if mongodb_connection is forced" do
@@ -47,7 +58,6 @@ describe Mongify::Translation::Process do
47
58
  @translation.process(@sql_connection, @no_sql_connection)
48
59
  end
49
60
 
50
-
51
61
  context "fetch_reference_ids" do
52
62
  it "should get correct information" do
53
63
  @no_sql_connection = mock()
@@ -153,8 +163,8 @@ describe Mongify::Translation::Process do
153
163
  :sql_name => 'user_accounts')
154
164
  @translation.stub(:find).with('user_accounts').and_return([@ref_table])
155
165
 
156
- @sql_connection.should_receive(:select_rows).with('comments').and_return([{'commentable_id' => 1, 'commentable_type' => 'UserAccount', 'data' => 'good'}])
157
- @no_sql_connection.should_receive(:get_id_using_pre_mongified_id).with('user_accounts', 1).and_return(500)
166
+ @sql_connection.stub(:select_rows).with('comments').and_return([{'commentable_id' => 1, 'commentable_type' => 'UserAccount', 'data' => 'good'}])
167
+ @no_sql_connection.stub(:get_id_using_pre_mongified_id).with('user_accounts', 1).and_return(500)
158
168
  end
159
169
  context "embedded" do
160
170
  it "should work correctly" do
@@ -187,10 +197,27 @@ describe Mongify::Translation::Process do
187
197
  :reference_columns => [])
188
198
 
189
199
  @translation.stub(:all_tables).and_return([@table])
190
-
200
+ @no_sql_connection.should_receive(:get_id_using_pre_mongified_id).with('user_accounts', 1).and_return(500)
191
201
  @no_sql_connection.should_receive(:insert_into).with('comments', {'data' => 123, 'commentable_type' => 'UserAccount', 'commentable_id' => 500})
192
202
  @translation.send(:copy_polymorphic_tables)
193
203
  end
204
+ it "should copy even if there is no polymorphic data" do
205
+ @table = mock(:translate => {'data' => 123, 'commentable_type' => nil, 'commentable_id' => nil},
206
+ :name => 'comments',
207
+ :embedded? => false,
208
+ :polymorphic_as => 'commentable',
209
+ :polymorphic? => true,
210
+ :ignored? => false,
211
+ :embedded_as_object? => false,
212
+ :sql_name => 'comments',
213
+ :reference_columns => [])
214
+
215
+ @translation.stub(:all_tables).and_return([@table])
216
+ @sql_connection.should_receive(:select_rows).with('comments').and_return([{'commentable_id' => nil, 'commentable_type' => nil, 'data' => 'good'}])
217
+ @no_sql_connection.should_receive(:insert_into).with('comments', {'data' => 123, 'commentable_type' => nil, 'commentable_id' => nil})
218
+ @no_sql_connection.should_receive(:get_id_using_pre_mongified_id).never
219
+ @translation.send(:copy_polymorphic_tables)
220
+ end
194
221
  end
195
222
  end
196
223