Having a many-to-many association in the same model rails - ruby-on-rails

I have a model called User and for my model, a user can either be a Leader or a Member. In my user model i have this
class User < ActiveRecord::Base
attr_accessible :username, :type
end
I thought i could create a many-to-many association in the User model like this
class User < ActiveRecord::Base
attr_accessible :username, :type
has_and_belongs_to_many :users, :join_table => :team_members, :foreign_key => :team_leader_id
end
But i am not really sure how to go about it. So for example.
User 1 - type :leader
User 2 - type : member
User 3 - type: member.
I want to create a relationship that can show that User 1 is the leader of user 2 and user 3.
I am still a bit new to rails .

add leader_id to User
class User < ActiveRecord::Base
attr_accessible :username, :type
belongs_to :leader, class: User
has_many :members, class: User, foreign_key: :leader_id
end
use :
#user_1 = User.create(name: "Jhon")
#user_2 = User.create(name: "Tom", leader: #user_1)
#user_1.members

well, you have User table and user can be member or leader,
if you sure that there will not be any other roles
you can use boolean leader and can be true or false if false that mean this user is member
if you not sure if there may be any other roles
you can go with what you currently being used type column and can contain member or leader that is for the first part.
then you need leader to control many members then there is 2 possibilites:
Member can belongs to only 1 Leader then you will need to add a new column in user table called leader_id for example and in this case it will be
has_many :members, :class_name => "User", :foreign_key => :leader_id
Member can belongs to many leaders then you will need to create many to many relation and then will use a new table that contain leader_id and member_id and both should be refer to user table as a forigen keys.
And better than all and have this relation in User model and its only valid for Leader you can have 2 models that inherit from User and that is called STI Single Table Inheritance you can read more about it here:
class User < ActiveRecord::Base
# this type will be checked if Leader then its Leader model if Member then its Member model
self.inheritance_column = 'type'
end
class Member < User
end
class Leader < User
has_many :members, :class_name => "User", :foreign_key => :leader_id
end
this model is away better than all, and in this case let's say in your User model you have:
1 User type='Member'
2 User type='Leaeder'
if you say:
# will work
Member.find 1
User.find 1
# will fail as type is not Leader
Leader.find 1

Related

Assign user model to one of two models?

I have a user model that I want to assign to be teacher or student( teacher and student are two separated model ) because if the user signup he would have different registration fields depending on if he is teacher or student. User can be a teacher or student, not both.
I have tried, but don't think that this is the best way to do it. Any help?
class User < AR
has_secure_password
has_one :teacher, class_name: "teacher", foreign_key: "teacher_id", conditions: { role: 'teacher' }
has_one :student, class_name: "student", foreign_key: "student_id", conditions: { role: 'student' }
enum role: [:teacher, :student]
end
class Teacher < AR
belongs_to :user, class_name: "user", foreign_key: "user_id"
end
class Student < AR
belongs_to :user, class_name: "user", foreign_key: "user_id"
end
This is how you can implement the STI for your case
class User < AR
has_secure_password
# Make all forms with User data send params with ':user' as a param key
# instead of ':user_teacher'/':user_student'
def self.model_name
ActiveModel::Name.new(self, nil, 'User')
end
end
class Teacher < User
# custom methods
end
class Student < User
# custom methods
end
This way, you can have your form with form_for #user do # ....
One caveat, it is all placed in the Single table in the DB (hence the name Single Table Inheritance), and that means a lot of NULL values for the unrelated fields (say Teacher has a teacher_identification_number, and a user has student_identification_number which are different in size or they require different validation) for all the Students that attribute teacher_identification_number would be NULL, and vice-versa.
If the fields are much different between the two models, then you can analyze your data and put it in the different table to which only the Teacher/Student would have access to, that is called a Database Normalization (say Teacher has_many ClassInfo's or has_one JobInfo; or Teacher has_one TeacherProfile, and Student has_one StudentProfile or whatever).
It all really depends on how you model your DB.
References:
- Blog Post - Medium - STI
- Video Link - Drifting Ruby - STI
- Video Link - # RailsCasts - STI
- Blog Post - StudyTonight - DB Normalization

Rails - AssociationTypeMismatch error with associating a model with multiple instances of another model

I have two models, Accounts and CreditRecords. An account can have many credit records that belong to it. However, accounts can also trade credit records to other accounts, and I want to keep track of who the current account owner is, and who the original owner is.
class Account < ActiveRecord::Base
has_many :credit_records
class CreditRecord < ActiveRecord::Base
belongs_to :original_owner_id, :class_name => "Account"
belongs_to :account_id, :class_name => "Account"
When I try to set a CreditRecord.account_id to, say, 1, it updates fine. But if I try to set CreditRecord.original_owner_id to 3, I get this error:
ActiveRecord::AssociationTypeMismatch: Account(#70154465182260) expected, got Fixnum(#70154423875840)
Both account_id and original_owner_id are set to be integers.
original_account_id is expecting an account object. you cannot set an id.
credit_record.original_owner = account
credit_record.account = account
or
credit_record.account_id = account.id
Please rename your association to the following
class CreditRecord < ActiveRecord::Base
belongs_to :original_owner, :foreign_key => "account_id", :class_name => "Account"
belongs_to :account
I'm not sure why you want to name your association account_id instead of just account in your CreditRecord class. The problem with this approach is when you have/will have nested resources like the following in your routes:
resources :accounts do
resources :credit_records
end
you will get a URL pattern as /accounts/:account_id/credit_records/:id/..., and your params hash will have account_id parameter in it.
Suggest updating your associations as follows as suggested by #vimsha in his answer.
class CreditRecord < ActiveRecord::Base
belongs_to :original_owner, :class_name => Account, :foreign_key => 'account_id'
belongs_to :account, :class_name => Account
end
This will allow you to assign account's id attribute through credit record object like:
# Set account's id
credit_record.account.id = 1
# Set original_owner's id
credit_record.original_owner.id = 2

Rails - association with another table

I have a users table(and a user model). In my scenario, a user can have multiple identities. I.e. user michael1 (id = 1) can be connected to michael2 (id = 2) and michael3 (id = 3).
I created a table to store these connections. I called it user_relations and it has: id, user_id, related_user_id columns. In the previous example I'll have:
user_id | related_user_id
1 | 2
1 | 3
In users model I defined: has_many :user_relations, and in user_relation I defined: belongs_to :users.
Now, I want that when I have a user object I would be able to get:
current_user.user_relations - and get all users objects that are connected according to the table. In previous example, if I have current_user as user with id 1, I would like to get users with id 2 and 3.
How can I achieve that?
BTW - I have an id because I saw that without it, I am not able to use destroy_all. If anyone has an insight regarding this also, I am open to hear.
I think this should work. If I missed something you can look here for details:
class User < ActiveRecord::Base
has_many :user_relations
has_many :related_users, :through=> :user_relations
end
class UserRelations< ActiveRecord::Base
belongs_to :user, :class_name => "User", :foreign_key => "user_id"
belongs_to :related_user, :class_name => "User", :foreign_key => "related_user_id"
end

Rails. Virtual attribute (from a parent model) query

I have three models: Account, Member and Child.
Member belongs to Account
Child belongs to Member
Member has an account_id attribute
Child does not have an account_id attribute
So I can do this...
Member.where(:account_id => current_user.account.id)
c = Child.last
c.member.account_id
In an index action, I want to list all Children that belongs to a particular account. I didn't want to add an extra account_id column to the children table.
Of course, I can't do this...
Child Model
def account_id
self.member.account_id
end
Children Controller
Child.where(:account_id => current_user.account.id)
Is there a way to list all children that belongs to particular account without having to add an account_id attribute?
By the way, I have this on the existing query...
#children = Child.search(params[:search]).order(sort_column + ' ' + sort_direction).page(params[:page]).per(10)
Starting from Child class, it could be done this way:
Child.includes(:member).where(:'members.account_id' => current_user.account.id)
This could be used to modify your existing query.
class Account < ActiveRecord::Base
has_many :members
#This is the line you were looking for
has_many :children, :through => :members
end
class Member < ActiveRecord::Base
belongs_to :account
has_many :children, :class_name => "Child"
end
class Child < ActiveRecord::Base
belongs_to :member
end
Now assuming you have an accounts instance you can access all of its children by:
account.children

Rails - application design for 2 user types

I am writing ruby on rails app, that will have 2 different types of users (let's say sellers and buyers). I was thinking about using single table inheritance, but ultimately I decided to crate 2 separate models (I believe it's better solution in my case).
The problem is when I try to create private message model (both sellers and buyers can contact each other). Typically I would just generate model called message with 3 fields (from_user_id to_user_id and content). This will not work in my case, because there might be 2 users with the same id (1 seller and 1 buyer).
I was thinking about using e-mail to identify sender and receiver, but e-mail is not my primary key, so I guess I won't be able to use it a foreign key in message model. Oh - btw, what is the best way to make sure that e-mails are unique in both sellers and buyers tables (in other words user can't join as a seller and buyer from one email)
Any ideas how I can elegantly solve my problem?
Why do you decided to not have a single User model? Considered all the issues caused by having these users in two separated tables I would have a User model and extend this model to have a Buyer model and a Seller model.
I think a buyer or a seller is still a user of your application, this resolves the problem of the message from a user to another too.
class User < ActiveRecord::Base
# Remember to add a "type" column in the users table
end
class Seller < User
end
class Buyer < User
end
The messages are now between users, no matter which kind of user.
What you are looking for is a polymorphic association. What this allows you to do is have a model that can belong to multiple other models through the same relationship by specifying the ID as well as the Class of the other object. For example, if buyer ID 3 sends a message to seller ID 5, your message table will end up with a row like:
sender_id = 3
sender_type = Buyer
receiver_id = 5
receiver_type = Seller
To accomplish this in active record, your models will look like the following:
class Message < ActiveRecord::Base
belongs_to :sender, :polymorphic => true
belongs_to :receiver, :polymorphic => true
end
class Buyer < ActiveRecord::Base
has_many :sent_messages, :class_name => "Message", :as => :sender
has_many :received_messages, :class_name => "Message", :as => :receiver
end
class Seller < ActiveRecord::Base
has_many :sent_messages, :class_name => "Message", :as => :sender
has_many :received_messages, :class_name => "Message", :as => :receiver
end

Resources