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
@@ -3,9 +3,9 @@ module Mongify
3
3
  #
4
4
  # Sql connection configuration
5
5
  #
6
- #
6
+ #
7
7
  # Basic format should look something like this:
8
- #
8
+ #
9
9
  # sql_connection do
10
10
  # adapter "mysql"
11
11
  # host "localhost"
@@ -14,7 +14,7 @@ module Mongify
14
14
  # database "my_database"
15
15
  # end
16
16
  # Possible attributes:
17
- #
17
+ #
18
18
  # adapter
19
19
  # host
20
20
  # database
@@ -23,13 +23,15 @@ module Mongify
23
23
  # port
24
24
  # encoding
25
25
  # socket
26
- #
26
+ # batch_size
27
+ #
27
28
  class SqlConnection < Mongify::Database::BaseConnection
28
-
29
+
29
30
  # List of required fields to bulid a valid sql connection
30
31
  REQUIRED_FIELDS = %w{host adapter database}
31
-
32
- def initialize(options=nil)
32
+
33
+ def initialize(options={})
34
+ options['batch_size'] ||= 10000
33
35
  super(options)
34
36
  end
35
37
 
@@ -37,7 +39,7 @@ module Mongify
37
39
  def setup_connection_adapter
38
40
  ActiveRecord::Base.establish_connection(self.to_hash)
39
41
  end
40
-
42
+
41
43
  # Returns true or false depending if the record is valid
42
44
  def valid?
43
45
  return false unless @adapter
@@ -48,20 +50,20 @@ module Mongify
48
50
  end
49
51
  false
50
52
  end
51
-
53
+
52
54
  # Returns true or false depending if the connction actually talks to the database server.
53
55
  def has_connection?
54
56
  setup_connection_adapter
55
57
  connection.send(:connect) if ActiveRecord::Base.connection.respond_to?(:connect)
56
58
  true
57
59
  end
58
-
60
+
59
61
  # Returns the active_record connection
60
62
  def connection
61
63
  return nil unless has_connection?
62
64
  ActiveRecord::Base.connection
63
65
  end
64
-
66
+
65
67
  # Returns all the tables in the database server
66
68
  def tables
67
69
  return nil unless has_connection?
@@ -72,16 +74,43 @@ module Mongify
72
74
  def columns_for(table_name)
73
75
  self.connection.columns(table_name)
74
76
  end
75
-
77
+
76
78
  # Returns an array with hash values of all the records in a given table
77
- def select_rows(table_name)
78
- self.connection.select_all("SELECT * FROM #{table_name}")
79
+ def select_rows(table_name, &block)
80
+ return self.connection.select_all("SELECT * FROM #{table_name}") unless block_given?
81
+
82
+ row_count = count(table_name);
83
+ pages = (row_count.to_f/batch_size).ceil
84
+ (1..pages).each do |page|
85
+ rows = select_paged_rows(table_name, batch_size, page)
86
+ yield rows, page, pages
87
+ end
88
+
89
+ end
90
+
91
+ def select_paged_rows(table_name, batch_size, page)
92
+ connection.select_all("SELECT * FROM #{table_name} LIMIT #{batch_size} OFFSET #{(page - 1) * batch_size}")
93
+ end
94
+
95
+ # Returns an array with hash values of the records in a given table specified by a query
96
+ def select_by_query(query)
97
+ self.connection.select_all(query)
98
+ end
99
+
100
+ def count(table_name, where = nil)
101
+ q = "SELECT COUNT(*) FROM #{table_name}"
102
+ q = "#{q} WHERE #{where}" if where
103
+ self.connection.select_value(q).to_i
104
+ end
105
+
106
+ def execute(query)
107
+ self.connection.execute(query)
79
108
  end
80
109
 
81
110
  #######
82
111
  private
83
112
  #######
84
- # Used to check if this is a sqlite connection
113
+ # Used to check if this is a sqlite connection
85
114
  def sqlite_adapter?
86
115
  @adapter && (@adapter.downcase == 'sqlite' || @adapter.downcase == 'sqlite3')
87
116
  end
@@ -1,125 +1,130 @@
1
1
  module Mongify
2
2
  module Database
3
3
  #
4
- # A representation of a sql table and how it should map to a no_sql collection
4
+ # A representation of a sql table and how it should map to a no_sql collection
5
5
  #
6
6
  # ==== Structure
7
- #
7
+ #
8
8
  # Structure for defining a table is as follows:
9
9
  # table "table_name", {options} do
10
10
  # # columns go here...
11
11
  # end
12
- #
12
+ #
13
13
  # ==== Options
14
- #
14
+ #
15
15
  # Table Options are as follow:
16
16
  # table "table_name" # Does a straight copy of the table
17
17
  # table "table_name", :embed_in => 'users' # Embeds table_name into users, assuming a user_id is present in table_name.
18
18
  # # This will also assume you want the table embedded as an array.
19
- #
19
+ #
20
20
  # table "table_name", # Embeds table_name into users, linking it via a owner_id
21
21
  # :embed_in => 'users', # This will also assume you want the table embedded as an array.
22
- # :on => 'owner_id'
23
- #
22
+ # :on => 'owner_id'
23
+ #
24
24
  # table "table_name", # Embeds table_name into users as a one to one relationship
25
25
  # :embed_in => 'users', # This also assumes you have a user_id present in table_name
26
26
  # :on => 'owner_id', # You can also specify both :on and :as options when embedding
27
- # :as => 'object' # NOTE: If you rename the owner_id column, make sure you
27
+ # :as => 'object' # NOTE: If you rename the owner_id column, make sure you
28
28
  # # update the :on to the new column name
29
- #
30
- #
29
+ #
30
+ #
31
31
  # table "table_name", :rename_to => 'my_table' # This will allow you to rename the table as it's getting process
32
32
  # # Just remember that columns that use :reference need to
33
33
  # # reference the new name.
34
- #
34
+ #
35
35
  # table "table_name", :ignore => true # This will ignore the whole table (like it doesn't exist)
36
36
  # # This option is good for tables like: schema_migrations
37
- #
37
+ #
38
38
  # table "table_name", # This allows you to specify the table as being polymorphic
39
39
  # :polymorphic => 'notable', # and provide the name of the polymorphic relationship.
40
40
  # :embed_in => true # Setting embed_in => true allows the relationship to be
41
41
  # # embedded directly into the parent class.
42
42
  # # If you do not embed it, the polymorphic table will be copied in to
43
43
  # # MongoDB and the notable_id will be updated to the new BSON::ObjectID
44
- #
44
+ #
45
45
  # table "table_name" do # A table can take a before_save block that will be called just
46
46
  # before_save do |row| # before the row is saved to the no sql database.
47
47
  # row.admin = row.delete('permission').to_i > 50 # This gives you the ability to do very powerful things like:
48
48
  # end # Moving records around, renaming records, changing values in row based on
49
49
  # end # some values! Checkout Mongify::Database::DataRow to learn more
50
- #
51
- #
50
+ #
51
+ #
52
52
  # table "preferences", :embed_in => "users" do # As of version 0.2, embedded tables with a before_save will take an
53
53
  # before_save do |pref_row, user_row| # extra argument which is the parent row of the embedded table.
54
54
  # user_row.email_me = pref_row.delete('email_me') # This gives you the ability to move things from an embedded table row
55
55
  # end # to the parent row.
56
56
  # end
57
- #
57
+ #
58
58
 
59
59
  class Table
60
-
60
+
61
61
  attr_accessor :name, :sql_name
62
62
  attr_reader :options, :columns
63
-
63
+
64
64
  def initialize(sql_name, options={}, &block)
65
65
  @columns = []
66
66
  @column_lookup = {}
67
67
  @options = options.stringify_keys
68
68
  self.sql_name = sql_name
69
-
69
+
70
70
  self.instance_exec(&block) if block_given?
71
-
71
+
72
72
  import_columns
73
-
73
+
74
74
  self
75
75
  end
76
-
76
+
77
77
  # Returns the no_sql collection name
78
78
  def name
79
79
  @name ||= @options['rename_to']
80
80
  @name ||= self.sql_name
81
81
  end
82
-
82
+
83
83
  # Returns true if table is ignored
84
84
  def ignored?
85
85
  @options['ignore']
86
86
  end
87
-
87
+
88
88
  # Returns true if table is marked as polymorphic
89
89
  def polymorphic?
90
90
  !!@options['polymorphic']
91
91
  end
92
-
92
+
93
93
  # Returns the name of the polymorphic association
94
94
  def polymorphic_as
95
95
  @options['polymorphic'].to_s
96
96
  end
97
-
97
+
98
98
  # Add a Database Column to the table
99
99
  # This expects to get a {Mongify::Database::Column} or it will raise {Mongify::DatabaseColumnExpected} otherwise
100
100
  def add_column(column)
101
101
  raise Mongify::DatabaseColumnExpected, "Expected a Mongify::Database::Column" unless column.is_a?(Mongify::Database::Column)
102
102
  add_and_index_column(column)
103
103
  end
104
-
104
+
105
105
  # Lets you build a column in the table
106
106
  def column(name, type=nil, options={})
107
107
  options, type = type, nil if type.is_a?(Hash)
108
108
  type = type.to_sym if type
109
109
  add_and_index_column(Mongify::Database::Column.new(name, type, options))
110
110
  end
111
-
111
+
112
112
  # Returns the column if found by the sql_name
113
113
  def find_column(name)
114
114
  return nil unless (index = @column_lookup[name.to_s.downcase.to_sym])
115
115
  @columns[index]
116
116
  end
117
-
117
+
118
118
  # Returns a array of Columns which reference other columns
119
119
  def reference_columns
120
- @columns.reject{ |c| !c.referenced? }
120
+ @columns.reject{ |c| !c.referenced? }
121
+ end
122
+
123
+ # Returns the column of type :key
124
+ def key_column
125
+ @columns.find{ |c| c.type == :key }
121
126
  end
122
-
127
+
123
128
  # Returns a translated row
124
129
  # Takes in a hash of values
125
130
  def translate(row, parent=nil)
@@ -130,13 +135,13 @@ module Mongify
130
135
  end
131
136
  run_before_save(new_row, parent)
132
137
  end
133
-
134
-
138
+
139
+
135
140
  # Returns the name of the embed_in collection
136
141
  def embed_in
137
142
  @options['embed_in'].to_s unless @options['embed_in'].nil?
138
143
  end
139
-
144
+
140
145
  # Returns the type of embed it will be [object or array]
141
146
  def embed_as
142
147
  return nil unless embedded?
@@ -148,58 +153,69 @@ module Mongify
148
153
  def embedded_as_object?
149
154
  embed_as == 'object'
150
155
  end
151
-
156
+
152
157
  # Returns true if this is an embedded table
153
158
  def embedded?
154
159
  embed_in.present?
155
160
  end
156
-
161
+
157
162
  # Returns the name of the target column to embed on
158
163
  def embed_on
159
164
  return nil unless embedded?
160
165
  (@options['on'] || "#{@options['embed_in'].to_s.singularize}_id").to_s
161
166
  end
162
-
167
+
163
168
  # Used to save a block to be ran after the row has been processed but before it's saved to the no sql database
164
169
  def before_save(&block)
165
170
  @before_save_callback = block
166
171
  end
167
-
172
+
168
173
  #Used to remove any before save filter
169
174
  def remove_before_save_filter!
170
175
  @before_save_callback = nil
171
176
  end
172
-
177
+
173
178
  #######
174
179
  private
175
180
  #######
176
-
181
+
177
182
  # Runs the before save
178
183
  # Returns: a new modified row
179
184
  def run_before_save(row, parent=nil)
180
185
  parentrow = Mongify::Database::DataRow.new(parent) unless parent.nil?
181
186
  datarow = Mongify::Database::DataRow.new(row)
187
+
188
+ # don't allow deletion of pre_mongified_id, sync needs it!
189
+ pre_mongified_id = row['pre_mongified_id']
182
190
  @before_save_callback.call(datarow, parentrow) unless @before_save_callback.nil?
183
- if parentrow
184
- [datarow.to_hash, parentrow.to_hash]
191
+ new_row = datarow.to_hash
192
+ new_row['pre_mongified_id'] = pre_mongified_id if pre_mongified_id
193
+
194
+ if parentrow
195
+ parentrow_hash = parentrow.to_hash
196
+ unsets = parent.keys.inject({}) do |unset_keys, key|
197
+ unset_keys[key] = '1' unless parentrow_hash.has_key?(key)
198
+ unset_keys
199
+ end
200
+ [new_row, parentrow_hash, unsets]
185
201
  else
186
- datarow.to_hash
202
+ new_row
187
203
  end
188
204
  end
189
-
205
+
190
206
  # Indexes the column on the sql_name and adds column to the array
191
207
  def add_and_index_column(column)
192
208
  @column_lookup[column.sql_name.downcase.to_sym] = @columns.size
193
209
  @columns << column
194
210
  column
195
211
  end
196
-
212
+
197
213
  # Imports colunms that are sent in via the options['columns']
198
214
  def import_columns
199
215
  return unless import_columns = @options.delete('columns')
200
216
  import_columns.each { |c| add_column(c) }
201
217
  end
202
-
218
+
203
219
  end
204
220
  end
205
221
  end
@@ -1,17 +1,17 @@
1
1
  module Mongify
2
2
  # Base Mongify Error
3
3
  class MongifyError < RuntimeError; end
4
-
4
+
5
5
  # Not Implemented Error from Mongify
6
6
  class NotImplementedMongifyError < MongifyError; end
7
-
7
+
8
8
  # File Not Found Exception
9
9
  class FileNotFound < MongifyError; end
10
10
  # Raise when configuration file is missing
11
11
  class ConfigurationFileNotFound < FileNotFound; end
12
12
  # Raised when Translation file is missing
13
13
  class TranslationFileNotFound < FileNotFound; end
14
-
14
+
15
15
  # Basic Configuration Error Exception
16
16
  class ConfigurationError < MongifyError; end
17
17
  # Raise when a sqlConnection is required but not given
@@ -22,13 +22,13 @@ module Mongify
22
22
  class NoSqlConnectionRequired < ConfigurationError; end
23
23
  # Raised when a NoSqlConfiguration is invalid?
24
24
  class NoSqlConnectionInvalid < ConfigurationError; end
25
-
25
+
26
26
  # Raised when a Mongify::Database::Column is expected but not given
27
27
  class DatabaseColumnExpected < ConfigurationError; end
28
28
 
29
29
  # Raised when application has no root folder set
30
30
  class RootMissing < MongifyError; end
31
-
31
+
32
32
  # Raised when an invalid option is passed via CLI
33
33
  class InvalidOption < MongifyError; end
34
34
  end
@@ -8,7 +8,7 @@
8
8
  # You can redistribute it and/or modify it under the terms
9
9
  # of Ruby's license.
10
10
  #
11
- # This has been modified by
11
+ # This has been modified by
12
12
  # Andrew Kalek
13
13
  # Anlek Consulting
14
14
  # http://anlek.com
@@ -48,8 +48,8 @@ module Mongify
48
48
  # Formatting for the actual bar
49
49
  def fmt_bar
50
50
  bar_width = do_percentage * @terminal_width / 100
51
- sprintf("|%s%s|",
52
- @bar_mark * bar_width,
51
+ sprintf("|%s%s|",
52
+ @bar_mark * bar_width,
53
53
  " " * (@terminal_width - bar_width))
54
54
  end
55
55
 
@@ -65,9 +65,9 @@ module Mongify
65
65
 
66
66
  # Formatting for file transfer
67
67
  def fmt_stat_for_file_transfer
68
- if @finished_p then
68
+ if @finished_p then
69
69
  sprintf("%s %s %s", bytes, transfer_rate, elapsed)
70
- else
70
+ else
71
71
  sprintf("%s %s %s", bytes, transfer_rate, eta)
72
72
  end
73
73
  end
@@ -76,7 +76,7 @@ module Mongify
76
76
  def fmt_title
77
77
  @title[0,(@title_width - 1)] + ":"
78
78
  end
79
-
79
+
80
80
  # Formatting for count (x/y)
81
81
  def fmt_count
82
82
  sprintf('%15s', "(#{@current}/#{@total})")
@@ -106,7 +106,7 @@ module Mongify
106
106
  def bytes
107
107
  convert_bytes(@current)
108
108
  end
109
-
109
+
110
110
  # Gets formatting for time
111
111
  def format_time (t)
112
112
  t = t.to_i
@@ -126,19 +126,19 @@ module Mongify
126
126
  sprintf("ETA: %s", format_time(eta))
127
127
  end
128
128
  end
129
-
129
+
130
130
  # Returns elapsed time
131
131
  def elapsed
132
132
  elapsed = Time.now - @start_time
133
133
  sprintf("Time: %s", format_time(elapsed))
134
134
  end
135
-
135
+
136
136
  # Returns end of line
137
137
  # @return [String] "\n" or "\r"
138
138
  def eol
139
139
  if @finished_p then "\n" else "\r" end
140
140
  end
141
-
141
+
142
142
  # Calculates percentage
143
143
  # @return [Number] the percentage
144
144
  def do_percentage
@@ -148,7 +148,7 @@ module Mongify
148
148
  @current * 100 / @total
149
149
  end
150
150
  end
151
-
151
+
152
152
  # Gets the width of the terminal window
153
153
  def get_width
154
154
  UI.terminal_helper.output_cols
@@ -157,14 +157,14 @@ module Mongify
157
157
  # Draws the bar
158
158
  def show
159
159
  return unless @out
160
- arguments = @format_arguments.map {|method|
160
+ arguments = @format_arguments.map {|method|
161
161
  method = sprintf("fmt_%s", method)
162
162
  send(method)
163
163
  }
164
164
  line = sprintf(@format, *arguments)
165
165
 
166
166
  width = get_width
167
- if line.length == width - 1
167
+ if line.length == width - 1
168
168
  @out.print(line + eol)
169
169
  @out.flush
170
170
  elsif line.length >= width
@@ -188,7 +188,7 @@ module Mongify
188
188
  end
189
189
 
190
190
  # Use "!=" instead of ">" to support negative changes
191
- if cur_percentage != prev_percentage ||
191
+ if cur_percentage != prev_percentage ||
192
192
  Time.now - @previous_time >= 1 || @finished_p
193
193
  show
194
194
  end
@@ -209,7 +209,7 @@ module Mongify
209
209
  @finished_p = true
210
210
  show
211
211
  end
212
-
212
+
213
213
  # Returns if the bar is finished
214
214
  def finished?
215
215
  @finished_p