mongify 0.0.9 → 0.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 (40) hide show
  1. data/CHANGELOG.rdoc +8 -0
  2. data/Gemfile.lock +1 -1
  3. data/README.rdoc +40 -16
  4. data/features/options.feature +3 -7
  5. data/features/print.feature +1 -1
  6. data/features/process.feature +0 -1
  7. data/features/support/env.rb +4 -1
  8. data/lib/mongify.rb +3 -1
  9. data/lib/mongify/cli.rb +0 -1
  10. data/lib/mongify/cli/application.rb +10 -1
  11. data/lib/mongify/cli/help_command.rb +1 -0
  12. data/lib/mongify/cli/options.rb +13 -14
  13. data/lib/mongify/cli/version_command.rb +1 -0
  14. data/lib/mongify/cli/worker_command.rb +15 -7
  15. data/lib/mongify/configuration.rb +14 -12
  16. data/lib/mongify/database.rb +2 -1
  17. data/lib/mongify/database/base_connection.rb +25 -15
  18. data/lib/mongify/database/column.rb +99 -21
  19. data/lib/mongify/database/data_row.rb +67 -0
  20. data/lib/mongify/database/no_sql_connection.rb +48 -10
  21. data/lib/mongify/database/sql_connection.rb +34 -4
  22. data/lib/mongify/database/table.rb +69 -7
  23. data/lib/mongify/exceptions.rb +7 -0
  24. data/lib/mongify/translation.rb +45 -2
  25. data/lib/mongify/translation/printer.rb +4 -3
  26. data/lib/mongify/translation/process.rb +11 -4
  27. data/lib/mongify/ui.rb +10 -1
  28. data/lib/mongify/version.rb +2 -1
  29. data/spec/mongify/cli/worker_command_spec.rb +3 -4
  30. data/spec/mongify/configuration_spec.rb +6 -12
  31. data/spec/mongify/database/base_connection_spec.rb +7 -3
  32. data/spec/mongify/database/column_spec.rb +171 -28
  33. data/spec/mongify/database/data_row_spec.rb +102 -0
  34. data/spec/mongify/database/no_sql_connection_spec.rb +4 -6
  35. data/spec/mongify/database/table_spec.rb +23 -2
  36. data/spec/mongify/translation/printer_spec.rb +3 -3
  37. data/spec/support/config_reader.rb +3 -1
  38. data/spec/support/generate_database.rb +7 -0
  39. metadata +7 -5
  40. data/lib/mongify/cli/report.rb +0 -11
@@ -3,19 +3,43 @@ module Mongify
3
3
  #
4
4
  # Sql connection configuration
5
5
  #
6
+ #
7
+ # Basic format should look something like this:
8
+ #
9
+ # sql_connection do
10
+ # adaptor "mysql"
11
+ # host "localhost"
12
+ # username "root"
13
+ # password "passw0rd"
14
+ # database "my_database"
15
+ # end
16
+ # Possible attributes:
17
+ #
18
+ # adapter
19
+ # host
20
+ # database
21
+ # username
22
+ # password
23
+ # port
24
+ # encoding
25
+ # socket
26
+ #
6
27
  class SqlConnection < Mongify::Database::BaseConnection
7
-
8
- REQUIRED_FIELDS = %w{host adapter database encoding}
28
+
29
+ # List of required fields to bulid a valid sql connection
30
+ REQUIRED_FIELDS = %w{host adapter database}
9
31
 
10
32
  def initialize(options=nil)
11
33
  @prefixed_db = false
12
34
  super(options)
13
35
  end
14
36
 
37
+ # Setups up an active_record connection
15
38
  def setup_connection_adapter
16
39
  ActiveRecord::Base.establish_connection(self.to_hash)
17
40
  end
18
-
41
+
42
+ # Returns true or false depending if the record is valid
19
43
  def valid?
20
44
  return false unless @adapter
21
45
  if sqlite_adapter?
@@ -25,27 +49,32 @@ module Mongify
25
49
  end
26
50
  false
27
51
  end
28
-
52
+
53
+ # Returns true or false depending if the connction actually talks to the database server.
29
54
  def has_connection?
30
55
  setup_connection_adapter
31
56
  connection.send(:connect) if ActiveRecord::Base.connection.respond_to?(:connect)
32
57
  true
33
58
  end
34
59
 
60
+ # Returns the active_record connection
35
61
  def connection
36
62
  return nil unless has_connection?
37
63
  ActiveRecord::Base.connection
38
64
  end
39
65
 
66
+ # Returns all the tables in the database server
40
67
  def tables
41
68
  return nil unless has_connection?
42
69
  self.connection.tables
43
70
  end
44
71
 
72
+ # Returns all the columns for a given table
45
73
  def columns_for(table_name)
46
74
  self.connection.columns(table_name)
47
75
  end
48
76
 
77
+ # Returns an array with hash values of all the records in a given table
49
78
  def select_rows(table_name)
50
79
  self.connection.select_all("SELECT * FROM #{table_name}")
51
80
  end
@@ -53,6 +82,7 @@ module Mongify
53
82
  #######
54
83
  private
55
84
  #######
85
+ # Used to check if this is a sqlite connection
56
86
  def sqlite_adapter?
57
87
  @adapter && (@adapter.downcase == 'sqlite' || @adapter.downcase == 'sqlite3')
58
88
  end
@@ -1,8 +1,42 @@
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 system
4
+ # A representation of a sql table and how it should map to a no_sql collection
5
5
  #
6
+ # ==== Structure
7
+ #
8
+ # Structure for defining a table is as follows:
9
+ # table "table_name", {options} do
10
+ # # columns go here...
11
+ # end
12
+ #
13
+ # ==== Options
14
+ #
15
+ # Table Options are as follow:
16
+ # table "table_name" # Does a straight copy of the table
17
+ # table "table_name", :embed_in => :users # Embeds table_name into users, assuming a user_id is present in table_name.
18
+ # # This will also assume you want the table embedded as an array.
19
+ # table "table_name", :embed_in => :users, :on => 'owner_id'# Embeds table_name into users, linking it via a owner_id
20
+ # # This will also assume you want the table embedded as an array.
21
+ # table "table_name", :embed_in => :users, :as => :object # Embeds table_name into users as a one to one relationship
22
+ # # This also assumes you have a user_id present in table_name
23
+ # table "table_name", :embed_in => :posts, :on => 'post_id', :as => 'array'
24
+ # # You can also do both :on and :as
25
+ #
26
+ # <b>NOTE: If you rename the owner_id column, make sure you update the :on to the new column name</b>
27
+ #
28
+ # table "table_name", :rename_to => 'my_table' # This will allow you to rename the table as it's getting process
29
+ # # Just remember that columns that use :reference need to
30
+ # # reference the new name.
31
+ #
32
+ # table "table_name", :ignore => true # This will ignore the whole table (like it doesn't exist)
33
+ # # This option is good for tables like: schema_migrations
34
+ #
35
+ # table "table_name" do # A table can take a before_save block that will be called just
36
+ # before_save do |row| # before the row is saved to the no sql database.
37
+ # row.admin = row.delete('permission').to_i > 50 # This gives you the ability to do very powerful things like:
38
+ # end # Moving records around, renaming records, changing values in row based on
39
+ # end # some values! Checkout {Mongify::Database::DataRow} to learn more
6
40
  class Table
7
41
 
8
42
  attr_accessor :name, :sql_name
@@ -21,80 +55,108 @@ module Mongify
21
55
  self
22
56
  end
23
57
 
58
+ # Returns the no_sql collection name
24
59
  def name
25
60
  @name ||= @options['rename_to']
26
61
  @name ||= self.sql_name
27
62
  end
28
63
 
64
+ # Returns true if table is ignored
29
65
  def ignored?
30
66
  @options['ignore']
31
67
  end
32
68
 
33
- #Add a Database Column
69
+ # Add a Database Column to the table
70
+ # This expects to get a {Mongify::Database::Column} or it will raise {Mongify::DatabaseColumnExpected} otherwise
34
71
  def add_column(column)
35
72
  raise Mongify::DatabaseColumnExpected, "Expected a Mongify::Database::Column" unless column.is_a?(Mongify::Database::Column)
36
73
  add_and_index_column(column)
37
74
  end
38
75
 
39
-
76
+ # Lets you build a column in the table
40
77
  def column(name, type=nil, options={})
41
78
  options, type = type, nil if type.is_a?(Hash)
42
79
  type = type.to_sym if type
43
80
  add_and_index_column(Mongify::Database::Column.new(name, type, options))
44
81
  end
45
82
 
83
+ # Returns the column if found by the sql_name
46
84
  def find_column(name)
47
85
  return nil unless (index = @column_lookup[name.to_s.downcase])
48
86
  @columns[index]
49
87
  end
50
88
 
51
-
89
+ # Returns a array of Columns which reference other columns
52
90
  def reference_columns
53
91
  @columns.reject{ |c| !c.referenced? }
54
92
  end
55
93
 
94
+ # Returns a translated row
95
+ # Takes in a hash of values
56
96
  def translate(row)
57
97
  new_row = {}
58
98
  row.each do |key, value|
59
99
  c = find_column(key)
60
100
  new_row.merge!(c.present? ? c.translate(value) : {"#{key}" => value})
61
101
  end
62
- new_row
102
+ run_before_save(new_row)
63
103
  end
64
104
 
105
+
106
+ # Returns the name of the embed_in collection
65
107
  def embed_in
66
108
  @options['embed_in'].to_s unless @options['embed_in'].nil?
67
109
  end
68
110
 
111
+ # Returns the type of embed it will be [object or array]
69
112
  def embed_as
70
113
  return nil unless embed?
71
114
  return 'object' if @options['as'].to_s.downcase == 'object'
72
115
  'array'
73
116
  end
74
-
117
+
118
+ # Returns true if table is being embed as an object
75
119
  def embed_as_object?
76
120
  embed_as == 'object'
77
121
  end
78
122
 
123
+ # Returns true if this is an embedded table
79
124
  def embed?
80
125
  embed_in.present?
81
126
  end
82
127
 
128
+ # Returns the name of the target column to embed on
83
129
  def embed_on
84
130
  return nil unless embed?
85
131
  (@options['on'] || "#{@options['embed_in'].to_s.singularize}_id").to_s
86
132
  end
133
+
134
+ # Used to save a block to be ran after the row has been processed but before it's saved to the no sql database
135
+ def before_save(&block)
136
+ @before_save = block
137
+ end
87
138
 
88
139
  #######
89
140
  private
90
141
  #######
91
142
 
143
+ # Runs the before save
144
+ # Returns: a new modified row
145
+ def run_before_save(row)
146
+ return row unless @before_save
147
+ datarow = Mongify::Database::DataRow.new(row)
148
+ @before_save.call(datarow)
149
+ datarow.to_hash
150
+ end
151
+
152
+ # Indexes the column on the sql_name and adds column to the array
92
153
  def add_and_index_column(column)
93
154
  @column_lookup[column.sql_name] = @columns.size
94
155
  @columns << column
95
156
  column
96
157
  end
97
-
158
+
159
+ # Imports colunms that are sent in via the options['columns']
98
160
  def import_columns
99
161
  return unless import_columns = @options.delete('columns')
100
162
  import_columns.each { |c| add_column(c) }
@@ -1,13 +1,20 @@
1
1
  module Mongify
2
2
  # File Not Found Exception
3
3
  class FileNotFound < RuntimeError; end
4
+ # Raise when configuration file is missing
4
5
  class ConfigurationFileNotFound < FileNotFound; end
6
+ # Raised when Translation file is missing
5
7
  class TranslationFileNotFound < FileNotFound; end
6
8
 
9
+ # Basic Configuration Error Exception
7
10
  class ConfigurationError < RuntimeError; end
11
+ # Raise when a sqlConnection is required but not given
8
12
  class SqlConnectionRequired < ConfigurationError; end
13
+ # Raised when a noSqlConnection is required but not given
9
14
  class NoSqlConnectionRequired < ConfigurationError; end
15
+ # Raised when a Mongify::Database::Column is expected but not given
10
16
  class DatabaseColumnExpected < ConfigurationError; end
11
17
 
18
+ # Raised when application has no root folder set
12
19
  class RootMissing < RuntimeError; end
13
20
  end
@@ -4,16 +4,52 @@ module Mongify
4
4
  #
5
5
  # Actually runs the translation from sql to no sql
6
6
  #
7
+ # Basic translation file should look like this:
8
+ # table "users" do
9
+ # column "id", :key
10
+ # column "first_name", :string
11
+ # column "last_name", :string
12
+ # column "created_at", :datetime
13
+ # column "updated_at", :datetime
14
+ # end
15
+ #
16
+ # table "posts" do
17
+ # column "id", :key
18
+ # column "title", :string
19
+ # column "owner_id", :integer, :references => :users
20
+ # column "body", :text
21
+ # column "published_at", :datetime
22
+ # column "created_at", :datetime
23
+ # column "updated_at", :datetime
24
+ # end
25
+ #
26
+ # table "comments", :embed_in => :posts, :on => :post_id do
27
+ # column "id", :key
28
+ # column "body", :text
29
+ # column "post_id", :integer, :referneces => :posts
30
+ # column "user_id", :integer, :references => :users
31
+ # column "created_at", :datetime
32
+ # column "updated_at", :datetime
33
+ # end
34
+ #
35
+ # table "preferences", :embed_in => :users, :as => :object do
36
+ # column "id", :key
37
+ # column "user_id", :integer, :references => "users"
38
+ # column "notify_by_email", :boolean
39
+ # end
7
40
  class Translation
8
41
  include Printer
9
42
  include Process
10
43
  class << self
44
+ # Returns an instance of a translation object
45
+ # Takes a location of a translation file
11
46
  def parse(file_name)
12
47
  translation = self.new
13
48
  translation.instance_eval(File.read(file_name))
14
49
  translation
15
50
  end
16
51
 
52
+ #Returns an instence of a translation object with a given sql connection layout loaded
17
53
  def load(connection)
18
54
  raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless connection.is_a?(Mongify::Database::SqlConnection)
19
55
  return unless connection.valid? && connection.has_connection?
@@ -21,7 +57,7 @@ module Mongify
21
57
  connection.tables.each do |t|
22
58
  columns = []
23
59
  connection.columns_for(t).each do |ar_col|
24
- columns << Mongify::Database::Column.new(ar_col.name, ar_col.type)
60
+ columns << Mongify::Database::Column.new(ar_col.name, ar_col.type, :auto_detect => true)
25
61
  end
26
62
  translation.table(t, :columns => columns)
27
63
  end
@@ -33,27 +69,34 @@ module Mongify
33
69
  @all_tables = []
34
70
  end
35
71
 
72
+ # Creates a {Mongify::Database::Table} from the given input and adds it to the list of tables
36
73
  def table(table_name, options={}, &block)
37
74
  table = Mongify::Database::Table.new(table_name, options, &block)
38
- @all_tables << table
75
+ add_table(table)
39
76
  end
40
77
 
78
+ # Adds a {Mongify::Database::Table} to the list of tables
41
79
  def add_table(table)
42
80
  @all_tables << table
81
+ table
43
82
  end
44
83
 
84
+ # Returns an array of all tables in the translation
45
85
  def all_tables
46
86
  @all_tables
47
87
  end
48
88
 
89
+ # Returns an array of all tables that have not been ingored
49
90
  def tables
50
91
  all_tables.reject{ |t| t.ignored? }
51
92
  end
52
93
 
94
+ # Returns an array of all tables that have not been ignored and are just straight copy tables
53
95
  def copy_tables
54
96
  tables.reject{|t| t.embed?}
55
97
  end
56
98
 
99
+ # Returns an array of all tables that have not been ignored and are to be embedded
57
100
  def embed_tables
58
101
  tables.reject{|t| !t.embed?}
59
102
  end
@@ -1,9 +1,10 @@
1
1
  module Mongify
2
- #
3
- # Actually runs the translation from sql to no sql
4
- #
5
2
  class Translation
3
+ #
4
+ # Actually runs the translation from sql to no sql
5
+ #
6
6
  module Printer
7
+ # Outputs a translation into a string format
7
8
  def print
8
9
  ''.tap do |output|
9
10
  all_tables.each do |t|
@@ -1,11 +1,13 @@
1
1
  module Mongify
2
- #
3
- # This will take the Translation and do the processing on it
4
- #
5
2
  class Translation
3
+ #
4
+ # This module does the processing on the translation object
5
+ #
6
6
  module Process
7
7
  attr_accessor :sql_connection, :no_sql_connection
8
-
8
+
9
+ # Does the actual act of processing the translation.
10
+ # Takes in boht a sql connection and a no sql connection
9
11
  def process(sql_connection, no_sql_connection)
10
12
  raise Mongify::SqlConnectionRequired, "Can only read from Mongify::Database::SqlConnection" unless sql_connection.is_a?(Mongify::Database::SqlConnection)
11
13
  raise Mongify::NoSqlConnectionRequired, "Can only write to Mongify::Database::NoSqlConnection" unless no_sql_connection.is_a?(Mongify::Database::NoSqlConnection)
@@ -26,6 +28,7 @@ module Mongify
26
28
  private
27
29
  #######
28
30
 
31
+ # Does the straight copy (of tables)
29
32
  def copy_data
30
33
  self.copy_tables.each do |t|
31
34
  sql_connection.select_rows(t.sql_name).each do |row|
@@ -34,6 +37,7 @@ module Mongify
34
37
  end
35
38
  end
36
39
 
40
+ # Does a copy of the embedded tables
37
41
  def copy_embedded_tables
38
42
  self.embed_tables.each do |t|
39
43
  sql_connection.select_rows(t.sql_name).each do |row|
@@ -49,6 +53,7 @@ module Mongify
49
53
  end
50
54
  end
51
55
 
56
+ # Updates the reference ids in the no sql database
52
57
  def update_reference_ids
53
58
  self.tables.each do |t|
54
59
  no_sql_connection.select_rows(t.name).each do |row|
@@ -59,6 +64,7 @@ module Mongify
59
64
  end
60
65
  end
61
66
 
67
+ # Fetches the new _id from a collection
62
68
  def fetch_reference_ids(table, row)
63
69
  attributes = {}
64
70
  table.reference_columns.each do |c|
@@ -68,6 +74,7 @@ module Mongify
68
74
  attributes
69
75
  end
70
76
 
77
+ # Removes 'pre_mongiifed_id's from all collection
71
78
  def remove_pre_mongified_ids
72
79
  self.copy_tables.each { |t| no_sql_connection.remove_pre_mongified_ids(t.name) }
73
80
  end
@@ -4,39 +4,48 @@ module Mongify
4
4
  #
5
5
  class UI
6
6
  class << self
7
+ # Outputs to stream using puts method
7
8
  def puts(msg)
8
9
  out_stream.puts(msg) if out_stream
9
10
  end
10
-
11
+
12
+ # Outputs to stream using print method
11
13
  def print(msg)
12
14
  out_stream.print(msg) if out_stream
13
15
  end
14
16
 
17
+ # Gets input from user
15
18
  def gets
16
19
  in_stream ? in_stream.gets : ''
17
20
  end
18
21
 
22
+ # Outputs a question and gets input
19
23
  def request(msg)
20
24
  print(msg)
21
25
  gets.chomp
22
26
  end
23
27
 
28
+ # Asks a yes or no question and waits for reply
24
29
  def ask(msg)
25
30
  request("#{msg} [yn] ") == 'y'
26
31
  end
27
32
 
33
+ # Outputs a Warning (using puts command)
28
34
  def warn(msg)
29
35
  puts "WARNING: #{msg}"
30
36
  end
31
37
 
38
+ # Outputs a message and aborts execution of app
32
39
  def abort(message)
33
40
  UI.puts "PROGRAM HALT: #{message}"
34
41
  Kernel.abort message
35
42
  end
36
43
 
44
+ # Incoming stream
37
45
  def in_stream
38
46
  Configuration.in_stream
39
47
  end
48
+ # Output stream
40
49
  def out_stream
41
50
  Configuration.out_stream
42
51
  end