hanami-model 0.6.1 → 0.7.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.
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