Jsonapi-resources: Including composed relationships

Created on 21 Jun 2016  路  5Comments  路  Source: cerebris/jsonapi-resources

I want to find out how to handle symmetric relationships with JSON API (and an Ember.js front-end). I have the following models:

class Person < ActiveRecord::Base
  has_many :friended, through: :friender_friendships, class_name: Person
  has_many :friended_by, through: :friended_friendships, source: :friended, class_name: Person
  has_many :friender_friendships, class_name: Friendship, foreign_key: "friender_id"
  has_many :friended_friendships, class_name: Friendship, foreign_key: "friended_id"

  def friendships
    Friendship.where("friender_id = ? OR friended_id = ?", id, id);
  end

end
class Friendship < ActiveRecord::Base
  belongs_to :friender, class_name: Person
  belongs_to :friended, class_name: Person
end

And the current resources:

class PersonResource < JSONAPI::Resource
  attributes :name
  relationship :friender, to: :many, class_name: "Person"
  relationship :friended_by, to: :many, class_name: "Person"
  relationship :friender_friendships, to: :many, class_name: "Friendship"
  relationship :friended_friendships, to: :many, class_name: "Friendship"
  relationship :friendships, to: :many, class_name: "Friendship"
end
class FriendshipResource < JSONAPI::Resource
  has_one :friender
  has_one :friended
  attributes :strength
end

When looking at a person's profile, I would like to show all friendships of that person, whether they are on the "friender" or the "friended" end of the relationship. When I'm fetching the person resource, I pass include=friendships so that all friendships are also returned and I don't need further requests to fetch friendship data.

However, that fails with the following error:

Association named 'friendships' was not found on Person; perhaps you misspelled it?

The reason for this is that jsonapi_resources eventually calls records.includes(relationship) (see here), and since friendships is not a real relationship, ActiveRecord throws an error.

I'm not really sure how to solve this with jsonapi_resources. It might be possible to turn friendships into a "real" hasMany relationship with AR, but I haven't found a way to do that (although it's possible my AR-fu is just lacking).

If not with AR, is there a way to solve this problem with jsonapi_resources? (without making an extra roundtrip to the server). Thank you.

Enhancement

Most helpful comment

I have a similar issue. I have an extension defined on a has_many association:

class User < ActiveRecord::Base
  has_many :client_account_assignments
  has_many :clients, -> { uniq }, through: :client_account_assignments do
    def visible
      proxy_association.owner.visible_clients
    end
  end
  ...
  def visible_clients
    if has_role?(:admin) || self.all_client_access
      Client.all
    elsif has_role?(:client) || has_role?(:clientmanager)
      Client.where(id: self.client.id)
    else
      self.clients
    end
  end
  ...
end

In my API output, I always want @user.clients.visible. However, I cannot find a way to coerce the has_many :clients relationship in the user resource to output @model.clients.visible.

It seems like both my issue and the issue described @balinterdi could be solved if we had the ability to reference a method on the model whose output is known to be an AR relationship.

All 5 comments

I have a similar issue. I have an extension defined on a has_many association:

class User < ActiveRecord::Base
  has_many :client_account_assignments
  has_many :clients, -> { uniq }, through: :client_account_assignments do
    def visible
      proxy_association.owner.visible_clients
    end
  end
  ...
  def visible_clients
    if has_role?(:admin) || self.all_client_access
      Client.all
    elsif has_role?(:client) || has_role?(:clientmanager)
      Client.where(id: self.client.id)
    else
      self.clients
    end
  end
  ...
end

In my API output, I always want @user.clients.visible. However, I cannot find a way to coerce the has_many :clients relationship in the user resource to output @model.clients.visible.

It seems like both my issue and the issue described @balinterdi could be solved if we had the ability to reference a method on the model whose output is known to be an AR relationship.

Yes, @sirvine's suggestion would solve my problem, too, and is a great one, I think.

@balinterdi How did you solve that problem?

@fsmanuel I could not, I went around it.

I fetch the friendships separately, and then I force linkage data to be included for the linked resources:

class FriendshipResource < JSONAPI::Resource
  has_one :friender, always_include_linkage_data: true
  has_one :friended, always_include_linkage_data: true
  attributes :strength
end

This way, there is an extra request to fetch the friendships (e.g /people/24/friendships), but then the related friended and friender resources (of type person) for each one are not refetched from the server if they had been fetched before.

It's hard to explain this and I'm not sure if this makes sense. But you wrote this with perfect timing as I'm going to publish a two blog posts about this on my blog, the first of which goes out tomorrow :)

So, to clarify, my "solution" is not really a solution for the problem, and I'd still like to find one in jsonapi-resources. Being able to include a relationship that is not 1:1 mapped to an AR relationship would still be valuable.

I'm having the same problem described. I have Users and Games that are has_many :through associated through my Roles join table that also has what the User status is for a particular Game. @balinterdi also thanks for writing that blog post it helped me fix my issues on the Ember side of things. I may end up just creating another resource like you did.

Was this page helpful?
0 / 5 - 0 ratings