JSONAPI::Resources & non-default relationship names
Tags: jsonapi, rails, ember
Published: 2020-11-23 10:43:30

[JSONAPI](https://jsonapi.org/) is a standard that serves as an API anti-bikeshedding tool. [JSONAPI::Resources](https://jsonapi-resources.com/) is a minimal coding implementation of said standard for [Ruby on Rails](https://rubyonrails.org/)(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:

```rb
# 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:

```rb
# backend/config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    jsonapi_resources :elections
    jsonapi_resources :meetings
  end
end
```

models:

```rb
# 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:

```rb
# 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:
```rb
# 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](https://emberjs.com/) and [ember-data](https://guides.emberjs.com/release/models/):

```js
// 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`:

```js
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` 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