blueprinter 1.2.1 โ†’ 1.3.0

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: ab333d70266550843309c395b9dcda6635dedf6e5e50fe7fa705a1b8f706d984
4
- data.tar.gz: 4f290066a108b137897ba1e04507befeae3e6e79336955c73ba8217a4d60bb40
3
+ metadata.gz: 72b769b814788e3b67f5cf11fc9e15322f20240cf436eb81004d7014b1a89755
4
+ data.tar.gz: a975d061a805def418f281ef7012f3aa6031448c15e092578cab127a8c06e3bc
5
5
  SHA512:
6
- metadata.gz: 1eb8c065bcb12f262893bcbfce15bcfb3127eaf3d81d7fc153a6782ea77e48770a8f9d0468cef39c633ed9ca4edbaf27e9ae4ca0d7895d9940e932e6af4e3e38
7
- data.tar.gz: 8b7c9a792d3b7a5d4cc00c439682b5a8c06c68fe158cfdf41732b9c0e2d4cb6a957afcd4d4b9ecd472d329af78ec50430f29a45410742eaa45989f1dc66d94a6
6
+ metadata.gz: 2ff9c8a8af880ac84e7352b7e989dc84b05573d494a105879d65661a0f2db6441b69ff799531e4ccd2cc4f14e31cfb9e6acd416f43d2f70f8a6f9edde4dc4fe8
7
+ data.tar.gz: f46db4bfc8eaa1c3d60132e4c69872bc20c116c59033e36916e5dbaeebdc2ecac0c68d3c4abab80d7f45e6adb835602f0540d6c3a4e0582296a1d5ec075c0883
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  ## Unreleased
2
2
  --
3
3
 
4
+ ## 1.3.0 - 2026/04/14
5
+ - ๐Ÿš€ [FEATURE] Adds support for `Symbol#to_proc` syntax in fields and identifiers. See [#546](https://github.com/procore-oss/blueprinter/pull/546). Thanks to [@tob1k](https://github.com/tob1k).
6
+ - ๐Ÿš€ [FEATURE] Adds support for Proc in association `:options` parameter, enabling dynamic options based on the parent object. See [#579](https://github.com/procore-oss/blueprinter/pull/579). Thanks to [@lessthanjacob](https://github.com/lessthanjacob).
7
+ - ๐Ÿ› [BUGFIX] Passes options as keywords during block extraction. See [#521](https://github.com/procore-oss/blueprinter/pull/521). Thanks to [@tylerhunt](https://github.com/tylerhunt).
8
+ - ๐Ÿ› [BUGFIX] Ensures `Deprecation` module gets loaded before it's used. See [#549](https://github.com/procore-oss/blueprinter/pull/549). Thanks to [@jhollinger](https://github.com/jhollinger).
9
+ - ๐Ÿ› [BUGFIX] Fixes `ViewCollection` cache invalidation after view block evaluation, preventing silent loss of mutations when `reflections` triggers early cache compilation. See [#579](https://github.com/procore-oss/blueprinter/pull/579). Thanks to [@lessthanjacob](https://github.com/lessthanjacob).
10
+ - ๐Ÿšœ [REFACTOR] Caches `ViewCollection` and transformers to reduce object allocations and improve rendering performance. See [#566](https://github.com/procore-oss/blueprinter/pull/566). Thanks to [@lessthanjacob](https://github.com/lessthanjacob).
11
+
4
12
  ## 1.2.1 - 2025/09/11
5
13
  - ๐Ÿ› [BUGFIX] Adds back `Blueprinter.prepare` method with a deprecated warning. This method was previously public, but was removed as part of **1.2.0**.
6
14
 
data/README.md CHANGED
@@ -1051,7 +1051,7 @@ assoc[:category].options
1051
1051
  <details>
1052
1052
  <summary>Extensions</summary>
1053
1053
 
1054
- Blueprinter offers an extension system to hook into and modify certain behavior.
1054
+ Blueprinter provides an extension system that enables certain behavior to be modified as needed.
1055
1055
 
1056
1056
  ```ruby
1057
1057
  Blueprinter.configure do |config|
@@ -1060,13 +1060,53 @@ Blueprinter.configure do |config|
1060
1060
  end
1061
1061
  ```
1062
1062
 
1063
- Extension hooks:
1063
+ #### Available Hooks
1064
1064
 
1065
- * [pre_render](https://github.com/procore-oss/blueprinter/blob/abca9ca8ed23edd65a0f4b5ae43e25b8e27a2afc/lib/blueprinter/extension.rb#L18): Intercept the object before rendering begins
1065
+ * **pre_render** - Intercept the object before rendering begins. This allows you to modify, transform, or replace the object that will be serialized.
1066
1066
 
1067
- Some known extensions are:
1067
+ #### Creating Extensions
1068
+
1069
+ To create an extension, simply subclass `Blueprinter::Extension` and override the method representing the desired hook:
1070
+
1071
+ ```ruby
1072
+ class ObfuscateNameExtension < Blueprinter::Extension
1073
+ def pre_render(object, blueprint, view, options)
1074
+ return object unless object.respond_to?(:name)
1075
+
1076
+ modified_object = object.dup
1077
+ modified_object.name = ObsfuscateName.call(modified_object.name)
1078
+
1079
+ modified_object
1080
+ end
1081
+ end
1082
+ ```
1083
+
1084
+ #### Extension Execution Order
1085
+
1086
+ Extensions are executed in the order they are added to the configuration. Each extension receives the result from the previous extension, allowing for chained transformations:
1087
+
1088
+ ```ruby
1089
+ Blueprinter.configure do |config|
1090
+ config.extensions << SecurityExtension.new # Runs first
1091
+ config.extensions << AssociationLoaderExtension.new # Runs second
1092
+ config.extensions << UserEnrichmentExtension.new # Runs third
1093
+ end
1094
+ ```
1095
+
1096
+ #### Gem Extensions
1097
+
1098
+ Gems can be created that enrich `blueprinter`'s core functionality via extensions.
1099
+
1100
+ ##### Procore Supported Gems
1101
+
1102
+ * [blueprinter-activerecord](https://github.com/procore-oss/blueprinter-activerecord) - Provides ActiveRecord-specific optimizations and features
1103
+
1104
+ ##### Community Gems
1105
+
1106
+ > **_NOTE:_** The following are not officially maintained/supported by Procore OSS.
1107
+
1108
+ * [blueprinter_schema](https://github.com/thisismydesign/blueprinter_schema) - Create JSON Schemas from Blueprinter Serializers
1068
1109
 
1069
- * [blueprinter-activerecord](https://github.com/procore-oss/blueprinter-activerecord)
1070
1110
  </details>
1071
1111
 
1072
1112
  <details>
@@ -25,8 +25,8 @@ module Blueprinter
25
25
  extractor,
26
26
  parent_blueprint,
27
27
  options.merge(
28
- blueprint: blueprint,
29
- view: view,
28
+ blueprint:,
29
+ view:,
30
30
  association: true
31
31
  )
32
32
  )
@@ -45,7 +45,7 @@ module Blueprinter
45
45
  # end
46
46
  #
47
47
  # @return [Field] A Field object
48
- def identifier(method, name: method, extractor: Blueprinter.configuration.extractor_default.new, &block)
48
+ def identifier(method, name: method, extractor: Blueprinter.configuration.default_extractor, &block)
49
49
  view_collection[:identifier] << Field.new(
50
50
  method,
51
51
  name,
@@ -116,7 +116,7 @@ module Blueprinter
116
116
  current_view << Field.new(
117
117
  method,
118
118
  options.fetch(:name) { method },
119
- options.fetch(:extractor) { Blueprinter.configuration.extractor_default.new },
119
+ options.fetch(:extractor) { Blueprinter.configuration.default_extractor },
120
120
  self,
121
121
  options.merge(block:)
122
122
  )
@@ -133,6 +133,9 @@ module Blueprinter
133
133
  # JSON output.
134
134
  # @option options [Symbol] :view Specify the view to use or fall back to
135
135
  # to the :default view.
136
+ # @option options [Hash, Proc] :options Additional options to merge into the
137
+ # options hash passed to the nested blueprint. Can be a static Hash or a Proc
138
+ # that receives the parent object and returns a Hash.
136
139
  # @yield [object, options] The object and the options passed to render are
137
140
  # also yielded to the block.
138
141
  #
@@ -150,6 +153,16 @@ module Blueprinter
150
153
  # end
151
154
  # end
152
155
  #
156
+ # @example Passing static options to the nested blueprint.
157
+ # class UserBlueprint < Blueprinter::Base
158
+ # association :vehicles, blueprint: VehiclesBlueprint, options: { show_owner: true }
159
+ # end
160
+ #
161
+ # @example Passing dynamic options based on the parent object.
162
+ # class UserBlueprint < Blueprinter::Base
163
+ # association :vehicles, blueprint: VehiclesBlueprint, options: ->(user) { { owner_name: user.name } }
164
+ # end
165
+ #
153
166
  # @return [Association] An object
154
167
  # @raise [Blueprinter::Errors::InvalidBlueprint] if provided blueprint is not valid
155
168
  def association(method, options = {}, &block)
@@ -159,7 +172,7 @@ module Blueprinter
159
172
  current_view << Association.new(
160
173
  method:,
161
174
  name: options.fetch(:name) { method },
162
- extractor: options.fetch(:extractor) { AssociationExtractor.new },
175
+ extractor: options.fetch(:extractor) { association_extractor },
163
176
  blueprint: options.fetch(:blueprint),
164
177
  parent_blueprint: self,
165
178
  view: options.fetch(:view, :default),
@@ -329,6 +342,7 @@ module Blueprinter
329
342
  self.view_scope = view_collection[view_name]
330
343
  view_collection[:default].track_definition_order(view_name)
331
344
  yield
345
+ view_collection.invalidate_cache!
332
346
  self.view_scope = view_collection[:default]
333
347
  end
334
348
 
@@ -369,6 +383,10 @@ module Blueprinter
369
383
  def inherited(subclass)
370
384
  subclass.send(:view_collection).inherit(view_collection)
371
385
  end
386
+
387
+ def association_extractor
388
+ @_association_extractor ||= AssociationExtractor.new
389
+ end
372
390
  end
373
391
  end
374
392
  end
@@ -12,7 +12,6 @@ module Blueprinter
12
12
  :datetime_format,
13
13
  :default_transformers,
14
14
  :deprecations,
15
- :extractor_default,
16
15
  :field_default,
17
16
  :generator,
18
17
  :if,
@@ -20,7 +19,7 @@ module Blueprinter
20
19
  :sort_fields_by,
21
20
  :unless
22
21
  )
23
- attr_reader :extensions
22
+ attr_reader :extensions, :extractor_default
24
23
 
25
24
  VALID_CALLABLES = %i[if unless].freeze
26
25
 
@@ -59,5 +58,23 @@ module Blueprinter
59
58
  def valid_callable?(callable_name)
60
59
  VALID_CALLABLES.include?(callable_name)
61
60
  end
61
+
62
+ # @param extractor [Class<Blueprinter::AutoExtractor>]
63
+ def extractor_default=(extractor)
64
+ reset_default_extractor!
65
+
66
+ @extractor_default = extractor
67
+ end
68
+
69
+ # @return [Blueprinter::AutoExtractor]
70
+ def default_extractor
71
+ @_default_extractor ||= extractor_default.new
72
+ end
73
+
74
+ private
75
+
76
+ def reset_default_extractor!
77
+ @_default_extractor = nil
78
+ end
62
79
  end
63
80
  end
@@ -9,23 +9,41 @@ module Blueprinter
9
9
  include EmptyTypes
10
10
 
11
11
  def initialize
12
- @extractor = Blueprinter.configuration.extractor_default.new
12
+ @extractor = Blueprinter.configuration.default_extractor
13
13
  end
14
14
 
15
15
  def extract(association_name, object, local_options, options = {})
16
- options_without_default = options.except(:default, :default_if)
17
- # Merge in assocation options hash
18
- local_options = local_options.merge(options[:options]) if options[:options].is_a?(Hash)
16
+ options_without_default = if options.key?(:default) || options.key?(:default_if)
17
+ options.except(:default, :default_if)
18
+ else
19
+ options
20
+ end
21
+
19
22
  value = @extractor.extract(association_name, object, local_options, options_without_default)
20
23
  return default_value(options) if use_default_value?(value, options[:default_if])
21
24
 
25
+ # Merge in association options - supports both static Hash and dynamic Proc
26
+ local_options = merge_association_options(local_options, options[:options], object)
27
+
22
28
  view = options[:view] || :default
23
29
  blueprint = association_blueprint(options[:blueprint], value)
24
- blueprint.hashify(value, view_name: view, local_options: local_options)
30
+ blueprint.hashify(value, view_name: view, local_options:)
25
31
  end
26
32
 
27
33
  private
28
34
 
35
+ def merge_association_options(local_options, association_options, object)
36
+ return local_options unless association_options
37
+
38
+ additional_options = if association_options.respond_to?(:call)
39
+ association_options.call(object)
40
+ else
41
+ association_options
42
+ end
43
+
44
+ local_options.merge(additional_options)
45
+ end
46
+
29
47
  def default_value(association_options)
30
48
  return association_options.fetch(:default) if association_options.key?(:default)
31
49
 
@@ -6,7 +6,31 @@ module Blueprinter
6
6
  # @api private
7
7
  class BlockExtractor < Extractor
8
8
  def extract(_field_name, object, local_options, options = {})
9
- options[:block].call(object, local_options)
9
+ block = options[:block]
10
+
11
+ # Symbol#to_proc creates procs with signature [[:req], [:rest]]
12
+ # These procs forward ALL arguments to the method, which causes
13
+ # issues when we call block.call(object, local_options) because
14
+ # it becomes object.method_name(local_options), and most methods
15
+ # don't accept extra arguments.
16
+ #
17
+ # For Symbol#to_proc, we only pass the object.
18
+ # For regular blocks, we pass both object and local_options.
19
+ if symbol_to_proc?(block)
20
+ block.call(object)
21
+ else
22
+ block.call(object, **local_options)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def symbol_to_proc?(block)
29
+ # Symbol#to_proc has a characteristic signature:
30
+ # - Parameters: [[:req], [:rest]] (one required + rest args)
31
+ # - This is different from regular blocks which typically have
32
+ # optional parameters like [[:opt, :obj], [:opt, :options]]
33
+ block.parameters == [[:req], [:rest]]
10
34
  end
11
35
  end
12
36
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Blueprinter
4
4
  class DateTimeFormatter
5
- InvalidDateTimeFormatterError = Class.new(BlueprinterError)
5
+ class InvalidDateTimeFormatterError < BlueprinterError; end
6
6
 
7
7
  def format(value, options)
8
8
  return value if value.nil?
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'blueprinter/errors/invalid_root'
4
4
  require 'blueprinter/errors/meta_requires_root'
5
+ require 'blueprinter/deprecation'
5
6
 
6
7
  module Blueprinter
7
8
  # Encapsulates the rendering logic for Blueprinter.
@@ -29,7 +30,7 @@ module Blueprinter
29
30
  #
30
31
  # @return [String] JSON formatted String
31
32
  def render(object, options = {})
32
- jsonify(build_result(object: object, options: options))
33
+ jsonify(build_result(object:, options:))
33
34
  end
34
35
 
35
36
  # Generates a Hash representation of the provided object.
@@ -54,7 +55,7 @@ module Blueprinter
54
55
  #
55
56
  # @return [Hash]
56
57
  def render_as_hash(object, options = {})
57
- build_result(object: object, options: options)
58
+ build_result(object:, options:)
58
59
  end
59
60
 
60
61
  # Generates a JSONified hash.
@@ -79,7 +80,7 @@ module Blueprinter
79
80
  #
80
81
  # @return [Hash]
81
82
  def render_as_json(object, options = {})
82
- build_result(object: object, options: options).as_json
83
+ build_result(object:, options:).as_json
83
84
  end
84
85
 
85
86
  # Converts an object into a Hash representation based on provided view.
@@ -121,35 +122,43 @@ module Blueprinter
121
122
  attr_reader :blueprint, :options
122
123
 
123
124
  def prepare_data(object, view_name, local_options)
125
+ # Since we're currently providing the current view in the local_options hash when we extract fields, we can merge
126
+ # it in ahead of time to avoid allocating a new hash for every field extraction.
127
+ local_options_with_view = local_options.merge(view: view_name)
128
+
124
129
  if array_like?(object)
125
130
  object.map do |obj|
126
131
  object_to_hash(
127
132
  obj,
128
- view_name: view_name,
129
- local_options: local_options
133
+ view_name:,
134
+ local_options: local_options_with_view
130
135
  )
131
136
  end
132
137
  else
133
138
  object_to_hash(
134
139
  object,
135
- view_name: view_name,
136
- local_options: local_options
140
+ view_name:,
141
+ local_options: local_options_with_view
137
142
  )
138
143
  end
139
144
  end
140
145
 
141
146
  def object_to_hash(object, view_name:, local_options:)
142
- result_hash = view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
147
+ result_hash = {}
148
+
149
+ view_collection.fields_for(view_name).each do |field|
143
150
  next if field.skip?(field.name, object, local_options)
144
151
 
145
- value = field.extract(object, local_options.merge(view: view_name))
152
+ value = field.extract(object, local_options)
146
153
  next if value.nil? && field.options[:exclude_if_nil]
147
154
 
148
- hash[field.name] = value
155
+ result_hash[field.name] = value
149
156
  end
157
+
150
158
  view_collection.transformers(view_name).each do |transformer|
151
159
  transformer.transform(result_hash, object, local_options)
152
160
  end
161
+
153
162
  result_hash
154
163
  end
155
164
 
@@ -172,11 +181,11 @@ module Blueprinter
172
181
  end
173
182
 
174
183
  def build_result(object:, options:)
175
- view_name = options.fetch(:view, :default) || :default
184
+ view_name = options[:view] || :default
176
185
 
177
186
  prepared_object = hashify(
178
187
  object,
179
- view_name: view_name,
188
+ view_name:,
180
189
  local_options: options.except(:view, :root, :meta)
181
190
  )
182
191
  object_with_root = apply_root_key(
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blueprinter
4
- VERSION = '1.2.1'
4
+ VERSION = '1.3.0'
5
5
  end
@@ -4,6 +4,21 @@ require 'blueprinter/view'
4
4
 
5
5
  module Blueprinter
6
6
  # @api private
7
+ #
8
+ # ViewCollection manages the views defined in a Blueprint, along with their fields and transformers.
9
+ #
10
+ # To improve performance, the "structure" of each view is cached. Once the first view is accessed, we prewarm the
11
+ # cache for _all_ views (while this isn't the _most_ optimal approach, the overhead is negligible, and allows the
12
+ # caching logic to be quite simple).
13
+ #
14
+ # Thread Safety: view data is lazily compiled into a frozen snapshot on first access, and is assigned atomically. A
15
+ # mutex serializes the compilation itself to prevent duplicate work. After compilation, reads bypass the mutex entirely.
16
+ #
17
+ # Future optimization: a public compile! method could allow eager compilation at boot time
18
+ # (e.g. in a Rails after_initialize hook) to move the first-render compilation cost (~10ยตs per
19
+ # blueprint) out of the request path entirely.
20
+ #
21
+ # rubocop:disable Metrics/ClassLength
7
22
  class ViewCollection
8
23
  attr_reader :views, :sort_by_definition
9
24
 
@@ -13,37 +28,60 @@ module Blueprinter
13
28
  default: View.new(:default)
14
29
  }
15
30
  @sort_by_definition = Blueprinter.configuration.sort_fields_by.eql?(:definition)
31
+ @cache_mutex = Mutex.new
32
+ @cache = nil
16
33
  end
17
34
 
18
35
  def inherit(view_collection)
19
36
  view_collection.views.each do |view_name, view|
20
37
  self[view_name].inherit(view)
21
38
  end
39
+
40
+ invalidate_cache!
41
+ end
42
+
43
+ # Clears the compiled field/transformer cache. This should be called after any mutation to a view's fields,
44
+ # exclusions, or transformers to ensure the cache reflects the current state.
45
+ def invalidate_cache!
46
+ @cache = nil
22
47
  end
23
48
 
49
+ # @param [String] view_name
50
+ # @return [Boolean] true if the view exists, false otherwise
24
51
  def view?(view_name)
25
52
  views.key? view_name
26
53
  end
27
54
 
55
+ # Returns an array of Field objects for the provided View.
56
+ # @param [String] view_name
57
+ # @return [Array<Field>]
28
58
  def fields_for(view_name)
29
- return identifier_fields if view_name == :identifier
59
+ ensure_cached!
30
60
 
31
- fields, excluded_fields = sortable_fields(view_name)
32
- sorted_fields = sort_by_definition ? sort_by_def(view_name, fields) : fields.values.sort_by(&:name)
33
-
34
- (identifier_fields + sorted_fields).tap do |fields_array|
35
- fields_array.reject! { |field| excluded_fields.include?(field.name) }
36
- end
61
+ @cache[:fields][view_name]
37
62
  end
38
63
 
64
+ # Returns an array of Transformer objects for the provided View.
65
+ # @param [String] view_name
66
+ # @return [Array<Transformer>]
39
67
  def transformers(view_name)
40
- included_transformers = gather_transformers_from_included_views(view_name).reverse
41
- all_transformers = [*views[:default].view_transformers, *included_transformers].uniq
42
- all_transformers.empty? ? Blueprinter.configuration.default_transformers : all_transformers
68
+ ensure_cached!
69
+
70
+ @cache[:transformers][view_name]
43
71
  end
44
72
 
73
+ # @param [String] view_name
74
+ # @return [View]
45
75
  def [](view_name)
46
- @views[view_name] ||= View.new(view_name)
76
+ return @views[view_name] if @views.key?(view_name)
77
+
78
+ @cache_mutex.synchronize do
79
+ unless @views.key?(view_name)
80
+ @views[view_name] = View.new(view_name)
81
+ invalidate_cache!
82
+ end
83
+ @views[view_name]
84
+ end
47
85
  end
48
86
 
49
87
  private
@@ -52,6 +90,43 @@ module Blueprinter
52
90
  views[:identifier].fields.values
53
91
  end
54
92
 
93
+ def ensure_cached!
94
+ # Fast path: no lock needed once the cache is populated (atomic reference read).
95
+ return if @cache
96
+
97
+ @cache_mutex.synchronize do
98
+ # Re-check after acquiring the lock; another thread may have built the cache first.
99
+ return if @cache
100
+
101
+ fields = {}
102
+ transformers = {}
103
+
104
+ views.each_key do |view_name|
105
+ fields[view_name] = build_fields_for(view_name).freeze
106
+ transformers[view_name] = build_transformers(view_name).freeze
107
+ end
108
+
109
+ @cache = { fields: fields.freeze, transformers: transformers.freeze }.freeze
110
+ end
111
+ end
112
+
113
+ def build_fields_for(view_name)
114
+ return identifier_fields if view_name == :identifier
115
+
116
+ fields, excluded_fields = sortable_fields(view_name)
117
+ sorted_fields = sort_by_definition ? sort_by_def(view_name, fields) : fields.values.sort_by(&:name)
118
+
119
+ (identifier_fields + sorted_fields).tap do |fields_array|
120
+ fields_array.reject! { |field| excluded_fields.include?(field.name) }
121
+ end
122
+ end
123
+
124
+ def build_transformers(view_name)
125
+ included_transformers = gather_transformers_from_included_views(view_name).reverse
126
+ all_transformers = [*views[:default].view_transformers, *included_transformers].uniq
127
+ all_transformers.empty? ? Blueprinter.configuration.default_transformers : all_transformers
128
+ end
129
+
55
130
  # @param [String] view_name
56
131
  # @return [Array<(Hash, Hash<String, NilClass>)>] fields, excluded_fields
57
132
  def sortable_fields(view_name)
@@ -104,4 +179,5 @@ module Blueprinter
104
179
  [*already_included_transformers, *current_view.view_transformers].uniq
105
180
  end
106
181
  end
182
+ # rubocop:enable Metrics/ClassLength
107
183
  end
data/lib/blueprinter.rb CHANGED
@@ -4,6 +4,7 @@ module Blueprinter
4
4
  autoload :Base, 'blueprinter/base'
5
5
  autoload :BlueprinterError, 'blueprinter/blueprinter_error'
6
6
  autoload :Configuration, 'blueprinter/configuration'
7
+ autoload :Deprecation, 'blueprinter/deprecation'
7
8
  autoload :Errors, 'blueprinter/errors'
8
9
  autoload :Extension, 'blueprinter/extension'
9
10
  autoload :Transformer, 'blueprinter/transformer'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprinter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Procore Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-11 00:00:00.000000000 Z
11
+ date: 2026-04-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Blueprinter is a JSON Object Presenter for Ruby that takes business objects
14
14
  and breaks them down into simple hashes and serializes them to JSON. It can be used