rationale for :source in has_many - ruby-on-rails

I'm reading Service Oriented Design with Ruby on Rails, which includes the code you see in this image
My question is specifically about this line from the User model
has_many :followers, :through -> :followings, :source => :user
in which a User (as in an instance of the user model) has_many :followers, which are really just other users. So a user has_many users. This seems very weird to me that a User has_many :users, as if something should break when you write that, so I thought doign has_many :followers ...:source => :user might be meant to stop things from breaking.
According to one answer on this SO question Need help to understand :source option of has_one/has_many through of Rails, which was about the :source method for has_many associations, one rationale for using :source is to take code that looks this
class Newsletter
has_many :subscriptions
has_many :users, :through => :subscriptions
end
and change it into this
class Newsletter
has_many :subscriptions
has_many :subscribers, :through => :subscriptions, :source => :user
end
so that instead of doing this
Newsletter.find(id).users
one would do the much more logical (because Newsletters have subscribers not users)
Newsletter.find(id).subscribers
So, returning to the code from the book, my question is, is the only rationale for doing this in the User model
has_many :followers, :through -> :followings, :source => :user
to make more readable code User.find(id).followers ? There would otherwise be no problem doing
User.rb
has_many :users
Because if Rails let you do that (i.e. it doesn't break the application), then where would you put the belongs_to?
User.rb
has_many :users
belongs_to :user
This would seem very weird to me, but if :source is only there to let you write clearer queries, then this code should still work?
Update: the point of the question is, is there anything stopping anyone from doing this?
User.rb
has_many :users
belongs_to :user

You are correct you don't need the source, its because you are removing the through parameter
User doesn't have may users
User has many following, each of which have a user so you need:
class User < ActiveRecord::Base
has_many :followings
has_many :users, :through => :followings
end
See more here:
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association

Related

Source Reflection Errors with has_many :through

I'm attempting to create a system where my site's users can favorites pages. Those pages have two types, either clubs or sports. So, I have four models, associated as such:
User Model:
class User < ActiveRecord::Base
..
has_many :favorites
has_many :sports, :through => :favorites
has_many :clubs, :through => :favorites
..
end
Favorites Model:
class Favorite < ActiveRecord::Base
..
belongs_to :user
belongs_to :favoritable, :polymorphic => true
end
Club Model:
class Club < ActiveRecord::Base
..
has_many :favorites, :as => :favoritable
has_many :users, :through => :favorites
def to_param
slug
end
end
Sport Model:
class Sport < ActiveRecord::Base
..
def to_param
slug
end
..
has_many :favorites, :as => :favoritable
has_many :users, :through => :favorites
..
end
Essentially, the User has_many sports or clubs through favorites, and the association between favorites, sports, and clubs is polymorphic.
In practice, this is all working exactly the way I want it to, and the whole system I have designed works. However, I'm using Rails_Admin on my site, and I get an error in three places:
When loading the Dashboard (/admin) the first time. If I refresh the page, it works fine.
When loading the User model in Rails_Admin
When loading the Favorites model in Rails_Admin
Here is the error message on /admin/user (gist). All of the errors are similar, referencing ActiveRecord::Reflection::ThroughReflection#foreign_key delegated to source_reflection.foreign_key, but source_reflection is nil:.
Can anyone point me in the right direction so that I can fix this? I've searched all over, and asked other programmers/professionals, but no one could spot the error in my models. Thanks so much!
Alright, well, I finally worked this out, and figured that I'd post the fix just in case it helps someone else out in the future (no one likes finding someone else with the same problem and no posted answer).
As it turns out, with a polymorphic has_many :through, there is a little more configuration needed. My User model should have looked like this:
class User < ActiveRecord::Base
..
has_many :favorites
has_many :sports, :through => :favorites, :source => :favoritable, :source_type => "Sport"
has_many :clubs, :through => :favorites, :source => :favoritable, :source_type => "Club"
..
end
This answer to another question about polymorphic has_many :through associations is what helped me figure this out.
I encountered this error when the code included a has_many for an association that doesn't exist (mid-refactor). So it can also be caused by some general has_many misconfigure. The Ruby/Rails code never cares because the dynamic style of Ruby means the association is only called on demand. But Rails-Admin exhaustively inspects properties, leading to reflection problems.

How do I collect all the topics that are owned by the members of the groups I am a member of?

This may be tough for me to explain, so if it's not clear just let me know so I can edit as needed!
I have the following example:
class User < ActiveRecord::Base
has_many :topics
has_many :memberships
end
class Topic < ActiveRecord::Base
belongs_to :user
end
#join model between User and Group
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
has_many :topics, :through => :members
end
The problem I'm having is that I am trying to create a feed (#feed_topics) of all topics that are owned by all the members of the groups I am a member of, and I'm driving myself a little nuts.
Should I try to make this happen using associations, or make an instance method in my User model that has some ActiveRecord/SQL to union all the groups' members' topics into one ActiveRecord::Relation object?
My goal is to write current_user.feed_topics in my controller's action.
Sorry for not explaining earlier! The idea was to utilize 'Nested has_many_through's in order to get to your feed topics. This concept is documented here under the heading 'Nested Associations': http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.
Let me know if this still is unclear (or if it doesn't work).
class User < ActiveRecord::Base
has_many :topics
has_many :memberships
has_many :groups, :through => :membership
has_many :group_members, :through => :groups, :source => :member
has_many :feed_topics, :through => :group_members, :source => :topic
end
So far these are the final versions of the models from the original question (topic and membership did not change):
class User < ActiveRecord::Base
has_many :topics
has_many :memberships
has_many :groups, :through => :memberships
has_many :feed_topics, :through => :groups, :source => :member_topics
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
has_many :member_topics, :through => :members, :source => :topics
end
I am testing right now by adding more groups and members to see if it pulls in all the other members' topics of other groups.
EDIT: things seem to be working ok.
EDIT2: one little problem I had was seeing duplicate topics because a member was in multiple groups. I learned about :uniq => true and it saved the day.

How to write has_many through two objects (to the same 'destination' object)?

What is the correct way to describe the relationship between a User and the Outcomes of their Questions and Contacts? I want to be able to call User.outcomes and get all outcomes for the user, regardless of whether the outcome was for a question or a contact.
Here are my models as they stand right now. Are the has_many through relationships described correctly?
User Model
has_many :questions
has_many :contacts
has_many :outcomes, through: :questions
has_many :outcomes, through: :contacts
Question Model
has_many :outcomes
Contact Model
has_many :outcomes
Outcomes Model
belongs_to :question
belongs_to :contact
So, this is probably not the ideal solution because it returns an Array instead of an ActiveRecord::Relation. That means you lose lazy loading and the ability to further add scopes and where statements and whatnot. It's better than writing SQL and should do what you want though:
class User < ActiveRecord::Base
has_many :questions
has_many :contacts
has_many :questions_outcomes, :through => :questions, :class_name => "Outcomes"
has_many :contacts_outcomes, :through => :contacts, :class_name => "Outcomes"
def outcomes
return questions_outcomes + contacts_outcomes
end
end
Please let us know if you come up with something nicer.

Rails 3 associations

class User < ActiveRecord::Base
has_many :books
has_many :book_users
has_many :books, :through => :book_users
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :book_users
has_many :users, :through => :book_users
end
An user can write many books
An book can belong to only one user
An user can be a reader of different books
An book can be read by different users
User.books
should give me the books the user has written
User.books_read
should give me the books, are read by this user
How accomplish this ?
Second question, what is the simplest method to delete book_read from the user ?
I mean
User.method_name(book_id) # what's the method name ?
First question:
You either use :source or :class_name.
has_many :books_read, :class_name => "Book", :through => :book_users
I don't know for sure if :class_name works with has_many :through. If it doesn't work, try this:
has_many :books_read, :source => :book, :through => :book_users
That should do the trick.
Second question:
As far as I know there isn't really a simple method to delete books from the books_read relation. You could create your own method to accomplish this. Just make sure you delete records from :book_users and not the has_many :through relation. Or else the books themselves will be deleted.
def delete_book(book_id)
self.book_users.find_by_book_id(book_id).destroy
end
When using Rails 3 you can't use the find_by_... helper and need to use where.
def delete_book(book_id)
self.book_users.where(:book_id => book_id).destroy
end
Now you can call this function as follows:
User.find(1).delete_book(2)
I hope that helps you out.

Rails: Multiple "has_many through" for the two same models?

Can't wrap my head around this...
class User < ActiveRecord::Base
has_many :fantasies, :through => :fantasizings
has_many :fantasizings, :dependent => :destroy
end
class Fantasy < ActiveRecord::Base
has_many :users, :through => :fantasizings
has_many :fantasizings, :dependent => :destroy
end
class Fantasizing < ActiveRecord::Base
belongs_to :user
belongs_to :fantasy
end
... which works fine for my primary relationship, in that a User can have many Fantasies, and that a Fantasy can belong to many Users.
However, I need to add another relationship for liking (as in, a User "likes" a Fantasy rather than "has" it... think of Facebook and how you can "like" a wall-post, even though it doesn't "belong" to you... in fact, the Facebook example is almost exactly what I'm aiming for).
I gathered that I should make another association, but I'm kinda confused as to how I might use it, or if this is even the right approach. I started by adding the following:
class Fantasy < ActiveRecord::Base
...
has_many :users, :through => :approvals
has_many :approvals, :dependent => :destroy
end
class User < ActiveRecord::Base
...
has_many :fantasies, :through => :approvals
has_many :approvals, :dependent => :destroy
end
class Approval < ActiveRecord::Base
belongs_to :user
belongs_to :fantasy
end
... but how do I create the association through Approval rather than through Fantasizing?
If someone could set me straight on this, I'd be much obliged!
Keep your first set of code, then in your User Model add:
has_many :approved_fantasies, :through => :fantasizings, :source => :fantasy, :conditions => "fantasizings.is_approved = 1"
In your Fantasizing table, add an is_approved boolean field.

Resources