Skip to content

Commit

Permalink
Add join model for groups and forms
Browse files Browse the repository at this point in the history
We would like groups to have zero or more forms.

Ideally we would do this without adding data to the form record in the
forms-api database about data in the forms-admin database. This leads us
to using a join table, so we can associate groups with forms but also do
the reverse lookup. Normally to use a join table in Rails we use the
`has_and_belongs_to_many` association [[1]]. However, models in
ActiveResource cannot be associated with models in ActiveRecord [[2]].
This means we can't use any of the usual methods to maintain the
association.

Instead, this commit takes the approach of having an explicit join
model. It's clunky, but is the best approach I've tried so far. It
doesn't rely too much on understanding Rails magic, and it doesn't
require writing any SQL.

Probably the proper fix for this issue is keeping the form records and
the group records in the same database, but that is outside the scope of
this ticket.

[1]: https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
[2]: rails/activeresource#292
  • Loading branch information
lfdebrux committed Feb 29, 2024
1 parent 0f7d8d7 commit 3e947c8
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 1 deletion.
2 changes: 2 additions & 0 deletions app/models/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class Group < ApplicationRecord
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships

has_many :group_forms

validates :name, presence: true
before_create :set_external_id

Expand Down
10 changes: 10 additions & 0 deletions app/models/group_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class GroupForm < ApplicationRecord
self.primary_key = %i[form_id group_id]
self.table_name = :groups_form_ids

belongs_to :group

def form
Form.find(form_id)
end
end
7 changes: 7 additions & 0 deletions db/migrate/20240227074047_create_join_table_form_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class CreateJoinTableFormGroup < ActiveRecord::Migration[7.1]
def change
create_join_table :forms, :groups, table_name: :groups_form_ids do |t|
t.index :form_id, unique: true
end
end
end
8 changes: 7 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions spec/models/group_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,70 @@
expect { group.destroy }.to change(Membership, :count).by(-1)
end
end

describe "associating forms with groups" do
it "can have zero forms" do
group = build :group

expect(group.group_forms).to be_empty
end

it "can be associated with a form ID" do
group = build(:group, id: 1)
group.group_forms.build(form_id: 1)
group.save!

expect(described_class.find(1).group_forms).to eq [
GroupForm.build(form_id: 1, group_id: 1),
]
end

it "can be associated with many form IDs" do
group = build(:group, id: 1)
group.group_forms.build(form_id: 2)
group.group_forms.build(form_id: 3)
group.group_forms.build(form_id: 4)
group.save!

expect(described_class.find(1).group_forms).to eq [
GroupForm.build(form_id: 2, group_id: 1),
GroupForm.build(form_id: 3, group_id: 1),
GroupForm.build(form_id: 4, group_id: 1),
]
end

it "is associated with a form through the form ID" do
form = build :form, id: 1

ActiveResource::HttpMock.respond_to do |mock|
request_headers = { "Accept" => "application/json", "X-API-Token" => Settings.forms_api.auth_key }
mock.get "/api/v1/forms/1", request_headers, form.to_json, 200
end

group = build(:group, id: 1)
group.group_forms.build(form_id: 1)
group.save!

expect(described_class.find(1).group_forms[0].form).to eq form
end

it "associates forms with groups through the form ID" do
group = build(:group, id: 1)
group.group_forms.build(form_id: 1)
group.save!

expect(GroupForm.find_by(form_id: 1).group).to eq group
end

it "raises an error if a form already belongs to a group" do
group = build(:group, id: 1)
group.group_forms.build(form_id: 1)
group.save!

other_group = build(:group, id: 2)
other_group.group_forms.build(form_id: 1)

expect { other_group.save! }.to raise_error ActiveRecord::RecordNotUnique
end
end
end

0 comments on commit 3e947c8

Please sign in to comment.