Open neutral2010 opened 2 years ago
~/fBootcamp/fjord-books_app my-follow_users*
❯ rails g model Follow_following follower_id:integer followed_id:integer
Running via Spring preloader in process 82202
invoke active_record
create db/migrate/20220410150944_create_follow_followings.rb
create app/models/follow_following.rb
invoke test_unit
create test/models/follow_following_test.rb
create test/fixtures/follow_followings.yml
~/fBootcamp/fjord-books_app my-follow_users*
❯ rails db:migrate
== 20220410150944 CreateFollowFollowings: migrating ===========================
-- create_table(:follow_followings)
-> 0.0025s
-- add_index(:follow_followings, :follower_id)
-> 0.0010s
-- add_index(:follow_followings, :followed_id)
-> 0.0010s
-- add_index(:follow_followings, [:follower_id, :followed_id], {:unique=>true})
-> 0.0023s
== 20220410150944 CreateFollowFollowings: migrated (0.0078s) ==================
その後、このモデルは削除
~/fBootcamp/fjord-books_app my-follow_users
❯ rails d model Follow_following follower_id:integer followed_id:integer
Running via Spring preloader in process 9405
invoke active_record
remove db/migrate/20220410150944_create_follow_followings.rb
remove app/models/follow_following.rb
invoke test_unit
remove test/models/follow_following_test.rb
remove test/fixtures/follow_followings.yml
モデル作成のコミット(直線)を削除したい
→ git reset --hard fbecbf02001d0f00f482e0b3eca99ae96f9d7b88
また削除したモデルがができてしまっている!
→ 直線のコミットを取り消すコマンド git reset --hard HEAD~
無事にコミット取り消し完了!
→ その後にモデルを削除するコマンドでやりたいこと成功!
~/fBootcamp/fjord-books_app my-follow_users*
❯ git log
commit fbecbf02001d0f00f482e0b3eca99ae96f9d7b88 (HEAD -> my-follow_users)
Author: neutral2010 <kasiwamoti5@gmail.com>
Date: Mon Apr 11 00:13:01 2022 +0900
rails g model Follow_following follower_id:integer followed_id:integer
インデックス追加・組み合わせをユニークにする機能を実装
commit 41eba1156dbbfc4dec9de906b2985d060a8ce1fe (origin/07-follow_users, 07-follow_users)
Merge: ba999c0 ab65ff4
~/fBootcamp/fjord-books_app my-follow_users* 12s
❯ git reset --hard fbecbf02001d0f00f482e0b3eca99ae96f9d7b88
HEAD is now at fbecbf0 rails g model Follow_following follower_id:integer followed_id:integer
~/fBootcamp/fjord-books_app my-follow_users
❯ git log
commit fbecbf02001d0f00f482e0b3eca99ae96f9d7b88 (HEAD -> my-follow_users)
Author: neutral2010 <kasiwamoti5@gmail.com>
Date: Mon Apr 11 00:13:01 2022 +0900
rails g model Follow_following follower_id:integer followed_id:integer
インデックス追加・組み合わせをユニークにする機能を実装
commit 41eba1156dbbfc4dec9de906b2985d060a8ce1fe (origin/07-follow_users, 07-follow_users)
Merge: ba999c0 ab65ff4
~/fBootcamp/fjord-books_app my-follow_users 45s
❯ git status
On branch my-follow_users
nothing to commit, working tree clean
~/fBootcamp/fjord-books_app my-follow_users
❯ tig
~/fBootcamp/fjord-books_app my-follow_users 2m 10s
❯ git reset --hard HEAD~
HEAD is now at 41eba11 Merge branch '06-user_icon' into 07-follow_users
~/fBootcamp/fjord-books_app my-follow_users
❯ tig
~/fBootcamp/fjord-books_app my-follow_users 21s
❯ rails d model Follow_following follower_id:integer followed_id:integer
Running via Spring preloader in process 10415
invoke active_record
remove db/migrate/20220415234850_create_follow_followings.rb
remove app/models/follow_following.rb
invoke test_unit
remove test/models/follow_following_test.rb
remove test/fixtures/follow_followings.yml
以前設計のプラクティス(Twitter)の時の名前を使うことに。ER図ではfollow_relations
これをRailsの命名規則Active Record の基礎 - Railsガイド 2-1 命名ルール
、に従うと、、、
FollowRelation
follow_relations
データベースのテーブル名を探索するときに、モデルのクラス名を複数形にした名前で探索します。つまり、Bookというモデルクラスがある場合、これに対応するデータベースのテーブルは複数形の「books」になります。
モデルのクラス名が2語以上の複合語である場合、Rubyの慣習であるキャメルケース(CamelCaseのように語頭を大文字にしてスペースなしでつなぐ)に従ってください。一方、テーブル名はスネークケース(snake_caseなど、小文字とアンダースコアで構成する)にしなければなりません。
(Railsガイドより)
integer
or reference
)foreign_key
):はテーブル名の単数形_idにしないといけない。例えば、user_id
primary_key
て?(自分用に書いておくと、idのこと)bin/rails generate migration FollowRelation user:references
すると
foreign_key
)キーの作成」belongs_to :user
今回は、結局全部書き換えないといけなくなるので、integer
で指定するのでは。
両方に、完全でないところができてしまわないよう、、、
結論、以下の3点を記述することが理想形となります。 フォームに「required: true」 モデルに「presence: true」 テーブルに「null: false」 「required :true」を使用することで、空文字での送信を防ぐことができます。
~/fBootcamp/fjord-books_app my-follow_users
❯ rails g model FollowRelation follower_id:integer followed_id:integer
Running via Spring preloader in process 26241
invoke active_record
create db/migrate/20220418040546_create_follow_relations.rb
create app/models/follow_relation.rb
invoke test_unit
create test/models/follow_relation_test.rb
create test/fixtures/follow_relations.yml
元のファイル
class CreateFollowRelations < ActiveRecord::Migration[6.1]
def change
create_table :follow_relations do |t|
t.integer :follower_id
t.integer :followed_id
t.timestamps
end
end
end
変更後
class CreateFollowRelations < ActiveRecord::Migration[6.1]
def change
create_table :follow_relations do |t|
t.integer :follower_id, null: false #Not Null制約
t.integer :followed_id, null: false
t.timestamps
end
# follower_idとfollowed_idで頻繁に検索することになるのでインデックス追加
add_index :follow_relations, :follower_id
add_index :follow_relations, :followed_id
# 組み合わせが必ずユニークであることを保証する仕組み
# あるユーザーが同じユーザーを2回以上フォローすることを防ぐ
add_index :follow_relations, [:follower_id, :followed_id], unique: true
end
end
validates :follower_id, presence: true
validates :followed_id, presence: true
follower_id
(が、フォローしている主体の方)は、たくさんのfollowed_id
を持っている。→active_follow_relations
と仮に名付けている
followed_id
(が、フォローされている人の方)は、たくさんのfollower_id
を持っている。(いろんな人にフォローされる可能性。)→reverse_follow_relations
と仮に名付けているfollower_id
やfollowed_id
これは、カラム名になる。followers
となり、user.followers
はそれらのユーザーの配列を表すことになる。user.followers
は、userさんのフォロワー(userさんをフォローしている人)の集合体。following
となり、user.following
はそれらのユーザーの配列を表すことになる。user.following
は、userさんがフォローしている人の集合体。メソッド | 意味 |
---|---|
active_relationship.follower | フォロワーを返します |
active_relationship.followed | フォローしているユーザーを返します |
user.active_relationships.create(followed_id: other_user.id) | userと紐付けて能動的関係を作成/登録する |
user.active_relationships.create!(followed_id: other_user.id) | userを紐付けて能動的関係を作成/登録する(失敗時にエラーを出力) |
user.active_relationships.build(followed_id: other_user.id) | userと紐付けた新しいRelationshipオブジェクトを返す |
モデル間の関連付けを宣言すると、2つのモデルのそれぞれのインスタンス間で「主キー - 外部キー」情報を保持しておくようにRailsに指示が伝わる。同時に、いくつかの便利なメソッドもそのモデルに追加される。
【Rails】 アソシエーションを図解形式で徹底的に理解しよう! | Pikawaka
inverse_of
「ユーザーフォロー(7)」
rails g controller follow_relations
Running via Spring preloader in process 77201
create app/controllers/follow_relations_controller.rb
invoke erb
create app/views/follow_relations
invoke test_unit
create test/controllers/follow_relations_controller_test.rb
invoke helper
create app/helpers/follow_relations_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/follow_relations.scss
resources :follow_relations, only: %i(create destroy)
https://railstutorial.jp/chapters/user_microposts?version=6.0#code-microposts_resource 参照
active_relationship.follower
active_relationship.followed
メソッドが使えないことmodel: current_user.active_relationships.build
Started GET "/users/51/followers" for ::1 at 2022-04-26 08:37:08 +0900
Processing by UsersController#followers as HTML
Parameters: {"id"=>"51"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 51], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 51], ["LIMIT", 1]]
↳ app/controllers/users_controller.rb:29:in `set_user'
Rendering layout layouts/application.html.erb
Rendering users/followers.html.erb within layouts/application
User Load (0.3ms) SELECT "users".* FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."followed_id" WHERE "follow_relations"."follower_id" = ? [["follower_id", 51]]
↳ app/views/users/followers.html.erb:12
ActiveStorage::Attachment Load (0.3ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 1], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.3ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 2], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 3], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.3ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 5], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 10], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 14], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.8ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 19], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 20], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 25], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/followers.html.erb:17
Rendered users/followers.html.erb within layouts/application (Duration: 90.2ms | Allocations: 11684)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 102.5ms | Allocations: 15451)
Completed 200 OK in 124ms (Views: 100.7ms | ActiveRecord: 3.1ms | Allocations: 17756)
Started GET "/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhhbUZ2ZERCdU5HcHNjR1l3Wm10eE9Xb3hkWEJpY25WbWFXRTNlQVk2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUTJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SWtsTlIxODJNelEyTG1wd1p5STdJR1pwYkdWdVlXMWxLajFWVkVZdE9DY25TVTFIWHpZek5EWXVhbkJuQmpzR1ZEb1JZMjl1ZEdWdWRGOTBlWEJsU1NJUGFXMWhaMlV2YW5CbFp3WTdCbFE2RVhObGNuWnBZMlZmYm1GdFpUb0tiRzlqWVd3PSIsImV4cCI6IjIwMjItMDQtMjVUMjM6NDI6MDQuNzQyWiIsInB1ciI6ImJsb2Jfa2V5In19--f2c878c14c00976f664765d91713e76926916084/IMG_6346.jpg" for ::1 at 2022-04-26 08:37:08 +0900
Processing by ActiveStorage::DiskController#show as JPEG
Parameters: {"encoded_key"=>"[FILTERED]", "filename"=>"IMG_6346"}
Completed 200 OK in 1ms (ActiveRecord: 0.0ms | Allocations: 319)
Started GET "/users/1" for ::1 at 2022-04-26 08:37:50 +0900
Processing by UsersController#show as HTML
Parameters: {"id"=>"1"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 51], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/controllers/users_controller.rb:29:in `set_user'
Rendering layout layouts/application.html.erb
Rendering users/show.html.erb within layouts/application
(0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."followed_id" WHERE "follow_relations"."follower_id" = ? [["follower_id", 1]]
↳ app/views/users/show.html.erb:9
(0.2ms) SELECT COUNT(*) FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."follower_id" WHERE "follow_relations"."followed_id" = ? [["followed_id", 1]]
↳ app/views/users/show.html.erb:10
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 1], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/show.html.erb:35
User Exists? (0.9ms) SELECT 1 AS one FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."followed_id" WHERE "follow_relations"."follower_id" = ? AND "users"."id" = ? LIMIT ? [["follower_id", 51], ["id", 1], ["LIMIT", 1]]
↳ app/models/user.rb:28:in `following?'
FollowRelation Load (0.1ms) SELECT "follow_relations".* FROM "follow_relations" WHERE "follow_relations"."follower_id" = ? AND "follow_relations"."followed_id" = ? LIMIT ? [["follower_id", 51], ["followed_id", 1], ["LIMIT", 1]]
↳ app/views/users/show.html.erb:43
Rendered users/show.html.erb within layouts/application (Duration: 31.2ms | Allocations: 5870)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 53.9ms | Allocations: 9602)
Completed 200 OK in 64ms (Views: 53.2ms | ActiveRecord: 2.2ms | Allocations: 11721)
Started DELETE "/follow_relations/17" for ::1 at 2022-04-26 08:40:16 +0900
Processing by FollowRelationsController#destroy as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "followed_id"=>"1", "commit"=>"フォローを解除する", "id"=>"17"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 51], ["LIMIT", 1]]
FollowRelation Load (0.1ms) SELECT "follow_relations".* FROM "follow_relations" WHERE "follow_relations"."follower_id" = ? AND "follow_relations"."followed_id" = ? LIMIT ? [["follower_id", 51], ["followed_id", 17], ["LIMIT", 1]]
↳ app/controllers/follow_relations_controller.rb:11:in `destroy'
Redirected to http://localhost:3000/users/17
Completed 302 Found in 7ms (ActiveRecord: 0.2ms | Allocations: 1967)
Started GET "/users/17" for ::1 at 2022-04-26 08:40:16 +0900
Processing by UsersController#show as HTML
Parameters: {"id"=>"17"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 51], ["LIMIT", 1]]
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 17], ["LIMIT", 1]]
↳ app/controllers/users_controller.rb:29:in `set_user'
Rendering layout layouts/application.html.erb
Rendering users/show.html.erb within layouts/application
(0.5ms) SELECT COUNT(*) FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."followed_id" WHERE "follow_relations"."follower_id" = ? [["follower_id", 17]]
↳ app/views/users/show.html.erb:9
(0.2ms) SELECT COUNT(*) FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."follower_id" WHERE "follow_relations"."followed_id" = ? [["followed_id", 17]]
↳ app/views/users/show.html.erb:10
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 17], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/show.html.erb:35
User Exists? (2.3ms) SELECT 1 AS one FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."followed_id" WHERE "follow_relations"."follower_id" = ? AND "users"."id" = ? LIMIT ? [["follower_id", 51], ["id", 17], ["LIMIT", 1]]
↳ app/models/user.rb:28:in `following?'
Rendered users/show.html.erb within layouts/application (Duration: 32.8ms | Allocations: 6234)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 49.5ms | Allocations: 10597)
Completed 200 OK in 70ms (Views: 55.5ms | ActiveRecord: 3.9ms | Allocations: 13964)
WHERE follower_id = ? も followed_id = ? もどちらも発行されていそうでしょうか?もしそうであれば、どちらのインデックスも使われているのでよいと思います。
?
となっているのが、インデックスが使われていることになるの?(user.id
のところにもあるから違いそう。)
インデックスを貼っていない場合に、SQL文の表示は変わるの?だろうか?
Started GET "/users/51/followers" for 127.0.0.1 at 2022-05-08 18:02:05 +0900
Processing by UsersController#followers as HTML
Parameters: {"id"=>"51"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 51], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 51], ["LIMIT", 1]]
↳ app/controllers/users_controller.rb:23:in `set_user'
Rendering layout layouts/application.html.erb
Rendering users/followers.html.erb within layouts/application
User Load (0.4ms) SELECT "users".* FROM "users" INNER JOIN "follow_relations" ON "users"."id" = "follow_relations"."followed_id" WHERE "follow_relations"."follower_id" = ? [["follower_id", 51]]
↳ app/views/users/_follow_relations_index.erb:11
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 3], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/_follow_relations_index.erb:16
ActiveStorage::Attachment Load (0.6ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ? [["record_id", 6], ["record_type", "User"], ["name", "avatar"], ["LIMIT", 1]]
↳ app/views/users/_follow_relations_index.erb:16
Rendered users/_follow_relations_index.erb (Duration: 22.2ms | Allocations: 3403)
Rendered users/followers.html.erb within layouts/application (Duration: 24.5ms | Allocations: 3549)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 40.9ms | Allocations: 7302)
Completed 200 OK in 53ms (Views: 41.1ms | ActiveRecord: 1.6ms | Allocations: 9481)
今付けているユニーク制約はデータベースで設定されている。
t.index ["follower_id", "followed_id"], name: "index_follow_relations_on_follower_id_and_followed_id", unique: true
何回もフォローできるようにしてみるとどんなエラーが出るか?
これをアプリケーション側で設定するには? 「Active Record バリデーション」のことだった。
# app/models/follow_relation.rb
validates :follower_id, uniqueness: { scope: :followed_id,
message: 'フォローフォロワー関係があります' }
とすると、エラーが変わった。(多分、起こしたいエラーが発生できている。)
参考にしたもの
Web
本