blueprinter 0.6.0 → 0.7.0

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: b1a0e758e71974ab97aac018c58a8fb143cedcbb0cbdfc60c62fb06050defecd
4
- data.tar.gz: 455ff66f83def7e8aad7bb35df752580e664499ac3cb872c6354cacc9b881f9e
3
+ metadata.gz: 7305adc0699ba1c10026b7dbfc2de138d85b7e05f19de91d70fd10069bf274b1
4
+ data.tar.gz: a15a1d19a578a472c3cc91228070ff32441137e57b241571578e72ba89a5f892
5
5
  SHA512:
6
- metadata.gz: f1a697280e9f1da3ab3c59053449c965fc4785c077c6b336291451adc6dbdfa13876989c93ace254ae2545ec9cb0ff4cc5c7d9f64211d34d77a6539217b2c178
7
- data.tar.gz: 8bc59b0805ea6bda87dd948677f8a375c321fc7d0a880882d13e4a34d5b6a844dc6c3916bd9b2913b58c3b349844f8dc86357bdf7a879c35764137c8dff65f9d
6
+ metadata.gz: f93a3442dd563f3f929e8d1964a320823819a2ef8bc4aa292751caa35bd33c2dfe74716e49f38bf856d594ebe12251fa4ca0f9ecde01ea7fe2a564f3c9e2a375
7
+ data.tar.gz: 3ffea695ddd219bd1ec1721853c9a519159d883c31259545440511b8700b82dddc1b9e55595cd886c808f2b09aacf9e47f41e6b65efd7f8034b512c3576d0061
@@ -1,3 +1,9 @@
1
+ ## 0.7.0 - 2018/10/17
2
+
3
+ * [FEATURE] Allow associations to be defined with a block. Please see pr #106. Thanks to @hugopeixoto.
4
+ * [FEATURE] Inherit view definition when using inheritance. Please see pr #105. Thanks to @hugopeixoto.
5
+
6
+
1
7
  ## 0.6.0 - 2018/06/05
2
8
 
3
9
  * [FEATURE] Add `date_time` format as an option to `field`. Please see pr #68. Thanks to @njbbaer.
data/README.md CHANGED
@@ -41,6 +41,30 @@ And the output would look like:
41
41
  }
42
42
  ```
43
43
 
44
+ ### Renaming
45
+
46
+ You can rename the resulting JSON keys in both fields and associations by using the `name` option.
47
+
48
+ ```ruby
49
+ class UserBlueprint < Blueprinter::Base
50
+ identifier :uuid
51
+
52
+ field :email, name: :login
53
+
54
+ association :user_projects, name: :projects
55
+ end
56
+ ```
57
+
58
+ This will result in JSON that looks something like this:
59
+
60
+ ```json
61
+ {
62
+ "uuid": "92a5c732-2874-41e4-98fc-4123cd6cfa86",
63
+ "login": "my@email.com",
64
+ "projects": []
65
+ }
66
+ ```
67
+
44
68
  ### Views
45
69
  You may define different outputs by utilizing views:
46
70
  ```ruby
@@ -162,6 +186,44 @@ Output:
162
186
  }
163
187
  ```
164
188
 
189
+ #### Defining an association directly in the Blueprint
190
+
191
+ You can also pass a block to an association:
192
+
193
+ ```ruby
194
+ class ProjectBlueprint < Blueprinter::Base
195
+ identifier :uuid
196
+ field :name
197
+ end
198
+
199
+ class UserBlueprint < Blueprinter::Base
200
+ identifier :uuid
201
+
202
+ association :projects, blueprint: ProjectBlueprint do |user|
203
+ user.projects + user.company.projects
204
+ end
205
+ end
206
+ ```
207
+
208
+ Usage:
209
+
210
+ ```ruby
211
+ puts UserBlueprint.render(user)
212
+ ```
213
+
214
+ Output:
215
+
216
+ ```json
217
+ {
218
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
219
+ "projects": [
220
+ {"uuid": "b426a1e6-ac41-45ab-bfef-970b9a0b4289", "name": "query-console"},
221
+ {"uuid": "5bd84d6c-4fd2-4e36-ae31-c137e39be542", "name": "blueprinter"},
222
+ {"uuid": "785f5cd4-7d8d-4779-a6dd-ec5eab440eff", "name": "uncontrollable"}
223
+ ]
224
+ }
225
+ ```
226
+
165
227
  ### Passing additional properties to `render`
166
228
 
167
229
  `render` takes an options hash which you can pass additional properties, allowing you to utilize those additional properties in the `field` block. For example:
@@ -190,6 +252,18 @@ Output:
190
252
  }
191
253
  ```
192
254
 
255
+ ### Conditional field
256
+
257
+ `field` supports `:if` and `:unless` options argument that can be used to serialize the field conditionally.
258
+
259
+ ```ruby
260
+ class UserBlueprint < Blueprinter::Base
261
+ identifier :uuid
262
+ field :last_name, if: ->(user, options) { user.first_name != options[:first_name] }
263
+ field :age, unless: ->(user, _options) { user.age < 18 }
264
+ end
265
+ ```
266
+
193
267
  ### Custom formatting for dates and times
194
268
  To define a custom format for a Date or DateTime field, include the option `datetime_format` with the associated `strptime` format.
195
269
 
@@ -267,5 +341,8 @@ We use Yard for documentation. Here are the following documentation rules:
267
341
  - Document all public methods we expect to be utilized by the end developers.
268
342
  - Methods that are not set to private due to ruby visibility rule limitations should be marked with `@api private`.
269
343
 
344
+ ### Releasing a New Version
345
+ To release a new version, change the version number in `version.rb`, and update the `CHANGELOG.md`. Finally, maintainers need to run `bundle exec rake release`, which will automatically create a git tag for the version, push git commits and tags to Github, and push the `.gem` file to rubygems.org.
346
+
270
347
  ## License
271
348
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
18
18
  end
19
19
 
20
20
  RSpec::Core::RakeTask.new(:spec) do |t|
21
- t.rspec_opts = '--pattern spec/**/*_spec.rb'
21
+ t.rspec_opts = '--pattern spec/**/*_spec.rb --warnings'
22
22
  end
23
23
 
24
24
  Rake::TestTask.new(:benchmarks) do |t|
@@ -1,4 +1,3 @@
1
- require_relative 'blueprinter/configuration'
2
1
  require_relative 'blueprinter/base'
3
2
 
4
3
  module Blueprinter
@@ -1,5 +1,5 @@
1
1
  require_relative 'blueprinter_error'
2
- require_relative 'helpers/active_record_helpers'
2
+ require_relative 'configuration'
3
3
  require_relative 'extractor'
4
4
  require_relative 'extractors/association_extractor'
5
5
  require_relative 'extractors/auto_extractor'
@@ -7,6 +7,7 @@ require_relative 'extractors/block_extractor'
7
7
  require_relative 'extractors/hash_extractor'
8
8
  require_relative 'extractors/public_send_extractor'
9
9
  require_relative 'field'
10
+ require_relative 'helpers/active_record_helpers'
10
11
  require_relative 'view'
11
12
  require_relative 'view_collection'
12
13
 
@@ -36,10 +37,14 @@ module Blueprinter
36
37
  # end
37
38
  #
38
39
  # @return [Field] A Field object
39
- def self.identifier(method, name: method, extractor: AutoExtractor)
40
+ def self.identifier(method, name: method, extractor: AutoExtractor.new)
40
41
  view_collection[:identifier] << Field.new(method, name, extractor, self)
41
42
  end
42
43
 
44
+ def self.inherited(subclass)
45
+ subclass.send(:view_collection).inherit(view_collection)
46
+ end
47
+
43
48
  # Specify a field or method name to be included for serialization.
44
49
  # Takes a required method and an option.
45
50
  #
@@ -92,9 +97,9 @@ module Blueprinter
92
97
  # @return [Field] A Field object
93
98
  def self.field(method, options = {}, &block)
94
99
  options = if block_given?
95
- {name: method, extractor: BlockExtractor, block: {method => block}}
100
+ {name: method, extractor: BlockExtractor.new, block: block}
96
101
  else
97
- {name: method, extractor: AutoExtractor}
102
+ {name: method, extractor: AutoExtractor.new}
98
103
  end.merge(options)
99
104
  current_view << Field.new(method,
100
105
  options[:name],
@@ -114,6 +119,8 @@ module Blueprinter
114
119
  # JSON output.
115
120
  # @option options [Symbol] :view Specify the view to use or fall back to
116
121
  # to the :default view.
122
+ # @yield [Object] The object passed to `render` is also passed to the
123
+ # block.
117
124
  #
118
125
  # @example Specifying an association
119
126
  # class UserBlueprint < Blueprinter::Base
@@ -122,15 +129,29 @@ module Blueprinter
122
129
  # # code
123
130
  # end
124
131
  #
132
+ # @example Passing a block to be evaluated as the value.
133
+ # class UserBlueprint < Blueprinter::Base
134
+ # association :vehicles, blueprint: VehiclesBlueprint do |user|
135
+ # user.vehicles + user.company.vehicles
136
+ # end
137
+ # end
138
+ #
125
139
  # @return [Field] A Field object
126
- def self.association(method, options = {})
140
+ def self.association(method, options = {}, &block)
127
141
  raise BlueprinterError, 'blueprint required' unless options[:blueprint]
128
142
  name = options.delete(:name) || method
143
+
144
+ options = if block_given?
145
+ options.merge(extractor: BlockExtractor.new, block: block)
146
+ else
147
+ options.merge(extractor: AutoExtractor.new)
148
+ end
149
+
129
150
  current_view << Field.new(method,
130
- name,
131
- AssociationExtractor,
132
- self,
133
- options.merge(association: true))
151
+ name,
152
+ AssociationExtractor.new,
153
+ self,
154
+ options.merge(association: true))
134
155
  end
135
156
 
136
157
  # Generates a JSON formatted String.
@@ -215,7 +236,7 @@ module Blueprinter
215
236
  # @return [Array<Symbol>] an array of field names
216
237
  def self.fields(*field_names)
217
238
  field_names.each do |field_name|
218
- current_view << Field.new(field_name, field_name, AutoExtractor, self)
239
+ field(field_name)
219
240
  end
220
241
  end
221
242
 
@@ -1,9 +1,6 @@
1
1
  # @api private
2
2
  module Blueprinter
3
3
  class Extractor
4
- def initialize
5
- end
6
-
7
4
  def extract(field_name, object, local_options, options={})
8
5
  fail NotImplementedError, "An Extractor must implement #extract"
9
6
  end
@@ -1,8 +1,8 @@
1
1
  module Blueprinter
2
2
  class AssociationExtractor < Extractor
3
3
  def extract(association_name, object, local_options, options={})
4
- value = object.public_send(association_name)
5
- return (value || options[:default]) if value.nil?
4
+ value = options[:extractor].extract(association_name, object, local_options, options)
5
+ return options[:default] if value.nil?
6
6
  view = options[:view] || :default
7
7
  options[:blueprint].prepare(value, view_name: view, local_options: local_options)
8
8
  end
@@ -1,7 +1,12 @@
1
1
  module Blueprinter
2
2
  class AutoExtractor < Extractor
3
+ def initialize
4
+ @hash_extractor = HashExtractor.new
5
+ @public_send_extractor = PublicSendExtractor.new
6
+ end
7
+
3
8
  def extract(field_name, object, local_options, options = {})
4
- extractor = object.is_a?(Hash) ? HashExtractor : PublicSendExtractor
9
+ extractor = object.is_a?(Hash) ? @hash_extractor : @public_send_extractor
5
10
  extraction = extractor.extract(field_name, object, local_options, options)
6
11
  options.key?(:datetime_format) ? format_datetime(extraction, options[:datetime_format]) : extraction
7
12
  end
@@ -1,7 +1,7 @@
1
1
  module Blueprinter
2
2
  class BlockExtractor < Extractor
3
3
  def extract(field_name, object, local_options, options = {})
4
- options[:block][field_name].call(object, local_options)
4
+ options[:block].call(object, local_options)
5
5
  end
6
6
  end
7
7
  end
@@ -21,13 +21,13 @@ class Blueprinter::Field
21
21
  private
22
22
 
23
23
  def if_callable
24
- return @if_callable unless @if_callable.nil?
25
- @if_callable ||= callable_from(:if)
24
+ return @if_callable if defined?(@if_callable)
25
+ @if_callable = callable_from(:if)
26
26
  end
27
27
 
28
28
  def unless_callable
29
- return @unless_callable unless @unless_callable.nil?
30
- @unless_callable ||= callable_from(:unless)
29
+ return @unless_callable if defined?(@unless_callable)
30
+ @unless_callable = callable_from(:unless)
31
31
  end
32
32
 
33
33
  def callable_from(option_name)
@@ -1,3 +1,3 @@
1
1
  module Blueprinter
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
@@ -10,6 +10,20 @@ module Blueprinter
10
10
  @excluded_field_names = excluded_view_names
11
11
  end
12
12
 
13
+ def inherit(view)
14
+ view.fields.values.each do |field|
15
+ self << field
16
+ end
17
+
18
+ view.included_view_names.each do |view_name|
19
+ include_view(view_name)
20
+ end
21
+
22
+ view.excluded_field_names.each do |field_name|
23
+ exclude_field(field_name)
24
+ end
25
+ end
26
+
13
27
  def include_view(view_name)
14
28
  included_view_names << view_name
15
29
  end
@@ -19,10 +33,6 @@ module Blueprinter
19
33
  end
20
34
 
21
35
  def <<(field)
22
- if fields.has_key?(field.name)
23
- raise BlueprinterError,
24
- "Field #{field.name} already defined on #{name}"
25
- end
26
36
  fields[field.name] = field
27
37
  end
28
38
  end
@@ -9,6 +9,12 @@ module Blueprinter
9
9
  }
10
10
  end
11
11
 
12
+ def inherit(view_collection)
13
+ view_collection.views.each do |view_name, view|
14
+ self[view_name].inherit(view)
15
+ end
16
+ end
17
+
12
18
  def has_view?(view_name)
13
19
  views.has_key? view_name
14
20
  end
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.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hess
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-06-05 00:00:00.000000000 Z
12
+ date: 2018-10-17 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: factory_bot
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: nokogiri
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -54,7 +68,21 @@ dependencies:
54
68
  - !ruby/object:Gem::Version
55
69
  version: '0'
56
70
  - !ruby/object:Gem::Dependency
57
- name: rails
71
+ name: rake
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: activerecord
58
86
  requirement: !ruby/object:Gem::Requirement
59
87
  requirements:
60
88
  - - "~>"
@@ -159,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
187
  version: '0'
160
188
  requirements: []
161
189
  rubyforge_project:
162
- rubygems_version: 2.7.3
190
+ rubygems_version: 2.7.6
163
191
  signing_key:
164
192
  specification_version: 4
165
193
  summary: Simple Fast Declarative Serialization Library