Skip to content

Commit

Permalink
Merge pull request #273 from Netflix/release-1.3
Browse files Browse the repository at this point in the history
Release 1.3
  • Loading branch information
shishirmk authored Jul 17, 2018
2 parents 81375cf + 115a01a commit 5ff3fa9
Show file tree
Hide file tree
Showing 17 changed files with 647 additions and 164 deletions.
98 changes: 97 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ Fast JSON API serialized 250 records in 3.01 ms
* [Collection Serialization](#collection-serialization)
* [Caching](#caching)
* [Params](#params)
* [Conditional Attributes](#conditional-attributes)
* [Conditional Relationships](#conditional-relationships)
* [Sparse Fieldsets](#sparse-fieldsets)
* [Contributing](#contributing)


Expand Down Expand Up @@ -205,6 +208,18 @@ class MovieSerializer
end
```

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

```ruby
class MovieSerializer
include FastJsonapi::ObjectSerializer

attributes :name

attribute :released_in_year, &:year
end
```

### Links Per Object
Links are defined in FastJsonapi using the `link` method. By default, link 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.

Expand Down Expand Up @@ -259,6 +274,26 @@ hash = MovieSerializer.new([movie, movie], options).serializable_hash
json_string = MovieSerializer.new([movie, movie], options).serialized_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` autedetect 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.

```ruby
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
Requires a `cache_key` method be defined on model:

Expand All @@ -284,7 +319,6 @@ block you opt-in to using params by adding it as a block parameter.

```ruby
class MovieSerializer
class MovieSerializer
include FastJsonapi::ObjectSerializer

attributes :name, :year
Expand All @@ -308,6 +342,68 @@ 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.

```ruby
class MovieSerializer
include FastJsonapi::ObjectSerializer

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

attribute :director, if: Proc.new do |record, params|
# The director will be serialized only if the :admin key of params is true
params && params[:admin] == true
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.

```ruby
class MovieSerializer
include FastJsonapi::ObjectSerializer

# 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
```

### Sparse Fieldsets

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

```ruby
class MovieSerializer
include FastJsonapi::ObjectSerializer

attributes :name, :year
end

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

### Customizable Options

Option | Purpose | Example
Expand Down
30 changes: 14 additions & 16 deletions lib/extensions/has_one.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# frozen_string_literal: true

if defined?(::ActiveRecord)
::ActiveRecord::Associations::Builder::HasOne.class_eval do
# Based on
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
def self.define_accessors(mixin, reflection)
super
name = reflection.name
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_id
# if an attribute is already defined with this methods name we should just use it
return read_attribute(__method__) if has_attribute?(__method__)
association(:#{name}).reader.try(:id)
end
CODE
end
::ActiveRecord::Associations::Builder::HasOne.class_eval do
# Based on
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
def self.define_accessors(mixin, reflection)
super
name = reflection.name
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_id
# if an attribute is already defined with this methods name we should just use it
return read_attribute(__method__) if has_attribute?(__method__)
association(:#{name}).reader.try(:id)
end
CODE
end
end
6 changes: 5 additions & 1 deletion lib/fast_jsonapi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

module FastJsonapi
require 'fast_jsonapi/object_serializer'
require 'extensions/has_one'
if defined?(::Rails)
require 'fast_jsonapi/railtie'
elsif defined?(::ActiveRecord)
require 'extensions/has_one'
end
end
29 changes: 29 additions & 0 deletions lib/fast_jsonapi/attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module FastJsonapi
class Attribute
attr_reader :key, :method, :conditional_proc

def initialize(key:, method:, options: {})
@key = key
@method = method
@conditional_proc = options[:if]
end

def serialize(record, serialization_params, output_hash)
if include_attribute?(record, serialization_params)
output_hash[key] = if method.is_a?(Proc)
method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params)
else
record.public_send(method)
end
end
end

def include_attribute?(record, serialization_params)
if conditional_proc.present?
conditional_proc.call(record, serialization_params)
else
true
end
end
end
end
18 changes: 18 additions & 0 deletions lib/fast_jsonapi/link.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module FastJsonapi
class Link
attr_reader :key, :method

def initialize(key:, method:)
@key = key
@method = method
end

def serialize(record, serialization_params, output_hash)
output_hash[key] = if method.is_a?(Proc)
method.arity == 1 ? method.call(record) : method.call(record, serialization_params)
else
record.public_send(method)
end
end
end
end
Loading

0 comments on commit 5ff3fa9

Please sign in to comment.