How do you associate a double many_to_many relationship? and also, what is it called? I know that there's no "double" many_to_many.
So have these models in rails, a User, Role, UserRole, Menu, RoleMenu.
A user can access menus depending on the roles. On console, I can do this User.first.roles.first.menus. My question is there a way to do like this User.first.menus, so it'll shorten? How do you associate User to Menu? What should I add to my models? what migration should I create?
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
end
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, through: :user_roles
has_many :role_menus
has_many :menus, through: :role_menus
end
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name
t.timestamps null: false
end
end
end
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class CreateUserRoles < ActiveRecord::Migration
def change
create_table :user_roles do |t|
t.belongs_to :user
t.belongs_to :role
t.timestamps null: false
end
end
end
class Menu < ActiveRecord::Base
has_many :role_menus
has_many :roles, through: :role_menus
end
class CreateMenus < ActiveRecord::Migration
def change
create_table :menus do |t|
t.string :name
t.timestamps null: false
end
end
end
class RoleMenu < ActiveRecord::Base
belongs_to :role
belongs_to :menu
end
class CreateRoleMenus < ActiveRecord::Migration
def change
create_table :role_menus do |t|
t.belongs_to :role
t.belongs_to :menu
t.timestamps null: false
end
end
end
Did you mean User.first.menus instead of User.menus ? Because, latter can't be achieved as you are trying to access menus through User class (which is more of a scope implementation) and not the particular user.
For the first case, as I can see that you are already aware of the has_many, through association. We will use the same to achieve that. Following should work.
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
has_many :menus, through: :roles
end
How do you associate a double many_to_many relationship? and also, what is it called? I know that there's no "double" many_to_many.
Well, yes, there's nothing called double many to many association but it is more aptly called multiple or nested many to many relation/association. And as mentioned above, it can be achieved through has_many, through
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
has_many :menus, through: :roles
end
adding another has_many.. through should work
Related
Currently I have following migrations:
class CreateDevices < ActiveRecord::Migration[5.0]
def change
create_table :devices do |t|
t.string :name
t.string :abbr
t.timestamps
end
end
end
class CreateVendors < ActiveRecord::Migration[5.0]
def change
create_table :vendors do |t|
t.string :name
t.string :abbr
t.timestamps
end
end
end
class CreateDeviceVendors < ActiveRecord::Migration[5.0]
def change
create_table :device_vendors do |t|
t.string :device
t.string :vendor
t.timestamps
end
end
end
There is many to many relationship between device and vendor, so DeviceVendors table is getting used for that. Both tables abbr column (which is unique) is getting saved in this table as device and vendor respectively.
I am using this kind of table structure so that I can seed the data and don't have to check for ids in the primary tables.
How can I set the association in all three models so that I can access in better way. Something like this:
class Device < ApplicationRecord
has_many :device_vendors
has_many :vendors, through: device_vendors
end
class Vendor < ApplicationRecord
has_many :device_vendors
has_many :devices, through: device_vendors
end
class DeviceVendor < ApplicationRecord
belongs_to :device
belongs_to :vendor
end
I know I have to apply foreign_key: :abbr to belongs_to in models but not sure in which ones. Also whether I need to change/add the migration for this?
The foreign_key, as you point out, is on the belongs_to table, but you need to specify both primary_key and foreign_key (since none is the default id) in all associations:
class Device < ApplicationRecord
has_many :device_vendors, primary_key: "abbr", foreign_key: "device"
has_many :vendors, through: device_vendors
end
class Vendor < ApplicationRecord
has_many :device_vendors, primary_key: "abbr", foreign_key: "vendor"
has_many :devices, through: device_vendors
end
class DeviceVendor < ApplicationRecord
belongs_to :device, primary_key: "abbr", foreign_key: "device"
belongs_to :vendor, primary_key: "abbr", foreign_key: "vendor"
end
Also notice that the foreign key is not abbr, that's the primary key in both device and vendor; the foreign key is the one in the table with belongs_to (i.e. device and vendor in device_vendors).
I have a Users class, and a UserGroup class:
class User < ActiveRecord::Base
has_many :group_memberships
has_many :users_groups, through: :group_memberships
...
class UsersGroup < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
... and a GroupMembership class to join them -
class GroupMembership < ActiveRecord::Base
belongs_to :users, dependent: :destroy
belongs_to :users_groups, dependent: :destroy
My migrations look like this -
class CreateUsersGroups < ActiveRecord::Migration
def change
create_table :users_groups do |t|
t.string :title
t.string :status
t.string :about
t.timestamps null: false
end
end
end
class CreateGroupMembership < ActiveRecord::Migration
def change
create_table :group_memberships do |t|
t.integer :user_id, index: true
t.integer :users_group_id, index: true
t.boolean :owner
end
end
end
So user.group_memberships is perfectly happy, but user.users_groups returns an error -
undefined method `relation_delegate_class' for Users:Module
Similarly, users_group.group_memberships is fine, but users_group.users returns exactly the same error -
undefined method `relation_delegate_class' for Users:Module
... on the users module. I've stared at this for a couple of hours, but I'm sure it's simple syntax somewhere. What's the problem?
When using belongs_to I believe you need to use a singular format:
So not belongs_to :users but belongs_to :user
class GroupMembership < ActiveRecord::Base
belongs_to :user, dependent: :destroy
belongs_to :writers_group, dependent: :destroy
I believe that since you are using 'through' you will have to use:
user.group_memberships.users_groups
This is because users does not have users_groups or vice versa.
Instead you access the users_groups through group_memberships.
I am using devise for authentication and finding a way to get out of this.
Can I explore same design user having multiple roles ?. So that he can login as Teacher or Parent both?. Basically he can switch accounts like multiple roles.
class User < ActiveRecord
belongs_to :loginable, polymorphic: true
end
class Parent < ActiveRecord
has_one :user, as: :loginable
end
class Teacher < ActiveRecord
has_one :user, as: :loginable
end
for eg: loginable_type: "Parent", loginable_id: 123
I want to find a way to change above fields, if user is logging in as 'Teacher' and its ID.
You can add a polymorphic has_many relationship:
class CreateUserRoles < ActiveRecord::Migration
def change
create_table :user_roles do |t|
t.integer :role_id
t.integer :user_id
t.string :role_type # stores the class of role
t.timestamps
end
add_index :user_roles, [:role_id, :role_type]
end
end
class AddActiveRoleToUser < ActiveRecord::Migration
def change
change_table :user_roles do |t|
t.integer :active_role_id
t.timestamps
end
end
end
class User < ActiveRecord
has_many :roles, polymorphic: true
has_one :active_role, polymorphic: true
def has_role? role_name
self.roles.where(role_type: role_name).any?
end
end
My Application as a User Model generate via Devise Gem. I want to associate with each user some roles via many-to-many association through Assignment between User and Role. How do I generate migration script so that user is associated with some roles.Model classes will look like the following
class User < ActiveRecord::Base
has_many :assignments
has_many :roles, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
end
Add a migration like this:
rails g migration create_assignments_table
And fill that file with:
class CreateAssignmentsTable < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.references :user
t.references :role
t.timestamps
end
end
end
I like to use t.references instead of t.integer to (just semantically) reflect the relations between the tables, but that's up to you.
Just need a create table migration.
rails g migration create_assignments
And be sure it has the columns you need.
class CreateAssignments < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.integer :user_id
t.integer :role_id
t.timestamps
end
end
end
Very new to Rails, have managed a few simple projects, but now stepping into more complex associations between tables and was hoping for some help.
The scenario can best be related to a sports match. Let's say we have
1) A Team (has_many players)
2) A Player (belongs_to team)
3) A Match -- now it gets tricky.
A Match will have: 2 teams, and 22 players (11 on each side) that take part in it. Also, associated with each player, will be their scores for the match (for example, Shots on goal, Goals scored, Points, etc.)
What would be the best practice to create this kind of association? Any tips would be greatly appreciated.
Models
app/models/team.rb
class Team < ActiveRecord::Base
has_many :players, inverse_of: :team
has_many :team_matches
has_many :matches, through: :team_matches
end
app/models/player.rb
class Player < ActiveRecord::Base
belongs_to :team, inverse_of: :player
has_many :player_matches
has_many :matches, through: :player_matches
end
app/models/match.rb
class Match < ActiveRecord::Base
has_many :players, through: :player_matches
has_many :teams, through: :team_matches
end
app/models/team_match.rb
class TeamMatch < ActiveRecord::Base
belongs_to :team
belongs_to :match
end
app/models/player_match.rb
class PlayerMatch < ActiveRecord::Base
belongs_to :player
belongs_to :match
end
Migrations
db/migrate/create_matches.rb
class CreateMatches < ActiveRecord::Migration
def change
create_table :matches do |t|
t.datetime :happened_at
t.timestamps
end
end
end
db/migrate/create_players.rb
class CreatePlayers < ActiveRecord::Migration
def change
create_table :players do |t|
t.string :name
t.timestamps
end
end
end
db/migrate/create_teams.rb
class CreateTeams < ActiveRecord::Migration
def change
create_table :teams do |t|
t.string :name
t.timestamps
end
end
end
db/migrate/create_player_matches.rb
class CreatePlayerMatches < ActiveRecord::Migration
def change
create_table :player_matches do |t|
t.integer :match_id
t.integer :player_id
t.integer :player_shots_on_goal
t.integer :player_goals_scored
t.timestamps
end
end
end
db/migrate/create_team_matches.rb
class CreateTeamMatches < ActiveRecord::Migration
def change
create_table :team_matches do |t|
t.integer :match_id
t.integer :team_id
t.integer :team_points
t.timestamps
end
end
end
Edit1: #Mischa should share credit here! :)
Edit2: Sorry about the many versions, I totally underestimated this problem.
Player Has and belongs to Many Match
That table should contain the details of the player playing that match. For example for which team he played, from which minute (since players can change) etc.