blueprinter 0.5.0 → 0.6.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: 72efd8dd2ec9f959aa0a4ab79db6aca9a8654ea7a66474524fdfb49277fbd315
4
- data.tar.gz: b04bc7182d997999f0bf30babe1abb5ad9bd8f35206cc0648f00cae4026d6900
3
+ metadata.gz: b1a0e758e71974ab97aac018c58a8fb143cedcbb0cbdfc60c62fb06050defecd
4
+ data.tar.gz: 455ff66f83def7e8aad7bb35df752580e664499ac3cb872c6354cacc9b881f9e
5
5
  SHA512:
6
- metadata.gz: af5cd971e59e741c3edf95a5778f2462c679769bf4e95ba37f725fd8508f765bc296d5fd0412c8cbb1cbee41b213bd5f7a0c2b46f3c3b9ec63fb02c82acfac7b
7
- data.tar.gz: 418d5b106f804d9e11dede74cc89cf03c9734a0b15ef31a5cf8554b2ba0fd9e7e3515db3e3b15f2fee2e21c484c962cae03967bcd55990e9a5690fc04f50bdc2
6
+ metadata.gz: f1a697280e9f1da3ab3c59053449c965fc4785c077c6b336291451adc6dbdfa13876989c93ace254ae2545ec9cb0ff4cc5c7d9f64211d34d77a6539217b2c178
7
+ data.tar.gz: 8bc59b0805ea6bda87dd948677f8a375c321fc7d0a880882d13e4a34d5b6a844dc6c3916bd9b2913b58c3b349844f8dc86357bdf7a879c35764137c8dff65f9d
@@ -1,3 +1,9 @@
1
+ ## 0.6.0 - 2018/06/05
2
+
3
+ * [FEATURE] Add `date_time` format as an option to `field`. Please see pr #68. Thanks to @njbbaer.
4
+ * [FEATURE] Add conditional field support `:unless` and `:if` as an option to `field`. Please see pr #86. Thanks to @ojab.
5
+ * [BUGFIX] Fix case where miscellaneous options were not being passed through the `AutoExtractor`. See pr #83.
6
+
1
7
  ## 0.5.0 - 2018/05/15
2
8
 
3
9
  * [FEATURE] Add `default` option to `association` which will be used as the serialized value instead of `null` when the association evaluates to null.
data/README.md CHANGED
@@ -42,7 +42,7 @@ And the output would look like:
42
42
  ```
43
43
 
44
44
  ### Views
45
- You may define different ouputs by utilizing views:
45
+ You may define different outputs by utilizing views:
46
46
  ```ruby
47
47
  class UserBlueprint < Blueprinter::Base
48
48
  identifier :uuid
@@ -190,6 +190,25 @@ Output:
190
190
  }
191
191
  ```
192
192
 
193
+ ### Custom formatting for dates and times
194
+ To define a custom format for a Date or DateTime field, include the option `datetime_format` with the associated `strptime` format.
195
+
196
+ Usage:
197
+ ```ruby
198
+ class UserBlueprint < Blueprinter::Base
199
+ identifier :name
200
+ field :birthday, datetime_format: "%m/%d/%Y"
201
+ end
202
+ ```
203
+
204
+ Output:
205
+ ```json
206
+ {
207
+ "name": "John Doe",
208
+ "birthday": "03/04/1994"
209
+ }
210
+ ```
211
+
193
212
  ## Installation
194
213
  Add this line to your application's Gemfile:
195
214
 
@@ -37,7 +37,7 @@ module Blueprinter
37
37
  #
38
38
  # @return [Field] A Field object
39
39
  def self.identifier(method, name: method, extractor: AutoExtractor)
40
- view_collection[:identifier] << Field.new(method, name, extractor)
40
+ view_collection[:identifier] << Field.new(method, name, extractor, self)
41
41
  end
42
42
 
43
43
  # Specify a field or method name to be included for serialization.
@@ -53,6 +53,16 @@ module Blueprinter
53
53
  # @option options [Symbol] :name Use this to rename the method. Useful if
54
54
  # if you want your JSON key named differently in the output than your
55
55
  # object's field or method name.
56
+ # @option options [String] :datetime_format Format Date or DateTime object
57
+ # with given strftime formatting
58
+ # @option options [Symbol,Proc] :if Specifies a method, proc or string to
59
+ # call to determine if the field should be included (e.g.
60
+ # `if: :include_first_name?, or if: Proc.new { |user, options| options[:current_user] == user }).
61
+ # The method, proc or string should return or evaluate to a true or false value.
62
+ # @option options [Symbol,Proc] :unless Specifies a method, proc or string
63
+ # to call to determine if the field should be included (e.g.
64
+ # `unless: :include_first_name?, or unless: Proc.new { |user, options| options[:current_user] != user }).
65
+ # The method, proc or string should return or evaluate to a true or false value.
56
66
  # @yield [Object] The object passed to `render` is also passed to the
57
67
  # block.
58
68
  #
@@ -68,6 +78,17 @@ module Blueprinter
68
78
  # # other code
69
79
  # end
70
80
  #
81
+ # @example Passing an if proc and unless method..
82
+ # class UserBlueprint < Blueprinter::Base
83
+ # def skip_first_name?(user, options)
84
+ # user.first_name == options[:first_name]
85
+ # end
86
+ #
87
+ # field :first_name, unless: :skip_first_name?
88
+ # field :last_name, if: ->(user, options) { user.first_name != options[:first_name] }
89
+ # # other code
90
+ # end
91
+ #
71
92
  # @return [Field] A Field object
72
93
  def self.field(method, options = {}, &block)
73
94
  options = if block_given?
@@ -78,6 +99,7 @@ module Blueprinter
78
99
  current_view << Field.new(method,
79
100
  options[:name],
80
101
  options[:extractor],
102
+ self,
81
103
  options)
82
104
  end
83
105
 
@@ -107,6 +129,7 @@ module Blueprinter
107
129
  current_view << Field.new(method,
108
130
  name,
109
131
  AssociationExtractor,
132
+ self,
110
133
  options.merge(association: true))
111
134
  end
112
135
 
@@ -163,7 +186,6 @@ module Blueprinter
163
186
  unless view_collection.has_view? view_name
164
187
  raise BlueprinterError, "View '#{view_name}' is not defined"
165
188
  end
166
- fields = view_collection.fields_for(view_name)
167
189
  prepared_object = include_associations(object, view_name: view_name)
168
190
  if array_like?(object)
169
191
  prepared_object.map do |obj|
@@ -193,7 +215,7 @@ module Blueprinter
193
215
  # @return [Array<Symbol>] an array of field names
194
216
  def self.fields(*field_names)
195
217
  field_names.each do |field_name|
196
- current_view << Field.new(field_name, field_name, AutoExtractor)
218
+ current_view << Field.new(field_name, field_name, AutoExtractor, self)
197
219
  end
198
220
  end
199
221
 
@@ -270,6 +292,7 @@ module Blueprinter
270
292
 
271
293
  def self.object_to_hash(object, view_name:, local_options:)
272
294
  view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
295
+ next if field.skip?(object, local_options)
273
296
  hash[field.name] = field.extract(object, local_options)
274
297
  end
275
298
  end
@@ -2,7 +2,16 @@ module Blueprinter
2
2
  class AutoExtractor < Extractor
3
3
  def extract(field_name, object, local_options, options = {})
4
4
  extractor = object.is_a?(Hash) ? HashExtractor : PublicSendExtractor
5
- extractor.extract(field_name, object, local_options, options = {})
5
+ extraction = extractor.extract(field_name, object, local_options, options)
6
+ options.key?(:datetime_format) ? format_datetime(extraction, options[:datetime_format]) : extraction
7
+ end
8
+
9
+ private
10
+
11
+ def format_datetime(datetime, format)
12
+ datetime.strftime(format)
13
+ rescue NoMethodError
14
+ raise BlueprinterError, 'Cannot format invalid DateTime object'
6
15
  end
7
16
  end
8
17
  end
@@ -1,14 +1,44 @@
1
1
  # @api private
2
2
  class Blueprinter::Field
3
- attr_reader :method, :name, :extractor, :options
4
- def initialize(method, name, extractor, options = {})
3
+ attr_reader :method, :name, :extractor, :options, :blueprint
4
+ def initialize(method, name, extractor, blueprint, options = {})
5
5
  @method = method
6
6
  @name = name
7
7
  @extractor = extractor
8
+ @blueprint = blueprint
8
9
  @options = options
9
10
  end
10
11
 
11
12
  def extract(object, local_options)
12
13
  extractor.extract(method, object, local_options, options)
13
14
  end
15
+
16
+ def skip?(object, local_options)
17
+ return true if if_callable && !if_callable.call(object, local_options)
18
+ unless_callable && unless_callable.call(object, local_options)
19
+ end
20
+
21
+ private
22
+
23
+ def if_callable
24
+ return @if_callable unless @if_callable.nil?
25
+ @if_callable ||= callable_from(:if)
26
+ end
27
+
28
+ def unless_callable
29
+ return @unless_callable unless @unless_callable.nil?
30
+ @unless_callable ||= callable_from(:unless)
31
+ end
32
+
33
+ def callable_from(option_name)
34
+ return false unless options.key?(option_name)
35
+
36
+ tmp = options.fetch(option_name)
37
+ case tmp
38
+ when Proc then tmp
39
+ when Symbol then blueprint.method(tmp)
40
+ else
41
+ raise ArgumentError, "#{tmp.class} is passed to :#{option_name}"
42
+ end
43
+ end
14
44
  end
@@ -1,3 +1,3 @@
1
1
  module Blueprinter
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  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.5.0
4
+ version: 0.6.0
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: 2018-05-15 00:00:00.000000000 Z
12
+ date: 2018-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri