mongify 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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