mongify 0.0.4

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 (63) hide show
  1. data/.gitignore +20 -0
  2. data/CHANGELOG.rdoc +22 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +68 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +86 -0
  7. data/Rakefile +73 -0
  8. data/bin/mongify +14 -0
  9. data/features/options.feature +46 -0
  10. data/features/print.feature +10 -0
  11. data/features/process.feature +19 -0
  12. data/features/step_definitions/mongify_steps.rb +42 -0
  13. data/features/step_definitions/mongo_steps.rb +7 -0
  14. data/features/support/env.rb +28 -0
  15. data/lib/mongify.rb +22 -0
  16. data/lib/mongify/cli.rb +8 -0
  17. data/lib/mongify/cli/application.rb +43 -0
  18. data/lib/mongify/cli/help_command.rb +16 -0
  19. data/lib/mongify/cli/options.rb +94 -0
  20. data/lib/mongify/cli/report.rb +11 -0
  21. data/lib/mongify/cli/version_command.rb +18 -0
  22. data/lib/mongify/cli/worker_command.rb +93 -0
  23. data/lib/mongify/configuration.rb +46 -0
  24. data/lib/mongify/database.rb +5 -0
  25. data/lib/mongify/database/base_connection.rb +78 -0
  26. data/lib/mongify/database/column.rb +81 -0
  27. data/lib/mongify/database/no_sql_connection.rb +62 -0
  28. data/lib/mongify/database/sql_connection.rb +61 -0
  29. data/lib/mongify/database/table.rb +74 -0
  30. data/lib/mongify/exceptions.rb +13 -0
  31. data/lib/mongify/translation.rb +55 -0
  32. data/lib/mongify/translation/printer.rb +20 -0
  33. data/lib/mongify/translation/process.rb +61 -0
  34. data/lib/mongify/ui.rb +45 -0
  35. data/lib/mongify/version.rb +3 -0
  36. data/mongify.gemspec +42 -0
  37. data/spec/default.watch +199 -0
  38. data/spec/files/base_configuration.rb +9 -0
  39. data/spec/files/empty_translation.rb +0 -0
  40. data/spec/files/simple_translation.rb +26 -0
  41. data/spec/mongify/cli/application_spec.rb +19 -0
  42. data/spec/mongify/cli/help_command_spec.rb +18 -0
  43. data/spec/mongify/cli/options_spec.rb +62 -0
  44. data/spec/mongify/cli/version_command_spec.rb +24 -0
  45. data/spec/mongify/cli/worker_command_spec.rb +115 -0
  46. data/spec/mongify/configuration_spec.rb +25 -0
  47. data/spec/mongify/database/base_connection_spec.rb +59 -0
  48. data/spec/mongify/database/column_spec.rb +103 -0
  49. data/spec/mongify/database/no_sql_connection_spec.rb +131 -0
  50. data/spec/mongify/database/sql_connection_spec.rb +91 -0
  51. data/spec/mongify/database/table_spec.rb +120 -0
  52. data/spec/mongify/translation/printer_spec.rb +34 -0
  53. data/spec/mongify/translation/process_spec.rb +68 -0
  54. data/spec/mongify/translation_spec.rb +59 -0
  55. data/spec/mongify/ui_spec.rb +73 -0
  56. data/spec/mongify_spec.rb +15 -0
  57. data/spec/spec.opts +1 -0
  58. data/spec/spec_helper.rb +22 -0
  59. data/spec/support/config_reader.rb +21 -0
  60. data/spec/support/database.example +17 -0
  61. data/spec/support/database_output.txt +27 -0
  62. data/spec/support/generate_database.rb +91 -0
  63. metadata +370 -0
@@ -0,0 +1,3 @@
1
+ module Mongify
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,42 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "mongify/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "mongify"
6
+ s.version = Mongify::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Andrew Kalek"]
9
+ s.email = ["andrew.kalek@anlek.com"]
10
+ s.homepage = "http://github.com/anlek/mongify"
11
+ s.summary = %q{Translate your SQL data to MongoDB}
12
+ s.description = %q{Mongify allows you to map your data from a sql database and into a mongodb document database.}
13
+
14
+ s.default_executable = "mongify"
15
+
16
+ s.add_dependency('activerecord', ">= 2.3.10")
17
+ s.add_dependency('activesupport', ">= 2.3.10")
18
+ s.add_dependency('mongo', ">= 1.1.5")
19
+ s.add_dependency('bson_ext', ">= 1.1.5")
20
+ s.add_dependency('net-ssh', ">= 2.0")
21
+
22
+ s.add_development_dependency('rspec', '>= 2.0')
23
+ s.add_development_dependency('rcov', '>= 0.9.9')
24
+ s.add_development_dependency('cucumber', '>= 0.10')
25
+ s.add_development_dependency('mocha', '>= 0.9.8')
26
+ s.add_development_dependency('yard', '>= 0.5.3')
27
+ s.add_development_dependency('watchr', '>= 0.6')
28
+ s.add_development_dependency('sqlite3-ruby', '>= 1.3')
29
+ s.add_development_dependency('mysql', '>= 2.8.1')
30
+
31
+ s.files = `git ls-files`.split("\n")
32
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
33
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
34
+ s.require_paths = ["lib"]
35
+
36
+
37
+ s.extra_rdoc_files = [
38
+ "CHANGELOG.rdoc",
39
+ "README.rdoc"
40
+ ]
41
+ s.rdoc_options = ["--title", "Mongify -- SQL db to Mongo db converter", "--main", "README", "--line-numbers", "--inline-source"]
42
+ end
@@ -0,0 +1,199 @@
1
+ # Run me with:
2
+ #
3
+ # $ watchr specs.watchr
4
+
5
+ # --------------------------------------------------
6
+ # Convenience Methods
7
+ # --------------------------------------------------
8
+
9
+
10
+ def clear_console
11
+ puts "\e[H\e[2J"
12
+ end
13
+
14
+ def banner(*messages)
15
+ messages = messages.to_a unless messages.is_a?(Array)
16
+ spacing = 100
17
+ char = "*"
18
+ puts "\n",char * spacing
19
+ messages.each do |message|
20
+ puts "#{char}#{message.to_s.center(spacing-2)}#{char}"
21
+ end
22
+ puts char * spacing, "\n\n"
23
+ end
24
+
25
+ def strip_colors(message='')
26
+ message.gsub(/\e\[(\d+)m/, '')
27
+ end
28
+
29
+ # --------------------------------------------------
30
+ # Rspec Methods
31
+ # --------------------------------------------------
32
+
33
+
34
+ def all_spec_files
35
+ Dir['spec/**/*_spec.rb']
36
+ end
37
+
38
+ def run_spec_matching(thing_to_match)
39
+ matches = all_spec_files.grep(/#{thing_to_match}/i)
40
+ if matches.empty?
41
+ puts "Sorry, but there were no matches for #{thing_to_match}."
42
+ else
43
+ run_spec matches.join(' ')
44
+ end
45
+ end
46
+
47
+ def run_spec(files_to_run)
48
+ clear_console
49
+ puts("Running spec(s): #{files_to_run}")
50
+ result = %x(bundle exec rspec -c -b #{files_to_run})
51
+ rspec_to_growl(result)
52
+ no_int_for_you
53
+ end
54
+
55
+ def run_all_specs
56
+ run_spec(all_spec_files.join(' '))
57
+ end
58
+
59
+ # --------------------------------------------------
60
+ # Cuucmber Methods
61
+ # --------------------------------------------------
62
+
63
+ def run_cucumber_scenario (scenario_path='features', options={})
64
+ #options[:tag] ||= nil
65
+ run_cucumber(scenario_path, options)
66
+ end
67
+
68
+ def run_wip_scenarios
69
+ run_cucumber('features', :tags => "@wip")
70
+ end
71
+
72
+ def run_cucumber files_to_run, options={}
73
+ return puts "** Not running cucumber (#{files_to_run}) due to rspec failure **" unless rspec_passed?
74
+ clear_console
75
+ tags = (options[:tags].nil?) ? nil : "--tags #{options[:tags]}"
76
+ puts "Running Feature: #{files_to_run} #{ "with a #{options[:tags]} tag(s)" if tags }"
77
+
78
+ result = %x(bundle exec cucumber -c #{tags} #{files_to_run})
79
+ features_to_growl(result)
80
+ result
81
+ end
82
+
83
+ # --------------------------------------------------
84
+ # Growl Messages
85
+ # --------------------------------------------------
86
+ def growl(message, title="Unknown", image="~/.watchr/failed.png", priority=0)
87
+ return if message.empty?
88
+ begin
89
+ message = strip_colors(message)
90
+ banner(title.upcase, message)
91
+ growlnotify = `which growlnotify`.chomp
92
+ options = "--name Watchr --image '#{File.expand_path(image)}' -m '#{message}' -p #{priority || 0} -t #{title}"
93
+ system %(#{growlnotify} #{options} &)
94
+ rescue Exception => e
95
+ puts "ERROR: #{e}"
96
+ end
97
+ end
98
+
99
+ def rspec_to_growl(message)
100
+ result = message.split("\n").find{|l| l =~ /(\d+) examples?/}
101
+ @status = if !result || !result.include?('0 failures')
102
+ image = "~/.watchr/failed.png"
103
+ priority = 2
104
+ puts message.to_s
105
+ rspec_failed
106
+ "failed"
107
+ elsif result.include?("pending")
108
+ image = "~/.watchr/pending.png"
109
+ priority = 1
110
+ puts message.to_s
111
+ rspec_passed
112
+ "pending"
113
+ else
114
+ image = "~/.watchr/passed.png"
115
+ priority = -2
116
+ rspec_passed
117
+ "passed"
118
+ end
119
+ begin
120
+ growl(result, "RSpec - #{@status}", image, priority)
121
+ rescue
122
+ banner("Failed to growl notifiy for specs")
123
+ puts message
124
+ end
125
+ end
126
+
127
+ def features_to_growl(messages)
128
+ begin
129
+ messages = messages.split("\n") unless messages.is_a? Array
130
+ scenario_idx = messages.rindex(messages.find{|line| line =~ /^(\d) scenario/})
131
+ scenario, step, timing = messages[scenario_idx..(scenario_idx + 2)]
132
+ if scenario.include?('failed')
133
+ image = "~/.watchr/failed.png"
134
+ priority = 2
135
+ puts messages.join("\n")
136
+ elsif scenario.include?("undefined") || scenario.include?('pending')
137
+ image = "~/.watchr/pending.png"
138
+ priority = 1
139
+ puts messages.join("\n") #messages[(scenario_idx+3)..-1].join("\n")
140
+ else
141
+ image = "~/.watchr/passed.png"
142
+ priority = -2
143
+ end
144
+ growl("#{scenario}, #{step}", "Features", image, priority) rescue banner("Failed to growl notifiy for features".upcase)
145
+ rescue Exception => e
146
+ puts "ERROR in features growl: #{e}"
147
+ end
148
+ end
149
+
150
+
151
+ # --------------------------------------------------
152
+ # Watchr Rules
153
+ # --------------------------------------------------
154
+ watch('^spec/(.*)_spec\.rb') { |m| run_spec_matching(m[1]) }
155
+ watch('^lib/(.*)\.rb') { |m| run_spec_matching(m[1]); run_wip_scenarios }
156
+ watch('^app/(models|controllers)/(.*).rb') { |m| run_spec_matching("#{m[1]}/#{m[2]}"); run_wip_scenarios }
157
+ watch('^app/views/([^/]*)/(.*).(haml|erb)') { run_wip_scenarios }
158
+ watch('^spec/spec_helper\.rb') { run_all_specs }
159
+ watch('^spec/support/.*\.rb') { run_all_specs }
160
+ watch('^spec/factories/.*\.rb') { run_all_specs }
161
+ watch('^config/(.*)\.rb') { run_all_specs; run_cucumber_scenario }
162
+ watch('^features/(.*)\.feature') { |m| run_cucumber_scenario(m[0], :tags => "@wip") }
163
+ watch('^features/(.*)\.rb') { run_wip_scenarios }
164
+
165
+
166
+ # --------------------------------------------------
167
+ # Fail logging
168
+ # --------------------------------------------------
169
+ def rspec_failed
170
+ @rspec_failed = true
171
+ end
172
+ def rspec_passed
173
+ @rspec_failed = false
174
+ end
175
+ def rspec_passed?
176
+ !@rspec_failed
177
+ end
178
+
179
+ # --------------------------------------------------
180
+ # Signal Handling
181
+ # --------------------------------------------------
182
+
183
+ def no_int_for_you
184
+ @sent_an_int = nil
185
+ end
186
+
187
+ Signal.trap 'INT' do
188
+ if @sent_an_int then
189
+ clear_console
190
+ puts " Stopped watching."
191
+ exit
192
+ else
193
+ puts " Resetting tests... if you want to quit, press control-c again."
194
+ @sent_an_int = true
195
+ Kernel.sleep 1.5
196
+ run_all_specs
197
+ run_cucumber_scenario
198
+ end
199
+ end
@@ -0,0 +1,9 @@
1
+ sql_connection do
2
+ adapter 'sqlite3'
3
+ database 'spec/tmp/test.sqlite'
4
+ end
5
+
6
+ mongodb_connection do
7
+ host '127.0.0.1'
8
+ database 'mongify_test'
9
+ end
File without changes
@@ -0,0 +1,26 @@
1
+ table "users" do
2
+ column "id", :key
3
+ column "first_name", :string
4
+ column "last_name", :string
5
+ column "created_at", :datetime
6
+ column "updated_at", :datetime
7
+ end
8
+
9
+ table "posts" do
10
+ column "id", :key
11
+ column "title", :string
12
+ column "owner_id", :integer, :references => :users
13
+ column "body", :text
14
+ column "published_at", :datetime
15
+ column "created_at", :datetime
16
+ column "updated_at", :datetime
17
+ end
18
+
19
+ table "comments", :embed_in => :posts, :on => :post_id do
20
+ column "id", :key
21
+ column "body", :text
22
+ column "post_id", :integer, :referneces => :posts
23
+ column "user_id", :integer, :references => :users
24
+ column "created_at", :datetime
25
+ column "updated_at", :datetime
26
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongify::CLI::Application do
4
+ before(:each) do
5
+ @application = Mongify::CLI::Application.new()
6
+ end
7
+
8
+ context "execute!" do
9
+ it "should return a 0" do
10
+ @application.execute!.should == 0
11
+ end
12
+
13
+ it "should return a 1 on error" do
14
+ @application = Mongify::CLI::Application.new(["door"])
15
+ @application.execute!.should == 1
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ describe Mongify::CLI::HelpCommand do
3
+ before :each do
4
+ @text = 'Piece of interesting text'
5
+ @cmd = Mongify::CLI::HelpCommand.new(@text)
6
+ @view = mock('view').as_null_object
7
+ end
8
+
9
+ it 'displays the correct text on the view' do
10
+ @view.should_receive(:output).with(@text)
11
+ @cmd.execute(@view)
12
+ end
13
+
14
+ it 'tells the view it succeeded' do
15
+ @view.should_receive(:report_success)
16
+ @cmd.execute(@view)
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongify::CLI::Options do
4
+ before(:each) do
5
+ @config_file = File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), 'files', 'base_configuration.rb')
6
+ end
7
+ it "should run help command when passed an -h" do
8
+ @options = Mongify::CLI::Options.new(['-h'])
9
+ @options.parse
10
+ @options.instance_variable_get(:@command_class).should == Mongify::CLI::HelpCommand
11
+ end
12
+
13
+ it "should run version command when passed an -h" do
14
+ @options = Mongify::CLI::Options.new(['-v'])
15
+ @options.parse
16
+ @options.instance_variable_get(:@command_class).should == Mongify::CLI::VersionCommand
17
+ end
18
+
19
+ context "action" do
20
+ it "should get action command" do
21
+ @options = Mongify::CLI::Options.new(['check'])
22
+ @options.send(:action).should == 'check'
23
+ end
24
+
25
+ it "should return blank if no action is sent" do
26
+ @options = Mongify::CLI::Options.new(['-v'])
27
+ @options.send(:action).should == ''
28
+ end
29
+ end
30
+
31
+ context "translation" do
32
+ it "should return path" do
33
+ @options = Mongify::CLI::Options.new(['check', '-c', 'some/config', 'some/folder/translation'])
34
+ @options.send(:translation_file).should == 'some/folder/translation'
35
+ end
36
+
37
+ it "should return nil if no path specified" do
38
+ @options = Mongify::CLI::Options.new(['check', '-c', 'some/config'])
39
+ @options.send(:translation_file).should be_nil
40
+ end
41
+ end
42
+
43
+
44
+ context "Configuration file" do
45
+ it "should take option (-c) with a file" do
46
+ @options = Mongify::CLI::Options.new(['-c', @config_file])
47
+ @options.parse
48
+ @options.instance_variable_get(:@config_file).should == @config_file
49
+ end
50
+
51
+ it "should be require" do
52
+ @options = Mongify::CLI::Options.new(["database_translation.rb"])
53
+ lambda {@options.parse}.should raise_error(Mongify::ConfigurationFileNotFound)
54
+ end
55
+
56
+ it "should call Configuration.parse" do
57
+ Mongify::Configuration.should_receive(:parse)
58
+ @options = Mongify::CLI::Options.new(['-c', @config_file])
59
+ @options.parse
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongify::CLI::VersionCommand do
4
+ before :each do
5
+ @text = 'Piece of interesting text'
6
+ @cmd = Mongify::CLI::VersionCommand.new(@text)
7
+ @view = mock('view').as_null_object
8
+ end
9
+
10
+ it 'displays the text on the view' do
11
+ @view.expects(:output).with(/#{@text}/)
12
+ @cmd.execute(@view)
13
+ end
14
+
15
+ it 'displays the Reek version on the view' do
16
+ @view.should_receive(:output).with(/#{Mongify::VERSION}/)
17
+ @cmd.execute(@view)
18
+ end
19
+
20
+ it 'tells the view it succeeded' do
21
+ @view.should_receive(:report_success)
22
+ @cmd.execute(@view)
23
+ end
24
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongify::CLI::WorkerCommand do
4
+ before(:each) do
5
+ @config = Mongify::Configuration.new()
6
+ @sql_connection = Mongify::Database::SqlConnection.new()
7
+ @sql_connection.stub(:valid?).and_return(true)
8
+ @sql_connection.stub(:has_connection?).and_return(true)
9
+ @config.stub(:sql_connection).and_return(@sql_connection)
10
+ @no_sql_connection = Mongify::Database::NoSqlConnection.new
11
+ @no_sql_connection.stub(:valid?).and_return(true)
12
+ @no_sql_connection.stub(:has_connection?).and_return(true)
13
+ @config.stub(:no_sql_connection).and_return(@no_sql_connection)
14
+
15
+ @translation_file = 'spec/files/simple_translation.rb'
16
+
17
+ Mongify::Translation.stub(:load).and_return(stub(:print => 'worked'))
18
+ @view = mock('view').as_null_object
19
+ end
20
+
21
+ context "list_commands" do
22
+ it "should return same number as available" do
23
+ Mongify::CLI::WorkerCommand.list_commands.size.should == Mongify::CLI::WorkerCommand::AVAILABLE_COMMANDS.size
24
+ end
25
+ end
26
+
27
+ context "check command" do
28
+ before(:each) do
29
+ @command = Mongify::CLI::WorkerCommand.new('check', @config)
30
+ @command.stub(:check_sql_connection).and_return(true)
31
+ @command.stub(:check_nosql_connection).and_return(true)
32
+ end
33
+ it "should ensure sql_connection is correct" do
34
+ @command.should_receive(:check_sql_connection).and_return(true)
35
+ @command.execute(@view)
36
+ end
37
+ it "should ensure noSql connection is correct" do
38
+ @command.should_receive(:check_nosql_connection).and_return(true)
39
+ @command.execute(@view)
40
+ end
41
+ it "should require config file" do
42
+ lambda { @command = Mongify::CLI::WorkerCommand.new('check').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
43
+ end
44
+ end
45
+
46
+ context "non-existing command" do
47
+ before(:each) do
48
+ @command = Mongify::CLI::WorkerCommand.new('unknown')
49
+ end
50
+
51
+ it "should report error" do
52
+ @view.should_receive(:report_error)
53
+ @command.execute(@view)
54
+ end
55
+
56
+ it "should output an error" do
57
+ @view.should_receive(:output).with("Unknown action unknown\n\n")
58
+ @command.execute(@view)
59
+ end
60
+ end
61
+
62
+ context "translate command" do
63
+ before(:each) do
64
+ @command = Mongify::CLI::WorkerCommand.new('translate', @config)
65
+ Mongify::Translation.stub(:load).with(@sql_connection).and_return(stub(:print => 'worked'))
66
+ end
67
+
68
+ it "should require configuration file" do
69
+ lambda { Mongify::CLI::WorkerCommand.new('translate').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
70
+ end
71
+
72
+ it "should check sql connection" do
73
+ @command.should_receive(:check_sql_connection).and_return(true)
74
+ @command.execute(@view)
75
+ end
76
+
77
+ it "should call load on Translation" do
78
+ Mongify::Translation.should_receive(:load).with(@sql_connection).and_return(stub(:print => 'worked'))
79
+ @command.execute(@view)
80
+ end
81
+ end
82
+
83
+ context "process command" do
84
+ before(:each) do
85
+ @command = Mongify::CLI::WorkerCommand.new('process', @config, 'spec/files/simple_translation.rb')
86
+ Mongify::Translation.stub(:parse).and_return(mock(:process => true))
87
+ end
88
+ it "should report success" do
89
+ @view.should_receive(:report_error).never
90
+ @view.should_receive(:report_success)
91
+ @command.execute(@view)
92
+ end
93
+
94
+ it "should require config file" do
95
+ lambda { @command = Mongify::CLI::WorkerCommand.new('process').execute(@view) }.should raise_error(Mongify::ConfigurationFileNotFound)
96
+ end
97
+
98
+ it "should require transaction file" do
99
+ lambda { @command = Mongify::CLI::WorkerCommand.new('process', @config).execute(@view) }.should raise_error(Mongify::TranslationFileNotFound)
100
+ end
101
+
102
+ it "should check_connection" do
103
+ @command.should_receive(:check_connections).and_return(true)
104
+ @command.execute(@view)
105
+ end
106
+
107
+ it "should call process on translation" do
108
+ puts @config.no_sql_connection.inspect
109
+ Mongify::Translation.should_receive(:parse).and_return(mock(:process => true))
110
+ @command.execute(@view)
111
+ end
112
+
113
+
114
+ end
115
+ end