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:
:id => false
option from CreatePostsTagsTable migrationhas_and_belongs_to_many <...>, :through => :posts_tags
to has_many <...>, :through => :posts_tags
in both Tag and Post modelsP.S. By convention all file names in ruby are in lower snake case, it should be `posts_tag.rb', not 'Posts_Tag.rb'