-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Support for Nested Relationships for the Json Adapter #1114
Changes from all commits
edfec6c
8df52d5
4f2ad3e
a8e8515
05ec32a
8a6ee84
ce75e78
de2d9b4
2fd748e
85406f9
75d7a95
670c724
9f8f7fc
f11fa95
4339749
608741f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -7,37 +7,13 @@ class Json < Adapter | |||||
def serializable_hash(options = nil) | ||||||
options ||= {} | ||||||
if serializer.respond_to?(:each) | ||||||
@result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) } | ||||||
@result = serialize_array_without_root(serializer, options) | ||||||
else | ||||||
@hash = {} | ||||||
@result = resource_object_for(serializer) | ||||||
@result = add_resource_relationships(@result, serializer) | ||||||
|
||||||
@core = cache_check(serializer) do | ||||||
serializer.attributes(options) | ||||||
end | ||||||
|
||||||
serializer.associations.each do |association| | ||||||
serializer = association.serializer | ||||||
opts = association.options | ||||||
|
||||||
if serializer.respond_to?(:each) | ||||||
array_serializer = serializer | ||||||
@hash[association.key] = array_serializer.map do |item| | ||||||
cache_check(item) do | ||||||
item.attributes(opts) | ||||||
end | ||||||
end | ||||||
else | ||||||
@hash[association.key] = | ||||||
if serializer && serializer.object | ||||||
cache_check(serializer) do | ||||||
serializer.attributes(options) | ||||||
end | ||||||
elsif opts[:virtual_value] | ||||||
opts[:virtual_value] | ||||||
end | ||||||
end | ||||||
end | ||||||
@result = @core.merge @hash | ||||||
@result | ||||||
end | ||||||
|
||||||
{ root => @result } | ||||||
|
@@ -46,6 +22,111 @@ def serializable_hash(options = nil) | |||||
def fragment_cache(cached_hash, non_cached_hash) | ||||||
Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash) | ||||||
end | ||||||
|
||||||
private | ||||||
|
||||||
# iterate through the associations on the serializer, | ||||||
# adding them to the parent as needed (as singular or plural) | ||||||
# | ||||||
# nested_associations is a list of symbols that governs what | ||||||
# associations on the passed in seralizer to include | ||||||
def add_resource_relationships(parent, serializer, nested_associations = []) | ||||||
# have the include array normalized | ||||||
nested_associations = ActiveModel::Serializer::Utils.include_array_to_hash(nested_associations) | ||||||
|
||||||
included_associations = if nested_associations.present? | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so Eg. so Initially I thought this was to check if it's an already rendered relationship and avoid render it again. but it seems it is used here to loop the associations. What I presume is the the code inside this if might never be executed unless you have something like (probably): Eg. then you will find the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The if body actually decides what nested association to render. Cause, lets say a post has a ton of associations, but you only want the author. (The tests also cover this scenario) |
||||||
serializer.associations.select{ |association| | ||||||
# nested_associations is a hash of: | ||||||
# key => nested association to include | ||||||
nested_associations.has_key?(association.name) | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dig it. |
||||||
else | ||||||
serializer.associations | ||||||
end | ||||||
|
||||||
included_associations.each do |association| | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can loop straight through |
||||||
serializer = association.serializer | ||||||
opts = association.options | ||||||
key = association.key | ||||||
|
||||||
# sanity check if the association has nesting data | ||||||
has_nesting = nested_associations[key].present? | ||||||
if has_nesting | ||||||
include_options_from_parent = { include: nested_associations[key] } | ||||||
opts = opts.merge(include_options_from_parent) | ||||||
end | ||||||
|
||||||
if serializer.respond_to?(:each) | ||||||
parent[key] = add_relationships(serializer, opts) | ||||||
else | ||||||
parent[key] = add_relationship(serializer, opts) | ||||||
end | ||||||
end | ||||||
|
||||||
parent | ||||||
end | ||||||
|
||||||
# add a singular relationship | ||||||
# the options should always belong to the serializer | ||||||
def add_relationship(serializer, options) | ||||||
serialized_relationship = serialized_or_virtual_of(serializer, options) | ||||||
|
||||||
nested_associations_to_include = options[:include] | ||||||
if nested_associations_to_include.present? | ||||||
serialized_relationship = add_resource_relationships( | ||||||
serialized_relationship, | ||||||
serializer, | ||||||
nested_associations_to_include) | ||||||
end | ||||||
|
||||||
serialized_relationship | ||||||
end | ||||||
|
||||||
# add a many relationship | ||||||
def add_relationships(serializer, options) | ||||||
serialize_array(serializer, options) do |serialized_item, item_serializer| | ||||||
nested_associations_to_include = options[:include] | ||||||
|
||||||
if nested_associations_to_include.present? | ||||||
serialized_item = add_resource_relationships( | ||||||
serialized_item, | ||||||
item_serializer, | ||||||
nested_associations_to_include) | ||||||
end | ||||||
|
||||||
serialized_item | ||||||
end | ||||||
end | ||||||
|
||||||
|
||||||
def serialize_array_without_root(serializer, options) | ||||||
serializer.map { |s| FlattenJson.new(s).serializable_hash(options) } | ||||||
end | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the serializer should be calling the adapter... but I guess you didn't add that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the json_api adapter
if serializer.respond_to?(:each)
serializer.each do |s|
result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options)
@hash[:data] << result[:data] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that doesn't look very nice to look at. :-\ aren't there json_api refactors in the prs? I'm curious how that cleans things up. |
||||||
|
||||||
# a virtual value is something that doesn't need a serializer, | ||||||
# such as a ruby array, or any other raw value | ||||||
def serialized_or_virtual_of(serializer, options) | ||||||
if serializer && serializer.object | ||||||
resource_object_for(serializer) | ||||||
elsif options[:virtual_value] | ||||||
options[:virtual_value] | ||||||
end | ||||||
end | ||||||
|
||||||
def serialize_array(serializer, options) | ||||||
serializer.map do |item| | ||||||
serialized_item = resource_object_for(item) | ||||||
serialized_item = yield(serialized_item, item) if block_given? | ||||||
serialized_item | ||||||
end | ||||||
end | ||||||
|
||||||
def resource_object_for(serializer) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice abstraction! 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks! |
||||||
cache_check(serializer) do | ||||||
serializer.attributes(serializer.options) | ||||||
end | ||||||
end | ||||||
|
||||||
end | ||||||
end | ||||||
end | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
module ActiveModel::Serializer::Utils | ||
module_function | ||
|
||
# converts the include hash to a standard format | ||
# for constrained serialization recursion | ||
# | ||
# converts | ||
# [:author, :comments => [:author]] to | ||
# {:author => [], :comments => [:author]} | ||
# | ||
# and | ||
# [:author, :comments => {:author => :bio}, :posts => [:comments]] to | ||
# {:author => [], :comments => {:author => :bio}, :posts => [:comments]} | ||
# | ||
# The data passed in to this method should be an array where the last | ||
# parameter is a hash | ||
# | ||
# the point of this method is to normalize the include | ||
# options for the child relationships. | ||
# if a sub inclusion is still an array after this method, | ||
# it will get converted during the next iteration | ||
def include_array_to_hash(include_array) | ||
# still don't trust input | ||
# but this also allows | ||
# include: :author syntax | ||
include_array = Array[*include_array].compact | ||
|
||
result = {} | ||
|
||
hashes = include_array.select{|a| a.is_a?(Hash)} | ||
non_hashes = include_array - hashes | ||
|
||
hashes += non_hashes.map{ |association_name| { association_name => [] } } | ||
|
||
# now merge all the hashes | ||
hashes.each{|hash| result.merge!(hash) } | ||
|
||
result | ||
end | ||
|
||
|
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍