praxis-blueprints 3.0 → 3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +35 -0
- data/.travis.yml +10 -5
- data/CHANGELOG.md +27 -0
- data/Gemfile +1 -0
- data/Guardfile +13 -7
- data/README.md +8 -3
- data/Rakefile +4 -3
- data/lib/praxis-blueprints.rb +2 -1
- data/lib/praxis-blueprints/blueprint.rb +96 -87
- data/lib/praxis-blueprints/collection_view.rb +15 -10
- data/lib/praxis-blueprints/config_hash.rb +15 -12
- data/lib/praxis-blueprints/field_expander.rb +60 -48
- data/lib/praxis-blueprints/finalizable.rb +4 -8
- data/lib/praxis-blueprints/renderer.rb +50 -19
- data/lib/praxis-blueprints/version.rb +2 -1
- data/lib/praxis-blueprints/view.rb +22 -29
- data/praxis-blueprints.gemspec +37 -24
- data/spec/praxis-blueprints/blueprint_spec.rb +84 -58
- data/spec/praxis-blueprints/collection_view_spec.rb +16 -7
- data/spec/praxis-blueprints/config_hash_spec.rb +64 -0
- data/spec/praxis-blueprints/field_expander_spec.rb +46 -38
- data/spec/praxis-blueprints/renderer_spec.rb +128 -40
- data/spec/praxis-blueprints/view_spec.rb +24 -10
- data/spec/spec_helper.rb +14 -14
- data/spec/support/spec_blueprints.rb +32 -8
- metadata +112 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d81b3ad8d99f25a09e875be8cde132edfda0825
|
4
|
+
data.tar.gz: 06ac06171a9662bdeaec163cb1919d9aac030c55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37883fd914dc624b7771c2c54150f68361705ce87446abc1b60013eb36ac90775d1eeb71edb3e11a05e6226deedfbbe15b4d68af89b5ff8cad992004539963aa
|
7
|
+
data.tar.gz: 68504da7c58e07ea3f2950ec36638918fd0643954fff1ac70c27d9c2a210b79db68ee9ece1b736645520a22d9a45655f3ec86815b552572e00b906163bef2535
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
Style/Documentation:
|
4
|
+
Enabled: false
|
5
|
+
Metrics/MethodLength:
|
6
|
+
Enabled: false
|
7
|
+
Metrics/ClassLength:
|
8
|
+
Enabled: false
|
9
|
+
Metrics/LineLength:
|
10
|
+
Max: 200
|
11
|
+
Style/RedundantSelf:
|
12
|
+
Enabled: false
|
13
|
+
Style/ClassAndModuleChildren:
|
14
|
+
Enabled: false
|
15
|
+
Lint/Debugger:
|
16
|
+
Enabled: false
|
17
|
+
Metrics/AbcSize:
|
18
|
+
Enabled: false
|
19
|
+
Style/CaseEquality:
|
20
|
+
Enabled: false
|
21
|
+
Lint/UnusedMethodArgument:
|
22
|
+
AllowUnusedKeywordArguments: true
|
23
|
+
Style/FileName:
|
24
|
+
Exclude:
|
25
|
+
- 'lib/praxis-blueprints.rb'
|
26
|
+
Style/ClassVars:
|
27
|
+
Exclude:
|
28
|
+
- 'lib/praxis-blueprints/blueprint.rb'
|
29
|
+
|
30
|
+
# Offense count: 5
|
31
|
+
Metrics/CyclomaticComplexity:
|
32
|
+
Max: 8
|
33
|
+
# Offense count: 5
|
34
|
+
Metrics/PerceivedComplexity:
|
35
|
+
Max: 12
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,33 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 3.4
|
6
|
+
|
7
|
+
* Upgrade to latest Attributor gem (which has JSON-schema support), and add json-schema methods for blueprints.
|
8
|
+
|
9
|
+
## 3.3
|
10
|
+
|
11
|
+
* Include Attributor::Dumpable in Blueprint, so it renders (semi) correctly if
|
12
|
+
rendered with just `true` specified for fields.
|
13
|
+
* Fix bug rendering subobjects with nil values (manifested when `include_nil: true` there’s an explicit subsection of fields)
|
14
|
+
|
15
|
+
## 3.2
|
16
|
+
|
17
|
+
* Ensure we call `object.dump` in Renderer when fully dumping an instance (or array of instances) that have the Attributor::Dumpable module (i.e., when no subfields were selected)
|
18
|
+
* In other words, attributor types (custom or not) will need to include the Attributor::Dumpable module and properly implement the dump instance method to produce the right output with native ruby objects.
|
19
|
+
* Ensure `Blueprint` validates advanced requirements.
|
20
|
+
|
21
|
+
## 3.1
|
22
|
+
|
23
|
+
* Reworked FieldExpander's behavior with circular references.
|
24
|
+
* Removed `Praxis::FieldExpander::CircularExpansionError`
|
25
|
+
* `FieldExpander#expand` now returns self-referential hashes for circular
|
26
|
+
expansions
|
27
|
+
* `Renderer#render` catches stack overflows and returns a
|
28
|
+
`Praxis::Rendering::CircularRenderingError`, which includes a portions
|
29
|
+
from the start and end of the context at the time of the exception.
|
30
|
+
* Report `anonymous` in Blueprints following `Attributor` semantics.
|
31
|
+
|
5
32
|
## 3.0
|
6
33
|
|
7
34
|
* Added `FieldExpander`, a new class that recursively expands a tree of
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# Config file for Guard
|
2
3
|
# More info at https://github.com/guard/guard#readme
|
4
|
+
group :red_green_refactor, halt_on_fail: true do
|
5
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
6
|
+
watch(%r{^spec/.+_spec\.rb$})
|
7
|
+
watch(%r{^lib/praxis-blueprints/(.+)\.rb$}) { |m| "spec/praxis-blueprints/#{m[1]}_spec.rb" }
|
8
|
+
watch('spec/*.rb') { 'spec' }
|
9
|
+
watch('lib/praxis-blueprints.rb') { 'spec' }
|
10
|
+
watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
|
11
|
+
end
|
3
12
|
|
4
|
-
guard :
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
watch('lib/praxis-blueprints.rb') { 'spec' }
|
9
|
-
watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
|
13
|
+
guard :rubocop, cli: '--auto-correct --display-cop-names' do
|
14
|
+
watch(/.+\.rb$/)
|
15
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
16
|
+
end
|
10
17
|
end
|
11
|
-
|
data/README.md
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
# Praxis Blueprints [![TravisCI][travis-img-url]][travis-ci-url]
|
1
|
+
# Praxis Blueprints [![TravisCI][travis-img-url]][travis-ci-url] [![Coverage Status][coveralls-img-url]][coveralls-url]
|
2
2
|
|
3
|
-
[
|
4
|
-
[travis-ci-url]:https://travis-ci.org/rightscale/praxis-blueprints
|
3
|
+
[//]: # ( COMMENTED OUT UNTIL GEMNASIUM CAN SEE THE REPOS: [![Dependency Status][gemnasium-img-url]][gemnasium-url])
|
5
4
|
|
5
|
+
[travis-img-url]:https://travis-ci.org/praxis/praxis-blueprints.svg?branch=master
|
6
|
+
[travis-ci-url]:https://travis-ci.org/praxis/praxis-blueprints
|
7
|
+
[coveralls-img-url]:https://coveralls.io/repos/github/praxis/praxis-blueprints/badge.svg?branch=master
|
8
|
+
[coveralls-url]:https://coveralls.io/github/praxis/praxis-blueprints?branch=master
|
9
|
+
[gemnasium-img-url]:https://gemnasium.com/rightscale/praxis-blueprints.svg
|
10
|
+
[gemnasium-url]:https://gemnasium.com/rightscale/praxis-blueprints
|
6
11
|
|
7
12
|
Praxis Blueprints is a library that allows for defining a reusable class structures that has a set of typed attributes and a set of views with which to render them. Instantiations of Blueprints resemble ruby Structs which respond to methods of the attribute names. Rendering is format-agnostic in that
|
8
13
|
it results in a structured hash instead of an encoded string. Blueprints can automatically generate object structures that follow the attribute definitions.
|
data/Rakefile
CHANGED
@@ -1,17 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'bundler/setup'
|
2
3
|
|
3
|
-
require
|
4
|
+
require 'bundler/gem_tasks'
|
4
5
|
|
5
6
|
require 'rspec/core'
|
6
7
|
require 'rspec/core/rake_task'
|
7
8
|
require 'bundler/gem_tasks'
|
8
9
|
|
9
|
-
desc
|
10
|
+
desc 'Run RSpec code examples with simplecov'
|
10
11
|
RSpec::Core::RakeTask.new do |spec|
|
11
12
|
spec.pattern = FileList['spec/**/*_spec.rb']
|
12
13
|
end
|
13
14
|
|
14
|
-
task :
|
15
|
+
task default: :spec
|
15
16
|
|
16
17
|
require 'yard'
|
17
18
|
YARD::Rake::YardocTask.new
|
data/lib/praxis-blueprints.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'json'
|
2
3
|
require 'yaml'
|
3
4
|
require 'logger'
|
4
5
|
|
5
6
|
require 'attributor'
|
6
7
|
|
7
|
-
require
|
8
|
+
require 'praxis-blueprints/version'
|
8
9
|
|
9
10
|
require 'praxis-blueprints/finalizable'
|
10
11
|
require 'praxis-blueprints/config_hash'
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'ostruct'
|
2
3
|
|
3
4
|
# Blueprint ==
|
@@ -7,6 +8,8 @@ require 'ostruct'
|
|
7
8
|
module Praxis
|
8
9
|
class Blueprint
|
9
10
|
include Attributor::Type
|
11
|
+
include Attributor::Dumpable
|
12
|
+
|
10
13
|
extend Finalizable
|
11
14
|
|
12
15
|
if RUBY_ENGINE =~ /^jruby/
|
@@ -18,7 +21,6 @@ module Praxis
|
|
18
21
|
|
19
22
|
@@caching_enabled = false
|
20
23
|
|
21
|
-
|
22
24
|
attr_reader :validating
|
23
25
|
attr_accessor :object
|
24
26
|
attr_accessor :decorators
|
@@ -34,20 +36,20 @@ module Praxis
|
|
34
36
|
super
|
35
37
|
|
36
38
|
klass.instance_eval do
|
37
|
-
@views =
|
38
|
-
@options =
|
39
|
+
@views = {}
|
40
|
+
@options = {}
|
39
41
|
@domain_model = Object
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
43
45
|
# Override default new behavior to support memoized creation through an IdentityMap
|
44
|
-
def self.new(object, decorators=nil)
|
46
|
+
def self.new(object, decorators = nil)
|
45
47
|
if @@caching_enabled && decorators.nil?
|
46
48
|
cache = if object.respond_to?(:identity_map) && object.identity_map
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
object.identity_map.blueprint_cache[self]
|
50
|
+
else
|
51
|
+
self.cache
|
52
|
+
end
|
51
53
|
|
52
54
|
return cache[object] ||= begin
|
53
55
|
blueprint = self.allocate
|
@@ -65,14 +67,14 @@ module Praxis
|
|
65
67
|
'hash'
|
66
68
|
end
|
67
69
|
|
68
|
-
def self.describe(shallow=false,example: nil, **opts)
|
70
|
+
def self.describe(shallow = false, example: nil, **opts)
|
69
71
|
type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
|
70
72
|
|
71
|
-
if example
|
72
|
-
example = example.object
|
73
|
-
end
|
73
|
+
example = example.object if example
|
74
74
|
|
75
|
-
description = self.attribute.type.describe(shallow,example: example, **opts).merge!(id: self.id, name: type_name)
|
75
|
+
description = self.attribute.type.describe(shallow, example: example, **opts).merge!(id: self.id, name: type_name)
|
76
|
+
description.delete :anonymous # discard the Struct's view of anonymity, and use the Blueprint's one
|
77
|
+
description[:anonymous] = @_anonymous unless @_anonymous.nil?
|
76
78
|
|
77
79
|
unless shallow
|
78
80
|
description[:views] = self.views.each_with_object({}) do |(view_name, view), hash|
|
@@ -83,37 +85,30 @@ module Praxis
|
|
83
85
|
description
|
84
86
|
end
|
85
87
|
|
86
|
-
|
87
|
-
def self.attributes(opts={}, &block)
|
88
|
+
def self.attributes(opts = {}, &block)
|
88
89
|
if block_given?
|
89
|
-
if self.const_defined?(:Struct, false)
|
90
|
-
raise "Redefining Blueprint attributes is not currently supported"
|
91
|
-
else
|
90
|
+
raise 'Redefining Blueprint attributes is not currently supported' if self.const_defined?(:Struct, false)
|
92
91
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
@options.merge!(opts)
|
102
|
-
@block = block
|
92
|
+
if opts.key?(:reference) && opts[:reference] != self.reference
|
93
|
+
raise "Reference mismatch in #{self.inspect}. Given :reference option #{opts[:reference].inspect}, while using #{self.reference.inspect}"
|
94
|
+
elsif self.reference
|
95
|
+
opts[:reference] = self.reference # pass the reference Class down
|
96
|
+
else
|
97
|
+
opts[:reference] = self
|
103
98
|
end
|
104
99
|
|
100
|
+
@options.merge!(opts)
|
101
|
+
@block = block
|
102
|
+
|
105
103
|
return @attribute
|
106
104
|
end
|
107
105
|
|
108
|
-
unless @attribute
|
109
|
-
raise "@attribute not defined yet for #{self.name}"
|
110
|
-
end
|
106
|
+
raise "@attribute not defined yet for #{self.name}" unless @attribute
|
111
107
|
|
112
108
|
@attribute.attributes
|
113
109
|
end
|
114
110
|
|
115
|
-
|
116
|
-
def self.domain_model(klass=nil)
|
111
|
+
def self.domain_model(klass = nil)
|
117
112
|
return @domain_model if klass.nil?
|
118
113
|
@domain_model = klass
|
119
114
|
end
|
@@ -121,26 +116,25 @@ module Praxis
|
|
121
116
|
def self.check_option!(name, value)
|
122
117
|
case name
|
123
118
|
when :identity
|
124
|
-
raise Attributor::AttributorException, "Invalid identity type #{value.inspect}" unless value.
|
119
|
+
raise Attributor::AttributorException, "Invalid identity type #{value.inspect}" unless value.is_a?(::Symbol)
|
125
120
|
return :ok
|
126
121
|
else
|
127
122
|
return Attributor::Struct.check_option!(name, value)
|
128
123
|
end
|
129
124
|
end
|
130
125
|
|
131
|
-
|
132
|
-
def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
126
|
+
def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
133
127
|
case value
|
134
128
|
when self
|
135
129
|
value
|
136
130
|
when nil, Hash, String
|
137
131
|
# Need to parse/deserialize first
|
138
132
|
# or apply default/recursive loading options if necessary
|
139
|
-
if (value = self.attribute.load(value,context, **options))
|
133
|
+
if (value = self.attribute.load(value, context, **options))
|
140
134
|
self.new(value)
|
141
135
|
end
|
142
136
|
else
|
143
|
-
if value.
|
137
|
+
if value.is_a?(self.domain_model) || value.is_a?(self::Struct)
|
144
138
|
# Wrap the value directly
|
145
139
|
self.new(value)
|
146
140
|
else
|
@@ -151,7 +145,7 @@ module Praxis
|
|
151
145
|
end
|
152
146
|
|
153
147
|
class << self
|
154
|
-
|
148
|
+
alias from load
|
155
149
|
end
|
156
150
|
|
157
151
|
def self.caching_enabled?
|
@@ -173,36 +167,33 @@ module Praxis
|
|
173
167
|
|
174
168
|
def self.valid_type?(value)
|
175
169
|
# FIXME: this should be more... ducklike
|
176
|
-
value.
|
170
|
+
value.is_a?(self) || value.is_a?(self.attribute.type)
|
177
171
|
end
|
178
172
|
|
179
|
-
def self.example(context=nil, **values)
|
173
|
+
def self.example(context = nil, **values)
|
180
174
|
context = case context
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
175
|
+
when nil
|
176
|
+
["#{self.name}-#{values.object_id}"]
|
177
|
+
when ::String
|
178
|
+
[context]
|
179
|
+
else
|
180
|
+
context
|
181
|
+
end
|
188
182
|
|
189
183
|
self.new(self.attribute.example(context, values: values))
|
190
184
|
end
|
191
185
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil
|
186
|
+
def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
|
187
|
+
raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
|
196
188
|
context = [context] if context.is_a? ::String
|
197
189
|
|
198
|
-
unless value.
|
190
|
+
unless value.is_a?(self)
|
199
191
|
raise ArgumentError, "Error validating #{Attributor.humanize_context(context)} as #{self.name} for an object of type #{value.class.name}."
|
200
192
|
end
|
201
193
|
|
202
194
|
value.validate(context)
|
203
195
|
end
|
204
196
|
|
205
|
-
|
206
197
|
def self.view(name, **options, &block)
|
207
198
|
if block_given?
|
208
199
|
return self.views[name] = View.new(name, self, **options, &block)
|
@@ -218,7 +209,7 @@ module Praxis
|
|
218
209
|
object.render(view: view, context: context, **opts)
|
219
210
|
end
|
220
211
|
class << self
|
221
|
-
|
212
|
+
alias render dump
|
222
213
|
end
|
223
214
|
|
224
215
|
# Internal finalize! logic
|
@@ -234,7 +225,7 @@ module Praxis
|
|
234
225
|
end
|
235
226
|
|
236
227
|
def self.resolve_domain_model!
|
237
|
-
return unless self.domain_model.
|
228
|
+
return unless self.domain_model.is_a?(String)
|
238
229
|
|
239
230
|
@domain_model = self.domain_model.constantize
|
240
231
|
end
|
@@ -242,11 +233,12 @@ module Praxis
|
|
242
233
|
def self.define_attribute!
|
243
234
|
@attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
|
244
235
|
@block = nil
|
236
|
+
@attribute.type.anonymous_type true
|
245
237
|
self.const_set(:Struct, @attribute.type)
|
246
238
|
end
|
247
239
|
|
248
240
|
def self.define_readers!
|
249
|
-
self.attributes.each do |name,
|
241
|
+
self.attributes.each do |name, _attribute|
|
250
242
|
name = name.to_sym
|
251
243
|
|
252
244
|
# Don't redefine existing methods
|
@@ -256,7 +248,6 @@ module Praxis
|
|
256
248
|
end
|
257
249
|
end
|
258
250
|
|
259
|
-
|
260
251
|
def self.define_reader!(name)
|
261
252
|
attribute = self.attributes[name]
|
262
253
|
# TODO: profile and optimize
|
@@ -268,53 +259,52 @@ module Praxis
|
|
268
259
|
@decorators.send(name)
|
269
260
|
else
|
270
261
|
value = @object.__send__(name)
|
271
|
-
return value if value.nil? || value.
|
262
|
+
return value if value.nil? || value.is_a?(attribute.type)
|
272
263
|
attribute.load(value)
|
273
264
|
end
|
274
265
|
end
|
275
266
|
end
|
276
267
|
|
277
|
-
|
278
|
-
|
279
268
|
def self.generate_master_view!
|
280
269
|
attributes = self.attributes
|
281
270
|
view :master do
|
282
|
-
attributes.each do |
|
271
|
+
attributes.each do |name, _attr|
|
283
272
|
# Note: we can freely pass master view for attributes that aren't blueprint/containers because
|
284
273
|
# their dump methods will ignore it (they always dump everything regardless)
|
285
274
|
attribute name, view: :default
|
286
275
|
end
|
287
276
|
end
|
288
277
|
end
|
289
|
-
|
290
|
-
|
291
|
-
def initialize(object, decorators=nil)
|
278
|
+
|
279
|
+
def initialize(object, decorators = nil)
|
292
280
|
# TODO: decide what sort of type checking (if any) we want to perform here.
|
293
281
|
@object = object
|
294
282
|
|
295
|
-
@decorators = if decorators.
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
283
|
+
@decorators = if decorators.is_a?(Hash) && decorators.any?
|
284
|
+
OpenStruct.new(decorators)
|
285
|
+
else
|
286
|
+
decorators
|
287
|
+
end
|
300
288
|
|
301
289
|
@validating = false
|
302
290
|
end
|
303
291
|
|
292
|
+
# By default we'll use the object identity, to avoid rendering the same object twice
|
293
|
+
# Override, if there is a better way cache things up
|
294
|
+
def _cache_key
|
295
|
+
self.object
|
296
|
+
end
|
304
297
|
|
305
298
|
# Render the wrapped data with the given view
|
306
|
-
def render(view_name=nil, context: Attributor::DEFAULT_ROOT_CONTEXT,renderer: Renderer.new, **opts)
|
307
|
-
if view_name
|
308
|
-
warn
|
299
|
+
def render(view_name = nil, context: Attributor::DEFAULT_ROOT_CONTEXT, renderer: Renderer.new, **opts)
|
300
|
+
if !view_name.nil?
|
301
|
+
warn 'DEPRECATED: please do not pass the view name as the first parameter in Blueprint.render, pass through the view: named param instead.'
|
309
302
|
elsif opts.key?(:view)
|
310
303
|
view_name = opts[:view]
|
311
304
|
end
|
312
305
|
|
313
306
|
fields = opts[:fields]
|
314
|
-
if view_name.nil? && fields.nil?
|
315
|
-
view_name = :default
|
316
|
-
end
|
317
|
-
|
307
|
+
view_name = :default if view_name.nil? && fields.nil?
|
318
308
|
|
319
309
|
if view_name
|
320
310
|
unless (view = self.class.views[view_name])
|
@@ -325,7 +315,7 @@ module Praxis
|
|
325
315
|
|
326
316
|
# Accept a simple array of fields, and transform it to a 1-level hash with true values
|
327
317
|
if fields.is_a? Array
|
328
|
-
fields = fields.each_with_object({}) {|field, hash| hash[field] = true }
|
318
|
+
fields = fields.each_with_object({}) { |field, hash| hash[field] = true }
|
329
319
|
end
|
330
320
|
|
331
321
|
# expand fields
|
@@ -333,25 +323,37 @@ module Praxis
|
|
333
323
|
|
334
324
|
renderer.render(self, expanded_fields, context: context)
|
335
325
|
end
|
336
|
-
alias_method :to_hash, :render
|
337
|
-
alias_method :dump, :render
|
338
326
|
|
339
|
-
|
340
|
-
|
327
|
+
alias dump render
|
328
|
+
|
329
|
+
def to_h
|
330
|
+
Attributor.recursive_to_h(@object)
|
331
|
+
end
|
332
|
+
|
333
|
+
def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
|
334
|
+
raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
|
341
335
|
context = [context] if context.is_a? ::String
|
336
|
+
keys_with_values = []
|
342
337
|
|
343
|
-
raise
|
338
|
+
raise 'validation conflict' if @validating
|
344
339
|
@validating = true
|
345
340
|
|
346
|
-
|
347
|
-
|
341
|
+
errors = []
|
342
|
+
self.class.attributes.each do |sub_attribute_name, sub_attribute|
|
343
|
+
sub_context = self.class.generate_subcontext(context, sub_attribute_name)
|
348
344
|
value = self.send(sub_attribute_name)
|
345
|
+
keys_with_values << sub_attribute_name unless value.nil?
|
349
346
|
|
350
347
|
if value.respond_to?(:validating) # really, it's a thing with sub-attributes
|
351
348
|
next if value.validating
|
352
349
|
end
|
353
|
-
errors.
|
350
|
+
errors.concat(sub_attribute.validate(value, sub_context))
|
354
351
|
end
|
352
|
+
self.class.attribute.type.requirements.each do |req|
|
353
|
+
validation_errors = req.validate(keys_with_values, context)
|
354
|
+
errors.concat(validation_errors) unless validation_errors.empty?
|
355
|
+
end
|
356
|
+
errors
|
355
357
|
ensure
|
356
358
|
@validating = false
|
357
359
|
end
|
@@ -361,6 +363,13 @@ module Praxis
|
|
361
363
|
self.send(name)
|
362
364
|
end
|
363
365
|
|
366
|
+
# Delegates the json-schema methods to the underlying attribute/member_type
|
367
|
+
def self.as_json_schema(**args)
|
368
|
+
@attribute.type.as_json_schema(args)
|
369
|
+
end
|
370
|
+
|
371
|
+
def self.json_schema_type
|
372
|
+
@attribute.type.json_schema_type
|
373
|
+
end
|
364
374
|
end
|
365
|
-
|
366
375
|
end
|