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.
Related
I'm trying to set up some new classes and associations in a Rails 2.2.2 app (please don't ask me to upgrade it).
The idea is that there is a Conversation class. It can have many participants, which are a mix of User and Pupil records, and it can have many owners, which can be a mix of a variety of things, including User and Pupil records. This is what I have so far:
#the :owner association can point to *anything*, including a Pupil or User record
class ConversationOwnership < ActiveRecord::Base
belongs_to :conversation
belongs_to :owner, :polymorphic => true
end
#the :participant association will point to either a Pupil or User record
class ConversationParticipant < ActiveRecord::Base
belongs_to :conversation
belongs_to :participant, :polymorphic => true
end
class Conversation < ActiveRecord::Base
has_many :conversation_ownerships
has_many :owners, :through => :conversation_ownerships
has_many :conversation_participants
has_many :participants, :through => :conversation_participants
end
This currently isn't working:
Conversation.first.participants
=> ActiveRecord::HasManyThroughAssociationPolymorphicError: Cannot have a has_many :through association 'Conversation#participants' on the polymorphic object 'Participant#participant'.
Now, I know that the has_many_polymorphs plugin was designed to solve this very problem, but the problem with using that is that it automatically makes associations for each of the listed classes, and because User and Pupil are in both participants and owners, they would clash:
OWNER_CLASSES = [:ilps, :lessons, :digilearning_modules, :resources, :pupil_groups, :pupils, :users]
PARTICIPANT_CLASSES = [:pupils, :users, :contacts, :parent_carers]
has_many_polymorphs :participants, :from => PARTICIPANT_CLASSES, :through => :conversation_participants, :order => "conversation_participants.created_at"
has_many_polymorphs :owners, :from => OWNER_CLASSES, :through => :conversation_ownerships, :order => "conversation_ownerships.owner_type, conversation_ownerships.created_at"
With this, the first hmp makes a .pupils association which effectively means "participants that are Pupils", and the second hmp also makes a .pupils association which effectively means "owners that are Pupils".
I don't want these extra associations that has_many_polymorphs brings, which is why i thought I would roll my own associations. But, I can't get past that Cannot have a has_many :through association 'Conversation#participants' on the polymorphic object 'Participant#participant'. error.
This feels like it should be possible - is it?
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
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.
I'm writing an app where a user can both create their own pages for people to post on, and follow posts on pages that users have created. Here is what my model relationships look like at the moment...
class User < ActiveRecord::Base
has_many :pages
has_many :posts
has_many :followings
has_many :pages, :through => :followings, :source => :user
class Page < ActiveRecord::Base
has_many :posts
belongs_to :user
has_many :followings
has_many :users, :through => :followings
class Following < ActiveRecord::Base
belongs_to :user
belongs_to :page
class Post < ActiveRecord::Base
belongs_to :page
belongs_to :user
The trouble happens when I try to work my way down through the relationships in order to create a homepage of pages (and corresponding posts) a given user is following (similar to the way Twitter's user homepage works when you login - a page that provides you a consolidated view of all the latest posts from the pages you are following)...
I get a "method not found" error when I try to call followings.pages. Ideally, I'd like to be able to call User.pages in a way that gets me the pages a user is following, rather than the pages they have created.
I'm a programming and Rails newb, so any help would be much appreciated! I tried to search through as much of this site as possible before posting this question (along with numerous Google searches), but nothing seemed as specific as my problem...
You have defined the pages association twice. Change your User class as follows:
class User < ActiveRecord::Base
has_many :pages
has_many :posts
has_many :followings
has_many :followed_pages, :class_name => "Page",
:through => :followings, :source => :user
end
Now let's test the association:
user.pages # returns the pages created by the user
user.followed_pages # returns the pages followed by the user
tried following.page instead of followings.pages ?
As to your ideal, a simplified user model should suffice (:source should be inferred):
class User < ActiveRecord::Base
has_many :pages
has_many :posts
has_many :followings
has_many :followed_pages, :class_name => "Page", :through => :followings
end class
Now, using the many-to-many association :followings, a_user.followed_pages should yield a collection of pages.
Say there're two models : User and Post, i want to keep track of who has read which post, and who writes which post, which user favorites which posts etc. Then i come up with the following solution:
class User < ActiveRecord::Base
has_many :writings
has_many :posts, :through => :writings
has_many :readings
has_many :posts, :through => :readings
#..
end
class Post < ActiveRecord::Base
has_many :writings
has_many :posts, :through => :writings
#...
end
and setting up the intermediate models - Writing, Reading. this works, but finally i find out that when i writing this
#user.posts #...
the returned array contains housekeeping information both for writings and readings. How can i solve this problem.
You want something like this:
class User < ActiveRecord::Base
has_many :writings
has_many :posts, :through => :writings
has_many :readings
has_many :read_posts, :through => :readings, :class_name => "Post"
#..
end
By giving the association name something other than just :posts you can refer to each one individually. Now...
#user.posts # => posts that a user has written via writings
#user.read_posts # => posts that a user has read via readings