Some notes about relationships in Rails

Share this post!

I had been in troubles setting up some relationships… in Rails too. Just to avoid the same headhecks in the future I decided to write this post. This code is supposed to help me (and you!) to make things easier next time.

The code has been tested on ruby-2.3.0 and Rails 5.0.2.

One_to_many relationship

As an example, one Author can have many Interests.

# app/models/author.rb
class Author < ActiveRecord::Base
has_many :interests
end
# app/models/interest.rb
class Interest < ActiveRecord::Base
belongs_to :author
end
# app/controllers/authors_controller.rb
...
private
def set_author
@author = Author.find(params[:id])
end
def author_params
params.require(:author).permit(:name, :interest_id)
end
...
# app/views/authors/_form.html.erb
<%= form_for(@author) do |f| %>
<% if @author.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@author.errors.count, "error") %> prohibited this author from being saved:</h2>
<ul>
<% @author.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :author_id %><br>
<%= f.collection_select(:interest_id, Interest.all, :id, :name, prompt: true) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

Using has_and_belongs_to_many

# using has_and_belongs_to_many
rails g model mountain name
rails g model climber name
class Climber < ApplicationRecord
has_and_belongs_to_many :mountains
end
class Mountain < ApplicationRecord
has_and_belongs_to_many :climbers
end
# using a model...
rails g model climbers_mountains climber:references mountain:references
# ...Rails will create
class CreateClimbersMountains < ActiveRecord::Migration[5.0]
def change
create_table :climbers_mountains do |t|
t.references :climber, foreign_key: true
t.references :mountain, foreign_key: true
t.timestamps
end
end
end
# using a migration
rails g migration CreateJoinTableClimbersMountains climber mountain
# ...Rails will create this. You must uncomment one of the t.index lines
class CreateJoinTableClimbersMountains < ActiveRecord::Migration[5.0]
def change
create_join_table :climbers, :mountains do |t|
t.index [:climber_id, :mountain_id]
# t.index [:mountain_id, :climber_id]
end
end
end
# then you should execute
$ rake db:create
$ rake db:migrate (or 'rake db:migrate RAILS_ENV=test' for the test environment)
$ rails c
john = Climber.create(name: "John Doe")
udalaitz = Mountain.create(name: "Udalaitz")
john.mountains << udalaitz
===

Using has_many through

I prefer to use the ‘has_many :through’ based solution. It is possible to create a Model associated to the relationship, with attributes, so the solution is much more flexible.

# using has_many :through
rails g model mountain name
rails g model climber name
rails g model adventure climber:references mountain:references
class Climber < ApplicationRecord
has_many :adventures
has_many :mountains, :through => :adventures
end
class Mountain < ApplicationRecord
has_many :adventures
has_many :climbers, :through => :adventures
end
class Adventure < ApplicationRecord
belongs_to :climber
belongs_to :mountain
end
$ rake db:create
$ rake db:migrate (or 'rake db:migrate RAILS_ENV=test' for the test environment)
$ rails c
john = Climber.create(name: "John Doe")
udalaitz = Mountain.create(name: "Udalaitz")
john.mountains << udalaitz

, ,