mongify 1.0.1 → 1.1.0

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.yardopts +3 -0
  4. data/CHANGELOG.rdoc +5 -0
  5. data/Gemfile.lock +52 -6
  6. data/LICENSE +1 -1
  7. data/README.rdoc +29 -11
  8. data/Rakefile +37 -9
  9. data/features/options.feature +2 -0
  10. data/features/print.feature +1 -1
  11. data/features/process.feature +10 -1
  12. data/features/step_definitions/process_steps.rb +11 -1
  13. data/features/support/env.rb +1 -1
  14. data/lib/mongify/cli/application.rb +7 -7
  15. data/lib/mongify/cli/command/worker.rb +18 -14
  16. data/lib/mongify/cli/options.rb +2 -1
  17. data/lib/mongify/configuration.rb +5 -5
  18. data/lib/mongify/database/base_connection.rb +6 -6
  19. data/lib/mongify/database/column.rb +40 -40
  20. data/lib/mongify/database/data_row.rb +9 -9
  21. data/lib/mongify/database/no_sql_connection.rb +61 -35
  22. data/lib/mongify/database/sql_connection.rb +44 -15
  23. data/lib/mongify/database/table.rb +62 -46
  24. data/lib/mongify/exceptions.rb +5 -5
  25. data/lib/mongify/progressbar.rb +15 -15
  26. data/lib/mongify/status.rb +8 -8
  27. data/lib/mongify/translation.rb +19 -17
  28. data/lib/mongify/translation/process.rb +16 -123
  29. data/lib/mongify/translation/processor_common.rb +132 -0
  30. data/lib/mongify/translation/sync.rb +112 -0
  31. data/lib/mongify/ui.rb +9 -9
  32. data/lib/mongify/version.rb +1 -1
  33. data/mongify.gemspec +4 -2
  34. data/spec/files/deleting_fields_from_embedding_parent_translation.rb +19 -0
  35. data/spec/files/embedded_parent_translation.rb +1 -1
  36. data/spec/mongify/cli/application_spec.rb +1 -1
  37. data/spec/mongify/cli/options_spec.rb +1 -1
  38. data/spec/mongify/cli/worker_command_spec.rb +46 -17
  39. data/spec/mongify/database/column_spec.rb +21 -21
  40. data/spec/mongify/database/data_row_spec.rb +11 -11
  41. data/spec/mongify/database/no_sql_connection_spec.rb +61 -27
  42. data/spec/mongify/database/sql_connection_spec.rb +62 -2
  43. data/spec/mongify/database/table_spec.rb +53 -29
  44. data/spec/mongify/translation/printer_spec.rb +2 -2
  45. data/spec/mongify/translation/process_spec.rb +50 -34
  46. data/spec/mongify/translation/sync_spec.rb +184 -0
  47. data/spec/mongify/translation_spec.rb +8 -8
  48. data/spec/mongify/ui_spec.rb +12 -12
  49. data/spec/mongify_spec.rb +1 -1
  50. data/spec/spec_helper.rb +8 -1
  51. data/spec/support/config_reader.rb +2 -2
  52. data/spec/support/database_generator.rb +68 -25
  53. data/spec/support/database_output.txt +17 -0
  54. metadata +41 -6
@@ -2,8 +2,8 @@ module Mongify
2
2
  # This class is responsible for generating progress bars with status of mongify
3
3
  class Status
4
4
  # List of known notifications
5
- NOTIFICATIONS = ['copy_data', 'copy_embedded', 'copy_polymorphic', 'update_references', 'remove_pre_mongified']
6
-
5
+ NOTIFICATIONS = ['copy_data', 'copy_embedded', 'copy_polymorphic', 'update_references', 'remove_pre_mongified', 'set_last_updated_at']
6
+
7
7
  class << self
8
8
  #List of all the progress bars.
9
9
  def bars
@@ -13,14 +13,14 @@ module Mongify
13
13
  def add_bar(name, bar)
14
14
  self.bars[name] = bar
15
15
  end
16
-
16
+
17
17
  # Registers the ActiveSupport::Notifications for Mongify
18
18
  def register
19
19
  ActiveSupport::Notifications.subscribe(/^mongify\./) do |*args|
20
20
  event = ActiveSupport::Notifications::Event.new(*args)
21
-
21
+
22
22
  action_name = event.name.split('.', 2)[1]
23
-
23
+
24
24
  if Status::NOTIFICATIONS.include?(action_name)
25
25
  case event.payload[:action]
26
26
  when 'add'
@@ -37,12 +37,12 @@ module Mongify
37
37
  UI.warn("Unknown Notification Event #{action_name}")
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Unregisters from {ActiveSupport::Notifications}
42
42
  def unregister
43
43
  ActiveSupport::Notifications.unsubscribe(/^mongify\./)
44
44
  end
45
-
45
+
46
46
  # Publish an notification event
47
47
  # This will publish the event as an mongify.[name]
48
48
  # @param [String] name Name of the notification
@@ -55,5 +55,5 @@ module Mongify
55
55
  end
56
56
  end
57
57
  end
58
- end
58
+ end
59
59
  end
@@ -1,5 +1,6 @@
1
1
  require 'mongify/translation/printer'
2
2
  require 'mongify/translation/process'
3
+ require 'mongify/translation/sync'
3
4
  module Mongify
4
5
  #
5
6
  # Actually runs the translation from sql to no sql
@@ -12,7 +13,7 @@ module Mongify
12
13
  # column "created_at", :datetime
13
14
  # column "updated_at", :datetime
14
15
  # end
15
- #
16
+ #
16
17
  # table "posts" do
17
18
  # column "id", :key
18
19
  # column "title", :string
@@ -22,7 +23,7 @@ module Mongify
22
23
  # column "created_at", :datetime
23
24
  # column "updated_at", :datetime
24
25
  # end
25
- #
26
+ #
26
27
  # table "comments", :embed_in => :posts, :on => :post_id do
27
28
  # column "id", :key
28
29
  # column "body", :text
@@ -31,13 +32,13 @@ module Mongify
31
32
  # column "created_at", :datetime
32
33
  # column "updated_at", :datetime
33
34
  # end
34
- #
35
+ #
35
36
  # table "preferences", :embed_in => :users, :as => :object do
36
37
  # column "id", :key
37
38
  # column "user_id", :integer, :references => "users"
38
39
  # column "notify_by_email", :boolean
39
40
  # end
40
- #
41
+ #
41
42
  # table "notes", :embed_in => true, :polymorphic => 'notable' do
42
43
  # column "id", :key
43
44
  # column "user_id", :integer, :references => "users"
@@ -47,11 +48,12 @@ module Mongify
47
48
  # column "created_at", :datetime
48
49
  # column "updated_at", :datetime
49
50
  # end
50
- #
51
-
51
+ #
52
+
52
53
  class Translation
53
54
  include Printer
54
55
  include Process
56
+ include Sync
55
57
  class << self
56
58
  # Returns an instance of a translation object
57
59
  # Takes a location of a translation file
@@ -60,7 +62,7 @@ module Mongify
60
62
  translation.instance_eval(File.read(file_name))
61
63
  translation
62
64
  end
63
-
65
+
64
66
  #Returns an instence of a translation object with a given sql connection layout loaded
65
67
  def load(connection)
66
68
  raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless connection.is_a?(Mongify::Database::SqlConnection)
@@ -76,52 +78,52 @@ module Mongify
76
78
  translation
77
79
  end
78
80
  end
79
-
81
+
80
82
  def initialize
81
83
  @all_tables = []
82
84
  end
83
-
85
+
84
86
  # finds table by name
85
87
  def find(name)
86
88
  all_tables.find{ |t| t.name == name }
87
89
  end
88
-
90
+
89
91
  # Creates a {Mongify::Database::Table} from the given input and adds it to the list of tables
90
92
  def table(table_name, options={}, &block)
91
93
  table = Mongify::Database::Table.new(table_name, options, &block)
92
94
  add_table(table)
93
95
  end
94
-
96
+
95
97
  # Adds a {Mongify::Database::Table} to the list of tables
96
98
  def add_table(table)
97
99
  @all_tables << table
98
100
  table
99
101
  end
100
-
102
+
101
103
  # Returns an array of all tables in the translation
102
104
  def all_tables
103
105
  @all_tables
104
106
  end
105
-
107
+
106
108
  # Returns an array of all tables that have not been ingored
107
109
  def tables
108
110
  all_tables.reject{ |t| t.ignored? || t.polymorphic? }
109
111
  end
110
-
112
+
111
113
  # Returns an array of all tables that have not been ignored and are just straight copy tables
112
114
  def copy_tables
113
115
  tables.reject{|t| t.embedded?}
114
116
  end
115
-
117
+
116
118
  # Returns an array of all tables that have a polymorphic relationship
117
119
  def polymorphic_tables
118
120
  all_tables.reject{ |t| t.ignored? || !t.polymorphic? }
119
121
  end
120
-
122
+
121
123
  # Returns an array of all tables that have not been ignored and are to be embedded
122
124
  def embed_tables
123
125
  tables.reject{|t| !t.embedded?}
124
126
  end
125
-
127
+
126
128
  end
127
129
  end
@@ -1,23 +1,15 @@
1
+ require 'mongify/translation/processor_common'
1
2
  module Mongify
2
3
  class Translation
3
4
  #
4
5
  # This module does the processing on the translation object
5
6
  #
7
+ include ProcessorCommon
6
8
  module Process
7
- attr_accessor :sql_connection, :no_sql_connection
8
9
  # Does the actual act of processing the translation.
9
- # Takes in boht a sql connection and a no sql connection
10
+ # Takes in both a sql connection and a no sql connection
10
11
  def process(sql_connection, no_sql_connection)
11
- raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless sql_connection.is_a?(Mongify::Database::SqlConnection)
12
- raise Mongify::NoSqlConnectionRequired, "Can only write to Mongify::Database::NoSqlConnection" unless no_sql_connection.is_a?(Mongify::Database::NoSqlConnection)
13
-
14
- self.sql_connection = sql_connection
15
- raise SqlConnectionInvalid, "SQL Connection is not valid" unless self.sql_connection.valid?
16
- self.no_sql_connection = no_sql_connection
17
- raise NoSqlConnectionInvalid, "noSql Connection is not valid" unless self.no_sql_connection.valid?
18
-
19
- no_sql_connection.ask_to_drop_database if no_sql_connection.forced?
20
-
12
+ prepare_connections(sql_connection, no_sql_connection)
21
13
  setup_db_index
22
14
  copy_data
23
15
  copy_embedded_tables
@@ -26,91 +18,27 @@ module Mongify
26
18
  remove_pre_mongified_ids
27
19
  nil
28
20
  end
29
-
21
+
30
22
  #######
31
23
  private
32
24
  #######
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
-
25
+
41
26
  # Does the straight copy (of tables)
42
27
  def copy_data
43
28
  self.copy_tables.each do |t|
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|
47
- no_sql_connection.insert_into(t.name, t.translate(row))
48
- Mongify::Status.publish('copy_data')
49
- end
50
- Mongify::Status.publish('copy_data', :action => 'finish')
51
- end
52
- end
53
-
54
- # Does a copy of the embedded tables
55
- def copy_embedded_tables
56
- self.embed_tables.each do |t|
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|
60
- target_row = no_sql_connection.find_one(t.embed_in, {:pre_mongified_id => row[t.embed_on]})
61
- next unless target_row.present?
62
- # puts "target_row = #{target_row.inspect}", "---"
63
- row, parent_row = t.translate(row, target_row)
64
- parent_row ||= {}
65
- parent_row.delete("_id")
66
- parent_row.delete(t.name.to_s)
67
- #puts "parent_row = #{parent_row.inspect}", "---"
68
- row.delete(t.embed_on)
69
- row.merge!(fetch_reference_ids(t, row))
70
- row.delete('pre_mongified_id')
71
- save_function_call = t.embedded_as_object? ? '$set' : '$addToSet'
72
- no_sql_connection.update(t.embed_in, target_row['_id'], append_parent_object({save_function_call => {t.name => row}}, parent_row))
73
- Mongify::Status.publish('copy_embedded')
74
- end
75
- Mongify::Status.publish('copy_embedded', :action => 'finish')
76
- end
77
- end
78
-
79
- # Moves over polymorphic data
80
- def copy_polymorphic_tables
81
- self.polymorphic_tables.each do |t|
82
- polymorphic_id_col, polymorphic_type_col = "#{t.polymorphic_as}_id", "#{t.polymorphic_as}_type"
83
- rows = sql_connection.select_rows(t.sql_name)
84
- Mongify::Status.publish('copy_polymorphic', :size => rows.count, :name => "Polymorphicizing #{t.name}", :action => 'add')
85
- rows.each do |row|
86
-
87
- #If no data is in the column, skip importing
88
-
89
- if (row[polymorphic_type_col])
90
- table_name = row[polymorphic_type_col].tableize
91
- new_id = no_sql_connection.get_id_using_pre_mongified_id(table_name, row[polymorphic_id_col])
92
- end
93
-
94
- row = t.translate(row)
95
- row[polymorphic_id_col] = new_id if new_id
96
- row.merge!(fetch_reference_ids(t, row))
97
- row.delete('pre_mongified_id')
98
-
99
- if t.embedded? && table_name
100
- row.delete(polymorphic_id_col)
101
- row.delete(polymorphic_type_col)
102
- save_function_call = t.embedded_as_object? ? '$set' : '$addToSet'
103
- no_sql_connection.update(table_name, new_id, {save_function_call => {t.name => row}})
104
- else
105
- no_sql_connection.insert_into(t.name, row)
29
+ sql_connection.select_rows(t.sql_name) do |rows, page, total_pages|
30
+ Mongify::Status.publish('copy_data', :size => rows.count, :name => "Copying #{t.name} (#{page}/#{total_pages})", :action => 'add')
31
+ insert_rows = []
32
+ rows.each do |row|
33
+ insert_rows << t.translate(row)
34
+ Mongify::Status.publish('copy_data')
106
35
  end
107
-
108
- Mongify::Status.publish('copy_polymorphic')
36
+ no_sql_connection.insert_into(t.name, insert_rows) unless insert_rows.empty?
37
+ Mongify::Status.publish('copy_data', :action => 'finish')
109
38
  end
110
- Mongify::Status.publish('copy_polymorphic', :action => 'finish')
111
39
  end
112
40
  end
113
-
41
+
114
42
  # Updates the reference ids in the no sql database
115
43
  def update_reference_ids
116
44
  self.copy_tables.each do |t|
@@ -125,42 +53,7 @@ module Mongify
125
53
  Mongify::Status.publish('update_references', :action => 'finish')
126
54
  end
127
55
  end
128
-
129
- # Fetches the new _id from a collection
130
- def fetch_reference_ids(table, row)
131
- attributes = {}
132
- table.reference_columns.each do |c|
133
- new_id = nil
134
- if row[c.name.to_s].is_a?(Array)
135
- new_id = []
136
- row[c.name.to_s].each do |old_id|
137
- new_id << no_sql_connection.get_id_using_pre_mongified_id(c.references.to_s, old_id)
138
- end
139
- else
140
- new_id = no_sql_connection.get_id_using_pre_mongified_id(c.references.to_s, row[c.name.to_s])
141
- end
142
- attributes.merge!(c.name => new_id) unless new_id.nil?
143
- end
144
- attributes
145
- end
146
-
147
- # Removes 'pre_mongiifed_id's from all collection
148
- def remove_pre_mongified_ids
149
- self.copy_tables.each do |t|
150
- Mongify::Status.publish('remove_pre_mongified', :size => 1, :name => "Removing pre_mongified_id #{t.name}", :action => 'add')
151
- no_sql_connection.remove_pre_mongified_ids(t.name)
152
- Mongify::Status.publish('remove_pre_mongified', :action => 'finish')
153
- end
154
- end
155
-
156
- # Used to append parent object values to an embedded update call
157
- def append_parent_object(object, parent)
158
- return object if parent.blank?
159
- object["$set"] = object.has_key?('$set') ? object["$set"].merge(parent) : parent
160
- object
161
- end
162
-
163
-
56
+
164
57
  end
165
58
  end
166
59
  end
@@ -0,0 +1,132 @@
1
+ module Mongify
2
+ class Translation
3
+ #
4
+ # This module does the processing on the translation object
5
+ #
6
+ module ProcessorCommon
7
+ attr_accessor :sql_connection, :no_sql_connection
8
+
9
+ #########
10
+ protected
11
+ #########
12
+
13
+ # Prepares connections for process & sync
14
+ def prepare_connections(sql_connection, no_sql_connection)
15
+ raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless sql_connection.is_a?(Mongify::Database::SqlConnection)
16
+ raise Mongify::NoSqlConnectionRequired, "Can only write to Mongify::Database::NoSqlConnection" unless no_sql_connection.is_a?(Mongify::Database::NoSqlConnection)
17
+
18
+ self.sql_connection = sql_connection
19
+ raise SqlConnectionInvalid, "SQL Connection is not valid" unless self.sql_connection.valid?
20
+ self.no_sql_connection = no_sql_connection
21
+ raise NoSqlConnectionInvalid, "noSql Connection is not valid" unless self.no_sql_connection.valid?
22
+
23
+ no_sql_connection.ask_to_drop_database if no_sql_connection.forced?
24
+
25
+ end
26
+
27
+ # Setups up pre_mongifed_id as an index to speed up lookup performance
28
+ def setup_db_index
29
+ self.copy_tables.each do |t|
30
+ no_sql_connection.create_pre_mongified_id_index(t.name)
31
+ end
32
+ end
33
+
34
+ # Does a copy of the embedded tables
35
+ def copy_embedded_tables
36
+ self.embed_tables.each do |t|
37
+ rows = sql_connection.select_rows(t.sql_name)
38
+ Mongify::Status.publish('copy_embedded', :size => rows.count, :name => "Embedding #{t.name}", :action => 'add')
39
+ rows.each do |row|
40
+ target_row = no_sql_connection.find_one(t.embed_in, {:pre_mongified_id => row[t.embed_on]})
41
+ next unless target_row.present?
42
+ row, parent_row, unset_keys = t.translate(row, target_row)
43
+ parent_row ||= {}
44
+ parent_row.delete("_id")
45
+ parent_row.delete(t.name.to_s)
46
+ unset_keys ||= {}
47
+ row.delete(t.embed_on)
48
+ row.merge!(fetch_reference_ids(t, row))
49
+ row.delete('pre_mongified_id')
50
+ save_function_call = t.embedded_as_object? ? '$set' : '$addToSet'
51
+ no_sql_connection.update(t.embed_in, target_row['_id'], append_parent_object({save_function_call => {t.name => row}}, parent_row, unset_keys))
52
+ Mongify::Status.publish('copy_embedded')
53
+ end
54
+ Mongify::Status.publish('copy_embedded', :action => 'finish')
55
+ end
56
+ end
57
+
58
+ # Moves over polymorphic data
59
+ def copy_polymorphic_tables
60
+ self.polymorphic_tables.each do |t|
61
+ polymorphic_id_col, polymorphic_type_col = "#{t.polymorphic_as}_id", "#{t.polymorphic_as}_type"
62
+ rows = sql_connection.select_rows(t.sql_name)
63
+ Mongify::Status.publish('copy_polymorphic', :size => rows.count, :name => "Polymorphicizing #{t.name}", :action => 'add')
64
+ rows.each do |row|
65
+
66
+ #If no data is in the column, skip importing
67
+
68
+ if (row[polymorphic_type_col])
69
+ table_name = row[polymorphic_type_col].tableize
70
+ new_id = no_sql_connection.get_id_using_pre_mongified_id(table_name, row[polymorphic_id_col])
71
+ end
72
+
73
+ row = t.translate(row)
74
+ row[polymorphic_id_col] = new_id if new_id
75
+ row.merge!(fetch_reference_ids(t, row))
76
+ row.delete('pre_mongified_id')
77
+
78
+ if t.embedded? && table_name
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
86
+
87
+ Mongify::Status.publish('copy_polymorphic')
88
+ end
89
+ Mongify::Status.publish('copy_polymorphic', :action => 'finish')
90
+ end
91
+ end
92
+
93
+ # Fetches the new _id from a collection
94
+ def fetch_reference_ids(table, row)
95
+ attributes = {}
96
+ table.reference_columns.each do |c|
97
+ new_id = nil
98
+ if row[c.name.to_s].is_a?(Array)
99
+ new_id = []
100
+ row[c.name.to_s].each do |old_id|
101
+ new_id << no_sql_connection.get_id_using_pre_mongified_id(c.references.to_s, old_id)
102
+ end
103
+ else
104
+ new_id = no_sql_connection.get_id_using_pre_mongified_id(c.references.to_s, row[c.name.to_s])
105
+ end
106
+ attributes.merge!(c.name => new_id) unless new_id.nil?
107
+ end
108
+ attributes
109
+ end
110
+
111
+ # Removes 'pre_mongiifed_id's from all collection
112
+ def remove_pre_mongified_ids
113
+ self.copy_tables.each do |t|
114
+ Mongify::Status.publish('remove_pre_mongified', :size => 1, :name => "Removing pre_mongified_id #{t.name}", :action => 'add')
115
+ no_sql_connection.remove_pre_mongified_ids(t.name)
116
+ Mongify::Status.publish('remove_pre_mongified', :action => 'finish')
117
+ end
118
+ end
119
+
120
+ # Used to append parent object values to an embedded update call
121
+ def append_parent_object(object, parent, unset_keys = {})
122
+ return object if parent.blank?
123
+ object["$set"] = object.has_key?('$set') ? object["$set"].merge(parent) : parent
124
+ unless unset_keys.empty?
125
+ object["$unset"] = object.has_key?('$unset') ? object["$unset"].merge(unset_keys) : unset_keys
126
+ end
127
+ object
128
+ end
129
+
130
+ end
131
+ end
132
+ end