Creating a domain with Many-to-many relationships: — using Active Record Associations, Rails Nested Resources and Helper Methods
In my last Sinatra portfolio project, I created a climbing web application, Bolderer, which allows User to log bouldering Problems and track their climbing progress.
My article on building a simple domain with Sinatra: Sinatra Web App: MVC, Sessions, and Routes
Simple Association Diagram
The goal for my Ruby on Rails project is to adopt the Domain-driven Design (DDD) and continue to modify my domain: stretching beyond just User, Problem, and Style models. I would also like to improve my domain logic with more complex database queries and data relationships.
I want a user to not only be able to track their individual climbing progress (by logging problems that they have climbed), but also share and compare their 'sends' with others.
With this in mind, I have created a new Bolderer Association Diagram:

Note: comments and rewards will be implemented in the near future
To allow users to share the same problems that they have climbed, I have created a Many-to-many connection between the two models.
How to declare a Many-to-many association between Active Record models
A many-to-many connection is set up between the User model and Problem models, by implementing a has_many :through association. The join table Send belongs to both the User and Problem:
class User < ApplicationRecord
has_many :sends
has_many :problems, through: :sends
end
class Send < ApplicationRecord
belongs_to :user
belongs_to :problem
end
class Problem < ApplicationRecord
belongs_to :wall
has_many :sends
has_many :users, through: :sends
has_many :problem_styles
has_many :styles, through: :problem_styles
end
class Wall < ApplicationRecord
has_many :problems
end
class Style < ApplicationRecord
has_many :problem_styles
has_many :problems, through: :problem_styles
end
class ProblemStyle < ApplicationRecord
belongs_to :problem
belongs_to :style
end
With the help of Active Record has_many :through association, I can now use methods provided by Rails, such as:
@user.sends-
@user.problems @send.user@send.problem@problem.users@problem.sends
Following the set up of my model associations, I encounter the next problem: with so many models present, how can I maintain a separation of concerns and organize my routes in a relatively DRY manner?
How to Use Nested Resources with appropriate RESTful URLs
⚠️ Routes Previously Used in my Sinatra Application
| HTTP Verb | Route | CRUD Action | Used for/ result |
|---|---|---|---|
| GET | / | index | index page to welcome user - login/ signup |
| GET | /problems | index | displays all problem (all problems are rendered) |
| POST | /problems | create | creates a problem; save to db |
| GET | /problems/:id | show | displays one problem based on ID in the url (just one problem is rendered) |
| GET | /problems/:id/edit | edit | displays edit form based on ID in the URL |
| PATCH | /problems/:id | update | modifies an existing problem based on ID in the url |
| DELETE | /problems/:id | delete | deletes one article based on ID in the URL |
| GET | /users/:username | show | display one user’s problems based on :username in the url |
Note: excluding login/ create account routes
As you can see, all of the problems belonging to a user were just displayed under their show page in my Sinatra Bolderer application. A user may have sent a problem without knowing that their friend has also sent it. Meanwhile, the problem index view was displaying multiple duplicated problems logged by different users.
In our newly drawn association diagram, a send can logically be considered a child object to a user, so it can also be considered a nested resource of a user for routing purposes. I am now able to document this parent/child relationship in my routes and URLs.
💡 Nested Resource Routes Used in my Rails Application
resources :users, only: [:index, :new, :create, :show] do
resources :sends
get 'sends/sort/easiest', to: 'sends#easiest'
get '/sends/sort/hardest', to: 'sends#hardest'
end
In addition to the routes for Sends, this declaration will also route Sends to a SendsController, where I will be defining all the actions used to:
- Display all sends (sends#index)
- Display a form for creating a new send belonging to a user (sends#new)
- Create a new send belonging to a user (sends#create)
- Display a specific send by a user (sends#show)
- Display a form for editing a send belonging to a user (sends#edit)
- Update a specific send by a user (sends#update)
- Delete a specific send belonging to a specific user (sends#delete)
Nested Route URL Helpers
Rails has also magically generated a set of nested route URL Helpers for my nested resource routes.
Some examples of the routing helpers created include user_sends_path and edit_user_sends_path. These helpers take an instance of User as the first parameter (user_sends_url(@user)).
Combining Nested Route URL helpers with link_to helper
I can now easily use Rails link_to helper method and the named helpers for our nested routes to create a link to a specific User's Send show page:
🔗 Creating a link in plain HTML:
<a href="/users/#{@send.user.id}/sends/#{@send.id}">See this send</a>
🔗 V.S. Using Rails helper method link_to + named helpers for nested routes:
<%= link_to 'See this send', user_send_path(@send.user, @send) %>
Note: Rails is able to extract the ids of the @send.user and @send passed into the user_send_path, and it will redirect us to the show page of this specific send.
🔎 Class-level Active Record Scope methods
Lastly, I find it immensely helpful to use Rails scope methods for improving my domain logic, as it allows me to perform complex database queries:
Problem model
scope :sort_by_date, -> { order('created_at desc') }
scope :sort_by_grade, -> { order('grade desc') }
Send model
scope :sort_by_date, -> { order('date_sent desc') }
scope :sort_by_grade_desc, -> (user) {
where(user_id: user).joins(:problem).order('grade desc')
}
scope :sort_by_grade_asc, -> (user) {
where(user_id: user).joins(:problem).order('grade asc')
}
User model
# query user table for user who climbed the hardest graded problem
scope :best_climber, -> { joins(:problems).order('grade desc').distinct.limit(1).first }
A user can now easily browse problems and sends:
-
User'ssends: sort by most recent sends, easiest to hardest sends, and hardest to easiest sends -
Problems: sort by most recently created problems, easiest to hardest problems, and hardest to easiest problems -
User: Display the crusher who climbed the hardest problem
Takeaways
- With the help of Rails, I can easily create Active Record associations between different models while developing my domain.
- Implement nested resources, a powerful tool, to represent the parent/child relationships in my domain (A
Sendbelongs to aUser) and to keep my routes tidy. - Use the 🔗nested route URL helpers to easily link to different views
- Use 🔎scope methods to perform complex database queries.


Top comments (3)
Nice work on breaking down these Rails concepts! One question...what software do you use to create your association diagrams? Thanks!
Thanks, Smith! I created my association diagrams on app.diagrams.net.
Thanks so much!