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.
- data/CHANGELOG.rdoc +8 -0
- data/Gemfile.lock +1 -1
- data/README.rdoc +40 -16
- data/features/options.feature +3 -7
- data/features/print.feature +1 -1
- data/features/process.feature +0 -1
- data/features/support/env.rb +4 -1
- data/lib/mongify.rb +3 -1
- data/lib/mongify/cli.rb +0 -1
- data/lib/mongify/cli/application.rb +10 -1
- data/lib/mongify/cli/help_command.rb +1 -0
- data/lib/mongify/cli/options.rb +13 -14
- data/lib/mongify/cli/version_command.rb +1 -0
- data/lib/mongify/cli/worker_command.rb +15 -7
- data/lib/mongify/configuration.rb +14 -12
- data/lib/mongify/database.rb +2 -1
- data/lib/mongify/database/base_connection.rb +25 -15
- data/lib/mongify/database/column.rb +99 -21
- data/lib/mongify/database/data_row.rb +67 -0
- data/lib/mongify/database/no_sql_connection.rb +48 -10
- data/lib/mongify/database/sql_connection.rb +34 -4
- data/lib/mongify/database/table.rb +69 -7
- data/lib/mongify/exceptions.rb +7 -0
- data/lib/mongify/translation.rb +45 -2
- data/lib/mongify/translation/printer.rb +4 -3
- data/lib/mongify/translation/process.rb +11 -4
- data/lib/mongify/ui.rb +10 -1
- data/lib/mongify/version.rb +2 -1
- data/spec/mongify/cli/worker_command_spec.rb +3 -4
- data/spec/mongify/configuration_spec.rb +6 -12
- data/spec/mongify/database/base_connection_spec.rb +7 -3
- data/spec/mongify/database/column_spec.rb +171 -28
- data/spec/mongify/database/data_row_spec.rb +102 -0
- data/spec/mongify/database/no_sql_connection_spec.rb +4 -6
- data/spec/mongify/database/table_spec.rb +23 -2
- data/spec/mongify/translation/printer_spec.rb +3 -3
- data/spec/support/config_reader.rb +3 -1
- data/spec/support/generate_database.rb +7 -0
- metadata +7 -5
- 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
|
-
|
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
|
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) }
|
data/lib/mongify/exceptions.rb
CHANGED
@@ -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
|
data/lib/mongify/translation.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/mongify/ui.rb
CHANGED
@@ -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
|