I have a property model and a user model.
A user with the role of 'admin', which is represented by a column on the users table, can have many properties.
A user with a role of 'guest' can also belong to a property, which gives them access to that property.
How should I do this in Rails?
authorizations table -> user_id, property_id
class Authorization < ActiveRecord::Base
belongs_to :user
belongs_to :property
end
class User < ActiveRecord::Base
has_many :authorizations
has_many :properties, through: :authorizations
end
class Property < ActiveRecord::Base
has_many :authorizations
has_many :users, through: :authorizations
end
then you can do User.find(id).properties
First, you need a has_many :through association between your models User and Property. So, create a new table properties_users with columns user_id and propety_id. And do following changes to the models:
class PropertiesUser < ActiveRecord::Base
belongs_to :user
belongs_to :property
end
class User < ActiveRecord::Base
has_many :properties_users
has_many :properties, through: :properties_users
end
class Property < ActiveRecord::Base
has_many :properties_users
has_many :users, through: :properties_users
end
Now, we need to make sure that a guest user does not have more than one property. For that we can add a validation to model PropertiesUser like below:
class PropertiesUser < ActiveRecord::Base
validate :validate_property_count_for_guest
private
def validate_property_count_for_guest
return unless user && user.guest?
if user.properties.not(id: self.id).count >= 1
self.errors.add(:base, 'guest user cannot have more than one properties')
end
end
end
class User < ActiveRecord::Base
def guest?
# return `true` if user is guest
end
end
Finally, to access a guest user's property, define a dedicated method in model User:
class User < ActiveRecord::Base
def property
# Raise error if `property` is called on non-guest users
raise 'user has multiple properties' unless guest?
properties.first
end
end
Now, you can fetch a guest user's property by running:
user = User.first
user.guest?
=> true
user.property
=> <#Property 1> # A record of Property
Related
I have the following problem,
A user can have several professions, more than 10. For example, a user may be a doctor, teacher, and N. Each profession has its own attributes.
I could do, Doctor belongs_to User, but if I want to know all the professions of this user I will have to check each row of the User table.
I created the following code
class User < ApplicationRecord
has_many :jobables
end
class Job < ApplicationRecord
belongs_to :user
belongs_to :jobable
end
class Jobable < ApplicationRecord
has_one :job
end
class Medic < Jobable
end
class Programmer < Jobable
end
But I do not know if that would be the best answer
I would think that it would be much easier to do something like:
class User < ApplicationRecord
has_many :user_professions
has_many :professions, through: :user_professions
end
# == Schema Information
#
# Table name: professions
#
# id :integer not null, primary key
# name :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Profession < ApplicationRecord
has_many :user_professions
has_many :users, through: :user_professions
end
class UserProfession < ApplicationRecord
belongs_to :user
belongs_to :profession
end
You could then create logic to ensure that a Profession is only assigned to a User once.
Then, you could simply do:
#user.professions
And get all the Professions for a User.
You could also do:
#profession.users
And get all the Users that belong to the Profession.
Based on the edit to your question, you could do something like:
class UserProfession < ApplicationRecord
belongs_to :user
belongs_to :profession
belongs_to :profession_detail, polymorphic: true
end
In which case you might have something like:
class DoctorDetail < ApplicationRecord
end
And you could do something like:
#user.professional_detail_for(:doctor)
Of course, you would need to implement the professional_detail_for method on the User model which might look something like:
class User < ApplicationRecord
has_many :user_professions
has_many :professions, through: :user_professions
def professional_detail_for(profession_type)
user_profession_for(profession_for(profession_type)).try(:profession_detail)
end
private
def profession_for(profession_type)
Profession.find_by(name: profession_type.to_s)
end
def user_profession_for(profession)
user_professions.find_by(profession: profession)
end
end
That's a little rough, but I imagine you get the idea.
I have three models; events, users and entries. I would like on my users page for to be able to retrieve information relating to the events associated with the event associated with the user.
def show
#user = User.find(params[:id])
#entry = #user.entries.paginate(page: params[:page])
end
It is more than happy with #user.entries.count but I would like to link up in a table something like this:
Event Name - Event Location - Course
My models are bellow:
class Event < ApplicationRecord
has_many :entries, dependent: :destroy
class User < ApplicationRecord
has_many :entries, dependent: :destroy
class Entry < ApplicationRecord
belongs_to :user
belongs_to :event
If they're related as:
class User < ApplicationRecord
has_many :events
end
class Event < ApplicationRecord
has_many :entries
belongs_to :user
end
class Entry < ApplicationRecord
belongs_to :event
end
Then you can use joins starting from Entry, up to User and check events where the user id is the one what you need:
Entry.joins(event: :user).where(users: { id: user_id })
I have a site that allows users to log in via multiple services (LinkedIn, Email, Twitter, etc..).
I have the below structure set up to model a User and their multiple identities. Basically a user can have multiple identieis, but only one of a given type (e.g. can't have 2 Twitter identiteis).
I decided to set it up as a polymorphic relationship, as drawn below. Basically there's a middle table identities that maps a User entry to multiple *_identity tables.
The associations are as follows (shown only for LinkedInIdentity, but can be extrapolated)
# /app/models/user.rb
class User < ActiveRecord::Base
has_many :identities
has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity"
...
end
# /app/models/identity
class Identity < ActiveRecord::Base
belongs_to :user
belongs_to :identity, polymorphic: true
...
end
# /app/models/linkedin_identity.rb
class LinkedinIdentity < ActiveRecord::Base
has_one :identity, as: :identity
has_one :user, through: :identity
...
end
The problem I'm running into is with the User model. Since it can have multiple identities, I use has_many :identities. However, for a given identity type (e.g. LinkedIn), I used has_one :linkedin_identity ....
The problem is that the has_one statement is through: :identity, and there's no singular association called :identity. There's only a plural :identities
> User.first.linkedin_identity
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :identity in model User
Any way around this?
I would do it like so - i've changed the relationship name between Identity and the others to external_identity, since saying identity.identity is just confusing, especially when you don't get an Identity record back. I'd also put a uniqueness validation on Identity, which will prevent the creation of a second identity of the same type for any user.
class User < ActiveRecord::Base
has_many :identities
has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity"
end
# /app/models/identity
class Identity < ActiveRecord::Base
#fields: user_id, external_identity_id
belongs_to :user
belongs_to :external_identity, polymorphic: true
validates_uniqueness_of :external_identity_type, :scope => :user_id
...
end
# /app/models/linkedin_identity.rb
class LinkedinIdentity < ActiveRecord::Base
# Force the table name to be singular
self.table_name = "linkedin_identity"
has_one :identity
has_one :user, through: :identity
...
end
EDIT - rather than make the association for linkedin_identity, you could always just have a getter and setter method.
#User
def linkedin_identity
(identity = self.identities.where(external_identity_type: "LinkedinIdentity").includes(:external_identity)) && identity.external_identity
end
def linkedin_identity_id
(li = self.linkedin_identity) && li.id
end
def linkedin_identity=(linkedin_identity)
self.identities.build(external_identity: linkedin_identity)
end
def linkedin_identity_id=(li_id)
self.identities.build(external_identity_id: li_id)
end
EDIT2 - refactored the above to be more form-friendly: you can use the linkedin_identity_id= method as a "virtual attribute", eg if you have a form field like "user[linkedin_identity_id]", with the id of a LinkedinIdentity, you can then do #user.update_attributes(params[:user]) in the controller in the usual way.
Here is an idea that has worked wonderfully over here for such as case. (My case is a tad diffferent since all identites are in the same table, subclasses of the same base type).
class EmailIdentity < ActiveRecord::Base
def self.unique_for_user
false
end
def self.to_relation
'emails'
end
end
class LinkedinIdentity < ActiveRecord::Base
def self.unique_for_user
true
end
def self.to_relation
'linkedin'
end
end
class User < ActiveRecord::Base
has_many :identities do
[LinkedinIdentity EmailIdentity].each do |klass|
define_method klass.to_relation do
res = proxy_association.select{ |identity| identity.is_a? klass }
res = res.first if klass.unique_for_user
res
end
end
end
end
You can then
#user.identities.emails
#user.identities.linkedin
Here is two models: user and user_level. User has_many user_levels and user-level belongs to user.
class user < ActiveRecord::Base
has_many :user_level
end
class UserLevel < ActiveRecord::Base
belongs_to :user
end
UserLevel.find_by_role('sales') will retrieve all record (w/ user_id) of role sales. How to retrieve user email given user_level with role 'sales'?
Thanks.
User.joins(:user_levels).where(:user_levels => { :role => "sales"}).select("email")
or
UserLevel.joins(:user).where(:role => "sales").select("email")
update
UserLevel.find_by_role("sales").users
You may have the associations backwards and actually want:
class User < ActiveRecord::Base
belongs_to :user_level
end
class UserLevel < ActiveRecord::Base
has_many :users
end
Then:
#users = UserLevel.find_by_role('sales').users
You can then iterate over the #user collection and use the email within the iterations.
I have a need to design a system to track users memberships to groups with varying roles (currently three).
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class Role < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :group
end
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
end
Ideally what I want is to simply set
#group.users << #user
and have the membership have the correct role. I can use :conditions to select data that has been manually inserted as such :
:conditions => ["memberships.role_id= ? ", Grouprole.find_by_name('user')]
But when creating the membership to the group the role_id is not being set.
Is there a way to do this as at present I have a somewhat repetitive piece of code for each user role in my Group model.
UPDATED
It should be noted what id ideally like to achieved is something similar to
#group.admins << #user
#group.moderators << #user
This would create the membership to the group and set the membership role (role_id ) appropriately.
You can always add triggers in your Membership model to handle assignments like this as they are created. For instance:
class Membership < ActiveRecord::Base
before_save :assign_default_role
protected
def assign_default_role
self.role = Role.find_by_name('user')
end
end
This is just an adaptation of your example.