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