Open takanori-matsushita opened 4 years ago
リスト 13.1: Micropostモデルを生成する
php artisan make:model Micropost -a
-aオプションを付けることでfactory, migration, seeder, controllerが同時に生成される。
リスト 13.2: 自動生成されたMicropostモデル Laravelでは自動生成されないので、以下のようにuserメソッドを実装する。
app/Micropost.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Micropost extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
リスト 13.3: インデックスが付与されたMicropostのマイグレーション マイグレーションファイルのupメソッドを以下のように書き換える。
database/migrations/[timestamps]_create_microposts_table.php
public function up()
{
Schema::create('microposts', function (Blueprint $table) {
$table->id();
$table->string('content');
$table->integer('user_id');
$table->timestamps();
$table->index(['user_id', 'created_at']);
});
}
できたらマイグレーションを実行する。
php artisan migrate
演習
>>> $post = new Micropost()
=> App\Micropost {#3108}
>>> $post->user_id = 1
=> 1
>>> $post->content = 'Lorem ipsum'
=> "Lorem ipsum"
>>> $post->created_at
=> null
>>> $post->updated_at
=> null
>>> $post->user
=> App\User {#3117
id: 1,
name: "Example User",
email: "example@railstutorial.org",
email_verified_at: "2020-04-23 08:55:10",
created_at: "2020-04-23 08:47:10",
updated_at: "2020-04-23 16:15:35",
}
>>> $post->save()
=> true
>>> $post->created_at
=> Illuminate\Support\Carbon @1587700290 {#3109
date: 2020-04-24 03:51:30.0 UTC (+00:00),
timezone: "UTC",
}
>>> $post->updated_at
=> Illuminate\Support\Carbon @1587700290 {#3121
date: 2020-04-24 03:51:30.0 UTC (+00:00),
timezone: "UTC",
}
リスト 13.4: 新しいMicropostの有効性に対するテスト green 省略
リスト 13.5: マイクロポストのuser_idに対する検証 green
リクエストの作成
php artisan make:request MicropostFromRequest
app/Http/Requests/MicropostFormRequest.php rulesメソッドを以下のように編集
public function rules()
{
return [
'user_id' => 'required'
];
}
リスト 13.6: green 省略
リスト 13.7: Micropostモデルのバリデーションに対するテスト red 省略
リスト 13.8: Micropostモデルのバリデーション green app/Micropost.php
public function rules()
{
return [
'user_id' => 'required',
'content' => 'required|max:140'
];
}
リスト 13.9: green 省略
先のMicropostモデルに記述済み以下のようになっていれば良い。
リスト 13.10: マイクロポストがユーザーに所属する (belongs_to) 関連付け green
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Micropost extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
リスト 13.11: ユーザーがマイクロポストを複数所有する (has_many) 関連付け green app/User.php
public function micropost()
{
$this->hasMany('App\Micropost');
}
リスト 13.12: 慣習的に正しくマイクロポストを作成する green 省略
リスト 13.13: green 省略
演習
$user = User::find(1)
=> App\User {#3104
id: 1,
name: "Example User",
email: "example@railstutorial.org",
email_verified_at: "2020-04-23 08:55:10",
created_at: "2020-04-23 08:47:10",
updated_at: "2020-04-23 16:15:35",
}
>>> $user->micropost->content = "Lorem ipsum"=> "Lorem ipsum"
>>> $user->save()
=> true
>>> $user->micropost
=> Illuminate\Database\Eloquent\Collection {#3128
all: [
App\Micropost {#3126
id: 1,
content: "Lorem ipsum",
user_id: 1,
created_at: "2020-04-24 03:51:30",
updated_at: "2020-04-24 03:51:30",
},
],
}
>>> $user = User::find(1)
=> App\User {#3135
id: 1,
name: "Example User",
email: "example@railstutorial.org",
email_verified_at: "2020-04-23 08:55:10",
created_at: "2020-04-23 08:47:10",
updated_at: "2020-04-23 16:15:35",
}
>>> $micropost = Micropost::find(1)
=> App\Micropost {#3129
id: 1,
content: "Lorem ipsum",
user_id: 1,
created_at: "2020-04-24 03:51:30",
updated_at: "2020-04-24 03:51:30",
}
>>> $user == $micropost->user
=> true
リスト 13.14: マイクロポストの順序付けをテストする red 省略
リスト 13.15: マイクロポスト用のfixture 省略
リスト 13.16: red 省略
リスト 13.17: default_scopeでマイクロポストを順序付ける green クエリスコープ機能を利用する。グローバルスコープとローカルスコープがあるが、常に降順で取り出したいため、グローバルスコープを利用して実装する。
app/Micropost.php
:
use Illuminate\Database\Eloquent\Builder;
class Micropost extends Model
{
/**
* @return void
*/
protected static function booted()
{
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('created_at', 'desc');
});
}
}
リスト 13.18: green 省略
リスト 13.19: マイクロポストは、その所有者 (ユーザー) と一緒に破棄されることを保証する
Observerを作成する。ObserverとはEloquentのイベント発生によって発火するメソッドの集まりのこと。
php artisan make:observer UserObserver --model=User
--modelオプションは指定したモデルのObserverの雛形を作成してくれる。何らかのアクションが起こった際に、各CRUDで関連するモデルへ処理を実行する。
ここでは、Userモデルでユーザーが削除された際に、紐付いているMicropostも合わせて削除するため、以下のように編集する。
app/Observers/UserObserver.php
public function deleted(User $user)
{
$user->microposts->each(function ($post) {
$post->delete();
});
}
app/User.php Observerに記述した処理がそれぞれのCRUDで実行されるように、以下を追記する。
:
use App\Observers\UserObserver;
class User extends Authenticatable
{
:
/**
* @return void
*/
public static function booted()
{
User::observe(UserObserver::class);
}
:
}
リスト 13.20: dependent: :destroyのテスト green 省略
リスト 13.21: green 省略
演習 省略
Micropostモデル生成時に-aオプションでコントローラも生成されている。
リスト 13.22: 1つのマイクロポストを表示するパーシャル viewファイルの作成
resources/views/components/micropost.blade.php
<li id="micropost-{{ $micropost->id }}">
<a href="{{ $micropost->user->id }}">
<img src="{{ gravator_for($micropost->user, $options = ['size' => 50]) }}">
</a>
<span class="user">
<a href="{{route('users.show', $micropost->user->id)}}">{{ $micropost->user->name }}</a>
</span>
<span class="content">{{ $micropost->content }}</span>
<span class="timestamp">
Posted {{ $micropost->created_at->diffForHumans() }} ago.
</span>
</li>
以下に注目する。
Posted {{ $micropost->created_at->diffForHumans() }} ago.
->diffForHumans()
メソッドで 〇〇分前、〇〇日前のように表示することができる。
リスト 13.23: @micropostsインスタンス変数をshowアクションに追加する app/Http/Controllers/MicropostController.php
public function show(User $user)
{
$grav_url = "http://www.gravatar.com/avatar/" . md5(strtolower(trim($user->email)));
$microposts = $user->microposts()->paginate(30);
return view('users.show', ['user' => $user, 'grav_url' => $grav_url, 'microposts' => $microposts]);
}
$microposts変数は、関連付けされたデータが30件以上あれば、ページネーションとして出力されるようになる。
リスト 13.24: マイクロポストをユーザーのshowページ (プロフィール画面) に追加する resources/views/users/show.blade.php
@php
$title = $user->name
@endphp
@extends('layouts.layout')
@section('content')
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<img src={{gravator_for($user)}}>
{{$user->name}}
</h1>
</section>
</aside>
<div class="col-md-8">
@if (!empty($user->microposts)) //もしユーザーの投稿データがあれば、以下の処理を行う
<h3>Microposts ({{ $user->microposts->count() }})</h3>
<ol class="microposts">
@each('components.micropost', $microposts, 'micropost')
</ol>
{{$microposts->links()}} //ページネーションの出力
@endif
</div>
</div>
@endsection
演習 省略
リスト 13.25: サンプルデータにマイクロポストを追加する Micropostファクトリの作成 database/factories/MicropostFactory.php
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Micropost;
use Faker\Generator as Faker;
$factory->define(Micropost::class, function (Faker $faker) {
return [
'content' => $faker->sentence(20), //20単語の文章
'user_id' => rand(1, 6), //1から6までの数字をランダムで生成
'created_at' => $faker->dateTime(),
'updated_at' => $faker->dateTime()
];
});
Micropostシーダーの作成 database/seeds/MicropostSeeder.php
<?php
use Illuminate\Database\Seeder;
class MicropostSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
factory(\App\Micropost::class, 500)->create();
}
}
シーダーの登録 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); //追加
}
}
編集できたら以下のコマンドを実行
php artisan migrate:refresh --seed
リスト 13.26: マイクロポスト用のCSS (本章で利用するCSSのすべて)
resources/sass/_custom.scss
そのままコピペしてyarn dev
を実行
演習 省略
省略
リスト 13.30: マイクロポストリソースのルーティング routes/web.php
Route::resource('microposts', 'MicropostController', ['only' => ['store', 'destroy']]);
リスト 13.31: Micropostsコントローラの認可テスト red 省略 13.32 13.33も省略
リスト 13.34: Micropostsコントローラの各アクションに認可を追加する green 今回はすべてのルートに対して認可を与えたいため、ルーティングを定義したファイルに記述する。 routes/web.php
Route::resource('microposts', 'MicropostController', ['only' => ['store', 'destroy']])->middleware('auth'); // ->middleware('auth')とすることでMicropostの全アクションに対して認可処理を実行できる
リスト 13.35: green 省略
リスト 13.36: Micropostsコントローラのcreateアクション app/Http/Controllers/MicropostController.php
use App\Http\Requests\MicropostFormRequest;
:
:
public function store(MicropostFormRequest $request)
{
$micropost = new Micropost;
$form = $request->all();
unset($form['_token']);
$micropost->fill($form)->save();
session()->flash('success', 'Micropost created!');
return redirect()->route('root');
}
リスト 13.37: Homeページ (/) にマイクロポストの投稿フォームを追加する resources/views/static_pages/home.blade.php
@auth
<div class="row">
<aside class="col-md-4">
<section class="user_info">
@include('shared.user_info')
</section>
<section class="micropost_form">
@include('shared.micropost_form')
</section>
</aside>
</div>
@else
:
@endauth
リスト 13.38: サイドバーで表示するユーザー情報のパーシャル ここでもコード内でロジックが発生するため、ビューコンポーザを作成する。
app/Http/Composers/CurrentUserComposer.php
<?php
namespace App\Http\Composers;
use Illuminate\View\View;
class CurrentUserComposer
{
public function compose(View $view)
{
$view->with('current_user', \Auth::user());
}
}
サービスプロバイダの作成
php artisan make:provider CurrentUserServiceProvider
bootメソッドを以下のように定義する app/Providers/CurrentUserServiceProvider.php
public function boot()
{
View::composer(
'static_pages.home',
'App\Http\Composers\CurrentUserComposer'
);
}
作成したプロバイダーの登録 config/app.php
/*
*View Composer Service Providers...
*/
:
App\Providers\CurrentUserServiceProvider::class, //追加
],
resources/views/shared/user_info.blade.php
@php
$count = $current_user->load('microposts')->microposts()->count(); //currentにmicropostsのuser_idを紐付けてカウントする
@endphp
<a href="{{route('users.show', $current_user->id)}}">
<img src="{{gravator_for($current_user, ['size' => 50])}}">
</a>
<h1>{{ $current_user->name }}</h1>
<span>
<a href="{{ route('users.show', $auth_id) }}">view my profile</a>
</span>
@if ($count > 1)
<span>{{ $count }} microposts</span>
@else
<span>{{ $count }} micropost</span>
@endif
リスト 13.39: マイクロポスト投稿フォームのパーシャル resources/views/shared/micropost_form.blade.php
<form action="{{route('microposts.store')}}" method=post>
@csrf
@include('shared.error_messages')
<div class="field">
<input type="hidden" name="user_id" value="{{$current_user->id}}">
<textarea name="content" id="" cols="30" rows="10" placeholder="Compose new micropost...">{{old('content')}}</textarea>
<input type="submit" value="Post" class="btn btn-primary">
</div>
</form>
リスト 13.40: homeアクションにマイクロポストのインスタンス変数を追加する 実装済みのため省略
実際に投稿してみると、403 | this action is unauthorized.が表示される。 これは、storeアクションで利用しているMicropostFormRequestのauthorizeメソッドが関係している。
app/Http/Requests/MicropostFormRequest.php
public function authorize()
{
return false;
}
これを以下のように変更する。
public function authorize()
{
return true;
}
これで、無事に投稿できるようになる。
リスト 13.41 〜 13.45 省略
演習 resources/views/static_pages/home.blade.php
@php
$title = ''
@endphp
@extends('layouts.layout')
@section('content')
@auth
@include('static_pages.authorize.true')
@else
@include('static_pages.authorize.false')
@endauth
@endsection
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="micropost_form">
@include('shared.micropost_form')
</section>
</aside>
</div>
resources/views/static_pages/authorize/false.blade.php
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
sample application.
</h2>
<a href={{route('users.signup')}} class="btn btn-lg btn-primary">Sign up now!</a>
</div>
<a href="http://rubyonrails.org/'"><img src="/images/rails.png" alt="Rails logo"></a>
リスト 13.46: マイクロポストのステータスフィードを実装するための準備
リスト 13.47: homeアクションにフィードのインスタンス変数を追加する app/Http/Controllers/StaticPagesController.php
public function home()
{
\Auth::user() ? $feed_items = \Auth::user()->feed()->paginate(30) : $feed_items = [];
return view('static_pages.home', compact('feed_items'));
}
リスト 13.48: ステータスフィードのパーシャル resources/views/shared/feed.blade.php
@if (!empty($feed_items))
<ol class="microposts">
@each('components.micropost', $feed_items, 'micropost')
</ol>
{{ $feed_items->links() }}
@endif
リスト 13.49: 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="micropost_form">
@include('shared.micropost_form')
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
@include('shared.feed')
</div>
</div>
リスト 13.50: createアクションに空の@feed_itemsインスタンス変数を追加する 特にエラーが出ないため、省略
リスト 13.51: マイクロポストのパーシャルに削除リンクを追加する if文のところを追加
resources/views/components/micropost.blade.php
<li id="micropost-{{ $micropost->id }}">
<a href="{{ $micropost->user->id }}">
<img src="{{ gravator_for($micropost->user, $options = ['size' => 50]) }}">
</a>
<span class="user">
<a href="{{route('users.show', $micropost->user->id)}}">{{ $micropost->user->name }}</a>
</span>
<span class="content">{{ $micropost->content }}</span>
<span class="timestamp">
Posted {{ $micropost->created_at->diffForHumans() }} ago.
@if (Auth::id() === $micropost->user->id)
<form action="{{route('microposts.destroy', $micropost->id)}}" method="post" class="delete">
@method('delete')
@csrf
<button type="submit" onclick="return confirm('You sure?')">delete</button>
</form>
@endif
</span>
</li>
branch: user-microposts