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
andElection
. - Where
meeting
can have oneactive_election
. - Therefore in table
meetings
there is a columnactive_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
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
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
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
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
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; }
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();
And it should all work 🪄🦄
Notes
- In these cases the usage of
hasMany
andbelongsTo
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)