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.
Related
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')
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
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
Okay, setting aside the fact that asking how to "destroy orphans" on a chat board is just evil, here is my technical question:
I am making a simple blog-site, using Activerecord and Sinatra (and I am new to both). I have a "many to many" relationship between a Post model and a Tag model, and have been getting errors that I was not able to sort out. Finally, I arranged the models and migration so that I am not getting errors; however, when I destroy an instance of a tag, the the associations on the "posts_tags" table are left in place (existing as orphans), and I want to know how to destroy them (cue evil music).
Can anyone assist me with this?
Here are my migration for all three tables:
class CreatePostsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title, default: 'title here'
t.string :content, default: 'content here'
t.belongs_to :blog, index: true
t.timestamps
end
end
end
class CreateTagTable < ActiveRecord::Migration[5.2]
def change
create_table :tags do |t|
t.string :name, unique: true
end
end
end
class CreatePostsTagsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts_tags, :id => false do |t|
t.belongs_to :tag, index: true
t.belongs_to :post, index: true
# t.integer :post_id
# t.integer :tag_id
end
end
end
And here are my three models:
file-name: Post_Tag (I have named, and re-named this class and file-name over the course of a couple days).
class PostsTag < ActiveRecord::Base
# self.table_name = "posts_tags" #had to add this line for seed file not to give errors
belongs_to :post
belongs_to :tag
end
class Post < ActiveRecord::Base
belongs_to :blog
has_many :posts_tags
has_many :tags, :through => :posts_tags
has_many :comments, dependent: :destroy
end
class Tag < ActiveRecord::Base
has_many :posts_tags
has_many :posts, :through => :posts_tags
# has_and_belongs_to_many :posts, :through => :posts_tags
end
This configuration is not giving me error when I search for a post's tags, or a tag's posts, BUT when I do #tag = Tag.find_by_id(1) #tag.destroy the "posts_tags" table is left still having all of the tag_id == 1 rows still there. When I try to destroy them, I get errors (which I think is because that would also destroy the associations).
Does anyone know how to fix this? Is it okay to simply remove the PostsTag model all together?
The Activerecord documentation uses an example where the join-table is a single word, so I wasn't able to find the answer there.
I saw that some people were simply not making a model for the join-table. When I removed my join-table model, I got this error:
#post = Post.find_by_id(1)
#post.tags.each do |tag|
p tag.name
end
NameError at /
uninitialized constant Tag::PostsTag
Thanks for any help!
#
Phase Two of question:
After the tip that I could try adding this code to my models:
class Post < ActiveRecord::Base
has_many :posts_tags, dependent: :destroy
class Tag < ActiveRecord::Base
I am running this in my server/app file:
delete "/tag/destroy" do
body = JSON.parse request.body.read # body: {id: number} or {name: string}
#tag_to_destroy = Tag.where(body)[0]
#tag_to_destroy.destroy
redirect "/tag"
end
I am sending this request via postman:
http://localhost:4567/tag/destroy
body of request:
{
"id": 12
}
And I am getting this error:
(even though there is are rows in the posts_tags database with tag_id = 12, post_id = variousNumbers):
== Sinatra (v2.0.1) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 0, max threads: 16
* Environment: development* Listening on tcp://localhost:4567
Use Ctrl-C to stop
D, [2018-05-07T10:54:19.604906 #99099] DEBUG -- : Tag Load (0.5ms) SELECT "tags".* FROM "tags" WHERE "t
ags"."id" = $1 [["id", 12]]
D, [2018-05-07T10:54:19.617955 #99099] DEBUG -- : (0.3ms) BEGIN
D, [2018-05-07T10:54:19.633736 #99099] DEBUG -- : PostsTag Load (1.5ms) SELECT "posts_tags".* FROM "pos
ts_tags" WHERE "posts_tags"."tag_id" = $1 [["tag_id", 12]]
D, [2018-05-07T10:54:19.642305 #99099] DEBUG -- : PostsTag Destroy (1.6ms) DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
D, [2018-05-07T10:54:19.642685 #99099] DEBUG -- : (0.2ms) ROLLBACK
2018-05-07 10:54:19 - ActiveRecord::StatementInvalid - PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
^
: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL:
/Users/maiya/.rvm/gems/ruby-2.5.1/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:603:in `async_exec'
Question update:
The join-table migration was previously:
create_table :posts_tags :id => false do |t|
t.belongs_to :tag, index: true
# comment: the above line creates: t.integer :post_id
t.belongs_to :post, index: true
# comment: the above line creates: t.integer :tag_id
end
All is OK now with your migrations and models, you just need to change in the Post and Tag models:
has_many :posts_tags
to
has_many :posts_tags, dependent: :destroy
In this case after you destroy any tag or post all associated posts_tags are destroyed too, it will ensure referential integrity of your app and prevent errors.
Also, it is good idea to rollback migrations, add foreign_key: true option to t.belongs_to lines, and migrate it again. This option will provide referential integrity on db level.
UPDATE:
has_and_belongs_to_many (HABTM) doesn't work with through option, you mixes it up with has_many association. Please, read about it more in guides
To make it all working you need to:
remove :id => false option from CreatePostsTagsTable migration
remigrate or drop and recreate db
change has_and_belongs_to_many <...>, :through => :posts_tags to has_many <...>, :through => :posts_tags in both Tag and Post models
P.S. By convention all file names in ruby are in lower snake case, it should be `posts_tag.rb', not 'Posts_Tag.rb'
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