I'm still learning how to use has_many and has_many through relationships effectly. I am currently building a system where I would like users to be able to access certain maps that they are added to.
The map model is what I need the user to be able to access if they are apart of a certain group.
class Map < ApplicationRecord
has_many :rows
has_many :mapgroups
has_many :groups, through: :mapgroups
end
Since a user can belong to many groups I have a has_many through relationship
class Usergroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
class User < ApplicationRecord
has_many :usergroups
has_many :groups, through: :usergroups
end
class Group < ApplicationRecord
has_many :usergroups
has_many :users, through: :usergroups
has_many :mapgroups
has_many :maps, through: :mapgroups
end
I thought about making a mapgroup model to take care of this but, so far, I am not so sure this is going to work.
class Mapgroup < ApplicationRecord
belongs_to :map
belongs_to :group
end
I am looking for a method to check to see what groups the user is apart of and then, based on those groups, give the user access to the corresponding maps. Am I on the right track with the relationships? How could I do this?
If you want to use MapGroup model only for keeping users an map connected (ModelGroup has only foreign keys on Group and Map) it's not the best approach. In this case it's better to opt for has_and_belongs_to_many assosiation. It will let you to assosiate Groups and Maps without creating useless model.
A has_and_belongs_to_many association creates a direct many-to-many connection with another model, with no intervening model.
class Group < ApplicationRecord
...
has_and_belongs_to_many :maps
end
class Map < ApplicationRecord
...
has_and_belongs_to_many :groups
end
Related
I am working on an app where users have many quizzes and quizzes can have many users. I have set the relationships:
class User < ApplicationRecord
has_many :studies
has_many :quizzes, through: :studies
end
class Quiz < ApplicationRecord
has_many :studies
has_many :users, through: :studies
end
class Study < ApplicationRecord
belongs_to :user
belongs_to :quiz
end
I have a field in the Study table to store the score that the user made on the quiz, but I am unable to access the field. I have tried #quiz.studies.score and #quiz.study.score but Rails give me an undefined method. How to I access the field in a join model of a has_many though relationship?
#quiz.studies return the collection of studies. So you have to use first, last, each to get the score of the specific studies.
Try this:
#quiz.studies.first.score
I've been going back and forward on this and I would like some advices.
I have "User" that can be part of many "Organizations", and for each one they can have many "Roles". (actually I have this scenario repeated with other kind of users and with something like roles, but for the sake of the example I summed it up).
My initial approach was doing a Table with user_id, organization_id and role_id, but that would mean many registers with the same user_id and organization_id just to change the role_id.
So I thought of doing an organization_users relation table and an organization_users_roles relation. The thing is, now I don't exactly know how to code the models.
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users, join_table: :organization_users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations, join_table: :organization_users
end
class OrganizationUser < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :organizations
has_many :organization_user_roles
has_many :roles, through: :organization_user_roles
end
class OrganizationUserRole < ActiveRecord::Base
has_and_belongs_to_many :roles
has_and_belongs_to_many :organization_users
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :organization_user_roles
end
If for example I want to get: ´OrganizationUser.first.roles´ I get an error saying: PG::UndefinedTable: ERROR: relation "organization_user_roles" does not exist
How should I fix my models?
You should use a much simpler approach. According to your description, Roles is actually what connects Users to Organizations and vice-versa.
Using the has_many and has_many :through associations, this can be implemented like the following:
class User < ActiveRecord::Base
has_many :roles, inverse_of: :users, dependent: :destroy
has_many :organizations, inverse_of: :users, through: :roles
end
class Organization < ActiveRecord::Base
has_many :roles, inverse_of: :organizations, dependent: :destroy
has_many :users, inverse_of: :organizations, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user, inverse_of: :roles
belongs_to :organization, inverse_of: :roles
end
If you wish to preserve roles when you destroy users or organizations, change the dependent: keys to :nullify. This might be a good idea if you add other descriptive data in your Role and want the role to remain even though temporarily vacated by a user, for example.
The has_many :through association reference:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
To add to jaxx's answer (I upvoted), I originally thought you'd be best looking at has_many :through:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :positions
has_many :organizations, through: :positions
end
#app/models/position.rb
class Position < ActiveRecord::Base
#columns id | user_id | organization_id | role_id | etc | created_at | updated_at
belongs_to :user
belongs_to :organization
belongs_to :role
delegate :name, to: :role #-> #position.name
end
#app/models/organization.rb
class Organization < ActiveRecord::Base
has_many :positions
has_many :users, through: :positions
end
#app/models/role.rb
class Role < ActiveRecord::Base
has_many :positions
end
This will allow you to call the following:
#organization = Organization.find x
#organization.positions
#organization.users
#user = User.find x
#user.organizations
#user.positions
This is much simpler than your approach, and therefore has much more ability to keep your system flexible & extensible.
If you want to scope your #organizations, you should be able to do so, and still call the users / positions you need.
One of the added benefits of the code above is that the Position model will give you an actual set of data which can be shared between organizations and users.
It resolves one of the main issues with jaxx's answer, which is that you have to set a role for every association you make. With my interpretation, your roles can be set on their own, and each position assigned the privileges each role provides.
If the user can have many Roles for a single organisation,
and OrganizationUser represents this membership,
than, yes, you need another table for organization_user_roles.
You need to explicitly create it in the database (normally with a migration)
To not get confused, try to find a nice name for OrganisationUser, like employment, membership, etc.
I am a beginner in rails, and I have a question about accessing one model from another model that is associated with several degrees of separation.
Let's say I have these models:
Account has_many Spies
Spy has many SpyRelationships and belongs to Account
SpyRelationship belongs to Listing and belongs to Spy
How would I set up the associations so that I could simply pull all the listings associated with a given Account (via its spies and spyrelationships)? What line of code would allow me to do so, after those associations are setup properly?
I'm guessing you want to access a listing through a spy?
class Account < ActiveRecord::Base
has_many :spies
has_many :listings, through: :spies
end
class Spy < ActiveRecord::Base
belongs_to :account
has_many :spy_relationships
has_many :listings, through: :spy_relationships
end
class SpyRelationship < ActiveRecord::Base
belongs_to :listing
belongs_to :spy
end
class Listing < ActiveRecord::Base
has_many :spy_relationships
end
I have a User model:
class User < ActiveRecord::Base
has_many :projects, dependent: :destroy
end
and a Project model:
class Project < ActiveRecord::Base
belongs_to :user
end
What should I do if I want a User to be able to fund Projects, and a Project can be funded by many Users?
This would mean I get a Many-to-many relationship, and I would need an additional intermediate table. Call it user_projects:
class UserProject < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
But how do I cope with the previous relationship I had between the models before I implemented the third one?
How do I know which project belongs to which user if I have the intermediate table?
Would I modify the tables the following way?
class User < ActiveRecord::Base
has_many :projects, through: :user_project, dependent: :destroy
has_many :user_projects
end
class Project < ActiveRecord::Base
has_many :user_projects
has_many :users, through: user_project
end
Whether or not you need an intermediate table depends on if there is any associated data you need to store with the user/project pair -- such as a dollar amount or date info or role, etc.
If you don't need to store anything else, then just use a HABTM relationship. Otherwise, your final solution would be the way to go.
I personally don't like the choice of 'user_project' as it's too close to the HABTM's 'users_projects'. Perhaps something like ProjectMember or ProjectFunder or Funding would be better, but it kind of depends on what extra data you need to store.
On the application I'm currently working on, I'm stuck on setting up the associations between 3 models to ensure referential integrity. I have an Event model, Building model, and a Room model. The association in real life is pretty intuitive. An Event can only be in one Building and one Room. A Building clearly can have multiple rooms.
Here's what I have set up now. However, how can Events specify their room if they belong to Buildings, and the foreign key for the Room is in the Events table? Is this where you use a has_many :through relationship? Is it good practice to store both the Building and Room foreign keys in the Event table, as Rooms are owned by Buildings? What about the conditional relationship that requires a building to be specified before allowing a room to be specified (some buildings have 2 rooms, others have 20, for example)
Sorry I'm so unclear on this. Thanks in advance for the help!
class Event < ActiveRecord::Base
belongs_to :building
end
class Building < ActiveRecord::Base
has_many :events
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :building
end
I think the best way to handle this is to do something like the following:
class Event < ActiveRecord::Base
belongs_to :room
has_one :building, :through => :room
end
class Building < ActiveRecord::Base
has_many :events
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :building
end
So you can use has_one :through to specify that an event owns a hotel
I would recommend the following:
class Event < ActiveRecord::Base
belongs_to :room
has_one :building, through: :room
end
class Building < ActiveRecord::Base
has_many :events, through: :rooms
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :building
has_many :events
end
This way you can do #room.events, #event.building, #building.events