Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

render_as_json, making sorting fields optional #119

Merged
merged 9 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.9.0 - 2018/11/29

* [FEATURE] Added a `render_as_json` API. Similar to `render_as_hash` but returns a JSONified hash. Please see pr [#119](https://github.com/procore/blueprinter/pull/119). Thanks to [@ritikesh](https://github.com/ritikesh).
* [FEATURE] Sorting of fields in the response is now configurable to sort by definition or by name(asc only). Please see pr [#119](https://github.com/procore/blueprinter/pull/119). Thanks to [@ritikesh](https://github.com/ritikesh).
* [ENHANCEMENT] Updated readme for above features and some existing undocumented features like `exclude fields`, `render_as_hash`. Please see pr [#119](https://github.com/procore/blueprinter/pull/119). Thanks to [@ritikesh](https://github.com/ritikesh).

## 0.8.0 - 2018/11/19

* [FEATURE] Extend Support for other JSON encoders like yajl-ruby. Please see pr [#118](https://github.com/procore/blueprinter/pull/118). Thanks to [@ritikesh](https://github.com/ritikesh).
Expand Down
102 changes: 100 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,40 @@ Output:
}
```

### Exclude fields
You can specifically choose to exclude certain fields for specific views
```ruby
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:
```ruby
puts UserBlueprint.render(user, view: :extended)
```

Output:
```json
{
"uuid": "733f0758-8f21-4719-875f-262c3ec743af",
"address": "123 Fake St.",
"first_name": "John",
"login": "[email protected]"
}
```

### Associations
You may include associated objects. Say for example, a user has projects:
```ruby
Expand Down Expand Up @@ -278,6 +312,42 @@ Output:
}
```

### render_as_hash
Same as `render`, returns a Ruby Hash.

Usage:

```ruby
puts UserBlueprint.render_as_hash(user, company: company)
```

Output:

```ruby
{
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:

```ruby
puts UserBlueprint.render_as_json(user, company: company)
```

Output:

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

### Conditional field

`field` supports `:if` and `:unless` options argument that can be used to serialize the field conditionally.
Expand Down Expand Up @@ -328,6 +398,34 @@ $ 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.

## Sorting
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:

```ruby
Blueprinter.configure do |config|
config.sort_fields_by = :definition
end
```

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

Output:
```json
{
"name": "John Doe",
"email": "[email protected]",
"birthday": "03/04/1994"
}
```

## 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:
Expand All @@ -352,15 +450,15 @@ gem 'oj'
[yajl-ruby](https://github.com/brianmario/yajl-ruby) is a fast and powerful JSON generator/parser. To use `yajl-ruby` in place of `JSON / OJ`, use:

```ruby
require 'yajl' # you can skip this if OJ has already been required.
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](https://github.com/brianmario/yajl-ruby#json-gem-compatibility-api). In this case, `JSON.generate` is patched to use `Yajl::Encoder.encode` internally.
Note: You should be doing this only if you aren't using `yajl-ruby` through the JSON API by requiring `yajl/json_gem`. More details [here](https://github.com/brianmario/yajl-ruby#json-gem-compatibility-api). In this case, `JSON.generate` is patched to use `Yajl::Encoder.encode` internally.

## How to Document

Expand Down
33 changes: 28 additions & 5 deletions lib/blueprinter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,7 @@ def self.association(method, options = {}, &block)
#
# @return [String] JSON formatted String
def self.render(object, options = {})
view_name = options.delete(:view) || :default
jsonify(prepare(object, view_name: view_name, local_options: options))
jsonify(prepare_for_render(object, options))
end

# Generates a hash.
Expand All @@ -196,8 +195,27 @@ def self.render(object, options = {})
#
# @return [Hash]
def self.render_as_hash(object, options= {})
view_name = options.delete(:view) || :default
prepare(object, view_name: view_name, local_options: options)
prepare_for_render(object, options)
end

# Generates a JSONified hash.
# Takes a required object and an optional view.
#
# @param object [Object] the Object to serialize upon.
# @param options [Hash] the options hash which requires a :view. Any
# additional key value pairs will be exposed during serialization.
# @option options [Symbol] :view Defaults to :default.
# The view name that corresponds to the group of
# fields to be serialized.
#
# @example Generating a hash with an extended view
# post = Post.all
# Blueprinter::Base.render_as_json post, view: :extended
# # => [{"id" => "1", "title" => "Hello"},{"id" => "2", "title" => "My Day"}]
#
# @return [Hash]
def self.render_as_json(object, options= {})
prepare_for_render(object, options).as_json
end

# This is the magic method that converts complex objects into a simple hash
Expand Down Expand Up @@ -308,7 +326,12 @@ def self.view(view_name)
@current_view = view_collection[:default]
end

private
# Begin private class methods
def self.prepare_for_render(object, options)
view_name = options.delete(:view) || :default
prepare(object, view_name: view_name, local_options: options)
end
private_class_method :prepare_for_render

def self.inherited(subclass)
subclass.send(:view_collection).inherit(view_collection)
Expand Down
3 changes: 2 additions & 1 deletion lib/blueprinter/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module Blueprinter
class Configuration
attr_accessor :generator, :method
attr_accessor :generator, :method, :sort_fields_by

def initialize
@generator = JSON
@method = :generate
@sort_fields_by = :name_asc
end

def jsonify(blob)
Expand Down
2 changes: 1 addition & 1 deletion lib/blueprinter/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Blueprinter
VERSION = '0.8.0'
VERSION = '0.9.0'
end
21 changes: 17 additions & 4 deletions lib/blueprinter/view_collection.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module Blueprinter
# @api private
class ViewCollection
attr_reader :views
attr_reader :views, :sort_by_definition
def initialize
@views = {
identifier: View.new(:identifier),
default: View.new(:default)
}
@sort_by_definition = Blueprinter.configuration.sort_fields_by.eql?(:definition)
end

def inherit(view_collection)
Expand All @@ -20,7 +21,11 @@ def has_view?(view_name)
end

def fields_for(view_name)
identifier_fields + sortable_fields(view_name).values.sort_by(&:name)
sorted_fields = sortable_fields(view_name).values
unless sort_by_definition
sorted_fields = sorted_fields.sort_by(&:name)
end
identifier_fields + sorted_fields
end

def [](view_name)
Expand All @@ -35,10 +40,10 @@ def identifier_fields

def sortable_fields(view_name)
fields = views[:default].fields
fields = fields.merge(views[view_name].fields)
fields = merge_fields(fields, views[view_name].fields)
views[view_name].included_view_names.each do |included_view_name|
if view_name != included_view_name
fields = fields.merge(sortable_fields(included_view_name))
fields = merge_fields(fields, sortable_fields(included_view_name))
end
end

Expand All @@ -48,5 +53,13 @@ def sortable_fields(view_name)

fields
end

def merge_fields(source_fields, included_fields)
unless sort_by_definition
source_fields.merge(included_fields)
else
included_fields.merge(source_fields)
end
end
end
end
12 changes: 12 additions & 0 deletions spec/integrations/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@
end
end
end

describe '::render_as_json' do
subject { blueprint_with_block.render_as_json(object_with_attributes) }
context 'Outside Rails project' do
context 'Given passed object has dot notation accessible attributes' do
let(:obj) { object_with_attributes }
it 'returns a hash with expected format' do
expect(subject).to eq({ "id" => obj.id, "position_and_company" => "#{obj.position} at #{obj.company}"})
end
end
end
end

describe 'identifier' do
let(:rendered) do
Expand Down
8 changes: 8 additions & 0 deletions spec/units/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
after { Blueprinter.configure { |config|
config.generator = JSON
config.method = :generate
config.sort_fields_by = :name_asc
} }

it 'should set the generator' do
Expand All @@ -22,5 +23,12 @@
expect(Blueprinter.configuration.generator).to be(Yajl::Encoder)
expect(Blueprinter.configuration.method).to be(:encode)
end

it 'should set the sort_fields_by' do
Blueprinter.configure { |config|
config.sort_fields_by = :definition
}
expect(Blueprinter.configuration.sort_fields_by).to be(:definition)
end
end
end