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,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