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
data/hanami-model.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Hanami::Model::VERSION
|
9
9
|
spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
|
10
10
|
spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
11
|
+
spec.summary = 'A persistence layer for Hanami'
|
12
|
+
spec.description = 'A persistence framework with entities and repositories'
|
13
13
|
spec.homepage = 'http://hanamirb.org'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
@@ -17,12 +17,15 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
|
-
spec.required_ruby_version = '>= 2.
|
20
|
+
spec.required_ruby_version = '>= 2.3.0'
|
21
21
|
|
22
|
-
spec.add_runtime_dependency 'hanami-utils',
|
23
|
-
spec.add_runtime_dependency '
|
22
|
+
spec.add_runtime_dependency 'hanami-utils', '~> 0.8'
|
23
|
+
spec.add_runtime_dependency 'rom-sql', '~> 0.9'
|
24
|
+
spec.add_runtime_dependency 'rom-repository', '~> 0.3'
|
25
|
+
spec.add_runtime_dependency 'dry-types', '~> 0.9'
|
26
|
+
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
24
27
|
|
25
28
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
26
29
|
spec.add_development_dependency 'minitest', '~> 5'
|
27
|
-
spec.add_development_dependency 'rake', '~>
|
30
|
+
spec.add_development_dependency 'rake', '~> 11'
|
28
31
|
end
|
data/lib/hanami/entity.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require 'hanami/
|
2
|
-
require 'hanami/utils/attributes'
|
1
|
+
require 'hanami/model/types'
|
3
2
|
|
4
3
|
module Hanami
|
5
4
|
# An object that is defined by its identity.
|
@@ -22,17 +21,8 @@ module Hanami
|
|
22
21
|
#
|
23
22
|
# class Person
|
24
23
|
# include Hanami::Entity
|
25
|
-
# attributes :name, :age
|
26
24
|
# end
|
27
25
|
#
|
28
|
-
# When a class includes `Hanami::Entity` it receives the following interface:
|
29
|
-
#
|
30
|
-
# * #id
|
31
|
-
# * #id=
|
32
|
-
# * #initialize(attributes = {})
|
33
|
-
#
|
34
|
-
# `Hanami::Entity` also provides the `.attributes=` for defining attribute accessors for the given names.
|
35
|
-
#
|
36
26
|
# If we expand the code above in **pure Ruby**, it would be:
|
37
27
|
#
|
38
28
|
# @example Pure Ruby
|
@@ -60,239 +50,165 @@ module Hanami
|
|
60
50
|
# @since 0.1.0
|
61
51
|
#
|
62
52
|
# @see Hanami::Repository
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# @example With Object
|
69
|
-
# require 'hanami/model'
|
70
|
-
#
|
71
|
-
# class User
|
72
|
-
# include Hanami::Entity
|
73
|
-
# end
|
74
|
-
#
|
75
|
-
# @example With Struct
|
76
|
-
# require 'hanami/model'
|
53
|
+
class Entity
|
54
|
+
require 'hanami/entity/schema'
|
55
|
+
|
56
|
+
# Syntactic shortcut to reference types in custom schema DSL
|
77
57
|
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
def self.included(base)
|
82
|
-
base.class_eval do
|
83
|
-
extend ClassMethods
|
84
|
-
attributes :id
|
85
|
-
end
|
58
|
+
# @since 0.7.0
|
59
|
+
module Types
|
60
|
+
include Hanami::Model::Types
|
86
61
|
end
|
87
62
|
|
63
|
+
# Class level interface
|
64
|
+
#
|
65
|
+
# @since 0.7.0
|
66
|
+
# @api private
|
88
67
|
module ClassMethods
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# These attributes can match the database columns, but this isn't a
|
92
|
-
# requirement. The mapper used by the relative repository will translate
|
93
|
-
# these names automatically.
|
94
|
-
#
|
95
|
-
# An entity can work with attributes not configured in the mapper, but
|
96
|
-
# of course they will be ignored when the entity will be persisted.
|
97
|
-
#
|
98
|
-
# Please notice that the required `id` attribute is automatically defined
|
99
|
-
# and can be omitted in the arguments.
|
68
|
+
# Define manual entity schema
|
100
69
|
#
|
101
|
-
#
|
70
|
+
# With a SQL database this setup happens automatically and you SHOULD NOT
|
71
|
+
# use this DSL. You should use only when you want to customize the automatic
|
72
|
+
# setup.
|
102
73
|
#
|
103
|
-
#
|
74
|
+
# If you're working with an entity that isn't "backed" by a SQL table or
|
75
|
+
# with a schema-less database, you may want to manually setup a set of
|
76
|
+
# attributes via this DSL. If you don't do any setup, the entity accepts all
|
77
|
+
# the given attributes.
|
104
78
|
#
|
105
|
-
# @
|
106
|
-
# @see Hanami::Model::Mapper
|
79
|
+
# @param blk [Proc] the block that defines the attributes
|
107
80
|
#
|
108
|
-
# @
|
109
|
-
# require 'hanami/model'
|
81
|
+
# @since 0.7.0
|
110
82
|
#
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
# User.attributes => #<Set: {:id, :name, :age}>
|
116
|
-
#
|
117
|
-
# @example Given params is array of attributes
|
118
|
-
# require 'hanami/model'
|
119
|
-
#
|
120
|
-
# class User
|
121
|
-
# include Hanami::Entity
|
122
|
-
# attributes [:name, :age]
|
123
|
-
# end
|
124
|
-
# User.attributes => #<Set: {:id, :name, :age}>
|
125
|
-
#
|
126
|
-
# @example Extend entity
|
127
|
-
# require 'hanami/model'
|
128
|
-
#
|
129
|
-
# class User
|
130
|
-
# include Hanami::Entity
|
131
|
-
# attributes :name
|
132
|
-
# end
|
133
|
-
#
|
134
|
-
# class DeletedUser < User
|
135
|
-
# include Hanami::Entity
|
136
|
-
# attributes :deleted_at
|
137
|
-
# end
|
138
|
-
#
|
139
|
-
# User.attributes => #<Set: {:id, :name}>
|
140
|
-
# DeletedUser.attributes => #<Set: {:id, :name, :deleted_at}>
|
141
|
-
#
|
142
|
-
def attributes(*attrs)
|
143
|
-
return @attributes ||= Set.new unless attrs.any?
|
144
|
-
|
145
|
-
Hanami::Utils::Kernel.Array(attrs).each do |attr|
|
146
|
-
if allowed_attribute_name?(attr)
|
147
|
-
define_attr_accessor(attr)
|
148
|
-
self.attributes << attr
|
149
|
-
end
|
150
|
-
end
|
83
|
+
# @see Hanami::Entity
|
84
|
+
def attributes(&blk)
|
85
|
+
self.schema = Schema.new(&blk)
|
86
|
+
@attributes = true
|
151
87
|
end
|
152
88
|
|
153
|
-
#
|
89
|
+
# Assign a schema
|
154
90
|
#
|
155
|
-
# @param
|
91
|
+
# @param value [Hanami::Entity::Schema] the schema
|
156
92
|
#
|
157
|
-
# @since 0.
|
93
|
+
# @since 0.7.0
|
158
94
|
# @api private
|
159
|
-
def
|
160
|
-
|
95
|
+
def schema=(value)
|
96
|
+
return if defined?(@attributes)
|
97
|
+
@schema = value
|
161
98
|
end
|
162
99
|
|
163
|
-
#
|
164
|
-
#
|
165
|
-
# @since 0.5.1
|
100
|
+
# @since 0.7.0
|
166
101
|
# @api private
|
167
|
-
|
168
|
-
|
169
|
-
end
|
170
|
-
|
171
|
-
protected
|
102
|
+
attr_reader :schema
|
103
|
+
end
|
172
104
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
105
|
+
# @since 0.7.0
|
106
|
+
# @api private
|
107
|
+
def self.inherited(klass)
|
108
|
+
klass.class_eval do
|
109
|
+
@schema = Schema.new
|
110
|
+
extend ClassMethods
|
177
111
|
end
|
178
112
|
end
|
179
113
|
|
180
|
-
#
|
181
|
-
# weren't explicitly defined with `.attributes=`.
|
114
|
+
# Instantiate a new entity
|
182
115
|
#
|
183
|
-
# @param attributes [Hash]
|
116
|
+
# @param attributes [Hash,#to_h,NilClass] data to initialize the entity
|
184
117
|
#
|
185
|
-
# @
|
186
|
-
# or private methods.
|
118
|
+
# @return [Hanami::Entity] the new entity instance
|
187
119
|
#
|
188
|
-
# @
|
120
|
+
# @raise [TypeError] if the given attributes are invalid
|
189
121
|
#
|
190
|
-
# @
|
191
|
-
def initialize(attributes =
|
192
|
-
attributes
|
193
|
-
|
194
|
-
public_send(setter, v) if respond_to?(setter)
|
195
|
-
end
|
122
|
+
# @since 0.1.0
|
123
|
+
def initialize(attributes = nil)
|
124
|
+
@attributes = self.class.schema[attributes]
|
125
|
+
freeze
|
196
126
|
end
|
197
127
|
|
198
|
-
#
|
128
|
+
# Entity ID
|
199
129
|
#
|
200
|
-
#
|
201
|
-
# and if they have the same #id.
|
130
|
+
# @return [Object,NilClass] the ID, if present
|
202
131
|
#
|
203
|
-
# @since 0.
|
204
|
-
def
|
205
|
-
|
206
|
-
self.id == other.id
|
132
|
+
# @since 0.7.0
|
133
|
+
def id
|
134
|
+
attributes.fetch(:id, nil)
|
207
135
|
end
|
208
136
|
|
209
|
-
#
|
210
|
-
#
|
211
|
-
# @since 0.2.0
|
137
|
+
# Handle dynamic accessors
|
212
138
|
#
|
213
|
-
#
|
214
|
-
#
|
215
|
-
# class User
|
216
|
-
# include Hanami::Entity
|
217
|
-
# attributes :name
|
218
|
-
# end
|
139
|
+
# If internal attributes set has the requested key, it returns the linked
|
140
|
+
# value, otherwise it raises a <tt>NoMethodError</tt>
|
219
141
|
#
|
220
|
-
#
|
221
|
-
|
222
|
-
|
223
|
-
|
142
|
+
# @since 0.7.0
|
143
|
+
def method_missing(m, *)
|
144
|
+
attribute?(m) or super # rubocop:disable Style/AndOr
|
145
|
+
attributes.fetch(m, nil)
|
224
146
|
end
|
225
147
|
|
226
|
-
#
|
148
|
+
# Implement generic equality for entities
|
227
149
|
#
|
228
|
-
#
|
150
|
+
# Two entities are equal if they are instances of the same class and they
|
151
|
+
# have the same id.
|
229
152
|
#
|
230
|
-
# @
|
231
|
-
# require 'hanami/model'
|
232
|
-
# class User
|
233
|
-
# include Hanami::Entity
|
234
|
-
# attributes :name
|
235
|
-
# end
|
153
|
+
# @param other [Object] the object of comparison
|
236
154
|
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
|
240
|
-
|
155
|
+
# @return [FalseClass,TrueClass] the result of the check
|
156
|
+
#
|
157
|
+
# @since 0.1.0
|
158
|
+
def ==(other)
|
159
|
+
self.class == other.class &&
|
160
|
+
id == other.id
|
241
161
|
end
|
242
162
|
|
243
|
-
#
|
163
|
+
# Implement predictable hashing for hash equality
|
244
164
|
#
|
245
|
-
#
|
165
|
+
# @return [Integer] the object hash
|
246
166
|
#
|
247
|
-
# @since 0.
|
248
|
-
|
249
|
-
|
250
|
-
# require 'hanami/model'
|
251
|
-
# class User
|
252
|
-
# include Hanami::Entity
|
253
|
-
# attributes :name, :email
|
254
|
-
# end
|
255
|
-
#
|
256
|
-
# user = User.new(id: 23, name: 'Luca')
|
257
|
-
# user.inspect # #<User:0x007fa7eefe0b58 @id=nil @name="Luca" @email=nil>
|
258
|
-
def inspect
|
259
|
-
attr_list = attribute_names.inject([]) do |res, name|
|
260
|
-
res << "@#{name}=#{read_attribute(name).inspect}"
|
261
|
-
end.join(' ')
|
262
|
-
|
263
|
-
"#<#{self.class.name}:0x00#{(__id__ << 1).to_s(16)} #{attr_list}>"
|
167
|
+
# @since 0.7.0
|
168
|
+
def hash
|
169
|
+
[self.class, id].hash
|
264
170
|
end
|
265
171
|
|
266
|
-
|
172
|
+
# Freeze the entity
|
173
|
+
#
|
174
|
+
# @since 0.7.0
|
175
|
+
def freeze
|
176
|
+
attributes.freeze
|
177
|
+
super
|
178
|
+
end
|
267
179
|
|
268
|
-
#
|
180
|
+
# Serialize entity to a Hash
|
269
181
|
#
|
270
|
-
# @
|
182
|
+
# @return [Hash] the result of serialization
|
271
183
|
#
|
272
|
-
# @
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
#
|
184
|
+
# @since 0.1.0
|
185
|
+
def to_h
|
186
|
+
attributes.deep_dup.to_h
|
187
|
+
end
|
188
|
+
|
189
|
+
# @since 0.7.0
|
190
|
+
alias to_hash to_h
|
191
|
+
|
192
|
+
protected
|
193
|
+
|
194
|
+
# Check if the attribute is allowed to be read
|
278
195
|
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
|
282
|
-
|
283
|
-
attributes.each do |attribute, value|
|
284
|
-
public_send("#{attribute}=", value)
|
285
|
-
end
|
196
|
+
# @since 0.7.0
|
197
|
+
# @api private
|
198
|
+
def attribute?(name)
|
199
|
+
self.class.schema.attribute?(name)
|
286
200
|
end
|
287
201
|
|
288
202
|
private
|
289
203
|
|
290
|
-
#
|
291
|
-
#
|
292
|
-
|
204
|
+
# @since 0.1.0
|
205
|
+
# @api private
|
206
|
+
attr_reader :attributes
|
207
|
+
|
208
|
+
# @since 0.7.0
|
293
209
|
# @api private
|
294
|
-
def
|
295
|
-
|
210
|
+
def respond_to_missing?(name, _include_all)
|
211
|
+
attribute?(name)
|
296
212
|
end
|
297
213
|
end
|
298
214
|
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'hanami/model/types'
|
2
|
+
require 'hanami/utils/hash'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
class Entity
|
6
|
+
# Entity schema is a definition of a set of typed attributes.
|
7
|
+
#
|
8
|
+
# @since 0.7.0
|
9
|
+
# @api private
|
10
|
+
#
|
11
|
+
# @example SQL Automatic Setup
|
12
|
+
# require 'hanami/model'
|
13
|
+
#
|
14
|
+
# class Account
|
15
|
+
# include Hanami::Entity
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# account = Account.new(name: "Acme Inc.")
|
19
|
+
# account.name # => "Hanami"
|
20
|
+
#
|
21
|
+
# account = Account.new(foo: "bar")
|
22
|
+
# account.foo # => NoMethodError
|
23
|
+
#
|
24
|
+
# @example Non-SQL Manual Setup
|
25
|
+
# require 'hanami/model'
|
26
|
+
#
|
27
|
+
# class Account
|
28
|
+
# include Hanami::Entity
|
29
|
+
#
|
30
|
+
# attributes do
|
31
|
+
# attribute :id, Types::Int
|
32
|
+
# attribute :name, Types::String
|
33
|
+
# attribute :codes, Types::Array(Types::Int)
|
34
|
+
# attribute :users, Types::Array(User)
|
35
|
+
# attribute :email, Types::String.constrained(format: /@/)
|
36
|
+
# attribute :created_at, Types::DateTime
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# account = Account.new(name: "Acme Inc.")
|
41
|
+
# account.name # => "Acme Inc."
|
42
|
+
#
|
43
|
+
# account = Account.new(foo: "bar")
|
44
|
+
# account.foo # => NoMethodError
|
45
|
+
#
|
46
|
+
# @example Schemaless Entity
|
47
|
+
# require 'hanami/model'
|
48
|
+
#
|
49
|
+
# class Account
|
50
|
+
# include Hanami::Entity
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# account = Account.new(name: "Acme Inc.")
|
54
|
+
# account.name # => "Acme Inc."
|
55
|
+
#
|
56
|
+
# account = Account.new(foo: "bar")
|
57
|
+
# account.foo # => "bar"
|
58
|
+
class Schema
|
59
|
+
# Schemaless entities logic
|
60
|
+
#
|
61
|
+
# @since 0.7.0
|
62
|
+
# @api private
|
63
|
+
class Schemaless
|
64
|
+
# @since 0.7.0
|
65
|
+
# @api private
|
66
|
+
def initialize
|
67
|
+
freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param attributes [#to_hash] the attributes hash
|
71
|
+
#
|
72
|
+
# @return [Hash]
|
73
|
+
#
|
74
|
+
# @since 0.7.0
|
75
|
+
# @api private
|
76
|
+
def call(attributes)
|
77
|
+
if attributes.nil?
|
78
|
+
{}
|
79
|
+
else
|
80
|
+
attributes.dup
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @since 0.7.0
|
85
|
+
# @api private
|
86
|
+
def attribute?(_name)
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Schema definition
|
92
|
+
#
|
93
|
+
# @since 0.7.0
|
94
|
+
# @api private
|
95
|
+
class Definition
|
96
|
+
# Schema DSL
|
97
|
+
#
|
98
|
+
# @since 0.7.0
|
99
|
+
class Dsl
|
100
|
+
# @since 0.7.0
|
101
|
+
# @api private
|
102
|
+
def self.build(&blk)
|
103
|
+
attributes = new(&blk).to_h
|
104
|
+
[attributes, Hanami::Model::Types::Coercible::Hash.schema(attributes)]
|
105
|
+
end
|
106
|
+
|
107
|
+
# @since 0.7.0
|
108
|
+
# @api private
|
109
|
+
def initialize(&blk)
|
110
|
+
@attributes = {}
|
111
|
+
instance_eval(&blk)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Define an attribute
|
115
|
+
#
|
116
|
+
# @param name [Symbol] the attribute name
|
117
|
+
# @param type [Dry::Types::Definition] the attribute type
|
118
|
+
#
|
119
|
+
# @since 0.7.0
|
120
|
+
def attribute(name, type)
|
121
|
+
@attributes[name] = type
|
122
|
+
end
|
123
|
+
|
124
|
+
# @since 0.7.0
|
125
|
+
# @api private
|
126
|
+
def to_h
|
127
|
+
@attributes
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Instantiate a new DSL instance for an entity
|
132
|
+
#
|
133
|
+
# @param blk [Proc] the block that defines the attributes
|
134
|
+
#
|
135
|
+
# @return [Hanami::Entity::Schema::Dsl] the DSL
|
136
|
+
#
|
137
|
+
# @since 0.7.0
|
138
|
+
# @api private
|
139
|
+
def initialize(&blk)
|
140
|
+
raise LocalJumpError unless block_given?
|
141
|
+
@attributes, @schema = Dsl.build(&blk)
|
142
|
+
@attributes = Hash[@attributes.map { |k, _| [k, true] }]
|
143
|
+
freeze
|
144
|
+
end
|
145
|
+
|
146
|
+
# Process attributes
|
147
|
+
#
|
148
|
+
# @param attributes [#to_hash] the attributes hash
|
149
|
+
#
|
150
|
+
# @raise [TypeError] if the process fails
|
151
|
+
#
|
152
|
+
# @since 0.7.0
|
153
|
+
# @api private
|
154
|
+
def call(attributes)
|
155
|
+
schema.call(attributes)
|
156
|
+
rescue Dry::Types::SchemaError => e
|
157
|
+
raise TypeError.new(e.message)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check if the attribute is known
|
161
|
+
#
|
162
|
+
# @param name [Symbol] the attribute name
|
163
|
+
#
|
164
|
+
# @return [TrueClass,FalseClass] the result of the check
|
165
|
+
#
|
166
|
+
# @since 0.7.0
|
167
|
+
# @api private
|
168
|
+
def attribute?(name)
|
169
|
+
attributes.key?(name)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
# @since 0.7.0
|
175
|
+
# @api private
|
176
|
+
attr_reader :schema
|
177
|
+
|
178
|
+
# @since 0.7.0
|
179
|
+
# @api private
|
180
|
+
attr_reader :attributes
|
181
|
+
end
|
182
|
+
|
183
|
+
# Build a new instance of Schema with the attributes defined by the given block
|
184
|
+
#
|
185
|
+
# @param blk [Proc] the optional block that defines the attributes
|
186
|
+
#
|
187
|
+
# @return [Hanami::Entity::Schema] the schema
|
188
|
+
#
|
189
|
+
# @since 0.7.0
|
190
|
+
# @api private
|
191
|
+
def initialize(&blk)
|
192
|
+
@schema = if block_given?
|
193
|
+
Definition.new(&blk)
|
194
|
+
else
|
195
|
+
Schemaless.new
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Process attributes
|
200
|
+
#
|
201
|
+
# @param attributes [#to_hash] the attributes hash
|
202
|
+
#
|
203
|
+
# @raise [TypeError] if the process fails
|
204
|
+
#
|
205
|
+
# @since 0.7.0
|
206
|
+
# @api private
|
207
|
+
def call(attributes)
|
208
|
+
Utils::Hash.new(
|
209
|
+
schema.call(attributes)
|
210
|
+
).symbolize!
|
211
|
+
end
|
212
|
+
|
213
|
+
# @since 0.7.0
|
214
|
+
# @api private
|
215
|
+
alias [] call
|
216
|
+
|
217
|
+
# Check if the attribute is known
|
218
|
+
#
|
219
|
+
# @param name [Symbol] the attribute name
|
220
|
+
#
|
221
|
+
# @return [TrueClass,FalseClass] the result of the check
|
222
|
+
#
|
223
|
+
# @since 0.7.0
|
224
|
+
# @api private
|
225
|
+
def attribute?(name)
|
226
|
+
schema.attribute?(name)
|
227
|
+
end
|
228
|
+
|
229
|
+
protected
|
230
|
+
|
231
|
+
# @since 0.7.0
|
232
|
+
# @api private
|
233
|
+
attr_reader :schema
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|