Chris Muench
Chris Muench

Reputation: 18328

has_and_belongs_to_many Association table name

I am trying to figure out how rails decides to name a table. I had to do trial and error to figure it out.

The table ended up being named menu_categories_items. I want to know how it does it.

Models

# == Schema Information
#
# Table name: menu_categories
#
#  id                 :integer          not null, primary key
#  name               :string(255)
#  order              :integer
#  image_url          :string(255)
#  parent_category_id :integer
#  created_at         :datetime
#  updated_at         :datetime
#

class MenuCategory < ActiveRecord::Base
  belongs_to :parent_category, class_name: "MenuCategory"
  has_and_belongs_to_many :menu_items
end

# == Schema Information
#
# Table name: menu_items
#
#  id          :integer          not null, primary key
#  name        :string(255)
#  description :text
#  price       :decimal(, )
#  order       :integer
#  created_at  :datetime
#  updated_at  :datetime
#

class MenuItem < ActiveRecord::Base
  has_and_belongs_to_many :menu_categories
end

Migration

class CreateMenuCategoriesAndMenuItems < ActiveRecord::Migration
  def change
    create_table :menu_categories do |t|
      t.string :name
      t.integer :order
      t.string :image_url
      t.references :parent_category, index: true

      t.timestamps
    end

    create_table :menu_items do |t|
      t.string :name
      t.text :description
      t.decimal :price
      t.integer :order

      t.timestamps
    end

    create_table :menu_categories_items, id: false do |t|
      t.belongs_to :menu_category
      t.belongs_to :menu_item
    end
  end
end

Upvotes: 5

Views: 4625

Answers (4)

Richard Peck
Richard Peck

Reputation: 76784

HABTM

has_and_belongs_to_many associative tables generally have the naming convention of [plural_first_alphabetical_model]_[plural_second_alphabetical_model] with any shared naming prepended

Whilst this is the Rails standard, you can change the name to be your own by using the join_table argument:

#app/models/item.rb
class Item < ActiveRecord::Base
   has_and_belongs_to_many :categories, join_table: "test"
end

#app/models/category.rb
class Category < ActiveRecord::Base
   has_and_belongs_to_many :items, join_table: "test"
end

You can see how this works here:

If you create a has_and_belongs_to_many association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the :join_tableoption, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.

--

has_many :through

has_many :through tables are generally called [alphabetical_model_name_singular]_[alphabetical_model_name_plural]

The difference with has_many :through is that since there's a model behind this association, you need to appreciate that your table will be tied to the model itself; meaning you can actually call the join table anything you want / need


Recommendation

I would actually recommend you remove the "menu" from your models, unless, of course, they are part of a much broader spectrum of data.

The problem you have is that you're going to have to call menu_ every time you wish to call the associations or data:

@item = MenuItem.find 1
@item.menu_categories

This is too verbose. You'll be MUCH better keeping your code DRY, allowing you to call the specific objects by their "natural" names:

#app/models/item.rb
Class Item < ActiveRecord::Base
   has_and_belongs_to_many :categories
end

#app/models/category.rb
Class Category < ActiveRecord::Base
   has_and_belongs_to_many :items
end

Your HABTM table, by default, will be categories_items, allowing you to call the following:

@item = Item.find 1
@item.categories

--

OOP

You have to remember that Ruby / Rails is object orientated, meaning that every time you create any functionality in your application, you basically have to craft it around the objects in your application

Without going into too much detail here, let me explain that one of the main problems you have is that you're basically shirking the object orientated process, by calling your objects menu_x and menu_y. You need to call objects by their "plain" name - a simple name which describes what they are, associating them if required with another module.

You goal of object orientated development is to do the likes of the following:

@item = Item.find 1
@item.switch_menus 2
@item.reload!

See how this is extremely "human"? OOP development needs to craft experience around objects - allowing you to manipulate & change the object as required

Upvotes: 9

Jay Mitchell
Jay Mitchell

Reputation: 1240

You're explicitly creating the table name in your migration: create_table :menu_categories_items. The first argument to create_table becomes that table name.

If you use create_join_table then the table name will be generated by Rails, and the resulting table name will be alphabetical ordered by the joined tables.

Both HABTM and create_join_table remove a common prefix when generating the join table, which is why you see "menu_" only once in your table name. See the definition of derive_join_table_name for how it does this.

You can override the default table name by supplying a table_name option of your own to create_join_table.

Upvotes: 1

Pavan
Pavan

Reputation: 33542

From the API

If your tables share a common prefix, it will only appear once at the beginning. For example, the tables “catalog_categories” and “catalog_products” generate a join table name of “catalog_categories_products”

So,in your case the tables menu_categories and menu_items have the same prefix called menu.So the join table name would be menu_categories_items.

Upvotes: 5

Joel
Joel

Reputation: 4603

Rails naming conventions:

Database Table

Table names have all lowercase letters and underscores between words, also all table names need to be plural, e.g. invoice_items, orders

Model

The model is named using the class naming convention of unbroken MixedCase and is always the singular of the table name, e.g. table name might be orders, the model name would be Order. Rails will then look for the class definition in a file called order.rb in the /app/models directory. If the model class name has multiple capitalised words, the table name is assumed to have underscores between these words.

Controller

Controller class names are pluralized, such that OrdersController would be the controller class for the orders table. Rails will then look for the class definition in a file called orders_controller.rb in the /app/controllers directory.

Files, Directories and other pluralization

Files are named using lowercase and underscores. Assuming we have an Orders controller then the following other conventions will apply:

That there is a helper module named OrdersHelper in the orders_helper.rb found in the app/helpers directory Rails will look for view template files for the controller in the app/views/orders directory Output from this view will then be used in the layout defined in the orders.html.erb in the app/views/layouts directory Test files including order_test.rb will be created in the /test/unit directory, a file will be created in the /test/fixtures directory called orders.yml and finally a file called orders_controller_test.rb will be created in the /test/functional directory Primary Key The primary key of a table is assumed to be named id.

Foreign Key

The foreign key is named with the singular version of the target table name with _id appended to it, e.g. order_id in the items table where we have items linked to the orders table.

Many to Many Link Tables

Tables used to join two tables in a many to many relationship is named using the table names they link, with the table names in alphabetical order, for example items_orders.

Automated Record Timestamps

You can get ActiveRecord to automatically update the create and update times of records in a database table. To do this create two specially named columns created_at and updated_at to your table, i.e. t.datetime :created_at and t.datetime :updated_at. If you only want to store the date rather than a date and time, use :created_on and :updated_on.

Upvotes: 1

Related Questions