blueprinter 0.23.3 → 0.25.2

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: '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