Open takanori-matsushita opened 4 years ago
Relationshipモデルの作成
php artisan make:model Relationship -a
リスト 14.1: relationshipsテーブルにインデックスを追加する database/migrations/[time_stamps]_create_relationships_table.php
public function up()
{
Schema::create('relationships', function (Blueprint $table) {
$table->unsignedInteger('follower_id');
$table->unsignedInteger('followed_id');
$table->timestamps();
$table->index('follower_id');
$table->index('followed_id');
$table->index(['follower_id', 'followed_id']);
$table->unique(['follower_id', 'followed_id']);
$table->foreign('follower_id')->references('id')->on('users');
$table->foreign('followed_id')->references('id')->on('users');
});
インクリメント(id)を使用しない宣言とプライマリーキーの指定をする。 app/Relationship.php
class Relationship extends Model
{
protected $primaryKey = [
'follower_id', 'followed_id'
];
public $incrementing = false;
}
演習 省略
リスト 14.2: 能動的関係に対して1対多 (has_many) の関連付けを実装する app/User.php
public function active_relationships()
{
return $this->hasMany('App\Relationship', 'follower_id');
}
app/Observers/UserObserver.php
public function deleted(User $user)
{
$user->microposts->each(function ($post) {
$post->delete();
});
// 紐付いているfollower_idのデータをすべて削除
$user->active_relationships->each(function ($follower) {
$follower->delete();
});
}
リスト 14.3: リレーションシップ/フォロワーに対してbelongs_toの関連付けを追加する app/Relationship.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Relationship extends Model
{
public function follower()
{
$this->belongsTo('App\User');
}
public function followed()
{
$this->belongsTo('App\User');
}
}
演習
>>> $user = User::find(1)
=> App\User {#3133
id: 1,
name: "Example User",
email: "example@railstutorial.org",
email_verified_at: null,
created_at: "2020-04-26 10:09:32",
updated_at: "2020-04-26 10:09:32",
}
>>> $other_user = User::find(2)
id: 2,
name: "Johnson McClure MD",
email: "fsenger@example.net",
email_verified_at: null,
created_at: "2020-04-26 10:09:37",
updated_at: "2020-04-26 10:09:37",
}
>>> $user->active_relationships()->create(['followed_id'=> $other_user->id])
=> App\Relationship {#3138
followed_id: 2,
follower_id: 1,
updated_at: "2020-04-26 10:14:18",
created_at: "2020-04-26 10:14:18",
id: 1,
}
リスト 14.4: Relationshipモデルのバリデーションをテストする 省略
リスト 14.5: Relationshipモデルに対してバリデーションを追加する
リクエストの作成
php artisan make:requiest RelationshipRequest
app/Http/Requests/RelationshipRequest.php
public function rules()
{
return [
'follower_id' => 'required',
'followed_id' => 'required'
];
}
リスト 14.6: Relationship用のfixtureを空にする green 省略
14.1.4 フォローしているユーザー リスト 14.8: Userモデルにfollowingの関連付けを追加する app/User.php
class User extends Authenticatable
{
:
public function following()
{
return $this->belongsToMany(self::class, 'relationships', 'follower_id', 'followed_id');
}
}
リスト 14.9: “following” 関連のメソッドをテストする red 省略
リスト 14.10: "following" 関連のメソッド green app/User.php
class User extends Authenticatable
{
:
public function follow(Int $other_user)
{
return $this->following()->attach($other_user->id);
}
public function unfollow(Int $other_user)
{
return $this->following()->detach($other_user->id);
}
public function is_following(Int $other_user)
{
return (boolean) $this->following()->where('followed_id', $other_user->id)->first(['id'])
}
}
リスト 14.11: green 省略
リスト 14.12: 受動的関係を使ってuser.followersを実装する app/User.php
class User extends Authenticatable
{
:
public function passive_relationships()
{
return $this->hasMany('App\Relationship', 'followed_id');
}
:
public function followers()
{
return $this->belongsToMany(self::class, 'relationships', 'followed_id', 'follower_id');
}
}
app/Observers/UserObserver.php
:
public function deleted(User $user)
{
$user->microposts->each(function ($post) {
$post->delete();
});
$user->active_relationships->each(function ($follower) {
$follower->delete();
});
$user->passive_relationships->each(function ($followed) { //追加
$followed->delete();
})
}
リスト 14.13: followersに対するテスト green 省略
リスト 14.14: サンプルデータにfollowing/followerの関係性を追加する database/seeds/RelationshipSeeder.php
<?php
use App\User;
use App\Relationship;
use Illuminate\Database\Seeder;
class RelationshipSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$users = User::all();
$user = $users->first();
$following = range(3, 51);
$followers = range(4, 41);
foreach ($followers as $follower) {
factory(Relationship::class)->create([
'followed_id' => $user->id,
'follower_id' => $follower
]);
}
foreach ($following as $followed) {
factory(Relationship::class)->create([
'followed_id' => $followed,
'follower_id' => $user->id
]);
}
}
}
RelationshipSeederの登録 database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call(UsersTableSeeder::class);
$this->call(MicropostSeeder::class);
$this->call(RelationshipSeeder::class); //追加
}
}
演習 1, 2.
>>> $user = User::first()
>>> count($user->followers()->get())
=> 38
>>> count($user->following()->get())
=> 49
リスト 14.15: Usersコントローラにfollowingアクションとfollowersアクションを追加する routes/web.php
Route::get('/users/{user}/following', 'UsersController@following')->name('users.following');
Route::get('/users/{user}/followers', 'UsersController@followers')->name('users.followers');
リスト 14.16: フォロワーの統計情報を表示するパーシャル resources/views/shared/stats.blade.php
<div class="stats">
<a href="{{route('users.following', $user)}}">
<strong id="following" class="stat">
{{$user->following()->count()}}
</strong>
following
</a>
<a href="{{route('users.followers', $user)}}">
<strong id="followers" class="stat">
{{$user->followers()->count()}}
</strong>
followers
</a>
</div>
このままだとundefined userとエラーが出るため、コントローラで$userを渡す app/Http/Controllers/StaticPagesController.php
class StaticPagesController extends Controller
{
public function home()
{
// \Auth::user() ? $feed_items = \Auth::user()->microposts()->where('user_id', \Auth::id())->paginate(30) : $feed_items = [];
\Auth::user() ? $feed_items = \Auth::user()->feed()->paginate(30) : $feed_items = [];
$user = \Auth::user();
return view('static_pages.home', ['user' => $user, 'feed_items' => $feed_items]);
}
:
}
リスト 14.17: Homeページにフォロワーの統計情報を追加する resources/views/static_pages/authorize/true.blade.php
<div class="row">
<aside class="col-md-4">
<section class="user_info">
@include('shared.user_info')
</section>
<section class="stats"> //追加
@include('shared.stats') //追加
</section> //追加
<section class="micropost_form">
@include('shared.micropost_form')
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
@include('shared.feed')
</div>
</div>
リスト 14.18: Homeページのサイドバー用のSCSS resources/sass/_custom.scss
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
.stats {
overflow: auto;
margin-top: 0;
padding: 0;
a {
float: left;
padding: 0 10px;
border-left: 1px solid $gray-light;
color: gray;
&:first-child {
padding-left: 0;
border: 0;
}
&:hover {
text-decoration: none;
color: blue;
}
}
strong {
display: block;
}
}
.user_avatars {
overflow: auto;
margin-top: 10px;
.gravatar {
margin: 1px 1px;
}
a {
padding: 0;
}
}
.users.follow {
padding: 0;
}
以下コマンドを実行
yarn dev
リスト 14.19: フォロー/フォロー解除フォームのパーシャル resources/views/users/follow_form.blade.php
@auth
<div id="follow_form">
@if (Auth::user()->is_following($user))
<%= render 'unfollow' %>
@include('users.unfollow')
@else
<%= render 'follow' %>
@include('users.follow')
@endif
</div>
@endauth
リスト 14.20: Relationshipリソース用のルーティングを追加する routes/web.php
Route::resource('relationships', 'RelationshipController')->only(['store', 'destroy']);
リスト 14.21: ユーザーをフォローするフォーム resources/views/users/follow.blade.php
<form action="{{route('relationships.store')}}" method="post">
@csrf
<div><input type="hidden" name="followed_id" value="{{$user->id}}"></div>
<input type="submit" class="btn btn-primary" value="Follow">
</form>
リスト 14.22: ユーザーをフォロー解除するフォーム resources/views/users/unfollow.blade.php
<form action="{{route('relationships.destroy', $user->id)}}" method="post">
@csrf
@method('delete')
<div><input type="hidden" name="followed_id" value="{{$user->id}}"></div>
<input type="submit" class="btn" value="Unfollow">
</form>
演習 省略
14.2.3 [Following] と [Followers] ページ リスト 14.24: フォロー/フォロワーページの認可をテストする red 省略
リスト 14.25: followingアクションとfollowersアクション red app/Http/Controllers/UsersController.php
{
public function __construct()
{
$this->middleware('auth', [
'only' => ['index', 'edit', 'update', 'destroy', 'following', 'followers'] //following, followers追加
]);
: 省略
}
: 省略
public function following(User $user)
{
$title = 'Following';
$users = $user->following()->paginate(30);
return view('users.show_follow', ['title' => $title, 'users' => $users, 'user' => $user]);
}
public function followers(User $user)
{
$title = 'Followers';
$users = $user->followers()->paginate(30);
return view('users.show_follow', ['title' => $title, 'users' => $users, 'user' => $user]);
}
リスト 14.26: フォローしているユーザーとフォロワーの両方を表示するshow_followビュー green resources/views/users/show_follow.blade.php
@extends('layouts.layout')
@section('content')
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<img src="{{ gravator_for($user) }}">
<h1>{{ $user->name }}</h1>
<span>
<a href="{{route('users.show', $user)}}">view my profile</a>
</span>
<span><b>Microposts:</b> {{ $user->microposts()->count() }}</span>
</section>
<section class="stats">
@include('shared.stats')
@if(!empty($users))
<div class="user_avatars">
@foreach($users as $user)
<a href="{{route('users.show', $user)}}">
<img src="{{ gravator_for($user, ['size'=>30]) }}">
</a>
@endforeach
</div>
@endif
</section>
</aside>
<div class="col-md-8">
<h3>{{ $title }}</h3>
@if(!empty($users))
<ul class="users follow">
@each('components.user', $users, 'user')
</ul>
{{$users->links()}}
@endif
</div>
</div>
@endsection
リスト 14.27: green 省略
リスト 14.28: following/followerをテストするためのリレーションシップ用fixture 省略
リスト 14.29: following/followerページのテスト green 省略
リスト 14.30: green 省略
リスト 14.31: リレーションシップの基本的なアクセス制御に対するテスト red 省略
リスト 14.32: リレーションシップのアクセス制御 green routes/web.php
Route::resource('relationships', 'RelationshipController')->only(['store', 'destroy'])->middleware('auth');
これでも良いが、これまでauthミドルウェアを使ったルーティングをまとめることができる。 routes/web.php
Route::get('/', 'StaticPagesController@home')->name('root');
Route::get('/help', 'StaticPagesController@help')->name('help');
Route::get('/about', 'StaticPagesController@about')->name('about');
Route::get('/contact', 'StaticPagesController@contact')->name('contact');
Route::resource('users', 'UsersController')->except(['create', 'store']);
Route::get('/signup', 'Auth\RegisterController@showRegistrationForm')->name('users.signup');
Route::get('/users/{user}/following', 'UsersController@following')->name('users.following');
Route::get('/users/{user}/followers', 'UsersController@followers')->name('users.followers');
//ミドルウェアのauthを使用するグループのルーティング
Route::group(['middleware' => 'auth'], function () {
Route::resource('microposts', 'MicropostController',)->only(['store', 'destroy']);
Route::resource('relationships', 'RelationshipController')->only(['store', 'destroy']);
});
Auth::routes();
リスト 14.33: Relationshipsコントローラ app/Http/Controllers/RelationshipController.php
class RelationshipController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$user = User::find($request->followed_id);
\Auth::user()->follow($user);
return back();
}
/**
* Remove the specified resource from storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request)
{
\Auth::user()->unfollow($request->followed_id);
return back();
}
}
CSRFの対策処理 resources/views/layouts/laravel_default.blade.php
<meta name="csrf-token" content="{{csrf_token()}}"> //追加
<link rel="stylesheet" href={{ asset('/css/app.css') }}>
<script src={{'/js/app.js'}} defer></script>
リスト 14.42: ステータスフィードのテスト red 省略
リスト 14.43: red 省略
リスト 14.44: とりあえず動くフィードの実装 green リスト 14.45: green 省略
リスト 14.46: whereメソッド内の変数に、キーと値のペアを使う green 省略
リスト 14.47: フィードの最終的な実装 green app/User.php
public function feed()
{
$follower_id = \Auth::id();
$following_ids = Relationship::where('follower_id', $follower_id)->get('followed_id');
return Micropost::whereIn('user_id', $following_ids)->orWhere('user_id', $follower_id);
}
リスト 14.48: green 省略
branch: following-users