Rails: One-to-many association - ruby-on-rails

I'm using Rails 3 and I've got a one to many association I'm trying to define: A user can have many subject families assigned to him/her, but a subject family can only be assigned to one user.
Here's what I have defined:
class User
has_many :subject_families
class SubjectFamily
belongs_to :assignee, :class_name => "User", :foreign_key => 'assigned_to'
I added a migration that does this:
change_table(:subject_families) do |t|
t.integer :assigned_to
end
I'm getting an exception when I try to do:
u = User.first
s = u.subject_families
Here's the exception:
Invalid column name 'user_id'.: SELECT [subject_families].* FROM [subject_families] WHERE ([subject_families].user_id = 1)
I was expecting this to be using subject_families.assigned_to rather than user_id but lo and behold I was disappointed in this expectation. Can anyone see what I might have missed here? I've googled this a lot and from what I can see this SHOULD work.

I believe you also need to specify the :foreign_key option on the has_many association declaration in your User model.
class User
has_many :subject_families, :foreign_key => 'assigned_to'

You need to specify :foreign_key => 'assigned_to' in the has_many relationship on User as well.

Related

Rails one-to-one relationship

I have the following:
class User < ActiveRecord::Base
has_one :car, :class_name => 'Car', :foreign_key => 'user_id'
class Car < ActiveRecord::Base
belongs_to :worker, :class_name => 'User', :foreign_key => 'user_id'
It is basically a one-to-one relationship between a user and a car.
What I want is for the User to be able to have one and only one car. That implies the fact that if he creates a car assigned to him, he won't be able to create the second.
How could this be done?
There are certainly a couple different ways of accomplishing this. I would suggest creating a composite key index on that table to ensure that the user_id is unique in the table. This will ensure that it will only be present once. In a migration, you could write something like this.
add_index(:cars, :worker_id, :unique => true)
The first argument is the table name (don't forget this is generally the pluralized version of the class name). The field name comes second. The unique true is what will prevent you from inserting an extra row.
Note: This is a database level constraint. If you hit this because validations didn't catch it, it will throw an error.
In addition to this solution, you will want to add a validation to the Car model itself.
validates_uniqueness_of :worker_id, message: "can not have more than one car"
You'll see this error come through with something like "Worker ID can not have more than one car". You will most likely want to customize the "Worker ID" section of this. Refer to this post for instructions on how to do that.
You certainly don't have to do the db constraint, but in case anyone else inserts into the DB, it's a good idea. Otherwise, you'll have "invalid" data as far as Rails is concerned.
Change the definition of the relationship slightly:
class User < ActiveRecord::Base
has_one :car
class Car < ActiveRecord::Base
belongs_to :worker, :class_name => 'User', :foreign_key => 'user_id'
And you'll establish what you're looking for. See: http://guides.rubyonrails.org/association_basics.html#the-has-one-association
why don't you just test before the user tries to add a car?
if worker.car
raise "sorry buddy, no car for you"
else
car = Car.create(user_id: worker.id)
end

Eager Loading in a Rails 3 app

I am trying to reduce the number of database queries in my Rails 3 app.
User model:
has_many :agreements
Agreement model:
belongs_to :user
The agreements table has two user id fields... payer_id and payee_id. Is it possible to make something like the following work:
user_payer_agreements = current_user.agreements
user_payee_agreements = current_user.agreements
I could use user_id for one side of the transaction but I need to get both sides. Is it possible to specify payer_id or payee_id instead of user_id in the process of creating an association? If not, do i need use a join or a sql statement. Any help is appreciated.
You can do this:
has_many :payer_agreements, :class_name => 'Agreement', :foreign_key => :payer_id
has_many :payee_agreements, :class_name => 'Agreement', :foreign_key => :payee_id
With this you can do:
current_user.payer_agreements
current_user.payee_agreements
Is this what you are looking for?

Help understanding polymophic associations (rails)

I know there are plenty of resources on this but I'm having a tough time relating any of them to my situation so I was hoping someone could help me clarify how this works:
Basically I have a model Action, (which gets created anytime a user does something that affects another user, like commenting on their article or voting on someones photo, for example), these actions will be listed in the users dashboard page as all the actions that have taken place that relate to them, like a stream... sort of like Github's "News Feed"
I've decided to go with creating a polymorphic association, here is what my model looks like:
class Action < ActiveRecord::Base
belongs_to :instigator, :polymorphic => true
belongs_to :victim, :polymorphic => true
end
I used instigator and victim because anyone can create these actions, which in turn always affect another user, here is my User model
class User < ActiveRecord::Base
has_many :actions, :as => :instigator
has_many :actions, :as => :victim
end
And this is where I think I'm going wrong, because ultimately I want to have a query which when I run something like User.find(1).actions to actually return all the instances in which the user is both an instigator or a victim, I think I can't have both of those have_many's in there, because when used like this I only get the instances where the user is the victim.
Here is my migration:
create_table :actions do |t|
t.references :instigator, :polymorphic => true
t.references :victim, :polymorphic => true
t.string :action_performed
t.references :resource, :polymorphic => true
t.timestamps
end
Thanks for any help, I always love the great suggestions and help the SO community gives.
This reminds of classic Friendship model problem. Polymorphic association is besides the point.
Rails version agnostic solution:
class User < ActiveRecord::Base
has_many :instigator_actions, :class_name => "Action", :as => :instigator
has_many :victim_actions, :class_name => "Action", :as => :victim
has_many :actions, :finder_sql => '
SELECT a.*
FROM actions a
WHERE (a.instigator_type = "User" AND instigator_id = #{id}) OR
(a.victim_type = "User" AND victim_id = #{id})'
end
While creating the Actions create them using one of the first two associations.
u1.instigator_actions.create(:victim => u2)
OR
u1.victim_actions.create(:instigator => u2)
At the same time you can get a list of actions associated with an user using the actions association.
u1.actions
Firstly I suggest you use roles through Single table Inheritance. In your user table , you can have a type column which identifies someone as an instigator or as a victim. (Of course if someone is both , he will have 2 rows , so you will have to make sure you dont have the name as the primary key.)
So now you have a more structured layout. Coming to the polymorphism problem,try using a different interface. As in,
class Action < ActiveRecord::Base
belongs_to :actionable, :polymorphic => true
end
actionable need not be a separate class. Its just a name given to the interface.Like wise on the other side of the association.
The Rails Way by Obie Fernandez gives you a clear picture on this, so you can refer it for more dope on polymorphic associations.

Rails find confusion

Please help me get the include right.
Poem
has_many :awards
has_one :overall_ranking
Award
belongs_to :poem
# before
# has_one :overall_ranking, :foreign_key => :poem_id
## SOLUTION
# after
has_one :overall_ranking, :foreign_key => :poem_id, :primary_key => :poem_id
OverallRanking
belongs_to :poem
update: Award.all(:include => [:overall_ranking]) # works with SOLUTION
Please note that I cannot depend on Poem#id as users may delete the poem, but if it's a winner, I make a copy within Award, so I must depend only on Award#poem_id Thank you!
Your problem is that:
has_one :overall_ranking, :foreign_key => :poem_id
means that Award has one OverallRanking and that the ID of Award in has_one relation is in column poem_id, i.e. your logic is wrong.
It would make more sense if you would just use:
#award.poem.overall_rating
Or in find:
Award.all(:include => [:poem => {:overall_ranking}])
You might want to give some more information but I hope this helps:
You might want to try joins.
For example:
Award.all(:joins => :overall_ranking, :conditions => ['some_attribute_from_overall_ranking=?', true])
So this will find awards and include the overall_ranking.
There is some confusion for me.
Award
belongs_to :poem
has_one :overall_ranking, :foreign_key => :poem_id
Here you are using the same id for both relation. This means that you try to retrieve the overall ranking with the poem id.
If I translate to sql you say something like:
overall_ranking.id = poem_id
I think this is wrong.
If you like to have the same overall_ranking for award and poem you can write something like this:
Award.rb
belongs_to :poem
has_one :overall_ranking, :through=>:poem
You can include like
Award.all(:include => [:overall_ranking])
or nested
Award.all(:include => [{:poem=>:overall_ranking}])
Update
1.Your has one association is set up incorrectly.
Please see: http://blog.hasmanythrough.com/2007/1/15/basic-rails-association-cardinality
Award
belongs_to :poem
belongs_to :overall_ranking, :foreign_key => :poem_id
OverallRanking
belongs_to :poem
has_one :award
You always should have belongs_to at the model where you store the referencing id.
2. But this not resolves your problem in your logic.
With this you will still has association between Award#poem_id = OverallRanking#id. You should have Award#poem_id = OverallRanking#poem_id.
I suggest to add overall_ranking_id to Award and things become much cleaner.

Rails model belongs to model that belongs to model but i want to use another name

This may be a stupid question but im just starting to learn Rail thats why i am asking thsi question.
I have one model called "User" which handles all the users in my community. Now i want to add a guestbook to every user. So i created a model called "user_guestbook" and inserted this into the new model:
belongs_to :user
and this into the user model:
has_one :user_guestbook, :as => :guestbook
The next thing i did was to add a new model to handle the posts inside the guestbook. I named it "guestbook_posts" and added this code into the new model:
belongs_to :user_guestbook
And this into the user_guestbook model:
has_many :guestbook_posts, :as => :posts
What i wanted to achive was to be able to fetch all the posts to a certain user by:
#user = User.find(1)
puts #user.guestbook.posts
But it doesnt work for me. I dont know what i am doing wrong and if there is any easier way to do this please tell me so.
Just to note, i have created some migrations for it to as follows:
create_user_guestbook:
t.integer :user_id
create_guestbook_posts:
t.integer :guestbook_id
t.integer :from_user
t.string :post
Thanks in advance!
I think it should be:
#user
has_one :guestbook, :class_name => "UserGuestbook"
#user_guestbook
belongs_to :user
has_many :posts, :class_name => "GuestbookPost"
#guestbook_posts
belongs_to :user_guestbook
To get all posts that belongs to a single user, you can add this line to the user's model
has_many :posts, :through => :guestbook
And then, call this:
#user.posts

Resources