If we have 3 models => Customer, User and Thing and another model Owner thats inherits from User and we try create a has_many through association like this:
class Customer < ActiveRecord::Base
has_many :things, :dependent => :destroy
has_many :owners, through: :things
end
class Thing < ActiveRecord::Base
belongs_to :customer, foreign_key: "customer_id"
belongs_to :owner, foreign_key: "owner_id"
end
class Owner < User
has_many :things, :dependent => :destroy
has_many :customers, through: :things
end
Why will #owner.things not work for us? (#owner is an instance of Owner). It gives undefined method "things" error.
#owner is the current_user, but how do you specify it to be an instance of User?
Is the only solution to change owner_id to user_id or is there a better solution, please?
As you state, current_user is an instance of User, not of its subclass, Owner.
If you want to add that relation to the current_user, you could add it to its class, User, instead of Owner:
class User < ActiveRecord::Base # or whatever superclass you have for User
has_many :things, dependent: :destroy
end
Otherwise, if you want to stick to Owner, you should overwrite the creation of current_user so that it uses the Owner class instead of User.
Related
In Rails when I have made one Model as the foreign key in another model then I can delete that model while speciying its relation like:
class User < ApplicationRecord
has_many :garments, dependent: :destroy
end
But if I have one model which is created in another namespace like superadmin them how to write the dependent destroy relation in that case
for example I am using :
class User < ApplicationRecord
has_one superadmin::company , dependent: :destroy
end
which is incorrect.
The model company is present in namespace superadmin, please tell if their is a correct a way possible. Thanks in advance
It's incorrect, Way of reference to model and namespace with class name is incorrect:
incorrrect:
class User < ApplicationRecord
has_one superadmin::company , dependent: :destroy
end
corrrect:
class User < ApplicationRecord
has_one :company, :class_name => "Superadmin::Company", :dependent => :destroy
end
class User < ApplicationRecord
has_one :company, :class_name => "Superadmin::Company", :dependent => :destroy
#has_many :companies, :class_name => "Superadmin::Company", :dependent => :destroy
end
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 having a hard time with more advanced association in Rails.
Here is what I want to do:
landlord can create many properties, and they all belongs to the a landlord
each property can have many tenants, and each tenants can only belong to one property.
the land lord should be able to able to use CRUD on new tenants in each of his respective property.
the tenants can only see attributes/status related to their own property
Here is what I think how it should be laid out
class User < ActiveRecord::Base
belongs_to :tenant, polymorphic: true
end
class Property < ActiveRecord::Base
has_many :users, as: :tenant
belongs_to :landlords
end
class Landlord < ActiveRecord::Base
has_many :property
has_many :users, as: :tenant, through: :properties
end
Is this correct?
Is it better/less convoluted without the polymorphic association, and just use the through association?
I would do something like this:
class Landlord
has_many :properties
has_many :tenants, through: :properties
end
class Tenant
belongs_to :property
belongs_to :landlord, through: :properties
end
class Property
belongs_to :landlord
has_many :tenants
end
See this active record associations guide for more information.
We have a Company, CompanyUser, User and Rating model defined like this:
Company model
class Company < ActiveRecord::Base
has_many :company_users
has_many :users, through: :company_users
has_one :company_owner, where(is_owner: true), class_name: 'CompanyUser', foreign_key: :user_id
has_one :owner, through: :company_owner
end
There is an is_owner flag in the company_users table to identify the owner of the company.
CompanyUser model
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user
belongs_to :owner, class_name: 'User', foreign_key: :user_id
end
User model
class User < ActiveRecord::Base
has_many :company_users
has_many :companies, through: :company_users
has_many :ratings
end
Rating model
class Rating
belongs_to :user
belongs_to :job
end
I am able to find the owner of a company, by the following code:
#owner = #company.owner
I need to get the ratings and the jobs of the owner along with the owner. I can do this
#owner = #company.owner
#ratings = #owner.ratings.includes(:job)
But we have already used #owner.ratings at many places in the view, and it is difficult to change all the references in the views as it is a pretty big view spanning in several partials. I tried the following to get the ratings along with the owner
#owner = #company.owner.includes(:ratings => :job)
But this gives me error as #company.owner seems to give a User object and it does not seem to support chaining.
Is there a way I can get the included associations (ratings and job) inside the #owner object?
You should be able to do this with:
#owner = Company.where(id: #company.id).includes(owner: {ratings: :job}).owner
However this is not very clean. Much better would be to actually change #company variable:
#company = Company.includes(owner: {ratings: :job}).find(params[:company_id]) # or params id or any other call you're currently using to get the company.
Company built that way will already have everything included, so:
#owner = #company.owner
will pass a model with preloaded associations.
In my rails application I have Event and User models. The business rules dictate that an Event has one owner and many members, and the owner must be a member. The inverse would be User having many 'owned events' and 'events' (events he/she is a member of but not the owner). I had originally planned to model this association like so:
class Event < ActiveRecord::Base
has_many :event_members, dependent: :destroy
has_one :owner, through: :event_members, source: :user, conditions: { owner: true }
has_many :members, through: :event_members, source: :user
end
class EventMember < ActiveRecord::Base
belongs_to :event
belongs_to :user
end
class User < ActiveRecord::Base
has_many :owned_events, through: :event_members, conditions: { owner: true }
has_many :events, through: :event_members
end
I thought that this would ensure one owner and that owner would be included in the members association for free. However, this doesn't work since I can't specify a has_one through a collection - the :event_members association.
Is there a better way to model this? I could add a transient 'owner' attr on Event that queries EventMember by the owner flag - but will that work for setting the owner (i.e. can I grab the EventMember record that is created and make sure the owner flag is set)?
What about adding an owner attribute on the join model EventMember (also a field of type boolean in the event_members table, with default value of false). Access the owner and owned_events with methods on each respective model.
class Event < ActiveRecord::Base
has_many :event_members
has_many :members, :through => :event_members, :source => :user
def owner
event_members.ownerships.map(&:user).first
end
end
class EventMember < ActiveRecord::Base
belongs_to :event
belogns_to :user
scope :ownerships, -> { where(:owner => true) }
end
class User < ActiveRecord::Base
has_many :event_members
has_many :events, :through => :event_members
def owned_events
event_members.ownerships.map(&:event)
end
end
The Event object may have the responsibility of making a user its owner.
class Event
def make_owner!(user)
membership = event_members.where(:user_id = user).first
membership.owner = true
membership.save
end
end
Maybe place logic in there to enforce only one owner and abstract part of make_owner! to EventMember depending on how messy this gets in your application.