rail 7 + devise 4.9 でめっちゃ詰まった。

このページは備忘録がてら残したいと思います。

※ dockerを使ってます。
※ deviseは導入済み(下記のように)
※ docker-compose exec web rails g devise:install
※ docker-compose exec web rails g devise User
※ docker-compose exec web rails g devise:views User
※ docker-compose exec web rails g devise:controllers users

本題

Login(/sign_up)の部分でログインを行っても遷移しないというエラーに出くわした。
エラーを見たところ下記のようなエラーが出ていた。

****-web-1  | Processing by Devise::SessionsController#create as TURBO_STREAM
****-web-1  |   Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"test@gmail.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
****-web-1  |   User Load (3.0ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["email", "test@gmail.com"], ["LIMIT", 1]]
****-web-1  | Completed 401 Unauthorized in 394ms (ActiveRecord: 3.8ms | Allocations: 2821)


自分は思ったことは2つ。以下になる。

  • 401のエラーは認証のエラーなのでログイン時のパスワードやメールアドレスが間違っている
  • Processing by Devise::SessionsController#create as TURBO_STREAMとあったのでTURBO_STREAMが悪さしてる?

思ったことを1つずつ潰していこう!

ではやっていきましょう!
まず1つ目401のエラーは認証のエラーなのでログイン時のパスワードやメールアドレスが間違っている?
consoleにて下記のようにしてユーザーが存在するか確認を行います。

irb(main):003:0> user = User.find_by(email: "test@gmail.com")
  User Load (5.3ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "test@gmail.com"], ["LIMIT", 1]]
=> #<User id: 4, email: "test@gmail.com", created_at: "2023-07-10 14:51:06.906726000 +0000", updated_at: "2023-07-11 13:45:09.801442000 +0000", phone: "12345678", birthday: "20200101", name: "a">

ユーザーが登録できていることは確認できました!
では次にパスワードが問題ないか確認をします。

irb(main):004:0> user.valid_password?('*******')
=> true

*******は自分の設定したパスワードに置き換えて下さい。

パスワードにも問題は見当たらないですね。

では次!
Processing by Devise::SessionsController#create as TURBO_STREAMとあったのでTURBO_STREAMが悪さしてる?

下記のサイトは環境のバージョンは違うのですが、同様の事象が起きているサイトを見つけて、viewのページでのform_forの部分にhtml: {'data-turbo' => "false"}を追加してturboを使用しなければいいのでは。とあったので追加をしたのですが、遷移しない状態は変わらず、、、。 github-com.translate.goog

んーーーと悩み色々やってみたが、4時間経過。
下記ファイルを見て:confirmable←これ!!!!!!
悪さをしていた訳ではないですが、:confirmableが有効になっていると、ユーザーが新規登録した際、またはメールアドレスを変更した際に、自動的に確認メールが送信されこれを確認しないとログインができないと!
なんと!知識不足!!

models/user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

  validates :email, presence: true, uniqueness: true, format: { with: VALID_EMAIL_REGEX }
  validates :phone, presence: true, uniqueness: true
  validates :birthday, presence: true
end

おっとでもこのメールアドレスはテスト用で架空のアドレスなので、存在しません。
コンソールで確認メールを確認した事にしましょう!

irb(main):001:0> user = User.find_by(email: 'test@gmail.com')
  User Load (1.4ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "test@gmail.com"], ["LIMIT", 1]]
=> #<User id: 4, email: "test@gmail.com", created_at: "2023-07-10 14:51:06.906726000 +0000", updated_at: "2023-07-10 14:51:06.906726000 +0000", phone: "12345678", birthday: "20200101", name: "a">

irb(main):002:0> user.confirm
  TRANSACTION (0.5ms)  BEGIN
  User Update (1.9ms)  UPDATE "users" SET "confirmed_at" = $1, "updated_at" = $2 WHERE "users"."id" = $3  [["confirmed_at", "2023-07-11 13:45:09.800418"], ["updated_at", "2023-07-11 13:45:09.801442"], ["id", 4]]
  TRANSACTION (2.9ms)  COMMIT
=> true

irb(main):003:0> user.save
  TRANSACTION (0.8ms)  BEGIN
  User Exists? (1.4ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 AND "users"."id" != $2 LIMIT $3  [["email", "test@gmail.com"], ["id", 4], ["LIMIT", 1]]
  User Exists? (0.8ms)  SELECT 1 AS one FROM "users" WHERE "users"."phone" = $1 AND "users"."id" != $2 LIMIT $3  [["phone", "12345678"], ["id", 4], ["LIMIT", 1]]
  TRANSACTION (0.7ms)  COMMIT
=> true

これで確認メールの確認が完了!
再度ログインを行ってみると無事に遷移!!

とりあえず一見落着!!

p.s
ちなみにまだ原因はわかってないのですが、html: {'data-turbo' => "false"}を追加したままログインを行うと、問題なく指定したページに遷移し、html: {'data-turbo' => "false"}を外すとデフォルトのページ(root_path)に遷移します

railsでバリデーションエラーが表示されない

Rails 7.0.4

railsでバリデーションエラーが表示されなくて困った困った。
下記で自分の思う限りでは記載したはずなのにエラーが出ない、、、。
どうすればいいのかと2~3時間も時間を費やしてしまった。

原因はstatus: :unprocessable_entityをつけないといけないようになってるようです。
Rails 7からは追記が必要らしいです。

    def update
      if @product.update(product_params)
        redirect_to admin_products_path(@product), notice: 'Product was successfully updated.'
      else
        render :edit, status: :unprocessable_entity
      end
    end

admin/products_controller.rb

# frozen_string_literal: true

module Admin
  class ProductsController < ApplicationController
    before_action :basic_auth
    before_action :set_product, only: %i[update edit destroy]

    def index
      @products = Product.all
    end

    def new
      @product = Product.new
    end

    def update
      if @product.update(product_params)
        redirect_to admin_products_path(@product), notice: 'Product was successfully updated.'
      else
        render :edit
      end
    end

    def create
      @product = Product.new(product_params)
      @product.save ? redirect_to(admin_products_path) : render(:new)
    end

    def edit; end

    def destroy
      @product.destroy
      redirect_to admin_products_path, notice: 'Product was successfully destroyed.'
    end

    private

    def set_product
      @product = Product.find(params[:id])
    end

    def product_params
      params.require(:product).permit(:name, :description, :price, :image)
    end

    def basic_auth
      authenticate_or_request_with_http_basic do |username, password|
        username == 'admin' && password == 'pw'
      end
    end
  end
end

edit.html.erb

<section class="py-5">
  <%= form_with(model: @product, url: admin_product_path(@product), local: true) do |f| %>
    <%# <%= render partial: 'shared/error_messages', object: @product %>
    <%= render 'shared/error_messages', object: @product %>
    <div class="form-floating">
      <%= f.text_area :name, class: 'form-control', id: 'floatingName', placeholder: 'Enter product name' %>
      <%= f.label :name, 'Name', for: 'floatingName' %>
    </div>
    <div class="form-floating">
      <%= f.text_area :description, class: 'form-control', id: 'floatingDescription', placeholder: 'Enter product name' %>
      <%= f.label :description, 'Description', for: 'floatingDescription' %>
    </div>
    <div class="form-floating">
      <%= f.number_field :price, class: 'form-control', id: 'floatingPrice', placeholder: 'Enter product price' %>
      <%= f.label :price, 'Price', for: 'floatingPrice' %>
    </div>
    <div class="form-floating">
      <%= f.file_field :image, class: 'form-control', id: 'floatingImage' %>
      <%= f.label :image, 'Image', for: 'floatingImage' %>
    </div>
    <%= f.submit "更新", class: 'btn btn-primary' %>
  <% end %>
</section>

_error_messages.html.erb

<% if object.errors.any? %>
  <div class="alert alert-danger">
    <ul>
      <% object.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

qiita.com

railsで管理者画面のあれやこれ

メモと整理がてら記載します。

docker環境で実施していることを前提としています。

docker-compose exec web rails g controller admin/products index show new edit --no-helper
model

下記コマンドでモデルの作成を行う

docker-compose exec web rails g model Product name description:text price:integer

作成したファイルの中身はこんな感じ

  create_table "products", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "name"
    t.text "description"
    t.integer "price"
  end

下記コマンドを実行して上記で確認したテーブルを作成する

rails db:migrate
routes

上記実施後config/routes.rbにて下記を確認します。
※今回は下記画面のみの作成になります。

  • 商品一覧
  • 商品作成
  • 商品編集
  • 商品削除(一覧画面から削除できるように)
Rails.application.routes.draw do
  namespace :admin do
    # []内のアクション以外を使うという意味を表す
    resources :products, except: [:show]
  end

namespace :admin do ~~ endまで囲まれている部分はadmin/が付与されます
下記のようGETした際はadmin/products#xxxxがルートして呼び出されるようになります。

docker-compose exec web rails routes | grep products

admin_products         GET         /admin/products(.:format)              admin/products#index
                                      POST      /admin/products(.:format)              admin/products#create
new_admin_product  GET         /admin/products/new(.:format)      admin/products#new
edit_admin_product  GET         /admin/products/:id/edit(.:format) admin/products#edit
admin_product           PATCH    /admin/products/:id(.:format)         admin/products#update
                                     PUT         /admin/products/:id(.:format)         admin/products#update
admin_product           DELETE  /admin/products/:id(.:format)         admin/products#destroy

なぜnewとcreateの2つも必要なのか?と思ったが、 理由はnewアクションでフォームを、そのフォームをcreateでデータの保存等を行うとのことらしい。
他にもeditも同様に、editでフォームを、updateでデータの更新等を行う。

controller

adimin/products_controller.rb

# frozen_string_literal: true

module Admin
  class ProductsController < ApplicationController
    before_action :basic_auth
    before_action :set_product, only: %i[update edit destroy]

    def index
      @products = Product.all
    end

    def new
      @product = Product.new
    end

    def update
      if @product.update(product_params)
        redirect_to ({ action: :index }), notice: 'Product was successfully updated.'
      else
        render :edit, status: :unprocessable_entity
      end
    end

    def create
      @product = Product.new(product_params)
      if @product.save
        redirect_to ({ action: :index }), notice: 'Product was successfully create.'
      else
        render(:new)
      end
    end

    def edit; end

    def destroy
      @product.destroy
      redirect_to ({ action: :index }), notice: 'Product was successfully destroyed.'
    end

    private

    def set_product
      @product = Product.find(params[:id])
    end

    def product_params
      params.require(:product).permit(:name, :description, :price, :image)
    end

    def basic_auth
      authenticate_or_request_with_http_basic do |username, password|
        username == 'admin' && password == 'pw'
      end
    end
  end
end

XXXX_pathは各画面に遷移することを示す routesファイルに記載のあるやつで、xxx_pathとつければその画面に飛ぶ

before_action :set_product, only: %i[update edit destroy]を使って何度もコードを記載するのを防いでます。(DRY) after_actionもあるので良ければ調べてみよう。

編集画面だけを紹介

<section class="py-5">
  <%= form_with(model: @product, url: admin_product_path(@product), local: true) do |f| %>
    <div class="form-floating">
      <%= f.text_area :name, class: 'form-control', id: 'floatingName', placeholder: 'Enter product name' %>
      <%= f.label :name, 'Name', for: 'floatingName' %>
    </div>
    <div class="form-floating">
      <%= f.text_area :description, class: 'form-control', id: 'floatingDescription', placeholder: 'Enter product name' %>
      <%= f.label :description, 'Description', for: 'floatingDescription' %>
    </div>
    <div class="form-floating">
      <%= f.number_field :price, class: 'form-control', id: 'floatingPrice', placeholder: 'Enter product price' %>
      <%= f.label :price, 'Price', for: 'floatingPrice' %>
    </div>
    <div class="form-floating">
      <%= f.file_field :image, class: 'form-control', id: 'floatingImage' %>
      <%= f.label :image, 'Image', for: 'floatingImage' %>
    </div>
    <%= f.submit "更新", class: 'btn btn-primary' %>
  <% end %>
</section>

<%= form_with(model: @product, url: admin_product_path(@product), local: true) do |f| %>
model:にはフォームで作成または編集される具体的なインスタンスを指定(コントローラのアクション内で設定)
編集画面では、通常updateアクションが呼び出される(更新ボタン押下時)ので、url:にはそのパス(admin_product_path(@product))を指定します。

流れ的にはこんな感じ
form_withで指定されたurl: のパス(このケースでは admin_product_path(@product))に対してHTTP POST(新規作成)またはPATCH(更新)リクエストを送信

Railsのルーティングシステムは、そのリクエストを適切なコントローラとアクション(この場合は ProductsControllerのupdateアクション)にルーティング

updateアクション内で @product.update(product_params) が呼び出される
これにより、フォームから送信されたデータを使用して @product オブジェクトが更新されます。

updateメソッドが成功すれば(つまり、バリデーションにパスすれば)、 redirect_to(admin_products_path(@product)) が呼び出され、ユーザは @product のビューにリダイレクトされます。
updateメソッドが失敗した場合(つまり、バリデーションに失敗した場合)、 render(:edit) が呼び出され、ユーザは同じ edit ビューに留まり、エラーメッセージが表示されます。

qiita.com

zenn.dev

以上、メモがてらのあれやこれでした。

optparseの使い方について

この表題を見てoptparseってなんぞや?って思われる方が多いかと思います。
そんなoptparseはどんなもの?から使い方までを紹介したいと思います。

optparseとは

Ruby標準ライブラリの一部で、コマンドラインのオプションを取り扱うためものです。
コマンドラインのオプションというのはterminal(黒い画面)にてコマンドを打ちますよね?
例えば下記のコマンド。
ディレクトリー内にあるもの全てを表示するというコマンドなのですが、-aこれがオプションになります。
※ちなみlsのみだとディレクトリー内にあるものを表示するというコマンドになります。

ls -a

つまりoptparseはオプションを自分で文字や内容を設定することができます。

optparseを使う流れ
  1. OptionParserオブジェクトoptを生成する。
  2. オプションを取り扱うブロックを opt に登録する。
  3. opt.parse(ARGV) でコマンドラインを実際に parse する。

わかりやすく言うと下記になります。
オブジェクトというのは設計図を元に作成したもののことで
1. 設計図をもとにoptを作る
2. どのようなオプションを作るかブロックに登録、記載する
※ブロックについては上記のリンクにて!
3. opt.parse!を最後に記載しよう!

optparseの使い方
1. 設計図をもとにoptを作る
opt = OptionParser.new

2. どのようなオプションを作るかブロックに登録、記載する
opt.on('short', 'long') do |sample|
# 条件など
end

3. `opt.parse!`を最後に記載しよう!
opt.parse!(ARGV)


1文ずつ具体的に説明していきます!

opt = OptionParser.new

OptionParserオブジェクト(設計図)にてoptを作成

opt.on('short', 'long') do |sample|
# 条件など
end

上記コードでのshortは短い形式のオプションを表し、longは長い形式のオプションを表します。
下記に具体例を出します。

opt.on('-m M', '--month=M') do |sample|

-m M: -mは短い形式のオプションを表し、オプションの引数をM
--month=M: --monthは長い形式のオプションを表し、オプションの引数をM
※上記はshortかlongのどちらかを一方のみでも問題ないです。
ここでの引数Mコマンドライン上で入力をされたものが入ります。
ruby sample.rb -m 5と入力された場合であればMには5が入ります。

opt.parse!(ARGV)

解析・実行を行う。
ARGVはプログラムが実行された際に与えられたコマンドライン引数が配列として格納されます。
例えばterminalでruby sample.rb -m 5とコマンドを入力した場合、["-m", "5"]が配列に格納される

参考サイト

docs.ruby-lang.org

qiita.com

qiita.com

以上、optparseはどんなもの?から使い方までを紹介でした。

WebアプリをDocker化

この記事ではWebアプリをDocker化する方法を紹介していきたいと思います。
※今回の例はrubyrailsです。

まずはアプリケーションディレクトリ内にDockerfileを作成します Dockerfile内にはどういう内容でコンテナを作成するかを記載していきます。
既存プロジェクトがある場合はバージョン等は合わせて下さい。

FROM ruby:3.0.2

RUN mkdir /rails-app
WORKDIR /rails-app
COPY Gemfile /rails-app/Gemfile
COPY Gemfile.lock /rails-app/Gemfile.lock
RUN bundle install

COPY . /rails-app


次にdocker-compose.ymlを作成します
docker-composeは複数のコンテナを立ち上げる際にとても便利です。
下記ではWebDBのコンテナが2つ立ち上がるようになっています。

version: '3'

volumes:
  db-data:

services:
  db:
    image: postgres
    volumes:
      - 'db-data:/var/lib/postgresql/data'

  web:
    build: .

    command: bundle exec rails s -p 3000 -b '0.0.0.0'

    ports:
      - '3000:3000'

    # ホストのファイルシステムとコンテナのファイルシステムを同期
    volumes:
      - '.:/rails-app'

    environment:
      - 'DATABASE_PASSWORD=test'

    tty: true
    stdin_open: true

    depends_on:
      - db

余談ですが、DBを変更した場合(sqlitepostgresql)はdatabase関連のファイルを変更するように注意して下さい。


Dockerfileに基づいてコンテナイメージが作成されます

docker-compose build

ビルドされたイメージを使用してコンテナを起動

docker-compose up

DBの生成と初期化を行います

docker-compose run web rake db:create db:migrate

後はlocalhost:3000にアクセスすれば表示さればOK
railsはポート番号は3000なので3000になっていますが他の言語では違うので注意


以上でアプリのDocker化が完了です!

GitHub Pagesについて

GitHub Pagesとは

GitHub Pagesとは
静的サイトを簡単に提供することができるサービス
※静的サイトと動的サイトの違いとメリット

www.xserver.ne.jp


GitHub Pagesのメリット・デメリット

GitHub Pagesのメリット・デメリットについて紹介していきたいと思います
メリット

デメリット


GitHub Pagesの公開方法

それではGitHub Pagesでの公開方法を紹介します!
1.まずは下記画像のRepositories(楕円の青)をクリック

2.New(青四角)をクリック

3.各番号に合わせて内容を入力していく

3-1.Repository nameを入力

3-2.Publicを入力 ※Privateにすると公開できませんので注意して下さい。

3-3.Create repositoryを入力

リポジトリ作成後 ~ pushまでの流れは割愛します。
わからない場合は下記を参照ください。

web-engineer-wiki.com

4.Settings(青四角)をクリック
※皆さんの画面とは少々違う部分があると思いますが、特に問題はありません。

5.Pages(青四角)をクリック

6.各番号に合わせて内容を変更していく

6-1.Noneをmaster or mainに変更

6-2.Saveを押下

7.青四角が出てくれば完成です!
※青四角が出るまでに時間が掛かる場合があります。

以上、GitHub Pagesの紹介 ~ 公開方法でした!

【Linux】パーミッションの仕組み・設定

ここではパーミッションの仕組みを説明したいと思います。
パーミッションの仕組みを理解できればファイルの権限や管理、管理者でのコマンドの実行ができます。
また下記のなんだこれ。と思わせるようなコマンドも理解できます!

$ chmod 777 test.txt


オーナーとグループについて

まずはファイルのオーナーとグループについて理解していきたいと思います! Linuxでファイルのオーナーは下記の意味合いを持ちます。

オーナー: ファイルの所有者
グループ: ファイルの所有グループ

ユーザーをグループに所属させ権限を与えることによって、複数ユーザーの権限を一括で管理できるようになります。
下記のコマンドでファイルのオーナーとグループが確認ができます。

$ ls -l 確認したいファイル
>> -rw-r--r--@ 1 test  staff  755  5 27 23:34 XXX.txt

オーナー: test
グループ: staff


パーミッションの確認

パーミッションとは、、
ファイルやディレクトリを誰がどのように操作できるかを定めたものになります
確認コマンドはオーナーとグループを確認したコマンドと同じになります。

$ ls -l 確認したいファイル
>> -rw-r--r--@ 1 test  staff  755  5 27 23:34 XXX.txt

ファイルタイプ:  -
オーナーのパーミッション: rw-
グループのパーミッション:  r--
その他のユーザーのパーミッション: r--

記号の意味

記号 内容
r read (読み取り)
w write (書き込み)
x execute (実行)

上記のコマンド結果の場合だと下記になります

記号 読み取り 書き込み 実行
オーナー ⚪︎ ⚪︎ ×
グループ所属のユーザー ⚪︎ × ×
その他ユーザー ⚪︎ × ×

余談なのですがファイルタイプには色々あり、こんな物もあるのか〜程度で覚えておくといいかもしれません。

ファイルタイプ 内容
- 通常ファイル(テキスト, バイナリーファイルなど)
d ディレクト
b ブロックデバイス
c キャラクタデバイス
p パイプ
s ソケット

ブロックデバイス: 主にストレージデバイス(HDD、SSDなど)へのアクセスを表現する特殊なファイル
キャラクターデバイス: 主にキーボードやマウスなどの入力デバイスや、プリンタなどの出力デバイスへのアクセスを表現する特殊なファイル
パイプ: 一つのプログラムから別のプログラムへデータを渡すための通信路を表現する特殊なファイル
ソケット: ネットワーク通信やシステム内部のプロセス間通信(IPC)に使われる特殊なファイル

パーミッションの設定

それではファイルやディレクトリーのパーミッションを設定、設定変更をみていきましょう。

$ chmod [ugoa] [+-=] [rwx] ファイル名
記号 内容
u 所有者の権限
g グループの権限
o その他ユーザーの権限
a 全ユーザーの権限
記号 内容
+ 権限の追加
- 権限の削除
= 記述した権限にする

ちょっとわかりづらいですが、chmodコマンドは数値でも指定できるので紹介します。

chmod 8進数の数値 ファイル名
パーミッション 数値
r 4
w 2
x 1

下記でパーミッションを数値に変換した場合は、

>> -rw-r--r--
          ↓
>> chmod 0+4+2 4 4
          ↓
>> chmod 644 ファイル名


管理者権限を持つ特別なユーザー(スーパーユーザー)
sudo コマンド


以上で、パーミッションの確認、設定ができるようになります。
是非理解して使ってみましょう!