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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/README.md +54 -420
- data/hanami-model.gemspec +9 -6
- data/lib/hanami/entity.rb +107 -191
- data/lib/hanami/entity/schema.rb +236 -0
- data/lib/hanami/model.rb +52 -138
- data/lib/hanami/model/association.rb +37 -0
- data/lib/hanami/model/associations/belongs_to.rb +19 -0
- data/lib/hanami/model/associations/dsl.rb +29 -0
- data/lib/hanami/model/associations/has_many.rb +200 -0
- data/lib/hanami/model/configuration.rb +52 -224
- data/lib/hanami/model/configurator.rb +62 -0
- data/lib/hanami/model/entity_name.rb +35 -0
- data/lib/hanami/model/error.rb +37 -24
- data/lib/hanami/model/mapping.rb +29 -35
- data/lib/hanami/model/migration.rb +31 -0
- data/lib/hanami/model/migrator.rb +111 -88
- data/lib/hanami/model/migrator/adapter.rb +39 -16
- data/lib/hanami/model/migrator/connection.rb +23 -11
- data/lib/hanami/model/migrator/mysql_adapter.rb +38 -17
- data/lib/hanami/model/migrator/postgres_adapter.rb +20 -19
- data/lib/hanami/model/migrator/sqlite_adapter.rb +9 -8
- data/lib/hanami/model/plugins.rb +25 -0
- data/lib/hanami/model/plugins/mapping.rb +55 -0
- data/lib/hanami/model/plugins/schema.rb +55 -0
- data/lib/hanami/model/plugins/timestamps.rb +118 -0
- data/lib/hanami/model/relation_name.rb +24 -0
- data/lib/hanami/model/sql.rb +161 -0
- data/lib/hanami/model/sql/console.rb +41 -0
- data/lib/hanami/model/sql/consoles/abstract.rb +33 -0
- data/lib/hanami/model/sql/consoles/mysql.rb +63 -0
- data/lib/hanami/model/sql/consoles/postgresql.rb +68 -0
- data/lib/hanami/model/sql/consoles/sqlite.rb +46 -0
- data/lib/hanami/model/sql/entity/schema.rb +125 -0
- data/lib/hanami/model/sql/types.rb +95 -0
- data/lib/hanami/model/sql/types/schema/coercions.rb +198 -0
- data/lib/hanami/model/types.rb +99 -0
- data/lib/hanami/model/version.rb +1 -1
- data/lib/hanami/repository.rb +287 -723
- metadata +77 -40
- data/EXAMPLE.md +0 -213
- data/lib/hanami/entity/dirty_tracking.rb +0 -74
- data/lib/hanami/model/adapters/abstract.rb +0 -281
- data/lib/hanami/model/adapters/file_system_adapter.rb +0 -288
- data/lib/hanami/model/adapters/implementation.rb +0 -111
- data/lib/hanami/model/adapters/memory/collection.rb +0 -132
- data/lib/hanami/model/adapters/memory/command.rb +0 -113
- data/lib/hanami/model/adapters/memory/query.rb +0 -653
- data/lib/hanami/model/adapters/memory_adapter.rb +0 -179
- data/lib/hanami/model/adapters/null_adapter.rb +0 -24
- data/lib/hanami/model/adapters/sql/collection.rb +0 -287
- data/lib/hanami/model/adapters/sql/command.rb +0 -88
- data/lib/hanami/model/adapters/sql/console.rb +0 -33
- data/lib/hanami/model/adapters/sql/consoles/mysql.rb +0 -49
- data/lib/hanami/model/adapters/sql/consoles/postgresql.rb +0 -48
- data/lib/hanami/model/adapters/sql/consoles/sqlite.rb +0 -26
- data/lib/hanami/model/adapters/sql/query.rb +0 -788
- data/lib/hanami/model/adapters/sql_adapter.rb +0 -296
- data/lib/hanami/model/coercer.rb +0 -74
- data/lib/hanami/model/config/adapter.rb +0 -116
- data/lib/hanami/model/config/mapper.rb +0 -45
- data/lib/hanami/model/mapper.rb +0 -124
- data/lib/hanami/model/mapping/attribute.rb +0 -85
- data/lib/hanami/model/mapping/coercers.rb +0 -314
- data/lib/hanami/model/mapping/collection.rb +0 -490
- data/lib/hanami/model/mapping/collection_coercer.rb +0 -79
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
#
|
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.
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
-
# @
|
72
|
-
#
|
73
|
-
|
74
|
-
|
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
|
-
#
|
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
|
-
#
|
108
|
-
#
|
109
|
-
|
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
|
-
#
|
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.
|
153
|
-
|
154
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
221
|
-
#
|
222
|
-
# @since 0.4.0
|
76
|
+
# @since 0.7.0
|
223
77
|
# @api private
|
224
|
-
def root
|
225
|
-
|
78
|
+
def define_mappings(root, &blk)
|
79
|
+
@mappings[root] = Mapping.new(&blk)
|
226
80
|
end
|
227
81
|
|
228
|
-
#
|
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
|
235
|
-
|
236
|
-
|
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
|
-
|
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
|
-
|
249
|
-
|
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
|
-
|
254
|
-
|
255
|
-
|
256
|
-
@adapter = adapter_config.build(mapper)
|
257
|
-
end
|
94
|
+
repositories.each do |r|
|
95
|
+
relation = r.relation
|
96
|
+
entity = r.entity
|
258
97
|
|
259
|
-
|
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
|
data/lib/hanami/model/error.rb
CHANGED
@@ -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
|
8
|
+
class Error < ::StandardError
|
9
|
+
# @api private
|
10
|
+
# @since 0.7.0
|
11
|
+
@__mapping__ = Concurrent::Map.new # rubocop:disable Style/VariableNumber
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
# @
|
23
|
-
|
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 =
|
42
|
+
def initialize(message = 'Invalid command')
|
30
43
|
super
|
31
44
|
end
|
32
45
|
end
|
33
46
|
|
34
|
-
# Error for
|
47
|
+
# Error for Constraint Violation
|
35
48
|
#
|
36
|
-
# @since 0.
|
37
|
-
class
|
38
|
-
def initialize(message =
|
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 =
|
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 =
|
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 =
|
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 =
|
87
|
+
def initialize(message = 'Check constraint has been violated')
|
75
88
|
super
|
76
89
|
end
|
77
90
|
end
|