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
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 = %q{A persistence layer for Hanami}
12
- spec.description = %q{A persistence framework with entities, repositories, data mapper and query objects}
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.0.0'
20
+ spec.required_ruby_version = '>= 2.3.0'
21
21
 
22
- spec.add_runtime_dependency 'hanami-utils', '~> 0.7'
23
- spec.add_runtime_dependency 'sequel', '~> 4.9'
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', '~> 10'
30
+ spec.add_development_dependency 'rake', '~> 11'
28
31
  end
data/lib/hanami/entity.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'hanami/utils/kernel'
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
- module Entity
64
- # Inject the public API into the hosting class.
65
- #
66
- # @since 0.1.0
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
- # User = Struct.new(:id, :name) do
79
- # include Hanami::Entity
80
- # end
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
- # (Re)defines getters, setters and initialization for the given attributes.
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
- # @param attrs [Array<Symbol>] a set of arbitrary attribute names
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
- # @since 0.2.0
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
- # @see Hanami::Repository
106
- # @see Hanami::Model::Mapper
79
+ # @param blk [Proc] the block that defines the attributes
107
80
  #
108
- # @example
109
- # require 'hanami/model'
81
+ # @since 0.7.0
110
82
  #
111
- # class User
112
- # include Hanami::Entity
113
- # attributes :name, :age
114
- # end
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
- # Define setter/getter methods for attributes.
89
+ # Assign a schema
154
90
  #
155
- # @param attr [Symbol] an attribute name
91
+ # @param value [Hanami::Entity::Schema] the schema
156
92
  #
157
- # @since 0.3.1
93
+ # @since 0.7.0
158
94
  # @api private
159
- def define_attr_accessor(attr)
160
- attr_accessor(attr)
95
+ def schema=(value)
96
+ return if defined?(@attributes)
97
+ @schema = value
161
98
  end
162
99
 
163
- # Check if attr_reader define the given attribute
164
- #
165
- # @since 0.5.1
100
+ # @since 0.7.0
166
101
  # @api private
167
- def allowed_attribute_name?(name)
168
- !instance_methods.include?(name)
169
- end
170
-
171
- protected
102
+ attr_reader :schema
103
+ end
172
104
 
173
- # @see Class#inherited
174
- def inherited(subclass)
175
- subclass.attributes(*attributes)
176
- super
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
- # Defines a generic, inefficient initializer, in case that the attributes
181
- # weren't explicitly defined with `.attributes=`.
114
+ # Instantiate a new entity
182
115
  #
183
- # @param attributes [Hash] a set of attribute names and values
116
+ # @param attributes [Hash,#to_h,NilClass] data to initialize the entity
184
117
  #
185
- # @raise NoMethodError in case the given attributes are trying to set unknown
186
- # or private methods.
118
+ # @return [Hanami::Entity] the new entity instance
187
119
  #
188
- # @since 0.1.0
120
+ # @raise [TypeError] if the given attributes are invalid
189
121
  #
190
- # @see .attributes
191
- def initialize(attributes = {})
192
- attributes.each do |k, v|
193
- setter = "#{ k }="
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
- # Overrides the equality Ruby operator
128
+ # Entity ID
199
129
  #
200
- # Two entities are considered equal if they are instances of the same class
201
- # and if they have the same #id.
130
+ # @return [Object,NilClass] the ID, if present
202
131
  #
203
- # @since 0.1.0
204
- def ==(other)
205
- self.class == other.class &&
206
- self.id == other.id
132
+ # @since 0.7.0
133
+ def id
134
+ attributes.fetch(:id, nil)
207
135
  end
208
136
 
209
- # Return the hash of attributes
210
- #
211
- # @since 0.2.0
137
+ # Handle dynamic accessors
212
138
  #
213
- # @example
214
- # require 'hanami/model'
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
- # user = User.new(id: 23, name: 'Luca')
221
- # user.to_h # => { :id => 23, :name => "Luca" }
222
- def to_h
223
- Hash[attribute_names.map { |a| [a, read_attribute(a)] }]
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
- # Return the set of attribute names
148
+ # Implement generic equality for entities
227
149
  #
228
- # @since 0.5.1
150
+ # Two entities are equal if they are instances of the same class and they
151
+ # have the same id.
229
152
  #
230
- # @example
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
- # user = User.new(id: 23, name: 'Luca')
238
- # user.attribute_names # #<Set: {:id, :name}>
239
- def attribute_names
240
- self.class.attributes
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
- # Return the contents of the entity as a nicely formatted string.
163
+ # Implement predictable hashing for hash equality
244
164
  #
245
- # Display all attributes of the entity for inspection (even if they are nil)
165
+ # @return [Integer] the object hash
246
166
  #
247
- # @since 0.5.1
248
- #
249
- # @example
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
- alias_method :to_s, :inspect
172
+ # Freeze the entity
173
+ #
174
+ # @since 0.7.0
175
+ def freeze
176
+ attributes.freeze
177
+ super
178
+ end
267
179
 
268
- # Set attributes for entity
180
+ # Serialize entity to a Hash
269
181
  #
270
- # @since 0.2.0
182
+ # @return [Hash] the result of serialization
271
183
  #
272
- # @example
273
- # require 'hanami/model'
274
- # class User
275
- # include Hanami::Entity
276
- # attributes :name
277
- # end
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
- # user = User.new(name: 'Lucca')
280
- # user.update(name: 'Luca')
281
- # user.name # => 'Luca'
282
- def update(attributes={})
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
- # Return the value by attribute name
291
- #
292
- # @since 0.5.1
204
+ # @since 0.1.0
205
+ # @api private
206
+ attr_reader :attributes
207
+
208
+ # @since 0.7.0
293
209
  # @api private
294
- def read_attribute(attr_name)
295
- public_send(attr_name)
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