I have a book model and a notes model. Each book can have many notes.
# note.rb
id | book_id | content | page_number | author_id |
I want to run a lot of queries like
Get all the notes for page 43 of a certain book
Show all the noted pages of a certain book
These types of queries seem to favor making a separate noted_pages model so that a book can have many noted_pages and each noted_page can have many notes. This is fine but my noted_pages table would effectively just have an id column and a page_number column which doesn't sit right with me.
Is there a more standard way to implement this kind of setup or is my thinking ok?
A noted_page table would relate notes to pages, but do you need a page table?
If you need a page table, then yes, worry about the note<->page many-to-many relationship and create a link table. If you don't need to store book_pages as rows of a table, then don't.
Your design:
id | book_id | content | page_number | author_id |
Will give you the answers you want by querying like this:
Get all the notes for page 43 of a certain book
select *
from note
where book_id=123 and page_number=43;
Show all the noted pages of a certain book
select page_number, count(id)
from note where book_id=123
group by page_number;
If performance is an issue then put an index on the page number. You could also make (id, book_id, page_number) into a composite key, so your data will store (note 3, book 123, page 43).
If you want a many to many relationship, as in a book can have many notes, and notes can have many books, you must have a join table like you stated.
It would look like:
noted_page
--------------
id book_id note_id
This is just general practice, whenever you have a many-to-many relationship you MUST have a join table.
(Some frameworks/orm's don't require you to have a id column, but as far as I know, Rails/ActiveRecord does require it.)
No design is bad ... it just need to be justified.
I see different solutions :
A BOOK {title, author, edited_date ...} has many PAGE {content} has_many NOTES {note_content}
A BOOK {content, title, author, edited_date ...} has_many NOTES {note_content, page_number}
It depends on how you work with the book. Do you plan on saving the content of the book by pages or not ? If not, then can you find the page, and therefore the notes ?
Regarding your questions :
A model with one attribute is not the sign that it "smells". You can even have a 1-to-1 relationship, if it's justifier.
There's no "standard" way of implementing stuff. Like I said, it depends on how you work, on how you plan your users to use it (UI-wised), how you would store, etc....
You definatly won't need a many-to-many in your case ... rails provide enough tools t fetch from a model to its associations, w/ w/o conditions, etc...
Let me know if it helps.
Both of these can be simple ARel queries, no scope is required
The latter one is a simple check of the existence of the notes collection:
#book.notes.empty? #if there are no notes, it'll return true and vice versa
To get the book notes for a certain page:
#book.notes.where(["page_number = ?", page_number]) #where page_number is a variable, possibly a parameter
However, I do think you need to think through how you're going to model your data. Marcel has a good point.
Your design is fine. Don't over normalize. You'll probably just want to put an index on book_id, page_number on your notes table so you can do the lookup efficiently.
Related
I have a Rails app that is basically designed this way:
It has a Book model, that has an external_id (all saved Book records have an external_id). The external_id links to an external source about books that doesn't allow for the data to be stored. We use a Presenter to handle some of the differences in the Book model and the external library's class to smooth things over for the view.
We let users do things like "Favorite" their books, regardless of source, so we have a join table and model with a book_id and a user_id to record favorites.
However, in some of the queries, there will be a list of results displayed to the user from the external source, even though we might have Book records with those external_ids. We want to be able to display information like who that the user is friends with that has favorited that book.
It seems there are a couple of ways to handle this:
1) Always load the canonical Book record (if it exists) in the presenter based on the external_id, and override the Book#friends_who_favorited method to return false if no external_id was found
2) Overload the presenter to either call Book#friends_who_favorited or if not a Book record, create its own join query based on external_id (since we wouldn't know the book id yet).
3) Denormalize the database a little, and make sure that we always store the external_id everywhere -- Basically treat external_id like the primary key since every Book record has an external_id. Then the queries can be done more directly, not require a join query, and we wouldn't need multiple queries written. But, this ties us even more to that external source since now our database design will be based on external_id.
It seems like #1 might be the best way to do it, even though it would introduce an extra query to Book (Book.where(external_id: x).first), since #2 would require writing a whole set of additional queries to handle the external_id case. But, I'm open to suggestions as I'm not fully comfortable with any of these methods.
Based on the discussions, if I do that I might consider this solution:
Setup
Uniform the identifier of all books to an id instead of ActiveRecord default id. This is the current field external_id, though I would prefer to rename it without underscore, say rid represents resource id.
Use a format for internal books on rid different from external books.
For example, suppose the format of external id like "abcde12345", then you name the internal books rid as "int_123" according to actual id so all of them are guaranteed to be unique.
Use a model callback to update rid after creating. If it's internal, copy its id and add "int_" prefix. If it's external, save its external id to that field.
Usage
Now usage would be simpler. For every action, use rid instead of original id. When an user favouring the book, the association would be the rid.
In the join table, you can also keep the original id there, so that when one day you changed implementation, there would still be original ids available.
Now the join table will have 4 fields: id, user_id, book_id(the original id), book_rid.
To display the users who liked this book, no matter the book is external or not, you can now query based on the rid in join table and fulfil the job.
Refacoring
Actually refacoring on this solution should not be hard and do no harm.
Add a field rid in the join table
Build a query task to fill rid of all books. Actually it's for internal books only which has blank external_id at this moment.
Build a query to fill the rid field in join table.
Refacor associating method to specify association id, and other related methods if needed.
Let's say that each Product has a category. I want to ask the Users to select several categories that the user is interested in, and find the Products that have the same category. This is similar to what Quora, Stumbleupon, and Pinterest all do.
What would be the best way to set this database structure in Rails? Should I create 3 tables: User, Product, and Category, and make the relations
User has many Categories & Product has many Categories?
The problem I see with this is doesn't it create, rather than reference, a new instance of Categories to each row of Users and Products?
*extra: What if I wanted subcategories? For example, if the user chose Technology, it could further ask to choose between web dev, mobile dev, hardware, etc.
You could do that kind of 'recommendation' pretty easily.
Something like this should work (N.B.: I did not test this code, but it is right in spirit):
def recommended_products
joins(:categories, :products).where("product_id not in (?)", self.products)
end
Explanation of each bit:
joins(:categories, :products): this does a SQL join of users, products, and categories. This gives you a 'table' where each user-product-category combination is in it's own row.
.where("product_id not in (?)", self.products): adds a SQL where clause to filter out all the rows that have products in the current user's list of products.
The associations are not a problem. They don't create any new instances by themselves, only if you write code that creates new instances yourself.
As for sub categories, I think you'll do better to make that it's own question, as it's easily a whole post in itself.
I'm working on an app that displays Products. Each Product is a model (Product) with some common info like name, brand etc. To separate what kind of Product we're looking at we have a product_type (like: Phone, TV, Sofa etc) which is basically a tinyint field (e.g. 1 = Phone, 2 = TV).
Now, depending on the product_type each Product can have different options: if it's a phone the Product should have extra info associated (like weight, does it have LTE, does it have a front-facing camera etc), a TV should have info like display size and so on.
My question is: What is the best /or/ easiest way to add this extra data to the Product, depending on the type of Product?
I was thinking of having an extra model ProductOptions with fields product_id, option_type, option_value (VARCHAR) which would store extra fields, but I'm not sure if this is optional for performance & search. In this case, searching would mean finding all ProductOptions that match a given criteria (e.g. po = ProductOption.where(:option_type => "LTE", :option_value => "yes") ) and then doing a Product.findAllIn(po) to find the actual products.
Or should I go with Postgres and use HStore? Is HStore efficient when searching?
Any ideas?
Rails newbie, all code is pseudo-code
If you're already looking at ProductType, you might make this Single-Table Inheritance and actually define different product types. That would be easier to code anyway. In this case:
class Product < ActiveRecord::Base
# common functionality goes here
end
class Sofa < Product
# sofa specific functionality goes here
end
You can then put all the options on the product table, but have a different attr_accessible, validations, and views for the different product types.
The best part about this setup is it 'just works' in rails, all you have to do is put a type column on the products table. Also, it lets you find all sofas like so:
Sofa.all
and all everything like so
Product.all
As far as database performance, well, you'll end up with a fair number of null fields, but all your attributes will be searchable (unlike serialization), and you only need to query on one table. So, I would expect this to outperform any other option I know of.
Plus, it's nice and object oriented. :)
Good luck, welcome to Rails!
How to create an unique association (many to many) with Rails ?
Ive :
article +-----+ article_user +------+ user
I want that the user can mark only one time an article, not twice or more.
How can I do that ? I Tried with uniqueness it doesn't work.
Sounds like you're looking for a composite primary key - so that a combination of article and user is always unique in your joining table.
Last time I check this isn't natively possible in ActiveRecord - you might want to try something like this:
http://compositekeys.rubyforge.org/
Stu
Let's say I have four completely independent models (Movie, Book, Game, Album) that control the types of things I have in my media collection. With them I can CRUD and tag individual albums, movies etc.
But I need to keep track, and do some stuff that is common to, all of them. So I figured I need an Item model that would give me a table item like this:
| id | item_id | item_type | status | possession |
+----+---------+-----------+--------+------------+
| 01 | 01 | 1 | 3 | 2 |
Where the status and possession bit would let me keep track of whether the item is new or used, with me or lent (to whom, in another table), etc, and the table itself would let me know how many items I have in total. All without touching the original four models and their objects, that I think should only have information about what they are, not what I can do to them. EDIT: Note that every time a movie or book is added, it also must update the items table with its related information.
I'm a newbie and I had some ideas on how to go about it, but none proved successful. I know it's a lot to ask but I would like to know, how can I accomplish this?
Any help will be appreciated, thanks.
The simplest way to do this is to use a polymorphic association.
You are describing polymorphic associations in ActiveRecord. Check out these URLs:
Railscasts - Polymorphic Assiciation
A Guide to Active Record Associations - Polymorphic Associations
Inheritance in Rails (in my opinion) leave a lot to be desired, as it only allows single table inheritance.
What I would do, is relate each of your models (Movie, Book, etc) to an Item in a 1-to-1 relationship.
class Book < ActiveRecord::Base
belongs_to :item
end
Edit: After taking a look at the polymorphic stuff (something I forgot that Rails had, to be honest), I think that might be more what you're looking for. My way would work, but I think the other way would work better.
quick comments on attribute names ...
"Status" is IMHO horrible. Maybe "Acquisition type".
"Item type" is not too bad, but "Media type" might be more appropriate.
Here is a very comprehensive "state of the art" guide on entity naming conventions that would be worth skimming through at least. http://www.uscg.mil/directives/ci/5000-5999/CI_5230_42A.pdf