hanami-model 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +54 -420
  4. data/hanami-model.gemspec +9 -6
  5. data/lib/hanami/entity.rb +107 -191
  6. data/lib/hanami/entity/schema.rb +236 -0
  7. data/lib/hanami/model.rb +52 -138
  8. data/lib/hanami/model/association.rb +37 -0
  9. data/lib/hanami/model/associations/belongs_to.rb +19 -0
  10. data/lib/hanami/model/associations/dsl.rb +29 -0
  11. data/lib/hanami/model/associations/has_many.rb +200 -0
  12. data/lib/hanami/model/configuration.rb +52 -224
  13. data/lib/hanami/model/configurator.rb +62 -0
  14. data/lib/hanami/model/entity_name.rb +35 -0
  15. data/lib/hanami/model/error.rb +37 -24
  16. data/lib/hanami/model/mapping.rb +29 -35
  17. data/lib/hanami/model/migration.rb +31 -0
  18. data/lib/hanami/model/migrator.rb +111 -88
  19. data/lib/hanami/model/migrator/adapter.rb +39 -16
  20. data/lib/hanami/model/migrator/connection.rb +23 -11
  21. data/lib/hanami/model/migrator/mysql_adapter.rb +38 -17
  22. data/lib/hanami/model/migrator/postgres_adapter.rb +20 -19
  23. data/lib/hanami/model/migrator/sqlite_adapter.rb +9 -8
  24. data/lib/hanami/model/plugins.rb +25 -0
  25. data/lib/hanami/model/plugins/mapping.rb +55 -0
  26. data/lib/hanami/model/plugins/schema.rb +55 -0
  27. data/lib/hanami/model/plugins/timestamps.rb +118 -0
  28. data/lib/hanami/model/relation_name.rb +24 -0
  29. data/lib/hanami/model/sql.rb +161 -0
  30. data/lib/hanami/model/sql/console.rb +41 -0
  31. data/lib/hanami/model/sql/consoles/abstract.rb +33 -0
  32. data/lib/hanami/model/sql/consoles/mysql.rb +63 -0
  33. data/lib/hanami/model/sql/consoles/postgresql.rb +68 -0
  34. data/lib/hanami/model/sql/consoles/sqlite.rb +46 -0
  35. data/lib/hanami/model/sql/entity/schema.rb +125 -0
  36. data/lib/hanami/model/sql/types.rb +95 -0
  37. data/lib/hanami/model/sql/types/schema/coercions.rb +198 -0
  38. data/lib/hanami/model/types.rb +99 -0
  39. data/lib/hanami/model/version.rb +1 -1
  40. data/lib/hanami/repository.rb +287 -723
  41. metadata +77 -40
  42. data/EXAMPLE.md +0 -213
  43. data/lib/hanami/entity/dirty_tracking.rb +0 -74
  44. data/lib/hanami/model/adapters/abstract.rb +0 -281
  45. data/lib/hanami/model/adapters/file_system_adapter.rb +0 -288
  46. data/lib/hanami/model/adapters/implementation.rb +0 -111
  47. data/lib/hanami/model/adapters/memory/collection.rb +0 -132
  48. data/lib/hanami/model/adapters/memory/command.rb +0 -113
  49. data/lib/hanami/model/adapters/memory/query.rb +0 -653
  50. data/lib/hanami/model/adapters/memory_adapter.rb +0 -179
  51. data/lib/hanami/model/adapters/null_adapter.rb +0 -24
  52. data/lib/hanami/model/adapters/sql/collection.rb +0 -287
  53. data/lib/hanami/model/adapters/sql/command.rb +0 -88
  54. data/lib/hanami/model/adapters/sql/console.rb +0 -33
  55. data/lib/hanami/model/adapters/sql/consoles/mysql.rb +0 -49
  56. data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +0 -48
  57. data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +0 -26
  58. data/lib/hanami/model/adapters/sql/query.rb +0 -788
  59. data/lib/hanami/model/adapters/sql_adapter.rb +0 -296
  60. data/lib/hanami/model/coercer.rb +0 -74
  61. data/lib/hanami/model/config/adapter.rb +0 -116
  62. data/lib/hanami/model/config/mapper.rb +0 -45
  63. data/lib/hanami/model/mapper.rb +0 -124
  64. data/lib/hanami/model/mapping/attribute.rb +0 -85
  65. data/lib/hanami/model/mapping/coercers.rb +0 -314
  66. data/lib/hanami/model/mapping/collection.rb +0 -490
  67. data/lib/hanami/model/mapping/collection_coercer.rb +0 -79
@@ -1,47 +1,41 @@
1
- require 'hanami/model/mapping/collection'
2
- require 'hanami/model/mapping/collection_coercer'
3
- require 'hanami/model/mapping/coercers'
1
+ require 'transproc'
4
2
 
5
3
  module Hanami
6
4
  module Model
7
- # Mapping internal utilities
5
+ # Mapping
8
6
  #
9
7
  # @since 0.1.0
10
- module Mapping
11
- # Unmapped collection error.
12
- #
13
- # It gets raised when the application tries to access to a non-mapped
14
- # collection.
15
- #
16
- # @since 0.1.0
17
- class UnmappedCollectionError < Hanami::Model::Error
18
- def initialize(name)
19
- super("Cannot find collection: #{ name }")
20
- end
8
+ class Mapping
9
+ def initialize(&blk)
10
+ @attributes = {}
11
+ @r_attributes = {}
12
+ instance_eval(&blk)
13
+ @processor = @attributes.empty? ? ::Hash : Transproc(:rename_keys, @attributes)
21
14
  end
22
15
 
23
- # Invalid entity error.
24
- #
25
- # It gets raised when the application tries to access to a existing
26
- # entity.
27
- #
28
- # @since 0.2.0
29
- class EntityNotFound < Hanami::Model::Error
30
- def initialize(name)
31
- super("Cannot find class for entity: #{ name }")
32
- end
16
+ def model(entity)
33
17
  end
34
18
 
35
- # Invalid repository error.
36
- #
37
- # It gets raised when the application tries to access to a existing
38
- # repository.
39
- #
40
- # @since 0.2.0
41
- class RepositoryNotFound < Hanami::Model::Error
42
- def initialize(name)
43
- super("Cannot find class for repository: #{ name }")
44
- end
19
+ def register_as(name)
20
+ end
21
+
22
+ def attribute(name, options)
23
+ from = options.fetch(:from, name)
24
+
25
+ @attributes[name] = from
26
+ @r_attributes[from] = name
27
+ end
28
+
29
+ def process(input)
30
+ @processor[input]
31
+ end
32
+
33
+ def reverse?
34
+ @r_attributes.any?
35
+ end
36
+
37
+ def translate(attribute)
38
+ @r_attributes.fetch(attribute)
45
39
  end
46
40
  end
47
41
  end
@@ -0,0 +1,31 @@
1
+ module Hanami
2
+ module Model
3
+ # Database migration
4
+ #
5
+ # @since 0.7.0
6
+ # @api private
7
+ class Migration
8
+ # @since 0.7.0
9
+ # @api private
10
+ attr_reader :gateway
11
+
12
+ # @since 0.7.0
13
+ # @api private
14
+ attr_reader :migration
15
+
16
+ # @since 0.7.0
17
+ # @api private
18
+ def initialize(gateway, &block)
19
+ @gateway = gateway
20
+ @migration = gateway.migration(&block)
21
+ freeze
22
+ end
23
+
24
+ # @since 0.7.0
25
+ # @api private
26
+ def run(direction = :up)
27
+ migration.apply(gateway.connection, direction)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,7 +1,5 @@
1
1
  require 'sequel'
2
2
  require 'sequel/extensions/migration'
3
- require 'hanami/model/migrator/connection'
4
- require 'hanami/model/migrator/adapter'
5
3
 
6
4
  module Hanami
7
5
  module Model
@@ -11,51 +9,13 @@ module Hanami
11
9
  class MigrationError < Hanami::Model::Error
12
10
  end
13
11
 
14
- # Define a migration
15
- #
16
- # It must define an up/down strategy to write schema changes (up) and to
17
- # rollback them (down).
18
- #
19
- # We can use <tt>up</tt> and <tt>down</tt> blocks for custom strategies, or
20
- # only one <tt>change</tt> block that automatically implements "down" strategy.
21
- #
22
- # @param blk [Proc] a block that defines up/down or change database migration
23
- #
24
- # @since 0.4.0
25
- #
26
- # @example Use up/down blocks
27
- # Hanami::Model.migration do
28
- # up do
29
- # create_table :books do
30
- # primary_key :id
31
- # column :book, String
32
- # end
33
- # end
34
- #
35
- # down do
36
- # drop_table :books
37
- # end
38
- # end
39
- #
40
- # @example Use change block
41
- # Hanami::Model.migration do
42
- # change do
43
- # create_table :books do
44
- # primary_key :id
45
- # column :book, String
46
- # end
47
- # end
48
- #
49
- # # DOWN strategy is automatically generated
50
- # end
51
- def self.migration(&blk)
52
- Sequel.migration(&blk)
53
- end
54
-
55
12
  # Database schema migrator
56
13
  #
57
14
  # @since 0.4.0
58
- module Migrator
15
+ class Migrator
16
+ require 'hanami/model/migrator/connection'
17
+ require 'hanami/model/migrator/adapter'
18
+
59
19
  # Create database defined by current configuration.
60
20
  #
61
21
  # It's only implemented for the following databases:
@@ -76,12 +36,14 @@ module Hanami
76
36
  #
77
37
  # Hanami::Model.configure do
78
38
  # # ...
79
- # adapter type: :sql, uri: 'postgres://localhost/foo'
39
+ # adapter :sql, 'postgres://localhost/foo'
80
40
  # end
81
41
  #
82
42
  # Hanami::Model::Migrator.create # Creates `foo' database
43
+ #
44
+ # NOTE: Class level interface SHOULD be removed in Hanami 2.0
83
45
  def self.create
84
- adapter(connection).create
46
+ new.create
85
47
  end
86
48
 
87
49
  # Drop database defined by current configuration.
@@ -104,12 +66,14 @@ module Hanami
104
66
  #
105
67
  # Hanami::Model.configure do
106
68
  # # ...
107
- # adapter type: :sql, uri: 'postgres://localhost/foo'
69
+ # adapter :sql, 'postgres://localhost/foo'
108
70
  # end
109
71
  #
110
72
  # Hanami::Model::Migrator.drop # Drops `foo' database
73
+ #
74
+ # NOTE: Class level interface SHOULD be removed in Hanami 2.0
111
75
  def self.drop
112
- adapter(connection).drop
76
+ new.drop
113
77
  end
114
78
 
115
79
  # Migrate database schema
@@ -132,7 +96,7 @@ module Hanami
132
96
  #
133
97
  # Hanami::Model.configure do
134
98
  # # ...
135
- # adapter type: :sql, uri: 'postgres://localhost/foo'
99
+ # adapter :sql, 'postgres://localhost/foo'
136
100
  # migrations 'db/migrations'
137
101
  # end
138
102
  #
@@ -145,7 +109,7 @@ module Hanami
145
109
  #
146
110
  # Hanami::Model.configure do
147
111
  # # ...
148
- # adapter type: :sql, uri: 'postgres://localhost/foo'
112
+ # adapter :sql, 'postgres://localhost/foo'
149
113
  # migrations 'db/migrations'
150
114
  # end
151
115
  #
@@ -154,12 +118,10 @@ module Hanami
154
118
  #
155
119
  # # Migrate to a specifiy version
156
120
  # Hanami::Model::Migrator.migrate(version: "20150610133853")
121
+ #
122
+ # NOTE: Class level interface SHOULD be removed in Hanami 2.0
157
123
  def self.migrate(version: nil)
158
- version = Integer(version) unless version.nil?
159
-
160
- Sequel::Migrator.run(connection, migrations, target: version, allow_missing_migration_files: true) if migrations?
161
- rescue Sequel::Migrator::Error => e
162
- raise MigrationError.new(e.message)
124
+ new.migrate(version: version)
163
125
  end
164
126
 
165
127
  # Migrate, dump schema, delete migrations.
@@ -192,7 +154,7 @@ module Hanami
192
154
  #
193
155
  # Hanami::Model.configure do
194
156
  # # ...
195
- # adapter type: :sql, uri: 'postgres://localhost/foo'
157
+ # adapter :sql, 'postgres://localhost/foo'
196
158
  # migrations 'db/migrations'
197
159
  # schema 'db/schema.sql'
198
160
  # end
@@ -200,10 +162,10 @@ module Hanami
200
162
  # # Reads all files from "db/migrations" and apply and delete them.
201
163
  # # It generates an updated version of "db/schema.sql"
202
164
  # Hanami::Model::Migrator.apply
165
+ #
166
+ # NOTE: Class level interface SHOULD be removed in Hanami 2.0
203
167
  def self.apply
204
- migrate
205
- adapter(connection).dump
206
- delete_migrations
168
+ new.apply
207
169
  end
208
170
 
209
171
  # Prepare database: drop, create, load schema (if any), migrate.
@@ -223,7 +185,7 @@ module Hanami
223
185
  #
224
186
  # Hanami::Model.configure do
225
187
  # # ...
226
- # adapter type: :sql, uri: 'postgres://localhost/foo'
188
+ # adapter :sql, 'postgres://localhost/foo'
227
189
  # migrations 'db/migrations'
228
190
  # end
229
191
  #
@@ -235,18 +197,17 @@ module Hanami
235
197
  #
236
198
  # Hanami::Model.configure do
237
199
  # # ...
238
- # adapter type: :sql, uri: 'postgres://localhost/foo'
200
+ # adapter :sql, 'postgres://localhost/foo'
239
201
  # migrations 'db/migrations'
240
202
  # schema 'db/schema.sql'
241
203
  # end
242
204
  #
243
205
  # Hanami::Model::Migrator.apply # => updates schema dump
244
206
  # Hanami::Model::Migrator.prepare # => creates `foo', load schema and run pending migrations (if any)
207
+ #
208
+ # NOTE: Class level interface SHOULD be removed in Hanami 2.0
245
209
  def self.prepare
246
- drop rescue nil
247
- create
248
- adapter(connection).load
249
- migrate
210
+ new.prepare
250
211
  end
251
212
 
252
213
  # Return current database version timestamp
@@ -262,40 +223,82 @@ module Hanami
262
223
  # # 20150610133853_create_books.rb
263
224
  #
264
225
  # Hanami::Model::Migrator.version # => "20150610133853"
226
+ #
227
+ # NOTE: Class level interface SHOULD be removed in Hanami 2.0
265
228
  def self.version
266
- adapter(connection).version
229
+ new.version
267
230
  end
268
231
 
269
- private
232
+ # Instantiate a new migrator
233
+ #
234
+ # @param configuration [Hanami::Model::Configuration] framework configuration
235
+ #
236
+ # @return [Hanami::Model::Migrator] a new instance
237
+ #
238
+ # @since 0.7.0
239
+ # @api private
240
+ def initialize(configuration: self.class.configuration)
241
+ @configuration = configuration
242
+ @adapter = Adapter.for(configuration)
243
+ end
270
244
 
271
- # Loads an adapter for the given connection
245
+ # @since 0.7.0
246
+ # @api private
272
247
  #
273
- # @since 0.4.0
248
+ # @see Hanami::Model::Migrator.create
249
+ def create
250
+ adapter.create
251
+ end
252
+
253
+ # @since 0.7.0
274
254
  # @api private
275
- def self.adapter(connection)
276
- Adapter.for(connection)
255
+ #
256
+ # @see Hanami::Model::Migrator.drop
257
+ def drop
258
+ adapter.drop
277
259
  end
278
260
 
279
- # Delete all the migrations
261
+ # @since 0.7.0
262
+ # @api private
280
263
  #
281
- # @since 0.4.0
264
+ # @see Hanami::Model::Migrator.migrate
265
+ def migrate(version: nil)
266
+ adapter.migrate(migrations, version) if migrations?
267
+ end
268
+
269
+ # @since 0.7.0
282
270
  # @api private
283
- def self.delete_migrations
284
- migrations.each_child(&:delete)
271
+ #
272
+ # @see Hanami::Model::Migrator.apply
273
+ def apply
274
+ migrate
275
+ adapter.dump
276
+ delete_migrations
285
277
  end
286
278
 
287
- # Database connection
279
+ # @since 0.7.0
280
+ # @api private
288
281
  #
289
- # @since 0.4.0
282
+ # @see Hanami::Model::Migrator.prepare
283
+ def prepare
284
+ drop
285
+ rescue
286
+ ensure
287
+ create
288
+ adapter.load
289
+ migrate
290
+ end
291
+
292
+ # @since 0.7.0
290
293
  # @api private
291
- def self.connection
292
- Sequel.connect(
293
- configuration.adapter.uri
294
- )
295
- rescue Sequel::AdapterNotFound
296
- raise MigrationError.new("Current adapter (#{ configuration.adapter.type }) doesn't support SQL database operations.")
294
+ #
295
+ # @see Hanami::Model::Migrator.version
296
+ def version
297
+ adapter.version
297
298
  end
298
299
 
300
+ private
301
+
299
302
  # Hanami::Model configuration
300
303
  #
301
304
  # @since 0.4.0
@@ -304,20 +307,40 @@ module Hanami
304
307
  Model.configuration
305
308
  end
306
309
 
310
+ # @since 0.7.0
311
+ # @api private
312
+ attr_reader :configuration
313
+
314
+ # @since 0.7.0
315
+ # @api private
316
+ attr_reader :connection
317
+
318
+ # @since 0.7.0
319
+ # @api private
320
+ attr_reader :adapter
321
+
307
322
  # Migrations directory
308
323
  #
309
- # @since 0.4.0
324
+ # @since 0.7.0
310
325
  # @api private
311
- def self.migrations
326
+ def migrations
312
327
  configuration.migrations
313
328
  end
314
329
 
315
330
  # Check if there are migrations
316
331
  #
317
- # @since 0.4.0
332
+ # @since 0.7.0
333
+ # @api private
334
+ def migrations?
335
+ Dir["#{migrations}/*.rb"].any?
336
+ end
337
+
338
+ # Delete all the migrations
339
+ #
340
+ # @since 0.7.0
318
341
  # @api private
319
- def self.migrations?
320
- Dir["#{ migrations }/*.rb"].any?
342
+ def delete_migrations
343
+ migrations.each_child(&:delete)
321
344
  end
322
345
  end
323
346
  end
@@ -3,7 +3,7 @@ require 'shellwords'
3
3
 
4
4
  module Hanami
5
5
  module Model
6
- module Migrator
6
+ class Migrator
7
7
  # Migrator base adapter
8
8
  #
9
9
  # @since 0.4.0
@@ -25,7 +25,9 @@ module Hanami
25
25
  #
26
26
  # @since 0.4.0
27
27
  # @api private
28
- def self.for(connection)
28
+ def self.for(configuration) # rubocop:disable Metrics/MethodLength
29
+ connection = connection_for(configuration)
30
+
29
31
  case connection.database_type
30
32
  when :sqlite
31
33
  require 'hanami/model/migrator/sqlite_adapter'
@@ -38,15 +40,30 @@ module Hanami
38
40
  MySQLAdapter
39
41
  else
40
42
  self
41
- end.new(connection)
43
+ end.new(connection, configuration)
44
+ end
45
+
46
+ class << self
47
+ private
48
+
49
+ # @since 0.7.0
50
+ # @api private
51
+ def connection_for(configuration)
52
+ Sequel.connect(
53
+ configuration.url
54
+ )
55
+ rescue Sequel::AdapterNotFound
56
+ raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
57
+ end
42
58
  end
43
59
 
44
60
  # Initialize an adapter
45
61
  #
46
62
  # @since 0.4.0
47
63
  # @api private
48
- def initialize(connection)
64
+ def initialize(connection, configuration)
49
65
  @connection = Connection.new(connection)
66
+ @schema = configuration.schema
50
67
  end
51
68
 
52
69
  # Create database.
@@ -57,7 +74,7 @@ module Hanami
57
74
  #
58
75
  # @see Hanami::Model::Migrator.create
59
76
  def create
60
- raise MigrationError.new("Current adapter (#{ connection.database_type }) doesn't support create.")
77
+ raise MigrationError.new("Current adapter (#{connection.database_type}) doesn't support create.")
61
78
  end
62
79
 
63
80
  # Drop database.
@@ -68,7 +85,15 @@ module Hanami
68
85
  #
69
86
  # @see Hanami::Model::Migrator.drop
70
87
  def drop
71
- raise MigrationError.new("Current adapter (#{ connection.database_type }) doesn't support drop.")
88
+ raise MigrationError.new("Current adapter (#{connection.database_type}) doesn't support drop.")
89
+ end
90
+
91
+ def migrate(migrations, version)
92
+ version = Integer(version) unless version.nil?
93
+
94
+ Sequel::Migrator.run(connection.raw, migrations, target: version, allow_missing_migration_files: true)
95
+ rescue Sequel::Migrator::Error => e
96
+ raise MigrationError.new(e.message)
72
97
  end
73
98
 
74
99
  # Load database schema.
@@ -79,7 +104,7 @@ module Hanami
79
104
  #
80
105
  # @see Hanami::Model::Migrator.prepare
81
106
  def load
82
- raise MigrationError.new("Current adapter (#{ connection.database_type }) doesn't support load.")
107
+ raise MigrationError.new("Current adapter (#{connection.database_type}) doesn't support load.")
83
108
  end
84
109
 
85
110
  # Database version.
@@ -87,9 +112,10 @@ module Hanami
87
112
  # @since 0.4.0
88
113
  # @api private
89
114
  def version
90
- return unless connection.adapter_connection.tables.include?(MIGRATIONS_TABLE)
115
+ table = connection.table(MIGRATIONS_TABLE)
116
+ return if table.nil?
91
117
 
92
- if record = connection.adapter_connection[MIGRATIONS_TABLE].order(MIGRATIONS_TABLE_VERSION_COLUMN).last
118
+ if record = table.order(MIGRATIONS_TABLE_VERSION_COLUMN).last
93
119
  record.fetch(MIGRATIONS_TABLE_VERSION_COLUMN).scan(/\A[\d]{14}/).first.to_s
94
120
  end
95
121
  end
@@ -100,6 +126,10 @@ module Hanami
100
126
  # @api private
101
127
  attr_reader :connection
102
128
 
129
+ # @since 0.4.0
130
+ # @api private
131
+ attr_reader :schema
132
+
103
133
  # Returns a database connection
104
134
  #
105
135
  # Given a DB connection URI we can connect to a specific database or not, we need this when creating
@@ -110,7 +140,6 @@ module Hanami
110
140
  #
111
141
  # @since 0.5.0
112
142
  # @api private
113
- #
114
143
  def new_connection(global: false)
115
144
  uri = global ? connection.global_uri : connection.uri
116
145
 
@@ -147,12 +176,6 @@ module Hanami
147
176
  escape connection.password
148
177
  end
149
178
 
150
- # @since 0.4.0
151
- # @api private
152
- def schema
153
- Model.configuration.schema
154
- end
155
-
156
179
  # @since 0.4.0
157
180
  # @api private
158
181
  def migrations_table