Skip to content

Feature/dynamic relationships stage 1#709

Open
garytaylor wants to merge 15 commits intoJSONAPI-Resources:masterfrom
shiftcommerce:feature/dynamic-relationships-stage-1
Open

Feature/dynamic relationships stage 1#709
garytaylor wants to merge 15 commits intoJSONAPI-Resources:masterfrom
shiftcommerce:feature/dynamic-relationships-stage-1

Conversation

@garytaylor
Copy link
Copy Markdown
Contributor

This PR prepares the ground for allowing "dynamic relationships" which allow relationships to be defined based on the data in the model.
This is extremely useful when you want to present the data that you have in say some metadata as a relationship to assist the front end developers who have a nice convenient jsonapi client gem.

Probably best if we have an example

This is an example of how my data might look without dynamic relationships

  "data": {
    "type": "products",
    "id": "1",
    "attributes": {
      "name": "jsonapi-resources",
      "meta_attributes": {
        "related-images": [1, 2, 3, 5, 9]
      }
    }
  }

The key thing here is the "related-images" bit which is an array of ids. The data could be stored like this for a number of reasons, but isn't much help for a front end developer who would need to know to call something like :-

Api::Image.where(id: [1, 2, 3, 5, 9])

So, because we want all of our API users to be our friends and not hate us - with this change, the "_relationships" is now an instance method as well as a class method. We cant override the class method for this to add relationships as this would affect all instance. So, instead, we might override the instance method and add the relationships from data in the instance.

The actual overriding code mentioned above is not included in this PR, this is purely the ground work for this to even be allowed to happen. A decent API to allow the dynamic relationships to be easily defined is another subject.

This PR does not change the functionality of the existing system (unless the config validate_includes is set to false (mentioned below)) - it is mainly refactoring of the code that was dynamically generated in the JSONAPI::Resource class for the relationships. This code is now moved into the JSONAPI::AssociatedResource class which is then called by the dynamically generated code mentioned above.
This is purely for reuse. We can now call the same code WITHOUT needing it to be defined as a method on the resource (such as "records_for_<>") - instead we detect that this method is not defined and try "records_for(relationship_name)" instead which the resource can fetch the data based on the dynamic relationship name.

The change to the _relationships method will not affect existing systems as the definition of it simply calls the class method as standard - which is identical to what it used to do.

So, I mentioned the validate_includes config flag - so what is this for ?
Under normal operation, if a client requested a relationship using the includes parameter that is not defined in the resource up front, it will respond with a bad request.
This prevents dynamic relationships from the word go.
So, if we set this to false, this prevents this validation which does mean that if the relationship does not exist, then we will get an error from a different place. But, if the relationship is a dynamic one, then it can work.

This change allows a resource to add to the relationships in the instance - for "virtual" relationships that are not defined in the model.
This is paving the way for dynamic virtual relationships based on the model data
When adding new relationships on a class, these new methods are now used in place of the code being defined inline (to allow re use in the next phase)
Note - the same code is being executed - just in a slightly different way - no functionality changes or breaking changes, with the exception of if someone has these methods already defined
…still respond to :save which is the important thing.
garytaylor added 10 commits May 14, 2016 14:33
…y relations that are not real AR relations that can be passed to AR's include method.

This resolves the issue where a 'relationship' is purely a method on an AR object that might return an array, an AR scope etc.. but is not a true relationship. When the query is executed the relationship will not exist and AR gets confused
Merge latest from cerebris into master
…ionship_names_to_relations when they are not defined as AR relationships. This prevents issues when the query with its includes is executed
…into cerebris-master

# Conflicts:
#	lib/jsonapi/configuration.rb
…into cerebris-master

# Conflicts:
#	lib/jsonapi/configuration.rb
Relationships that do not exist in the class can now still be included
@hidde-jan
Copy link
Copy Markdown
Contributor

Any progress on this? Is there a chance that this will be merged?

@lgebhardt
Copy link
Copy Markdown
Contributor

I've got some concerns about the added complexity and performance implications of this PR for what I'm seeing as an edge case. Have you tried simply setting up relationships and selectively hiding them using the fetchable_fields method to prune the relationships which are not appropriate for a model?

@hidde-jan
Copy link
Copy Markdown
Contributor

hidde-jan commented Aug 11, 2016

EDIT: scratch all of this, turns out this already works.

I'm personally interested in using this type of feature for 'pseudo-relationships'. Suppose we have the following setup:

class Movie
  has_many :votes
end

class Vote
  belongs_to :movie
  belongs_to :person

  # with unique index on [:person_id, :movie_id]
end

class Person
  has_many :votes
end

What I would like is to give the MovieResource a pseudo-relationship with a Vote, such that I can do something like this:

class MovieResource
  has_one :vote

  def vote
    @model.votes.find_by(person_id: context[:current_user])
  end

That doesn't seem possible currently, since has_one :vote is not backed by an ActiveRecord relationship.

@garytaylor
Copy link
Copy Markdown
Contributor Author

Sorry guys - not been looking at emails as often as I should.
The problem with defining relationships and selectively hiding them is that
these relationships are completely data driven so are unknown in advance.

It is sometimes very useful to present information as dynamic relationships
like this to make the client side easier rather than expecting them to find
things themselves.

The change that I made was mainly refactoring to allow existing code to be
used either using the metaprogramming techniques in the resource etc OR in
this dynamic situation.
We are using it in UAT at the moment and it has been working fine, but
obviously I am having to maintain a branch at present, but am planning on
rebasing master back on to it and verify that all still works with the
latest code.

Cheers

Gary

On Thu, Aug 11, 2016 at 8:51 AM Hidde-Jan Jongsma notifications@github.com
wrote:

I'm personally interested in using this type of feature for
'pseudo-relationships'. Suppose we have the following setup:

class Moview
has_many :votesend
class Vote
belongs_to :movie
belongs_to :person

with unique index on [:person_id, :movie_id]end

class Person
has_many :votesend

What I would like is to give the MovieResource a pseudo-relationship with
a Vote, such that I can do something like this:

class MovieResource
has_one :vote

def vote
@model.votes.find_by(person_id: context[:current_user])
end

That doesn't seem possible currently, since has_one :vote is not backed
by an ActiveRecord relationship.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/cerebris/jsonapi-resources/pull/709#issuecomment-239094707,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAA1QYvKoWF9g7Cfj2Hf1KOeQlTxRnVTks5qetR4gaJpZM4Ib881
.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants