belongs_to with foreign_key not working in one direction - ruby-on-rails

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

Related

How do you set up MTI in Rails with a polymorphic belongs_to association?

In an effort to create a Short, Self Contained, Correct (Compilable), Example, imagine that I want to do the following.
I have a blog website. There are two types of posts, TextPost and LinkPost. There are also two types of users, User and Guest. I would like to implement Multiple Table Inheritance with TextPost and LinkPost, by which I mean (hopefully I'm using the term correctly):
At the model level, I will have Post, TextPost and LinkPost. TextPost and LinkPost will inherit from Post.
At the database level, I will have tables for the "leaf" models of TextPost and LinkPost, but not for Post.
Each type of Post can belong to either a User or a Guest. So we have a polymorphic belongs_to situation.
My question is how to accomplish these goals.
I tried the following, but it doesn't work.
class Post < ApplicationRecord
self.abstract_class = true
belongs_to :author, polymorphic: true # user or guest
validates :title, :author_id, :author_type, presence: true
end
class TextPost < Post
validates :content, presence: :true
end
class LinkPost < Post
validates :url, presence: :true
end
class User < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
validates :name, presence: true
end
class Guest < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
end
class CreateTextPosts < ActiveRecord::Migration[6.1]
def change
create_table :text_posts do |t|
t.string :title
t.string :content
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateLinkPosts < ActiveRecord::Migration[6.1]
def change
create_table :link_posts do |t|
t.string :title
t.string :url
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
class CreateGuests < ActiveRecord::Migration[6.1]
def change
create_table :guests do |t|
t.timestamps
end
end
end
Console output:
:001 > user = User.create(name: 'alice')
(1.6ms) SELECT sqlite_version(*)
TRANSACTION (0.1ms) begin transaction
TRANSACTION (0.1ms) SAVEPOINT active_record_1
User Create (1.2ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "alice"], ["created_at", "2021-06-11 23:33:38.445387"], ["updated_at", "2021-06-11 23:33:38.445387"]]
TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1
:002'> text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type:
'user')
Traceback (most recent call last):
1: from (irb):2:in `<main>'
NameError (wrong constant name user)
The names of constants look like the names of local variables, except that they begin with a capital letter.
All the built-in classes, along with the classes you define, have a corresponding global constant with the same name as the class called class name.
So in your case, when you define User class, there's a constant class name: User, but not user, that why the error NameError (wrong constant name user) is raised.
try text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type: 'User')

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

Reservation.last.card => NoMethodError: undefined method `card' for #<Reservation:0x0090d440e130> Did you mean? card_id

I'm sure it something really stupid, but I cannot seem to find the issue.
I'm trying to call Reservation.last.card, but get the error
Reservation Load (0.3ms) SELECT "reservations".* FROM "reservations" ORDER BY "reservations"."id" DESC LIMIT $1 [["LIMIT", 1]]
NoMethodError: undefined method `card' for #<Reservation:0x090d440e130>
Did you mean? card_id
migration + schema
class AddCardToReservations < ActiveRecord::Migration[5.2]
def change
add_reference :reservations, :card, foreign_key: true
end
end
create_table "reservations", force: :cascade do |t|
t.bigint "park_id"
t.bigint "card_id"
t.index ["card_id"], name: "index_reservations_on_card_id"
t.index ["park_id"], name: "index_reservations_on_park_id"
end
models
class Reservation < ApplicationRecord
has_one :card
belongs_to :park
end
class Card < ApplicationRecord
belongs_to :park
has_many :reservations
end
the line in the Reservation class...
has_one :card
Implies that the card object has a reservation_id which isn't the case, the foreign key is card_id in the reservation object, so what you want is...
belongs_to :card

First time trying to use a foreign key other than model_id in Rails

So I know usually, a belongs_to associates models together based on the id column, but in my case I want to associate it by the token column instead.
For example:
#app/models/user.rb
class User < ApplicationRecord
has_many :test_results
end
and
#app/models/test_result.rb
class TestResult < ApplicationRecord
belongs_to :user
end
The User model has a column named token, and so when I create a new entry in TestResult that has the same token as what appears in User, I want the TestResult to be associated to that User.
I tried this in the model form:
#app/models/test_result.rb
class TestResult < ApplicationRecord
belongs_to :user, foreign_key: "token"
end
but when I go create a new test result, I can see that ActiveRecord is still looking for the id field that matches, instead of token.
2.5.1 :001 > TestResult.create(token: "Hello")
(6.2ms) SET NAMES utf8, ##SESSION.sql_mode = CONCAT(CONCAT(##sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), ##SESSION.sql_auto_is_null = 0, ##SESSION.wait_timeout = 2147483
(0.2ms) BEGIN
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 0 LIMIT 1
(0.2ms) ROLLBACK
=> #<TestResult id: nil, token: "Hello", created_at: nil, updated_at: nil>
Here's what my migration files look like:
class TestResults < ActiveRecord::Migration[5.1]
def change
create_table :test_results do |t|
t.string :token
t.timestamps
end
end
end
and
class User < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.belongs_to :company, foreign_key: true
t.belongs_to :platform, foreign_key: true
t.string :token
t.timestamps
end
end
end
I know I am using the foreign_key wrong, but I'm not sure how.
Not sure if this is the best solution, but just adding the primary_key function to the TestResult model in my case worked.
#app/models/test_result.rb
class TestResult < ApplicationRecord
belongs_to :user, foreign_key: "token", primary_key: "token"
end

"can't write unknown attribute"

I run as my textbook says, but error occurs.
It's a book management application.
What I want to run
irb(main):001:0>publisher = Publisher.create name: 'Gihyo inc.', address: 'Ichigaya'
irb(main):002:0>publisher.books << Book.find(1)
irb(main):003:0>publisher.books.to_a
result
First,
irb(main):001:0>publisher = Publisher.create name: 'Gihyo inc.', address: 'Ichigaya'
seems succeeded.
Next,
irb(main):002:0>publisher.books << Book.find(1)
failed.
The result is as follows.
Book Load(0.6ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT 1 [["id", 1]]
(4.0ms) begin transaction
(0.3ms) rollback transaction
ActiveModel::MissingAttributeError: can't write unknown attribute `publisher_id'
from ...
related models
book.rb
class Book < ActiveRecord::Base
scope :costly, ->{where("price>?" ,3000) }
belongs_to :publisher
end
publisher.rb
class Publisher < ActiveRecord::Base
has_many :books
end
related migrations
20141218113551_create_publishers.rb
class CreatePublishers < ActiveRecord ::Migration
def change
create_table :publishers do |t|
t.string :name
t.text :address
t.timestamps
end
end
end
20141218113811_add_publisher_id_to_books.rb
class AddPublisherIdToBooks < ActiveRecord::Migration
def change
add_reference :books, :publisher, index: true
end
end
What is wrong?
I fixed this error after changing -
belongs_to :user, counter_cache: :true
to this - belongs_to :user, counter_cache: true
Notice the boolean, I was using as a symbol...which was totally incorrect.
It actually means, that you have no such field in database.
Checking your migration will help with this issue.

Resources