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,5 +1,4 @@
1
- require 'hanami/model/config/adapter'
2
- require 'hanami/model/config/mapper'
1
+ require 'rom/configuration'
3
2
 
4
3
  module Hanami
5
4
  module Model
@@ -9,265 +8,94 @@ module Hanami
9
8
  # via `Hanami::Model.configure`.
10
9
  #
11
10
  # @since 0.2.0
12
- class Configuration
13
- # Default migrations path
14
- #
15
- # @since 0.4.0
11
+ class Configuration < ROM::Configuration
12
+ # @since 0.7.0
16
13
  # @api private
17
- #
18
- # @see Hanami::Model::Configuration#migrations
19
- DEFAULT_MIGRATIONS_PATH = Pathname.new('db/migrations').freeze
14
+ attr_reader :mappings
20
15
 
21
- # Default schema path
22
- #
23
- # @since 0.4.0
16
+ # @since 0.7.0
24
17
  # @api private
25
- #
26
- # @see Hanami::Model::Configuration#schema
27
- DEFAULT_SCHEMA_PATH = Pathname.new('db/schema.sql').freeze
18
+ attr_reader :entities
28
19
 
29
- # The persistence mapper
30
- #
31
- # @return [Hanami::Model::Mapper]
32
- #
33
20
  # @since 0.2.0
34
- attr_reader :mapper
35
-
36
- # An adapter configuration template
37
- #
38
- # @return [Hanami::Model::Config::Adapter]
39
- #
40
- # @since 0.2.0
41
- attr_reader :adapter_config
42
-
43
- # Initialize a configuration instance
44
- #
45
- # @return [Hanami::Model::Configuration] a new configuration's
46
- # instance
47
- #
48
- # @since 0.2.0
49
- def initialize
50
- reset!
21
+ # @api private
22
+ def initialize(configurator)
23
+ super(configurator.backend, configurator.url)
24
+ @migrations = configurator._migrations
25
+ @schema = configurator._schema
26
+ @mappings = {}
27
+ @entities = {}
51
28
  end
52
29
 
53
- # Reset all the values to the defaults
54
- #
55
- # @return void
30
+ # NOTE: This must be changed when we want to support several adapters at the time
56
31
  #
57
- # @since 0.2.0
58
- def reset!
59
- @adapter = nil
60
- @adapter_config = nil
61
- @mapper = NullMapper.new
62
- @mapper_config = nil
63
- @migrations = DEFAULT_MIGRATIONS_PATH
64
- @schema = DEFAULT_SCHEMA_PATH
32
+ # @since 0.7.0
33
+ # @api private
34
+ def url
35
+ connection.url
65
36
  end
66
37
 
67
- alias_method :unload!, :reset!
68
-
69
- # Load the configuration for the current framework
38
+ # NOTE: This must be changed when we want to support several adapters at the time
70
39
  #
71
- # @return void
72
- #
73
- # @since 0.2.0
74
- def load!
75
- _build_mapper
76
- _build_adapter
77
-
78
- mapper.load!(@adapter)
40
+ # @since 0.7.0
41
+ # @api private
42
+ def connection
43
+ gateway.connection
79
44
  end
80
45
 
81
- # Register adapter
82
- #
83
- # There could only 1 adapter can be registered per application
84
- #
85
- # @overload adapter
86
- # Retrieves the configured adapter
87
- # @return [Hanami::Model::Config::Adapter,NilClass] the adapter, if
88
- # present
89
- #
90
- # @overload adapter
91
- # Register the adapter
92
- # @param @options [Hash] A set of options to register an adapter
93
- # @option options [Symbol] :type The adapter type. Eg. :sql, :memory
94
- # (mandatory)
95
- # @option options [String] :uri The database uri string (mandatory)
96
- #
97
- # @return void
98
- #
99
- # @raise [ArgumentError] if one of the mandatory options is omitted
100
- #
101
- # @see Hanami::Model.configure
102
- # @see Hanami::Model::Config::Adapter
103
- #
104
- # @example Register the adapter
105
- # require 'hanami/model'
46
+ # NOTE: This must be changed when we want to support several adapters at the time
106
47
  #
107
- # Hanami::Model.configure do
108
- # adapter type: :sql, uri: 'sqlite3://localhost/database'
109
- # end
110
- #
111
- # Hanami::Model.configuration.adapter_config
112
- #
113
- # @since 0.2.0
114
- def adapter(options = nil)
115
- if options.nil?
116
- @adapter_config
117
- else
118
- _check_adapter_options!(options)
119
- @adapter_config ||= Hanami::Model::Config::Adapter.new(options)
120
- end
48
+ # @since 0.7.0
49
+ # @api private
50
+ def gateway
51
+ environment.gateways[:default]
121
52
  end
122
53
 
123
- # Set global persistence mapping
124
- #
125
- # @overload mapping(blk)
126
- # Specify a set of mapping in the given block
127
- # @param blk [Proc] the mapping definitions
128
- #
129
- # @overload mapping(path)
130
- # Specify a relative path where to find the mapping file
131
- # @param path [String] the relative path
132
- #
133
- # @return void
134
- #
135
- # @see Hanami::Model.configure
136
- # @see Hanami::Model::Mapper
137
- #
138
- # @example Set global persistence mapper
139
- # require 'hanami/model'
140
- #
141
- # Hanami::Model.configure do
142
- # mapping do
143
- # collection :users do
144
- # entity User
145
- #
146
- # attribute :id, Integer
147
- # attribute :name, String
148
- # end
149
- # end
150
- # end
54
+ # Root directory
151
55
  #
152
- # @since 0.2.0
153
- def mapping(path=nil, &blk)
154
- @mapper_config = Hanami::Model::Config::Mapper.new(path, &blk)
56
+ # @since 0.4.0
57
+ # @api private
58
+ def root
59
+ Hanami.respond_to?(:root) ? Hanami.root : Pathname.pwd
155
60
  end
156
61
 
157
62
  # Migrations directory
158
63
  #
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
64
  # @since 0.4.0
171
- #
172
- # @see Hanami::Model::Migrations::DEFAULT_MIGRATIONS_PATH
173
- #
174
- # @example Set Custom Path
175
- # require 'hanami/model'
176
- #
177
- # Hanami::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
65
+ def migrations
66
+ (@migrations.nil? ? root : root.join(@migrations)).realpath
187
67
  end
188
68
 
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
69
+ # Path for schema dump file
200
70
  #
201
71
  # @since 0.4.0
202
- #
203
- # @see Hanami::Model::Migrations::DEFAULT_SCHEMA_PATH
204
- #
205
- # @example Set Custom Path
206
- # require 'hanami/model'
207
- #
208
- # Hanami::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
72
+ def schema
73
+ @schema.nil? ? root : root.join(@schema)
218
74
  end
219
75
 
220
- # Root directory
221
- #
222
- # @since 0.4.0
76
+ # @since 0.7.0
223
77
  # @api private
224
- def root
225
- Hanami.respond_to?(:root) ? Hanami.root : Pathname.pwd
78
+ def define_mappings(root, &blk)
79
+ @mappings[root] = Mapping.new(&blk)
226
80
  end
227
81
 
228
- # Duplicate by copying the settings in a new instance.
229
- #
230
- # @return [Hanami::Model::Configuration] a copy of the configuration
231
- #
232
- # @since 0.2.0
82
+ # @since 0.7.0
233
83
  # @api private
234
- def duplicate
235
- Configuration.new.tap do |c|
236
- c.instance_variable_set(:@adapter_config, @adapter_config)
237
- c.instance_variable_set(:@mapper, @mapper)
238
- end
84
+ def register_entity(plural, singular, klass)
85
+ @entities[plural] = klass
86
+ @entities[singular] = klass
239
87
  end
240
88
 
241
- private
242
-
243
- # Instantiate mapper from mapping block
244
- #
245
- # @see Hanami::Model::Configuration#mapping
246
- #
89
+ # @since 0.7.0
247
90
  # @api private
248
- # @since 0.2.0
249
- def _build_mapper
250
- @mapper = Hanami::Model::Mapper.new(&@mapper_config) if @mapper_config
251
- end
91
+ def define_entities_mappings(container, repositories)
92
+ return unless defined?(Sql::Entity::Schema)
252
93
 
253
- # @api private
254
- # @since 0.1.0
255
- def _build_adapter
256
- @adapter = adapter_config.build(mapper)
257
- end
94
+ repositories.each do |r|
95
+ relation = r.relation
96
+ entity = r.entity
258
97
 
259
- # @api private
260
- # @since 0.2.0
261
- #
262
- # NOTE Drop this manual check when Ruby 2.0 will not be supported anymore.
263
- # Use keyword arguments instead.
264
- def _check_adapter_options!(options)
265
- # TODO Maybe this is a candidate for Hanami::Utils::Options
266
- # We already have two similar cases:
267
- # 1. Hanami::Router :only/:except for RESTful resources
268
- # 2. Hanami::Validations.validate_options!
269
- [:type, :uri].each do |keyword|
270
- raise ArgumentError.new("missing keyword: #{keyword}") if !options.keys.include?(keyword)
98
+ entity.schema = Sql::Entity::Schema.new(entities, container.relations[relation], mappings.fetch(relation))
271
99
  end
272
100
  end
273
101
  end
@@ -0,0 +1,62 @@
1
+ module Hanami
2
+ module Model
3
+ # Configuration DSL
4
+ #
5
+ # @since 0.7.0
6
+ # @api private
7
+ class Configurator
8
+ # @since 0.7.0
9
+ # @api private
10
+ attr_reader :backend
11
+
12
+ # @since 0.7.0
13
+ # @api private
14
+ attr_reader :url
15
+
16
+ # @since 0.7.0
17
+ # @api private
18
+ attr_reader :directory
19
+
20
+ # @since 0.7.0
21
+ # @api private
22
+ attr_reader :_migrations
23
+
24
+ # @since 0.7.0
25
+ # @api private
26
+ attr_reader :_schema
27
+
28
+ # @since 0.7.0
29
+ # @api private
30
+ def self.build(&block)
31
+ new.tap { |config| config.instance_eval(&block) }
32
+ end
33
+
34
+ private
35
+
36
+ # @since 0.7.0
37
+ # @api private
38
+ def adapter(backend, url)
39
+ @backend = backend
40
+ @url = url
41
+ end
42
+
43
+ # @since 0.7.0
44
+ # @api private
45
+ def path(path)
46
+ @directory = path
47
+ end
48
+
49
+ # @since 0.7.0
50
+ # @api private
51
+ def migrations(path)
52
+ @_migrations = path
53
+ end
54
+
55
+ # @since 0.7.0
56
+ # @api private
57
+ def schema(path)
58
+ @_schema = path
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ module Hanami
2
+ module Model
3
+ # Conventional name for entities.
4
+ #
5
+ # Given a repository named <tt>SourceFileRepository</tt>, the associated
6
+ # entity will be <tt>SourceFile</tt>.
7
+ #
8
+ # @since 0.7.0
9
+ # @api private
10
+ class EntityName
11
+ # @since 0.7.0
12
+ # @api private
13
+ SUFFIX = /Repository\z/
14
+
15
+ # @param name [Class,String] the class or its name
16
+ # @return [String] the entity name
17
+ #
18
+ # @since 0.7.0
19
+ # @api private
20
+ def initialize(name)
21
+ @name = name.sub(SUFFIX, '')
22
+ end
23
+
24
+ # @since 0.7.0
25
+ # @api private
26
+ def underscore
27
+ Utils::String.new(@name).underscore.to_sym
28
+ end
29
+
30
+ def to_s
31
+ @name
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,41 +1,54 @@
1
+ require 'concurrent'
2
+
1
3
  module Hanami
2
4
  module Model
3
-
4
5
  # Default Error class
5
6
  #
6
7
  # @since 0.5.1
7
- Error = Class.new(::StandardError)
8
+ class Error < ::StandardError
9
+ # @api private
10
+ # @since 0.7.0
11
+ @__mapping__ = Concurrent::Map.new # rubocop:disable Style/VariableNumber
8
12
 
9
- # Error for non persisted entity
10
- # It's raised when we try to update or delete a non persisted entity.
11
- #
12
- # @since 0.1.0
13
- #
14
- # @see Hanami::Repository.update
15
- NonPersistedEntityError = Class.new(Error)
13
+ # @api private
14
+ # @since 0.7.0
15
+ def self.for(exception)
16
+ mapping.fetch(exception.class, self).new(exception)
17
+ end
16
18
 
17
- # Error for invalid mapper configuration
18
- # It's raised when mapping is not configured correctly
19
- #
20
- # @since 0.2.0
19
+ # @api private
20
+ # @since 0.7.0
21
+ def self.register(external, internal)
22
+ mapping.put_if_absent(external, internal)
23
+ end
24
+
25
+ # @api private
26
+ # @since 0.7.0
27
+ def self.mapping
28
+ @__mapping__
29
+ end
30
+ end
31
+
32
+ # Generic database error
21
33
  #
22
- # @see Hanami::Configuration#mapping
23
- InvalidMappingError = Class.new(Error)
34
+ # @since 0.7.0
35
+ class DatabaseError < Error
36
+ end
24
37
 
25
38
  # Error for invalid raw command syntax
26
39
  #
27
40
  # @since 0.5.0
28
41
  class InvalidCommandError < Error
29
- def initialize(message = "Invalid command")
42
+ def initialize(message = 'Invalid command')
30
43
  super
31
44
  end
32
45
  end
33
46
 
34
- # Error for invalid raw query syntax
47
+ # Error for Constraint Violation
35
48
  #
36
- # @since 0.3.1
37
- class InvalidQueryError < Error
38
- def initialize(message = "Invalid query")
49
+ # @since 0.7.0
50
+ class ConstraintViolationError < Error
51
+ def initialize(message = 'Constraint has been violated')
39
52
  super
40
53
  end
41
54
  end
@@ -44,7 +57,7 @@ module Hanami
44
57
  #
45
58
  # @since 0.6.1
46
59
  class UniqueConstraintViolationError < Error
47
- def initialize(message = "Unique constraint has been violated")
60
+ def initialize(message = 'Unique constraint has been violated')
48
61
  super
49
62
  end
50
63
  end
@@ -53,7 +66,7 @@ module Hanami
53
66
  #
54
67
  # @since 0.6.1
55
68
  class ForeignKeyConstraintViolationError < Error
56
- def initialize(message = "Foreign key constraint has been violated")
69
+ def initialize(message = 'Foreign key constraint has been violated')
57
70
  super
58
71
  end
59
72
  end
@@ -62,7 +75,7 @@ module Hanami
62
75
  #
63
76
  # @since 0.6.1
64
77
  class NotNullConstraintViolationError < Error
65
- def initialize(message = "NOT NULL constraint has been violated")
78
+ def initialize(message = 'NOT NULL constraint has been violated')
66
79
  super
67
80
  end
68
81
  end
@@ -71,7 +84,7 @@ module Hanami
71
84
  #
72
85
  # @since 0.6.1
73
86
  class CheckConstraintViolationError < Error
74
- def initialize(message = "Check constraint has been violated")
87
+ def initialize(message = 'Check constraint has been violated')
75
88
  super
76
89
  end
77
90
  end