Which fields I should index in a Rails application? - ruby-on-rails

I've been reading about the add_index method in Rails.
In a tutorial, I found this example:
class AddTitleToMicroposts < ActiveRecord::Migration
def change
create_table :microposts do |t|
t.string :title
t.string :content
t.integer :user_id
t.timestamps
end
add_index :microposts, [:user_id, :created_at]
end
end
I have no idea why [:user_id, :created_at] are indexed and not the others.
How do I know which fields to index in a Rails application?

Well it depends on your application, you mostly index foreign keys to improve Database performance. For instance next time you are going to search all the micropost that belong to a user, it will use the user_id index, and whenever you are searching all post created between certain times i'll use the created_at index. Indexes basically speed queries, you index based on what queries you are going to be doing constantly.

I would say that everywhere there is a belongs_to relationship, including join tables, there should be a corresponding index. Some might call this overboard, but the performance difference between an indexed relationship and a non-indexed relationship is profound.
So, if a User has_many Books, then your migration should have a corresponding line add_index: :books, :user_id. If a Book has_many Authors, and an Author also has_many Books through a join table BookAuthorings, then there should be two indexes on BookAuthoring - one that covers author_id, and one that covers book_id.
I'd say that a good, solid 90% of Rails performance problems that I've run into stem from a missing index somewhere.

I mostly index (as others said) the foreign keys and all other fields I intend to use for searching or sorting. So in you case I would add a key to at least the title field if you want to show the microposts in alphabetical order.
In case of the content field it may depend. If it contains a lot of text, you will most likely not do a simple sort or search but would want to do fulltext search. In this case a normal database index would not be of great help and you would have to use a fulltext search engine instead.

Related

Rails 5.1 - should I create a database index on primary_key and foreign_key?

Here is a migration to create a table:
class CreateTemplates < ActiveRecord::Migration[5.1]
def change
create_table :templates, id: :uuid do |t|
t.references :account, type: :uuid, foreign_key: true
t.string :name
t.text :info
t.string :title
t.timestamps
end
end
end
Since account_id is a foreign_key (and identifies the customer) it will appear in almost all (99%) of queries on this table - there is not much point in retrieving a template that belongs to another customer.
So should I drop the index the above migration created for the account_id foreign_key and create this one instead?
add_index :templates, [:id, :account_id], unique: false
Or should I keep the original and also add this?
EDIT
To clarify the 99% use case - I think I was mistaken there. When creating a template, the account_id is always inserted so that the index method of the tempaltes_controller will always return all templates using the account_id, so that a user only sees a list of templates belonging to their account. For edits, updates, deletes, those actions only need the template_id. So my 99% guess is wrong! Most queries won't actually need a composite key it seems to me.
If most of your queries are going to filter on a combination of [:id, :account_id](which is unlikely) then creating a composite index will improve the performance of your queries.
However, it sounds like that most of your queries will only require :account_id, If that is the case then you do not need to add a composite index.

index: true vs foreign_key: true (Rails)

Following a guide, I ran the following command:
rails g migration CreateSnippetsUsers snippet:belongs_to user:belongs_to
This created the following migration:
class CreateSnippetsUsers < ActiveRecord::Migration[5.0]
def change
create_table :snippets_users do |t|
t.belongs_to :snippet, foreign_key: true
t.belongs_to :user, foreign_key: true
end
end
end
In the past I've seen the same thing, but with index: true instead of foreign_key: true. What's the difference between the two?
Indexes, foreign keys and foreign keys constraints are strictly related concepts in databases that are often confused or misunderstood.
REFERENCES
When you declare a reference, you're simply saying to include a column whose values should match those of another table (and in Rails you also get some useful methods to navigate through the associated models). In the example:
create_table :appointments do |t|
t.references :student
end
the appointments table will have a column named student_id whose values should be in the pool of students' id values.
INDEXES
Since when you add a reference you will probably use that column often, you may (and probably should!) also tell you database to boost the look up speed using the reference column. You can do this with the option index: true (which by the way is a default option in the reference method since Rails 5). Indexes have few drawbacks, the main being a larger memory consumption.
FOREIGN KEY CONSTRAINTS
From what said so far, reference column and foreign column are synonyms. But do you remember when I said that a reference column's values should match those of another table? If you simply declare a reference, it's your responsibility to ensure that a matching row on the referenced table exists, or someone will end up doing nonsense actions like creating appointments for non-existing students. This is an example of database integrity, and fortunately there are some mechanisms that will grant a stronger level of integrity. These mechanisms are called ' database constraints'. What the option foreign_key: true does is exactly to add this kind of constraint on the reference column, to reject any entry whose foreign key values are not in the referenced table.
Database integrity is a complex task, growing in difficulty with the database's complexity. You probably should add also other kind of constraints, like using they keywords dependent: :destroy in your class to ensure that when you delete a student, all of its existing appointments are also destroyed.
As usual, here's a RTFM link: https://guides.rubyonrails.org/association_basics.html
Index improve speed of data retrieval operations on database tables. When we write index: true to any column, it adds a database index to this column. For example I was creating a table:
create_table :appointments do |t|
t.references :student, index: true
end
It will create student_id column in appointments table.
A foreign key have different use case, it is a relationship between tables. It allow us to declare an index in one table that is related to an index in another table and also some constraints are placed.The database enforces the rules of this relationship to maintain referential integrity. For example we have two table profiles and educations, and a profile may have many educations.
create_table :educations do |t|
t.belongs_to :profile, index: true, foreign_key: true
end
Now we have profile_id column in educations table which is foreign key of profiles table. It prevents a record from being entered into the educations table unless it contains a profile_id value that exists in the profiles table. So referential integrity will be maintained.

What is the best way to convert a has_many relation to has_and_belongs_to_many?

I have a has_many relation in my app. e.g. department has many users.
I want to covert it to a has_and_belongs_to_many relation.
As part of the migration I need to preserve the current relation between users and departments, meaning I have to move all the data to the new connecting table.
This is the migration I created:
class CreateUserDepartment < ActiveRecord::Migration
def change
create_table :users_departments do |t|
t.belongs_to :user
t.belongs_to :department
end
###############################################
# need to move the data to the new table here #
###############################################
remove_column :users, :sub_department_id
end
end
what is the best way to write the missing line?
If you must, you can use execute "your SQL". See this question:
How do I add some inserts in rails migration?
The main value of the "Don't" answer to that question is in explaining why you would not want to use your models to do this. Also, I'd be surprised if you can or would want to do this using change, you would probably need to use self.up.

Creating new rails migrations error

I've been using the tutorial by Michael Hartl and I am trying to create a new model called "Recipe" to allow users to post recipes. The model is essentially the same as the micropost model, in that a user should be able to post many recipes and all recipes should be linked to one user. I therefore used the same command and migration as for the micropost:
class CreateMicroposts < ActiveRecord::Migration
def change
create_table :microposts do |t|
t.string :content
t.integer :user_id
t.timestamps
end
add_index :microposts, [:user_id, :created_at]
end
end
Created using the command: rails generate model Micropost content:string user_id:integer
The issue I am having though is that the primary key in both tables is the user_id. Will this work, or when I go to try to enter a recipe will it try to pull from the microposts table instead? Thanks in advance
when I go to try to enter a recipe will it try to pull from the microposts table instead?
No. The Recipe model in your application by default will interact with a recipes table in your database. Similarly, a Micropost model will interact with a microposts table in your database by default.
As long as you work with the Recipe model, an instance of Recipe will not pull from the microposts table.
It sounds like you may have a fundamental misunderstanding about what a table's primary key is for, and perhaps how database tables work; but for now that's outside of the scope of this question.
As an aside, you probably should have done
rails generate model MicroPost content:string user_id:integer
(notice the uppercase P in MicroPost). This is a better name for the clase and will create a micro_posts table in your database.
The primary key is not the user_id in any of the two tables. Each model generates a table with an implicit numeric autoincremented id column that is used as the PK.
What the add_index does is add an index on the user_id column. An index is not a PK.
Anyway, you should change your add_index statement in your recipes migration to:
add_index :recipes, [:user_id, :created_at]

HABTM relationship with an array

I am a beginner with Rails 3 programming and I have one problem with creating the right model.
Let's say there is an application to manage the bibliography of a book, that is manage the mapping for each chapter of the list of referenced articles. So for the article part
I could have something like:
create_table :articles do |t|
t.string :title
t.text :content
...
On the bibliography side I would like to have a model like
create_table :bibliographies do |t|
t.string :chapter
t.text :ref
...
where ref is actually an array of references to articles, so it would be managed via serialize ActiveRecord method.
Ok, so now the issue is about how to make so that the elements of the array #bibliography.ref are references (in Ruby sense) to several article_id.
How do I model such a relationship, and what Rails 3 code should I write to express that?
The thing that confuses me is that a single field of a single instance of #bibliography would reference to many #article.id .
Thanks in advance
If you really want to store relationships like that, then I would define a method in Bibliography model, something like this
(Assuming that ref is an array of ids)
def articles
Article.where(:id => self.ref)
end
I would store the relationship differently though. Add a third table/model articles_bibliographies with article_id and bibliography_id fields. Then you can use the has_many :through association which is built into ActiveRecord.
in your Bibliography class you would then have something like:
has_many :articles_bibliographies
has_many :articles, :through => :articles_bibliographies
then you can just do #bibliography.articles
Read more here http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Following egze's suggestion, I found a way to solve my problem (without using arrays!).
So, I create a has_many-through relationship, but as I want to save the order for the
articles how they are mentioned in the bibliography, the table articles_bibliographies
has also a order_nr field, where I store which is the first, second, etc. article mentioned
in the bibliography:
create_table :articles_bibliographies do |t|
t.references :article
t.references :bibliography
t.integer :order_nr
This way I can retrieve and show an ordered list of elements with:
#bibliography.articles.order("order_nr ASC")

Resources