blueprinter 1.1.2 → 1.2.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.
@@ -10,14 +10,10 @@ module Blueprinter
10
10
  # @return [Boolean] true if object is a valid Blueprint
11
11
  # @raise [Blueprinter::Errors::InvalidBlueprint] if the object is not a valid Blueprint.
12
12
  def validate!(blueprint)
13
- if valid_blueprint?(blueprint)
14
- true
15
- else
16
- raise(
17
- Errors::InvalidBlueprint,
18
- "#{blueprint} is not a valid blueprint. Please ensure it subclasses Blueprinter::Base or is a Proc."
19
- )
20
- end
13
+ valid_blueprint?(blueprint) || raise(
14
+ Errors::InvalidBlueprint,
15
+ "#{blueprint} is not a valid blueprint. Please ensure it subclasses Blueprinter::Base or is a Proc."
16
+ )
21
17
  end
22
18
 
23
19
  private
@@ -6,8 +6,21 @@ require 'blueprinter/extractors/auto_extractor'
6
6
 
7
7
  module Blueprinter
8
8
  class Configuration
9
- attr_accessor :association_default, :datetime_format, :deprecations, :field_default, :generator, :if, :method,
10
- :sort_fields_by, :unless, :extractor_default, :default_transformers, :custom_array_like_classes
9
+ attr_accessor(
10
+ :association_default,
11
+ :custom_array_like_classes,
12
+ :datetime_format,
13
+ :default_transformers,
14
+ :deprecations,
15
+ :extractor_default,
16
+ :field_default,
17
+ :generator,
18
+ :if,
19
+ :method,
20
+ :sort_fields_by,
21
+ :unless
22
+ )
23
+ attr_reader :extensions
11
24
 
12
25
  VALID_CALLABLES = %i[if unless].freeze
13
26
 
@@ -24,10 +37,7 @@ module Blueprinter
24
37
  @extractor_default = AutoExtractor
25
38
  @default_transformers = []
26
39
  @custom_array_like_classes = []
27
- end
28
-
29
- def extensions
30
- @extensions ||= Extensions.new
40
+ @extensions = Extensions.new
31
41
  end
32
42
 
33
43
  def extensions=(list)
@@ -35,7 +45,7 @@ module Blueprinter
35
45
  end
36
46
 
37
47
  def array_like_classes
38
- @array_like_classes ||= [
48
+ @_array_like_classes ||= [
39
49
  Array,
40
50
  defined?(ActiveRecord::Relation) && ActiveRecord::Relation,
41
51
  *custom_array_like_classes
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Blueprinter
4
4
  module Errors
5
- class InvalidBlueprint < Blueprinter::BlueprinterError; end
5
+ class InvalidBlueprint < BlueprinterError; end
6
6
  end
7
7
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'blueprinter/blueprinter_error'
4
+
5
+ module Blueprinter
6
+ module Errors
7
+ class InvalidRoot < BlueprinterError
8
+ def initialize(message = 'root key must be a Symbol or a String')
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'blueprinter/blueprinter_error'
4
+
5
+ module Blueprinter
6
+ module Errors
7
+ class MetaRequiresRoot < BlueprinterError
8
+ def initialize(message = 'adding metadata requires that a root key is set')
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -21,7 +21,7 @@ module Blueprinter
21
21
 
22
22
  view = options[:view] || :default
23
23
  blueprint = association_blueprint(options[:blueprint], value)
24
- blueprint.prepare(value, view_name: view, local_options: local_options)
24
+ blueprint.hashify(value, view_name: view, local_options: local_options)
25
25
  end
26
26
 
27
27
  private
@@ -26,15 +26,11 @@ module Blueprinter
26
26
  private
27
27
 
28
28
  def if_callable
29
- return @if_callable if defined?(@if_callable)
30
-
31
- @if_callable = callable_from(:if)
29
+ @_if_callable ||= callable_from(:if)
32
30
  end
33
31
 
34
32
  def unless_callable
35
- return @unless_callable if defined?(@unless_callable)
36
-
37
- @unless_callable = callable_from(:unless)
33
+ @_unless_callable ||= callable_from(:unless)
38
34
  end
39
35
 
40
36
  def callable_from(condition)
@@ -23,7 +23,7 @@ module Blueprinter
23
23
  # @return [Hash<Symbol, Blueprinter::Reflection::View>]
24
24
  #
25
25
  def reflections
26
- @reflections ||= view_collection.views.transform_values do |view|
26
+ @_reflections ||= view_collection.views.transform_values do |view|
27
27
  View.new(view.name, view_collection)
28
28
  end
29
29
  end
@@ -45,7 +45,7 @@ module Blueprinter
45
45
  # @return [Hash<Symbol, Blueprinter::Reflection::Field>]
46
46
  #
47
47
  def fields
48
- @fields ||= @view_collection.fields_for(name).each_with_object({}) do |field, obj|
48
+ @_fields ||= @view_collection.fields_for(name).each_with_object({}) do |field, obj|
49
49
  next if field.options[:association]
50
50
 
51
51
  obj[field.name] = Field.new(field.method, field.name, field.options)
@@ -58,7 +58,7 @@ module Blueprinter
58
58
  # @return [Hash<Symbol, Blueprinter::Reflection::Association>]
59
59
  #
60
60
  def associations
61
- @associations ||= @view_collection.fields_for(name).each_with_object({}) do |field, obj|
61
+ @_associations ||= @view_collection.fields_for(name).each_with_object({}) do |field, obj|
62
62
  next unless field.options[:association]
63
63
 
64
64
  blueprint = field.options.fetch(:blueprint)
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'blueprinter/errors/invalid_root'
4
+ require 'blueprinter/errors/meta_requires_root'
5
+
6
+ module Blueprinter
7
+ # Encapsulates the rendering logic for Blueprinter.
8
+ module Rendering
9
+ include TypeHelpers
10
+
11
+ # Generates a JSON formatted String represantation of the provided object.
12
+ #
13
+ # @param object [Object] the Object to serialize.
14
+ # @param options [Hash] the options hash which requires a :view. Any
15
+ # additional key value pairs will be exposed during serialization.
16
+ # @option options [Symbol] :view Defaults to :default.
17
+ # The view name that corresponds to the group of
18
+ # fields to be serialized.
19
+ # @option options [Symbol|String] :root Defaults to nil.
20
+ # Render the json/hash with a root key if provided.
21
+ # @option options [Any] :meta Defaults to nil.
22
+ # Render the json/hash with a meta attribute with provided value
23
+ # if both root and meta keys are provided in the options hash.
24
+ #
25
+ # @example Generating JSON with an extended view
26
+ # post = Post.all
27
+ # Blueprinter::Base.render post, view: :extended
28
+ # # => "[{\"id\":1,\"title\":\"Hello\"},{\"id\":2,\"title\":\"My Day\"}]"
29
+ #
30
+ # @return [String] JSON formatted String
31
+ def render(object, options = {})
32
+ jsonify(build_result(object: object, options: options))
33
+ end
34
+
35
+ # Generates a Hash representation of the provided object.
36
+ # Takes a required object and an optional view.
37
+ #
38
+ # @param object [Object] the Object to serialize upon.
39
+ # @param options [Hash] the options hash which requires a :view. Any
40
+ # additional key value pairs will be exposed during serialization.
41
+ # @option options [Symbol] :view Defaults to :default.
42
+ # The view name that corresponds to the group of
43
+ # fields to be serialized.
44
+ # @option options [Symbol|String] :root Defaults to nil.
45
+ # Render the json/hash with a root key if provided.
46
+ # @option options [Any] :meta Defaults to nil.
47
+ # Render the json/hash with a meta attribute with provided value
48
+ # if both root and meta keys are provided in the options hash.
49
+ #
50
+ # @example Generating a hash with an extended view
51
+ # post = Post.all
52
+ # Blueprinter::Base.render_as_hash post, view: :extended
53
+ # # => [{id:1, title: Hello},{id:2, title: My Day}]
54
+ #
55
+ # @return [Hash]
56
+ def render_as_hash(object, options = {})
57
+ build_result(object: object, options: options)
58
+ end
59
+
60
+ # Generates a JSONified hash.
61
+ # Takes a required object and an optional view.
62
+ #
63
+ # @param object [Object] the Object to serialize upon.
64
+ # @param options [Hash] the options hash which requires a :view. Any
65
+ # additional key value pairs will be exposed during serialization.
66
+ # @option options [Symbol] :view Defaults to :default.
67
+ # The view name that corresponds to the group of
68
+ # fields to be serialized.
69
+ # @option options [Symbol|String] :root Defaults to nil.
70
+ # Render the json/hash with a root key if provided.
71
+ # @option options [Any] :meta Defaults to nil.
72
+ # Render the json/hash with a meta attribute with provided value
73
+ # if both root and meta keys are provided in the options hash.
74
+ #
75
+ # @example Generating a hash with an extended view
76
+ # post = Post.all
77
+ # Blueprinter::Base.render_as_json post, view: :extended
78
+ # # => [{"id" => "1", "title" => "Hello"},{"id" => "2", "title" => "My Day"}]
79
+ #
80
+ # @return [Hash]
81
+ def render_as_json(object, options = {})
82
+ build_result(object: object, options: options).as_json
83
+ end
84
+
85
+ # Converts an object into a Hash representation based on provided view.
86
+ #
87
+ # @param object [Object] the Object to convert into a Hash.
88
+ # @param view_name [Symbol] the view
89
+ # @param local_options [Hash] the options hash which requires a :view. Any
90
+ # additional key value pairs will be exposed during serialization.
91
+ # @return [Hash]
92
+ def hashify(object, view_name:, local_options:)
93
+ raise BlueprinterError, "View '#{view_name}' is not defined" unless view_collection.view?(view_name)
94
+
95
+ object = Blueprinter.configuration.extensions.pre_render(object, self, view_name, local_options)
96
+ prepare_data(object, view_name, local_options)
97
+ end
98
+
99
+ # @deprecated This method is no longer supported, and was not originally intended to be public. This will be removed
100
+ # in the next minor release. If similar functionality is needed, use `.render_as_hash` instead.
101
+ #
102
+ # This is the magic method that converts complex objects into a simple hash
103
+ # ready for JSON conversion.
104
+ #
105
+ # Note: we accept view (public interface) that is in reality a view_name,
106
+ # so we rename it for clarity
107
+ #
108
+ # @api private
109
+ def prepare(object, view_name:, local_options:)
110
+ Blueprinter::Deprecation.report(
111
+ <<~MESSAGE
112
+ The `prepare` method is no longer supported will be removed in the next minor release.
113
+ If similar functionality is needed, use `.render_as_hash` instead.
114
+ MESSAGE
115
+ )
116
+ render_as_hash(object, view_name:, local_options:)
117
+ end
118
+
119
+ private
120
+
121
+ attr_reader :blueprint, :options
122
+
123
+ def prepare_data(object, view_name, local_options)
124
+ if array_like?(object)
125
+ object.map do |obj|
126
+ object_to_hash(
127
+ obj,
128
+ view_name: view_name,
129
+ local_options: local_options
130
+ )
131
+ end
132
+ else
133
+ object_to_hash(
134
+ object,
135
+ view_name: view_name,
136
+ local_options: local_options
137
+ )
138
+ end
139
+ end
140
+
141
+ def object_to_hash(object, view_name:, local_options:)
142
+ result_hash = view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
143
+ next if field.skip?(field.name, object, local_options)
144
+
145
+ value = field.extract(object, local_options.merge(view: view_name))
146
+ next if value.nil? && field.options[:exclude_if_nil]
147
+
148
+ hash[field.name] = value
149
+ end
150
+ view_collection.transformers(view_name).each do |transformer|
151
+ transformer.transform(result_hash, object, local_options)
152
+ end
153
+ result_hash
154
+ end
155
+
156
+ def jsonify(data)
157
+ Blueprinter.configuration.jsonify(data)
158
+ end
159
+
160
+ def apply_root_key(object:, root:)
161
+ return object unless root
162
+ return { root => object } if root.is_a?(String) || root.is_a?(Symbol)
163
+
164
+ raise(Errors::InvalidRoot)
165
+ end
166
+
167
+ def add_metadata(object:, metadata:, root:)
168
+ return object unless metadata
169
+ return object.merge(meta: metadata) if root
170
+
171
+ raise(Errors::MetaRequiresRoot)
172
+ end
173
+
174
+ def build_result(object:, options:)
175
+ view_name = options.fetch(:view, :default) || :default
176
+
177
+ prepared_object = hashify(
178
+ object,
179
+ view_name: view_name,
180
+ local_options: options.except(:view, :root, :meta)
181
+ )
182
+ object_with_root = apply_root_key(
183
+ object: prepared_object,
184
+ root: options[:root]
185
+ )
186
+ add_metadata(
187
+ object: object_with_root,
188
+ metadata: options[:meta],
189
+ root: options[:root]
190
+ )
191
+ end
192
+ end
193
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Blueprinter
4
- VERSION = '1.1.2'
4
+ VERSION = '1.2.1'
5
5
  end
data/lib/blueprinter.rb CHANGED
@@ -11,7 +11,7 @@ module Blueprinter
11
11
  class << self
12
12
  # @return [Configuration]
13
13
  def configuration
14
- @configuration ||= Configuration.new
14
+ @_configuration ||= Configuration.new
15
15
  end
16
16
 
17
17
  def configure
@@ -20,7 +20,7 @@ module Blueprinter
20
20
 
21
21
  # Resets global configuration.
22
22
  def reset_configuration!
23
- @configuration = nil
23
+ @_configuration = nil
24
24
  end
25
25
  end
26
26
  end
@@ -36,7 +36,7 @@ module Blueprinter
36
36
  end
37
37
 
38
38
  def create_blueprint
39
- template 'blueprint.rb', File.join(path, "#{file_path}_blueprint.rb")
39
+ template 'blueprint.erb', File.join(path, "#{file_path}_blueprint.rb")
40
40
  end
41
41
 
42
42
  private
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.1.2
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Procore Technologies, Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-04 00:00:00.000000000 Z
11
+ date: 2025-09-11 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
@@ -34,6 +34,8 @@ files:
34
34
  - lib/blueprinter/empty_types.rb
35
35
  - lib/blueprinter/errors.rb
36
36
  - lib/blueprinter/errors/invalid_blueprint.rb
37
+ - lib/blueprinter/errors/invalid_root.rb
38
+ - lib/blueprinter/errors/meta_requires_root.rb
37
39
  - lib/blueprinter/extension.rb
38
40
  - lib/blueprinter/extensions.rb
39
41
  - lib/blueprinter/extractor.rb
@@ -44,22 +46,22 @@ files:
44
46
  - lib/blueprinter/extractors/public_send_extractor.rb
45
47
  - lib/blueprinter/field.rb
46
48
  - lib/blueprinter/formatters/date_time_formatter.rb
47
- - lib/blueprinter/helpers/base_helpers.rb
48
49
  - lib/blueprinter/helpers/type_helpers.rb
49
50
  - lib/blueprinter/reflection.rb
51
+ - lib/blueprinter/rendering.rb
50
52
  - lib/blueprinter/transformer.rb
51
53
  - lib/blueprinter/version.rb
52
54
  - lib/blueprinter/view.rb
53
55
  - lib/blueprinter/view_collection.rb
54
56
  - lib/generators/blueprinter/blueprint_generator.rb
55
- - lib/generators/blueprinter/templates/blueprint.rb
57
+ - lib/generators/blueprinter/templates/blueprint.erb
56
58
  homepage: https://github.com/procore-oss/blueprinter
57
59
  licenses:
58
60
  - MIT
59
61
  metadata:
60
62
  allowed_push_host: https://rubygems.org
61
63
  rubygems_mfa_required: 'true'
62
- post_install_message:
64
+ post_install_message:
63
65
  rdoc_options: []
64
66
  require_paths:
65
67
  - lib
@@ -67,7 +69,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
69
  requirements:
68
70
  - - ">="
69
71
  - !ruby/object:Gem::Version
70
- version: '3.0'
72
+ version: '3.1'
71
73
  required_rubygems_version: !ruby/object:Gem::Requirement
72
74
  requirements:
73
75
  - - ">="
@@ -75,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
77
  version: '0'
76
78
  requirements: []
77
79
  rubygems_version: 3.4.19
78
- signing_key:
80
+ signing_key:
79
81
  specification_version: 4
80
82
  summary: Simple Fast Declarative Serialization Library
81
83
  test_files: []
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'blueprinter/helpers/type_helpers'
4
- require 'blueprinter/view_collection'
5
-
6
- module Blueprinter
7
- module BaseHelpers
8
- def self.included(base)
9
- base.extend(SingletonMethods)
10
- end
11
-
12
- module SingletonMethods
13
- include TypeHelpers
14
-
15
- private
16
-
17
- def prepare_for_render(object, options)
18
- view_name = options.fetch(:view, :default) || :default
19
- root = options[:root]
20
- meta = options[:meta]
21
- validate_root_and_meta!(root, meta)
22
- prepare(
23
- object,
24
- view_name: view_name,
25
- local_options: options.except(
26
- :view,
27
- :root,
28
- :meta
29
- ),
30
- root: root,
31
- meta: meta
32
- )
33
- end
34
-
35
- def prepare_data(object, view_name, local_options)
36
- if array_like?(object)
37
- object.map do |obj|
38
- object_to_hash(obj,
39
- view_name: view_name,
40
- local_options: local_options)
41
- end
42
- else
43
- object_to_hash(object,
44
- view_name: view_name,
45
- local_options: local_options)
46
- end
47
- end
48
-
49
- def prepend_root_and_meta(data, root, meta)
50
- return data unless root
51
-
52
- ret = { root => data }
53
- meta ? ret.merge!(meta: meta) : ret
54
- end
55
-
56
- def inherited(subclass)
57
- subclass.send(:view_collection).inherit(view_collection)
58
- end
59
-
60
- def object_to_hash(object, view_name:, local_options:)
61
- result_hash = view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
62
- next if field.skip?(field.name, object, local_options)
63
-
64
- value = field.extract(object, local_options)
65
-
66
- next if value.nil? && field.options[:exclude_if_nil]
67
-
68
- hash[field.name] = value
69
- end
70
- view_collection.transformers(view_name).each do |transformer|
71
- transformer.transform(result_hash, object, local_options)
72
- end
73
- result_hash
74
- end
75
-
76
- def validate_root_and_meta!(root, meta)
77
- case root
78
- when String, Symbol
79
- # no-op
80
- when NilClass
81
- raise BlueprinterError, 'meta requires a root to be passed' if meta
82
- else
83
- raise BlueprinterError, 'root should be one of String, Symbol, NilClass'
84
- end
85
- end
86
-
87
- def jsonify(blob)
88
- Blueprinter.configuration.jsonify(blob)
89
- end
90
-
91
- def current_view
92
- @current_view ||= view_collection[:default]
93
- end
94
-
95
- def view_collection
96
- @view_collection ||= ViewCollection.new
97
- end
98
-
99
- def associations(view_name = :default)
100
- view_collection.fields_for(view_name).select { |f| f.options[:association] }
101
- end
102
- end
103
- end
104
- end