How to Add Migration with Multiple References to the Same Model in One Table? Ruby/Rails

How do I add migration with multiple references to the same model in one table? Ruby/Rails

You can do this simply with the add_column method in your migrations and set up the proper associations in your classes:

class AddFields < ActiveRecord::Migration
def change
add_column :tickets, :image_1_id, :integer
add_column :tickets, :image_2_id, :integer
end
end

class Ticket < ActiveRecord::Base
belongs_to :image_1, :class_name => "Image"
belongs_to :image_2, :class_name => "Image"
end

class Image < ActiveRecord::Base
has_many :primary_tickets, :class_name => "Ticket", :foreign_key => "image_1_id"
has_many :secondary_tickets, :class_name => "Ticket", :foreign_key => "image_2_id"
end

This blog post, Creating Multiple Associations with the Same Table, goes into more detail.

Defining two references to the same column in another table

A database table cannot have two columns with the same name. This is what you'll need in order to get this to work. (I'm going to use home_team and away_team to help distinguish them, but obviously you could use team1 and team2.)

rails g migration AddTeamsToMatch home_team_id:integer away_team_id:integer

This will generate a migration that looks like this:

class AddTeamsToMatch < ActiveRecord::Migration
def change
add_column :matches, :home_team_id, :integer
add_column :matches, :away_team_id, :integer
end
end

Then in your Team model, you can have the following:

class Team < ActiveRecord::Base
has_many :home_matches, class_name: "Match", foreign_key: "home_team_id"
has_many :away_matches, class_name: "Match", foreign_key: "away_team_id"

# Get all matches
def matches
self.home_matches + self.away_matches
end
end

In your Match model, you can have:

class Match < ActiveRecord::Base
belongs_to :home_team, class_name: "Team"
belongs_to :away_team, class_name: "Team"

# List both teams as array
def teams
[self.home_team, self.away_team]
end
end

Hopefully this helps.

Can I generate a migration with 2 fields that will reference on the same table?

Depends on how you want to interact with the things you can choose from 2 versions.

For tasks it was important to see who was the assigner/executor so I was able to sort the different (incoming/outgoing) tasks. So I could call user.assigned_tasks or user.executed_tasks.

For conversations I was not interested in interacting with that model. It does not matter who created it. I don't need conversation.sender or conversation.recipient. I need the messages (nested under conversation) to be identified based on the sender/recipient.

Version A

class Task < ActiveRecord::Base
belongs_to :assigner, class_name: "User"
belongs_to :executor, class_name: "User"
end

class User < ActiveRecord::Base
has_many :assigned_tasks, class_name: "Task", foreign_key: "assigner_id", dependent: :destroy
has_many :executed_tasks, class_name: "Task", foreign_key: "executor_id", dependent: :destroy
end

create_table "tasks", force: :cascade do |t|
t.integer "assigner_id"
t.integer "executor_id"
end
add_index "tasks", ["assigner_id"], name: "index_tasks_on_assigner_id", using: :btree
add_index "tasks", ["executor_id"], name: "index_tasks_on_executor_id", using: :btree

Version B

class User < ActiveRecord::Base
has_many :conversations, foreign_key: "sender_id", dependent: :destroy
end

class Conversation < ActiveRecord::Base
belongs_to :sender, class_name: "User", foreign_key: "sender_id"
belongs_to :recipient, class_name: "User", foreign_key: "recipient_id"
end

create_table "conversations", force: :cascade do |t|
t.integer "sender_id"
t.integer "recipient_id"
end
add_index "conversations", ["recipient_id"], name: "index_conversations_on_recipient_id", using: :btree
add_index "conversations", ["sender_id"], name: "index_conversations_on_sender_id", using: :btree

UPDATE:

If you run rails g model User or rails g model Order, migration will also be generated along test files, model file etc. This is the preferred way if the table does not exist yet. If table already exists and you wanna change it then you create only the migration which can be done 2 ways. The first is what you are talking about where u also pass the arguments, so when u open up the migration file the columns will already be there. But that is the hard way. You can just simply run rails g migration AddSomethingToThis. Here the migration name does not matter, you should just choose something descriptive so you can easily recognize the migration file later on. Then you open up the migration file and you put there the columns you want. In my code u see the necessary foreign keys. Besides that you can create price column etc., what you business logic needs. Then just run rake db:migrate, which will change your db schema. You can always check out your current schema in schema.rb. Never change the schema manually only by migrations.

Summing up:

  1. Create migration (with model if there is no table yet)
  2. Edit the migration file
  3. Run rake db:migrate so schema will be updated.

Update 2:

You can generate indexes easily by adding t.integer "recipient_id", index :true in the migration file and then the the line that you highlighted will appear in schema.rb (where I copied that part of my code from) after running rake db:migrate . Check out some migration examples.

So indexes are about performance. With indexes the search in the db is faster in most cases. It's something like bookmark in a book. Usually you add it to foreign keys since those are the bridges between 2 models. If there are some attributes in a model which is queried heavily then you can add it to those as well. Don't add to too many lines. You can imagine what happens when you add let's say 20 bookmarks to a book.

Rails reference 2 columns from another model

This first argument after the has_many needs to be different.

Something like this:

has_many :child_organization_organizations, class_name: "ChildOrganization", foreign_key: "organization_id", dependent: :destroy
has_many :child_organization_child_organizations, class_name: "ChildOrganization", foreign_key: "child_organization_id", dependent: :destroy

Rails: Multiple references to the same model error

Rails guesses based on the association, because you're referencing a table it can't determine from the association you'll have to add it yourself.

class CreateMessages < ActiveRecord::Migration
def change
create_table :messages do |t|
t.references :user, index: true, foreign_key: true
t.references :teammember, index: true
t.text :body
t.boolean :read, default: false

t.timestamps null: false
end
add_foreign_key :messages, :users, column: :teammember_id
end
end

Referencing the same model twice in Rails with a simple entity?

You can't actually use a single assocation here as ActiveRecord does not support assocations where the foreign key could be in one of two columns. Each has_one/has_many corresponds to a single foreign key column on the inverse side.

Instead you need one assocation on Address for each foreign key on Shipment:

class Shipment < ApplicationRecord
belongs_to :address_from,
class_name: 'Address',
inverse_of: :outgoing_shipments
belongs_to :address_to,
class_name: 'Address',
inverse_of: :incoming_shipments
end

class Address < ApplicationRecord
has_many :outgoing_shipments,
class_name: 'Shipment',
foreign_key: :address_from_id,
inverse_of: :address_from
has_many :incoming_shipments,
class_name: 'Shipment',
foreign_key: :address_to_id,
inverse_of: :address_to
end

While you could use has_one here you should note that there is nothing preventing a address from having multiple shipments unless you add uniqueness constraints on shipments.address_from_id and shipments.address_to_id and validations. Not sure why you would want this though.

Multiple foreign keys referencing the same table in RoR

This sounds like a has_many relationship to me - put the customer_id in the Address table instead.

Customer
has_many :addresses

Address
belongs_to :customer

You can also provide a foreign key and class in the assoc declaration

Customer
has_one :address
has_one :other_address, foreign_key => "address_id_2", class_name => "Address"


Related Topics



Leave a reply



Submit