mongify 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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