バーテンダーエンジニア

元フレアバーテンダーからエンジニアに転職。未経験からのエンジニア転職経験談・実務で感じたこと・個人開発で学んだ内容・日常話などマイペースに発信していきます。仕事ではphp、Laravel、Vue.js、Angular辺りを触ってます。趣味は筋トレ!!ソムリエの資格も持ってます^^

LaravelApiでログイン実装の際に500エラーが返ってきた...

エラー発生まで

Larvavelのデフォルトで用意されている認証機能を利用してAPIのユーザー認証機能を作成していました。
テストの勉強も兼ねて、テストコードを実行すると以下のエラーが発生しました。

-テストコード-
// ログイン可能を確認するテスト
    /**
     * @test
     */
    public function 登録済みのユーザーを認証して返却()
    {
        $response = $this->json('POST', route('login'), [
            'email'    => $this->user->email,
            'password' => 'password',
        ]);

        $response->assertStatus(200)
                 ->assertJson(['name' => $this->user->name]);

        $this->assertAuthenticatedAs($this->user);
    }

-エラー-
┐
   ├ Expected status code 200 but received 500.
   ├ Failed asserting that false is true.      

上記のテストコードは、認証機能が正常に動作するかを確認するものです。

エラーの原因

今回は、API側のrouteを「routes/api.php」に記載しました。Laravelのデフォルトの認証機能には、essionを使用する記載があります。
RoutingのapiミドルウェアではSessionStartの記載がないため、Sessionを上手く利用できずエラーが発生したようです。

-Kernel.php-

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];


解決方法

apiミドルウェアにSessionを使用するコードを追記する方法でも動作はすると思うのですが、
app/Providers/RouteServiceProvider.php を以下の通りに編集する事で対応が可能になります。

-app/Providers/RouteServiceProvider.php

protected function mapApiRoutes()
{
    Route::prefix('api')
         ->middleware('web') //  'api' → 'web' に変更
         ->namespace($this->namespace)
         ->group(base_path('routes/api.php'));
}

RouteServiceProvider はアプリケーション起動時にルート定義を読み込むためのクラスです。
今回の対応で、routes/api.php に記述したルート定義に適用されるミドルウェアグループをweb に変更する事でSessionが明記されるのでエラーを解決する事ができます。

laravel-enumを利用してみた

はじめに

こんにちは。関西でWebエンジニアをしているYKIです。
今回、laravelのマイグレーションenumを利用したい場面があり、少々調べたので自身の備忘録としてまとめて置きたいと思います。

enum型とは...

enum型は列挙型とも呼ばれ、複数の定数を一つの型で統一して管理できるものです。今回はenumをLaravelで利用していきます。

導入

Laravel本体ではサポートされていないようなのでComposer経由でパッケージを入れていきます。
今回は、"laravel-emun" を利用します。
では早速、下記コマンドでインストールを行います。

$ composer require bensampo/laravel-enum


実装

enumクラスを作成していきます。今回は性別のデータを例にします。

$ php artisan make:enum SexType
→ Enum created successfully.

デフォルトでは、App\Enums ディレクトリの下にphpファイルが作成されるのでこちらを編集していきます。

<?php

namespace App\Enums;
 
use BenSampo\Enum\Contracts\LocalizedEnum;
use BenSampo\Enum\Enum;
 
final class SexType extends Enum implements LocalizedEnum
{
    const OTHER = 0;
    const MALE = 1;
    const FEMALE = 2;
}

resources/lang/ja/enum.phpファイルを用意し、Enumの値の説明を日本語化します。

<?php

return [
        SexType::class => [
            SexType::OTHER => '未設定',
            SexType::MALE => '男性',
            SexType::FEMALE => '女性',
    ],
];


今回はマイグレーションで利用していきます。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Enums\SexType;
 
class AddColumnPaymentType extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('orders', function (Blueprint $table) {
            $table->enum('sex_type', SexType::getValues()) ←こちらで利用
                ->default(PaymentType::OTHER); ←デフォルト値をOTHERに設定
        });
    }
}

まとめ

ベタ書きするよりもすっきりと記述する事ができました。
今回初めての利用になりますので、補足等あればコメントいただけると幸いです。

参考リンク

公式はこちら github.com

Target class [url] does not exist. エラーに悩まされた話

はじめに

エンジニア転職を目指してRailsで作成したポートフォリオには自動テストを組み込んでいましたが、現在メインで使用しているLaravelでは、案件として自動テストを導入した経験がありませんでしたので、今回自作アプリに導入を試みてみました。
意気揚々とテストコードをググりながら書き出したのですが、すぐに壁にぶち当たりました。

発生したエラー

ユーザーの新規登録についてのテストコードを記載し実行すると下記のようなエラーが発生。

<?php
// ユーザー新規作成のテストコード
namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class UserRegisterTest extends TestCase
{
    use RefreshDatabase;

    public function setUp(): void
    {
        $this->params = [
            'name'                  => 'test_user',
            'email'                 => 'test@example.com',
            'password'              => 'password',
            'password_confirmation' => 'password'
        ];
    }

    /**
     * @test
     */
    public function 新規ユーザーを作成()
    {
        $response = $this->json('POST', route('register'), $this->params);

        $user = User::first();
        $this->assertEquals($this->params['name'], $user->name);

        $response->assertStatus(201);
        $response->assertJson([
            'name'  => $user->name
        ]);
    }
}
// 発生したエラー
lluminate\Contracts\Container\BindingResolutionException: Target class [url] does not exist.


エラー原因の調査

Target class [url] does not exist.エラーなので、routeの定義が上手く行ってないのかと考えphp artisan route:listコマンドで調査も問題なし。
authコマンドでファイル等新規で作成したので、composer dump-autoloadが必要なのかと思い実行も変更なし...。
ここまでの時点でテストコード側に問題があると考えました。

エラー原因の特定

テストコードでpostで投げるデータをsetUp()の中で定義しています。そこに問題がありました。各テストメソッドの実行前に呼ばれるメソッドなのですが、 このsetUp()を利用する際は、関数の一番上にparent::setUp()を記述する必要があります。

public function setUp(): void
    {
        parent::setUp(); ←追記

        $this->params = [
            'name'                  => 'test_user',
            'email'                 => 'test@example.com',
            'password'              => 'password',
            'password_confirmation' => 'password'
        ];
    }

上記のように修正を加える事でクリア!!
単純な事でしたが結構時間をもっていかれました。。

Railsで関連モデルを同時に更新する方法

はじめに

このブログは、下記ブログの続編になります。

ykichannel.hatenablog.jp こちらの記事では、userモデルとprofileモデルを1対1で関連づける方法を紹介しました。
今回は、関連づけたモデルを同時に更新する方法を備忘録としてまとめておきたいと思います。

前提条件

userモデルとprofileモデルは既に1対1の関連付けがされている事とします。(関連づけの方法がわからない場合は過去記事を参照ください)
開発環境は以下の通りです。

Mac OS Mojave 10.14.5
Ruby 2.5.3
Rails 5.2.3


accepts_nested_attributes_forを追加する

Railsが標準で提供している、ActiveRecordのメソッドの一つであるaccepts_nested_attributes_forを追加していきます。
これはモデル同士が関連付けられている時に、ネストさせることで一度にまとめてレコードの更新することを可能にします。

-user.rb
class User < ApplicationRecord
  has_one :profile, dependent: :destroy  
  accepts_nested_attributes_for :profile  
end  


フォームを作成する

フォームの作成には fields_forを利用する事でform_with内で異なるモデルを編集できるようにしていきます。

-views/users/edit.html.erb
<%= form_with model: @user, local: true do |f| %>
    <div class='form-group'>
      <%= f.label :name %>
      <%= f.text_field :name %>
    </div>
    <%= f.fields_for :profile, @user.profile || Profile.new do |pf| %>
      <div class='form-group'>
        <%= pf.label :user_profile %>
        <%= pf.text_area :user_profile %>
      </div>
    <% end %>
    <%= f.submit '更新する %>
  <% end %>


コントローラーの実装

ストロングパラメータを以下のように設定することでuser_profileの入力を渡すことが可能になります。

-controllers/users_controller.rb
  def user_update_params
      params.require(:user).permit(:name, profile_attributes: [:user_profile])
  end

あとは通常のアップデートアクションを実装することで同時に関連モデルuser.rbとprofile.rbを更新することが可能になります。

Railsコンソールのsandboxモード

お知らせ

ブログを移行しました。
本記事の内容は移行後のブログに記載しております。
申し訳ありませんが以下リンクより確認いただけますようよろしくお願いします。

sommelierengineer.hatenablog.com

form_withについて

はじめに


Railsでフォームを作成する際に使用するform_withについて調べてみたので備忘録として残しておきます。

form_withとは...


Rails5.1から追加されたヘルパーメソッドで、以前はフォームを作成したい時に関連したモデルがない場合はform_tag、モデルがある場合はform_forが利用されていました。今後は、form_withが主に利用されていくようです。

form_withの使い方

関連モデルのある場合
<%= form_with model: @user, local: true do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

オプションに、local: tureと入れる事でフォームのリモート + unobtrusive XHR送信が無効になります。

関連モデルのない場合(urlを指定する場合)
<%= form_with url: users_path, local: true do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

url:でフォームの送信先URLを指定しています。:urlではなく、:scopeとする事で現在のURLにフォームを送信する事も可能です。

最後に


form_withには、この他にも様々なオプションがあるので、今後も利用した際には続編としてまとめていきたいと思っています。

参考

Rails 5.1's form_with vs. form_tag vs. form_for – Patrik on Rails

(Ruby on Rails)Fakerを使ってダミーデータを投入する方法

はじめに


Railsでオリジナルアプリを作成中のYKIです。今回アプリケーションの表示などを確認する為に、ダミーデータを投入しようと思いFakerというgemを利用したのでその内容を備忘録として残しておきます!

Fakerのインストール


まずは、gemをインストールしていきます。

-Gemfile
group :development, :test do
...
  gem 'faker'
end

-ターミナル
$ bundle install

これでインストールは完了したので、早速ダミーデータを設定していきます。

seed.rbに記述する


今回は、Usersテーブルに50件のダミーデータを投入していきます。 Usersテーブルにはname, email, passwordを設定していく事にします。

-db/seeds.rb

50.times do |n|
  name = Faker::Name.name
  email = Faker::Internet.email
  password = "password"

User.create!(name: name, 
                      email: email,
                      password: password, 
                      password_confirmation: password)
end

Fakerで生成できるものはFakerのREADMEに記載されています。

ダミーデータを投入する


最後にターミナルで投入用のコマンドを入力します。

$ bundle exec rails db:seed

これで無事にダミーデータを投入する事に成功しました。