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
@@ -1,22 +1,15 @@
1
1
  module Mongify
2
2
  #
3
- # This extracts the configuration for sql and no sql
3
+ # Handles all tasks regarding the connection to sql and no-sql database
4
+ # It also handles the parsing of the data
4
5
  #
5
6
  class Configuration
6
7
  class << self
7
8
  attr_accessor :in_stream, :out_stream
8
9
 
9
- def parse_translation(file_name)
10
- raise Mongify::TranslationFileNotFound, "File #{file_name} is missing" unless File.exists?(file_name)
11
- Mongify::Translation.parse(file_name)
12
- end
13
-
14
- def parse_configuration(file_name)
15
- raise Mongify::ConfigurationFileNotFound, "File #{file_name} is missing" unless File.exists?(file_name)
16
- Mongify::Configuration.parse(file_name)
17
- end
18
-
10
+ # Parses a external configuration file and evaluates it and returns a instence of a configuration class
19
11
  def parse(file_name)
12
+ raise Mongify::ConfigurationFileNotFound, "File #{file_name} is missing" unless File.exists?(file_name)
20
13
  config = self.new
21
14
  config.instance_eval(File.read(file_name))
22
15
  config
@@ -24,18 +17,27 @@ module Mongify
24
17
 
25
18
  end #self
26
19
 
20
+ # Returns a no_sql_connection which is bound to a mongodb adapter
21
+ # or builds a new no_sql_connection if block is given
27
22
  def mongodb_connection(options={}, &block)
23
+ return @mongodb_conncetion if @mongodb_connection and !block_given?
28
24
  options.stringify_keys!
29
25
  options['adapter'] ||= 'mongodb'
30
- no_sql_connection(options, &block)
26
+ @mongodb_connection = no_sql_connection(options, &block)
31
27
  end
32
28
 
29
+ # Returns a sql_connection
30
+ # If a block is given, it will be executed on the connection
31
+ # For more information, see {Mongify::Database::SqlConnection}
33
32
  def sql_connection(options={}, &block)
34
33
  @sql_connection ||= Mongify::Database::SqlConnection.new(options)
35
34
  @sql_connection.instance_exec(&block) if block_given?
36
35
  @sql_connection
37
36
  end
38
37
 
38
+ # Returns a sql_connection
39
+ # If a block is given, it will be executed on the connection
40
+ # For more information, see {Mongify::Database::NoSqlConnection}
39
41
  def no_sql_connection(options={}, &block)
40
42
  @no_sql_connection ||= Mongify::Database::NoSqlConnection.new(options)
41
43
  @no_sql_connection.instance_exec(&block) if block_given?
@@ -1,5 +1,6 @@
1
1
  require 'mongify/database/base_connection'
2
2
  require 'mongify/database/no_sql_connection'
3
3
  require 'mongify/database/sql_connection'
4
+ require 'mongify/database/data_row'
4
5
  require 'mongify/database/table'
5
- require 'mongify/database/column'
6
+ require 'mongify/database/column'
@@ -1,22 +1,25 @@
1
1
  module Mongify
2
+ # Module that deals with all database related items
2
3
  module Database
3
4
  #
4
- # Basic configuration for any sql or non sql database
5
+ # This is a Basic configuration for any sql or non sql database
5
6
  #
6
7
  class BaseConnection
7
-
8
+ # List of required fields to make a valid base connection
8
9
  REQUIRED_FIELDS = %w{host}
10
+ # List of all the available fields to make up a connection
9
11
  AVAILABLE_FIELDS = %w{adapter host username password database socket port encoding}
10
-
12
+
11
13
  def initialize(options=nil)
12
14
  if options
13
15
  options.stringify_keys!
14
16
  options.each do |key, value|
15
- instance_variable_set "@#{key}", value
17
+ instance_variable_set "@#{key.downcase}", value if AVAILABLE_FIELDS.include?(key.downcase)
16
18
  end
17
19
  end
18
20
  end
19
-
21
+
22
+ # Returns all settings as a hash, this is used mainly in building ActiveRecord::Base.establish_connection
20
23
  def to_hash
21
24
  hash = {}
22
25
  instance_variables.each do |variable|
@@ -26,7 +29,9 @@ module Mongify
26
29
  hash
27
30
  end
28
31
 
32
+ # Ensures the required fields are filled
29
33
  def valid?
34
+ #TODO: Improve this to create an errors array with detailed errors (or maybe just use activemodel)
30
35
  REQUIRED_FIELDS.each do |require_field|
31
36
  return false unless instance_variables.include?("@#{require_field}") and
32
37
  !instance_variable_get("@#{require_field}").to_s.empty?
@@ -34,32 +39,37 @@ module Mongify
34
39
  true
35
40
  end
36
41
 
37
-
42
+ # Used to setup connection, Raises NotImplementedError because it needs to be setup in BaseConnection's children
38
43
  def setup_connection_adapter
39
44
  raise NotImplementedError
40
45
  end
41
46
 
47
+ # Used to test connection, Raises NotImplementedError because it needs to be setup in BaseConnection's children
42
48
  def has_connection?
43
49
  raise NotImplementedError
44
50
  end
45
51
 
46
- def adapter(value=nil)
47
- @adapter = value.to_s unless value.nil?
48
- @adapter
49
- end
50
-
51
52
 
52
- def respond_to?(method, *args)
53
+ # Returns true if we are trying to respond_to AVAILABLE_FIELDS functions
54
+ def respond_to?(method, *args)
53
55
  return true if AVAILABLE_FIELDS.include?(method.to_s)
54
56
  super(method)
55
57
  end
56
-
58
+
59
+ # Building set and/or return functions for AVAILABLE_FIELDS
60
+ # Example:
61
+ #
62
+ # def host(value=nil)
63
+ # @host = value.to_s unless value.nil?
64
+ # @host
65
+ # end
66
+ #
57
67
  def method_missing(method, *args)
58
- method_name = method.to_s #.gsub("=", '')
68
+ method_name = method.to_s
59
69
  if AVAILABLE_FIELDS.include?(method_name.to_s)
60
70
  class_eval <<-EOF
61
71
  def #{method_name}(value=nil)
62
- @#{method_name} = value unless value.nil?
72
+ @#{method_name} = value.to_s unless value.nil?
63
73
  @#{method_name}
64
74
  end
65
75
  EOF
@@ -2,28 +2,88 @@ require 'active_record/connection_adapters/abstract/schema_definitions'
2
2
  module Mongify
3
3
  module Database
4
4
  #
5
- # A column in the sql table
5
+ # A column that is used to access sql data and transform it into the no sql database
6
6
  #
7
+ #
8
+ # ==== Structure
9
+ #
10
+ # Structure for defining a column is as follows:
11
+ # column "name", :type, {options}
12
+ # <em>Columns with no type given will be set to <tt>:string</tt></em>
13
+ #
14
+ # ==== Notes
15
+ # Leaving a column out when defining a table will result in a copy of the information (as a string).
16
+ # ==== Types
17
+ #
18
+ # Types of columns are supported:
19
+ # :key # Columns that are primary keys need to be marked as :key type
20
+ # :integer # Will be converted to a integer
21
+ # :float # Will be converted to a float
22
+ # :decimal # Will be converted to a BigDecimal
23
+ # :string # Will be converted to a string
24
+ # :text # Will be converted to a string
25
+ # :datetime # Will be converted to a Time format (DateTime is not currently supported in the Mongo ruby driver)
26
+ # :date # Will be converted to a Time format (Date is not currently supported in the Mongo ruby driver)
27
+ # :timestamps # Will be converted to a Time format
28
+ # :time # Will be converted to a Time format (the date portion of the Time object will be 2000-01-01)
29
+ # :binary # Will be converted to a string
30
+ # :boolean # Will be converted to a true or false values
31
+ #
32
+ # ==== Options
33
+ #
34
+ # column "post_id", :integer, :referneces => :posts # Referenced columns need to be marked as such, this will mean that they will be updated
35
+ # # with the new BSON::ObjectID.
36
+ # <b>NOTE: if you rename the table 'posts', you should set the :references to the new name</b>
37
+ #
38
+ # column "name", :string, :ignore => true # Ignoring a column will make the column NOT copy over to the new database
39
+ #
40
+ # column "surname", :string, :rename_to => 'last_name'# Rename_to allows you to rename the column
41
+ #
42
+ # column "post_id", :integer, :auto_detect => true # Will run auto detect and make this column a :references => 'posts', :on => 'post_id' for you
43
+ # # More used when reading a sql database, NOT recommended for use during processing of translation
44
+ #
7
45
  class Column
8
46
  attr_reader :sql_name, :type, :options
9
47
 
48
+ #List of available options for a column
10
49
  AVAILABLE_OPTIONS = ['references', 'ignore', 'rename_to']
11
-
50
+
51
+ # Auto detects if a column is an :key column or is a reference column
52
+ def self.auto_detect(column)
53
+ case column.sql_name.downcase
54
+ when 'id'
55
+ column.type = :key if column.type == :integer
56
+ when /(.*)_id/
57
+ column.references = $1.to_s.pluralize unless column.referenced? || column.type != :integer
58
+ end
59
+ end
60
+
12
61
  def initialize(sql_name, type=:string, options={})
13
62
  @sql_name = sql_name
14
- type = :string if type.nil?
15
- @type = type.is_a?(Symbol) ? type : type.to_sym
63
+ options, type = type, nil if type.is_a?(Hash)
64
+ self.type = type
16
65
  @options = options.stringify_keys
17
-
18
- auto_detect!
66
+ @auto_detect = @options.delete('auto_detect')
67
+ run_auto_detect!
19
68
 
20
69
  self
21
70
  end
22
71
 
72
+ # Allows you to set a type
73
+ def type=(value=:string)
74
+ value = :string if value.nil?
75
+ @type = value.is_a?(Symbol) ? value : value.to_sym
76
+ end
77
+
78
+ # Returns the no_sql record name
23
79
  def name
24
80
  @name ||= rename_to || sql_name
25
81
  end
26
82
 
83
+ # Returns a translated hash from a given value
84
+ # Example:
85
+ # @column = Column.new("surname", :string, :rename_to => 'last_name')
86
+ # @column.translate("Smith") # => {"last_name" => "Smith"}
27
87
  def translate(value)
28
88
  return {} if ignored?
29
89
  case type
@@ -34,13 +94,24 @@ module Mongify
34
94
  end
35
95
  end
36
96
 
97
+ # Returns a string representation of the column as it would show in a translation file.
98
+ # Mainly used during print out of translation file
37
99
  def to_print
38
100
  "column \"#{name}\", :#{type}".tap do |output|
39
101
  output_options = options.map{|k, v| (v == nil) ? nil : ":#{k} => \"#{v}\""}.compact
40
102
  output << ", #{output_options.join(', ')}" unless output_options.blank?
41
103
  end
42
104
  end
105
+ alias :to_s :to_print
43
106
 
107
+ # Sets up a accessor method for an option
108
+ #
109
+ # def rename_to=(value)
110
+ # options['rename_to'] = value
111
+ # end
112
+ # def rename_to
113
+ # options['rename_to']
114
+ # end
44
115
  def method_missing(meth, *args, &blk)
45
116
  method_name = meth.to_s.gsub("=", '')
46
117
  if AVAILABLE_OPTIONS.include?(method_name)
@@ -58,18 +129,36 @@ module Mongify
58
129
  end
59
130
  end
60
131
 
132
+ # Returns true if the column is a reference column
61
133
  def referenced?
62
134
  !self.options['references'].nil?
63
135
  end
64
136
 
137
+ # Returns true if column is being renamed
65
138
  def renamed?
66
139
  self.name != self.sql_name
67
140
  end
68
141
 
142
+ # Returns true if column is a :key type column
143
+ def key?
144
+ self.type == :key
145
+ end
146
+
147
+ # Returns true if column should be auto_detected (passed via options)
148
+ def auto_detect?
149
+ !!@auto_detect
150
+ end
151
+
152
+ # Returns true if column is ignored
153
+ def ignored?
154
+ !!self.ignore
155
+ end
156
+
69
157
  #######
70
158
  private
71
159
  #######
72
160
 
161
+ # Casts the value to a given type
73
162
  def type_cast(value)
74
163
  return nil if value.nil?
75
164
  case type
@@ -81,28 +170,17 @@ module Mongify
81
170
  when :datetime then ActiveRecord::ConnectionAdapters::Column.string_to_time(value)
82
171
  when :timestamp then ActiveRecord::ConnectionAdapters::Column.string_to_time(value)
83
172
  when :time then ActiveRecord::ConnectionAdapters::Column.string_to_dummy_time(value)
84
- when :date then ActiveRecord::ConnectionAdapters::Column.string_to_date(value)
173
+ when :date then ActiveRecord::ConnectionAdapters::Column.string_to_time(value)
85
174
  when :binary then ActiveRecord::ConnectionAdapters::Column.binary_to_string(value)
86
175
  when :boolean then ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
87
176
  else value
88
177
  end
89
178
  end
90
179
 
91
- def key?
92
- self.type == :key
93
- end
94
-
95
- def ignored?
96
- !!self.ignore
97
- end
98
180
 
99
- def auto_detect!
100
- case name.downcase
101
- when 'id'
102
- @type = :key if self.type == :integer
103
- when /(.*)_id/
104
- self.references = $1.to_s.pluralize unless self.references
105
- end
181
+ # runs auto detect (see {Mongify::Database::Column.auto_detect})
182
+ def run_auto_detect!
183
+ self.class.auto_detect(self) if auto_detect?
106
184
  end
107
185
 
108
186
 
@@ -0,0 +1,67 @@
1
+ module Mongify
2
+ module Database
3
+ # Class that will be used to allow user to modify data for the given row
4
+ class DataRow
5
+ def initialize(hash={})
6
+ @hash = hash.dup.stringify_keys!
7
+ end
8
+
9
+ # See if a given key is included
10
+ def include?(key)
11
+ @hash.has_key?(key)
12
+ end
13
+
14
+ # Deletes a given key from the object
15
+ def delete(key)
16
+ @hash.delete(key)
17
+ end
18
+
19
+ # Returns a list of available keys
20
+ def keys
21
+ @hash.keys
22
+ end
23
+
24
+ # Outputs an hash
25
+ # This is used to write into the no sql database
26
+ def to_hash
27
+ @hash
28
+ end
29
+
30
+ # Used to manually read an attribute
31
+ def read_attribute(key)
32
+ @hash[key.to_s]
33
+ end
34
+ # Used to manually write an attribute
35
+ def write_attribute(key, value)
36
+ @hash[key.to_s] = value
37
+ end
38
+
39
+ # Passes Inspect onto the internal hash
40
+ def inspect
41
+ @hash.inspect
42
+ end
43
+
44
+ # Updated respond_to to return true if it's a key the hash
45
+ def respond_to?(method)
46
+ return true if @hash.has_key?(method.gsub('=', ''))
47
+ super(method)
48
+ end
49
+
50
+ # Added the ability to read and write attributes in the hash
51
+ def method_missing(meth, *args, &blk)
52
+ match = meth.to_s.match(/^([a-zA-Z\_]+)(=|$)$/)
53
+ if match
54
+ attribute, setter = match[1], !match[2].blank?
55
+ if setter
56
+ write_attribute(attribute, args.first)
57
+ else
58
+ read_attribute(attribute)
59
+ end
60
+ else
61
+ super(meth, *args, &blk)
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -2,10 +2,28 @@ require 'mongo'
2
2
  module Mongify
3
3
  module Database
4
4
  #
5
- # No sql connection configuration
5
+ # No Sql Connection configuration
6
+ #
7
+ # Basic format should look something like this:
8
+ #
9
+ # no_sql_connection do
10
+ # adapter "mongodb"
11
+ # host "localhost"
12
+ # database "my_database"
13
+ # end
14
+ #
15
+ # Possible attributes:
16
+ # adapter
17
+ # host
18
+ # database
19
+ # username
20
+ # password
21
+ # port
6
22
  #
7
23
  class NoSqlConnection < Mongify::Database::BaseConnection
8
24
  include Mongo
25
+
26
+ #Required fields for a no sql connection
9
27
  REQUIRED_FIELDS = %w{host database}
10
28
 
11
29
  def initialize(options=nil)
@@ -13,57 +31,77 @@ module Mongify
13
31
  adapter 'mongodb' if adapter.nil? || adapter == 'mongo'
14
32
  end
15
33
 
34
+ # Sets and/or returns a adapter
35
+ # It takes care of renaming adapter('mongo') to 'mongodb'
36
+ def adapter(name=nil)
37
+ name = 'mongodb' if name && name.to_s.downcase == 'mongo'
38
+ super(name)
39
+ end
40
+
41
+ # Returns a connection string that can be used to build a Mongo Connection
42
+ # (Currently this isn't used due to some issue early on in development)
16
43
  def connection_string
17
44
  "#{@adapter}://#{@host}#{":#{@port}" if @port}"
18
45
  end
19
46
 
47
+ # Returns true or false depending if the given attributes are present and valid to make up a
48
+ # connection to a mongo server
20
49
  def valid?
21
- @database.present? && @host.present?
50
+ super && @database.present?
22
51
  end
23
52
 
53
+ # Sets up a connection to the database
54
+ def setup_connection_adapter
55
+ connection = Connection.new(host, port)
56
+ connection.add_auth(database, username, password) if username && password
57
+ connection
58
+ end
59
+
60
+ # Returns a mongo connection
24
61
  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
62
+ @connection ||= setup_connection_adapter
29
63
  end
30
64
 
65
+ # Returns true or false depending if we have a connection to a mongo server
31
66
  def has_connection?
32
67
  connection.connected?
33
68
  end
34
69
 
70
+ # Returns the database from the connection
35
71
  def db
36
72
  @db ||= connection[database]
37
73
  end
38
74
 
75
+ # Returns a hash of all the rows from the database of a given collection
39
76
  def select_rows(collection)
40
77
  db[collection].find
41
78
  end
42
79
 
80
+ # Inserts into the collection a given row
43
81
  def insert_into(colleciton_name, row)
44
82
  db[colleciton_name].insert(row, :safe => true)
45
83
  end
46
84
 
85
+ # Updates a collection item with a given ID with the given attributes
47
86
  def update(colleciton_name, id, attributes)
48
87
  db[colleciton_name].update({"_id" => id}, attributes)
49
88
  end
50
89
 
90
+ # Finds one item from a collection with the given query
51
91
  def find_one(collection_name, query)
52
92
  db[collection_name].find_one(query)
53
93
  end
54
94
 
95
+ # Returns a row of a item from a given collection with a given pre_mongified_id
55
96
  def get_id_using_pre_mongified_id(colleciton_name, pre_mongified_id)
56
97
  db[colleciton_name].find_one('pre_mongified_id' => pre_mongified_id).try(:[], '_id')
57
98
  end
58
99
 
100
+ # Removes pre_mongified_id from all records in a given collection
59
101
  def remove_pre_mongified_ids(collection_name)
60
102
  db[collection_name].update({}, { '$unset' => { 'pre_mongified_id' => 1} }, :multi => true)
61
103
  end
62
104
 
63
- def reset!
64
- @connection = nil
65
- @db = nil
66
- end
67
105
  end
68
106
  end
69
107
  end