blueprinter-rb 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,1020 @@
1
+ [![Gem Version](https://badge.fury.io/rb/blueprinter-rb.svg)](https://badge.fury.io/rb/blueprinter-rb)
2
+ ![Tests](https://github.com/blueprinter-ruby/blueprinter/actions/workflows/tests.yml/badge.svg)
3
+ ![rubocop](https://github.com/blueprinter-ruby/blueprinter/actions/workflows/rubocop.yml/badge.svg)
4
+
5
+ # Blueprinter
6
+ 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.
7
+
8
+ It heavily relies on the idea of `views` which, similar to Rails views, are ways of predefining output for data in different contexts.
9
+
10
+ ## Documentation
11
+ Docs can be found [here](http://www.rubydoc.info/gems/blueprinter-rb).
12
+
13
+ ## Usage
14
+ <details>
15
+ <summary>Basic</summary>
16
+
17
+ 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]`.
18
+
19
+ You may define a simple blueprint like so:
20
+
21
+ ```ruby
22
+ class UserBlueprint < Blueprinter::Base
23
+ identifier :uuid
24
+
25
+ fields :first_name, :last_name, :email
26
+ end
27
+ ```
28
+
29
+ and then, in your code:
30
+ ```ruby
31
+ puts UserBlueprint.render(user) # Output is a JSON string
32
+ ```
33
+
34
+ And the output would look like:
35
+
36
+ ```json
37
+ {
38
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
39
+ "email": "john.doe@some.fake.email.domain",
40
+ "first_name": "John",
41
+ "last_name": "Doe"
42
+ }
43
+ ```
44
+ </details>
45
+
46
+
47
+ <details>
48
+ <summary>Collections</summary>
49
+
50
+
51
+ You can also pass a collection object or an array to the render method.
52
+
53
+ ```ruby
54
+ puts UserBlueprint.render(User.all)
55
+ ```
56
+
57
+ This will result in JSON that looks something like this:
58
+
59
+ ```json
60
+ [
61
+ {
62
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
63
+ "email": "john.doe@some.fake.email.domain",
64
+ "first_name": "John",
65
+ "last_name": "Doe"
66
+ },
67
+ {
68
+ "uuid": "733f0758-8f21-4719-875f-743af262c3ec",
69
+ "email": "john.doe.2@some.fake.email.domain",
70
+ "first_name": "John",
71
+ "last_name": "Doe 2"
72
+ }
73
+ ]
74
+ ```
75
+
76
+
77
+ You can also configure other classes to be treated like collections. For example, if you are using Mongoid, you can configure it to treat `Mongoid::Criteria` objects as collections:
78
+
79
+ ```ruby
80
+ Blueprinter.configure do |config|
81
+ config.custom_array_like_classes = [Mongoid::Criteria]
82
+ end
83
+ ```
84
+
85
+ Or if you wanted it to treat the `Set` class as a collection:
86
+
87
+ ```ruby
88
+ Blueprinter.configure do |config|
89
+ config.custom_array_like_classes = [Set]
90
+ end
91
+ ```
92
+
93
+ ---
94
+ </details>
95
+
96
+
97
+ <details>
98
+ <summary>Renaming</summary>
99
+
100
+
101
+ You can rename the resulting JSON keys in both fields and associations by using the `name` option.
102
+
103
+ ```ruby
104
+ class UserBlueprint < Blueprinter::Base
105
+ identifier :uuid
106
+
107
+ field :email, name: :login
108
+
109
+ association :user_projects, name: :projects
110
+ end
111
+ ```
112
+
113
+ This will result in JSON that looks something like this:
114
+
115
+ ```json
116
+ {
117
+ "uuid": "92a5c732-2874-41e4-98fc-4123cd6cfa86",
118
+ "login": "my@email.com",
119
+ "projects": []
120
+ }
121
+ ```
122
+
123
+ </details>
124
+
125
+
126
+ <details>
127
+ <summary>Views</summary>
128
+
129
+
130
+ You may define different outputs by utilizing views:
131
+ ```ruby
132
+ class UserBlueprint < Blueprinter::Base
133
+ identifier :uuid
134
+ field :email, name: :login
135
+
136
+ view :normal do
137
+ fields :first_name, :last_name
138
+ end
139
+
140
+ view :extended do
141
+ include_view :normal
142
+ field :address
143
+ association :projects
144
+ end
145
+ end
146
+ ```
147
+ A view can include fields from another view by utilizing `include_view` and `include_views`.
148
+
149
+ Usage:
150
+ ```ruby
151
+ puts UserBlueprint.render(user, view: :extended)
152
+ ```
153
+
154
+ Output:
155
+ ```json
156
+ {
157
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
158
+ "address": "123 Fake St.",
159
+ "first_name": "John",
160
+ "last_name": "Doe",
161
+ "login": "john.doe@some.fake.email.domain"
162
+ }
163
+ ```
164
+
165
+ </details>
166
+
167
+
168
+ <details>
169
+ <summary>Identifiers</summary>
170
+
171
+
172
+ `identifier`s are used to specify a field or method name used as an identifier. Usually, this is something like `:id`.
173
+
174
+ Example:
175
+ ```rb
176
+ class UserBlueprint < Blueprinter::Base
177
+ identifier :uuid
178
+ end
179
+ ```
180
+
181
+ Blueprinter `identifier`s have a few properties that set them apart from `field`s.
182
+
183
+ 1. Identifiers are **always** rendered and considered their own view (the `:identifier` view).
184
+ 2. When rendering, identifier fields are always sorted first, before other fields.
185
+
186
+ If either of the above two developer conveniences are not desired, you can simply create your identifier fields as regular `field`s.
187
+
188
+
189
+ </details>
190
+
191
+
192
+ <details>
193
+ <summary>Root</summary>
194
+
195
+
196
+ You can also optionally pass in a root key to wrap your resulting json in:
197
+ ```ruby
198
+ class UserBlueprint < Blueprinter::Base
199
+ identifier :uuid
200
+ field :email, name: :login
201
+
202
+ view :normal do
203
+ fields :first_name, :last_name
204
+ end
205
+ end
206
+ ```
207
+
208
+ Usage:
209
+ ```ruby
210
+ puts UserBlueprint.render(user, view: :normal, root: :user)
211
+ ```
212
+
213
+ Output:
214
+ ```json
215
+ {
216
+ "user": {
217
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
218
+ "first_name": "John",
219
+ "last_name": "Doe",
220
+ "login": "john.doe@some.fake.email.domain"
221
+ }
222
+ }
223
+ ```
224
+
225
+ </details>
226
+
227
+
228
+ <details>
229
+ <summary>Meta Attributes</summary>
230
+
231
+
232
+ You can additionally add meta-data to the json as well:
233
+ ```ruby
234
+ class UserBlueprint < Blueprinter::Base
235
+ identifier :uuid
236
+ field :email, name: :login
237
+
238
+ view :normal do
239
+ fields :first_name, :last_name
240
+ end
241
+ end
242
+ ```
243
+
244
+ Usage:
245
+ ```ruby
246
+ json = UserBlueprint.render(user, view: :normal, root: :user, meta: {links: [
247
+ 'https://app.mydomain.com',
248
+ 'https://alternate.mydomain.com'
249
+ ]})
250
+ puts json
251
+ ```
252
+
253
+ Output:
254
+ ```json
255
+ {
256
+ "user": {
257
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
258
+ "first_name": "John",
259
+ "last_name": "Doe",
260
+ "login": "john.doe@some.fake.email.domain"
261
+ },
262
+ "meta": {
263
+ "links": [
264
+ "https://app.mydomain.com",
265
+ "https://alternate.mydomain.com"
266
+ ]
267
+ }
268
+ }
269
+ ```
270
+ _NOTE:_ For meta attributes, a [root](#root) is mandatory.
271
+
272
+ </details>
273
+
274
+
275
+ <details>
276
+ <summary>Exclude Fields</summary>
277
+
278
+
279
+ You can specifically choose to exclude certain fields for specific views
280
+ ```ruby
281
+ class UserBlueprint < Blueprinter::Base
282
+ identifier :uuid
283
+ field :email, name: :login
284
+
285
+ view :normal do
286
+ fields :first_name, :last_name
287
+ end
288
+
289
+ view :extended do
290
+ include_view :normal
291
+ field :address
292
+ exclude :last_name
293
+ end
294
+ end
295
+ ```
296
+
297
+ Usage:
298
+ ```ruby
299
+ puts UserBlueprint.render(user, view: :extended)
300
+ ```
301
+
302
+ Output:
303
+ ```json
304
+ {
305
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
306
+ "address": "123 Fake St.",
307
+ "first_name": "John",
308
+ "login": "john.doe@some.fake.email.domain"
309
+ }
310
+ ```
311
+
312
+ Use `excludes` to exclude multiple fields at once inline.
313
+
314
+ ```ruby
315
+ class UserBlueprint < Blueprinter::Base
316
+ identifier :uuid
317
+ field :email, name: :login
318
+
319
+ view :normal do
320
+ fields :age, :first_name, :last_name,
321
+ end
322
+
323
+ view :extended do
324
+ include_view :normal
325
+ field :address
326
+ excludes :age, :last_name
327
+ end
328
+ end
329
+ ```
330
+
331
+ </details>
332
+
333
+
334
+ <details>
335
+ <summary>Associations</summary>
336
+
337
+
338
+ You may include associated objects. Say for example, a user has projects:
339
+ ```ruby
340
+ class ProjectBlueprint < Blueprinter::Base
341
+ identifier :uuid
342
+ field :name
343
+ end
344
+
345
+ class UserBlueprint < Blueprinter::Base
346
+ identifier :uuid
347
+ field :email, name: :login
348
+
349
+ view :normal do
350
+ fields :first_name, :last_name
351
+ association :projects, blueprint: ProjectBlueprint
352
+ end
353
+ end
354
+ ```
355
+
356
+ Usage:
357
+ ```ruby
358
+ puts UserBlueprint.render(user, view: :normal)
359
+ ```
360
+
361
+ Output:
362
+ ```json
363
+ {
364
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
365
+ "first_name": "John",
366
+ "last_name": "Doe",
367
+ "login": "john.doe@some.fake.email.domain",
368
+ "projects": [
369
+ {
370
+ "uuid": "dca94051-4195-42bc-a9aa-eb99f7723c82",
371
+ "name": "Beach Cleanup"
372
+ },
373
+ {
374
+ "uuid": "eb881bb5-9a51-4d27-8a29-b264c30e6160",
375
+ "name": "Storefront Revamp"
376
+ }
377
+ ]
378
+ }
379
+ ```
380
+
381
+ It is also possible to pass options from one Blueprint to another via an association.
382
+ For example:
383
+ ```ruby
384
+ class VehicleBlueprint < Blueprinter::Base
385
+ identifier :uuid
386
+ field :full_name do |vehicle, options|
387
+ "#{vehicle.model} #{options[:trim]}"
388
+ end
389
+ end
390
+
391
+ class DriverBlueprint < Blueprinter::Base
392
+ identifier :uuid
393
+
394
+ view :normal do
395
+ fields :first_name, :last_name
396
+ association :vehicles, blueprint: VehicleBlueprint, options: { trim: 'LX' }
397
+ end
398
+ end
399
+ ```
400
+
401
+ </details>
402
+
403
+
404
+ <details>
405
+ <summary>Default Association/Field Option</summary>
406
+
407
+
408
+ By default, an association or field that evaluates to `nil` is serialized as `nil`. A default serialized value can be specified as an option on the association or field for cases when the association/field could potentially evaluate to `nil`. You can also specify a global `field_default` or `association_default` in the Blueprinter config which will be used for all fields/associations that evaluate to nil.
409
+
410
+ #### Global Config Setting
411
+ ```ruby
412
+ Blueprinter.configure do |config|
413
+ config.field_default = "N/A"
414
+ config.association_default = {}
415
+ end
416
+ ```
417
+
418
+ #### Field-level/Association-level Setting
419
+ ```ruby
420
+ class UserBlueprint < Blueprinter::Base
421
+ identifier :uuid
422
+
423
+ view :normal do
424
+ field :first_name, default: "N/A"
425
+ association :company, blueprint: CompanyBlueprint, default: {}
426
+ end
427
+ end
428
+ ```
429
+
430
+ </details>
431
+
432
+
433
+ <details>
434
+ <summary>default_if</summary>
435
+
436
+
437
+ Sometimes, you may want certain "empty" values to pass through to the default value.
438
+ Blueprinter provides the ability to treat the following empty types as the default value (or `nil` if no default provided).
439
+
440
+ #### Blueprinter::EMPTY_COLLECTION
441
+ An empty array or empty active record collection.
442
+
443
+ #### Blueprinter::EMPTY_HASH
444
+ An empty hash.
445
+
446
+ #### Blueprinter::EMPTY_STRING
447
+ An empty string or symbol.
448
+
449
+ #### Field-level/Association-level Setting
450
+ ```ruby
451
+ class UserBlueprint < Blueprinter::Base
452
+ identifier :uuid
453
+
454
+ view :normal do
455
+ # If first_name is an empty string, it will become "N/A"
456
+ field :first_name, default_if: Blueprinter::EMPTY_STRING, default: "N/A"
457
+ # If the projects association collection is empty, it will become nil
458
+ association :projects, blueprint: ProjectBlueprint, default_if: Blueprinter::EMPTY_COLLECTION
459
+ end
460
+ end
461
+ ```
462
+
463
+ </details>
464
+
465
+
466
+ <details>
467
+ <summary>Supporting Dynamic Blueprints For Associations</summary>
468
+
469
+
470
+ When defining an association, we can dynamically evaluate the blueprint. This comes in handy when adding polymorphic associations, by allowing reuse of existing blueprints.
471
+ ```ruby
472
+ class Task < ActiveRecord::Base
473
+ belongs_to :taskable, polymorphic: true
474
+ end
475
+
476
+ class Project < ActiveRecord::Base
477
+ has_many :tasks, as: :taskable
478
+
479
+ def blueprint
480
+ ProjectBlueprint
481
+ end
482
+ end
483
+
484
+ class TaskBlueprint < Blueprinter::Base
485
+ identifier :uuid
486
+
487
+ view :normal do
488
+ field :title, default: "N/A"
489
+ association :taskable, blueprint: ->(taskable) {taskable.blueprint}, default: {}
490
+ end
491
+ end
492
+ ```
493
+ _NOTE:_ `taskable.blueprint` should return a valid Blueprint class. Currently, `has_many` is not supported because of the very nature of polymorphic associations.
494
+
495
+ </details>
496
+
497
+
498
+ <details>
499
+ <summary>Defining A Field Directly In The Blueprint</summary>
500
+
501
+
502
+ 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. This is done by passing `field` a block. The block also yields the object and any options that were passed from `render`. For example:
503
+
504
+ ```ruby
505
+ class UserBlueprint < Blueprinter::Base
506
+ identifier :uuid
507
+ field :full_name do |user, options|
508
+ "#{options[:title_prefix]} #{user.first_name} #{user.last_name}"
509
+ end
510
+ end
511
+ ```
512
+
513
+ Usage:
514
+
515
+ ```ruby
516
+ puts UserBlueprint.render(user, title_prefix: "Mr")
517
+ ```
518
+
519
+ Output:
520
+
521
+ ```json
522
+ {
523
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
524
+ "full_name": "Mr John Doe"
525
+ }
526
+ ```
527
+
528
+ </details>
529
+
530
+
531
+ <details>
532
+ <summary>Defining An Identifier Directly In The Blueprint</summary>
533
+
534
+
535
+ You can also pass a block to an identifier:
536
+
537
+ ```ruby
538
+ class UserBlueprint < Blueprinter::Base
539
+ identifier :uuid do |user, options|
540
+ options[:current_user].anonymize(user.uuid)
541
+ end
542
+ end
543
+ ```
544
+
545
+ Usage:
546
+
547
+ ```ruby
548
+ puts UserBlueprint.render(user, current_user: current_user)
549
+ ```
550
+
551
+ Output:
552
+
553
+ ```json
554
+ {
555
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
556
+ }
557
+ ```
558
+
559
+ </details>
560
+
561
+
562
+ <details>
563
+ <summary>Defining An Association Directly In The Blueprint</summary>
564
+
565
+
566
+ You can also pass a block to an association:
567
+
568
+ ```ruby
569
+ class ProjectBlueprint < Blueprinter::Base
570
+ identifier :uuid
571
+ field :name
572
+ end
573
+
574
+ class UserBlueprint < Blueprinter::Base
575
+ identifier :uuid
576
+
577
+ association :projects, blueprint: ProjectBlueprint do |user, options|
578
+ user.projects + options[:draft_projects]
579
+ end
580
+ end
581
+ ```
582
+
583
+ Usage:
584
+
585
+ ```ruby
586
+ puts UserBlueprint.render(user, draft_projects: Project.where(draft: true))
587
+ ```
588
+
589
+ Output:
590
+
591
+ ```json
592
+ {
593
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
594
+ "projects": [
595
+ {"uuid": "b426a1e6-ac41-45ab-bfef-970b9a0b4289", "name": "query-console"},
596
+ {"uuid": "5bd84d6c-4fd2-4e36-ae31-c137e39be542", "name": "blueprinter"},
597
+ {"uuid": "785f5cd4-7d8d-4779-a6dd-ec5eab440eff", "name": "uncontrollable"}
598
+ ]
599
+ }
600
+ ```
601
+
602
+ </details>
603
+
604
+
605
+ <details>
606
+ <summary>Passing Additional Properties To #render</summary>
607
+
608
+
609
+ `render` takes an options hash which you can pass additional properties, allowing you to utilize those additional properties in the `field` block. For example:
610
+
611
+ ```ruby
612
+ class UserBlueprint < Blueprinter::Base
613
+ identifier :uuid
614
+ field(:company_name) do |_user, options|
615
+ options[:company].name
616
+ end
617
+ end
618
+ ```
619
+
620
+ Usage:
621
+
622
+ ```ruby
623
+ puts UserBlueprint.render(user, company: company)
624
+ ```
625
+
626
+ Output:
627
+
628
+ ```json
629
+ {
630
+ "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
631
+ "company_name": "My Company LLC"
632
+ }
633
+ ```
634
+
635
+ </details>
636
+
637
+
638
+ <details>
639
+ <summary>Conditional Fields</summary>
640
+
641
+
642
+ Both the `field` and the global Blueprinter Configuration supports `:if` and `:unless` options that can be used to serialize fields conditionally.
643
+
644
+ #### Global Config Setting
645
+ ```ruby
646
+ Blueprinter.configure do |config|
647
+ config.if = ->(field_name, obj, _options) { !obj[field_name].nil? }
648
+ config.unless = ->(field_name, obj, _options) { obj[field_name].nil? }
649
+ end
650
+ ```
651
+
652
+ #### Field-level Setting
653
+ ```ruby
654
+ class UserBlueprint < Blueprinter::Base
655
+ identifier :uuid
656
+ field :last_name, if: ->(_field_name, user, options) { user.first_name != options[:first_name] }
657
+ field :age, unless: ->(_field_name, user, _options) { user.age < 18 }
658
+ end
659
+ ```
660
+
661
+ _NOTE:_ The field-level setting overrides the global config setting (for the field) if both are set.
662
+
663
+ </details>
664
+
665
+
666
+ <details>
667
+ <summary>Custom Formatting for Dates and Times</summary>
668
+
669
+
670
+ To define a custom format for a Date or DateTime field, include the option `datetime_format`.
671
+ This global or field-level option can be either a string representing the associated `strftime` format,
672
+ or a Proc which receives the original Date/DateTime object and returns the formatted value.
673
+ When using a Proc, it is the Proc's responsibility to handle any errors in formatting.
674
+
675
+
676
+ #### Global Config Setting
677
+ If a global datetime_format is set (either as a string format or a Proc), this option will be
678
+ invoked and used to format all fields that respond to `strftime`.
679
+ ```ruby
680
+ Blueprinter.configure do |config|
681
+ config.datetime_format = ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i }
682
+ end
683
+ ```
684
+
685
+ #### Field-level Setting
686
+ Usage (String Option):
687
+ ```ruby
688
+ class UserBlueprint < Blueprinter::Base
689
+ identifier :name
690
+ field :birthday, datetime_format: "%m/%d/%Y"
691
+ end
692
+ ```
693
+
694
+ Output:
695
+ ```json
696
+ {
697
+ "name": "John Doe",
698
+ "birthday": "03/04/1994"
699
+ }
700
+ ```
701
+
702
+ Usage (Proc Option):
703
+ ```ruby
704
+ class UserBlueprint < Blueprinter::Base
705
+ identifier :name
706
+ field :birthday, datetime_format: ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i }
707
+ end
708
+ ```
709
+
710
+ Output:
711
+ ```json
712
+ {
713
+ "name": "John Doe",
714
+ "birthday": 762739200
715
+ }
716
+ ```
717
+
718
+ _NOTE:_ The field-level setting overrides the global config setting (for the field) if both are set.
719
+
720
+ </details>
721
+
722
+
723
+ <details>
724
+ <summary>Transform Classes</summary>
725
+
726
+
727
+ Blueprinter provides the ability to specify `transform`s on views, which enable further
728
+ processing and transforming of resulting view field hashes prior to serialization.
729
+
730
+ Use `transform` to specify one transformer to be included for serialization.
731
+ A transformer is a class, extending `Blueprinter::Transformer` and implementing the `transform` method.
732
+ Whatever is returned from this `transform` method will end up being the resulting hash passed to serialization.
733
+
734
+ #### Example
735
+
736
+ Create a Transform class extending from `Blueprinter::Transformer`
737
+ ```ruby
738
+ class DynamicFieldTransformer < Blueprinter::Transformer
739
+ def transform(hash, object, options)
740
+ hash.merge!(object.dynamic_fields)
741
+ end
742
+ end
743
+ ```
744
+
745
+ ```ruby
746
+ class User
747
+ def custom_columns
748
+ self.dynamic_fields #which is an array of some columns
749
+ end
750
+
751
+ def custom_fields
752
+ custom_columns.each_with_object({}){|col,result| result[col] = self.send(col)}
753
+ end
754
+ end
755
+ ```
756
+
757
+ Then specify the transform to use for the view.
758
+ ```ruby
759
+ class UserBlueprint < Blueprinter::Base
760
+ fields :first_name, :last_name
761
+ transform DynamicTransformer
762
+ end
763
+ ```
764
+
765
+ #### Global Transforms
766
+
767
+ You can also specify global default transformers. Create one or more transformer classes extending from `Blueprinter::Transformer` and set the `default_transformers` configuration
768
+ ```ruby
769
+ class LowerCamelTransformer < Blueprinter::Transformer
770
+ def transform(hash, _object, _options)
771
+ hash.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
772
+ end
773
+ end
774
+ ```
775
+
776
+ ```ruby
777
+ Blueprinter.configure do |config|
778
+ config.default_transformers = [LowerCamelTransformer]
779
+ end
780
+ ```
781
+
782
+ **Note: Any transforms specified on a per-blueprint or per-view level will override the `default_transformers` in the configuration.**
783
+
784
+ </details>
785
+
786
+ <details>
787
+ <summary>Configurable Extractors</summary>
788
+
789
+
790
+ Blueprinter gets a given objects' values from the fields definitions using extractor classes. You can substitute your own extractor class globally or per-field.
791
+
792
+ #### Examples
793
+
794
+ For a specific kind of field, create an extractor class extending from `Blueprinter::Extractor`
795
+ ```ruby
796
+ class MyFieldExtractor < Blueprinter::Extractor
797
+ def extract(_field_name, _object, _local_options, _options={})
798
+ # process your obscure_object
799
+ _object.clarified
800
+ end
801
+ end
802
+ ```
803
+
804
+ ```ruby
805
+ class MysteryBlueprint < Blueprinter::Base
806
+ field :obscure_object, extractor: MyFieldExtractor
807
+ end
808
+ ```
809
+
810
+ For a global default, create an extractor class extending from `Blueprinter::AutoExtractor` and set the `extractor_default` configuration
811
+ ```ruby
812
+ class MyAutoExtractor < Blueprinter::AutoExtractor
813
+ def initialize
814
+ super
815
+ @my_field_extractor = MyFieldExtractor.new
816
+ end
817
+ def extractor(object, options)
818
+ # dispatch to any class AutoExtractor can, plus more
819
+ if detect_obscurity(object)
820
+ @my_field_extractor
821
+ else
822
+ super
823
+ end
824
+ end
825
+ end
826
+ ```
827
+
828
+ ```ruby
829
+ Blueprinter.configure do |config|
830
+ config.extractor_default = MyAutoExtractor
831
+ end
832
+ ```
833
+
834
+ </details>
835
+
836
+ <details>
837
+ <summary>Sorting Fields</summary>
838
+
839
+
840
+ By default the response sorts the keys by name. If you want the fields to be sorted in the order of definition, use the below configuration option.
841
+
842
+ Usage:
843
+
844
+ ```ruby
845
+ Blueprinter.configure do |config|
846
+ config.sort_fields_by = :definition
847
+ end
848
+ ```
849
+
850
+ ```ruby
851
+ class UserBlueprint < Blueprinter::Base
852
+ identifier :name
853
+ field :email
854
+ field :birthday, datetime_format: "%m/%d/%Y"
855
+ end
856
+ ```
857
+
858
+ Output:
859
+ ```json
860
+ {
861
+ "name": "John Doe",
862
+ "email": "john.doe@some.fake.email.domain",
863
+ "birthday": "03/04/1994"
864
+ }
865
+ ```
866
+
867
+ </details>
868
+
869
+
870
+ <details>
871
+ <summary>Deprecations</summary>
872
+
873
+
874
+ When functionality in Blueprinter is invoked, that has been deprecated, the default behavior is to
875
+ write a deprecation notice to stderror.
876
+
877
+ However, deprecations can be configured to report at three different levels:
878
+
879
+ | Key | Result |
880
+ |:-----------------:|:---------------------------------------------------------------:|
881
+ | `:stderr` (Default) | Deprecations will be written to stderror |
882
+ | `:raise` | Deprecations will be raised as `Blueprinter::BlueprinterError`s |
883
+ | `:silence` | Deprecations will be silenced and will not be raised or logged |
884
+
885
+ ### Example:
886
+ ```ruby
887
+ Blueprinter.configure do |config|
888
+ config.deprecations = :raise
889
+ end
890
+ ```
891
+
892
+ </details>
893
+
894
+
895
+ <details>
896
+ <summary>render_as_hash</summary>
897
+
898
+
899
+ Same as `render`, returns a Ruby Hash.
900
+
901
+ Usage:
902
+
903
+ ```ruby
904
+ puts UserBlueprint.render_as_hash(user, company: company)
905
+ ```
906
+
907
+ Output:
908
+
909
+ ```ruby
910
+ {
911
+ uuid: "733f0758-8f21-4719-875f-262c3ec743af",
912
+ company_name: "My Company LLC"
913
+ }
914
+ ```
915
+
916
+ </details>
917
+
918
+
919
+ <details>
920
+ <summary>render_as_json</summary>
921
+
922
+
923
+ Same as `render`, returns a Ruby Hash JSONified. This will call JSONify all keys and values.
924
+
925
+ Usage:
926
+
927
+ ```ruby
928
+ puts UserBlueprint.render_as_json(user, company: company)
929
+ ```
930
+
931
+ Output:
932
+
933
+ ```ruby
934
+ {
935
+ "uuid" => "733f0758-8f21-4719-875f-262c3ec743af",
936
+ "company_name" => "My Company LLC"
937
+ }
938
+ ```
939
+
940
+ </details>
941
+
942
+
943
+ ## Installation
944
+ Add this line to your application's Gemfile:
945
+
946
+ ```ruby
947
+ gem 'blueprinter-rb'
948
+ ```
949
+
950
+ And then execute:
951
+ ```bash
952
+ $ bundle
953
+ ```
954
+
955
+ Or install it yourself as:
956
+ ```bash
957
+ $ gem install blueprinter-rb
958
+ ```
959
+
960
+ You should also have `require 'json'` already in your project if you are not using Rails or if you are not using Oj.
961
+
962
+ ## OJ
963
+
964
+ 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:
965
+
966
+ ```ruby
967
+ require 'oj' # you can skip this if OJ has already been required.
968
+
969
+ Blueprinter.configure do |config|
970
+ config.generator = Oj # default is JSON
971
+ end
972
+ ```
973
+
974
+ Ensure that you have the `Oj` gem installed in your Gemfile if you haven't already:
975
+
976
+ ```ruby
977
+ # Gemfile
978
+ gem 'oj'
979
+ ```
980
+
981
+ ## Yajl-ruby
982
+
983
+ [yajl-ruby](https://github.com/brianmario/yajl-ruby) is a fast and powerful JSON generator/parser. To use `yajl-ruby` in place of `JSON / OJ`, use:
984
+
985
+ ```ruby
986
+ require 'yajl' # you can skip this if yajl has already been required.
987
+
988
+ Blueprinter.configure do |config|
989
+ config.generator = Yajl::Encoder # default is JSON
990
+ config.method = :encode # default is generate
991
+ end
992
+ ```
993
+
994
+ _NOTE:_ You should be doing this only if you aren't using `yajl-ruby` through the JSON API by requiring `yajl/json_gem`. More details [here](https://github.com/brianmario/yajl-ruby#json-gem-compatibility-api). In this case, `JSON.generate` is patched to use `Yajl::Encoder.encode` internally.
995
+
996
+ ## Contributing
997
+ 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.
998
+
999
+ ### Tests
1000
+ You can run tests with `bundle exec rake`.
1001
+
1002
+ ### Maintain The Docs
1003
+ We use Yard for documentation. Here are the following documentation rules:
1004
+
1005
+ - Document all public methods we expect to be utilized by the end developers.
1006
+ - Methods that are not set to private due to ruby visibility rule limitations should be marked with `@api private`.
1007
+
1008
+ ## How to Document
1009
+
1010
+ We use [Yard](https://yardoc.org/) for documentation. Here are the following
1011
+ documentation rules:
1012
+
1013
+ - Document all public methods we expect to be utilized by the end developers.
1014
+ - Methods that are not set to private due to ruby visibility rule limitations should be marked with `@api private`.
1015
+
1016
+ ### Releasing a New Version
1017
+ 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.
1018
+
1019
+ ## License
1020
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).