Skip to content
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

Smarter association id lookup-- no db hit on belongs_to for id-only #1857

Merged
merged 2 commits into from
Apr 30, 2017

Conversation

bf4
Copy link
Member

@bf4 bf4 commented Jul 26, 2016

If the serializer has an association,

  • If adapter is not JSON API, serialize it.
  • else if it is JSON API and the related resource
    • is not included, serialize just the ids
    • is included, serialize the all fields, or the requested fields

The thing where it doesn't actually call the association is a
special case where AMS needs to know it can get the relationship id
from the resource itself, so that userland code doesn't need to do any fancy association preloading.

The way this has been solved in JSONAPI-Resources
is to have special rules for looking up association ids.
And see JSONAPI::Relationship

In brief:

association foreign_key method loads relation (i.e. when foreign key is on relation )
has_many :authors :author_ids true
has_one :author :author_id true
belongs_to :author :author_id false

Caveat:

This will only work on active record objects

Includes:

Follows:

Related:

@mention-bot
Copy link

@bf4, thanks for your PR! By analyzing the annotation information on this pull request, we identified @beauby, @ehsanyousefi and @bolshakov to be potential reviewers

@NullVoxPopuli
Copy link
Contributor

Do you plan to fix the bug, or is this just to demonstrate the problem? (either is cool)

@bf4 bf4 force-pushed the smarter_association_id_lookup branch 2 times, most recently from 3abc80b to c93e599 Compare July 29, 2016 03:44
@bf4 bf4 force-pushed the smarter_association_id_lookup branch from c93e599 to cb515f8 Compare August 1, 2016 19:29
reflection_options[:include_data] = @_include_data
reflection_options[:new_options] = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hack to allow for refactoring...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just about to ask about this, thanks

@bf4
Copy link
Member Author

bf4 commented Aug 7, 2016

I think I got it working and all tests passing! Now time for review.

# parent_serializer = AuthorSerializer.new(@author)
# relationship = Relationship.new(parent_serializer, @serializer, nil, params)
# assert_equal(expected, relationship.as_json)
# end
Copy link
Member Author

@bf4 bf4 Aug 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests, like much of the JSONAPI code, are tightly coupled to the implementation.

They are asserting a relationship between two serializers that are not necessarily actually related.

The impl in this PR is to pass in the parent serializer and the association.

In these tests, the association would need to be derived from the @serializer. I couldn't get this the api to this test file right, so am relying on the remainder of the test suite.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(DO NOT merge with this commented out... tbd what to do about it)

@bf4
Copy link
Member Author

bf4 commented Aug 8, 2016

@bf4
Copy link
Member Author

bf4 commented Aug 8, 2016

(Test failure right now are regularly occurring but intermittent... tests pass though, and pr needs refactoring, so this is a good chance to look)

@sandstrom
Copy link

@bf4 this is a great feature!

Regarding "This will only work on active record objects", would it be possible to define an interface (+ default handling for ActiveRecord) such that other ORMs can use it too (or use some other mechanism, basically anything that'll make this useful for other ORMs that are similar to ActiveRecord). For example Mongoid.

@bf4
Copy link
Member Author

bf4 commented Aug 8, 2016

Well, I wrote it using respond_to, so if it quacks that way it will work. I'm not familiar with the interface needed by mongoid

B mobile phone

On Aug 8, 2016, at 6:48 AM, sandstrom notifications@github.com wrote:

@bf4 this is a great feature!

Regarding "This will only work on active record objects", would it be possible to define an interface (+ default handling for ActiveRecord) such that other ORMs can use it too (or use some other mechanism, basically anything that'll make this useful for other ORMs that are similar to ActiveRecord). For example Mongoid.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@@ -2,6 +2,12 @@ module ActiveModel
class Serializer
# @api private
class CollectionReflection < Reflection
def initialize(*)
super
@class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these maybe be methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as oppose to in the initializer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wary of too much logic in an initializer / any method

options[:new_options] = {
meta: test_options.delete(:meta),
links: test_options.delete(:links),
reflection: parent_serializer._reflections.to_a.first
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is broken (in the rebase off of master), but just wanted to push the work for the moment

@bf4 bf4 force-pushed the smarter_association_id_lookup branch from 32dbfa9 to 93fa05b Compare August 18, 2016 07:04
@serializable_resource_options = {} # adapter.instance_options
end

def build_author_relationship(author_attributes, association_name)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely rewrote the test harness and assertion in this file.. could use a second pair of eyes, but at least I think it is straightforward enough to read the assertions and understand, and to read the harness and understand what it's doing (even if not so clear why it's so complicated, and how could it be simpler).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's pretty straight forward, usage wise.

though, why do you need to reset the reflection stuff?
is that for isolation or something else?
my concern is that if something is happening to those normally that is being avoided here, it could reveal a hole in testing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just that normally these are created by the serializer using these as defaults in the reflection initializer, but here I am mutating them, which changes them for all the tests, and dup and deep_dup didn't work. So it's a hack to make the serializer behave in each test as if it were being defined for the first time.

B mobile phone

On Aug 18, 2016, at 6:53 AM, L. Preston Sego III notifications@github.com wrote:

In test/adapter/json_api/relationship_test.rb:

     setup do
  •      @blog = Blog.new(id: 1)
    
  •      @author = Author.new(id: 1, name: 'Steve K.', blog: @blog)
    
  •      @serializer = BlogSerializer.new(@blog)
    
  •      ActionController::Base.cache_store.clear
    
  •      @serializable_resource_options = {} # adapter.instance_options
    
  •    end
    
  •    def build_author_relationship(author_attributes, association_name)
    
    I think it's pretty straight forward, usage wise.

though, why do you need to reset the reflection stuff?
is that for isolation or something else?
my concern is that if something is happening to those normally that is being avoided here, it could reveal a hole in testing.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@NullVoxPopuli
Copy link
Contributor

An issue I noticed when playing around with this briefly:

pasted image at 2016_08_30 10_15 pm

sponsor should be of type organizations, and sponsored should be of type events

belongs_to associations now use the association_name for the model name, rather than the type of the corresponding serializer

@bf4 bf4 force-pushed the smarter_association_id_lookup branch 2 times, most recently from ce5c4b8 to 919380e Compare September 27, 2016 15:26
@bf4
Copy link
Member Author

bf4 commented Sep 27, 2016

I just rebased and broke a lot of stuff... I'll need to review the diff for what changes we actually want at this point. So, yes, lots of failures now that need to be addressed, it is known.

@bf4 bf4 force-pushed the smarter_association_id_lookup branch from ce5c4b8 to 6e41528 Compare April 30, 2017 23:32
@bf4 bf4 merged commit 7d0f4e0 into rails-api:master Apr 30, 2017
@bf4 bf4 deleted the smarter_association_id_lookup branch April 30, 2017 23:50
@furkanayhan
Copy link

furkanayhan commented Jun 16, 2017

@bf4 Hey, thank you for this improvement.
But I think there is something wrong with polymorphic-belongs_to relationships, after this pr is merged.

Examle:

serializer:

class CommentSerializer < ActiveModel::Serializer
  # ....
  belongs_to :commentable
end

request:
/comments?include=commentable

result:

//...
"relationships": {
  "commentable": {
    "data": {
      "id": "1",
      "type": "commentables"
    }
  }
}
//...

Can you check it out?

def initialize(*)
super
options[:links] = {}
options[:include_data_setting] = Serializer.config.include_data_default
options[:meta] = nil
@type = options.fetch(:type) do
class_name = options.fetch(:class_name, name.to_s.camelize.singularize)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is causing the bug reported here: #2160 In particular, the use of the field's name if the class_name and type aren't specified breaks belongs_to associations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants