praxis-blueprints 3.0 → 3.5
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/.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
|