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
@@ -0,0 +1,112 @@
1
+ require 'mongify/translation/processor_common'
2
+ module Mongify
3
+ class Translation
4
+ #
5
+ # This module does the sync processing on the translation object
6
+ #
7
+ include ProcessorCommon
8
+ module Sync
9
+ attr_accessor :max_updated_at
10
+ DRAFT_KEY = "__mongify_sync_draft__"
11
+ SYNC_HELPER_TABLE = "__mongify_sync_helper__"
12
+
13
+ class SyncHelperMigrator < ActiveRecord::Migration
14
+ def up
15
+ create_table SYNC_HELPER_TABLE, :id => false do |t|
16
+ t.string :table_name
17
+ t.datetime :last_updated_at
18
+ end
19
+ add_index SYNC_HELPER_TABLE, :table_name
20
+ end
21
+ end
22
+
23
+ # Does the actual act of sync processing the translation.
24
+ # Takes in both a sql connection and a no sql connection
25
+ def sync(sql_connection, no_sql_connection)
26
+ prepare_connections(sql_connection, no_sql_connection)
27
+ setup_sync_table
28
+ setup_db_index
29
+ sync_data
30
+ set_last_updated_at
31
+ copy_embedded_tables
32
+ sync_update_reference_ids
33
+ copy_polymorphic_tables
34
+ nil
35
+ end
36
+
37
+ #######
38
+ private
39
+ #######
40
+
41
+ def setup_sync_table
42
+ # make sure table exists
43
+ begin
44
+ self.sql_connection.execute("SELECT count(*) FROM #{SYNC_HELPER_TABLE}")
45
+ rescue
46
+ SyncHelperMigrator.new.up
47
+ end
48
+ # insert missing records for sync tables
49
+ self.copy_tables.each do |t|
50
+ if self.sql_connection.count(SYNC_HELPER_TABLE, "table_name = '#{t.sql_name}'") == 0
51
+ self.sql_connection.execute("INSERT INTO #{SYNC_HELPER_TABLE} (table_name, last_updated_at) VALUES ('#{t.sql_name}', '1970-01-01')")
52
+ end
53
+ end
54
+ end
55
+
56
+ # Does the straight copy (of tables)
57
+ def sync_data
58
+ self.copy_tables.each do |t|
59
+ q = "SELECT t.* FROM #{t.sql_name} t, #{SYNC_HELPER_TABLE} u " +
60
+ "WHERE t.updated_at > u.last_updated_at AND u.table_name = '#{t.sql_name}'"
61
+ rows = sql_connection.select_by_query(q)
62
+ Mongify::Status.publish('copy_data', :size => rows.count, :name => "Syncing #{t.name}", :action => 'add')
63
+ max_updated_at, max_updated_at_id = Time.new(1970), nil
64
+ rows.each do |row|
65
+ row_hash = t.translate(row)
66
+ updated_at = Time.parse(row['updated_at'])
67
+ if updated_at > max_updated_at
68
+ max_updated_at = updated_at
69
+ max_updated_at_id = row_hash['pre_mongified_id']
70
+ end
71
+ no_sql_connection.upsert(t.name, row_hash.merge({DRAFT_KEY => true}))
72
+ Mongify::Status.publish('copy_data')
73
+ end
74
+ (self.max_updated_at ||= {})[t.sql_name] = {'max_updated_at_id' => max_updated_at_id, 'key_column' => t.key_column.name}
75
+ Mongify::Status.publish('copy_data', :action => 'finish')
76
+ end
77
+ end
78
+
79
+ # Updates the reference ids in the no sql database
80
+ def sync_update_reference_ids
81
+ self.copy_tables.each do |t|
82
+ rows = no_sql_connection.select_by_query(t.name, {DRAFT_KEY => true})
83
+ Mongify::Status.publish('update_references', :size => rows.count, :name => "Updating References #{t.name}", :action => 'add')
84
+ rows.each do |row|
85
+ id = row["_id"]
86
+ attributes = fetch_reference_ids(t, row)
87
+ setter = {"$unset" => {DRAFT_KEY => true}}
88
+ setter["$set"] = attributes unless attributes.blank?
89
+ no_sql_connection.update(t.name, id, setter)
90
+ Mongify::Status.publish('update_references')
91
+ end
92
+ Mongify::Status.publish('update_references', :action => 'finish')
93
+ end
94
+ end
95
+
96
+ # Sets the last updated_at flag so that next sync doesn't unnecessarily copy old data
97
+ def set_last_updated_at
98
+ tables = self.copy_tables
99
+ Mongify::Status.publish('set_last_updated_at', :size => tables.length, :name => "Setting last_updated_at", :action => 'add')
100
+ tables.each do |t|
101
+ info = self.max_updated_at[t.sql_name]
102
+ if info && info['max_updated_at_id']
103
+ sql_connection.execute("UPDATE #{SYNC_HELPER_TABLE} SET last_updated_at = (SELECT updated_at FROM #{t.sql_name} WHERE #{info['key_column']} = '#{info['max_updated_at_id']}') WHERE table_name = '#{t.sql_name}'")
104
+ end
105
+ Mongify::Status.publish('set_last_updated_at')
106
+ end
107
+ Mongify::Status.publish('set_last_updated_at', :action => 'finish')
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -8,39 +8,39 @@ module Mongify
8
8
  def puts(msg)
9
9
  out_stream.puts(msg) if out_stream
10
10
  end
11
-
11
+
12
12
  # Outputs to stream using print method
13
13
  def print(msg)
14
14
  out_stream.print(msg) if out_stream
15
15
  end
16
-
16
+
17
17
  # Gets input from user
18
18
  def gets
19
19
  in_stream ? in_stream.gets : ''
20
20
  end
21
-
21
+
22
22
  # Outputs a question and gets input
23
23
  def request(msg)
24
24
  print(msg)
25
25
  gets.chomp
26
26
  end
27
-
27
+
28
28
  # Asks a yes or no question and waits for reply
29
29
  def ask(msg)
30
30
  request("#{msg} [yn] ") == 'y'
31
31
  end
32
-
32
+
33
33
  # Outputs a Warning (using puts command)
34
34
  def warn(msg)
35
35
  puts "WARNING: #{msg}"
36
36
  end
37
-
37
+
38
38
  # Outputs a message and aborts execution of app
39
39
  def abort(message)
40
40
  UI.puts "PROGRAM HALT: #{message}"
41
41
  Kernel.abort message
42
42
  end
43
-
43
+
44
44
  # Incoming stream
45
45
  def in_stream
46
46
  Configuration.in_stream
@@ -49,11 +49,11 @@ module Mongify
49
49
  def out_stream
50
50
  Configuration.out_stream
51
51
  end
52
-
52
+
53
53
  # Creates an instance of HighLine
54
54
  # which lets us figure out width of console
55
55
  # plus a whole lot more
56
- # @return [HighLine] instance
56
+ # @return [HighLine] instance
57
57
  def terminal_helper
58
58
  @terminal_helper ||= HighLine.new
59
59
  end
@@ -1,4 +1,4 @@
1
1
  module Mongify
2
2
  # Mongify's Current Version Number
3
- VERSION = "1.0.1"
3
+ VERSION = "1.1.0"
4
4
  end
@@ -23,10 +23,12 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency('cucumber', '>= 0.10')
24
24
  s.add_development_dependency('mocha', '>= 0.9.8')
25
25
  s.add_development_dependency('yard', '>= 0.5.3')
26
- s.add_development_dependency('sqlite3-ruby', '>= 1.3')
27
- s.add_development_dependency('mysql2', '~> 0.2.7')
26
+ s.add_development_dependency('sqlite3', '>= 1.3')
27
+ s.add_development_dependency('pg', '>= 0.17')
28
+ s.add_development_dependency('mysql2', '~> 0.3.1')
28
29
  s.add_development_dependency('watchr', '>= 0.6')
29
30
  s.add_development_dependency('rake')
31
+ s.add_development_dependency('pry-plus')
30
32
 
31
33
  s.files = `git ls-files`.split("\n")
32
34
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,19 @@
1
+ table 'teams' do
2
+ column 'id', :key
3
+ column 'name', :string
4
+ column 'phone', :string
5
+ column 'created_at', :datetime
6
+ column 'updated_at', :datetime
7
+ end
8
+
9
+ table 'coaches', :embed_in => 'teams', :as => 'object', :rename_to => 'coach' do
10
+ column 'id', :key
11
+ column 'team_id', :integer, :references => "teams"
12
+ column 'first_name', :string
13
+ column 'last_name', :string
14
+ column 'created_at', :datetime
15
+ column 'updated_at', :datetime
16
+ before_save do |row, parent_row|
17
+ row.write_attribute('phone',parent_row.delete('phone'))
18
+ end
19
+ end
@@ -12,7 +12,7 @@ table "preferences", :embed_in => :users, :as => :array do
12
12
  column "notify_by_email", :boolean
13
13
  column "created_at", :datetime, :ignore => true
14
14
  column "updated_at", :datetime, :ignore => true
15
-
15
+
16
16
  before_save do |row, parent|
17
17
  parent.notify_by_email = row.delete('notify_by_email')
18
18
  end
@@ -7,7 +7,7 @@ describe Mongify::CLI::Application do
7
7
  Mongify::Configuration.out_stream = nil
8
8
  @application.execute!.should == 0
9
9
  end
10
-
10
+
11
11
  it "should return a 1 on error" do
12
12
  @application = Mongify::CLI::Application.new(["door"])
13
13
  Mongify::Configuration.out_stream = nil
@@ -52,7 +52,7 @@ describe Mongify::CLI::Options do
52
52
  it "should call Configuration.parse" do
53
53
  Mongify::Configuration.should_receive(:parse).and_return(Mongify::Configuration.new)
54
54
  @options = Mongify::CLI::Options.new(['check', config_file])
55
- @options.parse
55
+ @options.parse
56
56
  end
57
57
  end
58
58
  end
@@ -11,19 +11,19 @@ describe Mongify::CLI::Command::Worker do
11
11
  @no_sql_connection.stub(:valid?).and_return(true)
12
12
  @no_sql_connection.stub(:has_connection?).and_return(true)
13
13
  @config.stub(:no_sql_connection).and_return(@no_sql_connection)
14
-
14
+
15
15
  @translation_file = 'spec/files/translation.rb'
16
-
16
+
17
17
  Mongify::Translation.stub(:load).and_return(stub(:print => 'worked'))
18
18
  @view = mock('view').as_null_object
19
19
  end
20
-
20
+
21
21
  context "list_commands" do
22
22
  it "should return same number as available" do
23
23
  Mongify::CLI::Command::Worker.list_commands.size.should == Mongify::CLI::Command::Worker::AVAILABLE_COMMANDS.size
24
24
  end
25
25
  end
26
-
26
+
27
27
  context "check command" do
28
28
  before(:each) do
29
29
  @command = Mongify::CLI::Command::Worker.new('check', @config)
@@ -42,17 +42,17 @@ describe Mongify::CLI::Command::Worker do
42
42
  lambda { @command = Mongify::CLI::Command::Worker.new('check').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
43
43
  end
44
44
  end
45
-
45
+
46
46
  context "non-existing command" do
47
47
  before(:each) do
48
48
  @command = Mongify::CLI::Command::Worker.new('unknown')
49
49
  end
50
-
50
+
51
51
  it "should report error" do
52
52
  @view.should_receive(:report_error)
53
53
  @command.execute(@view)
54
54
  end
55
-
55
+
56
56
  it "should output an error" do
57
57
  @view.should_receive(:output).with("Unknown action unknown\n\n")
58
58
  @command.execute(@view)
@@ -64,22 +64,22 @@ describe Mongify::CLI::Command::Worker do
64
64
  @command = Mongify::CLI::Command::Worker.new('translation', @config)
65
65
  Mongify::Translation.stub(:load).with(@sql_connection).and_return(stub(:print => 'worked'))
66
66
  end
67
-
67
+
68
68
  it "should require configuration file" do
69
69
  lambda { Mongify::CLI::Command::Worker.new('translation').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
70
70
  end
71
-
71
+
72
72
  it "should check sql connection" do
73
73
  @command.should_receive(:check_sql_connection).and_return(true)
74
74
  @command.execute(@view)
75
75
  end
76
-
76
+
77
77
  it "should call load on Translation" do
78
78
  Mongify::Translation.should_receive(:load).with(@sql_connection).and_return(stub(:print => 'worked'))
79
79
  @command.execute(@view)
80
80
  end
81
81
  end
82
-
82
+
83
83
  context "process command" do
84
84
  before(:each) do
85
85
  @command = Mongify::CLI::Command::Worker.new('process', @config, 'spec/files/translation.rb')
@@ -90,25 +90,54 @@ describe Mongify::CLI::Command::Worker do
90
90
  @view.should_receive(:report_success)
91
91
  @command.execute(@view)
92
92
  end
93
-
93
+
94
94
  it "should require config file" do
95
95
  lambda { @command = Mongify::CLI::Command::Worker.new('process').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
96
96
  end
97
-
97
+
98
98
  it "should require transaction file" do
99
99
  lambda { @command = Mongify::CLI::Command::Worker.new('process', @config).execute(@view) }.should raise_error(Mongify::TranslationFileNotFound)
100
100
  end
101
-
101
+
102
102
  it "should check_connection" do
103
103
  @command.should_receive(:check_connections).and_return(true)
104
104
  @command.execute(@view)
105
105
  end
106
-
106
+
107
107
  it "should call process on translation" do
108
108
  Mongify::Translation.should_receive(:parse).and_return(mock(:process => true))
109
109
  @command.execute(@view)
110
110
  end
111
-
112
-
113
111
  end
112
+
113
+ context "sync command" do
114
+ before(:each) do
115
+ @command = Mongify::CLI::Command::Worker.new('sync', @config, 'spec/files/translation.rb')
116
+ Mongify::Translation.stub(:parse).and_return(mock(:sync => true))
117
+ end
118
+ it "should report success" do
119
+ @view.should_receive(:report_error).never
120
+ @view.should_receive(:report_success)
121
+ @command.execute(@view)
122
+ end
123
+
124
+ it "should require config file" do
125
+ lambda { @command = Mongify::CLI::Command::Worker.new('sync').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
126
+ end
127
+
128
+ it "should require transaction file" do
129
+ lambda { @command = Mongify::CLI::Command::Worker.new('sync', @config).execute(@view) }.should raise_error(Mongify::TranslationFileNotFound)
130
+ end
131
+
132
+ it "should check_connection" do
133
+ @command.should_receive(:check_connections).and_return(true)
134
+ @command.execute(@view)
135
+ end
136
+
137
+ it "should call sync on translation" do
138
+ Mongify::Translation.should_receive(:parse).and_return(mock(:sync => true))
139
+ @command.execute(@view)
140
+ end
141
+ end
142
+
114
143
  end
@@ -4,41 +4,41 @@ describe Mongify::Database::Column do
4
4
  before(:each) do
5
5
  @column = Mongify::Database::Column.new('first_name')
6
6
  end
7
-
7
+
8
8
  it "should have name" do
9
9
  @column.name.should == 'first_name'
10
10
  end
11
11
  it "should have a sql_name" do
12
12
  @column.sql_name.should == 'first_name'
13
13
  end
14
-
14
+
15
15
  it "should allow you to omit the type while giving options" do
16
16
  @column = Mongify::Database::Column.new('account_id', :references => 'accounts')
17
17
  @column.options.should == {'references' => 'accounts'}
18
18
  end
19
-
19
+
20
20
  it "should get setup options" do
21
21
  @column = Mongify::Database::Column.new('account_id', :integer, :references => 'accounts')
22
22
  @column.options.should == {'references' => 'accounts'}
23
23
  end
24
-
24
+
25
25
  it "should force type to string if nil" do
26
26
  @column = Mongify::Database::Column.new('first_name', nil)
27
27
  @column.type.should == :string
28
28
  end
29
-
29
+
30
30
  context "auto_detect!" do
31
31
  it "should not auto detect automatically" do
32
32
  Mongify::Database::Column.should_receive(:auto_detect).never
33
33
  @column = Mongify::Database::Column.new('id', :integer)
34
34
  @column.should_not be_key
35
35
  end
36
-
36
+
37
37
  it "should auto_detect when option is passed in" do
38
38
  @column = Mongify::Database::Column.new('id', :integer, :auto_detect => true)
39
39
  @column.should be_key
40
40
  end
41
-
41
+
42
42
  context "id column" do
43
43
  before(:each) do
44
44
  @col = mock(:sql_name => 'id')
@@ -82,19 +82,19 @@ describe Mongify::Database::Column do
82
82
  end
83
83
  end
84
84
  end
85
-
85
+
86
86
  context "key?" do
87
87
  it "should be true" do
88
88
  @column = Mongify::Database::Column.new('id', :key)
89
89
  @column.should be_a_key
90
90
  end
91
-
91
+
92
92
  it "should be true" do
93
93
  @column = Mongify::Database::Column.new('first_name', :string)
94
94
  @column.should_not be_a_key
95
95
  end
96
96
  end
97
-
97
+
98
98
  context "rename_to" do
99
99
  before(:each) do
100
100
  @column = Mongify::Database::Column.new('surname', :string, :rename_to => 'last_name')
@@ -108,12 +108,12 @@ describe Mongify::Database::Column do
108
108
  it "should translate to new name" do
109
109
  @column.translate('value').should == {'last_name' => 'value'}
110
110
  end
111
-
111
+
112
112
  it "should rename this" do
113
113
  @column = Mongify::Database::Column.new("geoCode_longitude", :string, :rename_to => 'longitude')
114
114
  @column.translate(42.2222).should == {'longitude' => '42.2222'}
115
115
  end
116
-
116
+
117
117
  it "should be renamed" do
118
118
  @column.should be_renamed
119
119
  end
@@ -122,7 +122,7 @@ describe Mongify::Database::Column do
122
122
  col.should_not be_renamed
123
123
  end
124
124
  end
125
-
125
+
126
126
  context "options" do
127
127
  it "should allow to be set by name" do
128
128
  @column = Mongify::Database::Column.new('first_name')
@@ -134,8 +134,8 @@ describe Mongify::Database::Column do
134
134
  @column = Mongify::Database::Column.new('first_name')
135
135
  lambda { @column.unknown = "users" }.should raise_error(NoMethodError)
136
136
  end
137
- end
138
-
137
+ end
138
+
139
139
  context "as" do
140
140
  subject {Mongify::Database::Column.new('total', :decimal)}
141
141
  it "should default to string" do
@@ -173,7 +173,7 @@ describe Mongify::Database::Column do
173
173
  subject.scale.should be_zero
174
174
  end
175
175
  end
176
-
176
+
177
177
  context :to_print do
178
178
  before(:each) do
179
179
  @column = Mongify::Database::Column.new('first_name', :string)
@@ -195,14 +195,14 @@ describe Mongify::Database::Column do
195
195
  @column.to_print.should == %q{column "first_name", :key, :as => :integer}
196
196
  end
197
197
  end
198
-
198
+
199
199
  context :referenced? do
200
200
  it "should be true" do
201
201
  @column = Mongify::Database::Column.new('user_id', :integer, :references => 'users')
202
202
  @column.should be_a_referenced
203
203
  end
204
204
  end
205
-
205
+
206
206
  context :translate do
207
207
  it "should return a hash with the new translation" do
208
208
  @column = Mongify::Database::Column.new('first_name', :string)
@@ -213,7 +213,7 @@ describe Mongify::Database::Column do
213
213
  @column.should be_ignored
214
214
  @column.translate('bob').should == {}
215
215
  end
216
-
216
+
217
217
  it "should return pre_mongified_id when type is a key" do
218
218
  @column = Mongify::Database::Column.new('id', :key)
219
219
  @column.translate(123123).should == {"pre_mongified_id" => 123123}
@@ -295,7 +295,7 @@ describe Mongify::Database::Column do
295
295
  it "should return a string value" do
296
296
  @column.send(:type_cast, 101.43).should be_a_kind_of String
297
297
  end
298
-
298
+
299
299
  context :integer do
300
300
  before(:each) do
301
301
  @column = Mongify::Database::Column.new('price', :decimal, :as => :integer)
@@ -387,7 +387,7 @@ describe Mongify::Database::Column do
387
387
  @column.send(:type_cast, nil).should == result
388
388
  @column.send(:type_cast, "").should == result
389
389
  end
390
-
390
+
391
391
  end
392
392
  end
393
393
  end