blueprinter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f69da973430bcbb6be951377ccc7707e2d53228a
4
+ data.tar.gz: 818f307324662a8871d6abc6353cfbf52367cb33
5
+ SHA512:
6
+ metadata.gz: a8ee5758ecba43bb06423d4fbe01ebfcabb918c2980775b75e494f75350e90b8348fd20685b6c0c083d47196b0ec75f99757020d6a9e82af7699964231b6f0dc
7
+ data.tar.gz: 63d4da14d7b9d1144068961c163ef2d6b9a04adbf3e21db33a57e68779d7c580f9c5491571c2154a453bd2c41ee76326dacc5f9f13c990f701a423709aaf74af
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Procore Technologies, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ [![CircleCI](https://circleci.com/gh/procore/blueprinter.svg?style=svg)](https://circleci.com/gh/procore/blueprinter)
2
+
3
+ # Blueprinter
4
+ Blueprinter is a JSON Object Presenter for Ruby that takes business objects and breaks them down into simple hashes and serializes them to JSON. It can be used in Rails in place of other serializers (like JBuilder or ActiveModelSerializers). It is designed to be simple, direct, and performant.
5
+
6
+ It heavily relies on the idea of `views` which, similar to Rails views, are ways of predefining output for data in different contexts.
7
+
8
+ ## Documentation
9
+ !TODO Link to the docs
10
+
11
+ ## Usage
12
+ ### Basic
13
+ If you have an object you would like serialized, simply create a blueprint. Say, for example, you have a User record with the following attributes `[:uuid, :email, :first_name, :last_name, :password, :address]`.
14
+
15
+ You may define a simple blueprint like so:
16
+
17
+ ```ruby
18
+ class UserBlueprint < Blueprinter::Base
19
+ identifier :uuid
20
+
21
+ fields :first_name, :last_name, :email
22
+ end
23
+ ```
24
+
25
+ and then, in your code:
26
+ ```ruby
27
+ puts UserBlueprint.render(user) # Output is a JSON string
28
+ ```
29
+
30
+ And the output would look like:
31
+
32
+ ```json
33
+ {
34
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
35
+ "email": "john.doe@some.fake.email.domain",
36
+ "first_name": "John",
37
+ "last_name": "Doe"
38
+ }
39
+ ```
40
+
41
+ ### Views
42
+ You may define different ouputs by utilizing views:
43
+ ```ruby
44
+ class UserBlueprint < Blueprinter::Base
45
+ identifier :uuid
46
+ field :email, name: :login
47
+
48
+ view :normal do
49
+ fields :first_name, :last_name
50
+ end
51
+
52
+ view :extended do
53
+ include_view :normal
54
+ field :address
55
+ association :projects
56
+ end
57
+ end
58
+ ```
59
+
60
+ Usage:
61
+ ```ruby
62
+ puts UserBlueprint.render(user, view: :extended)
63
+ ```
64
+
65
+ Output:
66
+ ```json
67
+ {
68
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
69
+ "address": "123 Fake St.",
70
+ "first_name": "John",
71
+ "last_name": "Doe",
72
+ "login": "john.doe@some.fake.email.domain"
73
+ }
74
+ ```
75
+
76
+ ### Associations
77
+ You may include associated objects. Say for example, a user has projects:
78
+ ```ruby
79
+ class UserBlueprint < Blueprinter::Base
80
+ identifier :uuid
81
+ field :email, name: :login
82
+
83
+ view :normal do
84
+ fields :first_name, :last_name
85
+ association :projects
86
+ end
87
+ end
88
+ ```
89
+
90
+ Usage:
91
+ ```ruby
92
+ puts UserBlueprint.render(user, view: :extended)
93
+ ```
94
+
95
+ Output:
96
+ ```json
97
+ {
98
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
99
+ "first_name": "John",
100
+ "last_name": "Doe",
101
+ "login": "john.doe@some.fake.email.domain",
102
+ "projects": [
103
+ {
104
+ "uuid": "dca94051-4195-42bc-a9aa-eb99f7723c82",
105
+ "name": "Beach Cleanup"
106
+ },
107
+ {
108
+ "uuid": "eb881bb5-9a51-4d27-8a29-b264c30e6160",
109
+ "name": "Storefront Revamp"
110
+ }
111
+ ]
112
+ }
113
+ ```
114
+
115
+ ### Defining a field directly in the Blueprint
116
+
117
+ You can define a field directly in the Blueprint by passing it a block. This is especially useful if the object does not already have such an attribute or method defined, and you want to define it specifically for use with the Blueprint. For example:
118
+
119
+ ```ruby
120
+ class UserBlueprint < Blueprinter::Base
121
+ identifier :uuid
122
+ field :full_name do |user|
123
+ "#{user.first_name} #{user.last_name}"
124
+ end
125
+ end
126
+ ```
127
+
128
+ Usage:
129
+
130
+ ```ruby
131
+ puts UserBlueprint.render(user)
132
+ ```
133
+
134
+ Output:
135
+
136
+ ```json
137
+ {
138
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
139
+ "full_name": "John Doe"
140
+ }
141
+ ```
142
+
143
+ ### Passing additional properties to `render`
144
+
145
+ `render` takes an options hash which you can pass additional properties, allowing you to utilize those additional properties in the `field` block. For example:
146
+
147
+ ```ruby
148
+ class UserBlueprint < Blueprinter::Base
149
+ identifier :uuid
150
+ field(:company_name) do |_user, options|
151
+ options[:company].name
152
+ end
153
+ end
154
+ ```
155
+
156
+ Usage:
157
+
158
+ ```ruby
159
+ puts UserBlueprint.render(user, company: company)
160
+ ```
161
+
162
+ Output:
163
+
164
+ ```json
165
+ {
166
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
167
+ "company_name": "My Company LLC"
168
+ }
169
+ ```
170
+
171
+ ## Installation
172
+ Add this line to your application's Gemfile:
173
+
174
+ ```ruby
175
+ gem 'blueprinter'
176
+ ```
177
+
178
+ And then execute:
179
+ ```bash
180
+ $ bundle
181
+ ```
182
+
183
+ Or install it yourself as:
184
+ ```bash
185
+ $ gem install blueprinter
186
+ ```
187
+
188
+ You should also have `require 'json'` already in your project if you are not using Rails or if you are not using Oj.
189
+
190
+ ## OJ
191
+
192
+ By default, Blueprinter will be calling `JSON.generate(object)` internally and it expects that you have `require 'json'` already in your project's code. You may use `Oj` to generate in place of `JSON` like so:
193
+
194
+ ```ruby
195
+ require 'oj' # you can skip this if OJ has already been required.
196
+
197
+ Blueprinter.configure do |config|
198
+ config.generator = Oj # default is JSON
199
+ end
200
+ ```
201
+
202
+ Ensure that you have the `Oj` gem installed in your Gemfile if you haven't already:
203
+
204
+ ```ruby
205
+ # Gemfile
206
+ gem 'oj'
207
+ ```
208
+
209
+ ## Documentation
210
+
211
+ We use [Yard](https://yardoc.org/) for documentation. Here are the following
212
+ documentation rules:
213
+
214
+ - Document all public methods we expect to be utilized by the end developers.
215
+ - Methods that are not set to private due to ruby visibility rule limitations should be marked with `@api private`.
216
+
217
+ ## Contributing
218
+ Feel free to browse the issues, converse, and make pull requests. If you need help, first please see if there is already an issue for your problem. Otherwise, go ahead and make a new issue.
219
+
220
+ ### Tests
221
+ You can run tests with `bundle exec rake`.
222
+
223
+ ### Maintain The Docs
224
+ We use Yard for documentation. Here are the following documentation rules:
225
+
226
+ - Document all public methods we expect to be utilized by the end developers.
227
+ - Methods that are not set to private due to ruby visibility rule limitations should be marked with `@api private`.
228
+
229
+ ## License
230
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'rdoc/task'
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
5
+
6
+ begin
7
+ require 'bundler/setup'
8
+ rescue LoadError
9
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
10
+ end
11
+
12
+ RDoc::Task.new(:rdoc) do |rdoc|
13
+ rdoc.rdoc_dir = 'rdoc'
14
+ rdoc.title = 'Blueprinter'
15
+ rdoc.options << '--line-numbers'
16
+ rdoc.rdoc_files.include('README.md')
17
+ rdoc.rdoc_files.include('lib/**/*.rb')
18
+ end
19
+
20
+ RSpec::Core::RakeTask.new(:spec) do |t|
21
+ t.rspec_opts = '--pattern spec/**/*_spec.rb'
22
+ end
23
+
24
+ Rake::TestTask.new(:benchmarks) do |t|
25
+ t.libs << 'spec'
26
+ t.pattern = 'spec/benchmarks/**/*_test.rb'
27
+ t.verbose = false
28
+ end
29
+
30
+ task default: :spec
@@ -0,0 +1,5 @@
1
+ require_relative 'blueprinter/configuration'
2
+ require_relative 'blueprinter/base'
3
+
4
+ module Blueprinter
5
+ end
@@ -0,0 +1,294 @@
1
+ require_relative 'blueprinter_error'
2
+ require_relative 'helpers/active_record_helpers'
3
+ require_relative 'serializer'
4
+ require_relative 'serializers/association_serializer'
5
+ require_relative 'serializers/auto_serializer'
6
+ require_relative 'serializers/block_serializer'
7
+ require_relative 'serializers/hash_serializer'
8
+ require_relative 'serializers/public_send_serializer'
9
+ require_relative 'field'
10
+ require_relative 'view'
11
+ require_relative 'view_collection'
12
+ require_relative 'optimizer'
13
+
14
+ module Blueprinter
15
+ class Base
16
+ include ActiveRecordHelpers
17
+
18
+ # Specify a field or method name used as an identifier. Usually, this is
19
+ # something like :id
20
+ #
21
+ # Note: identifiers are always rendered and considerered their own view,
22
+ # similar to the :default view.
23
+ #
24
+ # @param method [Symbol] the method or field used as an identifier that you
25
+ # want to set for serialization.
26
+ # @param name [Symbol] to rename the identifier key in the JSON
27
+ # output. Defaults to method given.
28
+ # @param serializer [AssociationSerializer,AutoSerializer,BlockSerializer,HashSerializer,PublicSendSerializer]
29
+ # Kind of serializer to use.
30
+ # Either define your own or use Blueprinter's premade serializers.
31
+ # Defaults to AutoSerializer
32
+ #
33
+ # @example Specifying a uuid as an identifier.
34
+ # class UserBlueprint < Blueprinter::Base
35
+ # identifier :uuid
36
+ # # other code
37
+ # end
38
+ #
39
+ # @return [Field] A Field object
40
+ def self.identifier(method, name: method, serializer: AutoSerializer)
41
+ view_collection[:identifier] << Field.new(method, name, serializer)
42
+ end
43
+
44
+ # Specify a field or method name to be included for serialization.
45
+ # Takes a required method and an option.
46
+ #
47
+ # @param method [Symbol] the field or method name you want to include for
48
+ # serialization.
49
+ # @param options [Hash] options to overide defaults.
50
+ # @option options [AssociationSerializer,BlockSerializer,HashSerializer,PublicSendSerializer] :serializer
51
+ # Kind of serializer to use.
52
+ # Either define your own or use Blueprinter's premade serializers. The
53
+ # Default serializer is AutoSerializer
54
+ # @option options [Symbol] :name Use this to rename the method. Useful if
55
+ # if you want your JSON key named differently in the output than your
56
+ # object's field or method name.
57
+ # @yield [Object] The object passed to `render` is also passed to the
58
+ # block.
59
+ #
60
+ # @example Specifying a user's first_name to be serialized.
61
+ # class UserBlueprint < Blueprinter::Base
62
+ # field :first_name
63
+ # # other code
64
+ # end
65
+ #
66
+ # @example Passing a block to be evaluated as the value.
67
+ # class UserBlueprint < Blueprinter::Base
68
+ # field :full_name {|obj| "#{obj.first_name} #{obj.last_name}"}
69
+ # # other code
70
+ # end
71
+ #
72
+ # @return [Field] A Field object
73
+ def self.field(method, options = {}, &block)
74
+ options = if block_given?
75
+ {name: method, serializer: BlockSerializer, block: {method => block}}
76
+ else
77
+ {name: method, serializer: AutoSerializer}
78
+ end.merge(options)
79
+ current_view << Field.new(method,
80
+ options[:name],
81
+ options[:serializer],
82
+ options)
83
+ end
84
+
85
+ # Specify an associated object to be included for serialization.
86
+ # Takes a required method and an option.
87
+ #
88
+ # @param method [Symbol] the association name
89
+ # @param options [Hash] options to overide defaults.
90
+ # @option options [Symbol] :name Use this to rename the association in the
91
+ # JSON output.
92
+ # @option options [Symbol] :view Specify the view to use or fall back to
93
+ # to the :default view.
94
+ #
95
+ # @example Specifying an association
96
+ # class UserBlueprint < Blueprinter::Base
97
+ # # code
98
+ # association :vehicles, view: :extended
99
+ # # code
100
+ # end
101
+ #
102
+ # @return [Field] A Field object
103
+ def self.association(method, options = {})
104
+ name = options.delete(:name) || method
105
+ current_view << Field.new(method,
106
+ name,
107
+ AssociationSerializer,
108
+ options.merge(association: true))
109
+ end
110
+
111
+ # Generates a JSON formatted String.
112
+ # Takes a required object and an optional view.
113
+ #
114
+ # @param object [Object] the Object to serialize upon.
115
+ # @param options [Hash] the options hash which requires a :view. Any
116
+ # additional key value pairs will be exposed during serialization.
117
+ # @option options [Symbol] :view Defaults to :default.
118
+ # The view name that corresponds to the group of
119
+ # fields to be serialized.
120
+ #
121
+ # @example Generating JSON with an extended view
122
+ # post = Post.all
123
+ # Blueprinter::Base.render post, view: :extended
124
+ # # => "[{\"id\":1,\"title\":\"Hello\"},{\"id\":2,\"title\":\"My Day\"}]"
125
+ #
126
+ # @return [String] JSON formatted String
127
+ def self.render(object, options = {})
128
+ view_name = options.delete(:view) || :default
129
+ jsonify(prepare(object, view_name: view_name, local_options: options))
130
+ end
131
+
132
+ # This is the magic method that converts complex objects into a simple hash
133
+ # ready for JSON conversion.
134
+ #
135
+ # Note: we accept view (public interface) that is in reality a view_name,
136
+ # so we rename it for clarity
137
+ #
138
+ # @api private
139
+ def self.prepare(object, view_name:, local_options:)
140
+ unless view_collection.has_view? view_name
141
+ raise BlueprinterError, "View '#{view_name}' is not defined"
142
+ end
143
+ fields = view_collection.fields_for(view_name)
144
+ prepared_object = Optimizer.optimize(object, fields: fields)
145
+ prepared_object = include_associations(prepared_object, view_name: view_name)
146
+ if array_like?(object)
147
+ prepared_object.map do |obj|
148
+ object_to_hash(obj,
149
+ view_name: view_name,
150
+ local_options: local_options)
151
+ end
152
+ else
153
+ object_to_hash(prepared_object,
154
+ view_name: view_name,
155
+ local_options: local_options)
156
+ end
157
+ end
158
+
159
+ # Specify one or more field/method names to be included for serialization.
160
+ # Takes at least one field or method names.
161
+ #
162
+ # @param method [Symbol] the field or method name you want to include for
163
+ # serialization.
164
+ #
165
+ # @example Specifying a user's first_name and last_name to be serialized.
166
+ # class UserBlueprint < Blueprinter::Base
167
+ # fields :first_name, :last_name
168
+ # # other code
169
+ # end
170
+ #
171
+ # @return [Array<Symbol>] an array of field names
172
+ def self.fields(*field_names)
173
+ field_names.each do |field_name|
174
+ current_view << Field.new(field_name, field_name, AutoSerializer)
175
+ end
176
+ end
177
+
178
+ # @api private
179
+ def self.associations(view_name = :default)
180
+ view_collection.fields_for(view_name).select { |f| f.options[:association] }
181
+ end
182
+
183
+ # Specify another view that should be mixed into the current view.
184
+ #
185
+ # @param view_name [Symbol] the view to mix into the current view.
186
+ #
187
+ # @example Including a normal view into an extended view.
188
+ # class UserBlueprint < Blueprinter::Base
189
+ # # other code...
190
+ # view :normal do
191
+ # fields :first_name, :last_name
192
+ # end
193
+ # view :extended do
194
+ # include_view :normal # include fields specified from above.
195
+ # field :description
196
+ # end
197
+ # #=> [:first_name, :last_name, :description]
198
+ # end
199
+ #
200
+ # @return [Array<Symbol>] an array of view names.
201
+ def self.include_view(view_name)
202
+ current_view.include_view(view_name)
203
+ end
204
+
205
+
206
+ # Exclude a field that was mixed into the current view.
207
+ #
208
+ # @param field_name [Symbol] the field to exclude from the current view.
209
+ #
210
+ # @example Excluding a field from being included into the current view.
211
+ # view :normal do
212
+ # fields :position, :company
213
+ # end
214
+ # view :special do
215
+ # include_view :normal
216
+ # field :birthday
217
+ # exclude :position
218
+ # end
219
+ # #=> [:company, :birthday]
220
+ #
221
+ # @return [Array<Symbol>] an array of field names
222
+ def self.exclude(field_name)
223
+ current_view.exclude_field(field_name)
224
+ end
225
+
226
+ # Specify a view and the fields it should have.
227
+ # It accepts a view name and a block. The block should specify the fields.
228
+ #
229
+ # @param view_name [Symbol] the view name
230
+ # @yieldreturn [#fields,#field,#include_view,#exclude] Use this block to
231
+ # specify fields, include fields from other views, or exclude fields.
232
+ #
233
+ # @example Using views
234
+ # view :extended do
235
+ # fields :position, :company
236
+ # include_view :normal
237
+ # exclude :first_name
238
+ # end
239
+ #
240
+ # @return [View] a Blueprinter::View object
241
+ def self.view(view_name)
242
+ @current_view = view_collection[view_name]
243
+ yield
244
+ @current_view = view_collection[:default]
245
+ end
246
+
247
+ private
248
+
249
+ def self.object_to_hash(object, view_name:, local_options:)
250
+ view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
251
+ hash[field.name] = field.serialize(object, local_options)
252
+ end
253
+ end
254
+ private_class_method :object_to_hash
255
+
256
+ def self.include_associations(object, view_name:)
257
+ unless defined?(ActiveRecord::Base) &&
258
+ object.is_a?(ActiveRecord::Base) &&
259
+ object.respond_to?(:klass)
260
+ return object
261
+ end
262
+ # TODO: Do we need to support more than `eager_load` ?
263
+ fields_to_include = associations(view).select { |a|
264
+ a.options[:include] != false
265
+ }.map(&:method)
266
+ if !fields_to_include.empty?
267
+ object.eager_load(*fields_to_include)
268
+ else
269
+ object
270
+ end
271
+ end
272
+ private_class_method :include_associations
273
+
274
+ def self.jsonify(blob)
275
+ Blueprinter.configuration.generator.generate(blob)
276
+ end
277
+ private_class_method :jsonify
278
+
279
+ def self.current_view
280
+ @current_view ||= view_collection[:default]
281
+ end
282
+ private_class_method :current_view
283
+
284
+ def self.view_collection
285
+ @view_collection ||= ViewCollection.new
286
+ end
287
+ private_class_method :view_collection
288
+
289
+ def self.array_like?(object)
290
+ object.is_a?(Array) || active_record_relation?(object)
291
+ end
292
+ private_class_method :array_like?
293
+ end
294
+ end
@@ -0,0 +1,3 @@
1
+ module Blueprinter
2
+ class BlueprinterError < StandardError; end
3
+ end
@@ -0,0 +1,17 @@
1
+ module Blueprinter
2
+ class Configuration
3
+ attr_accessor :generator
4
+
5
+ def initialize
6
+ @generator = JSON
7
+ end
8
+ end
9
+
10
+ def self.configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def self.configure
15
+ yield configuration if block_given?
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # @api private
2
+ class Blueprinter::Field
3
+ attr_reader :method, :name, :serializer, :options
4
+ def initialize(method, name, serializer, options = {})
5
+ @method = method
6
+ @name = name
7
+ @serializer = serializer
8
+ @options = options
9
+ end
10
+
11
+ def serialize(object, local_options)
12
+ serializer.serialize(method, object, local_options, options)
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Blueprinter
2
+ module ActiveRecordHelpers
3
+ def self.included(base)
4
+ base.extend(SingletonMethods)
5
+ end
6
+
7
+ def active_record_relation?(object)
8
+ self.class.active_record_relation?(object)
9
+ end
10
+
11
+ module SingletonMethods
12
+ def active_record_relation?(object)
13
+ !!(defined?(ActiveRecord::Relation) &&
14
+ object.is_a?(ActiveRecord::Relation))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module Blueprinter
2
+ # @api private
3
+ class Optimizer
4
+ include ActiveRecordHelpers
5
+ class << self
6
+ def optimize(object, fields:)
7
+ return object unless active_record_relation?(object)
8
+ select_columns = (active_record_attributes_for(object) &
9
+ fields.map(&:method)) +
10
+ required_lookup_attributes_for(object)
11
+ object.select(*select_columns)
12
+ end
13
+
14
+ private
15
+
16
+ def active_record_attributes_for(object)
17
+ object.klass.column_names.map(&:to_sym)
18
+ end
19
+
20
+ def required_lookup_attributes_for(object)
21
+ # TODO: We may not need all four of these
22
+ lookup_values = (object.includes_values +
23
+ object.preload_values +
24
+ object.joins_values +
25
+ object.eager_load_values).uniq
26
+ lookup_values.map {|value| object.reflections[value.to_s].foreign_key}
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # @api private
2
+ class Blueprinter::Serializer
3
+ def initialize
4
+ end
5
+
6
+ def serialize(field_name, object, local_options, options={})
7
+ fail NotImplementedError, "A serializer must implement #serialize"
8
+ end
9
+
10
+ def self.serialize(field_name, object, local_options, options={})
11
+ self.new.serialize(field_name, object, local_options, options)
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class Blueprinter::AssociationSerializer < Blueprinter::Serializer
2
+ def serialize(association_name, object, local_options, options={})
3
+ if options[:blueprint]
4
+ view = options[:view] || :default
5
+ options[:blueprint].prepare(object.public_send(association_name), view_name: view, local_options: local_options)
6
+ else
7
+ object.public_send(association_name)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module Blueprinter
2
+ class AutoSerializer < Blueprinter::Serializer
3
+ def serialize(field_name, object, local_options, options = {})
4
+ serializer = object.is_a?(Hash) ? HashSerializer : PublicSendSerializer
5
+ serializer.serialize(field_name, object, local_options, options = {})
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class Blueprinter::BlockSerializer < Blueprinter::Serializer
2
+ def serialize(field_name, object, local_options, options = {})
3
+ options[:block][field_name].call(object, local_options)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Blueprinter::HashSerializer < Blueprinter::Serializer
2
+ def serialize(field_name, object, local_options, options = {})
3
+ object[field_name] || object[field_name.to_s]
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Blueprinter::PublicSendSerializer < Blueprinter::Serializer
2
+ def serialize(field_name, object, local_options, options = {})
3
+ object.public_send(field_name)
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Blueprinter
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,29 @@
1
+ module Blueprinter
2
+ # @api private
3
+ class View
4
+ attr_reader :excluded_field_names, :fields, :included_view_names, :name
5
+
6
+ def initialize(name, fields: {}, included_view_names: [], excluded_view_names: [])
7
+ @name = name
8
+ @fields = fields
9
+ @included_view_names = included_view_names
10
+ @excluded_field_names = excluded_view_names
11
+ end
12
+
13
+ def include_view(view_name)
14
+ included_view_names << view_name
15
+ end
16
+
17
+ def exclude_field(field_name)
18
+ excluded_field_names << field_name
19
+ end
20
+
21
+ def <<(field)
22
+ if fields.has_key?(field.name)
23
+ raise BlueprinterError,
24
+ "Field #{field.name} already defined on #{name}"
25
+ end
26
+ fields[field.name] = field
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ module Blueprinter
2
+ # @api private
3
+ class ViewCollection
4
+ attr_reader :views
5
+ def initialize
6
+ @views = {
7
+ identifier: View.new(:identifier),
8
+ default: View.new(:default)
9
+ }
10
+ end
11
+
12
+ def has_view?(view_name)
13
+ views.has_key? view_name
14
+ end
15
+
16
+ def fields_for(view_name)
17
+ identifier_fields + sortable_fields(view_name).values.sort_by(&:name)
18
+ end
19
+
20
+ def [](view_name)
21
+ @views[view_name] ||= View.new(view_name)
22
+ end
23
+
24
+ private
25
+
26
+ def identifier_fields
27
+ views[:identifier].fields.values
28
+ end
29
+
30
+ def sortable_fields(view_name)
31
+ fields = views[:default].fields
32
+ fields = fields.merge(views[view_name].fields)
33
+ views[view_name].included_view_names.each do |included_view_name|
34
+ if view_name != included_view_name
35
+ fields = fields.merge(sortable_fields(included_view_name))
36
+ end
37
+ end
38
+
39
+ views[view_name].excluded_field_names.each do |name|
40
+ fields.delete(name)
41
+ end
42
+
43
+ fields
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ =begin
2
+ module RabbitPainter
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ attr_reader :serializer
9
+ def self.serializer(klass)
10
+ @serializer = klass
11
+ end
12
+ end
13
+ end
14
+ =end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :blueprinter do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blueprinter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Hess
8
+ - Derek Carter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-01-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oj
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: pry
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rails
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 5.1.2
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 5.1.2
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.7'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.7'
70
+ - !ruby/object:Gem::Dependency
71
+ name: sqlite3
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: yard
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 0.9.11
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.9.11
98
+ description: Blueprinter is a JSON Object Presenter for Ruby that takes business objects
99
+ and breaks them down into simple hashes and serializes them to JSON. It can be used
100
+ in Rails in place of other serializers (like JBuilder or ActiveModelSerializers).
101
+ It is designed to be simple, direct, and performant.
102
+ email:
103
+ - adamhess1991@gmail.com
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - MIT-LICENSE
109
+ - README.md
110
+ - Rakefile
111
+ - lib/blueprinter.rb
112
+ - lib/blueprinter/base.rb
113
+ - lib/blueprinter/blueprinter_error.rb
114
+ - lib/blueprinter/configuration.rb
115
+ - lib/blueprinter/field.rb
116
+ - lib/blueprinter/helpers/active_record_helpers.rb
117
+ - lib/blueprinter/optimizer.rb
118
+ - lib/blueprinter/serializer.rb
119
+ - lib/blueprinter/serializers/association_serializer.rb
120
+ - lib/blueprinter/serializers/auto_serializer.rb
121
+ - lib/blueprinter/serializers/block_serializer.rb
122
+ - lib/blueprinter/serializers/hash_serializer.rb
123
+ - lib/blueprinter/serializers/public_send_serializer.rb
124
+ - lib/blueprinter/version.rb
125
+ - lib/blueprinter/view.rb
126
+ - lib/blueprinter/view_collection.rb
127
+ - lib/rabbit_painter.rb
128
+ - lib/tasks/blueprinter_tasks.rake
129
+ homepage: https://github.com/procore/blueprinter
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 2.2.2
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.5.1
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Simple Fast Declarative Serialization Library
153
+ test_files: []