Rails Rich Associations - undefined method error - ruby-on-rails

I have three basic models that I am working with:
class User < ActiveRecord::Base
has_many :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
class Group < ActiveRecord::Base
has_many :assignments
end
Using this schema, I would assume the "Assignment" model is sort of the join table, which holds the information for which users belong to which groups. So, what I am trying to do is, using a User object, find out what groups they belong to.
In Rail console, I am doing the following:
me = User.find(1)
Which returns the user object, as it should. Then, I attempt to see which "groups" this user belongs to, which I thought it would go through the "Assignment" model. But, I'm obviously doing something wrong:
me.groups
Which returns:
NoMethodError: undefined method `groups' for #<User:0x007fd5d6320c68>
How would I go about finding out which "groups" the "me" object belongs to?
Thanks very much!

You have to declare the User - Groups relation in each model:
class User < ActiveRecord::Base
has_many :assignments
has_many :groups, through: :assignments
end
class Group < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
end
Also, I recommend you to set some validations on the Assignment model to make sure an Assignment always refers to a Group AND a User:
class Assignment < ActiveRecord::Base
belongs_to :group
belongs_to :user
validates :user_id, presence: true
validates :group_id, presence: true
end

class User < ActiveRecord::Base
has_many :assignments
has_many :groups, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
class Group < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
end
Please refer association basics

Your me is of type User not Assignment. You want to do:
me.assignments.first.groups
This will give you all the groups belonging to the user's first assignment. To get all the groups you could do as MrYoshiji has commented below:
me.assignments.map(&:groups)

You didn't define a has_many on groups. Try
me.assignments.first.group
should work.

Related

How to create a group of users (roomates) within one product (property) in Rails

I have a question on a platform I'm developing in Ruby on Rails 5.2.
I have an Owner model which is the owner of properties/property. The owner will post a property so that users (in this case roomates) can share the same property/house/department, etc.
I have Owners and I have Users (both tables are created using devise):
Owner.rb:
class Owner < ApplicationRecord
has_many :properties
end
User.rb:
class User < ApplicationRecord
#Theres nothing here (yet)
end
This is where the magic happens. Property.rb:
class Property < ApplicationRecord
belongs_to :owner
has_many :amenities
has_many :services
accepts_nested_attributes_for :amenities
accepts_nested_attributes_for :services
mount_uploaders :pictures, PropertypictureUploader
validates :amenities, :services, presence: true
scope :latest, -> { order created_at: :desc }
end
How can multiple users share a property? I'm aware that it will have a many-to-many association but I'm a bit confused how to connect these relationships so when the owner posts a property it will display something like:
Property available for: 3 users
And then begin to limit users until it completes the amount of users available.
This sounds like your average many to many assocation:
class User < ApplicationRecord
has_many :tenancies, foreign_key: :tenant_id
has_many :properties, through: :tenancies
end
class Tenancy < ApplicationRecord
belongs_to :tenant, class_name: 'User'
belongs_to :property
end
class Property < ApplicationRecord
has_many :tenancies
has_many :tenants, through: :tenancies
def availablity
# or whatever attribute you have that defines the maximum number
max_tenants - tenancies.count
end
end
You can restrict the number of tenants with a custom validation.
You can use a join table, called users_properties. This table will have a property_id and user_id. You'll then have the following in your properties model:
has_many :users_properties
has_many :users, through: :users_properties
Read more about it here https://guides.rubyonrails.org/association_basics.html

How should I approach this relations in ruby?

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.

Active Record association has_many with multiple foreign keys

I'm trying to set up a referral model. A referral contains a user who is referred, a user who does the referring, and a doctor
class User < ActiveRecord::Base
has_many :referrals
belongs_to :profile, polymorphic: true
end
class Referral < ActiveRecord::Base
belongs_to :user
belongs_to :referrer, :class_name => "User"
belongs_to :doctor, :class_name => "User"
end
I'm able to create the generic has_many :referrals to see doctors that have been referred to the user however I'd also like to see the doctors that you've referred to others (using the referrer column).
I've tried has_many :doctors_referred, primary_key: "referrer_id" and has_many :doctors_referred, through: :referrals ,source: "referrer" with no luck. How can I see which doctors a user has referred?
# mostly irrelevant
class PatientProfile < ActiveRecord::Base
has_one :user, as: :profile
end
class DoctorProfile < ActiveRecord::Base
has_one :user, as: :profile
end
My first attempt would be this:
class User < ActiveRecord::Base
has_many :referrals_as_referrer, source: :referred
has_many :referrals_as_referree, source: :user
has_many :doctors_as_referrer, through: :referrals_as_referrer
has_many :doctors_as_referree, through: :referrals_as_referree
end
The issue here is that saying has_many :referrals isn't enough because you can have a referral where you were the referrer or one where you were the referree. With this you can type user.referrals_as_referrer which will give a list of referrals where user is the referrer. You can also write user.doctors_as_referrer which will go through the previous association and retrieve a list of users that represent doctors which were referred by a referral in the user.referrals_as_referrer list.
I haven't tested this and I might be wrong, but let's iterate once you try it out.

Active Record Associations: has_and_belongs_to_many, has_many :through or polymorphic association?

The Ruby on Rails app I am working on allows users to create and share agendas with other users.
In addition, we must be able to:
Display a list of agendas for each user, on his profile
Display a list of users associated with an agenda, on the agenda's page
When sharing an agenda with another user, define a role for this user, and display the role of this user on the list mentioned right above
I was going to go with a has_and_belongs_to_many association between the user and the agenda models, like that:
class User < ActiveRecord::Base
has_and_belongs_to_many :agendas
end
class Agenda < ActiveRecord::Base
has_and_belongs_to_many :users
end
But then I wondered whether this would let me get and display the #user.agenda.user.role list of roles on the given agenda page of a given user.
And I thought I should probably go with a has_many :through association instead, such as:
class User < ActiveRecord::Base
has_many :roles
has_many :agendas, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class Agenda < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
And although I was pretty comfortable about the idea of a user having several roles (one for each agenda), I am not sure about the idea of an agenda having several roles (one for each user?).
Finally, to add to the confusion, I read about the polymorphic association and thought it could also be a viable solution, if done this way for instance:
class Role < ActiveRecord::Base
belongs_to :definition, polymorphic: true
end
class User < ActiveRecord::Base
has_many :roles, as: :definition
end
class Agenda < ActiveRecord::Base
has_many :roles, as: :definition
end
Does any of the above solutions sound right for the situation?
UPDATE: Doing some research, I stumbled upon this article (from 2012) explaining that has_many :through was a "smarter" choice than has_and_belongs_to_many. In my case, I am still not sure about the fact that an agenda would have many roles.
UPDATE 2: As suggested in the comments by #engineersmnkyn, a way of solving this would be to go with two join tables. I tried to implement the following code:
class User < ActiveRecord::Base
has_many :agendas, through: :jointable
end
class Agenda < ActiveRecord::Base
end
class Role < ActiveRecord::Base
end
class Jointable < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
has_many :agendaroles through :jointable2
end
class Jointable2 < ActiveRecord::Base
belongs_to :roles
belongs_to :useragenda
end
I am not sure about the syntax though. Am I on the right track? And how should I define the Agenda and the Role models?
UPDATE 3: What if I went with something like:
class User < ActiveRecord::Base
has_many :roles
has_many :agendas, through: :roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class Agenda < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
and then, in the migration file, go with something like:
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.belongs_to :user, index: true
t.belongs_to :agenda, index: true
t.string :privilege
t.timestamps
end
end
end
Would I be able to call #user.agenda.privilege to get the privilege ("role" of creator, editor or viewer) of a given user for a given agenda?
Conversely, would I be able to call #agenda.user.privilege ?
Okay I will preface by saying I have not tested this but I think one of these 2 choices should work well for you.
Also if these join tables will never need functionality besides a relationship then has_and_belongs_to_many would be fine and more concise.
Basic Rails rule of thumb:
If you need to work with the relationship model as its own entity, use has_many :through. Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.
First using your example (http://repl.it/tNS):
class User < ActiveRecord::Base
has_many :user_agendas
has_many :agendas, through: :user_agendas
has_many :user_agenda_roles, through: :user_agendas
has_many :roles, through: :user_agenda_roles
def agenda_roles(agenda)
roles.where(user_agenda_roles:{agenda:agenda})
end
end
class Agenda < ActiveRecord::Base
has_many :user_agendas
has_many :users, through: :user_agendas
has_many :user_agenda_roles, through: :user_agendas
has_many :roles, through: :user_agenda_roles
def user_roles(user)
roles.where(user_agenda_roles:{user: user})
end
end
class Role < ActiveRecord::Base
has_many :user_agenda_roles
end
class UserAgenda < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
end
class UserAgendaRoles < ActiveRecord::Base
belongs_to :role
belongs_to :user_agenda
end
This uses a join table to hold the relationship of User <=> Agenda and then a table to join UserAgenda => Role.
The Second Option is to use a join table to hold the relationship of User <=> Agenda and another join table to handle the relationship of User <=> Agenda <=> Role. This option will take a bit more set up from a CRUD standpoint for things like validating if the user is a user for that Agenda but allows a little flexibility.
class User < ActiveRecord::Base
has_many :user_agendas
has_many :agendas, through: :user_agendas
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
def agenda_roles(agenda)
roles.where(user_agenda_roles:{agenda: agenda})
end
end
class Agenda < ActiveRecord::Base
has_many :user_agendas
has_many :users, through: :user_agendas
has_many :user_agenda_roles
has_many :roles, through: :user_agenda_roles
def user_roles(user)
roles.where(user_agenda_roles:{user: user})
end
end
class Role < ActiveRecord::Base
has_many :user_agenda_roles
end
class UserAgenda < ActiveRecord::Base
belongs_to :user
belongs_to :agenda
end
class UserAgendaRoles < ActiveRecord::Base
belongs_to :role
belongs_to :user
belongs_to :agenda
end
I know this is a long answer but I wanted to show you more than 1 way to solve the problem in this case. Hope it helps

Rails: three-way has_many/through relationship: prevent duplicate join items creation

Here is an extract of the models I have:
class User < ActiveRecord::Base
has_many :participations
has_many :groups, through: :participations
has_many :subgroups, through: :participations
end
class Group < ActiveRecord::Base
has_many :participations
has_many :users, through: :participations
has_many :subgroups
end
class Subgroup < ActiveRecord::Base
has_many :participations
has_many :users, through: :participations
end
class Participation < ActiveRecord::Base
belongs_to: :user
belongs_to: :group
belongs_to: :subgroup
validates :user, presence: true
validates :group, presence: true
# Subgroup can be empty, as long as user as not been placed.
# There should be only one participation per couple User:Group
validates_uniqueness_of :group_id, :scope => [:user_id]
# Also has a state-machine, describing the participation status.
end
Explanation: groups are split in subgroups, users select the group they join, but not the subgroup, which is selected later by an administrator.
When a User is added to a Group (group_a.users << user_a), a Participation is automatically created by ActiveRecord.
I would like the same participation to be reused when the same User is added to a Subgroup of that Group (subgroup_1.users << user_a with subgroup_1 a Subgroup of group_a's).
What happens actually is ActiveRecord trying to create a new Participation record, which conflicts with the previously created one (validates_uniqueness_of :group_id, :scope => [:user_id]
fires an error).
Is there anyway I could make this work? I tried hooking before_validation, before_save, and some other stuff, but every attempt failed.
Maybe there is a better way to actually model this relationship?
Any help is welcome.
Thank you,
David
You could DRY up all of your code by instead calling
class User < ActiveRecord::Base
has_many :participations
has_many :groups, through: :participations
has_many :subgroups, through: :groups # HMT -> HMT
end
Would this solve your problem? This probably won't scale, but we'll worry about that later :).

Resources