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)