0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel で Ruby on Rails チュートリアル【11章】メール

Last updated at Posted at 2020-11-14

環境

  • Laravel 8.6
  • PHP 8.0
  • Bootstrap 5.1

11.1 AccountActivationsリソース

11.1.1 AccountActivationsコントローラ

/routes/web.php
Route::get('account_activations/{token}/edit', [AccountActivationsController::class, 'edit'])->name('activation');

11.1.2 AccountActivationのデータモデル

/database/migrations/[timestamp]_add_activation_to_users.php
class AddActivationToUsers extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('activation_digest')->nullable();
            $table->boolean('activated')->default(false);
            $table->dateTime('activated_at')->nullable();
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('activation_digest');
            $table->dropColumn('activated');
            $table->dropColumn('activated_at');
        });
    }
}
/app/Http/Controllers/UsersController.php
    public function store(Request $request)
    {
        $request->email = Str::lower($request->email);
        $request->validate([
            "name"                  => "required|max:50",
            "email"                 => "required|max:255|email|unique:users",
            "password"              => "required|confirmed|min:6",
            "password_confirmation" => "required|min:6",
        ]);
        $user = new User;
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $activation_token = Str::random(22);
        $user->activation_digest = bcrypt($activation_token);
        $user->save();
        Auth::login($user);
        session()->flash("message", ['success' => 'Welcome to the Sample App!']);
        return redirect()->route("users.show", $user);
    }
/database/factories/UserFactory.php
$factory->define(User::class, function (Faker $faker) {
    return [
            'name'         => $this->faker->name(),
            'email'        => "example-{$this->faker->unique()->randomNumber}@railstutorial.org",
            'password'     => bcrypt("password"),
            'activated'    => true,
            'activated_at' => Carbon::now(),
    ];
});
/database/seeds/DatabaseSeeder.php
        DB::table('users')->insert([
            "name"         => "Example User",
            "email"        => "example@railstutorial.org",
            "password"     => bcrypt("foobar"),
            "admin"        => true,
            "activated"    => true,
            "activated_at" => Carbon::now(),
        ]);
/database/seeds/test/TestSeeder.php
        User::create(["name" => "Michael Example", "email" => "michael@example.com", "password" => bcrypt("password"), "admin" => true, "activated" => true, "activated_at" => Carbon::now()]);
        User::create(["name" => "Sterling Archer", "email" => "duchess@example.gov", "password" => bcrypt("password"), "activated" => true, "activated_at" => Carbon::now()]);
        User::create(["name" => "Lana Kane", "email" => "hands@example.gov", "password" => bcrypt("password"), "activated" => true, "activated_at" => Carbon::now()]);
        User::create(["name" => "Malory Archer", "email" => "boss@example.gov", "password" => bcrypt("password"), "activated" => true, "activated_at" => Carbon::now()]);

11.2 アカウント有効化のメール送信

11.2.1 送信メールのテンプレート

sail artisan make:mail AccountActivation
sail artisan make:mail PasswordReset
/resources/views/emails/account_activation.blade.php
<h1>Sample App</h1>

<p>Hi {{ $user->name }},</p>

<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>

{{ link_to_route("activation", "Activate", ["token" => $activation_token, "email" => $user->email]) }}
/app/Mail/AccountActivation.php
class AccountActivation extends Mailable
{
    use Queueable, SerializesModels;

    public $user;
    public $activation_token;

    public function __construct($user, $activation_token)
    {
        $this->user = $user;
        $this->activation_token = $activation_token;
    }

    public function build()
    {
        return $this->from("noreply@example.com")
                    ->subject("Account activation")
                    ->view('emails.account_activation');
    }
}
/app/Http/Controllers/UsersController.php
        Mail::to($user)->send(new AccountActivation($user, $activation_token));

11.2.2 送信メールのプレビュー

mailHogを使用する
https://readouble.com/laravel/8.x/ja/sail.html

11.2.3 送信メールのテスト

/tests/Unit/MailerTest.php
    protected function setUp(): void
    {
        parent::setUp();
        Artisan::call('migrate:fresh');
        $this->seed('TestSeeder');
    }

    public function testAccountActivation()
    {
        Mail::fake();

        $user = User::find(1);
        $activation_token = Str::random(22);
        Mail::to($user)->send(new AccountActivation($user, $activation_token));
        Mail::assertSent(AccountActivation::class, function ($mail) use ($user, $activation_token) {
            $mail->build();
            $this->assertEquals("Account activation", $mail->subject);
            $this->assertTrue($mail->hasTo($user->email));
            $this->assertTrue($mail->hasFrom("noreply@example.com"));
            $this->assertEquals($user->name, $mail->user->name);
            $this->assertEquals($activation_token, $mail->activation_token);
            $this->assertEquals($user->email, $mail->user->email);
            return true;
        });
    }

11.2.4 ユーザーのcreateアクションを更新

/app/Http/Controllers/UsersController.php
        $user->save();
        Mail::to($user)->send(new AccountActivation($user, $activation_token));
        session()->flash('message', ['info' => 'Please check your email to activate your account.']);
        return redirect("/");
/tests/Feature/UsersSignupTest.php
        // $response->assertViewIs("users.show");
        // $this->assertTrue(Auth::check());

11.3 アカウントを有効化する

11.3.1 authenticated?メソッドの抽象化

11.3.2 editアクションで有効化

/app/Http/Controllers/AccountActivationsController.php
class AccountActivationsController extends Controller
{
    public function edit(Request $request, $token)
    {
        $user = User::where('email', $request->email)->first();
        if ($user && !$user->activated && Hash::check($token, $user->activation_digest)) {
            $user->activated = true;
            $user->activated_at = Carbon::now();
            $user->save();
            Auth::login($user);
            session()->flash('message', ['success' => 'Account activated!']);
            return redirect()->route("users.show", $user->id);
        } else {
            session()->flash('message', ['danger' => 'Invalid activation link']);
            return redirect("/");
        }
    }
}
/app/Http/Controllers/SessionController.php
    public function store(Request $request)
    {
        $user = User::where('email', $request->email)->first();
        if ($user && Hash::check($request->password, $user->password)) {
            if ($user->activated === true) {
                Auth::login($user, $request->remember_me === "1");
                return redirect()->intended("/users/" . Auth::id());
            } else {
                session()->flash('message', ['warning' => 'Account not activated. Check your email for the activation link.']);
                return redirect("/");
            }
        } else {
            session()->flash('message', ['danger' => 'Invalid email/password combination']);
            return back()->withInput();
        }
    }

11.3.3 有効化のテストとリファクタリング

/tests/Feature/UsersSignupTest.php
class UsersSignupTest extends TestCase
{
    use RefreshDatabase;

    public function testInvalidSignup()
    {
        $this->get("signup");
        $count = User::all()->count();
        $response = $this->followingRedirects()
                        ->post(route("signup"), [
                            "name" => "",
                            "email" => "user@invalid",
                            "password" => "foo",
                            "password_confirmation" => "bar"
                        ]);
        $this->assertSame($count, User::all()->count());
        $response->assertViewIs("users.create");
        $dom = $this->dom($response->content());
        $this->assertSame(1, $dom->filter('div#error_explanation')->count());
        $this->assertSame(1, $dom->filter('div.alert-danger')->count());
    }

    public function testValidSignupWithAccountActivation()
    {
        Mail::fake();

        $this->get(route('signup'));
        $count = User::all()->count();
        $response = $this->followingRedirects()
            ->post(route('signup'), [
                'name'                  => "Example User",
                'email'                 => "user@example.com",
                'password'              => "password",
                'password_confirmation' => "password",
            ]);
        $this->assertSame($count + 1, User::all()->count());
        Mail::assertSent(AccountActivation::class, 1);
        $user = User::where("email", "user@example.com")->first();
        $activation_token = Str::random(22);
        $user->update(["activation_digest" => bcrypt($activation_token)]);
        $this->assertFalse($user->activated);

        $this->post("login", ["email" => $user->email, "password" => "password"]);
        $this->assertFalse(Auth::check());
        $this->get(route("activation", ["token" => "incalid token", "email" => $user->email]));
        $this->assertFalse(Auth::check());
        $this->get(route("activation", ["token" => $activation_token, "email" => "wrong"]));
        $this->assertFalse(Auth::check());
        $response = $this->get(route("activation", ["token" => $activation_token, "email" => $user->email]));
        $this->assertTrue(User::find($user->id)->activated);
        $response->assertRedirect(route("users.show", $user->id));
        $this->assertTrue(Auth::check());
    }
}
/app/Http/Models/User.php
    protected $casts = [
        'email_verified_at' => 'datetime',
        'admin'             => 'boolean',
        'activated'         => 'boolean',
    ];

    public function activate()
    {
        $this->activated = true;
        $this->activated_at = Carbon::now();
        $this->save();
    }

    public function sendActivateEmail($token)
    {
        Mail::to($this)->send(new AccountActivation($this, $token));
    }

課題

/app/Http/Controllers/UsersController.php
    public function index()
    {
        $users = User::where("activated", true)->paginate(30);
        return view('users.index')->with('users', $users);
    }
...
    public function show(User $user)
    {
        if ($user->activated) {
            return view('users.show')->with('user', $user);
        } else {
            return redirect("/");
        }
    }

11.4 本番環境でのメール送信

sendgridを使用

heroku addons:create sendgrid:starter
heroku config:set MAIL_HOST=smtp.sendgrid.net
heroku config:set MAIL_USERNAME={heroku config:get SENDGRID_USERNAME}
heroku config:set MAIL_PASSWORD={heroku config:get SENDGRID_PASSWORD}

11.5 最後に

https://windii.jp/backend/laravel/api-register
https://www.ritolab.com/entry/38
https://sendgrid.com/docs/for-developers/sending-email/laravel/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?