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

Refactor JSONAPI adapter #1103

Merged
merged 15 commits into from
Sep 6, 2015
14 changes: 0 additions & 14 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,6 @@ def json_key
@root || object.class.model_name.to_s.underscore
end

def id
object.id if object
end

def json_api_type
if config.jsonapi_resource_type == :plural
object.class.model_name.plural
else
object.class.model_name.singular
end
end

def attributes(options = {})
attributes =
if options[:fields]
Expand All @@ -165,8 +153,6 @@ def attributes(options = {})
self.class._attributes.dup
end

attributes += options[:required_fields] if options[:required_fields]

attributes.each_with_object({}) do |name, hash|
unless self.class._fragmented
hash[name] = send(name)
Expand Down
170 changes: 81 additions & 89 deletions lib/active_model/serializer/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ def initialize(serializer, options = {})
super
@hash = { data: [] }

if fields = options.delete(:fields)
@options[:include] ||= []
if @options[:include].is_a?(String)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain more about this. It's not related to type and id, and I also don't understood the why of String verification + split

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, understood 😁

@options[:include] = @options[:include].split(',')
end

fields = options.delete(:fields)
if fields
@fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
else
@fieldset = options[:fieldset]
Expand All @@ -31,150 +37,136 @@ def serializable_hash(options = nil)

add_links(options)
else
@hash[:data] = attributes_for_serializer(serializer, options)
add_resource_relationships(@hash[:data], serializer)
primary_data = primary_data_for(serializer, options)
relationships = relationships_for(serializer)
included = included_for(serializer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@hash[:data] = primary_data
@hash[:data][:relationships] = relationships if relationships.any?
@hash[:included] = included if included.any?
end
@hash
end

def fragment_cache(cached_hash, non_cached_hash)
root = false if @options.include?(:include)
JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)
end

private

def add_relationships(resource, name, serializers)
resource[:relationships] ||= {}
resource[:relationships][name] ||= { data: [] }
resource[:relationships][name][:data] += serializers.map { |serializer| { type: serializer.json_api_type, id: serializer.id.to_s } }
def resource_identifier_type_for(serializer)
if ActiveModel::Serializer.config.jsonapi_resource_type == :singular
serializer.object.class.model_name.singular
else
serializer.object.class.model_name.plural
end
end

def add_relationship(resource, name, serializer, val = nil)
resource[:relationships] ||= {}
resource[:relationships][name] = { data: val }

if serializer && serializer.object
resource[:relationships][name][:data] = { type: serializer.json_api_type, id: serializer.id.to_s }
def resource_identifier_id_for(serializer)
if serializer.respond_to?(:id)
serializer.id
else
serializer.object.id
end
end

def add_included(resource_name, serializers, parent = nil)
unless serializers.respond_to?(:each)
return unless serializers.object
serializers = Array(serializers)
end
resource_path = [parent, resource_name].compact.join('.')
if include_assoc?(resource_path)
@hash[:included] ||= []
def resource_identifier_for(serializer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

type = resource_identifier_type_for(serializer)
id = resource_identifier_id_for(serializer)

serializers.each do |serializer|
attrs = attributes_for_serializer(serializer, @options)
{ id: id.to_s, type: type }
end

add_resource_relationships(attrs, serializer, add_included: false)
def resource_object_for(serializer, options = {})
options[:fields] = @fieldset && @fieldset.fields_for(serializer)

@hash[:included].push(attrs) unless @hash[:included].include?(attrs)
end
cache_check(serializer) do
result = resource_identifier_for(serializer)
attributes = serializer.attributes(options).except(:id)
result[:attributes] = attributes if attributes.any?
result
end
end

serializers.each do |serializer|
serializer.associations.each do |association|
serializer = association.serializer

add_included(association.key, serializer, resource_path) if serializer
end if include_nested_assoc? resource_path
def primary_data_for(serializer, options)
if serializer.respond_to?(:each)
serializer.map { |s| resource_object_for(s, options) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

else
resource_object_for(serializer, options)
end
end

def attributes_for_serializer(serializer, options)
def relationship_value_for(serializer, options = {})
if serializer.respond_to?(:each)
result = []
serializer.each do |object|
result << resource_object_for(object, options)
end
serializer.map { |s| resource_identifier_for(s) }
else
result = resource_object_for(serializer, options)
if options[:virtual_value]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we don't need required fields because they're the { id: '', type: ''} in the resource identifier object method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly.

options[:virtual_value]
elsif serializer && serializer.object
resource_identifier_for(serializer)
end
end
result
end

def resource_object_for(serializer, options)
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
options[:required_fields] = [:id, :json_api_type]
def relationships_for(serializer)
Hash[serializer.associations.map { |association| [association.key, { data: relationship_value_for(association.serializer, association.options) }] }]
end

cache_check(serializer) do
attributes = serializer.attributes(options)
def included_for(serializer)
serializer.associations.flat_map { |assoc| _included_for(assoc.key, assoc.serializer) }.uniq
end

result = {
id: attributes.delete(:id).to_s,
type: attributes.delete(:json_api_type)
}
def _included_for(resource_name, serializer, parent = nil)
if serializer.respond_to?(:each)
serializer.flat_map { |s| _included_for(resource_name, s, parent) }.uniq
else
return [] unless serializer && serializer.object
result = []
resource_path = [parent, resource_name].compact.join('.')

result[:attributes] = attributes if attributes.any?
if include_assoc?(resource_path)
primary_data = primary_data_for(serializer, @options)
relationships = relationships_for(serializer)
primary_data[:relationships] = relationships if relationships.any?
result.push(primary_data)
end

if include_nested_assoc?(resource_path)
non_empty_associations = serializer.associations.select(&:serializer)

non_empty_associations.each do |association|
result.concat(_included_for(association.key, association.serializer, resource_path))
result.uniq!
end
end
result
end
end

def include_assoc?(assoc)
return false unless @options[:include]
check_assoc("#{assoc}$")
end

def include_nested_assoc?(assoc)
return false unless @options[:include]
check_assoc("#{assoc}.")
end

def check_assoc(assoc)
include_opt = @options[:include]
include_opt = include_opt.split(',') if include_opt.is_a?(String)
include_opt.any? do |s|
s.match(/^#{assoc.gsub('.', '\.')}/)
end
end

def add_resource_relationships(attrs, serializer, options = {})
options[:add_included] = options.fetch(:add_included, true)

serializer.associations.each do |association|
key = association.key
serializer = association.serializer
opts = association.options

attrs[:relationships] ||= {}

if serializer.respond_to?(:each)
add_relationships(attrs, key, serializer)
else
if opts[:virtual_value]
add_relationship(attrs, key, nil, opts[:virtual_value])
else
add_relationship(attrs, key, serializer)
end
end

if options[:add_included]
Array(serializer).each do |s|
add_included(key, s)
end
end
end
@options[:include].any? { |s| s.match(/^#{assoc.gsub('.', '\.')}/) }
end

def add_links(options)
links = @hash.fetch(:links) { {} }
collection = serializer.object
if is_paginated?(collection)
@hash[:links] = add_pagination_links(links, collection, options)
end
@hash[:links] = add_pagination_links(links, collection, options) if paginated?(collection)
end

def add_pagination_links(links, collection, options)
pagination_links = JsonApi::PaginationLinks.new(collection, options[:context]).serializable_hash(options)
links.update(pagination_links)
end

def is_paginated?(collection)
def paginated?(collection)
collection.respond_to?(:current_page) &&
collection.respond_to?(:total_pages) &&
collection.respond_to?(:size)
Expand Down
5 changes: 0 additions & 5 deletions test/serializers/attributes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ def test_attributes_with_fields_option
@profile_serializer.attributes(fields: [:name]))
end

def test_required_fields
assert_equal({ name: 'Name 1', description: 'Description 1' },
@profile_serializer.attributes(fields: [:name, :description], required_fields: [:name]))
end

def test_attributes_inheritance_definition
assert_equal([:id, :body], @serializer_klass._attributes)
end
Expand Down