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

Associations #30

Merged
merged 10 commits into from
Jul 21, 2016
Merged

Associations #30

merged 10 commits into from
Jul 21, 2016

Conversation

gaorlov
Copy link
Contributor

@gaorlov gaorlov commented Jul 8, 2016

adding an ActiveRecord-like interface for basic associations.

the general interface is

#{association} name, opts= {}

where the target class and foreign key is by default figured out from the name, but can be overriden

assumptions

  • the servers you're preloading from/associating have controller entities that respond to show(find) and index(where)
  • important: the index(where) action has to support skip_pagination. Otherwise you may lose associations as they get capped by the per-page limit

assocaitions

  • belongs_to - your resource has the #{object}_id for the association.
    • calls object_class.find
  • has_one - will call the server with opts.merge "#{root_class}_id => root_object.id and grab the first thing that comes back
    • calls object_class.where
  • has_many - same as has_one but will give you the full array of objects
    • calls 'object_class.where`

options

  • :foreign_key - this is what the server will get as the key for has_one and has_many. belongs_to is weird and will send the key to the resource object. for example
class Admin < JsonApiResource::Resource
  wraps Service::Client::Admin

  property user_id
  # this one is a little weird
  belongs_to :person, foreign_key: :user_id, class: User
end
  • :action - If you have a custom action you're using for lookup, you can override the default where and find. For example
class Service::Client::Superuser < Service::Client::Base
  # i don't know why you would have this, but whatever
  custom_endpoint :superuser_from_user_id, :on=> :collection, :request_method=> :get
end

class User
  wraps Service::Client::User
  has_one :superuser, action: :superuser_from_user_id
end

NOTE: keep in mind, this will still make the call with the opts.merge foreign_key => root_object.id hash. If you want to override the query, you may want to consider 1: if the API is RESTful 2: rolling your own association.

  • :prefetched_ids (has_many only) - in the case that the root object has a collection of ids that come preloaded in it
class User < JsonApiResource::Resource
  wraps Service::Client::User
  property address_ids, []

  has_many :addresses, prefetched_ids: :address_ids
end

notes

:through is not supported, nor will it ever be, because of the hidden complexity and cost of n HTTP calls.

Preloader

Sometimes you have many objects that you need to fetch associations for. It's expensive to have to iterate over them one by one and annoying to have to assign the results. Well, now there's a Preloader that can do all of that for you

example

Let's say you have users who have addresses.

class User < JsonApiResource::Resource
  wraps Service::Client::User

  # shiny new function yay
  has_many :addresses
end

class Address < JsonApiResource::Resource
  wraps Service::Client::Address
end

With the associations in place, you can now use them to preload all the addresses for your users in a single query.

@users = User.where id: [1, 2, 3, 4, 5]

# that's it. that's all you have to do.
JsonApiClient::Associations::Preloader.preload @users, :addresses

# all the users now have addresses assigned to them and will not hit the server again
puts @users.map &:addresses

params

The Preloader takes 2 parameters

  • the objects you want the associations to be tied to (@users in our example)
  • the list of associations you want bulk fetched from the server as symbols.
    • can be a single symbol, or a list
    • has to have a corresponding association on the objects, and the name has to match

notes

This is a simple tool so don't expect too much magic. The Preloader will explode if

  • the objects aren't the same class
  • the results aren't the same class (although i don't know how that would be possible)
  • the result set can't be matched to the objects

@dplummer
Copy link

dplummer commented Jul 9, 2016

You could add through support by using the includes params:

class User
  has_many :posts #... w/e
  has_many :attached_medias, through: :posts
end

# With an AR-style interface: 
User.includes(posts: :attached_medias).all
# => GET /api/users?includes=posts.attached_medias

# or from an instance:
user = User.find(123)
user.attached_medias
#=> GET /api/posts?user_id=123&includes=attached_medias

@gaorlov
Copy link
Contributor Author

gaorlov commented Jul 9, 2016

Sure, but i also never want to make through available. This is something the user has to implement for her/himself to fully understand the complexity and http cost imo

@gaorlov
Copy link
Contributor Author

gaorlov commented Jul 9, 2016

Also: this assumes that there's a certain amount of compatible infrastructure in place which there may not be.

@gaorlov gaorlov merged commit 1f9cd51 into master Jul 21, 2016
@gaorlov gaorlov deleted the associations branch July 21, 2016 23:58
end

def test_belongs_to_works_with_expicit_class
Account::Client::PartnerUser.stub :find, JsonApiClient::ResultSet.new([User.new(id: 2)]) do

Choose a reason for hiding this comment

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

what's an explicit class vs a implicit class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

whether the class is user specified or derived from the association name, like

# implicit
has_one :address

#explicit
has_one :specialty, class: LawyerSpecialty

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.

None yet

3 participants