Skip to content

Commit 8e4702c

Browse files
committed
Merge pull request #298 from thewatts/nw-engines
Engine Support for Links
2 parents b7eea8a + 0fae109 commit 8e4702c

5 files changed

Lines changed: 370 additions & 29 deletions

File tree

lib/jsonapi/link_builder.rb

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
module JSONAPI
2+
class LinkBuilder
3+
attr_reader :base_url,
4+
:primary_resource_klass,
5+
:route_formatter
6+
7+
def initialize(config = {})
8+
@base_url = config[:base_url]
9+
@primary_resource_klass = config[:primary_resource_klass]
10+
@route_formatter = config[:route_formatter]
11+
end
12+
13+
def engine?
14+
!!engine_name
15+
end
16+
17+
def engine_name
18+
@engine_name ||= build_engine_name
19+
end
20+
21+
def primary_resources_url
22+
if engine?
23+
engine_primary_resources_url
24+
else
25+
regular_primary_resources_url
26+
end
27+
end
28+
29+
def query_link(query_params)
30+
"#{ primary_resources_url }?#{ query_params.to_query }"
31+
end
32+
33+
def relationships_related_link(source, association)
34+
"#{ self_link(source) }/#{ route_for_association(association) }"
35+
end
36+
37+
def relationships_self_link(source, association)
38+
"#{ self_link(source) }/relationships/#{ route_for_association(association) }"
39+
end
40+
41+
def self_link(source)
42+
if engine?
43+
engine_resource_url(source)
44+
else
45+
regular_resource_url(source)
46+
end
47+
end
48+
49+
private
50+
51+
def build_engine_name
52+
scopes = module_scopes_from_class(primary_resource_klass)
53+
54+
unless scopes.empty?
55+
"#{ scopes.first.to_s.camelize }::Engine".safe_constantize
56+
end
57+
end
58+
59+
def engine_path_from_resource_class(klass)
60+
path_name = engine_resources_path_name_from_class(klass)
61+
engine_name.routes.url_helpers.send(path_name)
62+
end
63+
64+
def engine_primary_resources_path
65+
engine_path_from_resource_class(primary_resource_klass)
66+
end
67+
68+
def engine_primary_resources_url
69+
"#{ base_url }#{ engine_primary_resources_path }"
70+
end
71+
72+
def engine_resource_path(source)
73+
resource_path_name = engine_resource_path_name_from_source(source)
74+
engine_name.routes.url_helpers.send(resource_path_name, source.id)
75+
end
76+
77+
def engine_resource_path_name_from_source(source)
78+
scopes = module_scopes_from_class(source.class)[1..-1]
79+
base_path_name = scopes.map { |scope| scope.downcase }.join("_")
80+
end_path_name = source.class._type.to_s.singularize
81+
"#{ base_path_name }_#{ end_path_name }_path"
82+
end
83+
84+
def engine_resource_url(source)
85+
"#{ base_url }#{ engine_resource_path(source) }"
86+
end
87+
88+
def engine_resources_path_name_from_class(klass)
89+
scopes = module_scopes_from_class(klass)[1..-1]
90+
base_path_name = scopes.map { |scope| scope.downcase }.join("_")
91+
end_path_name = klass._type.to_s
92+
"#{ base_path_name }_#{ end_path_name }_path"
93+
end
94+
95+
def format_route(route)
96+
route_formatter.format(route.to_s)
97+
end
98+
99+
def formatted_module_path_from_class(klass)
100+
scopes = module_scopes_from_class(klass)
101+
102+
unless scopes.empty?
103+
"/#{ scopes.map(&:downcase).join('/') }/"
104+
else
105+
"/"
106+
end
107+
end
108+
109+
def module_scopes_from_class(klass)
110+
klass.name.to_s.split("::")[0...-1]
111+
end
112+
113+
def regular_primary_resources_path
114+
[
115+
formatted_module_path_from_class(primary_resource_klass),
116+
route_formatter.format(primary_resource_klass._type.to_s),
117+
].join
118+
end
119+
120+
def regular_primary_resources_url
121+
"#{ base_url }#{ regular_primary_resources_path }"
122+
end
123+
124+
def regular_resource_path(source)
125+
[
126+
formatted_module_path_from_class(source.class),
127+
route_formatter.format(source.class._type.to_s),
128+
"/#{ source.id }",
129+
].join
130+
end
131+
132+
def regular_resource_url(source)
133+
"#{ base_url }#{ regular_resource_path(source) }"
134+
end
135+
136+
def route_for_association(association)
137+
format_route(association.name)
138+
end
139+
end
140+
end

lib/jsonapi/resource_serializer.rb

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require_relative "link_builder"
2+
13
module JSONAPI
24
class ResourceSerializer
35

@@ -12,16 +14,15 @@ class ResourceSerializer
1214
# key_formatter: KeyFormatter class to override the default configuration
1315
# base_url: a string to prepend to generated resource links
1416

15-
def initialize(primary_resource_klass, options = {})
16-
@primary_resource_klass = primary_resource_klass
17-
@primary_class_name = @primary_resource_klass._type
17+
attr_reader :url_generator
1818

19-
@fields = options.fetch(:fields, {})
20-
@include = options.fetch(:include, [])
19+
def initialize(primary_resource_klass, options = {})
20+
@primary_class_name = primary_resource_klass._type
21+
@fields = options.fetch(:fields, {})
22+
@include = options.fetch(:include, [])
2123
@include_directives = options[:include_directives]
22-
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
23-
@route_formatter = options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
24-
@base_url = options.fetch(:base_url, '')
24+
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
25+
@url_generator = generate_link_builder(primary_resource_klass, options)
2526
end
2627

2728
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
@@ -68,7 +69,7 @@ def serialize_to_links_hash(source, requested_association)
6869
end
6970

7071
def find_link(query_params)
71-
"#{@base_url}/#{formatted_module_path_from_klass(@primary_resource_klass)}#{@route_formatter.format(@primary_resource_klass._type.to_s)}?#{query_params.to_query}"
72+
url_generator.query_link(query_params)
7273
end
7374

7475
private
@@ -196,38 +197,22 @@ def relationship_data(source, include_directives)
196197

197198
def relationship_links(source)
198199
links = {}
199-
links[:self] = self_href(source)
200+
links[:self] = url_generator.self_link(source)
200201

201202
links
202203
end
203204

204-
def formatted_module_path(source)
205-
formatted_module_path_from_klass(source.class)
206-
end
207-
208-
def formatted_module_path_from_klass(klass)
209-
klass.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : ''
210-
end
211-
212-
def self_href(source)
213-
"#{@base_url}/#{formatted_module_path(source)}#{@route_formatter.format(source.class._type.to_s)}/#{source.id}"
214-
end
215-
216205
def already_serialized?(type, id)
217206
type = format_key(type)
218207
@included_objects.key?(type) && @included_objects[type].key?(id)
219208
end
220209

221-
def format_route(route)
222-
@route_formatter.format(route.to_s)
223-
end
224-
225210
def self_link(source, association)
226-
"#{self_href(source)}/relationships/#{format_route(association.name)}"
211+
url_generator.relationships_self_link(source, association)
227212
end
228213

229214
def related_link(source, association)
230-
"#{self_href(source)}/#{format_route(association.name)}"
215+
url_generator.relationships_related_link(source, association)
231216
end
232217

233218
def has_one_linkage(source, association)
@@ -327,5 +312,13 @@ def format_value(value, format)
327312
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
328313
value_formatter.format(value)
329314
end
315+
316+
def generate_link_builder(primary_resource_klass, options)
317+
LinkBuilder.new(
318+
base_url: options.fetch(:base_url, ''),
319+
route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
320+
primary_resource_klass: primary_resource_klass,
321+
)
322+
end
330323
end
331324
end

test/fixtures/active_record.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,11 +1164,19 @@ class NumeroTelefoneResource < JSONAPI::Resource
11641164
end
11651165
end
11661166

1167+
module MyEngine
1168+
module Api
1169+
module V1
1170+
class PersonResource < JSONAPI::Resource
1171+
end
1172+
end
1173+
end
1174+
end
1175+
11671176
warn 'start testing Name Collisions'
11681177
# The name collisions only emmit warnings. Exceptions would change the flow of the tests
11691178

11701179
class LinksResource < JSONAPI::Resource
1171-
11721180
end
11731181

11741182
class BadlyNamedAttributesResource < JSONAPI::Resource

test/test_helper.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require 'rails/test_help'
1515
require 'minitest/mock'
1616
require 'jsonapi-resources'
17+
require 'pry'
1718

1819
require File.expand_path('../helpers/value_matchers', __FILE__)
1920
require File.expand_path('../helpers/assertions', __FILE__)
@@ -43,6 +44,12 @@ class TestApp < Rails::Application
4344
ActiveSupport::JSON::Encoding.time_precision = 0 if Rails::VERSION::MAJOR >= 4 && Rails::VERSION::MINOR >= 1
4445
end
4546

47+
module MyEngine
48+
class Engine < ::Rails::Engine
49+
isolate_namespace MyEngine
50+
end
51+
end
52+
4653
# Patch RAILS 4.0 to not use millisecond precision
4754
if Rails::VERSION::MAJOR >= 4 && Rails::VERSION::MINOR < 1
4855
module ActiveSupport
@@ -198,6 +205,16 @@ def show_queries
198205
jsonapi_resources :numeros_telefone
199206
end
200207
end
208+
209+
mount MyEngine::Engine => "/boomshaka", as: :my_engine
210+
end
211+
212+
MyEngine::Engine.routes.draw do
213+
namespace :api do
214+
namespace :v1 do
215+
jsonapi_resources :people
216+
end
217+
end
201218
end
202219

203220
# Ensure backward compatibility with Minitest 4

0 commit comments

Comments
 (0)