many to many relation in Rails - ruby-on-rails-3

I take a look at these 2 blog posts and feel a little obscure about has_many relationship in RoR:
In this one: http://kconrails.com/2010/01/16/many-to-many-relationships-in-ruby-on-rails/ , in "has_many :through" section, the author said the migration files for join table Contributions is like:
class CreateContributions < ActiveRecord::Migration
def self.up
create_table :contributions do |t|
t.references :artist
t.references :song
t.string :instrument
t.timestamps
end
...........
So the migration of join table will reference to 2 table Artist and Song using t.references :artist and t.references :song. And if we want to access an attribute like the instrument artist play for that song, we can access it using join table Contributions.
In the second post: http://kconrails.com/2010/01/29/has_and_belongs_to_many-associations-in-ruby-on-rails/. Section: "has_many :through", author introduce "full-fledged table" named Categorizations. The migration is like below:
class CreateCategorizations < ActiveRecord::Migration
def self.up
create_table :categorizations do |t|
t.integer :category_id
t.integer :item_id
t.timestamps
end
...........
So the migration of full-fledged table will reference to 2 table Category and Item by t.integer :category_id and t.integer :item_id. And we can only access the attributes specific to something that both related to 2 attributes of this table like the timestamps, can we add more attributes (something like t.string :instrument) to the model as above?
For example, If i have many-to-many relation models like Manufacturer and Product, I want to keep track of the Price, I should put it into full-fledged table, right? But if I want to just add one attribute but do not keep track of it like Original_From (show where the product was produced), I only need to put into join table?
Can I generalize it this way: when the number of attributes are limited and do not important to keep track, use the join table. When the number of attributes are many and we want to keep track, we will use full-fledged table. Is it correct?
What are the differences when we access the data? Like if i want to access the Price or the Location in the example above?
I still not clear about the different between full-fledged table and join table. Please give me some ideas. Thank you very much!

I think you have this mostly right. has_and_belongs_to_many (HABTM) on both models of a many-to-many relationship assumes a simple join table between (no id, no timestamps, just foreign keys to each of the tables' primary keys.)
If the relationship itself (the join table) is "useful" -- something you would want to reference in your class, in particular because it has a field of its own, you would define it as its own model and use has_many :through => to define the relationship on both sides, with belongs_to in the new model. This is the "full fledged" option.
Mostly, you'll get the same methods with either option (e.g. you'll get a locations method on Price, and a prices method on Location). But certainly the HABTM option is more limited.
For example, I found recently that the accepts_nested_attributes_for method doesn't work with simple HABTM, but works fine with has_many :through.
I think this Rails guide does a really good job of describing when to use one versus the other.
(And it looks like the coming Rails 4.0 has much better support for HABTM and a few new features for dealing with one-to-many and many-to-many relationships :-)

Related

rails associations, retrieve conversation between 2 users

I have a rails app using postgres database, and I'm trying to add a feature where 2 users can directly chat with each other. I a user model and conversation model, and they are related by has_and_belongs_to_many relationship. I also has a joint table for this relationship.
class CreateJoinTableUserConversation < ActiveRecord::Migration
def change
create_join_table :users, :conversations do |t|
end
end
end
My goal is when a user clicks on the link to chat with another user, if there is no conversation object related to these 2 users, then create one. Otherwise retrieve the existing conversation. How can I do this with either active record or a sql call? I don't want to have to iterate through all the conversations and perform a check on whether or not these 2 users are in conversation.users, because that will take forever when there are a lot of conversations. Is there a way to access the joint table to somehow retrieve the record of a conversation that has 2 particular users associated with it?
EDIT
here is the table from the schema.
create_table "conversations", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
here are the models:
class Conversation < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :conversations
end
Instead of has_and_belongs_to_many relationship, you can use has_many: through relationship. Which will give you joint table model as in your case Conversation model.So that you can perform where query on your joint table using model.
But you will also need to add foreign keys of both the users in join table.

Opinions on Best Practice for a Relation that is linked to many other types of Relations

I have a Rails database for reviewing things at a school. My tables are School, Major, Course, Instructor, Review. The way my team currently has the Review table set up, there are non-nullable foreign keys to each one of the other tables.
My issue with this is that to submit a new review the user would want to fill in only 1 of those foreign keys. Is there a way to do this with Rails? Even if there is, it seems like this would be a better use case for Instructor_Review, Course_Review, etc tables. That also has the (very nice) benefit of being able to customize table attributes for each review.
However, if we were to break up Review into multiple tables, is there a mechanism in Rails for having common columns? The overall_rating attribute would need to be included for every type of review, should the attributes just have the same name or is there a way for Rails to have table Subclasses (I know there is in SQL...)
I wouldn't create different models for the different review types if they are similar (apart from the thing the review is about). This is probably a source of duplication which should be avoided.
Instead, you could use a polymorphic association. Your review model has one thing the review belongs to (be it a school, a teacher, or an instructor). So lets model it like that. Give it a reference to that thing and the type of that thing (so that Rails knows which class it belongs to).
With polymorphic associations, your classes can be linked like this:
class Review < ActiveRecord::Base
belongs_to :reviewable, polymorphic: true
belongs_to :user
# ...
end
class School < ActiveRecord::Base
has_many :reviews, as: :reviewable
end
class Teacher < ActiveRecord::Base
has_many :reviews, as: :reviewable
end
On the database-level this means that your review table just needs one foreign key to point at the reviewable thing (plus one type column). The review-migration would look like this:
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.references :user, index: true
t.references :reviewable, polymorphic: true, index: true, null: false
t.timestamps null: false
end
end
end
polymorphic: true lets Rails create the id- and type-column.
For details, please refer to the ActiveRecord documentation.
PS: There is also a RailsCast covering this issue. But beware: It's from 2009 - pretty old, but (from a quick glimpse at it) it should still work.
You are looking for polymophic relationships. Basically add a type column on the review and then you have to wire it up correctly. Here is a great article for that.
http://www.gotealeaf.com/blog/understanding-polymorphic-associations-in-rails
The review table will then have your common reviewable columns that will be shared. The next part is a little more complicated. You would then setup a review to have an extension.
To explain the extension, this is normally done when you have an entity that can either be a group or user (similar to how your InstructoreReview and CourseReview will by types of reviews). Those groups/users will have a profile that is similar to the extension of the review.
The ReviewExtension will have it's own table per review that you are extending and most of the time those extensions in their own review_extensions folder under models.
Once that is done you will delegate the common getter and setter methods you are wanting to the extension.
Hopefully this will at least give you enough to get going.

HABTM associations in Rails : collecting and counting the categories of a model's children

I have a has_and_belongs_to_many relationship setup. It looks like this:
books have_and_belong_to_many categories
categories have_and_belongs_to_many books
a store has_many books
a book belongs_to a store
I'm trying to show how many books in each store belong to each category. So my view would show Store X has 200 books and 80 of them are mystery, 60 are non fiction, etc.
I have been trying out a bunch of different ways of doing this, but no success so far. I think I'm starting in the wrong place. Any direction would be much appreciated.
Thanks
This is Rails 4 and psql by the way.
Provided that you have a books_categories join table you can add a has_many :categories, through: :books association to which links stores and categories through books.
class Store < ActiveRecord::Base
has_many :books
has_many :categories, through: :books
end
That's the easy part. Now lets get each category and the books count (revised):
def books_per_category
categories.select('categories.id, categories.name, count(books.id) as count')
.group('categories.id, categories.name')
.map do |c|
{
name: c.name,
count: c.count
}
end
end
Courtesy of #jakub-kosiƄski
Generally, instead of using Rails' built-in 'has_and_belongs_to_many' method, it is better practice to use a join table. In this setup, you have three tables:
Books
Categories
BookCategories
The BookCategories (or whatever you decide to call it) is a join table that belongs_to both Books and Categories and has a Foreign ID of each. You would then use Rails' "has_many :through" to link the Books and Categories.
The store would have a 'has_many' relationship with books. With the prior relationship setup right, you can then use this method to get the count for a store for a particular category:
Store.books.where(category:'Mystery')

Using a different foreign key with nested resources in Rails

When working with nested resources in Rails is it possible to use a value/field other than the primary key of the parent resource as the foreign key for the child resource objects?
e.g. if I have "books" that belong to "authors", I pass the "author"'s primary key to the book when it's created with t.references :author, index: true in app/db/migrate/[timestamp]_create_books.rb (right?).
Is it possible to pass the author's name, instead? (Assuming that the "authors" table has a "name" field...)
I ask because I have a preexisting table of books with various fields (author, title, subject, year, etc.) and it seems simpler to create an authors table with the unique authors from the books table and then join them where authors.name=books.author instead of having to figure out a way of getting the unique primary keys from authors to associate with the correct author in books. (But I am probably totally wrong about this.) (In any case, I am curious if it can be done and/or what the proper way of bringing in the preexisting database that lacks the author-book associations would be.)
(I apologize if my terminology is off.)
So, your models:
class Author < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :author, primary_key: "name", foreign_key: "author_name"
end
It should work. But this way breaks one of the Rails cornerstones: "Convention over configuration" and that's why you have chances to end up with total mess in your DB someday.
And what else attributes except 'name' Author model has? If there are few of them (or even only one - 'name') it will be better and easier to have only one model 'Book' with the attribute 'author'.

What is and examples of using data type - References

i wanted to know about the data type references and some examples of how/why it would be used on a website. If their is a difference when using Ruby-on-Rails, i tagged it just in case. I am new at programming and it would help tremendously to explain everything in layman's terms so i can slowly build my way up to being a computer wiz.
Appreciate the trouble in helping me, thanks.
I'm taking a guess that you're referring to t.references :associated_model in a migration?
Suppose two models, Post and Author.
class Post < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :posts
end
Your migration contains:
create_table :posts do |t|
t.references :author
end
This will create the author_id column on the posts table with the integer datatype.
In migrations, t.belongs_to is an alias for t.references and matches the naming used to set up the associations in your models.
It is not a real datatype, it is the rails shorthand for creating a foreign key in the table, which is by default an integer.
When you call t.references :widgets in your migration, it actually creates an integer column called widget_id
You may want to read through the Rails Migrations Guide to learn more about how database and migrations are handled in rails.