Reputation: 718
My Rails app is a system of Users transacting items between each other. Users bid on items and then the owner approves a bid which creates a transaction. Users can then write comments (messages) on transactions to organise things outside the app. The way I have it structured so far is like so:
User
has_many :items
has_many :bids
Item
belongs_to :user
has_many :bids
Bid
belongs_to :item
belongs_to :user
has_one :transaction
Transaction
belongs_to :bid
has_many :messages
Message
belongs_to :transaction
belongs_to :from, :class_name => "User"
belongs_to :to, :class_name => "User"
I think this works well in terms of reducing redundancy, but I'm having a few issues here getting data out efficiently. For instance, to retrieve a user
's "given items" (i.e. users' items for which there exists a transaction on one of the bids) I am doing:
user.items.joins(:bids).where("bids.id" => Transaction.select("bid_id"))
or received items:
Item.joins(:bids).where("bids.user_id" => user.id).where("bids.id" => Transaction.select("bid_id"))
which seems to be pretty costly for the information I want.
More importantly I want to aggregate users' transactions and show a ratio of given to received items, which might show up next to a user's name on any given page. At the moment what I'm doing is fetching and counting all the users' given and received items above and then dividing (which is extremely costly...). I was thinking of just having a column on the User table with received_count and given_count which updates every time a Transaction is created or destroyed, but this seems unreliable.
My question is, is there a better way to structure my data so that getting information like a user's transactions is much simpler, while keeping it normalized?
Thanks!
Upvotes: 1
Views: 125
Reputation: 3139
Drop the strict normalization. For an archival model like Transaction
you should at least store the two^user_ids (sender_id, receiver_id) and the item_id on it. A Bid won't change its item or user so you're pretty save here. You might even want to store the users' names on it if you display those names often.
Denormalized databases are not bad, and once you get to use a database like mongodb or couchdb you will use it more often.
Upvotes: 1
Reputation: 2191
Often normalising and performance are trade offs. In your case I think it makes sense to add the extra columns to the Users table to effectively cache the results. You could also add an extra column to items to show if they have had a bid with a transaction.
Normalising is really a base principle, and not something you should feel like you need to stick to for correctness, caching commonly used results even if it creates redundancy is also a really good approach.
The only other option would be to actually use caching (eg. memcached). This would let you store the counts in an actual cache, and just "bust" them when a new transaction occurs for that user.
Upvotes: 2