okysabeni okysabeni - 23 days ago 8
Ruby Question

Rails self referential has_many through: is not adding the right record

Been wrecking my brain all day for this. I created a has_many through relationships for a

pool_tournament_match
so each match can have many other matches. I created a table called
pool_tournament_match_relationships
.


create_table :pool_tournament_match_relationships do |t|
t.belongs_to :parent, class_name: 'PoolTournamentMatch', index: true
t.timestamps
end


pool_tournament_match.rb




has_many :pool_tournament_match_relationships,
class_name: 'PoolTournamentMatchRelationship',
foreign_key: :parent_id
has_many :parents, through: :pool_tournament_match_relationships


So, I should be able to do something like
match.pool_tournament_match_relationships.create(parent: anotherMatch)


However, when I do this, the record added to the relationships table is actually that of
match
and not
anotherMatch
. So, for example if match id is 1 and anotherMatch id is 2. 1 will be entered into the relationships table.

Here is the output from the console:

m.pool_tournament_match_relationships.create(parent: m2)



INSERT INTO "pool_tournament_match_relationships" ("parent_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["parent_id", 1], ["created_at", 2016-11-08 21:51:29 UTC], ["updated_at", 2016-11-08 21:51:29 UTC]]


Notice that
parent_id
entered is 1 which is the id of m instead of m2.


irb(main):012:0> m.id
=> 1
irb(main):013:0> m2.id
=> 5


I appreciate your help!

EDIT: Add the schema for the relationships table:

create_table "pool_tournament_match_relationships", force: :cascade do |t|
t.integer "parent_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["parent_id"], name: "index_pool_tournament_match_relationships_on_parent_id", using: :btree
end

Answer

With has-many-through relationships, you shouldn't have to mess with creating the records for the relationship table or assigning attributes to them. Just use basic operators on the operative classes and let Rails figure out the relationships for you.

The names of your classes are long and I'm not quite straight in my head how they work, so I'll illustrate with some from one of my projects.

# app/models/event.rb

class Event < ActiveRecord::Base
  has_many :aid_stations, dependent: :destroy
  has_many :splits, through: :aid_stations
...
end

# app/models/split.rb

class Split < ActiveRecord::Base
  has_many :aid_stations, dependent: :destroy
  has_many :events, through: :aid_stations
...
end

# app/models/aid_station.rb

class AidStation < ActiveRecord::Base
  belongs_to :event
  belongs_to :split
...
end

# app/controllers/events_controller.rb

class EventsController < ApplicationController
  before_action :set_event
  ...

  def associate_split
    @event.splits << Split.find(params[:split_id])
    redirect_to splits_event_path(id: @event.id)
  end

  def remove_split
    @event.splits.delete(params[:split_id])
    redirect_to splits_event_path(@event)
  end

  private

  def set_event
    @event = Event.find(params[:id])
  end
end

Note that associate_split creates an aid_station record and remove_split destroys one, but there is no mention of any aid_stations in the methods. Rails takes care of that behind the scenes.

This would work equally well in the splits_controller if I had chosen to put the associate and remove methods there.

EDIT: Here's the relevant portion of the schema for aid_stations:

  create_table "aid_stations", force: :cascade do |t|
    t.integer  "event_id"
    t.integer  "split_id"
    ...
  end
Comments