lotus-model 0.3.2 → 0.4.0

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