lotus-model 0.3.2 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acf5bf11caa86301e6e5335064ca5c5454df5b86
4
- data.tar.gz: 0782b930d37c6236c6c676017ae34caf68b1c2d2
3
+ metadata.gz: 446ed910e7e39e23fe19ceb56cf66ec8bc545145
4
+ data.tar.gz: 121c4c8b74f095a7a5e07396d6a37f2fdbeb6a5b
5
5
  SHA512:
6
- metadata.gz: 0299bb1a135bb8f43804f44968126425fde51be8377b035f2635312d49cc42bc2d4ab62374967f3a574007ae5c6f16e43ba5867372c495c05113fe4420126efa
7
- data.tar.gz: a69e4eddfb3b10999487d08bf754f6bf413ac487e80ecb1b3d9b5a34f19856e9da9633854a8c987afa5a5f3d74589835dddde4f5a0265e6d0179ff1af72f33a9
6
+ metadata.gz: 4df597074019ffca04b8552579a0febd2382462ca6a9472be3b99b5a780b590f0485ec9872018a4dd9dcb31b1243f3f4838096aa4b4030882f36b81971b336f3
7
+ data.tar.gz: 5dd2f57855b6b08011e8b8e230b610b5f3c3a9edfd4d1f89203faa34b08b25664310d4713f44190c9c7aedc85ebf02c907bfa0aaccc2ee69d0dd39aa56077cef
@@ -1,6 +1,13 @@
1
1
  # Lotus::Model
2
2
  A persistence layer for Lotus
3
3
 
4
+ ## v0.4.0 - 2015-06-23
5
+ ### Added
6
+ - [Luca Guidi] Database migrations
7
+
8
+ ### Changed
9
+ - [Matthew Bellantoni] Made `Repository.execute` not callable from the outside (private Ruby method, public API).
10
+
4
11
  ## v0.3.2 - 2015-05-22
5
12
  ### Added
6
13
  - [Dmitry Tymchuk & Luca Guidi] Fix for dirty tracking of attributes changed in place (eg. `book.tags << 'non-fiction'`)
data/README.md CHANGED
@@ -298,6 +298,36 @@ class ArticleRepository
298
298
  end
299
299
  ```
300
300
 
301
+ You can also extract the common logic from your repository into a module to reuse it in other repositories. Here is a pagination example:
302
+
303
+ ```ruby
304
+ module RepositoryHelpers
305
+ module Pagination
306
+ def paginate(limit: 10, offset: 0)
307
+ query do
308
+ limit(limit).offset(offset)
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+ class ArticleRepository
315
+ include Lotus::Repository
316
+ extend RepositoryHelpers::Pagination
317
+
318
+ def self.published
319
+ query do
320
+ where(published: true)
321
+ end
322
+ end
323
+
324
+ # other repository-specific methods here
325
+ end
326
+ ```
327
+
328
+ That will allow `.paginate` usage on `ArticleRepository`, for example:
329
+ `ArticleRepository.published.paginate(15, 0)`
330
+
301
331
  **Your models and repositories have to be in the same namespace.** Otherwise `Lotus::Model::Mapper#load!`
302
332
  will not initialize your repositories correctly.
303
333
 
@@ -413,6 +443,29 @@ In the example above, we reuse the adapter because the target tables (`people` a
413
443
 
414
444
  An object that implements an interface for querying the database.
415
445
  This interface may vary, according to the adapter's specifications.
446
+
447
+ Here is common interface for existing class:
448
+
449
+ * `.all` - Resolves the query by fetching records from the database and translating them into entities
450
+ * `.where`, `.and` - Adds a condition that behaves like SQL `WHERE`
451
+ * `.or` - Adds a condition that behaves like SQL `OR`
452
+ * `.exclude`, `.not` - Logical negation of a #where condition
453
+ * `.select` - Selects only the specified columns
454
+ * `.order`, `.asc` - Specify the ascending order of the records, sorted by the given columns
455
+ * `.reverse_order`, `.desc` - Specify the descending order of the records, sorted by the given columns
456
+ * `.limit` - Limit the number of records to return
457
+ * `.offset` - Specify an `OFFSET` clause. Due to SQL syntax restriction, offset MUST be used with `#limit`
458
+ * `.sum` - Returns the sum of the values for the given column
459
+ * `.average`, `.avg` - Returns the average of the values for the given column
460
+ * `.max` - Returns the maximum value for the given column
461
+ * `.min` - Returns the minimum value for the given column
462
+ * `.interval` - Returns the difference between the MAX and MIN for the given column
463
+ * `.range` - Returns a range of values between the MAX and the MIN for the given column
464
+ * `.exist?` - Checks if at least one record exists for the current conditions
465
+ * `.count` - Returns a count of the records for the current conditions
466
+
467
+ If you need more information regarding those methods, you can use comments from [memory](https://github.com/lotus/model/blob/master/lib/lotus/model/adapters/memory/query.rb#L29) or [sql](https://github.com/lotus/model/blob/master/lib/lotus/model/adapters/sql/query.rb#L28) adapters interface.
468
+
416
469
  Think of an adapter for Redis, it will probably employ different strategies to filter records than an SQL query object.
417
470
 
418
471
  ### Conventions
@@ -207,13 +207,14 @@ module Lotus
207
207
  # .exclude(company: 'enterprise')
208
208
  #
209
209
  # # => SELECT * FROM `projects` WHERE (`language` != 'java') AND (`company` != 'enterprise')
210
+ #
210
211
  # @example Expressions
211
212
  #
212
213
  # query.exclude{ age > 31 }
213
214
  #
214
215
  # # => SELECT * FROM `users` WHERE (`age` <= 31)
215
216
  def exclude(condition = nil, &blk)
216
- _push_to_conditions(:exclude, condition || blk).inspect
217
+ _push_to_conditions(:exclude, condition || blk)
217
218
  self
218
219
  end
219
220
 
@@ -10,6 +10,21 @@ module Lotus
10
10
  #
11
11
  # @since 0.2.0
12
12
  class Configuration
13
+ # Default migrations path
14
+ #
15
+ # @since 0.4.0
16
+ # @api private
17
+ #
18
+ # @see Lotus::Model::Configuration#migrations
19
+ DEFAULT_MIGRATIONS_PATH = Pathname.new('db/migrations').freeze
20
+
21
+ # Default schema path
22
+ #
23
+ # @since 0.4.0
24
+ # @api private
25
+ #
26
+ # @see Lotus::Model::Configuration#schema
27
+ DEFAULT_SCHEMA_PATH = Pathname.new('db/schema.sql').freeze
13
28
 
14
29
  # The persistence mapper
15
30
  #
@@ -45,6 +60,8 @@ module Lotus
45
60
  @adapter_config = nil
46
61
  @mapper = NullMapper.new
47
62
  @mapper_config = nil
63
+ @migrations = DEFAULT_MIGRATIONS_PATH
64
+ @schema = DEFAULT_SCHEMA_PATH
48
65
  end
49
66
 
50
67
  alias_method :unload!, :reset!
@@ -137,6 +154,77 @@ module Lotus
137
154
  @mapper_config = Lotus::Model::Config::Mapper.new(path, &blk)
138
155
  end
139
156
 
157
+ # Migrations directory
158
+ #
159
+ # It defaults to <tt>db/migrations</tt>.
160
+ #
161
+ # @overload migrations
162
+ # Get migrations directory
163
+ # @return [Pathname] migrations directory
164
+ #
165
+ # @overload migrations(path)
166
+ # Set migrations directory
167
+ # @param path [String,Pathname] the path
168
+ # @raise [Errno::ENOENT] if the given path doesn't exist
169
+ #
170
+ # @since 0.4.0
171
+ #
172
+ # @see Lotus::Model::Migrations::DEFAULT_MIGRATIONS_PATH
173
+ #
174
+ # @example Set Custom Path
175
+ # require 'lotus/model'
176
+ #
177
+ # Lotus::Model.configure do
178
+ # # ...
179
+ # migrations 'path/to/migrations'
180
+ # end
181
+ def migrations(path = nil)
182
+ if path.nil?
183
+ @migrations
184
+ else
185
+ @migrations = root.join(path).realpath
186
+ end
187
+ end
188
+
189
+ # Schema
190
+ #
191
+ # It defaults to <tt>db/schema.sql</tt>.
192
+ #
193
+ # @overload schema
194
+ # Get schema path
195
+ # @return [Pathname] schema path
196
+ #
197
+ # @overload schema(path)
198
+ # Set schema path
199
+ # @param path [String,Pathname] the path
200
+ #
201
+ # @since 0.4.0
202
+ #
203
+ # @see Lotus::Model::Migrations::DEFAULT_SCHEMA_PATH
204
+ #
205
+ # @example Set Custom Path
206
+ # require 'lotus/model'
207
+ #
208
+ # Lotus::Model.configure do
209
+ # # ...
210
+ # schema 'path/to/schema.sql'
211
+ # end
212
+ def schema(path = nil)
213
+ if path.nil?
214
+ @schema
215
+ else
216
+ @schema = root.join(path)
217
+ end
218
+ end
219
+
220
+ # Root directory
221
+ #
222
+ # @since 0.4.0
223
+ # @api private
224
+ def root
225
+ Lotus.respond_to?(:root) ? Lotus.root : Pathname.pwd
226
+ end
227
+
140
228
  # Duplicate by copying the settings in a new instance.
141
229
  #
142
230
  # @return [Lotus::Model::Configuration] a copy of the configuration
@@ -0,0 +1,321 @@
1
+ require 'sequel'
2
+ require 'sequel/extensions/migration'
3
+ require 'lotus/model/migrator/adapter'
4
+
5
+ module Lotus
6
+ module Model
7
+ # Migration error
8
+ #
9
+ # @since 0.4.0
10
+ class MigrationError < ::StandardError
11
+ end
12
+
13
+ # Define a migration
14
+ #
15
+ # It must define an up/down strategy to write schema changes (up) and to
16
+ # rollback them (down).
17
+ #
18
+ # We can use <tt>up</tt> and <tt>down</tt> blocks for custom strategies, or
19
+ # only one <tt>change</tt> block that automatically implements "down" strategy.
20
+ #
21
+ # @param blk [Proc] a block that defines up/down or change database migration
22
+ #
23
+ # @since 0.4.0
24
+ #
25
+ # @example Use up/down blocks
26
+ # Lotus::Model.migration do
27
+ # up do
28
+ # create_table :books do
29
+ # primary_key :id
30
+ # column :book, String
31
+ # end
32
+ # end
33
+ #
34
+ # down do
35
+ # drop_table :books
36
+ # end
37
+ # end
38
+ #
39
+ # @example Use change block
40
+ # Lotus::Model.migration do
41
+ # change do
42
+ # create_table :books do
43
+ # primary_key :id
44
+ # column :book, String
45
+ # end
46
+ # end
47
+ #
48
+ # # DOWN strategy is automatically generated
49
+ # end
50
+ def self.migration(&blk)
51
+ Sequel.migration(&blk)
52
+ end
53
+
54
+ # Database schema migrator
55
+ #
56
+ # @since 0.4.0
57
+ module Migrator
58
+ # Create database defined by current configuration.
59
+ #
60
+ # It's only implemented for the following databases:
61
+ #
62
+ # * SQLite3
63
+ # * PostgreSQL
64
+ # * MySQL
65
+ #
66
+ # @raise [Lotus::Model::MigrationError] if an error occurs
67
+ #
68
+ # @since 0.4.0
69
+ #
70
+ # @see Lotus::Model::Configuration#adapter
71
+ #
72
+ # @example
73
+ # require 'lotus/model'
74
+ # require 'lotus/model/migrator'
75
+ #
76
+ # Lotus::Model.configure do
77
+ # # ...
78
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
79
+ # end
80
+ #
81
+ # Lotus::Model::Migrator.create # Creates `foo' database
82
+ def self.create
83
+ adapter(connection).create
84
+ end
85
+
86
+ # Drop database defined by current configuration.
87
+ #
88
+ # It's only implemented for the following databases:
89
+ #
90
+ # * SQLite3
91
+ # * PostgreSQL
92
+ # * MySQL
93
+ #
94
+ # @raise [Lotus::Model::MigrationError] if an error occurs
95
+ #
96
+ # @since 0.4.0
97
+ #
98
+ # @see Lotus::Model::Configuration#adapter
99
+ #
100
+ # @example
101
+ # require 'lotus/model'
102
+ # require 'lotus/model/migrator'
103
+ #
104
+ # Lotus::Model.configure do
105
+ # # ...
106
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
107
+ # end
108
+ #
109
+ # Lotus::Model::Migrator.drop # Drops `foo' database
110
+ def self.drop
111
+ adapter(connection).drop
112
+ end
113
+
114
+ # Migrate database schema
115
+ #
116
+ # It's possible to migrate "down" by specifying a version
117
+ # (eg. <tt>"20150610133853"</tt>)
118
+ #
119
+ # @param version [String,NilClass] target version
120
+ #
121
+ # @raise [Lotus::Model::MigrationError] if an error occurs
122
+ #
123
+ # @since 0.4.0
124
+ #
125
+ # @see Lotus::Model::Configuration#adapter
126
+ # @see Lotus::Model::Configuration#migrations
127
+ #
128
+ # @example Migrate Up
129
+ # require 'lotus/model'
130
+ # require 'lotus/model/migrator'
131
+ #
132
+ # Lotus::Model.configure do
133
+ # # ...
134
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
135
+ # migrations 'db/migrations'
136
+ # end
137
+ #
138
+ # # Reads all files from "db/migrations" and apply them
139
+ # Lotus::Model::Migrator.migrate
140
+ #
141
+ # @example Migrate Down
142
+ # require 'lotus/model'
143
+ # require 'lotus/model/migrator'
144
+ #
145
+ # Lotus::Model.configure do
146
+ # # ...
147
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
148
+ # migrations 'db/migrations'
149
+ # end
150
+ #
151
+ # # Reads all files from "db/migrations" and apply them
152
+ # Lotus::Model::Migrator.migrate
153
+ #
154
+ # # Migrate to a specifiy version
155
+ # Lotus::Model::Migrator.migrate(version: "20150610133853")
156
+ def self.migrate(version: nil)
157
+ version = Integer(version) unless version.nil?
158
+
159
+ Sequel::Migrator.run(connection, migrations, target: version, allow_missing_migration_files: true) if migrations?
160
+ rescue Sequel::Migrator::Error => e
161
+ raise MigrationError.new(e.message)
162
+ end
163
+
164
+ # Migrate, dump schema, delete migrations.
165
+ #
166
+ # This is an experimental feature.
167
+ # It may change or be removed in the future.
168
+ #
169
+ # Actively developed applications accumulate tons of migrations.
170
+ # In the long term they are hard to maintain and slow to execute.
171
+ #
172
+ # "Apply" feature solves this problem.
173
+ #
174
+ # It keeps an updated SQL file with the structure of the database.
175
+ # This file can be used to create fresh databases for developer machines
176
+ # or during testing. This is faster than to run dozen or hundred migrations.
177
+ #
178
+ # When we use "apply", it eliminates all the migrations that are no longer
179
+ # necessary.
180
+ #
181
+ # @raise [Lotus::Model::MigrationError] if an error occurs
182
+ #
183
+ # @since 0.4.0
184
+ #
185
+ # @see Lotus::Model::Configuration#adapter
186
+ # @see Lotus::Model::Configuration#migrations
187
+ #
188
+ # @example Apply Migrations
189
+ # require 'lotus/model'
190
+ # require 'lotus/model/migrator'
191
+ #
192
+ # Lotus::Model.configure do
193
+ # # ...
194
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
195
+ # migrations 'db/migrations'
196
+ # schema 'db/schema.sql'
197
+ # end
198
+ #
199
+ # # Reads all files from "db/migrations" and apply and delete them.
200
+ # # It generates an updated version of "db/schema.sql"
201
+ # Lotus::Model::Migrator.apply
202
+ def self.apply
203
+ migrate
204
+ adapter(connection).dump
205
+ delete_migrations
206
+ end
207
+
208
+ # Prepare database: drop, create, load schema (if any), migrate.
209
+ #
210
+ # This is designed for development machines and testing mode.
211
+ # It works faster if used with <tt>apply</tt>.
212
+ #
213
+ # @raise [Lotus::Model::MigrationError] if an error occurs
214
+ #
215
+ # @since 0.4.0
216
+ #
217
+ # @see Lotus::Model::Migrator.apply
218
+ #
219
+ # @example Prepare Database
220
+ # require 'lotus/model'
221
+ # require 'lotus/model/migrator'
222
+ #
223
+ # Lotus::Model.configure do
224
+ # # ...
225
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
226
+ # migrations 'db/migrations'
227
+ # end
228
+ #
229
+ # Lotus::Model::Migrator.prepare # => creates `foo' and run migrations
230
+ #
231
+ # @example Prepare Database (with schema dump)
232
+ # require 'lotus/model'
233
+ # require 'lotus/model/migrator'
234
+ #
235
+ # Lotus::Model.configure do
236
+ # # ...
237
+ # adapter type: :sql, uri: 'postgres://localhost/foo'
238
+ # migrations 'db/migrations'
239
+ # schema 'db/schema.sql'
240
+ # end
241
+ #
242
+ # Lotus::Model::Migrator.apply # => updates schema dump
243
+ # Lotus::Model::Migrator.prepare # => creates `foo', load schema and run pending migrations (if any)
244
+ def self.prepare
245
+ drop rescue nil
246
+ create
247
+ adapter(connection).load
248
+ migrate
249
+ end
250
+
251
+ # Return current database version timestamp
252
+ #
253
+ # If no migrations were ran, it returns <tt>nil</tt>.
254
+ #
255
+ # @return [String,NilClass] current version, if previously migrated
256
+ #
257
+ # @since 0.4.0
258
+ #
259
+ # @example
260
+ # # Given last migrations is:
261
+ # # 20150610133853_create_books.rb
262
+ #
263
+ # Lotus::Model::Migrator.version # => "20150610133853"
264
+ def self.version
265
+ adapter(connection).version
266
+ end
267
+
268
+ private
269
+
270
+ # Loads an adapter for the given connection
271
+ #
272
+ # @since 0.4.0
273
+ # @api private
274
+ def self.adapter(connection)
275
+ Adapter.for(connection)
276
+ end
277
+
278
+ # Delete all the migrations
279
+ #
280
+ # @since 0.4.0
281
+ # @api private
282
+ def self.delete_migrations
283
+ migrations.each_child(&:delete)
284
+ end
285
+
286
+ # Database connection
287
+ #
288
+ # @since 0.4.0
289
+ # @api private
290
+ def self.connection
291
+ Sequel.connect(
292
+ configuration.adapter.uri
293
+ )
294
+ end
295
+
296
+ # Lotus::Model configuration
297
+ #
298
+ # @since 0.4.0
299
+ # @api private
300
+ def self.configuration
301
+ Model.configuration
302
+ end
303
+
304
+ # Migrations directory
305
+ #
306
+ # @since 0.4.0
307
+ # @api private
308
+ def self.migrations
309
+ configuration.migrations
310
+ end
311
+
312
+ # Check if there are migrations
313
+ #
314
+ # @since 0.4.0
315
+ # @api private
316
+ def self.migrations?
317
+ migrations.children.any?
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,169 @@
1
+ require 'uri'
2
+ require 'shellwords'
3
+
4
+ module Lotus
5
+ module Model
6
+ module Migrator
7
+ # Migrator base adapter
8
+ #
9
+ # @since 0.4.0
10
+ # @api private
11
+ class Adapter
12
+ # Migrations table to store migrations metadata.
13
+ #
14
+ # @since 0.4.0
15
+ # @api private
16
+ MIGRATIONS_TABLE = :schema_migrations
17
+
18
+ # Migrations table version column
19
+ #
20
+ # @since 0.4.0
21
+ # @api private
22
+ MIGRATIONS_TABLE_VERSION_COLUMN = :filename
23
+
24
+ # Loads and returns a specific adapter for the given connection.
25
+ #
26
+ # @since 0.4.0
27
+ # @api private
28
+ def self.for(connection)
29
+ case connection.database_type
30
+ when :sqlite
31
+ require 'lotus/model/migrator/sqlite_adapter'
32
+ SQLiteAdapter
33
+ when :postgres
34
+ require 'lotus/model/migrator/postgres_adapter'
35
+ PostgresAdapter
36
+ when :mysql
37
+ require 'lotus/model/migrator/mysql_adapter'
38
+ MySQLAdapter
39
+ else
40
+ self
41
+ end.new(connection)
42
+ end
43
+
44
+ # Initialize an adapter
45
+ #
46
+ # @since 0.4.0
47
+ # @api private
48
+ def initialize(connection)
49
+ @connection = connection
50
+ end
51
+
52
+ # Create database.
53
+ # It must be implemented by subclasses.
54
+ #
55
+ # @since 0.4.0
56
+ # @api private
57
+ #
58
+ # @see Lotus::Model::Migrator.create
59
+ def create
60
+ raise MigrationError.new("Current adapter (#{ @connection.database_type }) doesn't support create.")
61
+ end
62
+
63
+ # Drop database.
64
+ # It must be implemented by subclasses.
65
+ #
66
+ # @since 0.4.0
67
+ # @api private
68
+ #
69
+ # @see Lotus::Model::Migrator.drop
70
+ def drop
71
+ raise MigrationError.new("Current adapter (#{ @connection.database_type }) doesn't support drop.")
72
+ end
73
+
74
+ # Load database schema.
75
+ # It must be implemented by subclasses.
76
+ #
77
+ # @since 0.4.0
78
+ # @api private
79
+ #
80
+ # @see Lotus::Model::Migrator.prepare
81
+ def load
82
+ raise MigrationError.new("Current adapter (#{ @connection.database_type }) doesn't support load.")
83
+ end
84
+
85
+ # Database version.
86
+ #
87
+ # @since 0.4.0
88
+ # @api private
89
+ def version
90
+ return unless @connection.tables.include?(MIGRATIONS_TABLE)
91
+
92
+ if record = @connection[MIGRATIONS_TABLE].order(MIGRATIONS_TABLE_VERSION_COLUMN).last
93
+ record.fetch(MIGRATIONS_TABLE_VERSION_COLUMN).scan(/\A[\d]{14}/).first.to_s
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ # @since 0.4.0
100
+ # @api private
101
+ def new_connection
102
+ uri = URI.parse(@connection.uri)
103
+ scheme, userinfo, host, port = uri.select(:scheme, :userinfo, :host, :port)
104
+
105
+ uri = "#{ scheme }://"
106
+ uri += "#{ userinfo }@" unless userinfo.nil?
107
+ uri += host
108
+ uri += ":#{ port }" unless port.nil?
109
+
110
+ Sequel.connect(uri)
111
+ end
112
+
113
+ # @since 0.4.0
114
+ # @api private
115
+ def database
116
+ escape options.fetch(:database)
117
+ end
118
+
119
+ # @since 0.4.0
120
+ # @api private
121
+ def host
122
+ escape options.fetch(:host)
123
+ end
124
+
125
+ # @since 0.4.0
126
+ # @api private
127
+ def port
128
+ escape options.fetch(:port)
129
+ end
130
+
131
+ # @since 0.4.0
132
+ # @api private
133
+ def username
134
+ escape options.fetch(:user)
135
+ end
136
+
137
+ # @since 0.4.0
138
+ # @api private
139
+ def password
140
+ escape options.fetch(:password)
141
+ end
142
+
143
+ # @since 0.4.0
144
+ # @api private
145
+ def schema
146
+ Model.configuration.schema
147
+ end
148
+
149
+ # @since 0.4.0
150
+ # @api private
151
+ def migrations_table
152
+ escape MIGRATIONS_TABLE
153
+ end
154
+
155
+ # @since 0.4.0
156
+ # @api private
157
+ def options
158
+ @connection.opts
159
+ end
160
+
161
+ # @since 0.4.0
162
+ # @api private
163
+ def escape(string)
164
+ Shellwords.escape(string) unless string.nil?
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,64 @@
1
+ module Lotus
2
+ module Model
3
+ module Migrator
4
+ # MySQL adapter
5
+ #
6
+ # @since 0.4.0
7
+ # @api private
8
+ class MySQLAdapter < Adapter
9
+ # @since 0.4.0
10
+ # @api private
11
+ def create
12
+ new_connection.run %(CREATE DATABASE #{ database };)
13
+ end
14
+
15
+ # @since 0.4.0
16
+ # @api private
17
+ def drop
18
+ new_connection.run %(DROP DATABASE #{ database };)
19
+ rescue Sequel::DatabaseError => e
20
+ message = if e.message.match(/doesn\'t exist/)
21
+ "Cannot find database: #{ database }"
22
+ else
23
+ e.message
24
+ end
25
+
26
+ raise MigrationError.new(message)
27
+ end
28
+
29
+ # @since 0.4.0
30
+ # @api private
31
+ def dump
32
+ dump_structure
33
+ dump_migrations_data
34
+ end
35
+
36
+ # @since 0.4.0
37
+ # @api private
38
+ def load
39
+ load_structure
40
+ end
41
+
42
+ private
43
+
44
+ # @since 0.4.0
45
+ # @api private
46
+ def dump_structure
47
+ system "mysqldump --user=#{ username } --password=#{ password } --no-data --skip-comments --ignore-table=#{ database }.#{ migrations_table } #{ database } > #{ schema }"
48
+ end
49
+
50
+ # @since 0.4.0
51
+ # @api private
52
+ def load_structure
53
+ system "mysql --user=#{ username } --password=#{ password } #{ database } < #{ escape(schema) }" if schema.exist?
54
+ end
55
+
56
+ # @since 0.4.0
57
+ # @api private
58
+ def dump_migrations_data
59
+ system "mysqldump --user=#{ username } --password=#{ password } --skip-comments #{ database } #{ migrations_table } >> #{ schema }"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,99 @@
1
+ module Lotus
2
+ module Model
3
+ module Migrator
4
+ # PostgreSQL adapter
5
+ #
6
+ # @since 0.4.0
7
+ # @api private
8
+ class PostgresAdapter < Adapter
9
+ # @since 0.4.0
10
+ # @api private
11
+ HOST = 'PGHOST'.freeze
12
+
13
+ # @since 0.4.0
14
+ # @api private
15
+ PORT = 'PGPORT'.freeze
16
+
17
+ # @since 0.4.0
18
+ # @api private
19
+ USER = 'PGUSER'.freeze
20
+
21
+ # @since 0.4.0
22
+ # @api private
23
+ PASSWORD = 'PGPASSWORD'.freeze
24
+
25
+ # @since 0.4.0
26
+ # @api private
27
+ def create
28
+ new_connection.run %(CREATE DATABASE "#{ database }"#{ create_options })
29
+ end
30
+
31
+ # @since 0.4.0
32
+ # @api private
33
+ def drop
34
+ new_connection.run %(DROP DATABASE "#{ database }")
35
+ rescue Sequel::DatabaseError => e
36
+ message = if e.message.match(/does not exist/)
37
+ "Cannot find database: #{ database }"
38
+ else
39
+ e.message
40
+ end
41
+
42
+ raise MigrationError.new(message)
43
+ end
44
+
45
+ # @since 0.4.0
46
+ # @api private
47
+ def dump
48
+ set_environment_variables
49
+ dump_structure
50
+ dump_migrations_data
51
+ end
52
+
53
+ # @since 0.4.0
54
+ # @api private
55
+ def load
56
+ set_environment_variables
57
+ load_structure
58
+ end
59
+
60
+ private
61
+
62
+ # @since 0.4.0
63
+ # @api private
64
+ def create_options
65
+ result = ""
66
+ result += %( OWNER "#{ username }") unless username.nil?
67
+ result
68
+ end
69
+
70
+ # @since 0.4.0
71
+ # @api private
72
+ def set_environment_variables
73
+ ENV[HOST] = host unless host.nil?
74
+ ENV[PORT] = port.to_s unless port.nil?
75
+ ENV[PASSWORD] = password unless password.nil?
76
+ ENV[USER] = username unless username.nil?
77
+ end
78
+
79
+ # @since 0.4.0
80
+ # @api private
81
+ def dump_structure
82
+ system "pg_dump -i -s -x -O -T #{ migrations_table } -f #{ escape(schema) } #{ database }"
83
+ end
84
+
85
+ # @since 0.4.0
86
+ # @api private
87
+ def load_structure
88
+ system "psql -X -q -f #{ escape(schema) } #{ database }" if schema.exist?
89
+ end
90
+
91
+ # @since 0.4.0
92
+ # @api private
93
+ def dump_migrations_data
94
+ system "pg_dump -t #{ migrations_table } #{ database } >> #{ escape(schema) }"
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,110 @@
1
+ require 'pathname'
2
+
3
+ module Lotus
4
+ module Model
5
+ module Migrator
6
+ # SQLite3 Migrator
7
+ #
8
+ # @since 0.4.0
9
+ # @api private
10
+ class SQLiteAdapter < Adapter
11
+ # No-op for in-memory databases
12
+ #
13
+ # @since 0.4.0
14
+ # @api private
15
+ module Memory
16
+ # @since 0.4.0
17
+ # @api private
18
+ def create
19
+ end
20
+
21
+ # @since 0.4.0
22
+ # @api private
23
+ def drop
24
+ end
25
+ end
26
+
27
+ # Initialize adapter
28
+ #
29
+ # @since 0.4.0
30
+ # @api private
31
+ def initialize(connection)
32
+ super
33
+ extend Memory if memory?
34
+ end
35
+
36
+ # @since 0.4.0
37
+ # @api private
38
+ def create
39
+ path.dirname.mkpath
40
+ FileUtils.touch(path)
41
+ rescue Errno::EACCES
42
+ raise MigrationError.new("Permission denied: #{ path.sub(/\A\/\//, '') }")
43
+ end
44
+
45
+ # @since 0.4.0
46
+ # @api private
47
+ def drop
48
+ path.delete
49
+ rescue Errno::ENOENT
50
+ raise MigrationError.new("Cannot find database: #{ path.sub(/\A\/\//, '') }")
51
+ end
52
+
53
+ # @since 0.4.0
54
+ # @api private
55
+ def dump
56
+ dump_structure
57
+ dump_migrations_data
58
+ end
59
+
60
+ # @since 0.4.0
61
+ # @api private
62
+ def load
63
+ load_structure
64
+ end
65
+
66
+ private
67
+
68
+ # @since 0.4.0
69
+ # @api private
70
+ def path
71
+ root.join(
72
+ @connection.uri.sub(/#{ @connection.adapter_scheme }\:\/\//, '')
73
+ )
74
+ end
75
+
76
+ # @since 0.4.0
77
+ # @api private
78
+ def root
79
+ Lotus::Model.configuration.root
80
+ end
81
+
82
+ # @since 0.4.0
83
+ # @api private
84
+ def memory?
85
+ uri = path.to_s
86
+ uri.match(/sqlite\:\/\z/) ||
87
+ uri.match(/\:memory\:/)
88
+ end
89
+
90
+ # @since 0.4.0
91
+ # @api private
92
+ def dump_structure
93
+ system "sqlite3 #{ escape(path) } .schema > #{ escape(schema) }"
94
+ end
95
+
96
+ # @since 0.4.0
97
+ # @api private
98
+ def load_structure
99
+ system "sqlite3 #{ escape(path) } < #{ escape(schema) }" if schema.exist?
100
+ end
101
+
102
+ # @since 0.4.0
103
+ # @api private
104
+ def dump_migrations_data
105
+ system %(sqlite3 #{ escape(path) } .dump | grep '^INSERT INTO "#{ migrations_table }"' >> #{ escape(schema) })
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -3,6 +3,6 @@ module Lotus
3
3
  # Defines the version
4
4
  #
5
5
  # @since 0.1.0
6
- VERSION = '0.3.2'.freeze
6
+ VERSION = '0.4.0'.freeze
7
7
  end
8
8
  end
@@ -551,6 +551,7 @@ module Lotus
551
551
  end
552
552
  end
553
553
 
554
+ private
554
555
  # Executes the given raw statement on the adapter.
555
556
  #
556
557
  # Please note that it's only supported by some databases,
@@ -595,7 +596,6 @@ module Lotus
595
596
  @adapter.execute(raw)
596
597
  end
597
598
 
598
- private
599
599
  # Fabricates a query and yields the given block to access the low level
600
600
  # APIs exposed by the query itself.
601
601
  #
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
  spec.required_ruby_version = '>= 2.0.0'
21
21
 
22
- spec.add_runtime_dependency 'lotus-utils', '~> 0.4'
22
+ spec.add_runtime_dependency 'lotus-utils', '~> 0.5'
23
23
  spec.add_runtime_dependency 'sequel', '~> 4.9'
24
24
 
25
25
  spec.add_development_dependency 'bundler', '~> 1.6'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lotus-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-05-22 00:00:00.000000000 Z
12
+ date: 2015-06-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: lotus-utils
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '0.4'
20
+ version: '0.5'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '0.4'
27
+ version: '0.5'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: sequel
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,11 @@ files:
122
122
  - lib/lotus/model/mapping/coercer.rb
123
123
  - lib/lotus/model/mapping/coercions.rb
124
124
  - lib/lotus/model/mapping/collection.rb
125
+ - lib/lotus/model/migrator.rb
126
+ - lib/lotus/model/migrator/adapter.rb
127
+ - lib/lotus/model/migrator/mysql_adapter.rb
128
+ - lib/lotus/model/migrator/postgres_adapter.rb
129
+ - lib/lotus/model/migrator/sqlite_adapter.rb
125
130
  - lib/lotus/model/version.rb
126
131
  - lib/lotus/repository.rb
127
132
  - lotus-model.gemspec
@@ -145,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
150
  version: '0'
146
151
  requirements: []
147
152
  rubyforge_project:
148
- rubygems_version: 2.4.5
153
+ rubygems_version: 2.4.8
149
154
  signing_key:
150
155
  specification_version: 4
151
156
  summary: A persistence layer for Lotus