Model class name for JoinTable migration method in Rails - ruby-on-rails

In Rails guide in here, it is wrote:
There is also a generator which will produce join tables if JoinTable is part of the name:
rails g migration CreateJoinTableCustomerProduct customer product
it generates:
class CreateJoinTableCustomerProduct < ActiveRecord::Migration[5.1]
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
That simply creates customers_products table, both words are plural.
When I create a model CustomerProduct:
class CustomerProduct < ApplicationRecord
end
It always search for customer_products not customers_products
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'customer_products' doesn't exist
Is there any way to make the model refer to the correct table without explicitly specifying table_name or naming it CustomersProduct :) ?
It is good to mention that I am looking to use it with :has_many :through and not :has_and_belongs_to_many
Also, Why in the generated migration, there are two lines of adding indices with different order ?
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]

I believe you should name your "join model" and join table due to basic Rails naming conventions. The way I personally like is to name join table with its own word (not two other tables' words). Like not customers_products but something like selected_product or even customer_product will do the things. In these cases your model will search the table (selected_products or customer_products) correctly.
About indexes. For composite indexes the order of the columns matters. Basically if you have t.index [:customer_id, :product_id]:
CustomerProduct.where(customer_id: 5, product_id: 6) will use your index
CustomerProduct.where(customer_id: 5) will use your index, but
CustomerProduct.where(product_id: 5) won't use the index even though the index has product_id in it
So if your gonna query your table by both columns in all the combinations you need both of these composite indexes. You may see it by adding the indexes and trying EXPLAIN ANALYZE to learn how your queries would use the indexes.

Related

How should I use Rails to index and query a join table?

I have a ruby on Rails 4 app, using devise and with a User model and a Deal model.
I am creating a user_deals table for has_many/has_many relationship between User and Deal.
Here is the migration
class CreateUserDeals < ActiveRecord::Migration
def change
create_table :user_deals do |t|
t.belongs_to :user
t.belongs_to :deal
t.integer :nb_views
t.timestamps
end
end
end
When a user load a Deal (for example Deal id= 4), I use a method called show
controllers/deal.rb
#for the view of the Deal page
def show
end
In the view of this Deal id=4 page, I need to display the nb of views of the Devise's current_user inside the Deal page the user is currently on.
deal/show.html
here is the nb of views of user: <% current_user.#{deal_id}.nb_views%>
Lets' say I have 10M+ user_deals lines, I wanted to know if I should use an index
add_index :user_deals, :user_id
add_index :user_deals, :deal_id
or maybe
add_index(:deals, [:user_id, deal_id])
Indeed in other situations I would have said Yes, but here I don't know how Rails works behind the scenes. It feels as if Rails is aware of what to do without me needing to speed up the process,...as if when Rails loads this view that there is no SQL query (such as 'find the nb of views WHERe user_id= x and deal_id= Y')....because I'm using just for the current_user who is logged-in (via devise's current_user) and for deal_id Rails knows it as we are on the very page of this deal (show page) so I just pass it as a parameter.
So do I need an index to speed it up or not?
Your question on indexes is a good one. Rails does generate SQL* to do its magic so the normal rules for optimising databases apply.
The magic of devise only extends to the current_user. It fetches their details with a SQL query which is efficient because the user table created by devise has helpful indexes on it by default. But these aren't the indexes you'll need.
Firstly, there's a neater more idiomatic way to do what you're after
class CreateUserDeals < ActiveRecord::Migration
def change
create_join_table :users, :deals do |t|
t.integer :nb_views
t.index [:user_id, :deal_id]
t.index [:deal_id, :user_id]
t.timestamps
end
end
end
You'll notice that migration included two indexes. If you never expect to create a view of all users for a given deal then you won't need the second of those indexes. However, as #chiptuned says indexing each foreign key is nearly always the right call. An index on an integer costs few write resources but pays out big savings on read. It's a very low cost default defensive position to take.
You'll have a better time and things will feel clearer if you put your data fetching logic in the controller. Also, you're showing a deal so it will feel right to make that rather than current_user the centre of your data fetch.
You can actually do this query without using the through association because you can do it without touching the users table. (You'll likely want that through association for other circumstances though.)
Just has_many :user_deals will do the job for this.
To best take advantage of the database engine and do this in one query your controller can look like this:
def show
#deal = Deal.includes(:user_deals)
.joins(:user_deals)
.where("user_deals.user_id = ?", current_user.id)
.find(params["deal_id"])
end
Then in your view...
I can get info about the deal: <%= #deal.description %>
And thanks to the includes I can get user nb_views without a separate SQL query:
<%= #deal.user_deals.nb_views %>
* If you want to see what SQL rails is magically generating just put .to_sql on the end. e.g. sql_string = current_user.deals.to_sql or #deal.to_sql
Yes, you should use an index to speed up the querying of the user_deals records. Definitely at least on user_id, but probably both [:user_id, :deal_id] as you stated.
As for why you don't see a SQL query...
First off, your code in the view appears to be incorrect. Assuming you have set up a has_many :deals, through: :user_deals association on your User class, it should be something like:
here is the nb of views of user: <%= current_user.deals.find(deal_id).nb_views %>
If you see the right number showing up for nb_views, then a query should be made when the view is rendered unless current_user.deals is already being loaded earlier in the processing or you've got some kind of caching going on.
If Rails is "aware", there is some kind of reason behind it which you should figure out. Expected base Rails behavior is to have a SQL query issued there.
Is a cleaner way of indexing other tables not:
class CreateUserDeals < ActiveRecord::Migration
def change
create_table :user_deals do |t|
t.references :user
t.references :deal
t.integer :nb_views
t.timestamps
end
end
end

What is the correct way to generate multi-word reference fields in rails

rails generate model product name:string twoWord:string twoWordRef:references
produces the following migration file
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.string :twoWord
t.references :twoWordRef, index: true
t.timestamps null: false
end
add_foreign_key :products, :twoWordRefs
end
end
I thought that field names were supposed to be snake case, not camel case, yet rails generate model is producing camel case field names in the migration file. I think I am following the examples on my generate command.
I am also finding issues later on when I try to update via reference where rails assumes the foreign key is in snake case and can't find my foreign key in the table.
What am I doing wrong? Thanks
Rails automatically generate the database tables in snake case. For example if your model is TestModel corresponding table in database will be test_models. However same is not the case for attributes.
Do this instead:
rails generate model product name:string two_word:string two_word_ref:references
Update
This is completely out of scope for what OP asked, but I thought sharing this might be helpful if you are starting Rails. Rails uses a few conventions when naming models and tables (like singular name for model and plural for table). For this it uses ActiveSupport::Inflector module.
The Inflector transforms words from singular to plural, class names to
table names, modularized class names to ones without, and class names
to foreign keys. The default inflections for pluralization,
singularization, and uncountable words are kept in inflections.rb.
You can use its classify and tableize methods to validate corresponding names. Eg:
"test_models".classify
# => "TestModel"
"TestModel".tableize
# => "test_models"

rails non-integer primary key

I have a table called Contracts. Its current default primary key is the :id field that Rails automatically generates, which is an integer. I want to have a field called contractId that is a string type and use it as a primary key instead. What I want to know is:
Is this a best practice? Are there any potential issues with doing this?
How I would go about it
Ruby on Rails (RoR) likes to emphasise the concept of convention over configuration. Therefore, it seeks to minimialise the amount of configuration.
So if you want contractId that is a string type then you can add one extra field in your table and use it wherever you want and let the Rails use id as primarykey.
Change PrimaryKey
Generate a new migration file name it "ChangePrimaryKey" (You can give any name).
class ChangePrimaryKey < ActiveRecord::Migration
def up
remove_column :table, :id # remove existing primary key
rename_column :table, :udid, :id # rename existing UDID column
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
def down
# Remove the UDID primary key. Note this would differ based on your database
execute "ALTER TABLE table DROP CONSTRAINT table_pkey;"
rename_column :table, :id, :udid
add_column :table, :id, :primary_key
end
end
If you are creating a new table, your migration might look like this:
class AddTableWithDifferentPrimaryKey < ActiveRecord:Migration
def change
create_table :table, id: false do |t|
t.string :id, null: false
# other columns
t.timestamps
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
end
end
Notice the id: false options you pass into the table — this asks Rails not to create a primary key column on your behalf.
Changes to Model
In the model, it is essential that you add the following line in order for
Rails to programmatically find the column you intend to use as your primary key.
class Table < ActiveRecord::Base
self.primary_key = :id
# rest of span
end
I hope you can do rest of the things.
Don't change default id if you want to see Rails real Magics :)
As you may know, Rails supports changing the primary id column out of the box:
class Contract < ActiveRecord::Base
self.primary_key = "contractId"
end
Please note that even if the contractId column has a unique index, an index on a string column will always be a bit slower than an index in an integer column.
Furthermore, this is not the Rails way and might confuse other developers that work with this application. Especially building associations or routes are error-prone when your table has a non-standard primary key. IMHO that is a good reason to avoid using this technic as long as possible.

Efficiently create a join table migration

rails g migration CreateJoinTable zombie:index role:index
This creates this migration:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :zombies, :roles do |t|
t.index [:zombie_id, :role_id]
t.index [:role_id, :zombie_id] # I'd be happy if it didn't have this!
end
end
end
That migration is nearly there, but why do I have four indexes rather than two? Where in my generate command does it specify to create an extra two sets of indexes for indexes that already exist?
Try this instead:
rails g migration CreateJoinTableRolesZombies roles zombies
The migration comments out the indexes, presumably to show that the create_join_table handles this for you.
Note that in rails 4 the table names must be in sort order. Also, the migration name has been expanded in this example just to make it clear. CreateJoinTable appears in it, which is sufficient.
You've only two indexes, though it might index more than it should. See Index on multiple columns in RoR to explain the array syntax and how that changes t.index.

What indexes should be added for a polymorphic association in Ruby on Rails?

I have a polymorphic association in a Ruby on Rails model. In the migration, I have:
create_table "offer_defs" do |t|
t.integer "product_def_id"
t.string "product_def_type"
...
end
I would like to add an index for this association. I am hesitating between the following options:
add_index :offer_defs, [:product_def_id, :product_def_type]
or
add_index :offer_defs, :product_def_id
add_index :offer_defs, :product_def_type
or maybe both ?
What are the pros and cons?
I would go with the latter option. It'll help if you need to count polymorphic attachments of a specific type, which you might. If you wanted the multi column index, I'd at least swap the order of the two columns (putting type first), since that type field is much more likely to be a query param on this table than the id field.
note: I'm assuming you're using mysql here, and my advice should probably be ignored, or at least checked, if you're using a different db.

Resources