|
1 | 1 | class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapter |
2 | | - extend ActiveSupport::Autoload |
3 | | - autoload :PaginationLinks |
4 | | - autoload :FragmentCache |
5 | | - |
6 | | - def initialize(serializer, options = {}) |
7 | | - super |
8 | | - @hash = { data: [] } |
9 | | - |
10 | | - @options[:include] ||= [] |
11 | | - if @options[:include].is_a?(String) |
12 | | - @options[:include] = @options[:include].split(',') |
13 | | - end |
14 | | - |
15 | | - fields = options.delete(:fields) |
16 | | - if fields |
17 | | - @fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key) |
18 | | - else |
19 | | - @fieldset = options[:fieldset] |
20 | | - end |
21 | | - end |
22 | | - |
23 | | - def serializable_hash(options = nil) |
24 | | - options ||= {} |
25 | | - if serializer.respond_to?(:each) |
26 | | - serializer.each do |s| |
27 | | - result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options) |
28 | | - @hash[:data] << result[:data] |
29 | | - |
30 | | - if result[:included] |
31 | | - @hash[:included] ||= [] |
32 | | - @hash[:included] |= result[:included] |
33 | | - end |
34 | | - end |
35 | | - |
36 | | - add_links(options) |
37 | | - else |
38 | | - primary_data = primary_data_for(serializer, options) |
39 | | - relationships = relationships_for(serializer) |
40 | | - included = included_for(serializer) |
41 | | - @hash[:data] = primary_data |
42 | | - @hash[:data][:relationships] = relationships if relationships.any? |
43 | | - @hash[:included] = included if included.any? |
44 | | - end |
45 | | - @hash |
46 | | - end |
47 | | - |
48 | | - def fragment_cache(cached_hash, non_cached_hash) |
49 | | - root = false if @options.include?(:include) |
50 | | - ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash) |
51 | | - end |
52 | | - |
53 | | - private |
54 | | - |
55 | | - def resource_identifier_type_for(serializer) |
56 | | - if ActiveModel::Serializer.config.jsonapi_resource_type == :singular |
57 | | - serializer.object.class.model_name.singular |
58 | | - else |
59 | | - serializer.object.class.model_name.plural |
60 | | - end |
61 | | - end |
62 | | - |
63 | | - def add_relationships(resource, name, serializers) |
64 | | - resource[:relationships] ||= {} |
65 | | - resource[:relationships][name] ||= { data: [] } |
66 | | - resource[:relationships][name][:data] += serializers.map { |serializer| { type: serializer.json_api_type, id: serializer.id.to_s } } |
67 | | - end |
68 | | - |
69 | | - def resource_identifier_id_for(serializer) |
70 | | - if serializer.respond_to?(:id) |
71 | | - serializer.id |
72 | | - else |
73 | | - serializer.object.id |
74 | | - end |
75 | | - end |
76 | | - |
77 | | - def resource_identifier_for(serializer) |
78 | | - type = resource_identifier_type_for(serializer) |
79 | | - id = resource_identifier_id_for(serializer) |
80 | | - |
81 | | - { id: id.to_s, type: type } |
82 | | - end |
83 | | - |
84 | | - def resource_object_for(serializer, options = {}) |
85 | | - options[:fields] = @fieldset && @fieldset.fields_for(serializer) |
86 | | - |
87 | | - cache_check(serializer) do |
88 | | - result = resource_identifier_for(serializer) |
89 | | - attributes = serializer.attributes(options).except(:id) |
90 | | - result[:attributes] = attributes if attributes.any? |
91 | | - result |
92 | | - end |
93 | | - end |
94 | | - |
95 | | - def primary_data_for(serializer, options) |
96 | | - if serializer.respond_to?(:each) |
97 | | - serializer.map { |s| resource_object_for(s, options) } |
98 | | - else |
99 | | - resource_object_for(serializer, options) |
100 | | - end |
101 | | - end |
102 | | - |
103 | | - def relationship_value_for(serializer, options = {}) |
104 | | - if serializer.respond_to?(:each) |
105 | | - serializer.map { |s| resource_identifier_for(s) } |
106 | | - else |
107 | | - if options[:virtual_value] |
108 | | - options[:virtual_value] |
109 | | - elsif serializer && serializer.object |
110 | | - resource_identifier_for(serializer) |
111 | | - end |
112 | | - end |
113 | | - end |
114 | | - |
115 | | - def relationships_for(serializer) |
116 | | - Hash[serializer.associations.map { |association| [association.key, { data: relationship_value_for(association.serializer, association.options) }] }] |
117 | | - end |
118 | | - |
119 | | - def included_for(serializer) |
120 | | - serializer.associations.flat_map { |assoc| _included_for(assoc.key, assoc.serializer) }.uniq |
121 | | - end |
122 | | - |
123 | | - def _included_for(resource_name, serializer, parent = nil) |
124 | | - if serializer.respond_to?(:each) |
125 | | - serializer.flat_map { |s| _included_for(resource_name, s, parent) }.uniq |
126 | | - else |
127 | | - return [] unless serializer && serializer.object |
128 | | - result = [] |
129 | | - resource_path = [parent, resource_name].compact.join('.') |
130 | | - |
131 | | - if include_assoc?(resource_path) |
132 | | - primary_data = primary_data_for(serializer, @options) |
133 | | - relationships = relationships_for(serializer) |
134 | | - primary_data[:relationships] = relationships if relationships.any? |
135 | | - result.push(primary_data) |
136 | | - end |
137 | | - |
138 | | - if include_nested_assoc?(resource_path) |
139 | | - non_empty_associations = serializer.associations.select(&:serializer) |
140 | | - |
141 | | - non_empty_associations.each do |association| |
142 | | - result.concat(_included_for(association.key, association.serializer, resource_path)) |
143 | | - result.uniq! |
144 | | - end |
145 | | - end |
146 | | - result |
147 | | - end |
148 | | - end |
149 | | - |
150 | | - def include_assoc?(assoc) |
151 | | - check_assoc("#{assoc}$") |
152 | | - end |
153 | | - |
154 | | - def include_nested_assoc?(assoc) |
155 | | - check_assoc("#{assoc}.") |
156 | | - end |
157 | | - |
158 | | - def check_assoc(assoc) |
159 | | - @options[:include].any? { |s| s.match(/^#{assoc.gsub('.', '\.')}/) } |
160 | | - end |
161 | | - |
162 | | - def add_links(options) |
163 | | - links = @hash.fetch(:links) { {} } |
164 | | - collection = serializer.object |
165 | | - @hash[:links] = add_pagination_links(links, collection, options) if paginated?(collection) |
166 | | - end |
167 | | - |
168 | | - def add_pagination_links(links, resources, options) |
169 | | - pagination_links = ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks.new(resources, options[:context]).serializable_hash(options) |
170 | | - links.update(pagination_links) |
171 | | - end |
172 | | - |
173 | | - def paginated?(collection) |
174 | | - collection.respond_to?(:current_page) && |
175 | | - collection.respond_to?(:total_pages) && |
176 | | - collection.respond_to?(:size) |
177 | | - end |
| 2 | + extend ActiveSupport::Autoload |
| 3 | + autoload :PaginationLinks |
| 4 | + autoload :FragmentCache |
| 5 | + |
| 6 | + def initialize(serializer, options = {}) |
| 7 | + super |
| 8 | + @hash = { data: [] } |
| 9 | + |
| 10 | + @included = ActiveModel::Serializer::Utils.include_args_to_hash(@options[:include]) |
| 11 | + fields = options.delete(:fields) |
| 12 | + if fields |
| 13 | + @fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key) |
| 14 | + else |
| 15 | + @fieldset = options[:fieldset] |
| 16 | + end |
| 17 | + end |
| 18 | + |
| 19 | + def serializable_hash(options = nil) |
| 20 | + options ||= {} |
| 21 | + if serializer.respond_to?(:each) |
| 22 | + serializer.each do |s| |
| 23 | + result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options) |
| 24 | + @hash[:data] << result[:data] |
| 25 | + |
| 26 | + if result[:included] |
| 27 | + @hash[:included] ||= [] |
| 28 | + @hash[:included] |= result[:included] |
| 29 | + end |
| 30 | + end |
| 31 | + |
| 32 | + add_links(options) |
| 33 | + else |
| 34 | + primary_data = primary_data_for(serializer, options) |
| 35 | + relationships = relationships_for(serializer) |
| 36 | + included = included_for(serializer) |
| 37 | + @hash[:data] = primary_data |
| 38 | + @hash[:data][:relationships] = relationships if relationships.any? |
| 39 | + @hash[:included] = included if included.any? |
| 40 | + end |
| 41 | + @hash |
| 42 | + end |
| 43 | + |
| 44 | + def fragment_cache(cached_hash, non_cached_hash) |
| 45 | + root = false if @options.include?(:include) |
| 46 | + ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash) |
| 47 | + end |
| 48 | + |
| 49 | + private |
| 50 | + |
| 51 | + def resource_identifier_type_for(serializer) |
| 52 | + if ActiveModel::Serializer.config.jsonapi_resource_type == :singular |
| 53 | + serializer.object.class.model_name.singular |
| 54 | + else |
| 55 | + serializer.object.class.model_name.plural |
| 56 | + end |
| 57 | + end |
| 58 | + |
| 59 | + def add_relationships(resource, name, serializers) |
| 60 | + resource[:relationships] ||= {} |
| 61 | + resource[:relationships][name] ||= { data: [] } |
| 62 | + resource[:relationships][name][:data] += serializers.map { |serializer| { type: serializer.json_api_type, id: serializer.id.to_s } } |
| 63 | + end |
| 64 | + |
| 65 | + def resource_identifier_id_for(serializer) |
| 66 | + if serializer.respond_to?(:id) |
| 67 | + serializer.id |
| 68 | + else |
| 69 | + serializer.object.id |
| 70 | + end |
| 71 | + end |
| 72 | + |
| 73 | + def resource_identifier_for(serializer) |
| 74 | + type = resource_identifier_type_for(serializer) |
| 75 | + id = resource_identifier_id_for(serializer) |
| 76 | + |
| 77 | + { id: id.to_s, type: type } |
| 78 | + end |
| 79 | + |
| 80 | + def resource_object_for(serializer, options = {}) |
| 81 | + options[:fields] = @fieldset && @fieldset.fields_for(serializer) |
| 82 | + |
| 83 | + cache_check(serializer) do |
| 84 | + result = resource_identifier_for(serializer) |
| 85 | + attributes = serializer.attributes(options).except(:id) |
| 86 | + result[:attributes] = attributes if attributes.any? |
| 87 | + result |
| 88 | + end |
| 89 | + end |
| 90 | + |
| 91 | + def primary_data_for(serializer, options) |
| 92 | + if serializer.respond_to?(:each) |
| 93 | + serializer.map { |s| resource_object_for(s, options) } |
| 94 | + else |
| 95 | + resource_object_for(serializer, options) |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + def relationship_value_for(serializer, options = {}) |
| 100 | + if serializer.respond_to?(:each) |
| 101 | + serializer.map { |s| resource_identifier_for(s) } |
| 102 | + else |
| 103 | + if options[:virtual_value] |
| 104 | + options[:virtual_value] |
| 105 | + elsif serializer && serializer.object |
| 106 | + resource_identifier_for(serializer) |
| 107 | + end |
| 108 | + end |
| 109 | + end |
| 110 | + |
| 111 | + def relationships_for(serializer) |
| 112 | + Hash[serializer.associations.map { |association| [association.key, { data: relationship_value_for(association.serializer, association.options) }] }] |
| 113 | + end |
| 114 | + |
| 115 | + def included_for(serializer) |
| 116 | + included = @included.flat_map do |inc| |
| 117 | + association = serializer.associations.find { |assoc| assoc.key == inc.first } |
| 118 | + _included_for(association.serializer, inc.second) if association |
| 119 | + end |
| 120 | + |
| 121 | + included.uniq |
| 122 | + end |
| 123 | + |
| 124 | + def _included_for(serializer, includes) |
| 125 | + if serializer.respond_to?(:each) |
| 126 | + serializer.flat_map { |s| _included_for(s, includes) }.uniq |
| 127 | + else |
| 128 | + return [] unless serializer && serializer.object |
| 129 | + |
| 130 | + primary_data = primary_data_for(serializer, @options) |
| 131 | + relationships = relationships_for(serializer) |
| 132 | + primary_data[:relationships] = relationships if relationships.any? |
| 133 | + |
| 134 | + included = [primary_data] |
| 135 | + |
| 136 | + includes.each do |inc| |
| 137 | + association = serializer.associations.find { |assoc| assoc.key == inc.first } |
| 138 | + if association |
| 139 | + included.concat(_included_for(association.serializer, inc.second)) |
| 140 | + included.uniq! |
| 141 | + end |
| 142 | + end |
| 143 | + |
| 144 | + included |
| 145 | + end |
| 146 | + end |
| 147 | + |
| 148 | + def add_links(options) |
| 149 | + links = @hash.fetch(:links) { {} } |
| 150 | + collection = serializer.object |
| 151 | + @hash[:links] = add_pagination_links(links, collection, options) if paginated?(collection) |
| 152 | + end |
| 153 | + |
| 154 | + def add_pagination_links(links, resources, options) |
| 155 | + pagination_links = ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks.new(resources, options[:context]).serializable_hash(options) |
| 156 | + links.update(pagination_links) |
| 157 | + end |
| 158 | + |
| 159 | + def paginated?(collection) |
| 160 | + collection.respond_to?(:current_page) && |
| 161 | + collection.respond_to?(:total_pages) && |
| 162 | + collection.respond_to?(:size) |
| 163 | + end |
178 | 164 | end |
0 commit comments