Advice for caching nested related records - ruby-on-rails

I have relations like this:
class Post < AbstractModel
belongs_to: :user
has_many :comments
has_many :votes
class Comment < AbstractModel
belongs_to :post
belongs_to :user
has_many :votes
class Vote < AbstractModel
belongs_to :user
belongs_to :comment
belongs_to :post
class User < AbstractModel
has_many :comments
has_many :posts
has_many :votes
It's more complicated, but that is the basic idea. The posts, comments and users tables are pretty big, and the app is noticeably slow to show Posts.
I've already implemented eager loading, but the db load of these nested records is very slow for this legacy app with a couple million records.
On the post template, the comment load, with its votes and users, is the slowest section, even though I'm using includes for all the dependent record eager-loading everywhere in the app.
Is there a way to cache the db lookup, and expire the cache only if the underlying dependent records change?
I can't cache the HTML very easily, because the whole app is translated into many languages.
In the first edit of this question, I was wondering if it would be quicker to store the comments, users and votes as a serialized hash in each post, and just update that column whenever a comment is made or voted on.
In other words, make a new column in Post that stores a serialization of all that post's comments, each comments' votes, each vote's users, and each comment's author (user).
class Post < AbstractModel
serialize :comments_cache
The db is mysql, in case that is relevant to the speed of eager loading and query caching.
With serialization I have concerns about data corruption, but note that the serialized column is not the "source of truth", the separate records remain the source of the serialization.
I'm also wondering about Ruby's speed in serializing/deserializing the stuff for that column. But I'm convinced from Max's response that violating 1NF is not advisable.

Related

Is there a way to get a single record back through a has_many relationship in Rails?

In my Rails (3.2) app, an Order has many LineItems. A LineItem has many LineItemPayments. A LineItemPayment has one Payment. (LineItems can potentially be payed for multiple times (subscriptions), which is why I have the join table there.)
I need to be able to query for order information from a payment record. I can get an array of orders via relationships, but I know they will always be the same order. Is there a way in Rails to set up the association to reflect this? If not, would it better to set up a method for retrieving the array and then picking the order out of that, or rather just storing the order_id with the payment and set up a direct relationship that sidesteps all this?
You'll need to work with the orders collection and narrow it down accordingly per your own logic. Although you certainly 'can' add the order_id to the payment directly, that will denormalize your data (as a cache) which is only recommended when you start hitting performance bottlenecks in your queries - otherwise it's asking for trouble in the area of data integrity:
class Payment < ActiveRecord::Base
has_many :line_item_payments
has_many :line_items, :through => :line_item_payments
has_many :orders, :through => :line_items
# use this to get the order quickly
def order
orders.first
end
# use this to narrow the scope on the query interface for additional modifications
def single_order
orders.limit(1)
end
end
class LineItemPayment < ActiveRecord::Base
belongs_to :line_item
belongs_to :payment
end
class LineItem < ActiveRecord::Base
belongs_to :order
has_many :line_item_payments
end

Rails 2 tables, 1 model

I am relatively new to ruby/rails and I have the following question:
I am working on a scheduling app and have a model named Classes and another named ClassEntries. The relationship between them is that each user can have multiple class entries per semester, each relating to one class. Each record in the Classes table belongs to a specific University. A User can have multiple entries in the ClassEntries table for 1 semester (typically 5). Their schedule is comprised of all their ClassEntries with the same semester ID.
I am not sure whether I should have a third model called Schedule that brings together the info in the ClassEntries and Classes models for the user at hand. I originally wrote this functionality in PHP and I simply used a MySQL JOIN to gather the necessary information. In Rails it seems that there should be a better way to accomplish this.
What would be the best way of going about this in Rails?
Many thanks
So, what you are looking for is pretty much associations in Rails.
You would have the following:
def User < ActiveRecord::Base
has_many :course_entries
has_many :courses, :through => :class_entries
end
def CourseEntry < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
def Course < ActiveRecord::Base
has_many :course_entries
has_many :users, :through => :class_entries
end
With those associations set up, Rails would allow you to do such things like
some_user.courses or some_course.users and it will make the joins through CourseEntry for you.
Let me know if this helps. If you need me to go more in depth let me know.

ActiveRecord relationships between HAVE and IS

So I have the following models in my Ruby on Rails setup: users and courses
The courses need to have content_managers and those content_managers are made up of several individuals in the users model.
I'm a newbie, so bear with me. I was thinking of creating a new model called content_managers that has a user_id and a course_id that links the two tables. It makes sense to me that courses HAVE content_managers. However from the users model, it doesn't make sense that users HAVE content_managers. Some of them ARE content_managers.
From that point of view I believe I'm thinking about it incorrectly and need to set up my ActiveRecord in a different manner from what I'm envisioning. Any help is appreciated.
Thanks!
There's no "have" or "are" in ActiveRecord, only "has_many", "has_one" and "belongs_to". With those tools you should be able to do what you want.
An example:
class Course < ActiveRecord::Base
has_many :content_managers
end
class ContentManager < ActiveRecord::Base
has_many :content_manager_members
has_many :users,
:through => :content_manager_members,
:source => :user
end
class ContentManagerMember < ActiveRecord::Base
belongs_to :course_manager
belongs_to :user
end
class User < ActiveRecord::Base
has_many :content_manager_members
has_many :content_managers,
:through => :content_manager_members
end
Be sure to index these correctly and you should be fine, though navigating from User to Course will be slow. You may need to cache some of this in order to find the level of performance you want, but that's a separate issue that will be uncovered during testing.
Whenever implementing something like this, be sure to load it up with a sufficient amount of test data that will represent about 10x the anticipated usage level to know where the ceiling is. Some structures perform very well only at trivial dataset sizes, but melt down when exposed to real-world conditions.

Should I denormalize a has_many has_many?

I have this:
class User < ActiveRecord::Base
has_many :serials
has_many :sites, :through => :series
end
class Serial < ActiveRecord::Base
belongs_to :user
belongs_to :site
has_many :episodes
end
class Site < ActiveRecord::Base
has_many :serials
has_many :users, :through => :serials
end
class Episode < ActiveRecord::Base
belongs_to :serial
end
I would like to do some operations on User.serials.episodes but I know this would mean all sorts of clever tricks. I could in theory just put all the episode data into serial (denormalize) and then group_by Site when needed.
If I have a lot of episodes that I need to query on would this be a bad idea?
thanks
I wouldn't bother denormalizing.
If you need to look at counts, you can check out counter_cache on the relationship to save querying for that.
Do you have proper indexes on your foreign keys? If so, pulling the data from one extra join shouldn't be that big of a deal, but you might need to drop down to SQL to get all the results in one query without iterating over .serials:
User.serials.collect { |s| s.episodes }.uniq # ack! this could be bad
It really depends on the scale you are needing out of this application. If the app isn't going to need to serve tons and tons of people then go for it. If you are getting a lot of benefit from the active record associations then go ahead and use them. As your application scales you may find yourself replacing specific instances of the association use with a more direct approach to handle your traffic load though.

How to save an array of models in Rails

I've got models called answers, surveys, questions. Now in a survey, there can be up to 200 questions and so one survey can generate up to 200 answer-models in a page.
The question is: How can I save the array of answers I have in a single db-action and not iterate over the array and save each element individually, which takes a lot of time relatively?
You can pass the 'belongs_to' relationship an :autosave symbol. This will cause the answers to be automatically saved when you save the parent. Something like this then would probably be what you want:
class Survey < ActiveRecord::Base
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :survey, :autosave
has_one :answer
end
class Answer < ActiveRecord::Base
belongs_to :question, :autosave
end
I don't know how exactly this will perform behind the scenes, but it will allow ActiveRecord to optimise the SQL and removes the need for you to iterate explicitly over the relationships.
No matter what you do, don't forget to wrap your multiple inserts in a transaction. Will really speed things up.

Resources