Relationships

One to One

For one-to-one relationships, You can use the has_one and belongs_to in your models.
Note: one-to-one relationship does not support through associations yet.
1
class Team < Granite::Base
2
has_one :coach
3
4
column id : Int64, primary: true
5
column name : String
6
end
Copied!
This will add a coach and coach= instance methods to the team which returns associated coach.
1
class Coach < Granite::Base
2
table coaches
3
4
belongs_to :team
5
6
column id : Int64, primary: true
7
column name : String
8
end
Copied!
This will add a team and team= instance method to the coach.
For example:
1
team = Team.find! 1
2
# has_one side..
3
puts team.coach
4
5
coach = Coach.find! 1
6
# belongs_to side...
7
puts coach.team
8
9
coach.team = team
10
coach.save
11
12
# or in one-to-one you can also do
13
14
team.coach = coach
15
# coach is the child entity and contians the foreign_key
16
# so save should called on coach instance
17
coach.save
Copied!
In this example, you will need to add a team_id and index to your coaches table:
1
CREATE TABLE coaches (
2
id BIGSERIAL PRIMARY KEY,
3
team_id BIGINT,
4
name VARCHAR,
5
created_at TIMESTAMP,
6
updated_at TIMESTAMP
7
);
8
9
CREATE INDEX team_id_idx ON coaches (team_id);
Copied!
Foreign key is inferred from the class name of the Model which uses has_one. In above case team_id is assumed to be present in coaches table. In case its different you can specify one like this:
1
class Team < Granite::Base
2
has_one :coach, foreign_key: :custom_id
3
4
column id : Int64, primary: true
5
column name : String
6
end
7
8
class Coach < Granite::Base
9
belongs_to :team
10
11
column id : Int64, primary: true
12
end
Copied!
The class name inferred from the name but you can specify the class name:
1
class Team < Granite::Base
2
has_one coach : Coach, foreign_key: :custom_id
3
4
# or you can provide the class name as a parameter
5
has_one :coach, class_name: Coach, foreign_key: :custom_id
6
7
column id : Int64, primary: true
8
column name : String
9
end
10
11
class Coach < Granite::Base
12
belongs_to team : Team
13
14
# provide a custom foreign key
15
belongs_to team : Team, foreign_key: team_uuid : String
16
17
column id : Int64, primary: true
18
end
Copied!

One to Many

belongs_to and has_many macros provide a rails like mapping between Objects.
1
class User < Granite::Base
2
connection mysql
3
4
has_many :post
5
6
# pluralization requires providing the class name
7
has_many posts : Post
8
9
# or you can provide class name as a parameter
10
has_many :posts, class_name: Post
11
12
# you can provide a custom foreign key
13
has_many :posts, class_name: Post, foreign_key: :custom_id
14
15
column id : Int64, primary: true
16
column name : String
17
column email : String
18
timestamps
19
end
Copied!
This will add a posts instance method to the user which returns an array of posts.
1
class Post < Granite::Base
2
connection mysql
3
table posts
4
5
belongs_to :user
6
7
# or custom name
8
belongs_to my_user : User
9
10
# or custom foreign key
11
belongs_to user : User, foreign_key: uuid : String
12
13
column id : Int64, primary: true
14
column title : String
15
timestamps
16
end
Copied!
This will add a user and user= instance method to the post.
For example:
1
user = User.find! 1
2
user.posts.each do |post|
3
puts post.title
4
end
5
6
post = Post.find! 1
7
puts post.user
8
9
post.user = user
10
post.save
Copied!
In this example, you will need to add a user_id and index to your posts table:
1
CREATE TABLE posts (
2
id BIGSERIAL PRIMARY KEY,
3
user_id BIGINT,
4
title VARCHAR,
5
created_at TIMESTAMP,
6
updated_at TIMESTAMP
7
);
8
9
CREATE INDEX user_id_idx ON posts (user_id);
Copied!

Many to Many

Instead of using a hidden many-to-many table, Granite recommends always creating a model for your join tables. For example, let's say you have many users that belong to many rooms. We recommend adding a new model called participants to represent the many-to-many relationship.
Then you can use the belongs_to and has_many relationships going both ways.
1
class User < Granite::Base
2
has_many :participants, class_name: Participant
3
4
column id : Int64, primary: true
5
column name : String
6
end
7
8
class Participant < Granite::Base
9
table participants
10
11
belongs_to :user
12
belongs_to :room
13
14
column id : Int64, primary: true
15
end
16
17
class Room < Granite::Base
18
table rooms
19
20
has_many :participants, class_name: Participant
21
22
column id : Int64, primary: true
23
column name : String
24
end
Copied!
The Participant class represents the many-to-many relationship between the Users and Rooms.
Here is what the database table would look like:
1
CREATE TABLE participants (
2
id BIGSERIAL PRIMARY KEY,
3
user_id BIGINT,
4
room_id BIGINT,
5
created_at TIMESTAMP,
6
updated_at TIMESTAMP
7
);
8
9
CREATE INDEX user_id_idx ON TABLE participants (user_id);
10
CREATE INDEX room_id_idx ON TABLE participants (room_id);
Copied!

has_many through:

As a convenience, we provide a through: clause to simplify accessing the many-to-many relationship:
1
class User < Granite::Base
2
has_many :participants, class_name: Participant
3
has_many :rooms, class_name: Room, through: :participants
4
5
column id : Int64, primary: true
6
column name : String
7
end
8
9
class Participant < Granite::Base
10
belongs_to :user
11
belongs_to :room
12
13
column id : Int64, primary: true
14
end
15
16
class Room < Granite::Base
17
has_many :participants, class_name: Participant
18
has_many :users, class_name: User, through: :participants
19
20
column id : Int64, primary: true
21
column name : String
22
end
Copied!
This will allow you to find all the rooms that a user is in:
1
user = User.create(name: "Bob")
2
room = Room.create(name: "#crystal-lang")
3
room2 = Room.create(name: "#amber")
4
Participant.create(user_id: user.id, room_id: room.id)
5
Participant.create(user_id: user.id, room_id: room2.id)
6
7
user.rooms.each do |room|
8
puts room.name
9
end
Copied!
And the reverse, all the users in a room:
1
room.users.each do |user|
2
puts user.name
3
end
Copied!
Last modified 1yr ago