Rails associations with the same models - ruby-on-rails

I have two classes with the following associations:
class Incident
has_one :assignee
has_one :technician
class User
has_many :incidents
Note that the assignee and technician fields refer to objects of type User. How should these relationships be in the model?

Presumably the Incident should belong_to an assignee and technician, because the foreign key holding those relationships would be in the incidents table, not the employees table
class Incident
belongs_to :assignee, :class_name => 'User'
belongs_to :technician, :class_name => 'User'
class User
has_many :assigned_incidents, :class_name => 'Incident', :foreign_key => 'assignee_id'
# not sure the wording you'd want to use for this relationship
has_many :technician_incidents, :class_name => 'Incident', :foreign_key => 'technician_id'
You would want the foreign key fields to be incidents.assignee_id, incidents.technician_id

Here's a complete answer to this issue, in case people visiting this question are having a hard time putting everything together (as I was when I first looked into this).
Some parts of the answer take place in your Migrations and some in your Models:
Migrations
class CreateIncidents < ActiveRecord::Migration
create_table :incidents do |t|
def up
t.references :assignee
t.references :technician
end
end
end
Here you are specifying that there are two columns in this table that will be referred to as :assignee and :technician and which hold references to another table. Rails will actually create columns called 'assignee_id' and 'technician_id' for you. In our case they will each reference rows in the Users table, but we specify that in the models, not in the migrations.
Models
class Incident < ActiveRecord::Base
belongs_to :assignee, class_name => 'User'
belongs_to :technician, class_name => 'User'
end
Here you are creating a property on the Incident model named :assignee, then specifying that this property will be referencing an instance of the User class. Rails, seeing the 'belongs_to', will look for a column in your database called 'assignee_id', which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the technician.
This will allow you to access your assignee and technician, both instances of the User model, through an instance of the Incident model, like this:
Incident.assignee.name
Incident.technician.email
Here is your User Model:
class User < ActiveRecord::Base
has_many :assigned_incidents, :class_name => 'Incident', :foreign_key => 'assignee_id'
has_many :incidents_as_technician, :class_name => 'Incident', :foreign_key => 'technician_id'
end
Here you are creating a property on the User Model named :assigned_incidents, specifying that this property will be referencing instances of the Incident Model, and that the foreign key on the Incident model which references this, the User Model, that we want to use to for this property is called 'assignee_id'. Then you are doing the same thing for incidents_as_technician (this naming seems kind of awkward, but without better knowledge of what you're trying to do, I don't really have any great suggestions).
This allows you to get all of a user's assigned incidents or incidents as technician by doing this:
User.assigned_incidents
User.incidents_as_technician
Doing either of these will return an array of instances of the Incident model.

Related

How do I validate uniqueness of join table with extra column in Ruby on Rails

I have a has many through relationship in a model that is self referencing. In the join table I also have an extra column that defines the source of the relationship. When adding a new object to that relationship I'd like to avoid duplicates in the join table based on the user_id, friend_id, and source_id
User Model
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :class_name => "User", :through => :friendships
end
Join Model
class Friendship < ActiveRecord::Base
attr_accessible :friend_id, :user_id, :source_id, :alert, :hide
# Relationships
belongs_to :user
belongs_to :friend, :class_name => "User"
has_one :source
end
I understand that I can do this
unless user.friends.include?(newFriend)
user.friendships.build(:friend_id => friendUser.id, :source_id => source.id)
end
But that seems like it will check to see if the new user exists in the current user's friends. I need to check on the join model level and make sure that the connection doesn't exist with the given source id.
I know there are several ways to accomplish this, but I'm pretty new to ruby on rails and am looking for the "rails way" to do it.
You can validate based on multiple columns in your intermediate table like this:
validates_uniqueness_of :user_id, :scope => [:friend_id, :source_id]

Write a migration with reference to a model twice

I have a message model (Message) and this models as a userTo and userFrom, so two references to User. How can i write the migration? My user model is User.
Thank you
Here's a complete answer to this issue, in case people visiting this question are having a hard time putting everything together (as I was when I first looked into this).
Some parts of the answer take place in your Migrations and some in your Models:
Migrations
class CreateMessages < ActiveRecord::Migration
create_table :messages do |t|
def up
t.references :sender
t.references :recipient
end
end
end
Here you are specifying that there are two columns in this table that will be referred to as :sender and :recipient and which hold references to another table. Rails will actually create columns called 'sender_id' and 'recipient_id' for you. In our case they will each reference rows in the Users table, but we specify that in the models, not in the migrations.
Models
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
Here you are creating a property on the Message model named :sender, then specifying that this property will be referencing an instance of the User class. Rails, seeing the "belongs_to", will look for a column in your database called "sender_id", which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the recipient.
This will allow you to access your Sender and Recipient, both instances of the User model, through an instance of the Message model, like this:
#message.sender.name
#message.recipient.email
Here is your User Model:
class User < ActiveRecord::Base
has_many :sent_messages, :class_name => 'Message', :foreign_key => 'sender_id'
has_many :received_messages, :class_name => 'Message', :foreign_key => 'recipient_id'
end
Here you are creating a property on the User Model named :sent_messages, specifying that this property is related to the Message Model, and that the foreign key on the Message model which relates to this property is called 'sender_id'. Then you are doing the same thing for received messages.
This allows you to get all of a users sent or received messages by doing something like this:
#user.sent_messages
#user.received_messages
Doing either of these will return an array of instances of the Message model.
In the migration, create two different columns for each kind of user. For example:
add_column :messages, :sender_id, :integer
add_column :messages, :receiver_id, :integer
Then in the model, that's where the logic to map each column to the User class happens:
belongs_to :sender, :class_name => 'User'
belongs_to :receiver, :class_name => 'User'
Of course, use your own words for sender and receiver, but Rails will automatically associate sender to the sender_id column (and the same logic for receiver)
You will then be able to interact with both user user.sender and user.receiver.

How can I relate to an object twice in rails?

I've got a Users model and a Tasks model.
A task has a creator, of type user, and an assignee of type user.
I've already done a migration of AddUserIdtoTasks to get the creator relation working, but now I need to do the same thing again to add the assignee, but I'm already using the keyword 'user'. How should I go about building a proper relation.
A task only has one assignee, always.
I'm using devise for the user model.
has_one :creator, :class_name => "User"
has_one :asignee, :class_name => "User"
Or belongs_to, depending on how your fields are set up. has_one and belongs_to both take an optional :class_name argument for cases just such as yours.
Create the field assignee_id in your Task model and then use it for the relation as in
class Task < AR::Base
belongs_to :assignee, :class_name => 'User'
end
On the other side of the relation
class User < AR::Base
has_many: :assigned_tasks, :class_name => 'Task', :foreign_key => :assignee_id
end
Sorry, should be :class_name. Updated User class also with a :foreign_key parameter, without it user.assigned_tasks would have joined records using the :user_id parameter (the default value for has_many, i.e. "#{class_name}_id"`).
I invite you to read the link I've posted, it explains better than me all these things.
Source: http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

Rails ActiveRecord Association

Okay, so here is my question. I have a 3 different models, People, Roles, Client, and Store. Clients have many Stores and can also have many people. Stores have many people. People have various roles. 1 Person can work at multiple stores, and they may have different roles at each store.
For example. Joe may be an assistant manager at one store and a manager at another store. What I would like to be able to do is pull the correct roles by doing something like Store.find(1).people.find(1).roles (would return 'assistant manager' for example) or
Store.find(2).people.find(1).roles (would return 'manager' for example). Is this possible to do in ActiveRecord?
I've created a table :roles_people which has the following definition:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
t.references :client
end
However i can't figure out how to get associations to work properly using this table. Can anyone point me in the right direction?
Thanks
class People
belongs_to :client
has_many :store_roles
end
class Roles
has_many :store_roles
end
class StoreRole
belongs_to :role
belongs_to :people
belongs_to :store
end
class Client
has_many :stores
has_many :people
end
class Store
belongs_to :client
has_many :store_roles
has_many :roles, :through => :store_roles
end
Assume that all of those classes inherit from ActiveRecord::Base ;)
You're going to need to setup the migration and database structure to mirror these relationships. For each belongs_to there is an :object_id field on the table reference the appropriate table's id.
Your query is going to need to look something like:
Store.find(1).roles.find(:all, :conditions => ["store_roles.person_id = ?", 1])
I would probably add a method to the store model to make this a little easier:
def roles_for(person_id)
roles.find(:all, :conditions => ["store_roles.person_id = ?", person_id])
end
This way you can find the roles using:
Store.find(1).roles_for(1)
Or, better yet:
def self.roles_for(store_id, person_id)
Role.find(:all, :joins => :store_roles, :conditions => ["store_roles.store_id = ? AND store_roles.person_id = ?", store_id, person_id])
end
Which changes our finder to:
Store.roles_for(1, 1)
I would say that this last method is the most ideal since it causes only a single query, while each of the other options execute two queries to the database per role look-up (one to find the store, and one to get the roles for a person_id). Of course if you already have the Store object instantiated then it's not a big deal.
Hopefully this answer was sufficient :)
I think what you want is has_many :through
class Person < ActiveRecord::Base
has_many :roles_people
has_many :roles, :through => :roles_people
end
class Store < ActiveRecord::Base
has_many :roles_people
has_many :people, :through => roles_people
end
You'll also need to add relationships to RolePerson:
class RolePerson < ActiveRecord::Base
belongs_to :store
belongs_to :person
has_one :role
end
Is that what you were looking for?
Very helpful link #blog.hasmanythrough.com
has_and_belongs_to_many is your friend.
class Person < ActiveRecord::Base
has_and_belongs_to_many :roles
end
That way, you can get all roles the person has by calling Person.roles.all. The resulting query is going to use the people_roles table. You can also use has_many :through but have to build model classes for the join table yourself and maintain all the associations yourself. Sometimes it's necessary, sometimes it's not. Depends on the complexity of your actual model.
Nice question. You can't do exactly what you wanted, but i guess we can come close.
For completeness, i am going to recap your datastructure:
class Client
has_many :stores
end
class Store
has_many :people
has_many :roles
end
class Person
has_many :roles
has_many :stores
end
class Role
belongs_to :store
belongs_to :person
end
You see that the role does not need the link to the client, because that can be found straightaway from the store (i am assuming a stored is "owned" by only one client).
Now a role is linked both to a person and a store, so a person can have different roles per store.
And to find these in a clean way, i would use a helper function:
class Person
has_many :roles
has_many :stores
def roles_for(store)
roles.where("store_id=?", store.id)
end
end
So you can't write something like
store.people.first.roles
to get the roles of the first person working for that store.
But writing something like:
store.people.first.roles_for(store)
is not too hard i hope.
The reason why this is so is because in the context of the person (-> store.people.first) we no longer have any notion of the store (how we got there).
Hope this helps.
You need to change your table name in people_roles and you can drop both store and client references:
create_table :roles_people, :id => false do |t|
t.references :role
t.references :person
t.references :store
end
Role is something that belongs only to people.
You then need to use has_and_belongs_to_many:
class Person < ActiveRecord::Base
has_many :roles
has_many :stores, :through => :people_roles
end
class Store < ActiveRecord::Base
has_many :roles
has_many :people, :through => :people_roles
end
Than you can query:
Store.find(1).people.find(1).roles

has_one and has_many in same model. How does rails track them?

I a little confused about how this work even if it works properly. I have a model that has two association to the same other model.
Company has an owner and company has many employees of the class users.
here is my company model:
class Company < ActiveRecord::Base
validates_presence_of :name
has_many :employee, :class_name => 'User'
has_one :owner, :class_name => 'User'
accepts_nested_attributes_for :owner, :allow_destroy => true
end
here is my user model:
class User < ActiveRecord::Base
include Clearance::User
attr_accessible :lastname, :firstname #other attr are whitelisted in clearance gem
validates_presence_of :lastname, :firstname
belongs_to :company
end
Now assuming I have 3 employee of this company including the owner. When I first create the company I set the owner to the employee with id 1 and the two others (2,3) are added to the employee list by setting their company_id (user.company=company). All three have their company_id set to the company id which we can assume is 1
when I ask for company.owner, I get the right user and when I do company.employee, I get all three.
If I change the owner to user 2, it removes user 1 from the employees automatically by setting it's company_id to nil. This is fine and if I add him back as a simple employee all is still good.
How the heck does rails know which is which? What I mean is how does it know that an employee is owner and not just an employee? Nothing in the schema defines this.
I have a feeling I should reverse the owner association and make company belong_to a user.
As you have it now, there's nothing to distinguish owners from employees. Which means you're going to run into problems once you start removing people or try to change ownership.
As François points out, you're just lucking out in that the owner is the user that belongs to company with the lowest ID.
To fix the problem I would have my models relate in the following maner.
class Company < ActiveRecord::Base
belongs_to :owner, :class_name => "user"
has_many :employees, :class_name => "user"
validates_presence_of :name
accepts_nested_attributes_for :owner, :allow_destroy => true
end
class User < ActiveRecord::Base
include Clearance::User
attr_accessible :lastname, :firstname #other attr are whitelisted in clearance gem
validates_presence_of :lastname, :firstname
belongs_to :company
has_one :company, :foreign_key => :owner_id
end
You'll have to add another column called owner_id to the Companies table, but this more clearly defines your relationships. And will avoid any troubles associated with changing the owner. Take note that there might be a cyclical dependency if you go this route and have your database set so that both users.company_id and companies.owner_id cannot be null.
I'm not quite sure how well accepts_nested_attributes_for will play with a belongs_to relationship.
has_one is syntactic sugar for:
has_many :whatevers, :limit => 1
has one adds the :limit => 1 bit, thereby ensuring only 1 record is ever returned. In your has one declaration, make sure you have an :order clause, to return the right record in all circumstances. In this instance, I'd put a flag on the Employee to signify who is the owner, and sort by this column to get the right record 1st.
Your question about how come Rails knows this is because most databases will return records in their primary key order. So, the 1st added employee has ID 1, thereby will be returned 1st.
You could have a model called ownership -
ownership belongs_to company
ownership belongs_to user
user has_many ownerships
company has_one ownership

Resources