blueprinter 0.23.3 → 0.25.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '04947d283f28536a499f609e8e25bb4f4ced350b75756b04dcbeb2de71100dc0'
4
- data.tar.gz: ea71156a7134b1378fd829b0b1fd9712ab96b2b7c17d305ef5523e4872424038
3
+ metadata.gz: 9e5e7be1f829035c332a5baa935ef724f52c381bd60f039a20a89368acc7cfde
4
+ data.tar.gz: 7307febc9d3a860aa3b3ffe00a83066b808329a32a3d81dde5a7af7439dc4609
5
5
  SHA512:
6
- metadata.gz: 18148f52cda6a721e1b4c99d6b3d41d05bad040339d8c175ffd13f18cbd26dbde193541ebc7b42ce5a5cb6e421489741a1b6b780af3009ee850e87192f3edcea
7
- data.tar.gz: a1b0a95d9a61be97eaffe45f39bb5347840c8dc8b48e553262e34039f79fced01454b6e867d13a2eb2709ad1f7f21915bd23143d77978404c5b382a914b1532f
6
+ metadata.gz: 24411ac6c3068b7c08c167e1f91a6e4e25757fd4a32df56f710b0fbe84d07e7507cbdf05eb54645ba3b9b29b8b90c81cede3e68254b254c0523975c9c9236dc0
7
+ data.tar.gz: baef6ae7db2fcc0e6ab9eb4e4c11baf6453ecaf30519462bd6ea5d4dbd5cbd8b1e791f202b2b74e9f15ccc4d265d19828c33b339dc65041916d09351d7729e1f
@@ -1,13 +1,28 @@
1
- ## 0.23.3 - 2019/4/7
1
+ ## 0.25.2 - 2020/11/19
2
+ * 🚀 [FEATURE] Make deprecation behavior configurable (`:silence`, `:stderror`, `:raise`). See [#248](https://github.com/procore/blueprinter/pull/248) thanks to [@mcclayton](https://github.com/mcclayton)
3
+
4
+ ## 0.25.1 - 2020/8/18
5
+ * 🐛 [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)
6
+
7
+ ## 0.25.0 - 2020/7/06
8
+ * 🚀 [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).
9
+
10
+ ## 0.24.0 - 2020/6/22
11
+ * 🚀 [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).
12
+
13
+ ## 0.23.4 - 2020/4/28
14
+ * 🚀 [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).
15
+
16
+ ## 0.23.3 - 2020/4/7
2
17
  * 🐛 [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).
3
18
 
4
- ## 0.23.2 - 2019/3/16
19
+ ## 0.23.2 - 2020/3/16
5
20
  * 🐛 [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).
6
21
 
7
- ## 0.23.1 - 2019/3/13
22
+ ## 0.23.1 - 2020/3/13
8
23
  * 🐛 [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).
9
24
 
10
- ## 0.23.0 - 2019/1/31
25
+ ## 0.23.0 - 2020/1/31
11
26
  * 🚀 [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:
12
27
  ```
13
28
  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
 
@@ -830,6 +869,33 @@ Output:
830
869
  </details>
831
870
 
832
871
 
872
+ <details>
873
+ <summary>Deprecations</summary>
874
+
875
+ ---
876
+
877
+ When functionality in Blueprinter is invoked, that has been deprecated, the default behavior is to
878
+ write a deprecation notice to stderror.
879
+
880
+ However, deprecations can be configured to report at three different levels:
881
+
882
+ | Key | Result |
883
+ |:-----------------:|:---------------------------------------------------------------:|
884
+ | `:stderr` (Default) | Deprecations will be written to stderror |
885
+ | `:raise` | Deprecations will be raised as `Blueprinter::BlueprinterError`s |
886
+ | `:silence` | Deprecations will be silenced and will not be raised or logged |
887
+
888
+ ### Example:
889
+ ```ruby
890
+ Blueprinter.configure do |config|
891
+ config.deprecations = :raise
892
+ end
893
+ ```
894
+
895
+ ---
896
+ </details>
897
+
898
+
833
899
  <details>
834
900
  <summary>render_as_hash</summary>
835
901
 
@@ -1,5 +1,6 @@
1
1
  require_relative 'blueprinter_error'
2
2
  require_relative 'configuration'
3
+ require_relative 'deprecation'
3
4
  require_relative 'empty_types'
4
5
  require_relative 'extractor'
5
6
  require_relative 'extractors/association_extractor'
@@ -156,7 +157,7 @@ module Blueprinter
156
157
  #
157
158
  # @return [Field] A Field object
158
159
  def self.association(method, options = {}, &block)
159
- raise BlueprinterError, 'blueprint required' unless options[:blueprint]
160
+ validate_blueprint!(options[:blueprint], method)
160
161
 
161
162
  field(
162
163
  method,
@@ -214,7 +215,7 @@ module Blueprinter
214
215
  # # => [{id:1, title: Hello},{id:2, title: My Day}]
215
216
  #
216
217
  # @return [Hash]
217
- def self.render_as_hash(object, options= {})
218
+ def self.render_as_hash(object, options = {})
218
219
  prepare_for_render(object, options)
219
220
  end
220
221
 
@@ -239,7 +240,7 @@ module Blueprinter
239
240
  # # => [{"id" => "1", "title" => "Hello"},{"id" => "2", "title" => "My Day"}]
240
241
  #
241
242
  # @return [Hash]
242
- def self.render_as_json(object, options= {})
243
+ def self.render_as_json(object, options = {})
243
244
  prepare_for_render(object, options).as_json
244
245
  end
245
246
 
@@ -279,7 +280,6 @@ module Blueprinter
279
280
  end
280
281
 
281
282
 
282
-
283
283
  # Specify one transformer to be included for serialization.
284
284
  # Takes a class which extends Blueprinter::Transformer
285
285
  #
@@ -431,5 +431,26 @@ module Blueprinter
431
431
  yield
432
432
  @current_view = view_collection[:default]
433
433
  end
434
+
435
+ # Check whether or not a Blueprint supports the supplied view.
436
+ # It accepts a view name.
437
+ #
438
+ # @param view_name [Symbol] the view name
439
+ #
440
+ # @example With the following Blueprint
441
+ #
442
+ # class ExampleBlueprint < Blueprinter::Base
443
+ # view :custom do
444
+ # end
445
+ # end
446
+ #
447
+ # ExampleBlueprint.has_view?(:custom) => true
448
+ # ExampleBlueprint.has_view?(:doesnt_exist) => false
449
+ #
450
+ # @return [Boolean] a boolean value indicating if the view is
451
+ # supported by this Blueprint.
452
+ def self.has_view?(view_name)
453
+ view_collection.has_view? view_name
454
+ end
434
455
  end
435
456
  end
@@ -1,10 +1,11 @@
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, :deprecations, :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
 
7
7
  def initialize
8
+ @deprecations = :stderror
8
9
  @association_default = nil
9
10
  @datetime_format = nil
10
11
  @field_default = nil
@@ -14,6 +15,7 @@ module Blueprinter
14
15
  @sort_fields_by = :name_asc
15
16
  @unless = nil
16
17
  @extractor_default = AutoExtractor
18
+ @default_transformers = []
17
19
  end
18
20
 
19
21
  def jsonify(blob)
@@ -0,0 +1,35 @@
1
+ # @api private
2
+ module Blueprinter
3
+ class Deprecation
4
+ class << self
5
+ VALID_BEHAVIORS = %i(silence stderror raise).freeze
6
+ MESSAGE_PREFIX = "[DEPRECATION::WARNING] Blueprinter:".freeze
7
+
8
+ def report(message)
9
+ full_msg = qualified_message(message)
10
+
11
+ case behavior
12
+ when :silence
13
+ # Silence deprecation (noop)
14
+ when :stderror
15
+ warn full_msg
16
+ when :raise
17
+ raise BlueprinterError, full_msg
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def qualified_message(message)
24
+ "#{MESSAGE_PREFIX} #{message}"
25
+ end
26
+
27
+ def behavior
28
+ configured = Blueprinter.configuration.deprecations
29
+ return configured unless !VALID_BEHAVIORS.include?(configured)
30
+
31
+ :stderror
32
+ end
33
+ end
34
+ end
35
+ end
@@ -10,6 +10,8 @@ module Blueprinter
10
10
  private
11
11
 
12
12
  def use_default_value?(value, empty_type)
13
+ return value.nil? unless empty_type
14
+
13
15
  case empty_type
14
16
  when Blueprinter::EMPTY_COLLECTION
15
17
  array_like?(value) && value.empty?
@@ -18,7 +20,10 @@ module Blueprinter
18
20
  when Blueprinter::EMPTY_STRING
19
21
  value.to_s == ""
20
22
  else
21
- value.nil?
23
+ Blueprinter::Deprecation.report(
24
+ "Invalid empty type '#{empty_type}' received. Blueprinter will raise an error in the next major version."\
25
+ "Must be one of [nil, Blueprinter::EMPTY_COLLECTION, Blueprinter::EMPTY_HASH, Blueprinter::EMPTY_STRING]"
26
+ )
22
27
  end
23
28
  end
24
29
  end
@@ -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
@@ -34,7 +34,7 @@ class Blueprinter::Field
34
34
  callable = old_callable_from(condition)
35
35
 
36
36
  if callable && callable.arity == 2
37
- warn "[DEPRECATION] Blueprinter :#{condition} conditions now expects 3 arguments instead of 2."
37
+ Blueprinter::Deprecation.report("`:#{condition}` conditions now expects 3 arguments instead of 2.")
38
38
  ->(_field_name, obj, options) { callable.call(obj, options) }
39
39
  else
40
40
  callable
@@ -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.3'.freeze
2
+ VERSION = '0.25.2'.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)
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.3
4
+ version: 0.25.2
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-04-07 00:00:00.000000000 Z
12
+ date: 2020-11-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: factory_bot
@@ -197,6 +197,7 @@ files:
197
197
  - lib/blueprinter/base.rb
198
198
  - lib/blueprinter/blueprinter_error.rb
199
199
  - lib/blueprinter/configuration.rb
200
+ - lib/blueprinter/deprecation.rb
200
201
  - lib/blueprinter/empty_types.rb
201
202
  - lib/blueprinter/extractor.rb
202
203
  - lib/blueprinter/extractors/association_extractor.rb