has_many and single table inheritance - ruby-on-rails

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!

Related

How to a order a list of records based on how many of their associated records match records in a given array?

Sorry if the title's unclear, I didn't know how better to phrase it.
I have a model "Playlist" which has_many and belongs_to another model "User", through an intermediary model "PlaylistUser".
Let's say I'm on the page of a given Playlist (#playlist), and #users = #playlist.users. How can I list all the other Playlists, ordered by how many Users they share with #playlist?
So if #playlist.users = ["joe","nick","bob"], <playlist2>.users = ["nick","bob","tom"] and <playlist 3>.users = ["bob","jim","rich"], playlist2 should be listed first, because it shares 2 users with #playlist, while playlist3 only shares 1 user.
I hope I made what I'm trying to do clear enough, but let me know if additional clarification is needed.
Assocations:
class Playlist < ActiveRecord::Base
has_many :playlist_users
has_many :users, :through => :playlist_users
end
class PlaylistUser < ActiveRecord::Base
belongs_to :playlist
belongs_to :user
end
class User < ActiveRecord::Base
has_many :playlist_users
has_many :playlists, :through => :playlist_users
end
Playlist.joins(:users)
.where.not(id: #playlist.id)
.where(users: {id: #playlist.user_ids})
.group(:id)
.order('count(*) desc')
Or, if you need to access the result of count(*):
#playlists = Playlist.select('playlists.*, count(*) as shared_users_count')
.joins(...)
...
.order('shared_users_count desc')
# print out shared users count
#playlists.each { |pl| puts pl.shared_users_count }
This will simplify your models a little. You have already identified the "has and belongs to many" relationship, this is how to implement it.
First you need only two models. Playlist and User
class Playlist < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :playlists
end
Next you need 3 tables.
class SampleMigration < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
end
create_table :playlists do |t|
t.string :name
end
create_table :playlists_users, id: false do |t|
t.belongs_to :playlist
t.belongs_to :user
end
end
end
Note that plurals are important in naming collections, for example has_and_belongs_to_many :users and as names for your table create_table :users.
The join table does not need a model, but does need to be named in alphabetical order and the names pluralised.
Now you can use user.playlists to return an array of playlists connected to this user. Being an array you can call user.playlists[5] to get the 5th song in the list or you can iterate over it user.playlists.each do |list|.
So if #playlist.users = ["joe","nick","bob"], .users = ["nick","bob","tom"] and .users = ["bob","jim","rich"], playlist2 should be listed first, because it shares 2 users with #playlist, while playlist3 only shares 1 user.
There are two ways you can achieve this. Through a helper method where you retrieve all lists and union them in Rails, or through an SQL query. SQL is faster, but requires knowledge of SQL. Initially I would use Rails and tackle the SQl when there is a gain to be made.
One implementation might be (NOTE: treat this as pseudo code, I have not tested it);
# get all the lists
newPlaylist = array.new
users = #playlist.users
users.each do |user|
newPlaylist = newPlaylist + user.playlists
end
# count all the lists
countPlaylists = Hash.new
newPlaylist.each do |list|
if list.in? countPlaylists.keys
countPlaylists[list] = countPlaylists[list] + 1
else
countPlaylists[list] = 1
end
end
# sort the list - I'm not sure if it sorts on keys or values,
# but either way you should be able to figure it out
countPlaylists.sort

Rails ActiveRecord - How do I determine the owner in a polymorphic relation?

I have a model that could be owned by many other models (It has many foreign keys).
I'm going to try to make a polymorphic function on this model, that will behave depending on who it's owner is. Unfortunately I'm not sure what the active record code would be to find that out, and when I go in binding.pry the self object doesn't have any information I can tell.
So a good example would be Company and Person both have a Tax ID
When the Tax ID model is going to do something, it wants to know who it's owner is. Makes sense?
My actual relationship is a has_many, but I doubt that is the sticking point.
Assuming the following structure,
class Tax
belongs_to :taxable, polymorphic: true
end
class Company
has_many :taxes, as: :taxable
end
class Person
has_many :taxes, as: :taxable
end
create_table :taxes do |t|
t.integer :taxable_id
t.string :taxable_type
t.timestamps
end
each tax record can access its owner using tax.taxable. To get the type, use either
tax.taxable.class.name
or
tax.taxable_type
(With help from #SteveTurczyn and #MrYoshiji.)
class Tax
belongs_to :taxable, :polymorphic => true # tax table needs taxable_type taxable_id
end
class Company
has_one :tax, :as => :taxable
end
class Person
has_one :tax, :as => :taxable
end
Tax.first.taxable

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 -- How to setup model that can belong to either of 3 different models

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.

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