Simple, Fast, and Declarative Serialization Library for Ruby

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.

Usage

Basic


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].

You may define a simple blueprint like so:

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  fields :first_name, :last_name, :email
end

and then, in your code:

puts UserBlueprint.render(user) # Output is a JSON string

And the output would look like:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "email": "john.doe@some.fake.email.domain",
  "first_name": "John",
  "last_name": "Doe"
}

Collections


You can also pass a collection object or an array to the render method.

puts UserBlueprint.render(User.all)

This will result in JSON that looks something like this:

[
  {
    "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
    "email": "john.doe@some.fake.email.domain",
    "first_name": "John",
    "last_name": "Doe"
  },
  {
    "uuid": "733f0758-8f21-4719-875f-743af262c3ec",
    "email": "john.doe.2@some.fake.email.domain",
    "first_name": "John",
    "last_name": "Doe 2"
  }
]

Renaming


You can rename the resulting JSON keys in both fields and associations by using the name option.

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  field :email, name: :login

  association :user_projects, name: :projects
end

This will result in JSON that looks something like this:

{
  "uuid": "92a5c732-2874-41e4-98fc-4123cd6cfa86",
  "login": "my@email.com",
  "projects": []
}

Views


You may define different outputs by utilizing views:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end

  view :extended do
    include_view :normal
    field :address
    association :projects
  end
end

A view can include fields from another view by utilizing include_view and include_views.

Usage:

puts UserBlueprint.render(user, view: :extended)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "address": "123 Fake St.",
  "first_name": "John",
  "last_name": "Doe",
  "login": "john.doe@some.fake.email.domain"
}

Identifiers


identifiers are used to specify a field or method name used as an identifier. Usually, this is something like :id.

Example:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
end

Blueprinter identifiers have a few properties that set them apart from fields.

  1. Identifiers are always rendered and considered their own view (the :identifier view).
  2. When rendering, identifier fields are always sorted first, before other fields.

If either of the above two developer conveniences are not desired, you can simply create your identifier fields as regular fields.


Root


You can also optionally pass in a root key to wrap your resulting json in:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end
end

Usage:

puts UserBlueprint.render(user, view: :normal, root: :user)

Output:

{
  "user": {
    "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
    "first_name": "John",
    "last_name": "Doe",
    "login": "john.doe@some.fake.email.domain"
  }
}

Meta Attributes


You can additionally add meta-data to the json as well:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end
end

Usage:

json = UserBlueprint.render(user, view: :normal, root: :user, meta: {links: [
  'https://app.mydomain.com',
  'https://alternate.mydomain.com'
]})
puts json

Output:

{
  "user": {
    "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
    "first_name": "John",
    "last_name": "Doe",
    "login": "john.doe@some.fake.email.domain"
  },
  "meta": {
    "links": [
      "https://app.mydomain.com",
      "https://alternate.mydomain.com"
    ]
  }
}

NOTE: For meta attributes, a root is mandatory.


Exclude Fields


You can specifically choose to exclude certain fields for specific views

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end

  view :extended do
    include_view :normal
    field :address
    exclude :last_name
  end
end

Usage:

puts UserBlueprint.render(user, view: :extended)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "address": "123 Fake St.",
  "first_name": "John",
  "login": "john.doe@some.fake.email.domain"
}

Use excludes to exclude multiple fields at once inline.

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :age, :first_name, :last_name,
  end

  view :extended do
    include_view :normal
    field :address
    excludes :age, :last_name
  end
end

Associations


You may include associated objects. Say for example, a user has projects:

class ProjectBlueprint < Blueprinter::Base
  identifier :uuid
  field :name
end

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
    association :projects, blueprint: ProjectBlueprint
  end
end

Usage:

puts UserBlueprint.render(user, view: :normal)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "first_name": "John",
  "last_name": "Doe",
  "login": "john.doe@some.fake.email.domain",
  "projects": [
    {
      "uuid": "dca94051-4195-42bc-a9aa-eb99f7723c82",
      "name": "Beach Cleanup"
    },
    {
      "uuid": "eb881bb5-9a51-4d27-8a29-b264c30e6160",
      "name": "Storefront Revamp"
    }
  ]
}

It is also possible to pass options from one Blueprint to another via an association. For example:

class VehicleBlueprint < Blueprinter::Base
  identifier :uuid
  field :full_name do |vehicle, options|
    "#{vehicle.model} #{options[:trim]}"
  end
end

class DriverBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    fields :first_name, :last_name
    association :vehicles, blueprint: VehicleBlueprint, options: { trim: 'LX' }
  end
end

Default Association/Field Option


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.

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.

Field-level/Association-level Setting

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    # If first_name is an empty string, it will become "N/A"
    field :first_name, default_if: Blueprinter::EmptyString, default: "N/A"
    # If the projects association collection is empty, it will become nil
    association :projects, blueprint: ProjectBlueprint, default_if: Blueprinter::EmptyCollection
  end
end

Supporting Dynamic Blueprints For Associations


When defining an association, we can dynamically evaluate the blueprint. This comes in handy when adding polymorphic associations, by allowing reuse of existing blueprints.

class Task < ActiveRecord::Base
  belongs_to :taskable, polymorphic: true
end

class Project < ActiveRecord::Base
  has_many :tasks, as: :taskable

  def blueprint
    ProjectBlueprint
  end
end

class TaskBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    field :title, default: "N/A"
    association :taskable, blueprint: ->(taskable) {taskable.blueprint}, default: {}
  end
end

NOTE: taskable.blueprint should return a valid Blueprint class. Currently, has_many is not supported because of the very nature of polymorphic associations.


Defining A Field Directly In The Blueprint


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:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :full_name do |user, options|
    "#{options[:title_prefix]} #{user.first_name} #{user.last_name}"
  end
end

Usage:

puts UserBlueprint.render(user, title_prefix: "Mr")

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "full_name": "Mr John Doe"
}

Defining An Identifier Directly In The Blueprint


You can also pass a block to an identifier:

class UserBlueprint < Blueprinter::Base
  identifier :uuid do |user, options|
    options[:current_user].anonymize(user.uuid)
  end
end

Usage:

puts UserBlueprint.render(user, current_user: current_user)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
}

Defining An Association Directly In The Blueprint


You can also pass a block to an association:

class ProjectBlueprint < Blueprinter::Base
  identifier :uuid
  field :name
end

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  association :projects, blueprint: ProjectBlueprint do |user, options|
    user.projects + options[:draft_projects]
  end
end

Usage:

puts UserBlueprint.render(user, draft_projects: Project.where(draft: true))

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "projects": [
    {"uuid": "b426a1e6-ac41-45ab-bfef-970b9a0b4289", "name": "query-console"},
    {"uuid": "5bd84d6c-4fd2-4e36-ae31-c137e39be542", "name": "blueprinter"},
    {"uuid": "785f5cd4-7d8d-4779-a6dd-ec5eab440eff", "name": "uncontrollable"}
  ]
}

Passing Additional Properties To #render


render takes an options hash which you can pass additional properties, allowing you to utilize those additional properties in the field block. For example:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field(:company_name) do |_user, options|
    options[:company].name
  end
end

Usage:

puts UserBlueprint.render(user, company: company)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "company_name": "My Company LLC"
}

Conditional Fields


Both the field and the global Blueprinter Configuration supports :if and :unless options that can be used to serialize fields conditionally.

Global Config Setting

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

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

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.

Blueprinter.configure do |config|
  config.datetime_format = ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i }
end

Field-level Setting

Usage (String Option):

class UserBlueprint < Blueprinter::Base
  identifier :name
  field :birthday, datetime_format: "%m/%d/%Y"
end

Output:

{
  "name": "John Doe",
  "birthday": "03/04/1994"
}

Usage (Proc Option):

class UserBlueprint < Blueprinter::Base
  identifier :name
  field :birthday, datetime_format: ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i }
end

Output:

{
  "name": "John Doe",
  "birthday": 762739200
}

NOTE: The field-level setting overrides the global config setting (for the field) if both are set.


Transform Classes


Blueprinter provides the ability to specify transforms on views, which enable further processing and transforming of resulting view field hashes prior to serialization.

Use transform to specify one transformer to be included for serialization. A transformer is a class, extending Blueprinter::Transformer and implementing the transform method. Whatever is returned from this transform method will end up being the resulting hash passed to serialization.

Example

Create a Transform class extending from Blueprinter::Transformer

class DynamicFieldTransformer < Blueprinter::Transformer
  def transform(hash, object, options)
    hash.merge!(object.dynamic_fields)
  end
end
class User
  def custom_columns
    self.dynamic_fields #which is an array of some columns
  end

  def custom_fields
    custom_columns.each_with_object({}){|col,result|  result[col] = self.send(col)}
  end
end

Then specify the transform to use for the view.

class UserBlueprint < Blueprinter::Base
  fields :first_name, :last_name
  transform DynamicTransformer
end

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

class LowerCamelTransformer < Blueprinter::Transformer
  def transform(hash, _object, _options)
    hash.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
  end
end
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.

Examples

For a specific kind of field, create an extractor class extending from Blueprinter::Extractor

class MyFieldExtractor < Blueprinter::Extractor
  def extract(_field_name, _object, _local_options, _options={})
    # process your obscure_object
    _object.clarified
  end
end
class MysteryBlueprint < Blueprinter::Base
  field :obscure_object, extractor: MyFieldExtractor
end

For a global default, create an extractor class extending from Blueprinter::AutoExtractor and set the extractor_default configuration

class MyAutoExtractor < Blueprinter::AutoExtractor
  def initialize
    super
    @my_field_extractor = MyFieldExtractor.new
  end
  def extractor(object, options)
    # dispatch to any class AutoExtractor can, plus more
    if detect_obscurity(object)
      @my_field_extractor
    else
      super
    end
  end
end
Blueprinter.configure do |config|
  config.extractor_default = MyAutoExtractor
end

Sorting Fields


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.

Usage:

Blueprinter.configure do |config|
  config.sort_fields_by = :definition
end
class UserBlueprint < Blueprinter::Base
  identifier :name
  field :email
  field :birthday, datetime_format: "%m/%d/%Y"
end

Output:

{
  "name": "John Doe",
  "email": "john.doe@some.fake.email.domain",
  "birthday": "03/04/1994"
}

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:

KeyResult
:stderr (Default)Deprecations will be written to stderror
:raiseDeprecations will be raised as Blueprinter::BlueprinterErrors
:silenceDeprecations will be silenced and will not be raised or logged

Example:

Blueprinter.configure do |config|
  config.deprecations = :raise
end

render_as_hash


Same as render, returns a Ruby Hash.

Usage:

puts UserBlueprint.render_as_hash(user, company: company)

Output:

{
  uuid: "733f0758-8f21-4719-875f-262c3ec743af",
  company_name: "My Company LLC"
}

render_as_json


Same as render, returns a Ruby Hash JSONified. This will call JSONify all keys and values.

Usage:

puts UserBlueprint.render_as_json(user, company: company)

Output:

{
  "uuid" => "733f0758-8f21-4719-875f-262c3ec743af",
  "company_name" => "My Company LLC"
}

Installation

Add this line to your application's Gemfile:

gem 'blueprinter'

And then execute:

$ bundle

Or install it yourself as:

$ gem install blueprinter

You should also have require 'json' already in your project if you are not using Rails or if you are not using Oj.

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.

Contributing

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.

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.

License

The gem is available as open source under the terms of the MIT License.


Author: procore
Source code: https://github.com/procore/blueprinter
License: MIT license

#ruby   #ruby-on-rails 

What is GEEK

Buddha Community

Simple, Fast, and Declarative Serialization Library for Ruby

Simple, Fast, and Declarative Serialization Library for Ruby

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.

Usage

Basic


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].

You may define a simple blueprint like so:

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  fields :first_name, :last_name, :email
end

and then, in your code:

puts UserBlueprint.render(user) # Output is a JSON string

And the output would look like:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "email": "john.doe@some.fake.email.domain",
  "first_name": "John",
  "last_name": "Doe"
}

Collections


You can also pass a collection object or an array to the render method.

puts UserBlueprint.render(User.all)

This will result in JSON that looks something like this:

[
  {
    "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
    "email": "john.doe@some.fake.email.domain",
    "first_name": "John",
    "last_name": "Doe"
  },
  {
    "uuid": "733f0758-8f21-4719-875f-743af262c3ec",
    "email": "john.doe.2@some.fake.email.domain",
    "first_name": "John",
    "last_name": "Doe 2"
  }
]

Renaming


You can rename the resulting JSON keys in both fields and associations by using the name option.

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  field :email, name: :login

  association :user_projects, name: :projects
end

This will result in JSON that looks something like this:

{
  "uuid": "92a5c732-2874-41e4-98fc-4123cd6cfa86",
  "login": "my@email.com",
  "projects": []
}

Views


You may define different outputs by utilizing views:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end

  view :extended do
    include_view :normal
    field :address
    association :projects
  end
end

A view can include fields from another view by utilizing include_view and include_views.

Usage:

puts UserBlueprint.render(user, view: :extended)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "address": "123 Fake St.",
  "first_name": "John",
  "last_name": "Doe",
  "login": "john.doe@some.fake.email.domain"
}

Identifiers


identifiers are used to specify a field or method name used as an identifier. Usually, this is something like :id.

Example:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
end

Blueprinter identifiers have a few properties that set them apart from fields.

  1. Identifiers are always rendered and considered their own view (the :identifier view).
  2. When rendering, identifier fields are always sorted first, before other fields.

If either of the above two developer conveniences are not desired, you can simply create your identifier fields as regular fields.


Root


You can also optionally pass in a root key to wrap your resulting json in:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end
end

Usage:

puts UserBlueprint.render(user, view: :normal, root: :user)

Output:

{
  "user": {
    "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
    "first_name": "John",
    "last_name": "Doe",
    "login": "john.doe@some.fake.email.domain"
  }
}

Meta Attributes


You can additionally add meta-data to the json as well:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end
end

Usage:

json = UserBlueprint.render(user, view: :normal, root: :user, meta: {links: [
  'https://app.mydomain.com',
  'https://alternate.mydomain.com'
]})
puts json

Output:

{
  "user": {
    "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
    "first_name": "John",
    "last_name": "Doe",
    "login": "john.doe@some.fake.email.domain"
  },
  "meta": {
    "links": [
      "https://app.mydomain.com",
      "https://alternate.mydomain.com"
    ]
  }
}

NOTE: For meta attributes, a root is mandatory.


Exclude Fields


You can specifically choose to exclude certain fields for specific views

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end

  view :extended do
    include_view :normal
    field :address
    exclude :last_name
  end
end

Usage:

puts UserBlueprint.render(user, view: :extended)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "address": "123 Fake St.",
  "first_name": "John",
  "login": "john.doe@some.fake.email.domain"
}

Use excludes to exclude multiple fields at once inline.

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :age, :first_name, :last_name,
  end

  view :extended do
    include_view :normal
    field :address
    excludes :age, :last_name
  end
end

Associations


You may include associated objects. Say for example, a user has projects:

class ProjectBlueprint < Blueprinter::Base
  identifier :uuid
  field :name
end

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
    association :projects, blueprint: ProjectBlueprint
  end
end

Usage:

puts UserBlueprint.render(user, view: :normal)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "first_name": "John",
  "last_name": "Doe",
  "login": "john.doe@some.fake.email.domain",
  "projects": [
    {
      "uuid": "dca94051-4195-42bc-a9aa-eb99f7723c82",
      "name": "Beach Cleanup"
    },
    {
      "uuid": "eb881bb5-9a51-4d27-8a29-b264c30e6160",
      "name": "Storefront Revamp"
    }
  ]
}

It is also possible to pass options from one Blueprint to another via an association. For example:

class VehicleBlueprint < Blueprinter::Base
  identifier :uuid
  field :full_name do |vehicle, options|
    "#{vehicle.model} #{options[:trim]}"
  end
end

class DriverBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    fields :first_name, :last_name
    association :vehicles, blueprint: VehicleBlueprint, options: { trim: 'LX' }
  end
end

Default Association/Field Option


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.

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.

Field-level/Association-level Setting

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    # If first_name is an empty string, it will become "N/A"
    field :first_name, default_if: Blueprinter::EmptyString, default: "N/A"
    # If the projects association collection is empty, it will become nil
    association :projects, blueprint: ProjectBlueprint, default_if: Blueprinter::EmptyCollection
  end
end

Supporting Dynamic Blueprints For Associations


When defining an association, we can dynamically evaluate the blueprint. This comes in handy when adding polymorphic associations, by allowing reuse of existing blueprints.

class Task < ActiveRecord::Base
  belongs_to :taskable, polymorphic: true
end

class Project < ActiveRecord::Base
  has_many :tasks, as: :taskable

  def blueprint
    ProjectBlueprint
  end
end

class TaskBlueprint < Blueprinter::Base
  identifier :uuid

  view :normal do
    field :title, default: "N/A"
    association :taskable, blueprint: ->(taskable) {taskable.blueprint}, default: {}
  end
end

NOTE: taskable.blueprint should return a valid Blueprint class. Currently, has_many is not supported because of the very nature of polymorphic associations.


Defining A Field Directly In The Blueprint


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:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :full_name do |user, options|
    "#{options[:title_prefix]} #{user.first_name} #{user.last_name}"
  end
end

Usage:

puts UserBlueprint.render(user, title_prefix: "Mr")

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "full_name": "Mr John Doe"
}

Defining An Identifier Directly In The Blueprint


You can also pass a block to an identifier:

class UserBlueprint < Blueprinter::Base
  identifier :uuid do |user, options|
    options[:current_user].anonymize(user.uuid)
  end
end

Usage:

puts UserBlueprint.render(user, current_user: current_user)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
}

Defining An Association Directly In The Blueprint


You can also pass a block to an association:

class ProjectBlueprint < Blueprinter::Base
  identifier :uuid
  field :name
end

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  association :projects, blueprint: ProjectBlueprint do |user, options|
    user.projects + options[:draft_projects]
  end
end

Usage:

puts UserBlueprint.render(user, draft_projects: Project.where(draft: true))

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "projects": [
    {"uuid": "b426a1e6-ac41-45ab-bfef-970b9a0b4289", "name": "query-console"},
    {"uuid": "5bd84d6c-4fd2-4e36-ae31-c137e39be542", "name": "blueprinter"},
    {"uuid": "785f5cd4-7d8d-4779-a6dd-ec5eab440eff", "name": "uncontrollable"}
  ]
}

Passing Additional Properties To #render


render takes an options hash which you can pass additional properties, allowing you to utilize those additional properties in the field block. For example:

class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field(:company_name) do |_user, options|
    options[:company].name
  end
end

Usage:

puts UserBlueprint.render(user, company: company)

Output:

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "company_name": "My Company LLC"
}

Conditional Fields


Both the field and the global Blueprinter Configuration supports :if and :unless options that can be used to serialize fields conditionally.

Global Config Setting

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

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

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.

Blueprinter.configure do |config|
  config.datetime_format = ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i }
end

Field-level Setting

Usage (String Option):

class UserBlueprint < Blueprinter::Base
  identifier :name
  field :birthday, datetime_format: "%m/%d/%Y"
end

Output:

{
  "name": "John Doe",
  "birthday": "03/04/1994"
}

Usage (Proc Option):

class UserBlueprint < Blueprinter::Base
  identifier :name
  field :birthday, datetime_format: ->(datetime) { datetime.nil? ? datetime : datetime.strftime("%s").to_i }
end

Output:

{
  "name": "John Doe",
  "birthday": 762739200
}

NOTE: The field-level setting overrides the global config setting (for the field) if both are set.


Transform Classes


Blueprinter provides the ability to specify transforms on views, which enable further processing and transforming of resulting view field hashes prior to serialization.

Use transform to specify one transformer to be included for serialization. A transformer is a class, extending Blueprinter::Transformer and implementing the transform method. Whatever is returned from this transform method will end up being the resulting hash passed to serialization.

Example

Create a Transform class extending from Blueprinter::Transformer

class DynamicFieldTransformer < Blueprinter::Transformer
  def transform(hash, object, options)
    hash.merge!(object.dynamic_fields)
  end
end
class User
  def custom_columns
    self.dynamic_fields #which is an array of some columns
  end

  def custom_fields
    custom_columns.each_with_object({}){|col,result|  result[col] = self.send(col)}
  end
end

Then specify the transform to use for the view.

class UserBlueprint < Blueprinter::Base
  fields :first_name, :last_name
  transform DynamicTransformer
end

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

class LowerCamelTransformer < Blueprinter::Transformer
  def transform(hash, _object, _options)
    hash.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
  end
end
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.

Examples

For a specific kind of field, create an extractor class extending from Blueprinter::Extractor

class MyFieldExtractor < Blueprinter::Extractor
  def extract(_field_name, _object, _local_options, _options={})
    # process your obscure_object
    _object.clarified
  end
end
class MysteryBlueprint < Blueprinter::Base
  field :obscure_object, extractor: MyFieldExtractor
end

For a global default, create an extractor class extending from Blueprinter::AutoExtractor and set the extractor_default configuration

class MyAutoExtractor < Blueprinter::AutoExtractor
  def initialize
    super
    @my_field_extractor = MyFieldExtractor.new
  end
  def extractor(object, options)
    # dispatch to any class AutoExtractor can, plus more
    if detect_obscurity(object)
      @my_field_extractor
    else
      super
    end
  end
end
Blueprinter.configure do |config|
  config.extractor_default = MyAutoExtractor
end

Sorting Fields


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.

Usage:

Blueprinter.configure do |config|
  config.sort_fields_by = :definition
end
class UserBlueprint < Blueprinter::Base
  identifier :name
  field :email
  field :birthday, datetime_format: "%m/%d/%Y"
end

Output:

{
  "name": "John Doe",
  "email": "john.doe@some.fake.email.domain",
  "birthday": "03/04/1994"
}

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:

KeyResult
:stderr (Default)Deprecations will be written to stderror
:raiseDeprecations will be raised as Blueprinter::BlueprinterErrors
:silenceDeprecations will be silenced and will not be raised or logged

Example:

Blueprinter.configure do |config|
  config.deprecations = :raise
end

render_as_hash


Same as render, returns a Ruby Hash.

Usage:

puts UserBlueprint.render_as_hash(user, company: company)

Output:

{
  uuid: "733f0758-8f21-4719-875f-262c3ec743af",
  company_name: "My Company LLC"
}

render_as_json


Same as render, returns a Ruby Hash JSONified. This will call JSONify all keys and values.

Usage:

puts UserBlueprint.render_as_json(user, company: company)

Output:

{
  "uuid" => "733f0758-8f21-4719-875f-262c3ec743af",
  "company_name" => "My Company LLC"
}

Installation

Add this line to your application's Gemfile:

gem 'blueprinter'

And then execute:

$ bundle

Or install it yourself as:

$ gem install blueprinter

You should also have require 'json' already in your project if you are not using Rails or if you are not using Oj.

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.

Contributing

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.

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.

License

The gem is available as open source under the terms of the MIT License.


Author: procore
Source code: https://github.com/procore/blueprinter
License: MIT license

#ruby   #ruby-on-rails 

Ruby on Rails Development Services | Ruby on Rails Development

Ruby on Rails is a development tool that offers Web & Mobile App Developers a structure for all the codes they write resulting in time-saving with all the common repetitive tasks during the development stage.

Want to build a Website or Mobile App with Ruby on Rails Framework

Connect with WebClues Infotech, the top Web & Mobile App development company that has served more than 600 clients worldwide. After serving them with our services WebClues Infotech is ready to serve you in fulfilling your Web & Mobile App Development Requirements.

Want to know more about development on the Ruby on Rails framework?

Visit: https://www.webcluesinfotech.com/ruby-on-rails-development/

Share your requirements https://www.webcluesinfotech.com/contact-us/

View Portfolio https://www.webcluesinfotech.com/portfolio/

#ruby on rails development services #ruby on rails development #ruby on rails web development company #ruby on rails development company #hire ruby on rails developer #hire ruby on rails developers

Shardul Bhatt

Shardul Bhatt

1626850869

7 Reasons to Trust Ruby on Rails

Ruby on Rails is an amazing web development framework. Known for its adaptability, it powers 3,903,258 sites internationally. Ruby on Rails development speeds up the interaction within web applications. It is productive to such an extent that a Ruby on Rails developer can develop an application 25% to 40% quicker when contrasted with different frameworks. 

Around 2.1% (21,034) of the best 1 million sites utilize Ruby on Rails. The framework is perfect for creating web applications in every industry. Regardless of whether it's medical services or vehicles, Rails carries a higher degree of dynamism to each application. 

Be that as it may, what makes the framework so mainstream? Some say that it is affordable, some say it is on the grounds that the Ruby on Rails improvement environment is simple and basic. There are numerous reasons that make it ideal for creating dynamic applications.

Read more: Best Ruby on Rails projects Examples

7 reasons Ruby on Rails is preferred

There are a few other well-known backend services for web applications like Django, Flask, Laravel, and that's only the tip of the iceberg. So for what reason should organizations pick Ruby on Rails application development? We believe the accompanying reasons will feature why different organizations trust the framework -

Quick prototyping 

Rails works on building MVPs in a couple of months. Organizations incline toward Ruby on Rails quick application development as it offers them more opportunity to showcase the elements. Regular development groups accomplish 25% to 40% higher efficiency when working with Rails. Joined with agile, Ruby on Rails empowers timely delivery.

Basic and simple 

Ruby on Rails is easy to arrange and work with. It is not difficult to learn also. Both of these things are conceivable as a result of Ruby. The programming language has one of the most straightforward sentence structures, which is like the English language. Ruby is a universally useful programming language, working on things for web applications. 

Cost-effective 

Probably the greatest advantage of Rails is that it is very reasonable. The system is open-source, which implies there is no licensing charge included. Aside from that, engineers are additionally effectively accessible, that too at a lower cost. There are a large number of Ruby on Rails engineers for hire at an average compensation of $107,381 each year. 

Startup-friendly

Ruby on Rails is regularly known as "the startup technology." It offers adaptable, fast, and dynamic web improvement to new companies. Most arising organizations and new businesses lean toward this as a direct result of its quick application improvement capacities. It prompts quicker MVP development, which permits new companies to rapidly search for venture investment. 

Adaptable framework 

Ruby on Rails is profoundly adaptable and versatile. In any event, when engineers miss adding any functions, they can utilize different modules to add highlights into the application. Aside from that, they can likewise reclassify components by eliminating or adding them during the development environment. Indeed, even individual projects can be extended and changed. 

Convention over configuration

Regardless of whether it's Ruby on Rails enterprise application development or ecommerce-centered applications, the system utilizes convention over configuration. Developers don't have to go through hours attempting to set up the Ruby on Rails improvement environment. The standard conventions cover everything, improving on things for engineers on the task. The framework likewise utilizes the standard of "Don't Repeat Yourself" to guarantee there are no redundancies. 

Versatile applications 

At the point when organizations scale, applications regularly slack. However, this isn't the situation with Ruby on Rails web application development. The system powers sites with high traffic, It can deal with a huge load of worker demands immediately. Adaptability empowers new businesses to keep utilizing the structure even after they prepare their first model for dispatch. 

Checkout Pros and Cons of Ruby on Rails for Web Development

Bottom Line 

Ruby on Rails is as yet a significant framework utilized by organizations all over the world - of every kind. In this day and age, it is probably the best framework to digitize endeavors through powerful web applications.

A software development company provides comprehensive Ruby on Rails development to guarantee startups and MNCs can benefit as much as possible from their digital application needs. 

Reach us today for a FREE CONSULTATION

#ruby on rails development #ruby on rails application development #ruby on rails web application development #ruby on rails developer

Fast Ruby: Writing Fast Ruby. Collect Common Ruby Idioms.

Fast Ruby

In Erik Michaels-Ober's great talk, 'Writing Fast Ruby': Video @ Baruco 2014, Slide, he presented us with many idioms that lead to faster running Ruby code. He inspired me to document these to let more people know. I try to link to real commits so people can see that this can really have benefits in the real world. This does not mean you can always blindly replace one with another. It depends on the context (e.g. gsub versus tr). Friendly reminder: Use with caution!

Each idiom has a corresponding code example that resides in code.

All results listed in README.md are running with Ruby 2.2.0p0 on OS X 10.10.1. Machine information: MacBook Pro (Retina, 15-inch, Mid 2014), 2.5 GHz Intel Core i7, 16 GB 1600 MHz DDR3. Your results may vary, but you get the idea. : )

You can checkout the GitHub Actions build for these benchmark results ran against different Ruby implementations.

Let's write faster code, together! <3

Analyze your code

Checkout the fasterer project - it's a static analysis that checks speed idioms written in this repo.

Measurement Tool

Use benchmark-ips (2.0+).

Template

require "benchmark/ips"

def fast
end

def slow
end

Benchmark.ips do |x|
  x.report("fast code description") { fast }
  x.report("slow code description") { slow }
  x.compare!
end

Idioms

Index

General

Parallel Assignment vs Sequential Assignment code

Read the rationale here.

$ ruby -v code/general/assignment.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
 Parallel Assignment   149.201k i/100ms
Sequential Assignment
                       142.545k i/100ms
-------------------------------------------------
 Parallel Assignment      7.687M (± 6.9%) i/s -     38.345M
Sequential Assignment
                          6.320M (± 8.5%) i/s -     31.360M

Comparison:
 Parallel Assignment:  7686954.1 i/s
Sequential Assignment:  6320425.6 i/s - 1.22x slower

attr_accessor vs getter and setter code

https://www.omniref.com/ruby/2.2.0/files/method.h?#annotation=4081781&line=47

$ ruby -v code/general/attr-accessor-vs-getter-and-setter.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
Calculating -------------------------------------
   getter_and_setter    61.240k i/100ms
       attr_accessor    66.535k i/100ms
-------------------------------------------------
   getter_and_setter      1.660M (± 9.7%) i/s -      8.267M
       attr_accessor      1.865M (± 9.2%) i/s -      9.248M

Comparison:
       attr_accessor:  1865408.4 i/s
   getter_and_setter:  1660021.9 i/s - 1.12x slower

begin...rescue vs respond_to? for Control Flow code

$ ruby -v code/general/begin-rescue-vs-respond-to.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
      begin...rescue    29.452k i/100ms
         respond_to?   106.528k i/100ms
-------------------------------------------------
      begin...rescue    371.591k (± 5.4%) i/s -      1.855M
         respond_to?      3.277M (± 7.5%) i/s -     16.299M

Comparison:
         respond_to?:  3276972.3 i/s
      begin...rescue:   371591.0 i/s - 8.82x slower

define_method vs module_eval for Defining Methods code

$ ruby -v code/general/define_method-vs-module-eval.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
module_eval with string 125.000  i/100ms
       define_method    138.000  i/100ms
-------------------------------------------------
module_eval with string   1.130k (±20.3%) i/s -      5.500k
       define_method      1.346k (±25.9%) i/s -      6.348k

Comparison:
       define_method:        1345.6 i/s
module_eval with string:     1129.7 i/s - 1.19x slower

String#constantize vs a comparison for inflection

ActiveSupport's String#constantize "resolves the constant reference expression in its receiver".

Read the rationale here

ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-darwin20]

Calculating -------------------------------------
using an if statement
                          8.124M (± 1.8%) i/s -     41.357M in   5.092437s
  String#constantize      2.462M (± 2.4%) i/s -     12.315M in   5.004089s

Comparison:
using an if statement:  8123851.3 i/s
  String#constantize:  2462371.2 i/s - 3.30x  (± 0.00) slower

raise vs E2MM#Raise for raising (and defining) exeptions code

Ruby's Exception2MessageMapper module allows one to define and raise exceptions with predefined messages.

$ ruby -v code/general/raise-vs-e2mmap.rb
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]

Calculating -------------------------------------
Ruby exception: E2MM#Raise
                         2.865k i/100ms
Ruby exception: Kernel#raise
                        42.215k i/100ms
-------------------------------------------------
Ruby exception: E2MM#Raise
                         27.270k (± 8.8%) i/s -    137.520k
Ruby exception: Kernel#raise
                        617.446k (± 7.9%) i/s -      3.082M

Comparison:
Ruby exception: Kernel#raise:   617446.2 i/s
Ruby exception: E2MM#Raise:    27269.8 i/s - 22.64x slower

Calculating -------------------------------------
Custom exception: E2MM#Raise
                         2.807k i/100ms
Custom exception: Kernel#raise
                        45.313k i/100ms
-------------------------------------------------
Custom exception: E2MM#Raise
                         29.005k (± 7.2%) i/s -    145.964k
Custom exception: Kernel#raise
                        589.149k (± 7.8%) i/s -      2.945M

Comparison:
Custom exception: Kernel#raise:   589148.7 i/s
Custom exception: E2MM#Raise:    29004.8 i/s - 20.31x slower

loop vs while true code

$ ruby -v code/general/loop-vs-while-true.rb
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]

Calculating -------------------------------------
          While Loop     1.000  i/100ms
         Kernel loop     1.000  i/100ms
-------------------------------------------------
          While Loop      0.536  (± 0.0%) i/s -      3.000  in   5.593042s
         Kernel loop      0.223  (± 0.0%) i/s -      2.000  in   8.982355s

Comparison:
          While Loop:        0.5 i/s
         Kernel loop:        0.2 i/s - 2.41x slower

ancestors.include? vs <= code

$ ruby -vW0 code/general/inheritance-check.rb
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]
Warming up --------------------------------------
  less than or equal    66.992k i/100ms
  ancestors.include?    16.943k i/100ms
Calculating -------------------------------------
  less than or equal      1.250M (± 6.4%) i/s -      6.230M in   5.006896s
  ancestors.include?    192.603k (± 4.8%) i/s -    965.751k in   5.025917s

Comparison:
  less than or equal:  1249606.0 i/s
  ancestors.include?:   192602.9 i/s - 6.49x  slower

Method Invocation

call vs send vs method_missing code

$ ruby -v code/method/call-vs-send-vs-method_missing.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
                call   115.094k i/100ms
                send   105.258k i/100ms
      method_missing   100.762k i/100ms
-------------------------------------------------
                call      3.811M (± 5.9%) i/s -     18.991M
                send      3.244M (± 7.2%) i/s -     16.210M
      method_missing      2.729M (± 9.8%) i/s -     13.401M

Comparison:
                call:  3811183.4 i/s
                send:  3244239.1 i/s - 1.17x slower
      method_missing:  2728893.0 i/s - 1.40x slower

Normal way to apply method vs &method(...) code

$ ruby -v code/general/block-apply-method.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
              normal    85.749k i/100ms
             &method    35.529k i/100ms
-------------------------------------------------
              normal      1.867M (± 7.6%) i/s -      9.347M
             &method    467.095k (± 6.4%) i/s -      2.345M

Comparison:
              normal:  1866669.5 i/s
             &method:   467095.4 i/s - 4.00x slower

Function with single Array argument vs splat arguments code

$ ruby -v code/general/array-argument-vs-splat-arguments.rb
ruby 2.1.7p400 (2015-08-18 revision 51632) [x86_64-linux-gnu]
Calculating -------------------------------------
Function with single Array argument
                       157.231k i/100ms
Function with splat arguments
                         4.983k i/100ms
-------------------------------------------------
Function with single Array argument
                          5.581M (± 2.0%) i/s -     27.987M
Function with splat arguments
                         54.428k (± 3.3%) i/s -    274.065k

Comparison:
Function with single Array argument:  5580972.6 i/s
Function with splat arguments:    54427.7 i/s - 102.54x slower

Hash vs OpenStruct on access assuming you already have a Hash or an OpenStruct code

$ ruby -v code/general/hash-vs-openstruct-on-access.rb
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]

Calculating -------------------------------------
                Hash   128.344k i/100ms
          OpenStruct   110.723k i/100ms
-------------------------------------------------
                Hash      5.279M (± 7.0%) i/s -     26.311M
          OpenStruct      3.048M (± 7.0%) i/s -     15.169M

Comparison:
                Hash:  5278844.0 i/s
          OpenStruct:  3048139.8 i/s - 1.73x slower

Hash vs OpenStruct (creation) code

$ ruby -v code/general/hash-vs-openstruct.rb
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]

Calculating -------------------------------------
                Hash    75.510k i/100ms
          OpenStruct     9.126k i/100ms
-------------------------------------------------
                Hash      1.604M (±11.0%) i/s -      7.929M
          OpenStruct     96.855k (± 9.9%) i/s -    483.678k

Comparison:
                Hash:  1604259.1 i/s
          OpenStruct:    96855.3 i/s - 16.56x slower

Kernel#format vs Float#round().to_s code

$ ruby -v code/general/format-vs-round-and-to-s.rb
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]
Warming up --------------------------------------
         Float#round   106.645k i/100ms
       Kernel#format    84.304k i/100ms
            String#%    78.635k i/100ms
Calculating -------------------------------------
         Float#round      1.570M (± 3.2%) i/s - 7.892M in   5.030672s
       Kernel#format      1.144M (± 3.0%) i/s - 5.733M in   5.015621s
            String#%      1.047M (± 4.2%) i/s - 5.269M in   5.042970s

Comparison:
         Float#round:  1570411.4 i/s
       Kernel#format:  1144036.6 i/s - 1.37x  slower
            String#%:  1046689.1 i/s - 1.50x  slower

Array

Array#bsearch vs Array#find code

WARNING: bsearch ONLY works on sorted array. More details please see #29.

$ ruby -v code/array/bsearch-vs-find.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
                find     1.000  i/100ms
             bsearch    42.216k i/100ms
-------------------------------------------------
                find      0.184  (± 0.0%) i/s -      1.000  in   5.434758s
             bsearch    577.301k (± 6.6%) i/s -      2.913M

Comparison:
             bsearch:   577300.7 i/s
                find:        0.2 i/s - 3137489.63x slower

Array#length vs Array#size vs Array#count code

Use #length when you only want to know how many elements in the array, #count could also achieve this. However #count should be use for counting specific elements in array. Note #size is an alias of #length.

$ ruby -v code/array/length-vs-size-vs-count.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
        Array#length   172.998k i/100ms
          Array#size   168.130k i/100ms
         Array#count   164.911k i/100ms
-------------------------------------------------
        Array#length     11.394M (± 6.1%) i/s -     56.743M
          Array#size     11.303M (± 6.5%) i/s -     56.324M
         Array#count      9.195M (± 8.6%) i/s -     45.680M

Comparison:
        Array#length: 11394036.7 i/s
          Array#size: 11302701.1 i/s - 1.01x slower
         Array#count:  9194976.2 i/s - 1.24x slower

Array#shuffle.first vs Array#sample code

Array#shuffle allocates an extra array. 
Array#sample indexes into the array without allocating an extra array. 
This is the reason why Array#sample exists. 
—— @sferik rails/rails#17245

$ ruby -v code/array/shuffle-first-vs-sample.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
 Array#shuffle.first    25.406k i/100ms
        Array#sample   125.101k i/100ms
-------------------------------------------------
 Array#shuffle.first    304.341k (± 4.3%) i/s -      1.524M
        Array#sample      5.727M (± 8.6%) i/s -     28.523M

Comparison:
        Array#sample:  5727032.0 i/s
 Array#shuffle.first:   304341.1 i/s - 18.82x slower

Array#[](0) vs Array#first code

$ ruby -v code/array/array-first-vs-index.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
           Array#[0]   152.751k i/100ms
         Array#first   148.088k i/100ms
-------------------------------------------------
           Array#[0]      8.614M (± 7.0%) i/s -     42.923M
         Array#first      7.465M (±10.7%) i/s -     36.874M

Comparison:
           Array#[0]:  8613583.7 i/s
         Array#first:  7464526.6 i/s - 1.15x slower

Array#[](-1) vs Array#last code

$ ruby -v code/array/array-last-vs-index.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
            Array#[-1]   151.940k i/100ms
          Array#last   153.371k i/100ms
-------------------------------------------------
            Array#[-1]      8.582M (± 4.6%) i/s -     42.847M
          Array#last      7.639M (± 5.7%) i/s -     38.189M

Comparison:
            Array#[-1]:  8582074.3 i/s
          Array#last:  7639254.5 i/s - 1.12x slower

Array#insert vs Array#unshift code

$ ruby -v code/array/insert-vs-unshift.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin10.0]
Calculating -------------------------------------
       Array#unshift     4.000  i/100ms
        Array#insert     1.000  i/100ms
-------------------------------------------------
       Array#unshift     44.947  (± 6.7%) i/s -    224.000
        Array#insert      0.171  (± 0.0%) i/s -      1.000  in   5.841595s

Comparison:
       Array#unshift:       44.9 i/s
        Array#insert:        0.2 i/s - 262.56x slower

Array#new vs Fixnum#times + map code

Typical slowdown is 40-60% depending on the size of the array. See the corresponding pull request for performance characteristics.

ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]
Calculating -------------------------------------
           Array#new    63.875k i/100ms
  Fixnum#times + map    48.010k i/100ms
-------------------------------------------------
           Array#new      1.070M (± 2.2%) i/s -      5.365M
  Fixnum#times + map    678.097k (± 2.7%) i/s -      3.409M

Comparison:
           Array#new:  1069837.0 i/s
  Fixnum#times + map:   678097.4 i/s - 1.58x slower

Enumerable

Enumerable#each + push vs Enumerable#map code

$ ruby -v code/enumerable/each-push-vs-map.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
   Array#each + push     9.025k i/100ms
           Array#map    13.947k i/100ms
-------------------------------------------------
   Array#each + push     99.634k (± 3.2%) i/s -    505.400k
           Array#map    158.091k (± 4.2%) i/s -    794.979k

Comparison:
           Array#map:   158090.9 i/s
   Array#each + push:    99634.2 i/s - 1.59x slower

Enumerable#each vs for loop code

$ ruby -v code/enumerable/each-vs-for-loop.rb
ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin14]

Calculating -------------------------------------
            For loop    17.111k i/100ms
               #each    18.464k i/100ms
-------------------------------------------------
            For loop    198.517k (± 5.3%) i/s -    992.438k
               #each    208.157k (± 5.0%) i/s -      1.052M

Comparison:
               #each:   208157.4 i/s
            For loop:   198517.3 i/s - 1.05x slower

Enumerable#each_with_index vs while loop code

rails/rails#12065

$ ruby -v code/enumerable/each_with_index-vs-while-loop.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
          While Loop    22.553k i/100ms
     each_with_index    11.963k i/100ms
-------------------------------------------------
          While Loop    240.752k (± 7.1%) i/s -      1.218M
     each_with_index    126.753k (± 5.9%) i/s -    634.039k

Comparison:
          While Loop:   240752.1 i/s
     each_with_index:   126753.4 i/s - 1.90x slower

Enumerable#map...Array#flatten vs Enumerable#flat_map code

-- @sferik rails/rails@3413b88, Replace map.flatten with flat_map, Replace map.flatten(1) with flat_map

$ ruby -v code/enumerable/map-flatten-vs-flat_map.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
Array#map.flatten(1)     3.315k i/100ms
   Array#map.flatten     3.283k i/100ms
      Array#flat_map     5.350k i/100ms
-------------------------------------------------
Array#map.flatten(1)     33.801k (± 4.3%) i/s -    169.065k
   Array#map.flatten     34.530k (± 6.0%) i/s -    173.999k
      Array#flat_map     55.980k (± 5.0%) i/s -    283.550k

Comparison:
      Array#flat_map:    55979.6 i/s
   Array#map.flatten:    34529.6 i/s - 1.62x slower
Array#map.flatten(1):    33800.6 i/s - 1.66x slower

Enumerable#reverse.each vs Enumerable#reverse_each code

Enumerable#reverse allocates an extra array. 
Enumerable#reverse_each yields each value without allocating an extra array. 
This is the reason why Enumerable#reverse_each exists. 
-- @sferik rails/rails#17244

$ ruby -v code/enumerable/reverse-each-vs-reverse_each.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
  Array#reverse.each    16.746k i/100ms
  Array#reverse_each    18.590k i/100ms
-------------------------------------------------
  Array#reverse.each    190.729k (± 4.8%) i/s -    954.522k
  Array#reverse_each    216.060k (± 4.3%) i/s -      1.078M

Comparison:
  Array#reverse_each:   216060.5 i/s
  Array#reverse.each:   190729.1 i/s - 1.13x slower

Enumerable#sort_by.first vs Enumerable#min_by code

Enumerable#sort_by performs a sort of the enumerable and allocates a new array the size of the enumerable. Enumerable#min_by doesn't perform a sort or allocate an array the size of the enumerable. Similar comparisons hold for Enumerable#sort_by.last vs Enumerable#max_by, Enumerable#sort.first vs Enumerable#min, and Enumerable#sort.last vs Enumerable#max.

$ ruby -v code/enumerable/sort_by-first-vs-min_by.rb
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
Warming up --------------------------------------
   Enumerable#min_by    15.170k i/100ms
Enumerable#sort_by...first
                        10.413k i/100ms
Calculating -------------------------------------
   Enumerable#min_by    157.877k (± 0.9%) i/s -    804.010k in   5.093048s
Enumerable#sort_by...first
                        106.831k (± 1.3%) i/s -    541.476k in   5.069403s

Comparison:
   Enumerable#min_by:   157877.0 i/s
Enumerable#sort_by...first:   106831.1 i/s - 1.48x  slower

Enumerable#detect vs Enumerable#select.first code

$ ruby -v code/enumerable/select-first-vs-detect.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
Enumerable#select.first  8.515k i/100ms
   Enumerable#detect    33.885k i/100ms
-------------------------------------------------
Enumerable#select.first  89.757k (± 5.0%) i/s -      1.797M
   Enumerable#detect    434.304k (± 5.2%) i/s -      8.675M

Comparison:
   Enumerable#detect:   434304.2 i/s
Enumerable#select.first:    89757.4 i/s - 4.84x slower

Enumerable#select.last vs Enumerable#reverse.detect code

$ ruby -v code/enumerable/select-last-vs-reverse-detect.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
Enumerable#reverse.detect 62.636k i/100ms
Enumerable#select.last    11.687k i/100ms
-------------------------------------------------
Enumerable#reverse.detect 1.263M (± 8.2%) i/s -      6.326M
Enumerable#select.last  119.387k (± 5.7%) i/s -    596.037k

Comparison:
Enumerable#reverse.detect:  1263100.2 i/s
Enumerable#select.last:     119386.8 i/s - 10.58x slower

Enumerable#sort vs Enumerable#sort_by code

$ ruby -v code/enumerable/sort-vs-sort_by.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
Enumerable#sort_by (Symbol#to_proc) 2.680k i/100ms
  Enumerable#sort_by                2.462k i/100ms
     Enumerable#sort                1.320k i/100ms
-------------------------------------------------
Enumerable#sort_by (Symbol#to_proc) 25.916k (± 4.4%) i/s -    131.320k
  Enumerable#sort_by                24.650k (± 5.1%) i/s -    125.562k
     Enumerable#sort                14.018k (± 5.6%) i/s -     69.960k

Comparison:
Enumerable#sort_by (Symbol#to_proc):    25916.1 i/s
  Enumerable#sort_by:                   24650.2 i/s - 1.05x slower
     Enumerable#sort:                   14018.3 i/s - 1.85x slower

Enumerable#inject Symbol vs Enumerable#inject Proc code

Of note, to_proc for 1.8.7 is considerable slower than the block format

$ ruby -v code/enumerable/inject-symbol-vs-block.rb
ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-darwin14]
Warming up --------------------------------------
       inject symbol     1.893k i/100ms
      inject to_proc     1.583k i/100ms
        inject block     1.390k i/100ms
Calculating -------------------------------------
       inject symbol     19.001k (± 3.8%) i/s -     96.543k
      inject to_proc     15.958k (± 3.5%) i/s -     80.733k
        inject block     14.063k (± 3.9%) i/s -     70.890k

Comparison:
       inject symbol:    19001.5 i/s
      inject to_proc:    15958.3 i/s - 1.19x slower
        inject block:    14063.1 i/s - 1.35x slower

Date

Date.iso8601 vs Date.parse code

When expecting well-formatted data from e.g. an API, iso8601 is faster and will raise an ArgumentError on malformed input.

$ ruby -v code/date/iso8601-vs-parse.rb
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]
Warming up --------------------------------------
        Date.iso8601    28.880k i/100ms
          Date.parse    15.805k i/100ms
Calculating -------------------------------------
        Date.iso8601    328.035k (± 4.7%) i/s -      1.646M in   5.029287s
          Date.parse    175.546k (± 3.8%) i/s -    885.080k in   5.049444s

Comparison:
        Date.iso8601:   328035.3 i/s
          Date.parse:   175545.9 i/s - 1.87x  slower

Hash

Hash#[] vs Hash#fetch code

If you use Ruby 2.2, Symbol could be more performant than String as Hash keys. Read more regarding this: Symbol GC in Ruby 2.2 and Unraveling String Key Performance in Ruby 2.2.

$ ruby -v code/hash/bracket-vs-fetch.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
     Hash#[], symbol   143.850k i/100ms
  Hash#fetch, symbol   137.425k i/100ms
     Hash#[], string   143.083k i/100ms
  Hash#fetch, string   120.417k i/100ms
-------------------------------------------------
     Hash#[], symbol      7.531M (± 6.6%) i/s -     37.545M
  Hash#fetch, symbol      6.644M (± 8.2%) i/s -     32.982M
     Hash#[], string      6.657M (± 7.7%) i/s -     33.195M
  Hash#fetch, string      3.981M (± 8.7%) i/s -     19.748M

Comparison:
     Hash#[], symbol:  7531355.8 i/s
     Hash#[], string:  6656818.8 i/s - 1.13x slower
  Hash#fetch, symbol:  6643665.5 i/s - 1.13x slower
  Hash#fetch, string:  3981166.5 i/s - 1.89x slower

Hash#dig vs Hash#[] vs Hash#fetch code

Ruby 2.3 introduced Hash#dig which is a readable and performant option for retrieval from a nested hash, returning nil if an extraction step fails. See #102 (comment) for more info.

$ ruby -v code/hash/dig-vs-\[\]-vs-fetch.rb
ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]

Calculating -------------------------------------
            Hash#dig      5.719M (± 6.1%) i/s -     28.573M in   5.013997s
             Hash#[]      6.066M (± 6.9%) i/s -     30.324M in   5.025614s
          Hash#[] ||      5.366M (± 6.5%) i/s -     26.933M in   5.041403s
          Hash#[] &&      2.782M (± 4.8%) i/s -     13.905M in   5.010328s
          Hash#fetch      4.101M (± 6.1%) i/s -     20.531M in   5.024945s
 Hash#fetch fallback      2.975M (± 5.5%) i/s -     14.972M in   5.048880s

Comparison:
             Hash#[]:  6065791.0 i/s
            Hash#dig:  5719290.9 i/s - same-ish: difference falls within error
          Hash#[] ||:  5366226.5 i/s - same-ish: difference falls within error
          Hash#fetch:  4101102.1 i/s - 1.48x slower
 Hash#fetch fallback:  2974906.9 i/s - 2.04x slower
          Hash#[] &&:  2781646.6 i/s - 2.18x slower

Hash[] vs Hash#dup code

Source: http://tenderlovemaking.com/2015/02/11/weird-stuff-with-hashes.html

Does this mean that you should switch to Hash[]? Only if your benchmarks can prove that it’s a bottleneck. Please please please don’t change all of your code because this shows it’s faster. Make sure to measure your app performance first.

$ ruby -v code/hash/bracket-vs-dup.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
              Hash[]    29.403k i/100ms
            Hash#dup    16.195k i/100ms
-------------------------------------------------
              Hash[]    343.987k (± 8.7%) i/s -      1.735M
            Hash#dup    163.516k (±10.2%) i/s -    825.945k

Comparison:
              Hash[]:   343986.5 i/s
            Hash#dup:   163516.3 i/s - 2.10x slower

Hash#fetch with argument vs Hash#fetch + block code

Note that the speedup in the block version comes from avoiding repeated 
construction of the argument. If the argument is a constant, number symbol or 
something of that sort the argument version is actually slightly faster 
See also #39 (comment)

$ ruby -v code/hash/fetch-vs-fetch-with-block.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]
Calculating -------------------------------------
  Hash#fetch + const   129.868k i/100ms
  Hash#fetch + block   125.254k i/100ms
    Hash#fetch + arg   121.155k i/100ms
-------------------------------------------------
  Hash#fetch + const      7.031M (± 7.0%) i/s -     34.934M
  Hash#fetch + block      6.815M (± 4.2%) i/s -     34.069M
    Hash#fetch + arg      4.753M (± 5.6%) i/s -     23.746M

Comparison:
  Hash#fetch + const:  7030600.4 i/s
  Hash#fetch + block:  6814826.7 i/s - 1.03x slower
    Hash#fetch + arg:  4752567.2 i/s - 1.48x slower

Hash#each_key instead of Hash#keys.each code

Hash#keys.each allocates an array of keys; 
Hash#each_key iterates through the keys without allocating a new array. 
This is the reason why Hash#each_key exists. 
—— @sferik rails/rails#17099

$ ruby -v code/hash/keys-each-vs-each_key.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
      Hash#keys.each    56.690k i/100ms
       Hash#each_key    59.658k i/100ms
-------------------------------------------------
      Hash#keys.each    869.262k (± 5.0%) i/s -      4.365M
       Hash#each_key      1.049M (± 6.0%) i/s -      5.250M

Comparison:
       Hash#each_key:  1049161.6 i/s
      Hash#keys.each:   869262.3 i/s - 1.21x slower

Hash#key? instead of Hash#keys.include? code

Hash#keys.include? allocates an array of keys and performs an O(n) search; 
Hash#key? performs an O(1) hash lookup without allocating a new array.

$ ruby -v code/hash/keys-include-vs-key.rb
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

Calculating -------------------------------------
  Hash#keys.include?      8.612k (± 2.5%) i/s -     43.248k in   5.024749s
           Hash#key?      6.366M (± 5.5%) i/s -     31.715M in   5.002276s

Comparison:
           Hash#key?:  6365855.5 i/s
  Hash#keys.include?:     8612.4 i/s - 739.15x  slower

Hash#value? instead of Hash#values.include? code

Hash#values.include? allocates an array of values and performs an O(n) search; 
Hash#value? performs an O(n) search without allocating a new array.

$ ruby -v code/hash/values-include-vs-value.rb
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

Calculating -------------------------------------
Hash#values.include?     23.187k (± 4.3%) i/s -    117.720k in   5.086976s
         Hash#value?     38.395k (± 1.0%) i/s -    194.361k in   5.062696s

Comparison:
         Hash#value?:    38395.0 i/s
Hash#values.include?:    23186.8 i/s - 1.66x  slower

Hash#merge! vs Hash#[]= code

$ ruby -v code/hash/merge-bang-vs-\[\]=.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
         Hash#merge!     1.023k i/100ms
            Hash#[]=     2.844k i/100ms
-------------------------------------------------
         Hash#merge!     10.653k (± 4.9%) i/s -     53.196k
            Hash#[]=     28.287k (±12.4%) i/s -    142.200k

Comparison:
            Hash#[]=:    28287.1 i/s
         Hash#merge!:    10653.3 i/s - 2.66x slower

Hash#update vs Hash#[]= code

$ ruby -v code/hash/update-vs-\[\]=.rb
ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-darwin18]

Warming up --------------------------------------
            Hash#[]=     7.453k i/100ms
         Hash#update     4.311k i/100ms
Calculating -------------------------------------
            Hash#[]=     74.764k (± 1.9%) i/s -    380.103k in   5.085962s
         Hash#update     43.220k (± 0.8%) i/s -    219.861k in   5.087364s

Comparison:
            Hash#[]=:    74764.0 i/s
         Hash#update:    43220.1 i/s - 1.73x  (± 0.00) slower

Hash#merge vs Hash#**other code

$ ruby -v code/hash/merge-vs-double-splat-operator.rb
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]
Warming up --------------------------------------
        Hash#**other    64.624k i/100ms
          Hash#merge    38.827k i/100ms
Calculating -------------------------------------
        Hash#**other    798.397k (± 6.9%) i/s -      4.007M in   5.053516s
          Hash#merge    434.171k (± 4.5%) i/s -      2.174M in   5.018927s

Comparison:
        Hash#**other:   798396.6 i/s
          Hash#merge:   434170.8 i/s - 1.84x  slower

Hash#merge vs Hash#merge! code

$ ruby -v code/hash/merge-vs-merge-bang.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
          Hash#merge    39.000  i/100ms
         Hash#merge!     1.008k i/100ms
-------------------------------------------------
          Hash#merge    409.610  (± 7.6%) i/s -      2.067k
         Hash#merge!      9.830k (± 5.8%) i/s -     49.392k

Comparison:
         Hash#merge!:     9830.3 i/s
          Hash#merge:      409.6 i/s - 24.00x slower

{}#merge!(Hash) vs Hash#merge({}) vs Hash#dup#merge!({}) code

When we don't want to modify the original hash, and we want duplicates to be created 
See #42 for more details.

$ ruby -v code/hash/merge-bang-vs-merge-vs-dup-merge-bang.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]

Calculating -------------------------------------
{}#merge!(Hash) do end     2.006k i/100ms
        Hash#merge({})   762.000  i/100ms
   Hash#dup#merge!({})   736.000  i/100ms
-------------------------------------------------
{}#merge!(Hash) do end     20.055k (± 2.0%) i/s -    100.300k in   5.003322s
        Hash#merge({})      7.676k (± 1.2%) i/s -     38.862k in   5.063382s
   Hash#dup#merge!({})      7.440k (± 1.1%) i/s -     37.536k in   5.045851s

Comparison:
{}#merge!(Hash) do end:    20054.8 i/s
        Hash#merge({}):     7676.3 i/s - 2.61x slower
   Hash#dup#merge!({}):     7439.9 i/s - 2.70x slower

Hash#sort_by vs Hash#sort code

To sort hash by key.

$ ruby -v code/hash/hash-key-sort_by-vs-sort.rb
ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-darwin14]

Calculating -------------------------------------
      sort_by + to_h    11.468k i/100ms
         sort + to_h     8.107k i/100ms
-------------------------------------------------
      sort_by + to_h    122.176k (± 6.0%) i/s -    619.272k
         sort + to_h     81.973k (± 4.7%) i/s -    413.457k

Comparison:
      sort_by + to_h:   122176.2 i/s
         sort + to_h:    81972.8 i/s - 1.49x slower

Native Hash#slice vs other slice implementations before native code

Since ruby 2.5, Hash comes with a slice method to select hash members by keys.

$ ruby -v code/hash/slice-native-vs-before-native.rb
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
Warming up --------------------------------------
Hash#native-slice      178.077k i/100ms
Array#each             124.311k i/100ms
Array#each_w/_object   110.818k i/100ms
Hash#select-include     66.972k i/100ms
Calculating -------------------------------------
Hash#native-slice         2.540M (± 1.5%) i/s -     12.822M in   5.049955s
Array#each                1.614M (± 1.0%) i/s -      8.080M in   5.007925s
Array#each_w/_object      1.353M (± 2.6%) i/s -      6.760M in   5.000441s
Hash#select-include     760.944k (± 0.9%) i/s -      3.817M in   5.017123s

Comparison:
Hash#native-slice   :  2539515.5 i/s
Array#each          :  1613665.5 i/s - 1.57x  slower
Array#each_w/_object:  1352851.8 i/s - 1.88x  slower
Hash#select-include :   760944.2 i/s - 3.34x  slower

Proc & Block

Block vs Symbol#to_proc code

Symbol#to_proc is considerably more concise than using block syntax. 
...In some cases, it reduces the number of lines of code. 
—— @sferik rails/rails#16833

$ ruby -v code/proc-and-block/block-vs-to_proc.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
               Block     4.632k i/100ms
      Symbol#to_proc     5.225k i/100ms
-------------------------------------------------
               Block     47.914k (± 6.3%) i/s -    240.864k
      Symbol#to_proc     54.791k (± 4.1%) i/s -    276.925k

Comparison:
      Symbol#to_proc:    54791.1 i/s
               Block:    47914.3 i/s - 1.14x slower

Proc#call and block arguments vs yieldcode

In MRI Ruby before 2.5, block arguments are converted to Procs, which incurs a heap allocation.

$ ruby -v code/proc-and-block/proc-call-vs-yield.rb
ruby 2.4.4p296 (2018-03-28 revision 63013) [x86_64-darwin18]
Calculating -------------------------------------
        block.call      1.967M (± 2.0%) i/s -      9.871M in   5.019328s
     block + yield      2.147M (± 3.3%) i/s -     10.814M in   5.044319s
      unused block      2.265M (± 1.9%) i/s -     11.333M in   5.004522s
             yield     10.436M (± 1.6%) i/s -     52.260M in   5.008851s

Comparison:
             yield: 10436414.0 i/s
      unused block:  2265399.0 i/s - 4.61x  slower
     block + yield:  2146619.0 i/s - 4.86x  slower
        block.call:  1967300.9 i/s - 5.30x  slower

MRI Ruby 2.5 implements Lazy Proc allocation for block parameters:

$ ruby -v code/proc-and-block/proc-call-vs-yield.rb
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
Calculating -------------------------------------
        block.call      1.970M (± 2.3%) i/s -      9.863M in   5.009599s
     block + yield      9.075M (± 2.6%) i/s -     45.510M in   5.018369s
      unused block     11.176M (± 2.7%) i/s -     55.977M in   5.012741s
             yield     10.588M (± 1.9%) i/s -     53.108M in   5.017755s

Comparison:
      unused block: 11176355.0 i/s
             yield: 10588342.3 i/s - 1.06x  slower
     block + yield:  9075355.5 i/s - 1.23x  slower
        block.call:  1969834.0 i/s - 5.67x  slower

MRI Ruby 2.6 implements an optimization for block.call where a block parameter is passed:

$ ruby -v code/proc-and-block/proc-call-vs-yield.rb
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin18]
Calculating -------------------------------------
        block.call     10.587M (± 1.2%) i/s -     52.969M in   5.003808s
     block + yield     12.630M (± 0.3%) i/s -     63.415M in   5.020910s
      unused block     15.981M (± 0.8%) i/s -     80.255M in   5.022305s
             yield     15.352M (± 3.1%) i/s -     76.816M in   5.009404s

Comparison:
      unused block: 15980789.4 i/s
             yield: 15351931.0 i/s - 1.04x  slower
     block + yield: 12630378.1 i/s - 1.27x  slower
        block.call: 10587315.1 i/s - 1.51x  slower

String

String#dup vs String#+ code

Note that String.new is not the same as the options compared, since it is always ASCII-8BIT encoded instead of the script encoding (usually UTF-8).

$ ruby -v code/string/dup-vs-unary-plus.rb
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]

Calculating -------------------------------------
           String#+@      7.697M (± 1.4%) i/s -     38.634M in   5.020313s
          String#dup      3.566M (± 1.0%) i/s -     17.860M in   5.008377s

Comparison:
           String#+@:  7697108.3 i/s
          String#dup:  3566485.7 i/s - 2.16x  slower

String#casecmp vs String#casecmp? vs String#downcase + == code

String#casecmp? is available on Ruby 2.4 or later. Note that String#casecmp only works on characters A-Z/a-z, not all of Unicode.

$ ruby -v code/string/casecmp-vs-downcase-\=\=.rb
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19]
Warming up --------------------------------------
     String#casecmp?   395.796k i/100ms
String#downcase + ==   543.958k i/100ms
      String#casecmp   730.028k i/100ms
Calculating -------------------------------------
     String#casecmp?      3.687M (±10.9%) i/s -     18.602M in   5.158065s
String#downcase + ==      5.017M (±11.3%) i/s -     25.022M in   5.089175s
      String#casecmp      6.948M (± 6.0%) i/s -     35.041M in   5.062714s

Comparison:
      String#casecmp:  6948231.0 i/s
String#downcase + ==:  5017089.5 i/s - 1.38x  (± 0.00) slower
     String#casecmp?:  3686650.7 i/s - 1.88x  (± 0.00) slower

String Concatenation code

$ ruby -v code/string/concatenation.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]

Warming up --------------------------------------
            String#+   149.298k i/100ms
       String#concat   151.505k i/100ms
       String#append   153.389k i/100ms
         "foo" "bar"   195.552k i/100ms
  "#{'foo'}#{'bar'}"   193.784k i/100ms
Calculating -------------------------------------
            String#+      2.977M (± 1.1%) i/s -     14.930M in   5.015179s
       String#concat      3.017M (± 1.3%) i/s -     15.150M in   5.023063s
       String#append      3.076M (± 1.2%) i/s -     15.492M in   5.037683s
         "foo" "bar"      5.370M (± 1.0%) i/s -     26.986M in   5.026271s
  "#{'foo'}#{'bar'}"      5.182M (± 4.6%) i/s -     25.967M in   5.022093s

Comparison:
         "foo" "bar":  5369594.5 i/s
  "#{'foo'}#{'bar'}":  5181745.7 i/s - same-ish: difference falls within error
       String#append:  3075719.2 i/s - 1.75x slower
       String#concat:  3016703.5 i/s - 1.78x slower
            String#+:  2977282.7 i/s - 1.80x slower

String#match vs String.match? vs String#start_with?/String#end_with? code (start) code (end)

The regular expression approaches become slower as the tested string becomes longer. For short strings, String#match? performs similarly to String#start_with?/String#end_with?.

:warning: 
Sometimes you cant replace regexp with start_with?
for example: "a\nb" =~ /^b/ #=> 2 but "a\nb" =~ /\Ab/ #=> nil.
:warning: 
You can combine start_with? and end_with? to replace error.path =~ /^#{path}(\.rb)?$/ to this 
error.path.start_with?(path) && error.path.end_with?('.rb', '')
—— @igas rails/rails#17316

$ ruby -v code/string/start-string-checking-match-vs-start_with.rb
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]

Calculating -------------------------------------
           String#=~      1.088M (± 4.0%) i/s -      5.471M in   5.034404s
       String#match?      5.138M (± 5.0%) i/s -     25.669M in   5.008810s
  String#start_with?      6.314M (± 4.3%) i/s -     31.554M in   5.007207s

Comparison:
  String#start_with?:  6314182.0 i/s
       String#match?:  5138115.1 i/s - 1.23x  slower
           String#=~:  1088461.5 i/s - 5.80x  slower
$ ruby -v code/string/end-string-checking-match-vs-end_with.rb
  ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]

  Calculating -------------------------------------
             String#=~    918.101k (± 6.0%) i/s -      4.650M in   5.084079s
         String#match?      3.009M (± 6.8%) i/s -     14.991M in   5.005691s
      String#end_with?      4.548M (± 9.3%) i/s -     22.684M in   5.034115s

  Comparison:
      String#end_with?:  4547871.0 i/s
         String#match?:  3008554.5 i/s - 1.51x  slower
             String#=~:   918100.5 i/s - 4.95x  slower

String#start_with? vs String#[].== code

$ ruby -v code/string/end-string-checking-match-vs-end_with.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
  String#start_with?      2.047M (± 4.5%) i/s -     10.242M in   5.015146s
    String#[0, n] ==    711.802k (± 7.3%) i/s -      3.551M in   5.019543s
   String#[RANGE] ==    651.751k (± 6.2%) i/s -      3.296M in   5.078772s
   String#[0...n] ==    427.207k (± 5.7%) i/s -      2.136M in   5.019245s

Comparison:
  String#start_with?:  2046618.9 i/s
    String#[0, n] ==:   711802.3 i/s - 2.88x slower
   String#[RANGE] ==:   651751.2 i/s - 3.14x slower
   String#[0...n] ==:   427206.8 i/s - 4.79x slower

Regexp#=== vs String#match vs String#=~ vs String#match? code

String#match? and Regexp#match? are available on Ruby 2.4 or later. ActiveSupport provides a forward compatible extension of Regexp for older Rubies without the speed improvement.

:warning: 
Sometimes you can't replace match with match?
This is only useful for cases where you are checking 
for a match and not using the resultant match object. 
:warning: 
Regexp#=== is also faster than String#match but you need to switch the order of arguments.

$ ruby -v code/string/===-vs-=~-vs-match.rb
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]

Calculating -------------------------------------
       String#match?      6.284M (± 5.6%) i/s -     31.324M in   5.001471s
           String#=~      2.581M (± 4.7%) i/s -     12.977M in   5.038887s
          Regexp#===      2.482M (± 4.1%) i/s -     12.397M in   5.002808s
        String#match      2.097M (± 4.3%) i/s -     10.592M in   5.060535s

Comparison:
       String#match?:  6283591.8 i/s
           String#=~:  2581356.8 i/s - 2.43x  slower
          Regexp#===:  2482379.7 i/s - 2.53x  slower
        String#match:  2096984.3 i/s - 3.00x  slower

See #59 and #62 for discussions.

String#gsub vs String#sub vs String#[]= code

$ ruby -v code/string/gsub-vs-sub.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]

Warming up --------------------------------------
         String#gsub    48.360k i/100ms
          String#sub    45.739k i/100ms
String#dup["string"]=   59.896k i/100ms
Calculating -------------------------------------
         String#gsub    647.666k (± 3.3%) i/s -      3.240M in   5.008504s
          String#sub    756.665k (± 2.0%) i/s -      3.796M in   5.019235s
String#dup["string"]=   917.873k (± 1.8%) i/s -      4.612M in   5.026253s

Comparison:
String#dup["string"]=:   917873.1 i/s
          String#sub:    756664.7 i/s - 1.21x slower
         String#gsub:    647665.6 i/s - 1.42x slower

String#gsub vs String#tr code

rails/rails#17257

$ ruby -v code/string/gsub-vs-tr.rb
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]

Calculating -------------------------------------
         String#gsub    38.268k i/100ms
           String#tr    83.210k i/100ms
-------------------------------------------------
         String#gsub    516.604k (± 4.4%) i/s -      2.602M
           String#tr      1.862M (± 4.0%) i/s -      9.320M

Comparison:
           String#tr:  1861860.4 i/s
         String#gsub:   516604.2 i/s - 3.60x slower

Mutable vs Immutable code

$ ruby -v code/string/mutable_vs_immutable_strings.rb
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin14]

Calculating -------------------------------------
      Without Freeze      7.279M (± 6.6%) i/s -     36.451M in   5.029785s
         With Freeze      9.329M (± 7.9%) i/s -     46.370M in   5.001345s

Comparison:
         With Freeze:  9329054.3 i/s
      Without Freeze:  7279203.1 i/s - 1.28x slower

String#sub! vs String#gsub! vs String#[]= code

Note that String#[] will throw an IndexError when given string or regexp not matched.

$ ruby -v code/string/sub\!-vs-gsub\!-vs-\[\]\=.rb
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

Calculating -------------------------------------
  String#['string']=    74.512k i/100ms
 String#sub!'string'    52.801k i/100ms
String#gsub!'string'    34.480k i/100ms
  String#[/regexp/]=    55.325k i/100ms
 String#sub!/regexp/    45.770k i/100ms
String#gsub!/regexp/    27.665k i/100ms
-------------------------------------------------
  String#['string']=      1.215M (± 6.2%) i/s -      6.110M
 String#sub!'string'    752.731k (± 6.2%) i/s -      3.749M
String#gsub!'string'    481.183k (± 4.4%) i/s -      2.414M
  String#[/regexp/]=    840.615k (± 5.3%) i/s -      4.205M
 String#sub!/regexp/    663.075k (± 7.8%) i/s -      3.295M
String#gsub!/regexp/    342.004k (± 7.5%) i/s -      1.715M

Comparison:
  String#['string']=:  1214845.5 i/s
  String#[/regexp/]=:   840615.2 i/s - 1.45x slower
 String#sub!'string':   752731.4 i/s - 1.61x slower
 String#sub!/regexp/:   663075.3 i/s - 1.83x slower
String#gsub!'string':   481183.5 i/s - 2.52x slower
String#gsub!/regexp/:   342003.8 i/s - 3.55x slower

String#sub vs String#delete_prefix code

Ruby 2.5 introduced String#delete_prefix. Note that this can only be used for removing characters from the start of a string.

$ ruby -v code/string/sub-vs-delete_prefix.rb
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]
Calculating -------------------------------------
String#delete_prefix      4.112M (± 1.8%) i/s -     20.707M in   5.037928s
          String#sub    814.725k (± 1.4%) i/s -      4.088M in   5.018962s

Comparison:
String#delete_prefix:  4111531.1 i/s
          String#sub:   814725.3 i/s - 5.05x  slower

String#sub vs String#chomp vs String#delete_suffix code

Ruby 2.5 introduced String#delete_suffix as a counterpart to delete_prefix. The performance gain over chomp is small and during some runs the difference falls within the error margin. Note that this can only be used for removing characters from the end of a string.

$ ruby -v code/string/sub-vs-chomp-vs-delete_suffix.rb
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]
Calculating -------------------------------------
        String#sub    838.415k (± 1.7%) i/s -      4.214M in   5.027412s
      String#chomp      3.951M (± 2.1%) i/s -     19.813M in   5.017089s
String#delete_suffix    4.202M (± 2.1%) i/s -     21.075M in   5.017429s

Comparison:
String#delete_suffix:  4202201.7 i/s
        String#chomp:  3950921.9 i/s - 1.06x  slower
          String#sub:   838415.3 i/s - 5.01x  slower

String#unpack1 vs String#unpack[0] code

Ruby 2.4.0 introduced unpack1 to skip creating the intermediate array object.

$ ruby -v code/string/unpack1-vs-unpack\[0\].rb
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]
Warming up --------------------------------------
      String#unpack1   224.291k i/100ms
    String#unpack[0]   201.870k i/100ms
Calculating -------------------------------------
      String#unpack1      4.864M (± 4.2%) i/s -     24.448M in   5.035203s
    String#unpack[0]      3.778M (± 4.0%) i/s -     18.976M in   5.031253s

Comparison:
      String#unpack1:  4864467.2 i/s
    String#unpack[0]:  3777815.6 i/s - 1.29x  slower

Remove extra spaces (or other contiguous characters) code

The code is tested against contiguous spaces but should work for other chars too.

$ ruby -v code/string/remove-extra-spaces-or-other-chars.rb
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]
Warming up --------------------------------------
 String#gsub/regex+/     1.644k i/100ms
      String#squeeze    24.681k i/100ms
Calculating -------------------------------------
 String#gsub/regex+/     14.668k (± 5.1%) i/s -     73.980k in   5.056887s
      String#squeeze    372.910k (± 8.4%) i/s -      1.851M in   5.011881s

Comparison:
      String#squeeze:   372910.3 i/s
 String#gsub/regex+/:    14668.1 i/s - 25.42x  slower

Time

Time.iso8601 vs Time.parse code

When expecting well-formatted data from e.g. an API, iso8601 is faster and will raise an ArgumentError on malformed input.

$ ruby -v code/time/iso8601-vs-parse.rb
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]
Warming up --------------------------------------
        Time.iso8601    10.234k i/100ms
          Time.parse     4.228k i/100ms
Calculating -------------------------------------
        Time.iso8601    114.485k (± 3.5%) i/s -    573.104k in   5.012008s
          Time.parse     43.711k (± 4.1%) i/s -    219.856k in   5.038349s

Comparison:
        Time.iso8601:   114485.1 i/s
          Time.parse:    43710.9 i/s - 2.62x  slower

Range

cover? vs include? code

cover? only check if it is within the start and end, include? needs to traverse the whole range.

$ ruby -v code/range/cover-vs-include.rb
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]

Calculating -------------------------------------
        range#cover?    85.467k i/100ms
      range#include?     7.720k i/100ms
       range#member?     7.783k i/100ms
       plain compare   102.189k i/100ms
-------------------------------------------------
        range#cover?      1.816M (± 5.6%) i/s -      9.060M
      range#include?     83.344k (± 5.0%) i/s -    416.880k
       range#member?     82.654k (± 5.0%) i/s -    412.499k
       plain compare      2.581M (± 6.2%) i/s -     12.876M

Comparison:
       plain compare:  2581211.8 i/s
        range#cover?:  1816038.5 i/s - 1.42x slower
      range#include?:    83343.9 i/s - 30.97x slower
       range#member?:    82654.1 i/s - 31.23x slower

Less idiomatic but with significant performance ruby

Checkout: https://github.com/JuanitoFatas/fast-ruby/wiki/Less-idiomatic-but-with-significant-performance-difference

Submit New Entry

Please! Edit this README.md then Submit a Awesome Pull Request!

Something went wrong

Code example is wrong? :cry: Got better example? :heart_eyes: Excellent!

Please open an issue or Open a Pull Request to fix it.

Thank you in advance! :wink: :beer:

One more thing

Share this with your #Rubyfriends! <3

Brought to you by @JuanitoFatas

Feel free to talk with me on Twitter! <3

Also Checkout

Derailed Benchmarks

Go faster, off the Rails - Benchmarks for your whole Rails app

Benchmarking Ruby

Talk by Davy Stevenson @ RubyConf 2014.

davy/benchmark-bigo

Provides Big O notation benchmarking for Ruby.

The Ruby Challenge

Talk by Prem Sichanugrist @ Ruby Kaigi 2014.

Fasterer

Make your Rubies go faster with this command line tool.

License

CC-BY-SA

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Code License

CC0 1.0 Universal

To the extent possible under law, @JuanitoFatas has waived all copyright and related or neighboring rights to "fast-ruby".

This work belongs to the community.


Author:  fastruby
Source code: https://github.com/fastruby/fast-ruby
License:

#ruby #ruby-on-rails 

Jsonapi Serializer: A Fast JSON:API Serializer for Ruby Objects.

JSON:API Serialization Library

:warning: :construction: v2 (the master branch) is in maintenance mode! :construction: :warning:

We'll gladly accept bugfixes and security-related fixes for v2 (the master branch), but at this stage, contributions for new features/improvements are welcome only for v3. Please feel free to leave comments in the v3 Pull Request.


A fast JSON:API serializer for Ruby Objects.

Previously this project was called fast_jsonapi, we forked the project and renamed it to jsonapi/serializer in order to keep it alive.

We would like to thank the Netflix team for the initial work and to all our contributors and users for the continuous support!

Performance Comparison

We compare serialization times with ActiveModelSerializer and alternative implementations as part of performance tests available at jsonapi-serializer/comparisons.

We want to ensure that with every change on this library, serialization time stays significantly faster than the performance provided by the alternatives. Please read the performance article in the docs folder for any questions related to methodology.

Table of Contents

Features

  • Declaration syntax similar to Active Model Serializer
  • Support for belongs_to, has_many and has_one
  • Support for compound documents (included)
  • Optimized serialization of compound documents
  • Caching

Installation

Add this line to your application's Gemfile:

gem 'jsonapi-serializer'

Execute:

$ bundle install

Usage

Rails Generator

You can use the bundled generator if you are using the library inside of a Rails project:

rails g serializer Movie name year

This will create a new serializer in app/serializers/movie_serializer.rb

Model Definition

class Movie
  attr_accessor :id, :name, :year, :actor_ids, :owner_id, :movie_type_id
end

Serializer Definition

class MovieSerializer
  include JSONAPI::Serializer

  set_type :movie  # optional
  set_id :owner_id # optional
  attributes :name, :year
  has_many :actors
  belongs_to :owner, record_type: :user
  belongs_to :movie_type
end

Sample Object

movie = Movie.new
movie.id = 232
movie.name = 'test movie'
movie.actor_ids = [1, 2, 3]
movie.owner_id = 3
movie.movie_type_id = 1
movie

movies =
  2.times.map do |i|
    m = Movie.new
    m.id = i + 1
    m.name = "test movie #{i}"
    m.actor_ids = [1, 2, 3]
    m.owner_id = 3
    m.movie_type_id = 1
    m
  end

Object Serialization

Return a hash

hash = MovieSerializer.new(movie).serializable_hash

Return Serialized JSON

json_string = MovieSerializer.new(movie).serializable_hash.to_json

Serialized Output

{
  "data": {
    "id": "3",
    "type": "movie",
    "attributes": {
      "name": "test movie",
      "year": null
    },
    "relationships": {
      "actors": {
        "data": [
          {
            "id": "1",
            "type": "actor"
          },
          {
            "id": "2",
            "type": "actor"
          }
        ]
      },
      "owner": {
        "data": {
          "id": "3",
          "type": "user"
        }
      }
    }
  }
}

The Optionality of set_type

By default fast_jsonapi will try to figure the type based on the name of the serializer class. For example class MovieSerializer will automatically have a type of :movie. If your serializer class name does not follow this format, you have to manually state the set_type at the serializer.

Key Transforms

By default fast_jsonapi underscores the key names. It supports the same key transforms that are supported by AMS. Here is the syntax of specifying a key transform

class MovieSerializer
  include JSONAPI::Serializer

  # Available options :camel, :camel_lower, :dash, :underscore(default)
  set_key_transform :camel
end

Here are examples of how these options transform the keys

set_key_transform :camel # "some_key" => "SomeKey"
set_key_transform :camel_lower # "some_key" => "someKey"
set_key_transform :dash # "some_key" => "some-key"
set_key_transform :underscore # "some_key" => "some_key"

Attributes

Attributes are defined using the attributes method. This method is also aliased as attribute, which is useful when defining a single attribute.

By default, attributes are read directly from the model property of the same name. In this example, name is expected to be a property of the object being serialized:

class MovieSerializer
  include JSONAPI::Serializer

  attribute :name
end

Custom attributes that must be serialized but do not exist on the model can be declared using Ruby block syntax:

class MovieSerializer
  include JSONAPI::Serializer

  attributes :name, :year

  attribute :name_with_year do |object|
    "#{object.name} (#{object.year})"
  end
end

The block syntax can also be used to override the property on the object:

class MovieSerializer
  include JSONAPI::Serializer

  attribute :name do |object|
    "#{object.name} Part 2"
  end
end

Attributes can also use a different name by passing the original method or accessor with a proc shortcut:

class MovieSerializer
  include JSONAPI::Serializer

  attributes :name

  attribute :released_in_year, &:year
end

Links Per Object

Links are defined using the link method. By default, links are read directly from the model property of the same name. In this example, public_url is expected to be a property of the object being serialized.

You can configure the method to use on the object for example a link with key self will get set to the value returned by a method called url on the movie object.

You can also use a block to define a url as shown in custom_url. You can access params in these blocks as well as shown in personalized_url

class MovieSerializer
  include JSONAPI::Serializer

  link :public_url

  link :self, :url

  link :custom_url do |object|
    "https://movies.com/#{object.name}-(#{object.year})"
  end

  link :personalized_url do |object, params|
    "https://movies.com/#{object.name}-#{params[:user].reference_code}"
  end
end

Links on a Relationship

You can specify relationship links by using the links: option on the serializer. Relationship links in JSON API are useful if you want to load a parent document and then load associated documents later due to size constraints (see related resource links)

class MovieSerializer
  include JSONAPI::Serializer

  has_many :actors, links: {
    self: :url,
    related: -> (object) {
      "https://movies.com/#{object.id}/actors"
    }
  }
end

Relationship links can also be configured to be defined as a method on the object.

  has_many :actors, links: :actor_relationship_links

This will create a self reference for the relationship, and a related link for loading the actors relationship later. NB: This will not automatically disable loading the data in the relationship, you'll need to do that using the lazy_load_data option:

  has_many :actors, lazy_load_data: true, links: {
    self: :url,
    related: -> (object) {
      "https://movies.com/#{object.id}/actors"
    }
  }

Meta Per Resource

For every resource in the collection, you can include a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship.

class MovieSerializer
  include JSONAPI::Serializer

  meta do |movie|
    {
      years_since_release: Date.current.year - movie.year
    }
  end
end

Meta on a Relationship

You can specify relationship meta by using the meta: option on the serializer. Relationship meta in JSON API is useful if you wish to provide non-standard meta-information about the relationship.

Meta can be defined either by passing a static hash or by using Proc to the meta key. In the latter case, the record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.

class MovieSerializer
  include JSONAPI::Serializer

  has_many :actors, meta: Proc.new do |movie_record, params|
    { count: movie_record.actors.length }
  end
end

Compound Document

Support for top-level and nested included associations through options[:include].

options = {}
options[:meta] = { total: 2 }
options[:links] = {
  self: '...',
  next: '...',
  prev: '...'
}
options[:include] = [:actors, :'actors.agency', :'actors.agency.state']
MovieSerializer.new(movies, options).serializable_hash.to_json

Collection Serialization

options[:meta] = { total: 2 }
options[:links] = {
  self: '...',
  next: '...',
  prev: '...'
}
hash = MovieSerializer.new(movies, options).serializable_hash
json_string = MovieSerializer.new(movies, options).serializable_hash.to_json

Control Over Collection Serialization

You can use is_collection option to have better control over collection serialization.

If this option is not provided or nil autodetect logic is used to try understand if provided resource is a single object or collection.

Autodetect logic is compatible with most DB toolkits (ActiveRecord, Sequel, etc.) but cannot guarantee that single vs collection will be always detected properly.

options[:is_collection]

was introduced to be able to have precise control this behavior

  • nil or not provided: will try to autodetect single vs collection (please, see notes above)
  • true will always treat input resource as collection
  • false will always treat input resource as single object

Caching

To enable caching, use cache_options store: <cache_store>:

class MovieSerializer
  include JSONAPI::Serializer

  # use rails cache with a separate namespace and fixed expiry
  cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end

store is required can be anything that implements a #fetch(record, **options, &block) method:

  • record is the record that is currently serialized
  • options is everything that was passed to cache_options except store, so it can be everyhing the cache store supports
  • &block should be executed to fetch new data if cache is empty

So for the example above it will call the cache instance like this:

Rails.cache.fetch(record, namespace: 'jsonapi-serializer', expires_in: 1.hour) { ... }

Caching and Sparse Fieldsets

If caching is enabled and fields are provided to the serializer, the fieldset will be appended to the cache key's namespace.

For example, given the following serializer definition and instance:

class ActorSerializer
  include JSONAPI::Serializer

  attributes :first_name, :last_name

  cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end

serializer = ActorSerializer.new(actor, { fields: { actor: [:first_name] } })

The following cache namespace will be generated: 'jsonapi-serializer-fieldset:first_name'.

Params

In some cases, attribute values might require more information than what is available on the record, for example, access privileges or other information related to a current authenticated user. The options[:params] value covers these cases by allowing you to pass in a hash of additional parameters necessary for your use case.

Leveraging the new params is easy, when you define a custom id, attribute or relationship with a block you opt-in to using params by adding it as a block parameter.

class MovieSerializer
  include JSONAPI::Serializer

  set_id do |movie, params|
    # in here, params is a hash containing the `:admin` key
    params[:admin] ? movie.owner_id : "movie-#{movie.id}"
  end

  attributes :name, :year
  attribute :can_view_early do |movie, params|
    # in here, params is a hash containing the `:current_user` key
    params[:current_user].is_employee? ? true : false
  end

  belongs_to :primary_agent do |movie, params|
    # in here, params is a hash containing the `:current_user` key
    params[:current_user]
  end
end

# ...
current_user = User.find(cookies[:current_user_id])
serializer = MovieSerializer.new(movie, {params: {current_user: current_user}})
serializer.serializable_hash

Custom attributes and relationships that only receive the resource are still possible by defining the block to only receive one argument.

Conditional Attributes

Conditional attributes can be defined by passing a Proc to the if key on the attribute method. Return true if the attribute should be serialized, and false if not. The record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.

class MovieSerializer
  include JSONAPI::Serializer

  attributes :name, :year
  attribute :release_year, if: Proc.new { |record|
    # Release year will only be serialized if it's greater than 1990
    record.release_year > 1990
  }

  attribute :director, if: Proc.new { |record, params|
    # The director will be serialized only if the :admin key of params is true
    params && params[:admin] == true
  }

  # Custom attribute `name_year` will only be serialized if both `name` and `year` fields are present
  attribute :name_year, if: Proc.new { |record|
    record.name.present? && record.year.present?
  } do |object|
    "#{object.name} - #{object.year}"
  end
end

# ...
current_user = User.find(cookies[:current_user_id])
serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? }})
serializer.serializable_hash

Conditional Relationships

Conditional relationships can be defined by passing a Proc to the if key. Return true if the relationship should be serialized, and false if not. The record and any params passed to the serializer are available inside the Proc as the first and second parameters, respectively.

class MovieSerializer
  include JSONAPI::Serializer

  # Actors will only be serialized if the record has any associated actors
  has_many :actors, if: Proc.new { |record| record.actors.any? }

  # Owner will only be serialized if the :admin key of params is true
  belongs_to :owner, if: Proc.new { |record, params| params && params[:admin] == true }
end

# ...
current_user = User.find(cookies[:current_user_id])
serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? }})
serializer.serializable_hash

Specifying a Relationship Serializer

In many cases, the relationship can automatically detect the serializer to use.

class MovieSerializer
  include JSONAPI::Serializer

  # resolves to StudioSerializer
  belongs_to :studio
  # resolves to ActorSerializer
  has_many :actors
end

At other times, such as when a property name differs from the class name, you may need to explicitly state the serializer to use. You can do so by specifying a different symbol or the serializer class itself (which is the recommended usage):

class MovieSerializer
  include JSONAPI::Serializer

  # resolves to MovieStudioSerializer
  belongs_to :studio, serializer: :movie_studio
  # resolves to PerformerSerializer
  has_many :actors, serializer: PerformerSerializer
end

For more advanced cases, such as polymorphic relationships and Single Table Inheritance, you may need even greater control to select the serializer based on the specific object or some specified serialization parameters. You can do by defining the serializer as a Proc:

class MovieSerializer
  include JSONAPI::Serializer

  has_many :actors, serializer: Proc.new do |record, params|
    if record.comedian?
      ComedianSerializer
    elsif params[:use_drama_serializer]
      DramaSerializer
    else
      ActorSerializer
    end
  end
end

Ordering has_many Relationship

You can order the has_many relationship by providing a block:

class MovieSerializer
  include JSONAPI::Serializer

  has_many :actors do |movie|
    movie.actors.order(position: :asc)
  end
end

Sparse Fieldsets

Attributes and relationships can be selectively returned per record type by using the fields option.

class MovieSerializer
  include JSONAPI::Serializer

  attributes :name, :year
end

serializer = MovieSerializer.new(movie, { fields: { movie: [:name] } })
serializer.serializable_hash

Using helper methods

You can mix-in code from another ruby module into your serializer class to reuse functions across your app.

Since a serializer is evaluated in a the context of a class rather than an instance of a class, you need to make sure that your methods act as class methods when mixed in.

Using ActiveSupport::Concern


module AvatarHelper
  extend ActiveSupport::Concern

  class_methods do
    def avatar_url(user)
      user.image.url
    end
  end
end

class UserSerializer
  include JSONAPI::Serializer

  include AvatarHelper # mixes in your helper method as class method

  set_type :user

  attributes :name, :email

  attribute :avatar do |user|
    avatar_url(user)
  end
end

Using Plain Old Ruby

module AvatarHelper
  def avatar_url(user)
    user.image.url
  end
end

class UserSerializer
  include JSONAPI::Serializer

  extend AvatarHelper # mixes in your helper method as class method

  set_type :user

  attributes :name, :email

  attribute :avatar do |user|
    avatar_url(user)
  end
end

Customizable Options

OptionPurposeExample
set_typeType name of Objectset_type :movie
keyKey of Objectbelongs_to :owner, key: :user
set_idID of Objectset_id :owner_id or set_id { |record, params| params[:admin] ? record.id : "#{record.name.downcase}-#{record.id}" }
cache_optionsHash with store to enable caching and optional further cache optionscache_options store: ActiveSupport::Cache::MemoryStore.new, expires_in: 5.minutes
id_method_nameSet custom method name to get ID of an object (If block is provided for the relationship, id_method_name is invoked on the return value of the block instead of the resource object)has_many :locations, id_method_name: :place_ids
object_method_nameSet custom method name to get related objectshas_many :locations, object_method_name: :places
record_typeSet custom Object Type for a relationshipbelongs_to :owner, record_type: :user
serializerSet custom Serializer for a relationshiphas_many :actors, serializer: :custom_actor, has_many :actors, serializer: MyApp::Api::V1::ActorSerializer, or has_many :actors, serializer -> (object, params) { (return a serializer class) }
polymorphicAllows different record types for a polymorphic associationhas_many :targets, polymorphic: true
polymorphicSets custom record types for each object class in a polymorphic associationhas_many :targets, polymorphic: { Person => :person, Group => :group }

Performance Instrumentation

Performance instrumentation is available by using the active_support/notifications.

To enable it, include the module in your serializer class:

require 'jsonapi/serializer'
require 'jsonapi/serializer/instrumentation'

class MovieSerializer
  include JSONAPI::Serializer
  include JSONAPI::Serializer::Instrumentation

  # ...
end

Skylight integration is also available and supported by us, follow the Skylight documentation to enable it.

Running Tests

The project has and requires unit tests, functional tests and performance tests. To run tests use the following command:

rspec

Deserialization

We currently do not support deserialization, but we recommend to use any of the next gems:

JSONAPI.rb

This gem provides the next features alongside deserialization:

  • Collection meta
  • Error handling
  • Includes and sparse fields
  • Filtering and sorting
  • Pagination

Migrating from Netflix/fast_jsonapi

If you come from Netflix/fast_jsonapi, here is the instructions to switch.

Modify your Gemfile

- gem 'fast_jsonapi'
+ gem 'jsonapi-serializer'

Replace all constant references

class MovieSerializer
- include FastJsonapi::ObjectSerializer
+ include JSONAPI::Serializer
end

Replace removed methods

- json_string = MovieSerializer.new(movie).serialized_json
+ json_string = MovieSerializer.new(movie).serializable_hash.to_json

Replace require references

- require 'fast_jsonapi'
+ require 'jsonapi/serializer'

Update your cache options

See docs.

- cache_options enabled: true, cache_length: 12.hours
+ cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour

Contributing

Please follow the instructions we provide as part of the issue and pull request creation processes.

This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.


Author: jsonapi-serializer
Source code: https://github.com/jsonapi-serializer/jsonapi-serializer
License: Apache-2.0 license

#ruby  #ruby-on-rails