mongify 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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,81 @@
1
+ module Mongify
2
+ module Database
3
+ #
4
+ # A column in the sql table
5
+ #
6
+ class Column
7
+ attr_reader :name, :type, :options
8
+
9
+ AVAILABLE_OPTIONS = ['references', 'default']
10
+
11
+ def initialize(name, type=:string, *args)
12
+ @name = name
13
+ type = :string if type.nil?
14
+ @type = type.is_a?(Symbol) ? type : type.to_sym
15
+ @options = args.extract_options!.stringify_keys
16
+
17
+ auto_detect!
18
+
19
+ self
20
+ end
21
+
22
+ def translate(value)
23
+ case type
24
+ when :key
25
+ {"pre_mongified_#{name}" => value}
26
+ when :datetime
27
+ {"#{name}" => value.to_time}
28
+ else
29
+ {"#{self.name}" => value}
30
+ end
31
+ end
32
+
33
+ def to_print
34
+ "column \"#{name}\", :#{type}".tap do |output|
35
+ output_options = options.map{|k, v| (v == nil) ? nil : ":#{k} => \"#{v}\""}.compact
36
+ output << ", #{output_options.join(', ')}" unless output_options.blank?
37
+ end
38
+ end
39
+
40
+ def method_missing(meth, *args, &blk)
41
+ method_name = meth.to_s.gsub("=", '')
42
+ if AVAILABLE_OPTIONS.include?(method_name)
43
+ class_eval <<-EOF
44
+ def #{method_name}=(value)
45
+ options['#{method_name}'] = value
46
+ end
47
+ def #{method_name}
48
+ options['#{method_name}']
49
+ end
50
+ EOF
51
+ send(meth, *args, &blk)
52
+ else
53
+ super(meth, *args, &blk)
54
+ end
55
+ end
56
+
57
+ def reference?
58
+ !self.options['references'].nil?
59
+ end
60
+
61
+ #######
62
+ private
63
+ #######
64
+
65
+ def key?
66
+ self.type == :key
67
+ end
68
+
69
+ def auto_detect!
70
+ case name.downcase
71
+ when 'id'
72
+ @type = :key if self.type == :integer
73
+ when /(.*)_id/
74
+ self.references = $1.to_s.pluralize unless self.references
75
+ end
76
+ end
77
+
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,62 @@
1
+ require 'mongo'
2
+ module Mongify
3
+ module Database
4
+ #
5
+ # No sql connection configuration
6
+ #
7
+ class NoSqlConnection < Mongify::Database::BaseConnection
8
+ include Mongo
9
+ REQUIRED_FIELDS = %w{host database}
10
+
11
+ def initialize(options=nil)
12
+ super options
13
+ adapter 'mongodb' if adapter.nil? || adapter == 'mongo'
14
+ end
15
+
16
+ def connection_string
17
+ "#{@adapter}://#{@host}#{":#{@port}" if @port}"
18
+ end
19
+
20
+ def valid?
21
+ @database.present? && @host.present?
22
+ end
23
+
24
+ def connection
25
+ return @connection if @connection
26
+ @connection = Connection.new(host, port)
27
+ @connection.add_auth(database, username, password) if username && password
28
+ @connection
29
+ end
30
+
31
+ def has_connection?
32
+ connection.connected?
33
+ end
34
+
35
+ def db
36
+ @db ||= connection[database]
37
+ end
38
+
39
+ def select_rows(collection)
40
+ db[collection].find
41
+ end
42
+
43
+ def insert_into(colleciton_name, row)
44
+ db[colleciton_name].insert(row, :safe => true)
45
+ end
46
+
47
+ def update(colleciton_name, id, attributes)
48
+ db[colleciton_name].update({"_id" => id}, attributes)
49
+ end
50
+
51
+ def get_id_using_pre_mongified_id(colleciton_name, pre_mongified_id)
52
+ db[colleciton_name].find_one('pre_mongified_id' => pre_mongified_id).try(:[], '_id')
53
+ end
54
+
55
+
56
+ def reset!
57
+ @connection = nil
58
+ @db = nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ module Mongify
2
+ module Database
3
+ #
4
+ # Sql connection configuration
5
+ #
6
+ class SqlConnection < Mongify::Database::BaseConnection
7
+
8
+ REQUIRED_FIELDS = %w{host adapter database}
9
+
10
+ def initialize(options=nil)
11
+ @prefixed_db = false
12
+ super(options)
13
+ end
14
+
15
+ def setup_connection_adapter
16
+ ActiveRecord::Base.establish_connection(self.to_hash)
17
+ end
18
+
19
+ def valid?
20
+ return false unless @adapter
21
+ if sqlite_adapter?
22
+ return true if @database
23
+ else
24
+ return super
25
+ end
26
+ false
27
+ end
28
+
29
+ def has_connection?
30
+ setup_connection_adapter
31
+ connection.send(:connect) if ActiveRecord::Base.connection.respond_to?(:connect)
32
+ true
33
+ end
34
+
35
+ def connection
36
+ return nil unless has_connection?
37
+ ActiveRecord::Base.connection
38
+ end
39
+
40
+ def tables
41
+ return nil unless has_connection?
42
+ self.connection.tables
43
+ end
44
+
45
+ def columns_for(table_name)
46
+ self.connection.columns(table_name)
47
+ end
48
+
49
+ def select_rows(table_name)
50
+ self.connection.select_all("SELECT * FROM #{table_name}")
51
+ end
52
+
53
+ #######
54
+ private
55
+ #######
56
+ def sqlite_adapter?
57
+ @adapter && (@adapter.downcase == 'sqlite' || @adapter.downcase == 'sqlite3')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,74 @@
1
+ module Mongify
2
+ module Database
3
+ #
4
+ # A representation of a sql table and how it should map to a no_sql system
5
+ #
6
+ class Table
7
+
8
+ attr_accessor :name
9
+ attr_reader :options, :columns
10
+
11
+ def initialize(name, *args, &block)
12
+ @columns = []
13
+ @column_lookup = {}
14
+ @options = args.extract_options!.stringify_keys
15
+ self.name = name
16
+
17
+ self.instance_exec(&block) if block_given?
18
+
19
+ import_columns
20
+
21
+ self
22
+ end
23
+
24
+ #Add a Database Column
25
+ def add_column(column)
26
+ raise Mongify::DatabaseColumnExpected, "Expected a Mongify::Database::Column" unless column.is_a?(Mongify::Database::Column)
27
+ add_column_index(column.name, @columns.size)
28
+ @columns << column
29
+ end
30
+
31
+
32
+ def column(name, type=nil, options={})
33
+ options = type and type = nil if type.is_a?(Hash)
34
+ type = type.to_sym if type
35
+ add_column_index(name.to_s.downcase, @columns.size)
36
+ @columns << (col = Mongify::Database::Column.new(name, type, options))
37
+ col
38
+ end
39
+
40
+ def find_column(name)
41
+ return nil unless (index = @column_lookup[name.to_s.downcase])
42
+ @columns[index]
43
+ end
44
+
45
+
46
+ def reference_columns
47
+ @columns.reject{ |c| !c.reference? }
48
+ end
49
+
50
+ def translate(row)
51
+ new_row = {}
52
+ row.each do |key, value|
53
+ c = find_column(key)
54
+ new_row.merge!(c.present? ? c.translate(value) : {"#{key}" => value})
55
+ end
56
+ new_row
57
+ end
58
+
59
+ #######
60
+ private
61
+ #######
62
+
63
+ def add_column_index(name, index)
64
+ @column_lookup[name] = index
65
+ end
66
+
67
+ def import_columns
68
+ return unless import_columns = @options.delete('columns')
69
+ import_columns.each { |c| add_column(c) }
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,13 @@
1
+ module Mongify
2
+ # File Not Found Exception
3
+ class FileNotFound < RuntimeError; end
4
+ class ConfigurationFileNotFound < FileNotFound; end
5
+ class TranslationFileNotFound < FileNotFound; end
6
+
7
+ class ConfigurationError < RuntimeError; end
8
+ class SqlConnectionRequired < ConfigurationError; end
9
+ class NoSqlConnectionRequired < ConfigurationError; end
10
+ class DatabaseColumnExpected < ConfigurationError; end
11
+
12
+ class RootMissing < RuntimeError; end
13
+ end
@@ -0,0 +1,55 @@
1
+ require 'mongify/translation/printer'
2
+ require 'mongify/translation/process'
3
+ module Mongify
4
+ #
5
+ # Actually runs the translation from sql to no sql
6
+ #
7
+ class Translation
8
+ attr_reader :tables, :sql_connection, :no_sql_connection
9
+
10
+ include Printer
11
+ include Process
12
+ class << self
13
+ def parse(file_name)
14
+ translation = self.new
15
+ translation.instance_eval(File.read(file_name))
16
+ translation
17
+ end
18
+
19
+ def load(connection)
20
+ raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless connection.is_a?(Mongify::Database::SqlConnection)
21
+ return unless connection.has_connection?
22
+ translation = self.new
23
+ connection.tables.each do |t|
24
+ columns = []
25
+ connection.columns_for(t).each do |ar_col|
26
+ columns << Mongify::Database::Column.new(ar_col.name, ar_col.type, :default => ar_col.default)
27
+ end
28
+ translation.table(t, :columns => columns)
29
+ end
30
+ translation
31
+ end
32
+ end
33
+
34
+
35
+ def initialize
36
+ @tables = []
37
+ end
38
+
39
+ def table(table_name, options={}, &block)
40
+ table = Mongify::Database::Table.new(table_name, options, &block)
41
+ @tables << table
42
+ end
43
+
44
+ def add_table(table)
45
+ @tables << table
46
+ end
47
+
48
+ #######
49
+ private
50
+ #######
51
+
52
+
53
+
54
+ end
55
+ end
@@ -0,0 +1,20 @@
1
+ module Mongify
2
+ #
3
+ # Actually runs the translation from sql to no sql
4
+ #
5
+ class Translation
6
+ module Printer
7
+ def print
8
+ ''.tap do |output|
9
+ @tables.each do |t|
10
+ output << %Q[table "#{t.name}" do\n]
11
+ t.columns.each do |c|
12
+ output << "\t#{c.to_print}\n"
13
+ end
14
+ output << "end\n\n"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ module Mongify
2
+ #
3
+ # This will take the Translation and do the processing on it
4
+ #
5
+ class Translation
6
+ module Process
7
+ def sql_connection=(value)
8
+ @sql_connection=value
9
+ end
10
+ def no_sql_connection=(value)
11
+ @no_sql_connection=value
12
+ end
13
+ def process(sql_connection, no_sql_connection)
14
+ raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless sql_connection.is_a?(Mongify::Database::SqlConnection)
15
+ raise Mongify::NoSqlConnectionRequired, "Can only write to Mongify::Database::NoSqlConnection" unless no_sql_connection.is_a?(Mongify::Database::NoSqlConnection)
16
+
17
+ self.sql_connection = sql_connection
18
+ raise "SQL Connection is not valid" unless self.sql_connection.valid?
19
+ self.no_sql_connection = no_sql_connection
20
+ raise "noSql Connection is not valid" unless self.no_sql_connection.valid?
21
+
22
+ copy
23
+ update_reference_ids
24
+ nil
25
+ end
26
+
27
+ #######
28
+ private
29
+ #######
30
+
31
+ def copy
32
+ self.tables.each do |t|
33
+ sql_connection.select_rows(t.name).each do |row|
34
+ no_sql_connection.insert_into(t.name, t.translate(row))
35
+ end
36
+ end
37
+ end
38
+
39
+ def update_reference_ids
40
+ self.tables.each do |t|
41
+ no_sql_connection.select_rows(t.name).each do |row|
42
+ id = row["_id"]
43
+ attributes = {}
44
+ t.reference_columns.each do |c|
45
+ new_id = no_sql_connection.get_id_using_pre_mongified_id(c.references.to_s, row[c.name])
46
+ attributes.merge!(c.name => new_id) unless new_id.nil?
47
+ end
48
+ no_sql_connection.update(t.name, id, {"$set" => attributes}) unless attributes.blank?
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+ # Process that needs to take place
59
+ # import the data (moving the id to premongified_id)
60
+ # fix all the references to the new ids
61
+ #
@@ -0,0 +1,45 @@
1
+ module Mongify
2
+ #
3
+ # Used to output messages to the UI
4
+ #
5
+ class UI
6
+ class << self
7
+ def puts(msg)
8
+ out_stream.puts(msg) if out_stream
9
+ end
10
+
11
+ def print(msg)
12
+ out_stream.print(msg) if out_stream
13
+ end
14
+
15
+ def gets
16
+ in_stream ? in_stream.gets : ''
17
+ end
18
+
19
+ def request(msg)
20
+ print(msg)
21
+ gets.chomp
22
+ end
23
+
24
+ def ask(msg)
25
+ request("#{msg} [yn] ") == 'y'
26
+ end
27
+
28
+ def warn(msg)
29
+ puts "WARNING: #{msg}"
30
+ end
31
+
32
+ def abort(message)
33
+ UI.puts "PROGRAM HALT: #{message}"
34
+ Kernel.abort message
35
+ end
36
+
37
+ def in_stream
38
+ Configuration.in_stream
39
+ end
40
+ def out_stream
41
+ Configuration.out_stream
42
+ end
43
+ end
44
+ end
45
+ end