DEV Community

Cover image for JSONAPI::Resources & non-default relationship names
Michal Bryxí
Michal Bryxí

Posted on • Edited on

JSONAPI::Resources & non-default relationship names

JSONAPI is a standard that serves as an API anti-bikeshedding tool. JSONAPI::Resources is a minimal coding implementation of said standard for Ruby on Rails(RoR) and helped me to build big & complex codebases with next to zero lines of code.

JSONAPI::Resources builds on standards that RoR comes with, so in most cases you don't have to add any extra code and it will just work™. But every time I have to override default relationship names I get lost in the documentation. So I'm going to document my recent example here for future me (and you):

First we need to define two resources. For this example I will use:

  • Two models: Meeting and Election.
  • Where meeting can have one active_election.
  • Therefore in table meetings there is a column active_election_id.

Show me the code

Let's see how the schema looks like:

# backend/db/schema.rb ActiveRecord::Schema.define(version: 2020_11_21_090004) do create_table "meetings", force: :cascade do |t| t.bigint "active_election_id" # some other attributes end create_table "elections", force: :cascade do |t| # some other attributes end 
Enter fullscreen mode Exit fullscreen mode

Then we will need to define routes:

# backend/config/routes.rb Rails.application.routes.draw do namespace :api do jsonapi_resources :elections jsonapi_resources :meetings end end 
Enter fullscreen mode Exit fullscreen mode

models:

# backend/app/models/meeting.rb class Meeting < ApplicationRecord belongs_to :active_election, class_name: 'Election', optional: true end # backend/app/models/election.rb class Election < ApplicationRecord has_one :active_for, class_name: 'Meeting', foreign_key: 'active_election_id' end 
Enter fullscreen mode Exit fullscreen mode

and finally resources:

# backend/app/resources/api/meeting_resource.rb module Api class MeetingResource < Api::BaseResource model_name 'Meeting' has_one :active_election end end # backend/app/resources/api/election_resource.rb module Api class ElectionResource < Api::BaseResource model_name 'Election' has_one :active_for, class_name: 'Meeting', foreign_key: 'active_election_id' end end 
Enter fullscreen mode Exit fullscreen mode

Note: Api::BaseResource is not strictly necessary, but it's an abstraction that I find very useful, so just to make the example complete:

# backend/app/resources/api/base_resource.rb module Api class BaseResource < JSONAPI::Resource abstract # All the stuff that's common to all resources. # For example pundit authorization: include JSONAPI::Authorization::PunditScopedResource end end 
Enter fullscreen mode Exit fullscreen mode

How to use this in EmberJS

API is one part of the story. But I still have to have a client that will use it. In my case it's EmberJS and ember-data:

// ui/frontend/app/models/meeting.js import Model, { belongsTo } from '@ember-data/model'; export default class MeetingModel extends Model { @belongsTo('election', { inverse: "activeFor" }) activeElection; } // ui/frontend/app/models/election.js import Model, { hasMany } from '@ember-data/model'; export default class ElectionModel extends Model { @hasMany('meeting', {inverse: 'activeElection'}) activeFor; } 
Enter fullscreen mode Exit fullscreen mode

Conclusion

At this point I can simply assign active_election to a meeting:

let meeting = this.store.find('meeting', 27); let election = this.store.find('election', 42); meeting.activeElection = election; meeting.save(); 
Enter fullscreen mode Exit fullscreen mode

And it should all work 🪄🦄

Notes

  • In these cases the usage of hasMany and belongsTo might be confusing, but it's because those just define where the foreign_key lives. And partly because of my poor naming conventions.
  • There might be a way to simplify this even more and I will try to keep the article updated as I find orientate myself more in the topic.

Photo by Startup Stock Photos from Pexels

Top comments (0)