blueprinter 0.23.2 → 0.25.1
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 +18 -3
- data/README.md +39 -0
- data/lib/blueprinter/base.rb +24 -4
- data/lib/blueprinter/configuration.rb +2 -1
- data/lib/blueprinter/extractors/association_extractor.rb +2 -0
- data/lib/blueprinter/helpers/base_helpers.rb +48 -9
- data/lib/blueprinter/version.rb +1 -1
- data/lib/blueprinter/view.rb +10 -6
- data/lib/blueprinter/view_collection.rb +14 -10
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ea32b799d411874c19f6fcd40b522e12ac8eca461e3ed518c2ec21d1b2a4980
|
4
|
+
data.tar.gz: 1a9f5d9ce56d46821047bb693975a310abc044ff7a30c3e3c7ca908bcd81b3c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf07f48a3baaea65428a36c4751a0014f2178c3b391a69f8ad1f6081737454d62caa642b726ca16a8f6b53e83c5ccb61422dc2e470754d37f4c46bb257fea228
|
7
|
+
data.tar.gz: 5602227efea583382dc8e297151b473a859071cf293908feafdb00d7f50f4753cd53ae17a37e0697a47889543c883a3f0f668b45813ebc4d1bfa23f7c5a29a04
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,25 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.25.1 - 2020/8/18
|
2
|
+
* 🚀 [BUGFIX] Raise Blueprinter::BlueprinterError if Blueprint given is not of class Blueprinter::Base. Before it just raised a generic `undefined method 'prepare'`. See [#233](https://github.com/procore/blueprinter/pull/233) thanks to [@caws](https://github.com/caws)
|
3
|
+
|
4
|
+
## 0.25.0 - 2020/7/06
|
5
|
+
* 🚀 [FEATURE] Enable default `Blueprinter::Transformer`s to be set in the global configuration. [#222](https://github.com/procore/blueprinter/pull/222). Thanks to [@supremebeing7](https://github.com/supremebeing7).
|
6
|
+
|
7
|
+
## 0.24.0 - 2020/6/22
|
8
|
+
* 🚀 [FEATURE] Add an `options` option to associations to facilitate passing options from one blueprint to another. [#220](https://github.com/procore/blueprinter/pull/220). Thanks to [@mcclayton](https://github.com/mcclayton).
|
9
|
+
|
10
|
+
## 0.23.4 - 2020/4/28
|
11
|
+
* 🚀 [FEATURE] Public class method `has_view?` on Blueprinter::Base subclasses introduced in [#213](https://github.com/procore/blueprinter/pull/213). Thanks to [@spencerneste](https://github.com/spencerneste).
|
12
|
+
|
13
|
+
## 0.23.3 - 2020/4/7
|
14
|
+
* 🐛 [BUGFIX] Fixes issue where `exclude` fields in deeply nested views were not respected. Resolved issue [207](https://github.com/procore/blueprinter/issues/207) in [#208](https://github.com/procore/blueprinter/pull/208) by [@tpltn](https://github.com/tpltn).
|
15
|
+
|
16
|
+
## 0.23.2 - 2020/3/16
|
2
17
|
* 🐛 [BUGFIX] Fixes issue where fields "bled" into other views due to merge side-effects. Resolved issue [205](https://github.com/procore/blueprinter/issues/205) in [#204](https://github.com/procore/blueprinter/pull/204) by [@trevorrjohn](https://github.com/trevorrjohn).
|
3
18
|
|
4
|
-
## 0.23.1 -
|
19
|
+
## 0.23.1 - 2020/3/13
|
5
20
|
* 🐛 [BUGFIX] Fixes #172 where views would unintentionally ignore `sort_fields_by: :definition` configuration. Resolved in [#197](https://github.com/procore/blueprinter/pull/197) by [@wlkrw](https://github.com/wlkrw).
|
6
21
|
|
7
|
-
## 0.23.0 -
|
22
|
+
## 0.23.0 - 2020/1/31
|
8
23
|
* 🚀 [FEATURE] Configurable default extractor introduced in [#198](https://github.com/procore/blueprinter/pull/198) by [@wlkrw](https://github.com/wlkrw). You can now set a default extractor like so:
|
9
24
|
```
|
10
25
|
Blueprinter.configure do |config|
|
data/README.md
CHANGED
@@ -355,6 +355,26 @@ Output:
|
|
355
355
|
}
|
356
356
|
```
|
357
357
|
|
358
|
+
It is also possible to pass options from one Blueprint to another via an association.
|
359
|
+
For example:
|
360
|
+
```ruby
|
361
|
+
class VehicleBlueprint < Blueprinter::Base
|
362
|
+
identifier :uuid
|
363
|
+
field :full_name do |vehicle, options|
|
364
|
+
"#{vehicle.model} #{options[:trim]}"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
class DriverBlueprint < Blueprinter::Base
|
369
|
+
identifier :uuid
|
370
|
+
|
371
|
+
view :normal do
|
372
|
+
fields :first_name, :last_name
|
373
|
+
association :vehicles, blueprint: VehicleBlueprint, options: { trim: 'LX' }
|
374
|
+
end
|
375
|
+
end
|
376
|
+
```
|
377
|
+
|
358
378
|
---
|
359
379
|
</details>
|
360
380
|
|
@@ -739,6 +759,25 @@ class UserBlueprint < Blueprinter::Base
|
|
739
759
|
end
|
740
760
|
```
|
741
761
|
|
762
|
+
#### Global Transforms
|
763
|
+
|
764
|
+
You can also specify global default transformers. Create one or more transformer classes extending from `Blueprinter::Transformer` and set the `default_transformers` configuration
|
765
|
+
```ruby
|
766
|
+
class LowerCamelTransformer < Blueprinter::Transformer
|
767
|
+
def transform(hash, _object, _options)
|
768
|
+
hash.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
|
769
|
+
end
|
770
|
+
end
|
771
|
+
```
|
772
|
+
|
773
|
+
```ruby
|
774
|
+
Blueprinter.configure do |config|
|
775
|
+
config.default_transformers = [LowerCamelTransformer]
|
776
|
+
end
|
777
|
+
```
|
778
|
+
|
779
|
+
**Note: Any transforms specified on a per-blueprint or per-view level will override the `default_transformers` in the configuration.**
|
780
|
+
|
742
781
|
---
|
743
782
|
</details>
|
744
783
|
|
data/lib/blueprinter/base.rb
CHANGED
@@ -156,7 +156,7 @@ module Blueprinter
|
|
156
156
|
#
|
157
157
|
# @return [Field] A Field object
|
158
158
|
def self.association(method, options = {}, &block)
|
159
|
-
|
159
|
+
validate_blueprint!(options[:blueprint], method)
|
160
160
|
|
161
161
|
field(
|
162
162
|
method,
|
@@ -214,7 +214,7 @@ module Blueprinter
|
|
214
214
|
# # => [{id:1, title: Hello},{id:2, title: My Day}]
|
215
215
|
#
|
216
216
|
# @return [Hash]
|
217
|
-
def self.render_as_hash(object, options= {})
|
217
|
+
def self.render_as_hash(object, options = {})
|
218
218
|
prepare_for_render(object, options)
|
219
219
|
end
|
220
220
|
|
@@ -239,7 +239,7 @@ module Blueprinter
|
|
239
239
|
# # => [{"id" => "1", "title" => "Hello"},{"id" => "2", "title" => "My Day"}]
|
240
240
|
#
|
241
241
|
# @return [Hash]
|
242
|
-
def self.render_as_json(object, options= {})
|
242
|
+
def self.render_as_json(object, options = {})
|
243
243
|
prepare_for_render(object, options).as_json
|
244
244
|
end
|
245
245
|
|
@@ -279,7 +279,6 @@ module Blueprinter
|
|
279
279
|
end
|
280
280
|
|
281
281
|
|
282
|
-
|
283
282
|
# Specify one transformer to be included for serialization.
|
284
283
|
# Takes a class which extends Blueprinter::Transformer
|
285
284
|
#
|
@@ -431,5 +430,26 @@ module Blueprinter
|
|
431
430
|
yield
|
432
431
|
@current_view = view_collection[:default]
|
433
432
|
end
|
433
|
+
|
434
|
+
# Check whether or not a Blueprint supports the supplied view.
|
435
|
+
# It accepts a view name.
|
436
|
+
#
|
437
|
+
# @param view_name [Symbol] the view name
|
438
|
+
#
|
439
|
+
# @example With the following Blueprint
|
440
|
+
#
|
441
|
+
# class ExampleBlueprint < Blueprinter::Base
|
442
|
+
# view :custom do
|
443
|
+
# end
|
444
|
+
# end
|
445
|
+
#
|
446
|
+
# ExampleBlueprint.has_view?(:custom) => true
|
447
|
+
# ExampleBlueprint.has_view?(:doesnt_exist) => false
|
448
|
+
#
|
449
|
+
# @return [Boolean] a boolean value indicating if the view is
|
450
|
+
# supported by this Blueprint.
|
451
|
+
def self.has_view?(view_name)
|
452
|
+
view_collection.has_view? view_name
|
453
|
+
end
|
434
454
|
end
|
435
455
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Blueprinter
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :association_default, :datetime_format, :field_default, :generator, :if, :method, :sort_fields_by, :unless, :extractor_default
|
3
|
+
attr_accessor :association_default, :datetime_format, :field_default, :generator, :if, :method, :sort_fields_by, :unless, :extractor_default, :default_transformers
|
4
4
|
|
5
5
|
VALID_CALLABLES = %i(if unless).freeze
|
6
6
|
|
@@ -14,6 +14,7 @@ module Blueprinter
|
|
14
14
|
@sort_fields_by = :name_asc
|
15
15
|
@unless = nil
|
16
16
|
@extractor_default = AutoExtractor
|
17
|
+
@default_transformers = []
|
17
18
|
end
|
18
19
|
|
19
20
|
def jsonify(blob)
|
@@ -9,6 +9,8 @@ module Blueprinter
|
|
9
9
|
|
10
10
|
def extract(association_name, object, local_options, options={})
|
11
11
|
options_without_default = options.reject { |k,_| k == :default || k == :default_if }
|
12
|
+
# Merge in assocation options hash
|
13
|
+
local_options = local_options.merge(options[:options]) if options[:options].is_a?(Hash)
|
12
14
|
value = @extractor.extract(association_name, object, local_options, options_without_default)
|
13
15
|
return default_value(options) if use_default_value?(value, options[:default_if])
|
14
16
|
view = options[:view] || :default
|
@@ -8,11 +8,12 @@ module Blueprinter
|
|
8
8
|
include TypeHelpers
|
9
9
|
|
10
10
|
private
|
11
|
+
|
11
12
|
def prepare_for_render(object, options)
|
12
13
|
view_name = options.delete(:view) || :default
|
13
14
|
root = options.delete(:root)
|
14
15
|
meta = options.delete(:meta)
|
15
|
-
validate_root_and_meta(root, meta)
|
16
|
+
validate_root_and_meta!(root, meta)
|
16
17
|
prepare(object, view_name: view_name, local_options: options, root: root, meta: meta)
|
17
18
|
end
|
18
19
|
|
@@ -20,19 +21,19 @@ module Blueprinter
|
|
20
21
|
if array_like?(object)
|
21
22
|
object.map do |obj|
|
22
23
|
object_to_hash(obj,
|
23
|
-
|
24
|
-
|
24
|
+
view_name: view_name,
|
25
|
+
local_options: local_options)
|
25
26
|
end
|
26
27
|
else
|
27
28
|
object_to_hash(object,
|
28
|
-
|
29
|
-
|
29
|
+
view_name: view_name,
|
30
|
+
local_options: local_options)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
34
|
def prepend_root_and_meta(data, root, meta)
|
34
35
|
return data unless root
|
35
|
-
ret = {
|
36
|
+
ret = {root => data}
|
36
37
|
meta ? ret.merge!(meta: meta) : ret
|
37
38
|
end
|
38
39
|
|
@@ -43,15 +44,15 @@ module Blueprinter
|
|
43
44
|
def object_to_hash(object, view_name:, local_options:)
|
44
45
|
result_hash = view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
|
45
46
|
next if field.skip?(field.name, object, local_options)
|
46
|
-
|
47
|
+
hash[field.name] = field.extract(object, local_options)
|
47
48
|
end
|
48
49
|
view_collection.transformers(view_name).each do |transformer|
|
49
|
-
|
50
|
+
transformer.transform(result_hash, object, local_options)
|
50
51
|
end
|
51
52
|
result_hash
|
52
53
|
end
|
53
54
|
|
54
|
-
def validate_root_and_meta(root, meta)
|
55
|
+
def validate_root_and_meta!(root, meta)
|
55
56
|
case root
|
56
57
|
when String, Symbol
|
57
58
|
# no-op
|
@@ -62,6 +63,44 @@ module Blueprinter
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
def dynamic_blueprint?(blueprint)
|
67
|
+
blueprint.is_a?(Proc)
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_blueprint!(blueprint, method)
|
71
|
+
validate_presence_of_blueprint!(blueprint)
|
72
|
+
unless dynamic_blueprint?(blueprint)
|
73
|
+
validate_blueprint_has_ancestors!(blueprint, method)
|
74
|
+
validate_blueprint_has_blueprinter_base_ancestor!(blueprint, method)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_presence_of_blueprint!(blueprint)
|
79
|
+
raise BlueprinterError, 'Blueprint required' unless blueprint
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_blueprint_has_ancestors!(blueprint, association_name)
|
83
|
+
# If the class passed as a blueprint does not respond to ancestors
|
84
|
+
# it means it, at the very least, does not have Blueprinter::Base as
|
85
|
+
# one of its ancestor classes (e.g: Hash) and thus an error should
|
86
|
+
# be raised.
|
87
|
+
unless blueprint.respond_to?(:ancestors)
|
88
|
+
raise BlueprinterError, "Blueprint provided for #{association_name} "\
|
89
|
+
'association is not valid.'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_blueprint_has_blueprinter_base_ancestor!(blueprint, association_name)
|
94
|
+
# Guard clause in case Blueprinter::Base is present in the ancestor list
|
95
|
+
# for the blueprint class provided.
|
96
|
+
return if blueprint.ancestors.include? Blueprinter::Base
|
97
|
+
|
98
|
+
# Raise error describing what's wrong.
|
99
|
+
raise BlueprinterError, "Class #{blueprint.name} does not inherit from "\
|
100
|
+
'Blueprinter::Base and is not a valid Blueprinter '\
|
101
|
+
"for #{association_name} association."
|
102
|
+
end
|
103
|
+
|
65
104
|
def jsonify(blob)
|
66
105
|
Blueprinter.configuration.jsonify(blob)
|
67
106
|
end
|
data/lib/blueprinter/version.rb
CHANGED
data/lib/blueprinter/view.rb
CHANGED
@@ -2,18 +2,22 @@ module Blueprinter
|
|
2
2
|
# @api private
|
3
3
|
DefinitionPlaceholder = Struct.new :name, :view?
|
4
4
|
class View
|
5
|
-
attr_reader :excluded_field_names, :fields, :included_view_names, :name, :
|
5
|
+
attr_reader :excluded_field_names, :fields, :included_view_names, :name, :view_transformers, :definition_order
|
6
6
|
|
7
|
-
def initialize(name, fields: {}, included_view_names: [], excluded_view_names: [],transformers: [])
|
7
|
+
def initialize(name, fields: {}, included_view_names: [], excluded_view_names: [], transformers: [])
|
8
8
|
@name = name
|
9
9
|
@fields = fields
|
10
10
|
@included_view_names = included_view_names
|
11
11
|
@excluded_field_names = excluded_view_names
|
12
|
-
@
|
12
|
+
@view_transformers = transformers
|
13
13
|
@definition_order = []
|
14
14
|
@sort_by_definition = Blueprinter.configuration.sort_fields_by.eql?(:definition)
|
15
15
|
end
|
16
16
|
|
17
|
+
def transformers
|
18
|
+
view_transformers.empty? ? Blueprinter.configuration.default_transformers : view_transformers
|
19
|
+
end
|
20
|
+
|
17
21
|
def track_definition_order(method, is_view = true)
|
18
22
|
if @sort_by_definition
|
19
23
|
@definition_order << DefinitionPlaceholder.new(method, is_view)
|
@@ -33,8 +37,8 @@ module Blueprinter
|
|
33
37
|
exclude_field(field_name)
|
34
38
|
end
|
35
39
|
|
36
|
-
view.
|
37
|
-
|
40
|
+
view.view_transformers.each do |transformer|
|
41
|
+
add_transformer(transformer)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
@@ -61,7 +65,7 @@ module Blueprinter
|
|
61
65
|
end
|
62
66
|
|
63
67
|
def add_transformer(custom_transformer)
|
64
|
-
|
68
|
+
view_transformers << custom_transformer
|
65
69
|
end
|
66
70
|
|
67
71
|
def <<(field)
|
@@ -23,9 +23,10 @@ module Blueprinter
|
|
23
23
|
def fields_for(view_name)
|
24
24
|
return identifier_fields if view_name == :identifier
|
25
25
|
|
26
|
-
|
27
|
-
sorted_fields = sort_by_definition ? sort_by_def(view_name,
|
28
|
-
|
26
|
+
fields, excluded_fields = sortable_fields(view_name)
|
27
|
+
sorted_fields = sort_by_definition ? sort_by_def(view_name, fields) : fields.values.sort_by(&:name)
|
28
|
+
|
29
|
+
(identifier_fields + sorted_fields).reject { |field| excluded_fields.include?(field.name) }
|
29
30
|
end
|
30
31
|
|
31
32
|
def transformers(view_name)
|
@@ -42,20 +43,23 @@ module Blueprinter
|
|
42
43
|
views[:identifier].fields.values
|
43
44
|
end
|
44
45
|
|
46
|
+
# @param [String] view_name
|
47
|
+
# @return [Array<(Hash, Hash<String, NilClass>)>] fields, excluded_fields
|
45
48
|
def sortable_fields(view_name)
|
49
|
+
excluded_fields = {}
|
46
50
|
fields = views[:default].fields
|
47
51
|
fields = merge_fields(fields, views[view_name].fields)
|
48
52
|
views[view_name].included_view_names.each do |included_view_name|
|
49
|
-
if view_name
|
50
|
-
fields = merge_fields(fields, sortable_fields(included_view_name))
|
51
|
-
end
|
52
|
-
end
|
53
|
+
next if view_name == included_view_name
|
53
54
|
|
54
|
-
|
55
|
-
fields
|
55
|
+
view_fields, view_excluded_fields = sortable_fields(included_view_name)
|
56
|
+
fields = merge_fields(fields, view_fields)
|
57
|
+
excluded_fields.merge!(view_excluded_fields)
|
56
58
|
end
|
57
59
|
|
58
|
-
|
60
|
+
views[view_name].excluded_field_names.each { |name| excluded_fields[name] = nil }
|
61
|
+
|
62
|
+
[fields, excluded_fields]
|
59
63
|
end
|
60
64
|
|
61
65
|
# select and order members of fields according to traversal of the definition_orders
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blueprinter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.25.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Hess
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-08-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: factory_bot
|
@@ -123,6 +123,20 @@ dependencies:
|
|
123
123
|
- - "~>"
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '3.7'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rspec-rails
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - "<"
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 4.0.0
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - "<"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 4.0.0
|
126
140
|
- !ruby/object:Gem::Dependency
|
127
141
|
name: sqlite3
|
128
142
|
requirement: !ruby/object:Gem::Requirement
|