optimizing page load with has_many and scope - ruby-on-rails

I'm an experienced programmer who is relatively new to ruby/rails and databases. I have created a large website for signing up for courses. I thought I was being clever creating categories of course signups using scope and has_many. Logically, it encapsulates the information well, but my pages are now loading super-slow, and trying to eager load is confusing me.
I have models for Course, Person, CourseRole (student, teacher, etc.), and CourseSignup which includes one of each (Course, Person, CourseRole). It all works smoothly. Recently, I set up scopes in CourseRole to define the categories of signups (I had been hardcoding the role name, and wanted to get away from that). I then set up has_many relationships in Course for each of the categories. So, Course inherently has_many course_signups, and has my categories
has_many :student_signups, -> { CourseSignup.student }, class_name: 'CourseSignup', foreign_key: :course_id
has_many :teacher_signups, -> { CourseSignup.teacher }, class_name: 'CourseSignup', foreign_key: :course_id
etc. (I have 6 categories). I have a page that lists all courses and all of the signups for each course. Like:
Dodge Ball:
info about course
Students:
names of students
Teachers:
names of teachers
etc.
This page loads incredibly slowly. I was trying to add includes statements to the query (based on recommendations from the Bullet gem), but it actually makes it slower. This leads me to think I'm making this more complicated than I should, but I don't know enough to have a clue how to fix it. I imagine I should restructure my models. But I like the abstraction of the scopes/has_many
The page is generated by looping through rendering a partial which shows one course.
#courses = #cuco_session.assigned_courses.includes(:period).order('periods.start_time')
works but is very slow.
#courses = #cuco_session.assigned_courses
.includes(course_signups: [:person, :course_role])
.includes([:courses_rooms, :rooms])
.includes([:helper_signups, :student_signups, :volunteer_signups, :waiting_list_signups, :person_in_room_signups])
.includes(:period).order('periods.start_time')
Also works but is even slower.

We need more information about what is actually happening in the view to really answer why your page is slow.
Also a snippet of your log showing the actual SQL would be necessary.
You mention being new to using databases online with Rails. It's hard to give advice without more of a look into the structure of some of these models, but have you considered replacing CourseSignup with just a join table between Course and Person? It seems like that's what you're getting at here but it's hard to tell.

*has_many :student_signups, -> { CourseSignup.student }, class_name: 'CourseSignup', foreign_key: :course_id
has_many :teacher_signups, -> { CourseSignup.teacher }, class_name: 'CourseSignup', foreign_key: :course_id*
above code can be refactored using Single table inheritance
# app/models/person.rb
class Person < ApplicationRecord
#your code
end
# app/models/teacher.rb
class Teacher < Person
#your code
end
# app/models/student.rb
class Student < Person
#your code
end
For Course Role you can have a separate master table of Role and put references of Person table and Role table in Course Role table
I hope above solution put some light on your question.

Related

Get children of different models in one single query

class Category
has_many :images
has_many :articles
end
class Image
belongs_to :category
end
class Article
belongs_to :category
end
I'm trying to understand what solutions there are in Rails for children of different models to be queried by the same parent?
E.g. I'd like to get all images and articles that belong to the same category and sort them all by created_at.
You can try 'includes' in rails
Article.includes(:Category)
As I said it seems to me you can use eager loading multiple associations. In your case it could be something like this:
Category.where(id: 2).includes(:images, :articles).sort_by(&:created_at)
Basically you pass your desired Category ID and get :images, :articles which belongs_to Category with particular ID. sort_byprobably should do the sorting thing.
This blog post on eager loading could help you as well.
You can't simply force Active Record to bring all their dependences in a single query (afaik), regardless if is lazy/eager loading. I think your best bet is:
class Category
has_many :images, -> { order(:created_at) }
has_many :articles, -> { order(:created_at) }
end
categories = Category.includes(:images, :articles)
As long as you iterate categories and get their images and articles, this will make three queries, one for each table categories, images and articles, which is a good tradeoff for the ease of use of an ORM.
Now, if you insist to bring all that info in just one query, for sure it must be a way using Arel, but think twice if it worths. The last choice I see is the good old SQL with:
query = <<-SQL
SELECT *, images.*, articles.*
FROM categories
-- and so on with joins, orders, etc...
SQL
result = ActiveRecord::Base.connection.execute(query)
I really discourage this option as it will bring A LOT of duplicated info as you will joining three tables and it really would be a pain to sort them for your use.

Looking for a way to track history in rails database

I'm considering this an add-on question of sorts to the thread below:
Using join tables in ruby on rails
So we have 'Student' and 'Course' scaffolds joined by a many-to-many association, but in addition there is also a 'Semester' scaffold and what I wish to do is, for each student that is added to a course, for the application to search for previous iterations of the same course through past semesters, to that it's known how many times a student has taken that class before. I'm kind of mixed up at the moment as to how to implement this, so I was hoping someone could help me pin down the logic and code I should be operating by.
Some underlying assumptions I have so far:
'Course' and 'Semester' should, like 'Student' and 'Course', be joined
by a many-to-many association (many courses are taught per semester,
and a course is taught for more than one semester).
There should be an action (let's say get_student) within the course
controller to locate the student via student_id. This would be the main area I'm scratching my head as to what to do. What would this method look like in code?
Within the student-course join table I should have an attribute
'attempts' which increments each time get_student finds this
student_id combined with the course_id that calls the method.This
would be the mechanism that actually tells how many times the course
had been attempted by the student.
I initially wondered if there should be a 'semester' controller
action to activate get_student across all semesters, but now I'm
thinking that get_student should work fine without that.
Appreciate any help I can get on this. Thanks.
This is not a good answer, just a comment.
I would comment, but hear will be more clear. I ll update for the other points. This is just an ongoing feedback/discussion, not an answer.
class Semester < ApplicationRecord
has_many :courses
end
class Course < ApplicationRecord
has_many :students
end
And
semester.courses[0].students => outputs the students array for that
This could be the method to calculate the number of student that did that course:
def studentForCourse
#input_params.course_id => course id you are selecting
semester = Semester.find(input_params)
semester.courses.each do |course|
if course.id = input_params.course_id
nstudents = course.students.size
end
end

Should I use a LIKE query for these ActiveRecord relationships?

Let's say I have a single web page form user interface with 2 sets of checkboxes. With set 1 checkboxes, I can check off what Trainers I would like ("Jason", "Alexandra, etc.) With set 2 checkboxes, I can check off what animals I would like to see ("Tigers", "Bears", etc.) Once I submit the form with these options, I get back a list of zoos that match the criteria (let's assume all the trainers work at all the zoos and all the animals are at all the zoos for discussion's sake)
We'll be running our database query by "name" (e.g., search using trainer names and animal names, NOT database ids)
Let's say we are using a Postgres database that has hundreds of thousands of rows (if not millions).
Is it more efficient to search using an "ILIKE" query or is it better to do a standard join query (e.g., Zoo.includes(:animals, :trainers).where("animals.name = ? and trainers.name = ?", animal_names, trainer_names)?
Is there a better way than what I just showed in #1 above?
model setup
class Zoo < ActiveRecord::Base
has_many :animals, through: zoo_animals
has_many :trainers, through: zoo_trainers
has_many :zoo_trainers
has_many :zoo_animals
end
class Animal < ActiveRecord::Base
has_many :zoos, through :zoo_animals
has_many :zoo_animals
end
class Trainer < ActiveRecord::Base
has_many :zoos, through :zoo_trainers
has_many :zoo_trainers
end
class ZooAnimal < ActiveRecord::Base
belongs_to :animal
belongs_to :zoo
end
class ZooTrainer < ActiveRecord::Base
belongs_to :zoo
belongs_to :trainer
end
EDIT: let's suppose I don't have access to the database ID's.
LIKE '%Jason%' is much less efficient than querying for the exact string 'Jason' (or querying for an ID), because while exact comparisons and some uses of LIKE can use an index on the column being queried, LIKE with a pattern beginning with a wildcard can't use an index.
However, performance doesn't sound like the most important consideration here. LIKE %Jason% will still probably be fast enough on a reasonably sized database under reasonable load. If the application really needs to search for things by substring (which implies that a search might have multiple results), that requirement can't be met by simple equality.
There are an endless number of higher-powered solutions to searching text, including Postgres built-in full-text search and external solutions like Elasticsearch. Without specific requirements for scaling I'd go with LIKE until it started to slow down and only then invest in something more complicated.

Rails, self-joined table, proper way of creation

For my web application I need to implement a supervisor/student relationship. I need to join my "Person" table with itself through the "Supervision" table.
class Person < ActiveRecord::Base
has_many :supervised, :class_name => 'Supervision', :foreign_key => 'supervisor_id'
has_many :supervisors, :class_name => 'Supervision', :foreign_key => 'supervised_id'
end
class Supervision < ActiveRecord::Base
belongs_to :supervised, :class_name => 'Person'
belongs_to :supervisor, :class_name => 'Person'
end
Now I need help regarding the controller. I'm not sure if I need two controllers, one for supervised and one for supervisors, or just one "Supervision" controller.
Both the student and supervisor must be able to create a "Supervision". I'm just not sure how to let the controller know whether the current user needs to be the supervisor or the student. Any thoughts?
You could create two controllers, but that would not be DRY, so it is probably best avoided. You can either set up your routes so the URLs for Prof/Student appear to be different, but actually map to the same controller.
How many students a prof has:
the_prof = Person.find( *my record number* )
the_prof.supervised.count
Who they are is that same thing, so show their names
the_prof.supervised.each do |student|
puts student.name
end
How to determine who is a professor or not? I would add a boolean flag to the people table: is_prof
My initial thought was the way to determine if a person was a student is they have no supervised. If a professor, they have no supervisor, but that breaks down if a Professor gets rid of all his students or the Student gets rid of all his Professors. Suddenly, we're in the land of undefined, which is BAD.
The flag also makes it easy to segregate all the professors and student, so you can do
profs = Person.find_by_is_prof( true )
studs = Person.find_by_is_prof( false )
(make sure to index that field in your database)
I'm guessing you'll end up making at least two controllers: one for people acting as supervisors, ie: one to manage one's subordinates; and one to manage one's own person record. You may even have another which manages a person's sign up and allows them to select their supervisor, as part of a kind of a wizard-style step.
Try mocking out how you want the application to behave first by sketching out some wireframes. Those will help you figure out which resources need to be changed from where.
If you find, for instance, that you need a list of one's subordinates, then that's probably a SubordinatesController#index page. Adding a subordinate would probably be a #new / #create pair in that controller.
Controllers are really about figuring out how the UI will respond to different user actions. Setting /my/ supervisor, and noting that I'm /someone else/'s supervisor are probably to very different things at the UI level. Just because they happen to reside in the same table doesn't mean that the UI has to reflect that symmetry.
It's strange that one would be able to change their own supervisor list. I think that's where the weirdness of your question arises.
Perhaps that's actually a side-effect of changing some other membership, like moving to a different group in the organization, in which case the reassignment would be part of its own controller.

How many classes is too many? Rails STI

I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.

Resources