XUtils

Blueprinter

Simple, Fast, and Declarative Serialization Library for Ruby.


Test Gem Version Discord

Recent Organization Move

Please change your local remote to pull from this repository:

git remote set-url [previous-remote-name] git@github.com:procore-oss/blueprinter.git

to see the previous upstream remote name, run:

git remote -v

Blueprinter

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.

It heavily relies on the idea of views which, similar to Rails views, are ways of predefining output for data in different contexts.

Documentation

Docs can be found here.

Global Config Setting

Blueprinter.configure do |config|
  config.field_default = "N/A"
  config.association_default = {}
end

Field-level/Association-level Setting

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    field :first_name, default: "N/A"
    association :company, blueprint: CompanyBlueprint, default: {}
  end
end

default_if Sometimes, you may want certain "empty" values to pass through to the default value. Blueprinter provides the ability to treat the following empty types as the default value (or `nil` if no default provided). ### Blueprinter::EMPTY_COLLECTION An empty array or empty active record collection. ### Blueprinter::EMPTY_HASH An empty hash. ### Blueprinter::EMPTY_STRING An empty string or symbol. ### Global Config Setting - if and unless ```ruby Blueprinter.configure do |config| config.if = ->(field_name, obj, _options) { !obj[field_name].nil? } config.unless = ->(field_name, obj, _options) { obj[field_name].nil? } end ``` #### Field-level Setting ```ruby class UserBlueprint < Blueprinter::Base identifier :uuid field :last_name, if: ->(_field_name, user, options) { user.first_name != options[:first_name] } field :age, unless: ->(_field_name, user, _options) { user.age < 18 } end ``` _NOTE:_ The field-level setting overrides the global config setting (for the field) if both are set.
Custom Formatting for Dates and Times To define a custom format for a Date or DateTime field, include the option `datetime_format`. This global or field-level option can be either a string representing the associated `strftime` format, or a Proc which receives the original Date/DateTime object and returns the formatted value. When using a Proc, it is the Proc's responsibility to handle any errors in formatting. #### Global Config Setting - datetime If a global datetime_format is set (either as a string format or a Proc), this option will be invoked and used to format all fields that respond to `strftime`. ```ruby Blueprinter.configure do |config| config.datetime_format = ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i } end ``` #### Example Create a Transform class extending from `Blueprinter::Transformer` ```ruby class DynamicFieldTransformer < Blueprinter::Transformer def transform(hash, object, _options) hash.merge!(object.dynamic_fields) end end ``` ```ruby class User def dynamic_fields case role when :admin { employer: employer, time_in_role: determine_time_in role } when :maintainer { label: label, settings: generate_settings_hash } when :read_only { last_login_at: last_login_at } end end end ``` Then specify the transform to use for the view. ```ruby class UserBlueprint < Blueprinter::Base fields :first_name, :last_name transform DynamicTransformer end ``` #### Transform across views Transformers can be included across views: ```ruby class UserBlueprint < Blueprinter::Base transform DefaultTransformer view :normal do transform ViewTransformer end view :extended do include_view :normal end end ``` Both the `normal` and `extended` views have `DefaultTransformer` and `ViewTransformer` applied. Transformers are executed in a top-down order, so `DefaultTransformer` will be executed first, followed by `ViewTransformer`. #### Global Transforms You can also specify global default transformers. Create one or more transformer classes extending from `Blueprinter::Transformer` and set the `default_transformers` configuration ```ruby class LowerCamelTransformer < Blueprinter::Transformer def transform(hash, _object, _options) hash.transform_keys! { |key| key.to_s.camelize(:lower).to_sym } end end ``` ```ruby Blueprinter.configure do |config| config.default_transformers = [LowerCamelTransformer] end ``` **Note: Any transforms specified on a per-blueprint or per-view level will override the `default_transformers` in the configuration.**
Configurable Extractors Blueprinter gets a given objects' values from the fields definitions using extractor classes. You can substitute your own extractor class globally or per-field. # A Hash of views keyed by name views = WidgetBlueprint.reflections views.keys => [:default, :extended] # Hashes of fields and associations, keyed by name fields = views[:default].fields assoc = views[:default].associations # Get info about a field fields[:description].name fields[:description].display_name fields[:description].options # Get info about an association assoc[:category].name assoc[:category].display_name assoc[:category].blueprint assoc[:category].view assoc[:category].options ```
Extensions Blueprinter offers an extension system to hook into and modify certain behavior. ```ruby Blueprinter.configure do |config| config.extensions << MyExtension.new config.extensions << OtherExtension.new end ``` Extension hooks: * [pre_render](https://github.com/procore-oss/blueprinter/blob/abca9ca8ed23edd65a0f4b5ae43e25b8e27a2afc/lib/blueprinter/extension.rb#L18): Intercept the object before rendering begins Some known extensions are: * [blueprinter-activerecord](https://github.com/procore-oss/blueprinter-activerecord)

Deprecations

When functionality in Blueprinter is invoked, that has been deprecated, the default behavior is to write a deprecation notice to stderror.

However, deprecations can be configured to report at three different levels:

Key Result
:stderr (Default) Deprecations will be written to stderror
:raise Deprecations will be raised as Blueprinter::BlueprinterErrors
:silence Deprecations will be silenced and will not be raised or logged

OJ

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:

require 'oj' # you can skip this if OJ has already been required.

Blueprinter.configure do |config|
  config.generator = Oj # default is JSON
end

Ensure that you have the Oj gem installed in your Gemfile if you haven’t already:

# Gemfile
gem 'oj'

Yajl-ruby

yajl-ruby is a fast and powerful JSON generator/parser. To use yajl-ruby in place of JSON / OJ, use:

require 'yajl' # you can skip this if yajl has already been required.

Blueprinter.configure do |config|
  config.generator = Yajl::Encoder # default is JSON
  config.method = :encode # default is generate
end

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. In this case, JSON.generate is patched to use Yajl::Encoder.encode internally.

Tests

You can run tests with bundle exec rake.

Maintain The Docs

We use Yard for documentation. Here are the following documentation rules:

  • Document all public methods we expect to be utilized by the end developers.
  • Methods that are not set to private due to ruby visibility rule limitations should be marked with @api private.

How to Document

We use Yard for documentation. Here are the following documentation rules:

  • Document all public methods we expect to be utilized by the end developers.
  • Methods that are not set to private due to ruby visibility rule limitations should be marked with @api private.

Releasing a New Version

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.


Articles

  • coming soon...