blueprinter 0.23.2 → 0.25.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 783b36c0d89d672f0757327972b95b66f4338a759df7a7b3637cfea50ecc2b18
4
- data.tar.gz: 50e796b7d4f125bb415b6dead2ddfeda1785797d6be0fa63df2e1e56e8e390d0
3
+ metadata.gz: 1ea32b799d411874c19f6fcd40b522e12ac8eca461e3ed518c2ec21d1b2a4980
4
+ data.tar.gz: 1a9f5d9ce56d46821047bb693975a310abc044ff7a30c3e3c7ca908bcd81b3c2
5
5
  SHA512:
6
- metadata.gz: 9cfae50a0943d24d402409058d6b8f1e72972eaa7878778c6d1aff50cbd028d08acceba24289ba35d238c6915f607a45d0b94b84b61cb57caaf1f5ad9295e3e9
7
- data.tar.gz: 5bf9b8c90f10229245849682975d9a999d6aba0094f785cfa676f3fd02dc91587dab775ba2d874f42ad2ed992e50f47747f3c74347b31b90976727dd56fa0aa6
6
+ metadata.gz: bf07f48a3baaea65428a36c4751a0014f2178c3b391a69f8ad1f6081737454d62caa642b726ca16a8f6b53e83c5ccb61422dc2e470754d37f4c46bb257fea228
7
+ data.tar.gz: 5602227efea583382dc8e297151b473a859071cf293908feafdb00d7f50f4753cd53ae17a37e0697a47889543c883a3f0f668b45813ebc4d1bfa23f7c5a29a04
@@ -1,10 +1,25 @@
1
- ## 0.23.2 - 2019/3/16
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 - 2019/3/13
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 - 2019/1/31
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
 
@@ -156,7 +156,7 @@ module Blueprinter
156
156
  #
157
157
  # @return [Field] A Field object
158
158
  def self.association(method, options = {}, &block)
159
- raise BlueprinterError, 'blueprint required' unless options[:blueprint]
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
- view_name: view_name,
24
- local_options: local_options)
24
+ view_name: view_name,
25
+ local_options: local_options)
25
26
  end
26
27
  else
27
28
  object_to_hash(object,
28
- view_name: view_name,
29
- local_options: local_options)
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 = { root => data }
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
- hash[field.name] = field.extract(object, local_options)
47
+ hash[field.name] = field.extract(object, local_options)
47
48
  end
48
49
  view_collection.transformers(view_name).each do |transformer|
49
- transformer.transform(result_hash,object,local_options)
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
@@ -1,3 +1,3 @@
1
1
  module Blueprinter
2
- VERSION = '0.23.2'.freeze
2
+ VERSION = '0.25.1'.freeze
3
3
  end
@@ -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, :transformers, :definition_order
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
- @transformers = transformers
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.transformers.each do |transformer|
37
- self.add_transformer(transformer)
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
- transformers << custom_transformer
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
- fields_hash = sortable_fields(view_name)
27
- sorted_fields = sort_by_definition ? sort_by_def(view_name, fields_hash) : fields_hash.values.sort_by(&:name)
28
- identifier_fields + sorted_fields
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 != included_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
- views[view_name].excluded_field_names.each do |name|
55
- fields.delete(name)
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
- fields
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.23.2
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-03-16 00:00:00.000000000 Z
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