DEV Community

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

Posted on • Edited on

2 2

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)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more