From 25d01dfa4966774768cf079da00b7de77aecb7e2 Mon Sep 17 00:00:00 2001 From: Larry Gebhardt Date: Thu, 9 Jan 2020 08:33:07 -0500 Subject: [PATCH 1/7] Add tracking of join options for jsonapi-authorization gem --- lib/jsonapi/active_relation/join_manager.rb | 10 +- .../join_manager_test.rb | 108 ++++++++++-------- 2 files changed, 69 insertions(+), 49 deletions(-) diff --git a/lib/jsonapi/active_relation/join_manager.rb b/lib/jsonapi/active_relation/join_manager.rb index 3d1ec34b5..ffa1cf0f1 100644 --- a/lib/jsonapi/active_relation/join_manager.rb +++ b/lib/jsonapi/active_relation/join_manager.rb @@ -147,9 +147,15 @@ def perform_joins(records, options) related_resource_klass = join_details[:related_resource_klass] join_type = relationship_details[:join_type] + join_options = { + relationship: relationship, + relationship_details: relationship_details, + related_resource_klass: related_resource_klass, + } + if relationship == :root unless source_relationship - add_join_details('', {alias: resource_klass._table_name, join_type: :root}) + add_join_details('', {alias: resource_klass._table_name, join_type: :root, join_options: join_options}) end next end @@ -163,7 +169,7 @@ def perform_joins(records, options) options: options) } - details = {alias: self.class.alias_from_arel_node(join_node), join_type: join_type} + details = {alias: self.class.alias_from_arel_node(join_node), join_type: join_type, join_options: join_options} if relationship == source_relationship if relationship.polymorphic? && relationship.belongs_to? diff --git a/test/unit/active_relation_resource_finder/join_manager_test.rb b/test/unit/active_relation_resource_finder/join_manager_test.rb index 840c90ee2..2b38d0642 100644 --- a/test/unit/active_relation_resource_finder/join_manager_test.rb +++ b/test/unit/active_relation_resource_finder/join_manager_test.rb @@ -23,7 +23,7 @@ def test_no_added_joins records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts"', records.to_sql - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) end def test_add_single_join @@ -32,8 +32,22 @@ def test_add_single_join records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', records.to_sql - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags)).except!(:join_options)) + end + + def test_joins_have_join_options + filters = {'tags' => ['1']} + join_manager = JSONAPI::ActiveRelation::JoinManager.new(resource_klass: PostResource, filters: filters) + records = PostResource.records({}) + records = join_manager.join(records, {}) + assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', records.to_sql + + source_join_options = join_manager.source_join_details[:join_options] + assert_array_equals [:relationship, :relationship_details, :related_resource_klass], source_join_options.keys + + relationship_join_options = join_manager.join_details_by_relationship(PostResource._relationship(:tags))[:join_options] + assert_array_equals [:relationship, :relationship_details, :related_resource_klass], relationship_join_options.keys end def test_add_single_sort_join @@ -43,8 +57,8 @@ def test_add_single_sort_join records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', records.to_sql - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags)).except!(:join_options)) end def test_add_single_sort_and_filter_join @@ -54,8 +68,8 @@ def test_add_single_sort_and_filter_join records = PostResource.records({}) records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id"', records.to_sql - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags)).except!(:join_options)) end def test_add_sibling_joins @@ -69,9 +83,9 @@ def test_add_sibling_joins records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" LEFT OUTER JOIN "posts_tags" ON "posts_tags"."post_id" = "posts"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "posts_tags"."tag_id" LEFT OUTER JOIN "people" ON "people"."id" = "posts"."author_id"', records.to_sql - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags))) - assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:author))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:tags)).except!(:join_options)) + assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(PostResource._relationship(:author)).except!(:join_options)) end @@ -82,7 +96,7 @@ def test_add_joins_source_relationship records = join_manager.join(records, {}) assert_equal 'SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"', records.to_sql - assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details) + assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details.except!(:join_options)) end @@ -96,7 +110,7 @@ def test_add_joins_source_relationship_with_custom_apply assert_equal sql, records.to_sql - assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details) + assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details.except!(:join_options)) end def test_add_nested_scoped_joins @@ -110,11 +124,11 @@ def test_add_nested_scoped_joins records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments))) - assert_hash_equals({alias: 'authors_comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:author))) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags))) - assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:author))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments)).except!(:join_options)) + assert_hash_equals({alias: 'authors_comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:author)).except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags)).except!(:join_options)) + assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:author)).except!(:join_options)) # Now test with different order for the filters filters = { @@ -127,11 +141,11 @@ def test_add_nested_scoped_joins records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments))) - assert_hash_equals({alias: 'authors_comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:author))) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags))) - assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:author))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments)).except!(:join_options)) + assert_hash_equals({alias: 'authors_comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:author)).except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags)).except!(:join_options)) + assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:author)).except!(:join_options)) end def test_add_nested_joins_with_fields @@ -145,11 +159,11 @@ def test_add_nested_joins_with_fields records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) - assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments))) - assert_hash_equals({alias: 'authors_comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:author))) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags))) - assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:author))) + assert_hash_equals({alias: 'posts', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments)).except!(:join_options)) + assert_hash_equals({alias: 'authors_comments', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:author)).except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags)).except!(:join_options)) + assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:author)).except!(:join_options)) end def test_add_joins_with_sub_relationship @@ -160,10 +174,10 @@ def test_add_joins_with_sub_relationship records = Api::V10::PostResource.records({}) records = join_manager.join(records, {}) - assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details) - assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments))) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags))) - assert_hash_equals({alias: 'comments_people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PersonResource._relationship(:comments))) + assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.join_details_by_relationship(Api::V10::PostResource._relationship(:comments)).except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::CommentResource._relationship(:tags)).except!(:join_options)) + assert_hash_equals({alias: 'comments_people', join_type: :left}, join_manager.join_details_by_relationship(Api::V10::PersonResource._relationship(:comments)).except!(:join_options)) end def test_add_joins_with_sub_relationship_and_filters @@ -181,11 +195,11 @@ def test_add_joins_with_sub_relationship_and_filters records = PostResource.records({}) records = join_manager.join(records, {}) - assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details) - assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.join_details_by_relationship(PostResource._relationship(:comments))) - assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(CommentResource._relationship(:author))) - assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(CommentResource._relationship(:tags))) - assert_hash_equals({alias: 'comments_people', join_type: :left}, join_manager.join_details_by_relationship(PersonResource._relationship(:comments))) + assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'comments', join_type: :inner}, join_manager.join_details_by_relationship(PostResource._relationship(:comments)).except!(:join_options)) + assert_hash_equals({alias: 'people', join_type: :left}, join_manager.join_details_by_relationship(CommentResource._relationship(:author)).except!(:join_options)) + assert_hash_equals({alias: 'tags', join_type: :left}, join_manager.join_details_by_relationship(CommentResource._relationship(:tags)).except!(:join_options)) + assert_hash_equals({alias: 'comments_people', join_type: :left}, join_manager.join_details_by_relationship(PersonResource._relationship(:comments)).except!(:join_options)) end def test_polymorphic_join_belongs_to_just_source @@ -196,10 +210,10 @@ def test_polymorphic_join_belongs_to_just_source records = join_manager.join(records, {}) # assert_equal 'SELECT "pictures".* FROM "pictures" LEFT OUTER JOIN "products" ON "products"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = \'Product\' LEFT OUTER JOIN "documents" ON "documents"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = \'Document\'', records.to_sql - assert_hash_equals({alias: 'products', join_type: :left}, join_manager.source_join_details('products')) - assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.source_join_details('documents')) - assert_hash_equals({alias: 'products', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'products')) - assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'documents')) + assert_hash_equals({alias: 'products', join_type: :left}, join_manager.source_join_details('products').except!(:join_options)) + assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.source_join_details('documents').except!(:join_options)) + assert_hash_equals({alias: 'products', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'products').except!(:join_options)) + assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'documents').except!(:join_options)) end def test_polymorphic_join_belongs_to_filter @@ -210,9 +224,9 @@ def test_polymorphic_join_belongs_to_filter records = join_manager.join(records, {}) # assert_equal 'SELECT "pictures".* FROM "pictures" LEFT OUTER JOIN "products" ON "products"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = \'Product\' LEFT OUTER JOIN "documents" ON "documents"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = \'Document\'', records.to_sql - assert_hash_equals({alias: 'pictures', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'products', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'products')) - assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'documents')) + assert_hash_equals({alias: 'pictures', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'products', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'products').except!(:join_options)) + assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'documents').except!(:join_options)) end def test_polymorphic_join_belongs_to_filter_on_resource @@ -228,9 +242,9 @@ def test_polymorphic_join_belongs_to_filter_on_resource records = PictureResource.records({}) records = join_manager.join(records, {}) - assert_hash_equals({alias: 'pictures', join_type: :root}, join_manager.source_join_details) - assert_hash_equals({alias: 'products', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'products')) - assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'documents')) - assert_hash_equals({alias: 'file_properties', join_type: :left}, join_manager.join_details_by_relationship(PictureResource._relationship(:file_properties))) + assert_hash_equals({alias: 'pictures', join_type: :root}, join_manager.source_join_details.except!(:join_options)) + assert_hash_equals({alias: 'products', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'products').except!(:join_options)) + assert_hash_equals({alias: 'documents', join_type: :left}, join_manager.join_details_by_polymorphic_relationship(PictureResource._relationship(:imageable), 'documents').except!(:join_options)) + assert_hash_equals({alias: 'file_properties', join_type: :left}, join_manager.join_details_by_relationship(PictureResource._relationship(:file_properties)).except!(:join_options)) end end From 14584c582ce0f1366584509b9b0024f29ba29f34 Mon Sep 17 00:00:00 2001 From: Larry Gebhardt Date: Sat, 8 Feb 2020 11:44:50 -0500 Subject: [PATCH 2/7] Merge `records` in `apply_join` --- lib/jsonapi/active_relation_resource.rb | 5 +++ lib/jsonapi/relationship.rb | 4 +-- test/fixtures/active_record.rb | 28 ++++++--------- test/integration/book_authorization_test.rb | 38 +++++++++++++++++++++ test/unit/resource/relationship_test.rb | 24 +++++++++++++ 5 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 test/integration/book_authorization_test.rb diff --git a/lib/jsonapi/active_relation_resource.rb b/lib/jsonapi/active_relation_resource.rb index ed3b89d20..9ba26a51a 100644 --- a/lib/jsonapi/active_relation_resource.rb +++ b/lib/jsonapi/active_relation_resource.rb @@ -324,6 +324,11 @@ def apply_join(records:, relationship:, resource_type:, join_type:, options:) records = records.joins_left(relation_name) end end + + if relationship.merge_resource_records + records = records.merge(self.records(options)) + end + records end diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index a185587c9..0472e5e15 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -3,7 +3,7 @@ class Relationship attr_reader :acts_as_set, :foreign_key, :options, :name, :class_name, :polymorphic, :always_include_optional_linkage_data, :parent_resource, :eager_load_on_include, :custom_methods, - :inverse_relationship, :allow_include + :inverse_relationship, :allow_include, :merge_resource_records attr_writer :allow_include @@ -22,7 +22,7 @@ def initialize(name, options = {}) ActiveSupport::Deprecation.warn('Use polymorphic_types instead of polymorphic_relations') @polymorphic_types ||= options[:polymorphic_relations] end - + @merge_resource_records = options.fetch(:merge_resource_records, options[:relation_name].nil?) == true @always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true @eager_load_on_include = options.fetch(:eager_load_on_include, false) == true @allow_include = options[:allow_include] diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index c1f3cc674..6170eca81 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -1413,7 +1413,7 @@ class PostResource < JSONAPI::Resource has_one :author, class_name: 'Person' has_one :section - has_many :tags, acts_as_set: true, inverse_relationship: :posts, eager_load_on_include: false + has_many :tags, acts_as_set: true, inverse_relationship: :posts has_many :comments, acts_as_set: false, inverse_relationship: :post # Not needed - just for testing @@ -1932,16 +1932,7 @@ class AuthorResource < JSONAPI::Resource model_name 'Person' attributes :name - has_many :books, inverse_relationship: :authors, relation_name: -> (options) { - book_admin = options[:context][:book_admin] || options[:context][:current_user].try(:book_admin) - - if book_admin - :books - else - :not_banned_books - end - } - + has_many :books has_many :book_comments end @@ -1981,6 +1972,9 @@ class BookResource < JSONAPI::Resource } filter :banned, apply: :apply_filter_banned + filter :title, apply: ->(records, value, options) { + records.where('books.title LIKE ?', "#{value[0]}%") + } class << self def books @@ -1992,10 +1986,9 @@ def not_banned_books end def records(options = {}) - context = options[:context] - current_user = context ? context[:current_user] : nil + current_user = options.dig(:context, :current_user) - records = _model_class.all + records = super # Hide the banned books from people who are not book admins unless current_user && current_user.book_admin records = records.where(not_banned_books) @@ -2004,8 +1997,7 @@ def records(options = {}) end def apply_filter_banned(records, value, options) - context = options[:context] - current_user = context ? context[:current_user] : nil + current_user = options.dig(:context, :current_user) # Only book admins might filter for banned books if current_user && current_user.book_admin @@ -2045,7 +2037,7 @@ def approved_comments(approved = true) end def records(options = {}) - current_user = options[:context][:current_user] + current_user = options.dig(:context, :current_user) _model_class.for_user(current_user) end end @@ -2100,7 +2092,7 @@ class PostResource < JSONAPI::Resource has_one :author, class_name: 'Person', exclude_links: [:self, "related"] has_one :section, exclude_links: [:self, :related] - has_many :tags, acts_as_set: true, inverse_relationship: :posts, eager_load_on_include: false, exclude_links: :default + has_many :tags, acts_as_set: true, inverse_relationship: :posts, exclude_links: :default has_many :comments, acts_as_set: false, inverse_relationship: :post, exclude_links: ["self", :related] end diff --git a/test/integration/book_authorization_test.rb b/test/integration/book_authorization_test.rb new file mode 100644 index 000000000..6327f2ba3 --- /dev/null +++ b/test/integration/book_authorization_test.rb @@ -0,0 +1,38 @@ +require File.expand_path('../../test_helper', __FILE__) + +class BookAuthorizationTest < ActionDispatch::IntegrationTest + def setup + DatabaseCleaner.start + JSONAPI.configuration.json_key_format = :underscored_key + JSONAPI.configuration.route_format = :underscored_route + Api::V2::BookResource.paginator :offset + end + + def test_restricted_records_primary + Api::V2::BookResource.paginator :none + + # Not a book admin + $test_user = Person.find(1001) + assert_cacheable_jsonapi_get '/api/v2/books?filter[title]=Book%206' + assert_equal 12, json_response['data'].size + + # book_admin + $test_user = Person.find(1005) + assert_cacheable_jsonapi_get '/api/v2/books?filter[title]=Book%206' + assert_equal 111, json_response['data'].size + end + + def test_restricted_records_related + Api::V2::BookResource.paginator :none + + # Not a book admin + $test_user = Person.find(1001) + assert_cacheable_jsonapi_get '/api/v2/authors/1002/books' + assert_equal 1, json_response['data'].size + + # book_admin + $test_user = Person.find(1005) + assert_cacheable_jsonapi_get '/api/v2/authors/1002/books' + assert_equal 2, json_response['data'].size + end +end diff --git a/test/unit/resource/relationship_test.rb b/test/unit/resource/relationship_test.rb index a98d26601..ae28b4b36 100644 --- a/test/unit/resource/relationship_test.rb +++ b/test/unit/resource/relationship_test.rb @@ -18,6 +18,30 @@ def self.is_admin(context) end end +class TestRelationshipOptionsPostsResource < JSONAPI::Resource + model_name 'Post' + has_one :author, allow_include: :is_admin, merge_resource_records: false +end + +class RelationshipTest < ActiveSupport::TestCase + def test_merge_resource_records_enabled_by_default + relationship = JSONAPI::Relationship::ToOne.new(:author) + assert relationship.merge_resource_records + end + + def test_merge_resource_records_is_disabled_by_deafult_with_relation_name + relationship = JSONAPI::Relationship::ToOne.new(:author, + relation_name: "foo" ) + refute relationship.merge_resource_records + end + + def test_merge_resource_records_can_be_disabled + relationship = JSONAPI::Relationship::ToOne.new(:author, + merge_resource_records: false ) + refute relationship.merge_resource_records + end +end + class HasOneRelationshipTest < ActiveSupport::TestCase def test_polymorphic_type From 0dfe8c49c71a5cace39514b7a549555517d25626 Mon Sep 17 00:00:00 2001 From: lgebhardt Date: Mon, 7 Feb 2022 09:15:44 -0500 Subject: [PATCH 3/7] Add merge_resource_records to configuration, defaulting to true --- lib/jsonapi/configuration.rb | 9 ++++++++- lib/jsonapi/relationship.rb | 5 ++++- test/unit/resource/relationship_test.rb | 11 +++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index f7e899cfa..36dc7d2c9 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -39,7 +39,8 @@ class Configuration :default_resource_cache_field, :resource_cache_digest_function, :resource_cache_usage_report_function, - :default_exclude_links + :default_exclude_links, + :merge_resource_records def initialize #:underscored_key, :camelized_key, :dasherized_key, or custom @@ -158,6 +159,10 @@ def initialize # and relationships. Accepts either `:default`, `:none`, or array containing the # specific default links to exclude, which may be `:self` and `:related`. self.default_exclude_links = :none + + # Merge related resources `records` when performing joins and a relation_name is not specified. + # This will bring in permission scopes on the included resources. This can be overridden per relationship. + self.merge_resource_records = true end def cache_formatters=(bool) @@ -299,6 +304,8 @@ def allow_include=(allow_include) attr_writer :resource_cache_usage_report_function attr_writer :default_exclude_links + + attr_writer :merge_resource_records end class << self diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 0472e5e15..44b6373e5 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -22,7 +22,10 @@ def initialize(name, options = {}) ActiveSupport::Deprecation.warn('Use polymorphic_types instead of polymorphic_relations') @polymorphic_types ||= options[:polymorphic_relations] end - @merge_resource_records = options.fetch(:merge_resource_records, options[:relation_name].nil?) == true + + merge_resource_record_default = options[:relation_name] ? false : JSONAPI.configuration.merge_resource_records + @merge_resource_records = options.fetch(:merge_resource_records, merge_resource_record_default) == true + @always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true @eager_load_on_include = options.fetch(:eager_load_on_include, false) == true @allow_include = options[:allow_include] diff --git a/test/unit/resource/relationship_test.rb b/test/unit/resource/relationship_test.rb index ae28b4b36..4a56e684b 100644 --- a/test/unit/resource/relationship_test.rb +++ b/test/unit/resource/relationship_test.rb @@ -25,10 +25,21 @@ class TestRelationshipOptionsPostsResource < JSONAPI::Resource class RelationshipTest < ActiveSupport::TestCase def test_merge_resource_records_enabled_by_default + assert JSONAPI.configuration.merge_resource_records == true relationship = JSONAPI::Relationship::ToOne.new(:author) assert relationship.merge_resource_records end + def test_merge_resource_records_can_be_disabled_globally + original_config = JSONAPI.configuration.dup + + JSONAPI.configuration.merge_resource_records = false + relationship = JSONAPI::Relationship::ToOne.new(:author) + assert relationship.merge_resource_records == false + ensure + JSONAPI.configuration = original_config + end + def test_merge_resource_records_is_disabled_by_deafult_with_relation_name relationship = JSONAPI::Relationship::ToOne.new(:author, relation_name: "foo" ) From 2fe0b5b7b19d3c214bc25f522cec8f72fec76bbf Mon Sep 17 00:00:00 2001 From: lgebhardt Date: Mon, 7 Feb 2022 09:24:03 -0500 Subject: [PATCH 4/7] Rename `merge_resource_records` to `merge_resource_records_for_joins` --- lib/jsonapi/active_relation_resource.rb | 2 +- lib/jsonapi/configuration.rb | 6 +++--- lib/jsonapi/relationship.rb | 6 +++--- test/unit/resource/relationship_test.rb | 24 ++++++++++++------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/jsonapi/active_relation_resource.rb b/lib/jsonapi/active_relation_resource.rb index 9ba26a51a..7fb5b52d5 100644 --- a/lib/jsonapi/active_relation_resource.rb +++ b/lib/jsonapi/active_relation_resource.rb @@ -325,7 +325,7 @@ def apply_join(records:, relationship:, resource_type:, join_type:, options:) end end - if relationship.merge_resource_records + if relationship.merge_resource_records_for_joins records = records.merge(self.records(options)) end diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index 36dc7d2c9..27e472077 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -40,7 +40,7 @@ class Configuration :resource_cache_digest_function, :resource_cache_usage_report_function, :default_exclude_links, - :merge_resource_records + :merge_resource_records_for_joins def initialize #:underscored_key, :camelized_key, :dasherized_key, or custom @@ -162,7 +162,7 @@ def initialize # Merge related resources `records` when performing joins and a relation_name is not specified. # This will bring in permission scopes on the included resources. This can be overridden per relationship. - self.merge_resource_records = true + self.merge_resource_records_for_joins = true end def cache_formatters=(bool) @@ -305,7 +305,7 @@ def allow_include=(allow_include) attr_writer :default_exclude_links - attr_writer :merge_resource_records + attr_writer :merge_resource_records_for_joins end class << self diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 44b6373e5..42eba4680 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -3,7 +3,7 @@ class Relationship attr_reader :acts_as_set, :foreign_key, :options, :name, :class_name, :polymorphic, :always_include_optional_linkage_data, :parent_resource, :eager_load_on_include, :custom_methods, - :inverse_relationship, :allow_include, :merge_resource_records + :inverse_relationship, :allow_include, :merge_resource_records_for_joins attr_writer :allow_include @@ -23,8 +23,8 @@ def initialize(name, options = {}) @polymorphic_types ||= options[:polymorphic_relations] end - merge_resource_record_default = options[:relation_name] ? false : JSONAPI.configuration.merge_resource_records - @merge_resource_records = options.fetch(:merge_resource_records, merge_resource_record_default) == true + merge_resource_record_default = options[:relation_name] ? false : JSONAPI.configuration.merge_resource_records_for_joins + @merge_resource_records_for_joins = options.fetch(:merge_resource_records_for_joins, merge_resource_record_default) == true @always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true @eager_load_on_include = options.fetch(:eager_load_on_include, false) == true diff --git a/test/unit/resource/relationship_test.rb b/test/unit/resource/relationship_test.rb index 4a56e684b..20537fd59 100644 --- a/test/unit/resource/relationship_test.rb +++ b/test/unit/resource/relationship_test.rb @@ -20,36 +20,36 @@ def self.is_admin(context) class TestRelationshipOptionsPostsResource < JSONAPI::Resource model_name 'Post' - has_one :author, allow_include: :is_admin, merge_resource_records: false + has_one :author, allow_include: :is_admin, merge_resource_records_for_joins: false end class RelationshipTest < ActiveSupport::TestCase - def test_merge_resource_records_enabled_by_default - assert JSONAPI.configuration.merge_resource_records == true + def test_merge_resource_records_for_joins_enabled_by_default + assert JSONAPI.configuration.merge_resource_records_for_joins == true relationship = JSONAPI::Relationship::ToOne.new(:author) - assert relationship.merge_resource_records + assert relationship.merge_resource_records_for_joins end - def test_merge_resource_records_can_be_disabled_globally + def test_merge_resource_records_for_joins_can_be_disabled_globally original_config = JSONAPI.configuration.dup - JSONAPI.configuration.merge_resource_records = false + JSONAPI.configuration.merge_resource_records_for_joins = false relationship = JSONAPI::Relationship::ToOne.new(:author) - assert relationship.merge_resource_records == false + assert relationship.merge_resource_records_for_joins == false ensure JSONAPI.configuration = original_config end - def test_merge_resource_records_is_disabled_by_deafult_with_relation_name + def test_merge_resource_records_for_joins_is_disabled_by_deafult_with_relation_name relationship = JSONAPI::Relationship::ToOne.new(:author, relation_name: "foo" ) - refute relationship.merge_resource_records + refute relationship.merge_resource_records_for_joins end - def test_merge_resource_records_can_be_disabled + def test_merge_resource_records_for_joins_can_be_disabled relationship = JSONAPI::Relationship::ToOne.new(:author, - merge_resource_records: false ) - refute relationship.merge_resource_records + merge_resource_records_for_joins: false ) + refute relationship.merge_resource_records_for_joins end end From d8c7414b16dfc19616f05bf2f9b821098b3eb268 Mon Sep 17 00:00:00 2001 From: lgebhardt Date: Mon, 7 Feb 2022 14:42:03 -0500 Subject: [PATCH 5/7] Rename `merge_resource_records_for_joins` to `use_related_resource_records_for_joins` --- lib/jsonapi/active_relation_resource.rb | 2 +- lib/jsonapi/configuration.rb | 11 ++++++----- lib/jsonapi/relationship.rb | 6 +++--- test/unit/resource/relationship_test.rb | 24 ++++++++++++------------ 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/jsonapi/active_relation_resource.rb b/lib/jsonapi/active_relation_resource.rb index 7fb5b52d5..c2056776c 100644 --- a/lib/jsonapi/active_relation_resource.rb +++ b/lib/jsonapi/active_relation_resource.rb @@ -325,7 +325,7 @@ def apply_join(records:, relationship:, resource_type:, join_type:, options:) end end - if relationship.merge_resource_records_for_joins + if relationship.use_related_resource_records_for_joins records = records.merge(self.records(options)) end diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index 27e472077..ce4a3d3f7 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -40,7 +40,7 @@ class Configuration :resource_cache_digest_function, :resource_cache_usage_report_function, :default_exclude_links, - :merge_resource_records_for_joins + :use_related_resource_records_for_joins def initialize #:underscored_key, :camelized_key, :dasherized_key, or custom @@ -160,9 +160,10 @@ def initialize # specific default links to exclude, which may be `:self` and `:related`. self.default_exclude_links = :none - # Merge related resources `records` when performing joins and a relation_name is not specified. - # This will bring in permission scopes on the included resources. This can be overridden per relationship. - self.merge_resource_records_for_joins = true + # Use related `records` when performing joins. If a relation_name is specified this option is ignored for the + # relationship. This will bring in permission scopes on the included resources. + # This can be overridden per relationship. + self.use_related_resource_records_for_joins = true end def cache_formatters=(bool) @@ -305,7 +306,7 @@ def allow_include=(allow_include) attr_writer :default_exclude_links - attr_writer :merge_resource_records_for_joins + attr_writer :use_related_resource_records_for_joins end class << self diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 42eba4680..207c606ab 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -3,7 +3,7 @@ class Relationship attr_reader :acts_as_set, :foreign_key, :options, :name, :class_name, :polymorphic, :always_include_optional_linkage_data, :parent_resource, :eager_load_on_include, :custom_methods, - :inverse_relationship, :allow_include, :merge_resource_records_for_joins + :inverse_relationship, :allow_include, :use_related_resource_records_for_joins attr_writer :allow_include @@ -23,8 +23,8 @@ def initialize(name, options = {}) @polymorphic_types ||= options[:polymorphic_relations] end - merge_resource_record_default = options[:relation_name] ? false : JSONAPI.configuration.merge_resource_records_for_joins - @merge_resource_records_for_joins = options.fetch(:merge_resource_records_for_joins, merge_resource_record_default) == true + merge_resource_record_default = options[:relation_name] ? false : JSONAPI.configuration.use_related_resource_records_for_joins + @use_related_resource_records_for_joins = options.fetch(:use_related_resource_records_for_joins, merge_resource_record_default) == true @always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true @eager_load_on_include = options.fetch(:eager_load_on_include, false) == true diff --git a/test/unit/resource/relationship_test.rb b/test/unit/resource/relationship_test.rb index 20537fd59..e030ff309 100644 --- a/test/unit/resource/relationship_test.rb +++ b/test/unit/resource/relationship_test.rb @@ -20,36 +20,36 @@ def self.is_admin(context) class TestRelationshipOptionsPostsResource < JSONAPI::Resource model_name 'Post' - has_one :author, allow_include: :is_admin, merge_resource_records_for_joins: false + has_one :author, allow_include: :is_admin, use_related_resource_records_for_joins: false end class RelationshipTest < ActiveSupport::TestCase - def test_merge_resource_records_for_joins_enabled_by_default - assert JSONAPI.configuration.merge_resource_records_for_joins == true + def test_use_related_resource_records_for_joins_enabled_by_default + assert JSONAPI.configuration.use_related_resource_records_for_joins == true relationship = JSONAPI::Relationship::ToOne.new(:author) - assert relationship.merge_resource_records_for_joins + assert relationship.use_related_resource_records_for_joins end - def test_merge_resource_records_for_joins_can_be_disabled_globally + def test_use_related_resource_records_for_joins_can_be_disabled_globally original_config = JSONAPI.configuration.dup - JSONAPI.configuration.merge_resource_records_for_joins = false + JSONAPI.configuration.use_related_resource_records_for_joins = false relationship = JSONAPI::Relationship::ToOne.new(:author) - assert relationship.merge_resource_records_for_joins == false + assert relationship.use_related_resource_records_for_joins == false ensure JSONAPI.configuration = original_config end - def test_merge_resource_records_for_joins_is_disabled_by_deafult_with_relation_name + def test_use_related_resource_records_for_joins_is_disabled_by_deafult_with_relation_name relationship = JSONAPI::Relationship::ToOne.new(:author, relation_name: "foo" ) - refute relationship.merge_resource_records_for_joins + refute relationship.use_related_resource_records_for_joins end - def test_merge_resource_records_for_joins_can_be_disabled + def test_use_related_resource_records_for_joins_can_be_disabled relationship = JSONAPI::Relationship::ToOne.new(:author, - merge_resource_records_for_joins: false ) - refute relationship.merge_resource_records_for_joins + use_related_resource_records_for_joins: false ) + refute relationship.use_related_resource_records_for_joins end end From 48da62928c09e9bd6ac68b34bffb7edbf82e3bbc Mon Sep 17 00:00:00 2001 From: lgebhardt Date: Mon, 7 Feb 2022 14:52:08 -0500 Subject: [PATCH 6/7] Rework name and description --- lib/jsonapi/configuration.rb | 6 +++--- lib/jsonapi/relationship.rb | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index ce4a3d3f7..67777d257 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -160,9 +160,9 @@ def initialize # specific default links to exclude, which may be `:self` and `:related`. self.default_exclude_links = :none - # Use related `records` when performing joins. If a relation_name is specified this option is ignored for the - # relationship. This will bring in permission scopes on the included resources. - # This can be overridden per relationship. + # Use a related resource's records when performing joins. This setting allows included resources to account for + # permission scopes. It can be overridden explicitly per relationship. Furthermore, specifying a relation_name on + # a relationship will cause this setting to be ignored. self.use_related_resource_records_for_joins = true end diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 207c606ab..5261b6049 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -23,8 +23,14 @@ def initialize(name, options = {}) @polymorphic_types ||= options[:polymorphic_relations] end - merge_resource_record_default = options[:relation_name] ? false : JSONAPI.configuration.use_related_resource_records_for_joins - @use_related_resource_records_for_joins = options.fetch(:use_related_resource_records_for_joins, merge_resource_record_default) == true + use_related_resource_records_for_joins_default = if options[:relation_name] + false + else + JSONAPI.configuration.use_related_resource_records_for_joins + end + + @use_related_resource_records_for_joins = options.fetch(:use_related_resource_records_for_joins, + use_related_resource_records_for_joins_default) == true @always_include_optional_linkage_data = options.fetch(:always_include_optional_linkage_data, false) == true @eager_load_on_include = options.fetch(:eager_load_on_include, false) == true From 9ee8f02291e29fc68ad2f105cbe44f04f16a7cf7 Mon Sep 17 00:00:00 2001 From: lgebhardt Date: Mon, 7 Feb 2022 14:55:42 -0500 Subject: [PATCH 7/7] Fix description --- lib/jsonapi/configuration.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jsonapi/configuration.rb b/lib/jsonapi/configuration.rb index 67777d257..834ca1b1c 100644 --- a/lib/jsonapi/configuration.rb +++ b/lib/jsonapi/configuration.rb @@ -160,9 +160,9 @@ def initialize # specific default links to exclude, which may be `:self` and `:related`. self.default_exclude_links = :none - # Use a related resource's records when performing joins. This setting allows included resources to account for - # permission scopes. It can be overridden explicitly per relationship. Furthermore, specifying a relation_name on - # a relationship will cause this setting to be ignored. + # Use a related resource's `records` when performing joins. This setting allows included resources to account for + # permission scopes. It can be overridden explicitly per relationship. Furthermore, specifying a `relation_name` + # on a relationship will cause this setting to be ignored. self.use_related_resource_records_for_joins = true end