Skip to content

Commit fa37d64

Browse files
authored
Use records for joined resources for 0.10 (#1381)
Add tracking of join options for jsonapi-authorization gem Merge related `records` in `apply_join` Add `use_related_resource_records_for_joins` to configuration and relationships
1 parent 3fda2cf commit fa37d64

File tree

8 files changed

+176
-69
lines changed

8 files changed

+176
-69
lines changed

Diff for: lib/jsonapi/active_relation/join_manager.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,15 @@ def perform_joins(records, options)
147147
related_resource_klass = join_details[:related_resource_klass]
148148
join_type = relationship_details[:join_type]
149149

150+
join_options = {
151+
relationship: relationship,
152+
relationship_details: relationship_details,
153+
related_resource_klass: related_resource_klass,
154+
}
155+
150156
if relationship == :root
151157
unless source_relationship
152-
add_join_details('', {alias: resource_klass._table_name, join_type: :root})
158+
add_join_details('', {alias: resource_klass._table_name, join_type: :root, join_options: join_options})
153159
end
154160
next
155161
end
@@ -163,7 +169,7 @@ def perform_joins(records, options)
163169
options: options)
164170
}
165171

166-
details = {alias: self.class.alias_from_arel_node(join_node), join_type: join_type}
172+
details = {alias: self.class.alias_from_arel_node(join_node), join_type: join_type, join_options: join_options}
167173

168174
if relationship == source_relationship
169175
if relationship.polymorphic? && relationship.belongs_to?

Diff for: lib/jsonapi/active_relation_resource.rb

+5
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,11 @@ def apply_join(records:, relationship:, resource_type:, join_type:, options:)
324324
records = records.joins_left(relation_name)
325325
end
326326
end
327+
328+
if relationship.use_related_resource_records_for_joins
329+
records = records.merge(self.records(options))
330+
end
331+
327332
records
328333
end
329334

Diff for: lib/jsonapi/configuration.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ class Configuration
3939
:default_resource_cache_field,
4040
:resource_cache_digest_function,
4141
:resource_cache_usage_report_function,
42-
:default_exclude_links
42+
:default_exclude_links,
43+
:use_related_resource_records_for_joins
4344

4445
def initialize
4546
#:underscored_key, :camelized_key, :dasherized_key, or custom
@@ -158,6 +159,11 @@ def initialize
158159
# and relationships. Accepts either `:default`, `:none`, or array containing the
159160
# specific default links to exclude, which may be `:self` and `:related`.
160161
self.default_exclude_links = :none
162+
163+
# Use a related resource's `records` when performing joins. This setting allows included resources to account for
164+
# permission scopes. It can be overridden explicitly per relationship. Furthermore, specifying a `relation_name`
165+
# on a relationship will cause this setting to be ignored.
166+
self.use_related_resource_records_for_joins = true
161167
end
162168

163169
def cache_formatters=(bool)
@@ -299,6 +305,8 @@ def allow_include=(allow_include)
299305
attr_writer :resource_cache_usage_report_function
300306

301307
attr_writer :default_exclude_links
308+
309+
attr_writer :use_related_resource_records_for_joins
302310
end
303311

304312
class << self

Diff for: lib/jsonapi/relationship.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ class Relationship
33
attr_reader :acts_as_set, :foreign_key, :options, :name,
44
:class_name, :polymorphic, :always_include_optional_linkage_data,
55
:parent_resource, :eager_load_on_include, :custom_methods,
6-
:inverse_relationship, :allow_include
6+
:inverse_relationship, :allow_include, :use_related_resource_records_for_joins
77

88
attr_writer :allow_include
99

@@ -23,6 +23,15 @@ def initialize(name, options = {})
2323
@polymorphic_types ||= options[:polymorphic_relations]
2424
end
2525

26+
use_related_resource_records_for_joins_default = if options[:relation_name]
27+
false
28+
else
29+
JSONAPI.configuration.use_related_resource_records_for_joins
30+
end
31+
32+
@use_related_resource_records_for_joins = options.fetch(:use_related_resource_records_for_joins,
33+
use_related_resource_records_for_joins_default) == true
34+
2635
@always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true
2736
@eager_load_on_include = options.fetch(:eager_load_on_include, false) == true
2837
@allow_include = options[:allow_include]

Diff for: test/fixtures/active_record.rb

+10-18
Original file line numberDiff line numberDiff line change
@@ -1413,7 +1413,7 @@ class PostResource < JSONAPI::Resource
14131413

14141414
has_one :author, class_name: 'Person'
14151415
has_one :section
1416-
has_many :tags, acts_as_set: true, inverse_relationship: :posts, eager_load_on_include: false
1416+
has_many :tags, acts_as_set: true, inverse_relationship: :posts
14171417
has_many :comments, acts_as_set: false, inverse_relationship: :post
14181418

14191419
# Not needed - just for testing
@@ -1932,16 +1932,7 @@ class AuthorResource < JSONAPI::Resource
19321932
model_name 'Person'
19331933
attributes :name
19341934

1935-
has_many :books, inverse_relationship: :authors, relation_name: -> (options) {
1936-
book_admin = options[:context][:book_admin] || options[:context][:current_user].try(:book_admin)
1937-
1938-
if book_admin
1939-
:books
1940-
else
1941-
:not_banned_books
1942-
end
1943-
}
1944-
1935+
has_many :books
19451936
has_many :book_comments
19461937
end
19471938

@@ -1981,6 +1972,9 @@ class BookResource < JSONAPI::Resource
19811972
}
19821973

19831974
filter :banned, apply: :apply_filter_banned
1975+
filter :title, apply: ->(records, value, options) {
1976+
records.where('books.title LIKE ?', "#{value[0]}%")
1977+
}
19841978

19851979
class << self
19861980
def books
@@ -1992,10 +1986,9 @@ def not_banned_books
19921986
end
19931987

19941988
def records(options = {})
1995-
context = options[:context]
1996-
current_user = context ? context[:current_user] : nil
1989+
current_user = options.dig(:context, :current_user)
19971990

1998-
records = _model_class.all
1991+
records = super
19991992
# Hide the banned books from people who are not book admins
20001993
unless current_user && current_user.book_admin
20011994
records = records.where(not_banned_books)
@@ -2004,8 +1997,7 @@ def records(options = {})
20041997
end
20051998

20061999
def apply_filter_banned(records, value, options)
2007-
context = options[:context]
2008-
current_user = context ? context[:current_user] : nil
2000+
current_user = options.dig(:context, :current_user)
20092001

20102002
# Only book admins might filter for banned books
20112003
if current_user && current_user.book_admin
@@ -2045,7 +2037,7 @@ def approved_comments(approved = true)
20452037
end
20462038

20472039
def records(options = {})
2048-
current_user = options[:context][:current_user]
2040+
current_user = options.dig(:context, :current_user)
20492041
_model_class.for_user(current_user)
20502042
end
20512043
end
@@ -2100,7 +2092,7 @@ class PostResource < JSONAPI::Resource
21002092

21012093
has_one :author, class_name: 'Person', exclude_links: [:self, "related"]
21022094
has_one :section, exclude_links: [:self, :related]
2103-
has_many :tags, acts_as_set: true, inverse_relationship: :posts, eager_load_on_include: false, exclude_links: :default
2095+
has_many :tags, acts_as_set: true, inverse_relationship: :posts, exclude_links: :default
21042096
has_many :comments, acts_as_set: false, inverse_relationship: :post, exclude_links: ["self", :related]
21052097
end
21062098

Diff for: test/integration/book_authorization_test.rb

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require File.expand_path('../../test_helper', __FILE__)
2+
3+
class BookAuthorizationTest < ActionDispatch::IntegrationTest
4+
def setup
5+
DatabaseCleaner.start
6+
JSONAPI.configuration.json_key_format = :underscored_key
7+
JSONAPI.configuration.route_format = :underscored_route
8+
Api::V2::BookResource.paginator :offset
9+
end
10+
11+
def test_restricted_records_primary
12+
Api::V2::BookResource.paginator :none
13+
14+
# Not a book admin
15+
$test_user = Person.find(1001)
16+
assert_cacheable_jsonapi_get '/api/v2/books?filter[title]=Book%206'
17+
assert_equal 12, json_response['data'].size
18+
19+
# book_admin
20+
$test_user = Person.find(1005)
21+
assert_cacheable_jsonapi_get '/api/v2/books?filter[title]=Book%206'
22+
assert_equal 111, json_response['data'].size
23+
end
24+
25+
def test_restricted_records_related
26+
Api::V2::BookResource.paginator :none
27+
28+
# Not a book admin
29+
$test_user = Person.find(1001)
30+
assert_cacheable_jsonapi_get '/api/v2/authors/1002/books'
31+
assert_equal 1, json_response['data'].size
32+
33+
# book_admin
34+
$test_user = Person.find(1005)
35+
assert_cacheable_jsonapi_get '/api/v2/authors/1002/books'
36+
assert_equal 2, json_response['data'].size
37+
end
38+
end

0 commit comments

Comments
 (0)