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 %>
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 ビューに留まり、エラーメッセージが表示されます。
以上、メモがてらのあれやこれでした。
optparseの使い方について
この表題を見てoptparseってなんぞや?って思われる方が多いかと思います。
そんなoptparseはどんなもの?から使い方までを紹介したいと思います。
optparseとは
Rubyの標準ライブラリの一部で、コマンドラインのオプションを取り扱うためものです。
コマンドラインのオプションというのはterminal(黒い画面)にてコマンドを打ちますよね?
例えば下記のコマンド。
ディレクトリー内にあるもの全てを表示する
というコマンドなのですが、-a
これがオプションになります。
※ちなみls
のみだとディレクトリー内にあるものを表示するというコマンドになります。
ls -a
つまりoptparseはオプションを自分で文字や内容を設定することができます。
optparseを使う流れ
- OptionParserオブジェクトoptを生成する。
- オプションを取り扱うブロックを opt に登録する。
- 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"]
が配列に格納される
参考サイト
以上、optparseはどんなもの?から使い方までを紹介でした。
WebアプリをDocker化
この記事ではWebアプリをDocker化する方法を紹介していきたいと思います。
※今回の例はrubyとrailsです。
まずはアプリケーションディレクトリ内に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は複数のコンテナを立ち上げる際にとても便利です。
下記ではWeb
とDB
のコンテナが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を変更した場合(sqlite → postgresql)は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
とは
静的サイトを簡単に提供することができるサービス
※静的サイトと動的サイトの違いとメリット
GitHub Pagesのメリット・デメリット
GitHub Pagesのメリット・デメリットについて紹介していきたいと思います
メリット
- 手軽に静的サイトを公開できる
- カスタム(独自)ドメインを使用することができる
デメリット
- 用途が限られてしまう
- privateリポジトリでは公開できない
GitHub Pagesの公開方法
それではGitHub Pagesでの公開方法を紹介します!
1.まずは下記画像のRepositories(楕円の青)をクリック
2.New(青四角)をクリック
3.各番号に合わせて内容を入力していく
3-1.Repository nameを入力
3-2.Publicを入力 ※Privateにすると公開できませんので注意して下さい。
3-3.Create repositoryを入力
リポジトリ作成後 ~ push
までの流れは割愛します。
わからない場合は下記を参照ください。
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 コマンド
以上で、パーミッションの確認、設定ができるようになります。
是非理解して使ってみましょう!