ActiveRecord::StatementInvalid on two namespaced models - ruby-on-rails

I have to two models in the same namespace which have a habtm relation.
class Resource::Item < ApplicationRecord
has_and_belongs_to_many :resource_sets, foreign_key: 'resource_item_id', class_name: 'Resource::Set', table_name: 'resource_items_sets'
end
class Resource::Set < ApplicationRecord
has_and_belongs_to_many :resource_items, foreign_key: 'resource_set_id', class_name: 'Resource::Item', table_name: 'resource_items_sets'
end
The migration has been generated with rails g migration CreateJoinTableResourceItemsResourceSets resource_item resource_set
class CreateJoinTableResourceItemsResourceSets < ActiveRecord::Migration[5.2]
def change
create_join_table :resource_items, :resource_sets do |t|
# t.index [:resource_item_id, :resource_set_id]
# t.index [:resource_set_id, :resource_item_id]
end
end
end
So far everything looks great. The table resource_items_sets is being created with the two columns resource_item_id and resource_set_id.
This is the schema
create_table "resource_items_sets", id: false, force: :cascade do |t|
t.bigint "resource_item_id", null: false
t.bigint "resource_set_id", null: false
end
After creating an resource_item I get the following which is as expected.
pry(main)> Resource::Item.first.resource_sets
=> #<Resource::Set::ActiveRecord_Associations_CollectionProxy:0x3fdd08748004>
But doing the following throws an error. I was expecting 0.
pry(main)> Resource::Item.first.resource_sets.count
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column resource_items_sets.set_id does not exist
LINE 1: ...N "resource_items_sets" ON "resource_sets"."id" = "resource_...
^
: SELECT "resource_sets"."id" FROM "resource_sets" INNER JOIN "resource_items_sets" ON "resource_sets"."id" = "resource_items_sets"."set_id" WHERE "resource_items_sets"."resource_item_id" = $1 ORDER BY "resource_sets"."name" ASC
from /Users/username/.rvm/gems/ruby-2.6.0/gems/activerecord-5.2.2/lib/active_record/connection_adapters/postgresql_adapter.rb:677:in `async_prepare'
Caused by PG::UndefinedColumn: ERROR: column resource_items_sets.set_id does not exist
LINE 1: ...N "resource_items_sets" ON "resource_sets"."id" = "resource_...
Where does set_id come from when resource_set_id has been declare everywhere? How can I fix this issue? I want to keep the namespaces for both since I might end up creating items and set for more namespaces.
Thanks so much, guys!

You need to set the foreign keys on both sides of the join table because they can't be inferred in your case.
In this case the correct has_and_belongs_to_many calls should look like:
has_and_belongs_to_many :resource_items,
foreign_key: 'resource_set_id',
association_foreign_key: 'resource_item_id',
class_name: 'Resource::Item',
join_table: 'resource_items_sets'
end
and
class Resource::Item < ApplicationRecord
has_and_belongs_to_many :resource_sets,
foreign_key: 'resource_item_id',
association_foreign_key: 'resource_set_id',
class_name: 'Resource::Set',
join_table: 'resource_items_sets'
end
Note the added association_foreign_key option specifying the other foreign key in the join table.

Related

How can I directly update the join model in a has_many_through association?

I have a Rails app with two models, joined by a third:
class Food < ApplicationRecord
has_many :shopping_list_items
has_many :users, through: :shopping_list_items
end
class ShoppingListItem < ApplicationRecord
belongs_to :user
belongs_to :food
end
class User < ApplicationRecord
has_many :shopping_list_items
has_many :foods, through: :shopping_list_items
end
The middle model, ShoppingListItem, has a few extra attributes, including priority which I'd like to update directly.
So for instance, I'd like to do something like:
r = current_user.shopping_list_items.where(food_id: 1).first
r.priority = "urgent"
r.save
The object looks fine until I try to save it, when I get a SQL error:
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: ...$1, "updated_at" = $2 WHERE "shopping_list_items"."" IS NULL
^
: UPDATE "shopping_list_items" SET "priority" = $1, "updated_at" = $2 WHERE "shopping_list_items"."" IS NULL):
I guess it's complaining about the absence of a primary key? Not sure how to fix this, since the rails docs say that join tables shouldn't have a primary key column...
I created the middle table with a migration like this, :
create_table :shopping_list_items, id: false do |t|
t.belongs_to :user
t.belongs_to :food
t.string :priority
t.integer :position
t.timestamps
end

Rails relationship undefined column

I have Users and Absences
class User
has_many :absences
end
class Absence
belongs_to :student, foreign_key: "student_id", class_name: "User"
end
and my Absence migration has
t.integer :student_id, index: true, null: false
For some reason, I can say
Absence.first.student
but when I say
Absence.first.student.absences
I get
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column
absences.user_id does not exist) LINE 1: SELECT "absences".* FROM
"absences" WHERE "absences"."user_...
^ : SELECT "absences".* FROM "absences" WHERE "absences"."user_id" = $1 LIMIT $2
Obviously, I haven't set up sth right as it is looking for user_id instead of student_id but I have no idea why this happens... Thank you for any suggestions!
The reason you are getting this error because you didn't specify custom foreign key in has_many relation. Following code will solve your problem.
class User
has_many :absences, foreign_key: "student_id"
end
class Absence
belongs_to :student, foreign_key: "student_id", class_name: "User"
end

undefined column in rails many to many

So i defined a has_and_belongs_to_many relationship between my 2 models as shown below
class Client < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email
has_and_belongs_to_many :themes, class_name: 'XY::Theme'
end
class XY::Theme < ActiveRecord::Base
has_and_belongs_to_many :clients
end
then i defined my join table from the active record rails guide like this
class CreateClientsThemesJoinTable < ActiveRecord::Migration
def change
create_table :clients_xy_themes, id: false do |t|
t.integer :client_id
t.integer :xy_theme_id
end
add_index :clients_xy_themes, :client_id
add_index :clients_xy_themes, :xy_theme_id
end
end
but when i try to access the themes from clients table in rails console, iget this error
PG::UndefinedColumn: ERROR: column clients_xy_themes.theme_id does not exist
LINE 1: ...ER JOIN "clients_xy_themes" ON "xy_themes"."id" = "clients_v...
why is this happening? My migration specifically stated the keys on the themes table , but its trying to access a column that doesnt exist
The convention is, if your relation name is themes, then the foreign key name would be theme_id.
You could define the relation like:
has_and_belongs_to_many :xy_themes, class_name: 'XY::Theme'
Or you have to define the association_foreign_key option.
has_and_belongs_to_many :themes, class_name: 'XY::Theme', association_foreign_key: 'xy_theme_id'

belongs_to with foreign_key not working in one direction

class Position < ActiveRecord::Base
belongs_to :product, foreign_key: :symbol
end
class Product < ActiveRecord::Base
has_many :positions, primary_key: :symbol, foreign_key: :symbol
end
When I do
Product.first.positions.first I am getting a Product back.
But, when I do Position.first.product I am getting nothing.
When I look at the SQL generated by the query, it is:
SELECT "products.*" FROM "products" WHERE "products.id" = ? LIMIT 1 [["id", 0]]
Why?
The SQL generated is using products.id instead of products.symbol because you didn't tell it that the association should use symbol as the primary key instead of the default of id. So, in your Position class just add primary_key: :symbol to the belongs_to and I think that'll do it.
Try this:
class Product < ActiveRecord::Base
self.primary_key = "symbol"
end
First of all, you need to revise the creation of your model Product.
You need to create it the following way:
class CreateProducts < ActiveRecord::Migration
def change
create_table :products, id: false do |t|
t.string :symbol, null: false
t.timestamps
end
add_index :products, :symbol, unique: true
end
end
And then let your model know about the primary_key, that is not id:
class Product < ActiveRecord::Base
self.primary_key = "symbol"
end
And after that, when you do Product.last, it will generate the following query:
Product.last
# Product Load (0.3ms) SELECT "products".* FROM "products" ORDER BY "products"."symbol" DESC LIMIT 1

Advice for designing a relationship in Rails

I need to connect 2 players with a connecting table. That connecting table must have an active flag, and possibly more attributes going forward. I want to be able to associate 2 Players to the PlayerLink an active_player and passive_player, so that I may say player_link.active_player.name.
I have tried the following but am not getting the behavior I want. Perhaps I am missing something silly, or approaching it incorrectly.
module V1
class Player < ActiveRecord::Base
belongs_to :player_link
end
end
module V1
class PlayerLink < ActiveRecord::Base
has_one :active_player, class_name: "Player", foreign_key: :active_player_id
has_one :passive_player, class_name: "Player", foreign_key: :passive_player_id
end
end
schema.rb entry:
create_table "player_links", force: true do |t|
t.integer "active_player_id"
t.integer "passive_player_id"
t.boolean "active"
t.datetime "created_at"
t.datetime "updated_at"
end
Test query that blows up:
V1::PlayerLink.where(active: true).each do |l|
l.active_player.update_attribute(:foo, bar)
l.passive_player.update_attribute(:foo, bar
end
With the error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: players.active_player_id: SELECT "players".* FROM "players" WHERE "players"."active_player_id" = ?
Bonus: Why is Rails querying agains players?
These are called self-referential associations - I had trouble the first time I did them as well.
Try modifying your controller logic as follows:
module V1
class Player < ActiveRecord::Base
has_many :player_links
end
end
module V1
class PlayerLink < ActiveRecord::Base
belongs_to :player, :foreign_key => "active_player_id"
belongs_to :passive_player, :class_name => "Player", :foreign_key => "passive_player_id"
end
end
For more info, see RailsCast #163.

Resources