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