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.
Related
I've been researching this for a few days now, and I know there is an abundance of articles that talk about single table inheritance and polymorphic associations with Rails. A lot of helpful material is from 2009 or before and I'm wondering if there is a better approach to this problem now.
The situation
The application has a couple of different user types (ie, Buyer and Seller), each of which have a profile. The profile for each user type is really different, so I'm currently ruling out the idea of one generic "profile" table.
My current approach
which is the same as this solution.
class User < ActiveRecord::Base
# authentication stuff
end
class UserType1 < User
has_one :user_type_1_profile
end
class UserType2 < User
has_one :user_type_2_profile
end
...
class UserTypeN < User
has_one :user_type_n_profile
end
Based on my research, this is kind of a "mixed model" design.
Honestly, at this point I don't know of any other solution that would work. Every time I've seen similar questions asked I see the idea of polymorphic associations brought up. Could someone elaborate how a polymorphic association would work in this case?
Does anyone have any other design suggestions?
You would be better off with a polymorphic association here with various profile types.
class User < ActiveRecord::Base
belongs_to :profile, :polymorphic => true
end
class ProfileTypeA < ActiveRecord::Base
has_one :user, :as => :profile
end
class ProfileTypeB < ActiveRecord::Base
has_one :user, :as => :profile
end
Which would require you have a migration/table like this:
change_table :users do |t|
t.integer :profile_id
t.string :profile_type
# OR (same thing, the above is more explicit)
t.references :profile, :polymorphic => true
end
There's more info on this in the Guide: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
I'm trying to make an app that does testing similiar to what you would experience in school.
I have a model Question, which can belong to either an Exam, Quiz, or Assignment.
Should I create fields for ":exam_id, :integer, :null => false; :quiz_id, :integer, :null => false; :assignment_id, :integer, :null => false;"?
The question will belong to either one or a few or all of them ( so i can reuse the same question in diff models).
Should I remove the :null=>false so it could belong to either of them....or what the best way to set that up?
It sounds like what you want to do here is use a polymorphic relationship. You will need a generic name for exam/quiz/assignment and each question will belong to one of these. Say you call them Assessments, you would set up your models like this:
class Question << ActiveRecord::Base
belongs_to :assessment, :polymorphic => true
end
class Exam << ActiveRecord::Base
has_many :questions, :as => :assessment
end
class Quiz << ActiveRecord::Base
has_many :questions, :as => :assessment
end
class Assignment << ActiveRecord::Base
has_many :questions, :as => :assessment
end
Then you will need to add two fields to your Question model:
assessment_id
assessment_type
With this relationship, you can use it like:
#exam = Exam.create({:field1 => :val1})
#exam.questions.create({:field1 => :question1})
#exam.questions.create({:field1 => :question2})
and it will know exactly which questions belong to which model based on the additional fields in your question model.
I would probably create a look up table for each relationship, so you would have an exam_questions, quiz_questions, and homework_questions table.
Each of these would contain the id of the owner (exam_id for example), and the question (question_id).
This way if a question belonged to only or two of the three, you could just create rows for those relationships. This also makes it very easy to add new relationships if you were to introduce a new owner type, like studyguide or something.
You would leave the :null => false in with this method, since a relationship will either exist or it won't.
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
I have a has_many relationship between two entities, Feeds and Posts. I also have specific types of posts, Videos and Photos. This is structured in the database using single table inheritance.
Right now I have my Feed model specifying a has_many relationship between Feeds and Posts (including the subtypes)
class Feed < ActiveRecord::Base
has_many :posts
has_many :photos
has_many :videos
Is there a better, more conventional way to specify this? Or is what I have as simple as it can get?
If i understand you correctly you have Posts and posts can be either video or photo. as Jaryl said what you have is probably the easiest to understand/handle however if you wanted to get fancy you could use single table inheritance or polymophic associations.
STI - example (from Agile Web Development with Rails 3rd Edition)
create_table :people, :force => true do |t|
t.string :type
#common attributes
t.string :name
t.string :email
#attributes for type=Customer
t.decimal :balance, :precision => 10, :scale => 2
#attributes for type=Employee
t.integer :reports_to
t.integer :dept
#attributes for type=Manager
#none
end
class Person < ActiveRecord::Base
end
class Customer < Person
end
class Employee < Person
belongs_to :boss, :class_name => "Manager", :foreign_key => :reports_to
end
class Manager < Person
end
So if you create a customer
Customer.create(:name => 'John Doe', :email => 'john#doe.com', :balance => 78.29)
you can then find it via person
x = Person.find_by_name('John Doe')
x.class #=> Customer
x.email #=> john#doe.com
x.balance #=> 78.29
x.some_customer_class_method # will work because the Person.find method returned a Customer object
So you could have
class Post < ActiveRecord::Base
end
class Photo < Post
end
class Video < Post
end
and then you could find them all by Post.all but you would get back Photo and Video objects (and post objects if you have posts that are not photo or video)
don't forget the string :type in your db table
This is pretty much the simplest you can do.
Well, if photos could be treated the same as videos, then perhaps you could do away with STI and use named scopes to provide accessors to different types of content.
I agree that the example in the question is as simple as it gets. It is already using STI and clearly states the associations.
Also, you could rip out the STI later, and split :photos and :videos into their own separate tables without changing the Feed model's code one bit. Score!
I'm trying setup a generic sort of web of related objects. Let say I have 4 models.
Book
Movie
Tag
Category
I would like to able to do:
book = Book.find(1)
book.relations << Tag.find(2)
book.relations << Category.find(3)
book.relations #=> [Tag#2, Category#3]
movie = Movie.find(4)
movie.relations << book
movie.relations << Tag.find(5)
movie.relations #=> [Book#1, Tag#5]
Basically I want to be able to take any 2 objects of any model class (or model class that I allow) and declare that they are related.
Obviously I don't want to create a huge mess of join tables. This seems like it's not quite a has many through association, and not quite a polymorphic association.
Is this something that Rails can support via it's association declarations or should I be rolling my own logic here?
Support for polymorphism has improved dramatically since the early days. You should be able to achieve this in Rails 2.3 by using a single join table for all your models -- a Relation model.
class Relation
belongs_to :owner, :polymorphic => true
belongs_to :child_item, :polymorphic => true
end
class Book
has_many :pwned_relations, :as => :owner, :class_name => 'Relation'
has_many :pwning_relations, :as => :child_item, :class_name => 'Relation'
# and so on for each type of relation
has_many :pwned_movies, :through => :pwned_relations,
:source => :child_item, :source_type => 'Movie'
has_many :pwning_movies, :through => :pwning_relations,
:source => :owner, :source_type => 'Movie'
end
A drawback of this kind of data structure is that you are forced to create two different roles for what may be an equal pairing. If I want to see all the related movies for my Book, I have to add the sets together:
( pwned_movies + pwning_movies ).uniq
A common example of this problem is the "friend" relationship in social networking apps.
One solution used by Insoshi, among others, is to register an after_create callback on the join model ( Relation, in this case ), which creates the inverse relationship. An after_destroy callback would be similarly necessary, but in this way at the cost of some additional DB storage you can be confident that you will get all your related movies in a single DB query.
class Relation
after_create do
unless Relation.first :conditions =>
[ 'owner_id = ? and owner_type = ? and child_item_id = ? and child_item_type = ?', child_item_id, child_item_type, owner_id, owner_type ]
Relation.create :owner => child_item, :child_item => owner
end
end
end
I have come up with a bit of solution. I'm not sure it's the best however. It seems you cannot have a polymorphic has_many through.
So, I fake it a bit. But it means giving up the association proxy magic that I love so much, and that makes me sad. In a basic state, here is how it works.
book = Book.find(1)
book.add_related(Tag.find(2))
book.add_related(Category.find(3))
book.related #=> [Tag#2, Category#3]
book.related(:tags) #=> [Tag#2]
I wrapped it up in a reusable module, that can be added to any model class with a single has_relations class method.
http://gist.github.com/123966
I really hope I don;t have to completely re-implement the association proxy to work with this though.
I think the only way to do it exactly as you described is the join tables. It's not so bad though, just 6, and you can pretty much set-and-forget them.
depending on how closesly related your movies/books db tables are
what if you declared
class Items < ActiveRecord::Base
has_many :tags
has_many :categories
has_and_belongs_to_many :related_items,
:class => "Items",
:join_table => :related_items,
:foreign_key => "item_id",
:associated_foreign_key => "related_item_id"
end
class Books < Items
class Movies < Items
make sure you put type in your items table