- 投稿日:2020-12-15T23:25:05+09:00
RailsのFormオブジェクトをついに実装した
前書き
モノリシックなRailsアプリで、ActiveRecordに紐づかないモデルに対するフォームは、Form Objectを使えば綺麗に書けるらしい。- というのは前から知っていたが、実際自分の手で実装したことはなかった。
- この度、自分の手で実装する機会が業務で得られたので、記録を残す。
- 公表して問題ない程度に実装をぼやかしているので、その過程でタイポなどがあるかもしれないです。ご了承ください。
作りたいもの
- アプリへのpush通知を送ってくれるページを管理者用のページとして追加したい。
Formオブジェクトを使わない実装
- まずはFormオブジェクトを使わずに以下のように実装した。
config.rbnamespace :admin do resources :push_notifications, only: [:new, :create] endapp/controllers/admin/push_notifications_controller.rbclass Admin::PushNotificationsController < Admin::BaseController def new @demo_form = Admin::PushNotificationDemoForm.new end def create @demo_form = Admin::PushNotificationDemoForm.new( # user_idが空文字あるいはintに変換不可能な文字列の場合エラーが吐かれるが、formでf.number_fieldを指定しているので良しとする user_id: Integer(demo_form_params['user_id']), title: demo_form_params['title'], body: demo_form_params['body'], url: demo_form_params['url'], ) if @demo_form.save flash[:notice] = "ユーザー#{@demo_form.user_id}の#{Rails.env}アプリへのプッシュ通知が送信されました。" redirect_to new_admin_push_notification_path else flash.now[:alert] = @demo_form.errors.full_messages.join(" / ") return render :new end end private def demo_form_params params.require(:admin_push_notification_demo_form).permit(:user_id, :title, :body, :url) end endapp/views/admin/new.html.haml%h1= "push通知のテスト送信" = form_with(url: admin_push_notifications_path, local: true) do |f| .div = f.label :user_id, 'User ID (必須)' = f.number_field :user_id, autofocus: true, required: true .div = f.label :title, 'Title: (必須)' = f.text_area :title, autofocus: true, required: true .div = f.label :body, 'Body' = f.text_area :body, autofocus: true .div = f.label :url, 'URL' = f.text_field :url, autofocus: true .div = f.submit("送信")
- ご覧の通り、やりたい処理``以前に、Controller内で行われているバリデーションの連発が悪い意味で印象的です。
これをFormオブジェクトを使って実装する
config.rbnamespace :admin do resources :push_notifications, only: [:new, :create] endapp/controllers/admin/push_notifications_controller.rbclass Admin::PushNotificationsController < Admin::BaseController def new @demo_form = Admin::PushNotificationDemoForm.new render 'new' end def create @demo_form = Admin::PushNotificationDemoForm.new(demo_form_params) if @demo_form.save flash[:notice] = "ユーザー#{@demo_form.user_id}のへプッシュ通知が送信されました。" redirect_to new_admin_push_notification_path else flash.now[:alert] = @demo_form.errors.full_messages.join(" / ") return render :new end end private def demo_form_params params.require(:admin_push_notification_demo_form).permit(:user_id, :title, :body, :url) end endapp/forms/push_notification_demo_form.rbclass Admin::PushNotificationDemoForm include ActiveModel::Model validates :user_id, presence: true validates :title, presence: true validate :is_valid_user_id_and_has_individual_job def save return false if invalid? VisitNotificationService.new.notify( user_job_id: @job.id, user_id: @user_id, push_notification_content: VisitNotificationService.build_push_notification( title: @title, body: @body, url: @url, ) ) return true end private def is_valid_user_id_and_has_individual_job user = User.find_by(id: @user_id) unless user errors.add(:user_id, 'が無効です。') return false end @job = user.individual_job unless @job errors.add(:base, "指定されたUser(#{@user_id})に紐づくIndividualJobが存在しません。") return false end true end endapp/views/admin/new.html.haml%h1= "push通知のテスト送信" = form_for(@demo_form, url: admin_push_notifications_path, local: true) do |f| .div = f.label :user_id, 'User ID (必須)' = f.number_field :user_id, autofocus: true, required: true .div = f.label :title, 'Title: (必須)' = f.text_area :title, autofocus: true, required: true .div = f.label :body, 'Body' = f.text_area :body, autofocus: true .div = f.label :url, 'URL' = f.text_field :url, autofocus: true .div = f.submit("送信")
ご覧の通り、バリデーション(要はビジネスロジック)がformオブジェクトの中に押し込めれたために非常に綺麗に実装できます。
自分の今回の実装では、form_withで指定されるモデルとurlが異なっています。
また、エラーメッセージを(active record同様に)formオブジェクトのインスタンス変数であるerrors内に収納できるのも便利です。
参考
- 投稿日:2020-12-15T23:05:17+09:00
[Rails] Gemが正しくインストールされているか確認する方法
前提
# bundle install実行後 Bundle complete! 20 Gemfile dependencies, 83 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed.このように表示された場合、
「Gemfileに記述してあるGemは全て正しくインストールされている」
状態です。一文字でもミスがあれば、エラーメッセージを返してくれます。
確認方法①
"bundle info Gem名"コマンド
・Gem名のGem情報が表示される
確認方法②
"Gemfile.look"を確認
・"Gemfile.look"は「インストールされているGemの名前とVersionが記載されているファイル」
- 投稿日:2020-12-15T22:32:43+09:00
【Rails】ハッシュタグ機能を実装
概要
ハッシュタグ機能を実装する方法をまとめます。
参照
https://glodia.jp/blog/3936/
https://qiita.com/Naoki1126/items/4ea584a4c149d3ad5472これら2つの記事の情報を組み合わせて実装しました。
ありがとうございます。完成イメージ
今回は、写真投稿アプリを題材に、写真のキャプションにハッシュタグを導入する方法を説明します。
写真詳細画面のキャプションにリンク付きハッシュタグが記載され、クリックするとそのハッシュタグのページが表示される。
開発環境
- macOS Catalina 10.15.7
- ruby 2.6.5
- Rails 6.0.3.4
実装の流れ
- 各種モデルとマイグレーションファイルを準備
- ハッシュタグ保存・更新アクションをモデルに追加
- ルーティングを設定
- ヘルパーメソッドを作成
- コントローラーにhashtagアクションを作成
- ビューの編集
今回のコード
コードは、必要な部分以外は省略して記載します。
1. 各種モデルとマイグレーションファイルを準備
hashtagモデルを作成
%rails g model hashtag中間テーブルを作成
%rails g model photo_hashtag_relationマイグレーションファイルを編集
テーブルのカラムを設定します。
create_hashtags.rbclass CreateHashtags < ActiveRecord::Migration[6.0] def change create_table :hashtags do |t| t.string :hashname t.timestamps end add_index :hashtags, :hashname, unique: true end endcreate_photo_hashtag_relations.rbclass CreatePhotoHashtagRelations < ActiveRecord::Migration[6.0] def change create_table :photo_hashtag_relations do |t| t.references :photo, index: true, foreign_key: true t.references :hashtag, index: true, foreign_key: true t.timestamps end end endモデルを編集
バリデーションとアソシエーションを設定します。
hashtag.rbclass Hashtag < ApplicationRecord validates :hashname, presence: true, length: { maximum:99} has_many :photo_hashtag_relations has_many :photos, through: :photo_hashtag_relations endphoto_hashtag_relation.rbclass PhotoHashtagRelation < ApplicationRecord belongs_to :photo belongs_to :hashtag with_options presence: true do validates :photo_id validates :hashtag_id end endマイグレート
%rails db:migrate2. ハッシュタグ保存・更新アクションをモデルに追加
photoモデルに以下のコードを追加します。
これで、写真投稿(create)、編集(update)時にハッシュタグがhashtagsテーブルに保存されます。photo.rb# 省略 has_many :photo_hashtag_relations has_many :hashtags, through: :photo_hashtag_relations . . # 省略 . . #DBへのコミット直前に実施する after_create do photo = Photo.find_by(id: self.id) hashtags = self.caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/) photo.hashtags = [] hashtags.uniq.map do |hashtag| #ハッシュタグは先頭の'#'を外した上で保存 tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#')) photo.hashtags << tag end end before_update do photo = Photo.find_by(id: self.id) photo.hashtags.clear hashtags = self.caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/) hashtags.uniq.map do |hashtag| tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#')) photo.hashtags << tag end end endルーティングを設定
photosコントローラーにhashtagアクションを定義し、getで各ハッシュタグのページを表示させます。
URLは末尾にハッシュタグ名が来るようにしました。
:nameに入る値はこの後ヘルパーメソッドで設定します。例えば、#カメというハッシュタグだとURLは
.../photo/hashtag/カメ
となります。route.rbget '/photo/hashtag/:name', to: "photos#hashtag"ヘルパーメソッドを作成
photos_helper.rbに以下のコードを記載します。
photos_helper.rbファイルがない場合は自分で作成します。helperについては、https://www.sejuku.net/blog/28563 などを参照
このヘルパーメソッド使用により、リンク付きのハッシュタグが入ったキャプションが作成されます。
コード内では、ハッシュタグ名が末尾に入ったURLが作成され、ハッシュタグクリック時のリンク先として設定されています。
ハッシュタグ名の前のURLには、先程route.rbに書いたURLを書き込みます。photos_helper.rbmodule PhotosHelper def render_with_hashtags(caption) caption.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/){|word| link_to word, "/photo/hashtag/#{word.delete("#")}"}.html_safe end endコントローラーにhashtagアクションを作成
ハッシュタグに紐付いた写真を@photosに代入し、hashtagビューで使用し表示させます。
photos_controller.rbdef hashtag @user = current_user @tag = Hashtag.find_by(hashname: params[:name]) @photos = @tag.photos endビューの編集
先程作成したヘルパーメソッド
render_with_hashtagsを記載することにより、リンク付きハッシュタグが記載されたキャプションを写真詳細画面に表示させます。show.html.erb# 省略 <%= render_with_hashtags(@photo.caption) %>ハッシュタグ画面では、hashtagアクションで設定した
@photosを取り込み、ハッシュタグに紐づく写真を1枚ずつ表示させます。hashtag.html.erb<h2>ハッシュタグ #<%= @tag.hashname %></h2> <ul> <% @photos.each do |photo| %> <li> <%= link_to photo_path(photo.id) do %> <%= image_tag photo.image.variant(gravity: :center, resize:"640x640^", crop:"640x640+0+0"), if photo.image.attached? %> <% end %> </li> <% end %> </ul>おわりに
以上が、今回行ったハッシュタグ機能実装の方法です。
意味が理解できていないコードが多々ありますが、追々理解していければと思います。初学者なので間違いがありましたら、ご指摘いただきたいですm(_ _)m
- 投稿日:2020-12-15T22:00:14+09:00
ポートフォリオ開発まとめ
これまで、2週間かけて取り組んだポートフォリオの要点をまとめたので、アウトプットしたいと思います。
間違いがあれば、ご指摘いただければと思います。①要件定義
https://qiita.com/by-miwa30/items/93c4fc92b85e034b75cd②README
https://qiita.com/by-miwa30/items/d7e0395c8c4a13dcff6f③アプリの起動
https://qiita.com/by-miwa30/items/fbdaee77efaea6b6df11④ユーザー管理機能を実装
https://qiita.com/by-miwa30/items/4d868f447bfe7ce040f1⑤ストロングパラメーター
https://qiita.com/by-miwa30/items/fc761332dd0de9ef5bc2⑥新規登録
https://qiita.com/by-miwa30/items/65e175ee8c86e05c83ca⑦アプリ投稿
https://qiita.com/by-miwa30/items/dbdcccd494b33c1e713d⑧アプリの詳細ページ
https://qiita.com/by-miwa30/items/d01dd9a6ca68063affcd⑨コメント機能
https://qiita.com/by-miwa30/items/a97cebfb6f4617b7a275⑩ユーザー詳細ページ
https://qiita.com/by-miwa30/items/e78811610bfdeac8d735⑪AWSのアカウント作成
https://qiita.com/by-miwa30/items/ca356f6c021eb14fcfef⑫S3で保存先を用意する
https://qiita.com/by-miwa30/items/dc068e273d8e1d0c1764⑬EC2の初期設定
https://qiita.com/by-miwa30/items/09c972145fea0e919046⑭本番環境でデータベースを作成
https://qiita.com/by-miwa30/items/b1a73dbbde0f1bbcfa74⑮EC2のRailsを起動しよう(手動デプロイ)
https://qiita.com/by-miwa30/items/dd455dcb73d03e19012e⑯環境変数の設定
https://qiita.com/by-miwa30/items/924a653faef7a03eba36⑰本番環境Railsの起動エラー(手動)
https://qiita.com/by-miwa30/items/d7a37a9deccbe76c047e⑱Webサーバーの設定
https://qiita.com/by-miwa30/items/31251da3c26a8129aa60⑲デプロイを自動化
https://qiita.com/by-miwa30/items/d4b7b1fa3601bf1185ba⑳IPアドレスにアクセスエラー(自動デプロイ
https://qiita.com/by-miwa30/items/fbc620dfe6e71d4a417c
- 投稿日:2020-12-15T22:00:14+09:00
ポートフォリオまとめ
これまで、2週間かけて取り組んだポートフォリオの要点をまとめたので、アウトプットしたいと思います。
間違いがあれば、ご指摘いただければと思います。①要件定義
https://qiita.com/by-miwa30/items/93c4fc92b85e034b75cd②README
https://qiita.com/by-miwa30/items/d7e0395c8c4a13dcff6f③アプリの起動
https://qiita.com/by-miwa30/items/fbdaee77efaea6b6df11④ユーザー管理機能を実装
https://qiita.com/by-miwa30/items/4d868f447bfe7ce040f1⑤ストロングパラメーター
https://qiita.com/by-miwa30/items/fc761332dd0de9ef5bc2⑥新規登録
https://qiita.com/by-miwa30/items/65e175ee8c86e05c83ca⑦アプリ投稿
https://qiita.com/by-miwa30/items/dbdcccd494b33c1e713d⑧アプリの詳細ページ
https://qiita.com/by-miwa30/items/d01dd9a6ca68063affcd⑨コメント機能
https://qiita.com/by-miwa30/items/a97cebfb6f4617b7a275⑩ユーザー詳細ページ
https://qiita.com/by-miwa30/items/e78811610bfdeac8d735⑪AWSのアカウント作成
https://qiita.com/by-miwa30/items/ca356f6c021eb14fcfef⑫S3で保存先を用意する
https://qiita.com/by-miwa30/items/dc068e273d8e1d0c1764⑬EC2の初期設定
https://qiita.com/by-miwa30/items/09c972145fea0e919046⑭本番環境でデータベースを作成
https://qiita.com/by-miwa30/items/b1a73dbbde0f1bbcfa74⑮EC2のRailsを起動しよう(手動デプロイ)
https://qiita.com/by-miwa30/items/dd455dcb73d03e19012e⑯環境変数の設定
https://qiita.com/by-miwa30/items/924a653faef7a03eba36⑰本番環境Railsの起動エラー(手動)
https://qiita.com/by-miwa30/items/d7a37a9deccbe76c047e⑱Webサーバーの設定
https://qiita.com/by-miwa30/items/31251da3c26a8129aa60⑲デプロイを自動化
https://qiita.com/by-miwa30/items/d4b7b1fa3601bf1185ba⑳IPアドレスにアクセスエラー(自動デプロイ
https://qiita.com/by-miwa30/items/fbc620dfe6e71d4a417c
- 投稿日:2020-12-15T21:27:16+09:00
(解決方法)Mysql2::Error: Field 'カラム名' doesn't have a default value
ユーザ管理機能実装にあたって、deviseを導入し、いざ新規登録をしようとすると、タイトルのエラーが発生した。
その解決方法と原因について記す。解決方法
deviseのストロングパラメーターを設定する。
以下にコード例を示す。application_controllerclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? private def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :first_name, (複数のカラム名)]) end endエラーが発生した原因
エラー文の内容は、『mysqlに指定したカラム名はデフォルトの値で設定されていません』というもの。
deviseにデフォルトで設定されているカラムは、emailとpasswordだけである。そのため、それ以外のカラムをdeviseを通じて保存しようとしても、当然ユーザ情報を保存できない。なぜ解決されるのか
上記のコードにより、emailとpassword以外のカラムをユーザ情報として、deviseを通じて保存できるようにしている。
したがって、デフォルトで設定されていないカラムも保存できるようになり、エラーは解決される。
- 投稿日:2020-12-15T21:20:40+09:00
Railsのエラーメッセージの日本語化がうまくいかない
現象
エラーメッセージを日本語化するため、カラム名の日本語訳を記述したファイルがうまく機能しませんでした。
解決方法
記述のスペーシングを整えたらうまくいきました。
誤字脱字以外にもスペーシングで機能しないことがあることを新たに学ぶことができました。config/locales/models/ja.yml# 階層構造をスペーシングで表現しないとうまく機能しない ja: activerecord: models: event: イベント attributes: event: name: イベント名 place: 開催場所 content: イベント内容エラーメッセージを日本語化する手順
「rails-i18n」というgemをインストール
Gemfilegem 'rails-i18n'↓
bundle install
↓
config/application.rbの記述を編集config/application.rbconfig.i18n.default_locale = :jaこれで日本語化はできました。
しかし、カラム名はまだ英語表記のため、
(例)emailを入力してください
のようなエラーメッセージになってしまいます。カラム名を日本語に訳す記述をする
config/locales/models/に「ja.yml」というファイルを作成します。
作成したファイル内にカラム名に対応する日本語を記述します。config/locales/models/ja.yml# 階層構造をスペーシングで表現しないとうまく機能しない ja: activerecord: models: event: イベント attributes: event: name: イベント名 place: 開催場所 content: イベント内容参考文献
- 投稿日:2020-12-15T21:18:52+09:00
MySql 接続エラー
rails s後に、頻繁にmysqlのエラーが出るので考えてみたい。Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
エラー文の意味としては、ソケットと言われるクライアントとサーバーを接続する通信機構が存在していないので、'/tmp/mysql.sock'を通じてMySQLサーバーに接続できないとのこと。
対処法
$ sudo mysql.server restart Starting MySQL .. SUCCESS!基本、
sudo mysql.server restartで解決できる原因としては、Mysqlサーバーが起動していないと考えられる。
参考にしたサイト
- 投稿日:2020-12-15T21:04:34+09:00
Rails それぞれの命名規則をまとめみた
ControllerやModelをコマンドで作成する際に
単数形だっけ、複数形だっけ?となるのでまとめました概要
Railsでは、
基本理念の1つ CoC (Convention over Configuration, 設定より規約) が定義されていますCoCとは、初心者が意識しなくてもよく
熟考をしなくてよくなるだけでなく、より深い抽象化を育ててくれる
と、記事に書いてあります
引用記事つまり、開発者の決定すべきことを減少させ、単純にするということ
Controller編
複数のactionを持つため、複数系
種類 例 説明 コントローラー名 users 複数形 ファイル名 blogs_controller.rb 複数形 コントローラークラス名 UsersController キャメルケース コマンド rails g controller users 複数形 app/controllers/users_controller.rbclass UsersController < ApplicationController def index end endModel編
Modelは設計書のようなもの
設計書は1つなので単数形テーブルには複数の情報が保持されているので複数形
マイグレーションファイルや、クラスはテーブルを作るものなので
それに関連して、同じく複数形
種類 例 説明 モデル名 user 単数形 ファイル名 blog.rb 単数形 モデルクラス名 User 単数系、頭文字大文字 テーブル名名 users 複数形 マイグレーションファイル名 ☓☓☓_create_users.rb 複数形 コマンド rails g model User 単数形 app/models/user.rbclass User < ApplicationRecord has_many :articles enddb/migrate/2020☓☓☓_create_users.rbclass CreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end endView編
フォルダ配下に複数のViewファイルを持つため、複数形
種類 例 説明 フォルダ名 users 複数形 Routing編
Routingのリソース名はController名に基づいているため、複数形
種類 例 説明 リソース名 users 複数形 参考記事
- 投稿日:2020-12-15T20:49:29+09:00
Herokuでデプロイをやり直す方法
<この記事について>
Herokuでデプロイした後に、本番環境に反映されていなくて再度デプロイをやり直す
ことで無事に解決できたので備忘録として投稿![環境]
・Ruby 2.6.5,
・Rails 6.0.0
・macOS<状況>
railsでアプリを制作時に、本番環境での動きを確認するために、Herokuでデプロイを実施。
一度目は問題なく動作したが、2回目で不具合発生。ビューについて、ローカル環境と同様の
実装となっていないため、試行錯誤するも解決できず。メンターに質問したところ、
新たに1つの操作をすることで解決できました。以下、全てターミナルで操作となります。
$ git commit --allow-empty -m "任意のコミットメッセージ"→通常、差分がないとcommit/pushできないが、差分がなくても強制実行してくれるコマンド
あとは、通常のHerokuへのデプロイと同じ流れです。
$ git push heroku master$ heroku run raills db:migrate本番環境で確認して問題なければ完了。
<終わりに>
ローカル環境で問題ないのに本番環境で同じように動作しないと焦りやすいですが、
このようなコマンドで解決できることを知っているだけで精神的にも安定すると思います!
- 投稿日:2020-12-15T19:50:32+09:00
Railsチュートリアル(第6版) マイクロポストの投稿機能のバックグラウンド動作
概要
マイクロポストの投稿は以下の2stepで進む。
- ログインしてホーム画面を表示させる。ログイン状態のホーム画面にはマイクロポストの投稿フォームが設置されている。
- マイクロポストの内容を入力して送信ボタンを押すと、マイクロポストの内容がDBに保存される。
詳細
上記の各stepで実行されるバックグラウンドの動作は以下の通りである。
- ログイン時のバックグラウンド動作についてはこちらを参照。/static_pages/homeへのGETリクエストを送信するとStaticPagesコントローラのhomeアクションが実行される。このhomeアクションでは、ユーザーIDに紐付ける形で@micropostが新規作成される(マイクロポスト関連部分のみ抜粋)。homeアクションの最後で、対応したview(/static_pages/home.html.erb)が呼び出され、ホーム画面が表示される。このviewへはhomeアクションで作成された@micropostが渡されており、Railsはこの中身が空であることから、マイクロポストの新規作成であると判断している。
- ユーザーがマイクロポスト入力フォームに内容を入力して投稿ボタンを押すと、/micropostsへのPOSTリクエストが送信され、Micropostsコントローラのcreateアクションが実行される。このcreateアクション実行の直前には、現在ログイン中のユーザーであるかどうかが確認される。当のcreateアクションではparams[:micropost][:content]とparams[:micropost][:image]の値のみを許可して@micropostに格納し、DBへの保存を試みる。DBへの保存に成功すると、root_urlへのリダイレクトが行われ、ホーム画面が表示される。
- 投稿日:2020-12-15T19:31:49+09:00
rails のアプリケーションサーバについて
お久しぶりです。井出です。
前回の記事から1年経ちましたね。今年もやってきましたアドベントカレンダー!
昨日は若手のホープ、ダニエル・バレンボイムが大好きなダニーさんが記事を書いてくれました。
記事はこちら中途で入ったイケイケ系の彼ですが、
サーバばりばり触れるようになってきていて将来有望です!入社してからの振り返り記事とのことで
プラコレが気になる方は、ぜひ雰囲気を感じてもらえたらと思います!ダニーさんの流れで、自分も今年の振り返りをしてみようと思います。
去年まで自分のメイン言語はPHPでしたが、今年からRubyをメインにシステム開発していました。
記載方法が独特で最初は苦しめられましたが、振り返ってみると凄い楽しい言語です。
まだまだ理解できていない部分もあるので、来年はより深く理解できるよう進めていきます!そんなこんなで、rubyのFWであるRailsで有名なサーバアプリケーション
puma / unicorn について、先日というか昨日なんですが
コマンドを調べる機会があったので
機能の違いとあわせてまとめてみようと思います。unicornについて
前提:unicornはマルチプロセス/シングルスレッドです。
Unicornはプロセスごとに通信処理を行います。
そのため低速なio処理があった場合そこで足止めを食らい全体が重くなってしまいます。
これを回避するためにorkerの数を増やす必要がありますが
workerはCPUのコア数に依存する部分もあります。コマンド
起動
bundle exec unicorn -E production -c config/unicorn.rb -D-E 実行環境変数
-c 設定ファイルの位置
-D デーモン化停止
kill -QUIT cat pidファイルのパス kill -QUIT masterのpid再起動
kill -HUP cat pidファイルのパス kill -HUP masterのpid緩やかな再起動
kill -USR2 cat pidファイルのパス kill -USR2 masterのpid旧プロセスを保持した状態で、新プロセスを起動
起動後旧プロセスに停止シグナル(QUIT)を実施し停止させる。(仕様は https://github.com/phusion/unicorn 参考)
pumaについて
前提:pumaはマルチプロセス/マルチスレッドです。
rails5.2から標準機能となった。
リクエスト処理をスレッド単位で溜めて、それをマルチに実行することでの並列処理を可能としています。
リソースが少ない中でも効率的にリクエストをさばくことが可能になります。コマンド
起動
bundle exec puma -C config/puma.rb -d-C 設定ファイルの位置
-d デーモン化停止
kill -QUIT cat pidファイルのパス kill -QUIT masterのpid再起動(設定ファイルリロード無)
kill -USR1 cat pidファイルのパス kill -USR1 masterのpid再起動(設定ファイルリロード有)
kill -USR2 cat pidファイルのパス kill -USR2 masterのpid使用用途によって使い分けることができれば素敵ですが
大規模システムでもない限り、パフォーマンスへの影響は無さそうですね。
こちらの記事が、両者の性能部分などをまとめてくださっています。明日はフロントの将来有望株の好君が記事を書いてくれるので、ご期待を!
それではまた1週間後に会いましょう!Now hiring!
プラコレでは、自由な未来をつくるために
一緒に冒険したいエンジニア・デザイナーを募集しています!
https://www.wantedly.com/projects/262436
運営サービス
PLACOLE(プラコレウェディング)
DRESSY(ドレシー)byプラコレ
farny(ファーニー)byプラコレ
- 投稿日:2020-12-15T18:00:32+09:00
【Ruby】演算子"::(コロンコロン)"【定数】
まえがき
Railsを使っていれば、誰でも以下の記述を見たことあると思います。
models/application_record.rbclass ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
ApplicationRecordにActiveRecord::Baseを継承していますよね。なんにも変わったことありませんね。
・・・
・・
・
って、え、え、ちょっと待って下さい。
なんですか、::って!?!?!?
怖い!!!ということで、Ruby初学者である私が、
::について調べたことを健忘録として残します。結論
::は2つの特徴があります。1.
名前空間の絶対パスを指定するmodule Hoge class Fuga def self.fuga p "fugaです" end end end Hoge::Fuga.fuga #=> "fugaです" Fuga.fuga #エラー #Fugaクラスを作成 class Fuga def self.fuga p "fugaです" end end Hoge::Fuga.fuga #=> "fugaです" Fuga.fuga #=> "fugaです"2.
定数のスコープ演算子クラスまたはモジュールで定義された定数をスコープ外部から参照するためには
::を使います。class Hoge FUGA = "FUGA" #定数FUGA p FUGA #=> "FUGA" end #スコープ外から定数FUGAを出力する。 p Hoge::FUGA #=> "FUGA"また、Rubyではクラスやモジュールも定数として扱われます。
例えばHogeクラスを生成した時、Hogeクラスオブジェクトは、Hogeという名前の定数に代入されています。結局、ActiveRecord::Baseの
::は??ActiveRecord::Baseの
::はActiveRecordモジュール内のBaseクラスを指しています!!
ActiveRecordにネストされたBaseクラスなんですね〜。
簡単に書くと以下の通りです。active_record/base.rbmodule ActiveRecord #省略 class Base #省略 end #省略 endActiveRecord::Baseの正式な中身は以下の通りです。
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rbActiveRecordの機能についてはこの方が深くまとめております。
Rails: ActiveRecord::Baseメソッドのまとめ
参考
Ruby公式リファレンス 変数と定数
https://docs.ruby-lang.org/ja/latest/doc/spec=2fvariables.html#constRailsガイド Active Record の基礎
https://railsguides.jp/active_record_basics.html
- 投稿日:2020-12-15T16:57:55+09:00
【rails】dockerでrailsコンソールを使う方法
個人メモです。
dockerで起動しているアプリケーションのrails consoleにアクセスする方法。
dockerのコンテナ内で
rails cを実行する。rails consoleの起動
ターミナル##入りたいコンテナ名を確認 docker ps ##コンテナに入る docker exec -it コンテナ名 /bin/bash ##railsコンソールを開く root@96913c74e902:/app# rails c `Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead. Loading development environment (Rails 6.0.3.2) irb(main):001:0>ターミナルが、ローカル($) -> docker(root@96913c74e902:) -> rails(irb(main))の順に変わっていく。
irbはrailsの対話モード。
Interactive Rubyの略。
終了方法と注意点
終了するときは
exitかquit。※末尾の番号が0以外のときは
ctrl + cでクリアしてから##末尾が0以外だと抜けられない(式の途中と見做されている) irb(main):014:2> quit irb(main):015:2> exit irb(main):016:2> ##ctrl + c でクリアしてからexit irb(main):017:0> exit
コマンドの実行例
1行の場合irb(main):001:0> p "hello rails" "hello rails" => "hello rails" irb(main):002:0> 1+2 => 3複数行の場合irb(main):015:0> for i in 1...11 do irb(main):016:1* print "#{i} " irb(main):017:1> end 1 2 3 4 5 6 7 8 9 10 => 1...11
- 投稿日:2020-12-15T16:57:55+09:00
【Rails】dockerでrailsコンソールを使う方法
個人メモです。
dockerで起動しているアプリケーションのrails consoleにアクセスする方法。
dockerのコンテナ内で
rails cを実行する。rails consoleの起動
ターミナル##入りたいコンテナ名を確認 docker ps ##コンテナに入る docker exec -it コンテナ名 /bin/bash ##railsコンソールを開く root@96913c74e902:/app# rails c `Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead. Loading development environment (Rails 6.0.3.2) irb(main):001:0>ターミナルが、ローカル($) -> docker(root@96913c74e902:) -> rails(irb(main))の順に変わっていく。
irbはrailsの対話モード。
Interactive Rubyの略。
終了方法と注意点
終了するときは
exitかquit。※末尾の番号が0以外のときは
ctrl + cでクリアしてから##末尾が0以外だと抜けられない(式の途中と見做されている) irb(main):014:2> quit irb(main):015:2> exit irb(main):016:2> ##ctrl + c でクリアしてからexit irb(main):017:0> exit
コマンドの実行例
1行の場合irb(main):001:0> p "hello rails" "hello rails" => "hello rails" irb(main):002:0> 1+2 => 3複数行の場合irb(main):015:0> for i in 1...11 do irb(main):016:1* print "#{i} " irb(main):017:1> end 1 2 3 4 5 6 7 8 9 10 => 1...11
- 投稿日:2020-12-15T16:35:15+09:00
Railsのform_withはデフォルトでAjax通信をする
生じた現象
- formを入力してPOST送信をして返ってきたレスポンスのHTMLが、ブラウザでレンダリングされなかった。
対策
form_with(model: @form, local: true) do |f| # inputたち endのように、
local: true指定してやれば良い。参考
- https://stackoverflow.com/a/50647776 によると、以下。
Forms generated with form_with by default has data-remote set to true.
If the data-remote is set to true your form makes an AJAX call. So your view is getting rendered as the response of that AJAX call. That is why you are not getting any errors.
Add local: true in your form_with.
- 投稿日:2020-12-15T16:07:06+09:00
RSpecまとめ 3.モデルスペック
前回の続き。
モデルに対するテスト。モデルのバリデーションやクラスメソッド、インスタンスメソッドをテストしていきます。
モデルスペックの構造
モデルスペックには、以下の3つのテストを最低限入れます。
1.有効な属性で初期化された場合は、モデルの状態が有効(valid)になっていること
2.バリデーションを失敗させるデータであれば、モデルの状態が有効になっていないこと
3.クラスメソッドとインスタンスメソッドが期待通りに動作することモデルスペック作成
まずはUserモデルのテストです。
前回、スペックファイルの自動生成を設定しましたが、今回は既存のモデルに対するテストなので、手動でスペックファイルを生成します。$ rails g rspec:model userこのコマンドで、spec/models/user_spec.rbが生成されるので、このファイルにスペックを書いていきます。
spec/models/user_spec.rbrepuire 'rails_helper' #ヘルパーの読み込み RSpec.describe User, type: :model do #ブロック内にUserモデルのスペックをまとめている #name, email, passwordがあれば有効な状態であること it 'is valid with a name, email, and password' do #一つ一つのスペックはitで始まるブロック内に記述 user = User.new( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) expect(user).to be_valid #userが有効(valid)ならパスする end endRSpecでは、テストしたい値をexpect()に渡し、後ろに続くマッチャを呼び出すことでテストを行います。
今回の場合、userをbe_valid(有効か)というマッチャでテストしています。
また、(user)の後ろのtoは、テストする値がマッチャに適合すればsuccess、しなければfaildをテスト結果として返します。
toの反対の意味を持つto_notもよく使うので、覚えておくと良いでしょう。バリデーションのテスト
正しい値を与えたときにモデルが有効になっているかテストできたので、今度はバリデーションを失敗させるデータを与えたときにモデルが無効な状態かテストしていきます。
spec/models/user_spec.rb#nameがなければ無効であること it 'is invalid without a name' do user = User.new( name: nil email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("can't be blank") end今回のスペックでは、userのnameをnilにした状態でvalid?メソッドを使ってuserの有効性を検証し、最後にuser.errors[:name]に"can't be blank"という内容のものが含まれていればパスするという内容です。
nameがないことがバリデーションに失敗した原因であるかを知りたいので、発生したエラーの内容をテストしています。
user.valid?で有効性を検証しないとエラーメッセージが出ないので、注意しましょう。この方法に沿って、他のバリデーションのスペックも書いていきましょう。
spec/models/user_spec.rb#emailが重複していれば無効であること it 'is invalid without a duplicate email address' do User.create( neme: "zeisho" email: "hoge@hoge.com" password: "hogehoge" ) user = User.new( name: "Skywalker" email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("has already been taken") endcreateを使ってユーザーを保存し、その後で同じemailを持ったユーザーを生成するとemailの重複エラーが出るかという内容のスペックです。
この調子でUserモデルのスペックを完成させましょう。
完成したら、Projectモデルのスペックも作っていきます。
$ rails g rspec:model projectspec/models/project_spec.rbrequire 'rails_helper' RSpec.describe Project, type: :model do # ユーザー単位では重複したプロジェクト名を許可しないこと it "does not allow duplicate project names per user" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) new_project = user.projects.build( name: "Test Project" ) new_project.valid? expect(new_project.errors[:name]).to include("has already been taken") end # 二人のユーザーが同じ名前を使うことは許可すること it "allows two users to share a project name" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) other_user = User.create( name: "Skywalker", email: "hogehoge@hoge.com", password: "hogehoge" ) other_project = other_user.projects.build( name: "Test Project" ) expect(other_project).to be_valid end endProjectはUserに紐づいているので、今の書き方だとテストに必要なインスタンスを作るだけでコードが冗長化してしまいました。この辺りの問題は後で解決していきます。
クラスメソッド、インスタンスメソッドのテスト
このアプリには、Projectモデルに紐づいたNoteがあり、プロジェクトのメモとして文字列を格納できるようになっており、Noteモデルには検索機能を実装しています。
app/model/note.rbscope :search, ->(term) { where("LOWER(message) LIKE ?", "%#{term.downcase}%") }今回はこのクラスメソッドとスコープをテストしていきます。
$ rails g rspec:model notespec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #検索文字列に一致するメモを返すこと it "returns notes that match the search term" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("first")).to include(note1, note3) expect(Note.search("first")).to_not include(note2) end #検索結果が見つからなければ空のコレクションを返すこと it "returns an empty collection when no results are found" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("message")).to be_empty end endマッチャについてもっと詳しく
これまで、3つのマッチャ(be_valid, include, be_enpty)を使ってきましたが、RSpecが提供するマッチャをもっと知りたい場合、rspec-expectationsを参照すると良いでしょう。
スペックをDRYにする
discribe, context, before, afterを使ってスペックをDRYにしていきます。
discribe, context
スペック群を分類してブロック内にまとめることができます。
Noteモデルのスペックを例に見ていきましょう。spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #バリデーション用のスペック群 #メッセージ検索機能のスペック群 discribe "search message for a term" do #一致するデータが見つかるとき context "when a match is found" do #一致する場合のexample群 end #一致するデータが見つからないとき context "when no match is found" do #一致しない場合のexample群 end end endbefore, after
全てのテストで使用するテストデータを一箇所にまとめることができます。
spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do before do @user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) @project = user.projects.create( name: "Test Project" ) end #バリデーション用のスペック群 #メッセージ検索機能のスペック群 endbeforeには以下のオプションを設定できます。
before(:each)
describeまたはcontextブロック内の各(each)テストの前に実行before(:all)
describeまたはcontextブロック内の全(all)テストの前に一回だけ実行before(suite)
テストスイート全体の全ファイルを実行する前に実行exampleの後に後片付けが必要であれば、afterを使うことができます。
テストはDRYにし過ぎない!
テストは開発・本番環境とは違って可読性を優先してDRYにしていくので、もしスペックファイルの内容を確認するのにエディタのスクロールや、複数のファイルの行き来を頻繁に行っているならば、DRY過ぎます。
必要に応じてコードを重複させることを検討したり、ファイルを行き来しなくても役割のわかる変数・メソッド名をつけるよう心がけましょう。
- 投稿日:2020-12-15T16:07:06+09:00
Everyday Railsまとめ 3章『モデルスペック』
前回の続き。
モデルに対するテスト。モデルのバリデーションやクラスメソッド、インスタンスメソッドをテストしていきます。
モデルスペックの構造
モデルスペックには、以下の3つのテストを最低限入れます。
1.有効な属性で初期化された場合は、モデルの状態が有効(valid)になっていること
2.バリデーションを失敗させるデータであれば、モデルの状態が有効になっていないこと
3.クラスメソッドとインスタンスメソッドが期待通りに動作することモデルスペック作成
まずはUserモデルのテストです。
前回、スペックファイルの自動生成を設定しましたが、今回は既存のモデルに対するテストなので、手動でスペックファイルを生成します。$ rails g rspec:model userこのコマンドで、spec/models/user_spec.rbが生成されるので、このファイルにスペックを書いていきます。
spec/models/user_spec.rbrepuire 'rails_helper' #ヘルパーの読み込み RSpec.describe User, type: :model do #ブロック内にUserモデルのスペックをまとめている #name, email, passwordがあれば有効な状態であること it 'is valid with a name, email, and password' do #一つ一つのスペックはitで始まるブロック内に記述 user = User.new( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) expect(user).to be_valid #userが有効(valid)ならパスする end endRSpecでは、テストしたい値をexpect()に渡し、後ろに続くマッチャを呼び出すことでテストを行います。
今回の場合、userをbe_valid(有効か)というマッチャでテストしています。
また、(user)の後ろのtoは、テストする値がマッチャに適合すればsuccess、しなければfaildをテスト結果として返します。
toの反対の意味を持つto_notもよく使うので、覚えておくと良いでしょう。バリデーションのテスト
正しい値を与えたときにモデルが有効になっているかテストできたので、今度はバリデーションを失敗させるデータを与えたときにモデルが無効な状態かテストしていきます。
spec/models/user_spec.rb#nameがなければ無効であること it 'is invalid without a name' do user = User.new( name: nil email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("can't be blank") end今回のスペックでは、userのnameをnilにした状態でvalid?メソッドを使ってuserの有効性を検証し、最後にuser.errors[:name]に"can't be blank"という内容のものが含まれていればパスするという内容です。
nameがないことがバリデーションに失敗した原因であるかを知りたいので、発生したエラーの内容をテストしています。
user.valid?で有効性を検証しないとエラーメッセージが出ないので、注意しましょう。この方法に沿って、他のバリデーションのスペックも書いていきましょう。
spec/models/user_spec.rb#emailが重複していれば無効であること it 'is invalid without a duplicate email address' do User.create( neme: "zeisho" email: "hoge@hoge.com" password: "hogehoge" ) user = User.new( name: "Skywalker" email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("has already been taken") endcreateを使ってユーザーを保存し、その後で同じemailを持ったユーザーを生成するとemailの重複エラーが出るかという内容のスペックです。
この調子でUserモデルのスペックを完成させましょう。
完成したら、Projectモデルのスペックも作っていきます。
$ rails g rspec:model projectspec/models/project_spec.rbrequire 'rails_helper' RSpec.describe Project, type: :model do # ユーザー単位では重複したプロジェクト名を許可しないこと it "does not allow duplicate project names per user" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) new_project = user.projects.build( name: "Test Project" ) new_project.valid? expect(new_project.errors[:name]).to include("has already been taken") end # 二人のユーザーが同じ名前を使うことは許可すること it "allows two users to share a project name" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) other_user = User.create( name: "Skywalker", email: "hogehoge@hoge.com", password: "hogehoge" ) other_project = other_user.projects.build( name: "Test Project" ) expect(other_project).to be_valid end endProjectはUserに紐づいているので、今の書き方だとテストに必要なインスタンスを作るだけでコードが冗長化してしまいました。この辺りの問題は後で解決していきます。
クラスメソッド、インスタンスメソッドのテスト
このアプリには、Projectモデルに紐づいたNoteがあり、プロジェクトのメモとして文字列を格納できるようになっており、Noteモデルには検索機能を実装しています。
app/model/note.rbscope :search, ->(term) { where("LOWER(message) LIKE ?", "%#{term.downcase}%") }今回はこのクラスメソッドとスコープをテストしていきます。
$ rails g rspec:model notespec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #検索文字列に一致するメモを返すこと it "returns notes that match the search term" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("first")).to include(note1, note3) expect(Note.search("first")).to_not include(note2) end #検索結果が見つからなければ空のコレクションを返すこと it "returns an empty collection when no results are found" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("message")).to be_empty end endマッチャについてもっと詳しく
これまで、3つのマッチャ(be_valid, include, be_enpty)を使ってきましたが、RSpecが提供するマッチャをもっと知りたい場合、rspec-expectationsを参照すると良いでしょう。
スペックをDRYにする
discribe, context, before, afterを使ってスペックをDRYにしていきます。
discribe, context
スペック群を分類してブロック内にまとめることができます。
Noteモデルのスペックを例に見ていきましょう。spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #バリデーション用のスペック群 #メッセージ検索機能のスペック群 discribe "search message for a term" do #一致するデータが見つかるとき context "when a match is found" do #一致する場合のexample群 end #一致するデータが見つからないとき context "when no match is found" do #一致しない場合のexample群 end end endbefore, after
全てのテストで使用するテストデータを一箇所にまとめることができます。
spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do before do @user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) @project = user.projects.create( name: "Test Project" ) end #バリデーション用のスペック群 #メッセージ検索機能のスペック群 endbeforeには以下のオプションを設定できます。
before(:each)
describeまたはcontextブロック内の各(each)テストの前に実行before(:all)
describeまたはcontextブロック内の全(all)テストの前に一回だけ実行before(suite)
テストスイート全体の全ファイルを実行する前に実行exampleの後に後片付けが必要であれば、afterを使うことができます。
テストはDRYにし過ぎない!
テストは開発・本番環境とは違って可読性を優先してDRYにしていくので、もしスペックファイルの内容を確認するのにエディタのスクロールや、複数のファイルの行き来を頻繁に行っているならば、DRY過ぎます。
必要に応じてコードを重複させることを検討したり、ファイルを行き来しなくても役割のわかる変数・メソッド名をつけるよう心がけましょう。
- 投稿日:2020-12-15T15:19:12+09:00
RSpecのテスト中にMySQL client is not connected
開発環境
macOS Catalina 10.15.7
Ruby on Rails 6.0.0
RSpec 4.0.1
pry rails 0.3.9
FactoryBot 6.1.0エラー内容
consoleFailure/Error: _query(sql, @query_options.merge(options)) ActiveRecord::StatementInvalid: Mysql2::Error: MySQL client is not connectedどうやらMySQLクライアントとの接続が確立できていないようだ。
定義を見る限り、client が初期化されているにも関わらず、network socket (file descriptor) が無効な状態だとこのエラーになるみたいですね。
Mysql2 の "MySQL client is not connected" について検証
テストの実行結果を見ると、途中まではテストが成功しているため、ひとまずbinding.pryで処理を止めながらテスト内容を確認してみたところ、なぜかすべてのテストが成功した。consoleFinished in 16.16 seconds (files took 2.21 seconds to load) 15 examples, 0 failures仮説
FactoryBotのインスタンス生成の記述を増やしたタイミングでエラーがエラーが発生しはじめたため、ここで負荷がかかって処理が止まった可能性があると考えた。
対処法1
インスタンスを生成するタイミングで
sleepで処理を待機させることにした。RSpec.describe OrderItem, type: :model do describe '購入情報の保存' do before do @user = FactoryBot.create(:user) @item = FactoryBot.create(:item) @order_item = FactoryBot.build(:order_item) sleep 0.1 # 0.1秒待機 end # 省略結果
エラーを吐かずにテストが安定して成功するようになった。
対処法2
config/environments/test.rbに以下の記述をすることでも対処出来た。config/environments/test.rbRails.application.configure do config.active_job.queue_adapter = :inline # 省略 end参考リンク
- 投稿日:2020-12-15T14:46:50+09:00
authenticateメゾットについて。
user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password])user.authenticate(password)で
合っていればユーザーの情報を出す。
間違っていれば、falseを返すメゾット。主にログイン等に使われる。
if user && user.authenticate(params[:session][:password])この文は&&の論理積(and)はnilとfalse以外は
Rubyではnilとfalse以外のすべてのオブジェクトは、真偽値ではtrueになるという性質を利用して、ログインできるかできないかをif文を使って実行している。真偽値についての理解が浅かったため理解するのに時間がかかった。。。
- 投稿日:2020-12-15T14:44:19+09:00
Railsのform_withについて掘り下げる
軽く自己紹介
はじめましての方は初めまして。伊東と申します。
大卒後未経験でプログラマーになりまして、気づけば7,8年くらい働いています。
2020年3月からDMM WebCampのメンターをしています。対象読者
- Ruby on Railsで初めてMVCフレームワークを触った
- 検証ツールであまり見ない
- 初学者の方
前提
$ rails -v Rails 5.2.4.3 $ ruby -v ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-linux] $Rubyのバージョン、Rails5系だよってだけです。バージョン互換などは考慮しておりませんが、
書く内容はそんなに影響するような話じゃないのでは??って思っています。authenticity_tokenの箇所でデフォルトの挙動が違うぽいです...本題というわけではないので各バージョンで調べてくださいmm伝えたいこと
検証ツール見ていきましょうって話です。
普段からinput nameとかの属性をちゃんと見てる人は読む必要がない記事になってます。
本当に基本的なことしか書かないので悪しからずそもそもActionView::Helpersってなに??
よく使うのはform系ではないかな?と思います。
- form_with
- text_field_tag
- text_area_tag
- submit_tagなど
詳しくは以下リンクから見てみてください。
https://api.rubyonrails.org/classes/ActionView/Helpers.html事前準備
細かい説明は割愛して作っていきます。
プロジェクト作成らへん
ちなみに行頭の
$マークはrootユーザー#じゃないって意味です。$ rails new without_action-view-helpers # プロジェクト作成 # 省略 $ cd without_action-view-helpers/ $ rails g controller books index new # コントローラー(アクション含)作成 # 省略 $ rails g model Book title:string price:integer description:text # モデル作成 # 省略 $ rails db:migrate # DB反映 # 省略 $ルーティングの設定
config/routes.rbRails.application.routes.draw do resources :books, only: [:index, :new, :create, :show, :edit, :update] endルーティングの確認$ rails routes | grep book # grepは文字列検索しています books GET /books(.:format) books#index POST /books(.:format) books#create new_book GET /books/new(.:format) books#new edit_book GET /books/:id/edit(.:format) books#edit book GET /books/:id(.:format) books#show PATCH /books/:id(.:format) books#update PUT /books/:id(.:format) books#update $controller
めーっちゃシンプルな形。
app/controllers/books_controller.rbclass BooksController < ApplicationController def index @books = Book.all end def new @book = Book.new end def create book = Book.new(book_params) book.save redirect_to books_path end def show @book = Book.find(params[:id]) end def edit @book = Book.find(params[:id]) end def update book = Book.find(params[:id]) book.update(book_params) redirect_to books_path end private def book_params params.require(:book).permit(:title, :price, :description) end endview
app/views/books/new.html.erb<h1>Books#new</h1> <div> <%= render 'books/form', book: @book %> </div>部分テンプレート化しています。
app/views/books/_form.html.erb<%= form_with model: book, local: true do |f| %> <div> <%= f.label :title %> <%= f.text_field :title %> </div> <div> <%= f.label :price %> <%= f.number_field :price %> </div> <div> <%= f.label :description %> <%= f.text_area :description %> </div> <div> <%= f.submit :submit %> </div> <% end %>ブラウザの検証ツールを使用して掘り下げて見ていきます
new
まずformタグやinput>hiddenの部分から見ていきます<form action="/books" accept-charset="UTF-8" method="post"> <input name="utf8" type="hidden" value="✓"> <input type="hidden" name="authenticity_token" value="cgX+LsxfzE7A7F5Tm8GAlF2KdaifSdqdrWd6cOgazlcbZx8OQ4c6LazJtdRYIdPjzmfmPUGHkuBoxSQhdSqiIA=="> </form>action?accept-charset?method?hidden?指定していない項目がいくつもでてきました。
method
methodはsubmitされた時のhttpリクエストメソッドです。
action
actionはformタグ内のsubmitボタンがポチィされた時に実行されるURI(パス)を指定します。
/booksで methodがpostになっているので、books_controllerのcreateアクションに飛ぶという仕組みです。form_withでaction指定してなくない?
urlというパラメーターの指定が無いときはmodelからactionのpathを勝手に作ってくれています。モデル名とコントローラー名が一致している場合は 省略可能です。
model: bookと書くだけでいいので簡単ですね。ちなみにnamespaceを使用している場合は
model: [:admin, book]としてあげれば、/admin/booksというURIを作ってくれます。便利hidden
name="utf8" については調べてないです!割愛します
name="authenticity_token" についてはCSRFの対策として自動発行されているものです。他のサイトから不正なリクエストを受けないように発行してるものです。
今回は5.2系で試しているのでデフォルトで有効になっているようですがそれ以外のバージョンであれば必要に応じて設定すれば有効になります。inputタグ
入力項目はどのようになっているか<div> <label for="book_title">Title</label> <input type="text" name="book[title]" id="book_title"> </div>inputタグを見てみると
<input type="text" name="book[title]" id="book_title">となっています。
type="text"は単純にテキストを入力出来るという意味です。text以外にもあるのでこれ見ればわかると思います。 (詳しくはこれ: http://www.htmq.com/html5/input.shtml )
name="book[title]"これが多分一番大事になってくると思います。Rails的にはコントローラー側でparams[:book][:title]で入力された値が取得出来るという意味になります。これを理解していれば、入力された値がDBに保存できない?みたいなことは少なくなると思います。edit
適宜省略してます<form action="/books/1" accept-charset="UTF-8" method="post"> <input type="hidden" name="_method" value="patch"> <div> <label for="book_title">Title</label> <input type="text" value="たーいとる" name="book[title]" id="book_title"> </div> <div> <input type="submit" name="commit" value="submit" data-disable-with="submit"> </div> </form>formタグ, hiddenタグ
action部分は
action="/books/1"になっています。model: bookで指定している変数bookが保存済みの場合URIを自動的にbook.idを設定してくれています。hiddenタグで新規の時には無かったものが存在しています。
<input type="hidden" name="_method" value="patch">これでhttpメソッドをpatchにしています。
なんでformタグで指定しないの??ってなると思うんですけど、多分ですがform>methodではpost/getしか指定できないからだと思います(間違ってたら指摘してもらえると)。 http://www.htmq.com/html5/form.shtml
inputタグ
<input type="text" value="たーいとる" name="book[title]" id="book_title">value="たーいとる" という項目が増えています。これで入力部分の値を指定しています。それ以外は同じなので割愛します。
まとめ
HTMLも単純に画面に表示されたものを見るのではなく、どのようにHTMLが生成されているかを確認したり意識することで、HTMLであったりRailsの仕組みを少しずつ理解出来るようになっていくと思います。
あまり長い文章書くの得意じゃないんですが、役に立てると嬉しく思います。。。
またお声掛け頂いたメンターの方ありがとうございます。
- 投稿日:2020-12-15T14:43:09+09:00
Rails:modelとデータベースについてちょっとまとめてみた。
最初に
カレンダー企画2020の15日目
プログラミングの勉強を始めて3ヵ月程経ったので学んだことのメモをアウトプットとして記事に残します。
これからプログラミングの世界に入る人の手助けになれたら嬉しい限りです。
間違っていたり、言葉が違っていたり、誤解されるような言葉があったら教えてください^^
言葉を長々と読みづらかったら申し訳ありません。少しずつなれてがんばります。モデルとデータベース(DB)についてちょっとまとめてみる
modelとは?
モデルはここで簡単に説明している
アプリケーションのデータを取り扱う場所。データベースに対してデータの登録や取得、更新、削除などといった処理を役割として担っている。
たとえば
データベースという巨大な倉庫を管理しているイメージかな。
必要な時に必要な情報を取り出したり、しまったりしてくれる感じです。基本的にDBとのやり取りになる。
データベース(DB)とは?
DBはデータの保存先です。
DBのおかげでデータを効率的に保存できたり、検索できるようになっている。ゲームでいうところのメモリカードとかセーブデータを保存してある場所かな!
modelがゲーム機本体
今回は出てこないけど、Controllerはコントローラー
テレビ画面に表示するコードやテレビがViewになるにかな!
そんなイメージです。railsではDBに保存されているデータを「テーブル」にまとめてテーブル同士が関連して保存してあります。
テーブルはデータを整頓してくれるのでデータを取り出しやすくなります。
ゲームで過去のセーブデータがゴチャゴチャな並びだったら最新のものを探すの大変じゃないですか?
そういったことをなくす為にテーブルというので関連しているもの同士をわかりやすく保存してあるのです。テーブルについてもう少しだけ
ブログサイトをイメージしてください。
- タイトル
- 本文
- 投稿者
- 投稿日
こんな情報があったとします。
これらをテーブルにしてみると
ID タイトル 本文 投稿者 投稿日 1 初めまして 本日からブログを始めます yuta 12/1 2 こんな空スゴイ 今朝の青空 yuta 12/5 3 今日はどこにいるでしょう? 写真をみて当ててみて yuta 12/9 4 朝から、、、 黙々と仕事しています yuta 12/12 こんな感じになります。Excelみたいですね!でもまさにそのイメージです!!
このようにしてあると、どこがタイトルでいつ投稿したのかとか一目瞭然ですよね!これが整頓して分かりやすくするということです。テーブルにも名前がついており、
全体の事はテーブルと言います。
縦列:カラムと言います。
横列:レコードと言います。railsではテーブルが作られてデータが作られるとIDは自動生成されます。
ID1のレコードは?
ID タイトル 本文 投稿者 投稿日 1 初めまして 本日からブログを始めます yuta 12/1 このテーブルのカラム名は?
ID タイトル 本文 投稿者 投稿日 これになります。
タイトルカラムの中身は?
タイトル 初めまして こんな空スゴイ 今日はどこにいるでしょう? 朝から、、、 これらが抽出されます。
DBとデータのやり取りはデータベース言語(SQL)で行われます。
最後に
正直カラムを理解するのが最初は大変だったことを思い出しました^^;
ここではわかるけど実際作るとあれ?とよく混乱していました。^^;
- 投稿日:2020-12-15T14:23:42+09:00
Formクラス作る時は、ActiveModelが便利
以前、「Virtusが便利」というタイトルでFormクラスを作る記事を書かせていただきました。
この時は、Virtusを利用していたのですが、Rails5.2以降であれば、ActiveModelだけで済みます
正確には、ActiveModel::Attributesを使いますclass ConstructionsSearchForm include ActiveModel::Model include ActiveModel::Attributes attribute :building_name, :string attribute :sales_staff_id, :integer attribute :department_id, :integer ....(略) def search Construction.ransack( building_name_cont: building_name, sales_staff_id_eq: sales_staff_id, sales_staff_department_id_eq: department_id, ).result end end上記のようにFormクラスを作って、controllerで
@constructions = ConstructionsSearchForm.new(search_params).search呼び出すだけ。
簡単ですね!!validation等を追加したい時も前回と同様、
ActiveModel::Modelをincludeしているので、attribute :building_name, :string attribute :sales_staff_id, :integer attribute :department_id, :integer validate :building_name, presence: trueActiveRecordと同じ用に書いて、
.valid?で検証するだけです。
ActiveModel::Attributesで指定できる型は、こちらに書いてあります。
- 投稿日:2020-12-15T13:48:41+09:00
未経験で大学を休学したエンジニアが2020年に学んだこと
CAMPFIRE Communityでエンジニアをしております。Matsuiです。
趣味はスポーツ観戦、旅行などです。
2020年はプロ野球、UEFAチャンピオンズリーグ、F1&F2が個人的に盛り上がりました。好きなドライバーはMax Verstappenです。(誰にも伝わらないと思いますが、Daniel Ricciardoとのコンビのシーンがすごく好きです。)2020年は大学を休学し未経験からエンジニアに挑戦をした一年でした。このアドベントカレンダーを利用して2020年に私が経験したこと、学んだこと、意識したことをまとめます。
エンジニアにチャレンジする前(2020/1時点)のスペック
- プログラミング経験はほぼゼロ。(SQLのみ若干経験あり) - コンピュータやインターネットには比較的馴染みがある。(作る側ではなく、利用する側として。) - 国立大学文系学部に通っている。やったこと
2020年1~2月
- 初めて
rails newをする。- Railsガイドのチュートリアル?をやる。
2~3月
- CAMPFIREのコードを読むようになる。
- 社内でデータ取得用のカラム追加とタスクに日時挿入の処理を追加する。
4~6月
- サポートサービスのコード分離とサービス追加。
- 密結合状態にあったコードをコントローラーレベルで切り分け、コードの分離を行いました。
- またクラウドファンディングとコミュニティではサービスの性質が異なる部分もあるため、コミュニティ独自のサポートサービスを追加しました。
7~8月
- コミュニティページのデザイン変更に合わせて必要になったバックエンドの開発。
- 密結合状態のコードの分離を行いました。
9~11月
- サポートサービスをDBで管理の上、社内の管理画面から追加、編集などをできるようにしました。
- これまでは追加、編集のためにデプロイをエンジニアに依頼する必要がありましたが、エンジニアなしで追加、編集できるようにしました。
11~12月
- 社内で使用する管理画面の機能改修、脱ライブラリ化。
- CAMPFIRE全体で社内で使用する管理画面を脱ライブラリ化し、独自のシステムを構築し移行する流れがあったのでそれに合わせて、主にコミュニティに関する機能の移行を担当しました。(しています。現在も進行中です。)
意識したこと、学び
※個人の意見です
組織をどうしたいか、プロダクトをどうしたいかは一旦考えるのをやめた。
- 組織のことを考えることも大事ですが、自分の実力が不足している状態だったのでとにかく自分のことに集中しました。自分のことに集中することが組織のためくらい割り切っていました。
一通り業務がこなせるようになるまでは、情報もシャットアウト。
- 技術は進歩も早く常に新しい情報が流れてくるのでできるだけキャッチアップしたほうが良いですが、基礎が無いままキャッチアップをしても効率も低いままですし、アウトプットにつながらないのでまずは基礎固めに集中しました。(特に初期は。)
GitHubのPR,issue,Slackの議論やレビューなどはできる限り目を通す。
- どこに学びが転がっているか分からないのでGitHubのPR,issue,Slackの議論やレビューなどは自分に関係ないことでも、意識して積極的に目を通すようにしていました。(そして実装の際は徹底的に
パクり参考にしました。他人の思考や脳は外部ライブラリだと思っています。)分からない、困ったは一定時間たったら詳しい人に質問するべき。
- ありきたりですが、初期はこれが出来ず聞いたら迷惑なのでは、自分で突き詰めて考えた方が良いのではと思っていました。しかし、わからないことは一定時間(1時間など)以上悩んでも大体解決しないのでスパッと聞いたほうが良いと考えるようになりました。聞くことは迷惑でもないですし、迷惑だったとしても他のところで取り返せば良いくらいに開き直るようになりました。
さいごに
足りない部分、課題はありすぎて書ききれない、認識しているものが全てでないので省略します。
最後まで読んでいただきありがとうございます。
質問、ご意見、アドバイスなどあればコメントまでお願いします。
大学は卒業する予定です。おわり。
- 投稿日:2020-12-15T13:22:02+09:00
Nuxt.js + Rails APIをDocker上で立ち上げCRUD操作してみる
今回初めて Nuxt.js を触りました。
Todoアプリを作ろうかなと思ったのですが, せっかくならAPIを叩こうじゃないかということでサーバーサイドも用意してみました。サーバーサイドはRuby on Rails(API), クライアントサイドはNuxt.ts(Nuxt.js + TypeScript), DBはpostgresという構成で実装していきます。
環境構築に関しては, サーバーサイド/クライアンドサイド共にDocker上で動かしており, ディレクトリ構成はモノシリックにまとめました。
動作環境
macOS Catalina : version 10.15.4
Docker for macはインストール済みとする。ディレクトリ構成
ディレクトリ構成. ├── client-side ├── server-side └── docker-compose.yml1. サーバーサイド(Ruby on Rails)
Dockerfile作成
server-side/配下にdockerfileを作成。DockerfileFROM ruby:2.7.0 RUN apt-get update -qq && \ apt-get install -y \ build-essential \ libpq-dev \ nodejs \ postgresql-client WORKDIR /app COPY Gemfil Gemfile.lock /app/ RUN bundle installGemfile, Gemfile.lock作成
同じく
server-side/配下にGemfileとGemfile.lockを作成。Gemfile内に以下を記述。
Gemfilesource 'https://rubygems.org' gem 'rails', '6.0.3'Gemfile.lockは空のままで大丈夫。
docker-compose.yml作成
railsとpostgresの設定をdocker-compose.ymlに書いていきます。
docker-compose.ymlversion: '3.8' volumes: db_data: services: db: image: postgres volumes: - db_data/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password server-side: build: ./server-side/ command: bundle exec rails server -b 0.0.0.0 image: server-side ports: - 3000:3000 volumes: - ./server-side:/server-app tty: true stdin_open: true depends_on: - db links: - dbAPIモードで
rails new以下のコマンドを叩けば,
server-side/配下にrails関連のファイル群が作成されます。$ docker-compose run server-side rails new . --api --force --database=postgresql --skip-bundle
database.ymlの内容を修正このままだとserver-sideのコンテナからDBのコンテナにアクセスできないので
database.ymlの内容を修正します。以下のようになっていると思うので
database.ymldefault: &default adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>以下のように編集。
database.ymldefault: &default adapter: postgresql encoding: unicode host: db user: postgres password: password # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
server-sideホストを受け入れるように修正この設定をすることで, Nuxtからserver-sideにアクセスできます。
server-side/config/environments/development.rbconfig.hosts << "server-side"DBを作成
以下のコマンドを叩いてdbを作成。
$ docker-compose run server-side rails db:create動作させてみる
以下のコマンドを打って,
localhost:3000にアクセス。
railsのデフォ画面が表示されればOK!$ docker-compose up -dサーバーサイドのAPIを実装
以下のコマンドを叩き, コンテナの中に入った上で作業を進めていきます。
$ docker exec -it server-side bashルーティングを設定。
routes.rbRails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html namespace :api do namespace :v1 do resources :todos do collection do get :complete end end end end endTodoモデル, todosコントローラーを作成。
$ rails g model Todo title:string isDone:boolean $ rails db:migrate $ rails g controller api::v1::todoscontrollerの中身は以下のように書きました。
api/app/controllers/api/v1/posts_controller.rbclass Api::V1::TodosController < ApplicationController before_action :set_todo, only: [:update, :destroy] def index todos = Todo.where(isDone: false) render json: { status: 'SUCCESS', message: 'Loaded todos', data: todos } end def complete todos = Todo.where(isDone: true) render json: { status: 'SUCCESS', message: 'Loaded todos', data: todos } end def create todo = Todo.new(todo_params) if todo.save render json: { status: 'SUCCESS', data: todo } else render json: { status: 'ERROR', data: todo.errors } end end def destroy @todo.destroy render json: { status: 'SUCCESS', message: 'Deleted the todo', data: @todo } end def update if @todo.update(todo_params) render json: { status: 'SUCCESS', message: 'Updated the todo', data: @todo } else render json: { status: 'ERROR', message: 'Not updated', data: @todo.errors } end end private def set_todo @todo = Todo.find(params[:id]) end def todo_params params.require(:todo).permit(:title, :isDone) end end動作確認
この記事を参考に, Postmanを利用してCRUD操作ができるかどうか確認します。
curlコマンドでも確認できますが, たぶんPostmanの方が楽。2. クライアントサイド(Nuxt.js)
環境構築
基本的には 公式のInstallation に沿って進めるだけ。
nodeはインストール済みとします。(今回の環境では12/15現時点でのLTS ver. 14.15.1を使用しています。)プロジェクトの作成
まずは
create-nuxt-appで雛形作りましょう。$ npx create-nuxt-app client-side色々質問されると思うのですが, 今回は以下のように設定しました。(その他はデフォルト)
terminal? Project name: client-side ? Programming language: TypeScript ? Package manager: Yarn ? UI framework: None ? Nuxt.js modules: Axios ? Linting tools: None ? Testing framework: None ? Rendering mode: Single Page App ? Deployment target: Server (Node.js hosting) ? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection) ? Continuous integration: None ? Version control system: Noneこの辺りの設定は各自の好みで設定してください。
全てのオプションは ここから 確認できます。Dockerfile作成
client-side/配下にDockerfileを作成。DockerfileFROM node:14.15.1 WORKDIR /client-app COPY package.json yarn.lock ./ RUN yarn install CMD ["yarn", "dev"]docker-compose.ymlに
client-sideの設定を追加
server-sideの設定を記述したdocker-compose.yml にclient-sideの設定を追加します。docker-compose.ymlversion: '3.8' volumes: db_data: services: db: image: postgres volumes: - db_data/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password server-side: build: ./server-side/ image: server-side ports: - 3000:3000 volumes: - ./server-side:/server-app command: bundle exec rails server -b 0.0.0.0 tty: true stdin_open: true depends_on: - db links: - db # ここから下を追加 client-side: build: ./client-side/ image: client-side ports: - 8000:8000 volumes: - ./client-side:/client-app - /client-app/node_modules command: sh -c "yarn && yarn dev"portの設定
このままだとエラーが出るので, portとhostを以下のように設定します。
nuxt.config.jsexport default { // Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode) ssr: false, // ここを追記 server: { port: 8000, host: '0.0.0.0', }, // 以下省略 }動作させてみる
以下のコマンドを打って,
localhost:8000にアクセスするとNuxt.jsのデフォ画面が表示されます。$ docker-compose up -dこれで環境構築は完了!
3. サーバーサイドとクライアントサイドの連携
いよいよクライアント側からサーバーサイドのAPIを叩きにいきます。
感動の瞬間。。。CORS (オリジン間リソース共有) 問題を解消
CORSについては こちらの記事 が参考になると思います。
公式とGitHubのREADMEに解決方法がありました。
READMEの記述を参考に@nuxtjs/proxyをインストールし,app/nuxt.config.jsを以下のように編集します。
サーバーサイドのポート番号をは3000で指定していたので, ここはserver-side:3000で。(コンテナ間の通信はコンテナ名で解決するため,localhostではなくserver-sideにしている。)$ yarn add @nuxtjs/proxyapp/nuxt.config.jsmodules: [ '@nuxtjs/axios', '@nuxtjs/proxy' ], // 以下を追加 proxy: { '/api': { target: 'http://server-side:3000', pathRewrite: { '^/api': '/api/v1/', }, }, },Composition APIとaxiosを設定
この辺り使いたいので設定しましたが, なくてもCRUD操作はできます。
shell$ yarn add @nuxtjs/composition-apiclient-side/nuxt.config.jsmodules: [ '@nuxtjs/proxy', //追加 '@nuxtjs/axios', '@nuxtjs/composition-api', ],client-side/tsconfig.json"types": [ "@types/node", "@nuxt/types", #追加 "@nuxtjs/axios" ]型定義
client-sideに新たにmodels/todo.tsディレクトリを作り, 以下を記述。todo.tsexport interface ITodo { id: number; title: string; isDone: boolean; }viewを記述
本当はコンポーネントに分割して書くべきですが, 今回は1ファイルにまとめた方が見やすいかなと思ったのでまとめます。
client-side/pages/index.vueに以下の内容を記述。client-side/pages/index.vue<script lang="ts"> import { defineComponent, reactive, ref, onMounted, } from "@nuxtjs/composition-api"; import { ITodo } from "../models/todo"; import $axios from "@nuxtjs/axios"; export default defineComponent({ setup(_, { root }) { onMounted(() => { getTodo(); }); const todoItem = reactive({ title: "", isDone: false, }); const todoList = ref<ITodo[]>([]); const completeTodoList = ref<ITodo[]>([]); // todoをpost const addTodo = async () => { try { await root.$axios.post("/api/todos/", { title: todoItem.title, isDone: todoItem.isDone, }); getTodo(); todoItem.title = ""; } catch (e) { console.log(e); } }; // todoをget const getTodo = async () => { try { const response = await root.$axios.get("/api/todos"); todoList.value = { ...response.data.data }; getCompleteTodo(); } catch (e) { console.log(e); } }; // todoをupdate const updateTodo = async (i: number, todo: ITodo) => { try { const newTodo = todoList.value[i].title; await root.$axios.patch(`/api/todos/${todo.id}`, { title: newTodo }); } catch (e) { console.log(e); } }; // todoをdelete const deleteTodo = async (id: number) => { try { await root.$axios.delete(`/api/todos/${id}`); getTodo(); } catch (e) { console.log(e); } }; // todoをdone const completeTodo = async (todo: ITodo) => { try { todo.isDone = !todo.isDone; await root.$axios.patch(`/api/todos/${todo.id}`, { isDone: todo.isDone, }); getTodo(); } catch (e) { console.log(e); } }; // complete_todoをget const getCompleteTodo = async () => { try { const response = await root.$axios.get("/api/todos/complete"); completeTodoList.value = { ...response.data.data }; } catch (e) { console.log(e); } }; return { todoItem, todoList, completeTodoList, addTodo, deleteTodo, updateTodo, completeTodo, }; }, }); </script> <template> <div class="container"> <section class="todo-new"> <h1>Add todos</h1> <input v-model="todoItem.title" type="text" placeholder="todoを記入" /> <button @click="addTodo()">Todoを追加</button> </section> <section class="todo-index"> <h1>Incomplete todos</h1> <ul> <li v-for="(todo, i) in todoList" :key="i"> <input class="item" type="checkbox" :checked="todo.isDone" @change="completeTodo(todo)" /> <input class="item" type="text" v-model="todo.title" @change="updateTodo(i, todo)" /> <button @click="deleteTodo(todo.id)">削除する</button> </li> </ul> </section> <section class="todo-complete"> <h1>Complete todos</h1> <ul> <li v-for="(todo, i) in completeTodoList" :key="i"> <input class="item" type="checkbox" :checked="todo.isDone" @change="completeTodo(todo)" /> {{ todo.title }} <button @click="deleteTodo(todo.id)">削除する</button> </li> </ul> </section> </div> </template> <style> .container { margin: 80px auto; min-height: 100vh; text-align: center; } section { margin-bottom: 30px; } .item { font-size: 1rem; margin: 0 10x; } li { list-style: none; margin-bottom: 0.5em; } </style>実際に動作させてみる
docker-compose upさせて,
localhost:8000にアクセスすると以下のような画面になると思います。実際にtodoを追加/編集/削除してみてください。
まとめ
Dockerfileを1から書いたのも初めてだったので良い勉強になりました。
Nuxt.jsに関しては知らないことしかないので勉強していきます。
「ここのコードもっとこうした方がいいよ!」というのがあればぜひアドバイスお願いします。
- 投稿日:2020-12-15T12:51:04+09:00
[Rails]resources, member, collection ルーティング/名前付きパス/URLの覚え方[初学者向け]
はじめに
初めまして。ながえもんと申します。
3ヶ月超かけて、ようやくRailsチュートリアルと言う山を登り切ったので、
- resourcesで生成されるRESTfulな7つのルーティング
- memberメソッド、collectionメソッドで生成される任意ルーティング
- それらのアクション名、URL、名前付きパス
についてまとめました。
resourcesで生成されるパス/URLは非常に便利な一方で、
「あれ、名前付きパスはuser_path?users_path?」「URLは/users? /user? /:id?」と混乱する場面が多く、何となくモヤモヤしたままチュートリアルを一周してしまった方も多いはずです(…よね?)。そんな初学者仲間の皆様の参考になれば幸いです。
[環境]
ruby 2.6.3
Rails 6.0.3.4RESTfulな7つのルーティング(基礎)
リソース名は、(例によって)users リソースとします。
routes.rbresources :usersこのコードを実装すると生成される7つのルーティングは、
アクション順でみると
action名 HTTPreq URL 名前付きパス users#index GET /users users_path users#show GET /users/:id user_path users#new GET /users/:id/new new_user_path users#create POST /users users_path users#edit GET /users/:id/edit edit_user_path users#update PATCH/PUT /users/:id user_path users#destroy DELETE /users/:id user_path こうですね。
(※アクション名は コントローラ名#アクション名 と言う記法に倣っています)7つのアクション名とその順番に関しては、…頑張って覚えましょう。
ちょっとした覚えるコツとしては、
indexとshowはペア。indexはuserの集合(全体)を、showはuserの個体を「表示する機能」としてまとめて覚えましょう。
newとcreateもペア。newテンプレートのフォームに入力した情報を基に、createアクションに繋げてuserインスタンスを生成する事が多いでしょう。
editとupdateもペア。editテンプレートのフォームに入力した情報を基に、updateアクションに繋げてuserインスタンスの情報を更新する事が多いでしょう。
最後にdestroy。これはペアがいない孤独なアクション君です。URLと名前付きパスの関係
次に、URLや名前付きパスの2つの命名ルールを理解しましょう。
【ルール①】 /:id の有無で分類
まず前提として、URLは/users で始まります。ここは複数形で統一です。
その後ろに/:idが有るか無いかで分類します。/:idなしの時→ 名前付きパスは「複数形」のusers_path
/:idありの時→ 名前付きパスは「単数形」のuser_path【ルール②】 URLの後ろに付くオプションは、名前付きパスでは文頭に
ここで言うオプションとは、/users/○○
/users/:id/○○上の○○部分。
このパターンのURLの名前付きパスはルール①も踏まえて
オプション名_users_path または
オプション名_user_path となります。このように、頭にオプション名がくると言うルールで命名されています。
以上の2つのルールが分かれば、
/users/:id/new の名前付きパスは new_user_path同様に
/users/:id/edit の名前付きパスは edit_user_pathとなる事が理解できますね。
(この法則は、後で出てくるmemberやcollectionで生成する任意ルーティングにも当てはまりますので、覚えておきましょう!)
名前付きパスから7つのルーティングを再整理
今度は、今覚えた名前付きパスごとにルーティングを再整理しましょう。
名前付きパス HTTPreq URL 反応するaction users_path GET /users users#index 〃 POST /users users#create new_user_path GET /users/:id/new users#new edit_user_path GET /users/:id/edit users#edit user_path GET /users/:id users#show 〃 PATCH/PUT /users/:id users#update 〃 DELETE /users/:id users#destroy こうなります。
indexとcreateの2つがusers_path。
これはuserの集合(コレクション)に対するアクションと覚えましょう。
index:コレクション全体を表示する
create:コレクション全体に、1個インスタンスを加えるあとの5つはuser_pathがベースになります。
newとeditは先ほど見たようにオプション付きの名前付きパスですね。このように2つの方向から整理すると、だいぶ理解も深まって来たでしょうか。
ちなみに、$ rails routes を実行した際にはこちらの「名前付きパスごと」にルーティングが表示されるので、じっくり眺めてみると更に理解が深まるでしょう。
memberメソッドとcollectionメソッド
次に、usersリソースに任意のルーティングを加えるmemberメソッド、collectionメソッド
この2つのメソッドが生成するURLと名前付きパスについても見ていきましょう。これらは、resources :users にブロックとして渡します(ネストします)。
routes.rbresources :users do member do get :foo, :bar end collection do get :hogehoge end end各メソッドの特性:
memberメソッドは、「user_path」に対してアクションを追加。
collectionメソッドは、「users_path」に対してアクションを追加。メソッドの書き方:
get :foo, :bar の行に注目して下さい。
「HTTPリクエストの型 :任意アクション名, :任意アクション名 」と言う書き方になります。
※アクション名は複数渡すことも可能です。上の例では、users#foo, users#bar, users#hogehoge
と言う3つのアクション(メソッド)が生成されました。各アクションに対応するルーティングは、
『アクション名=URLや名前付きパスのオプション』
と考えると分かりやすいです。
オプションですから、URLでは「後ろ」に、名前付きパスでは「文頭」に来るルールでしたね。生成されたfooアクション、barアクション、hogehogeアクションを含めて
名前付きパスごとにルーティングを整理し直します。今の皆様方であれば、なぜこのような名前付きパスになるのか。
なぜこのようなURLなのか、理解できるはずです。
名前付きパス HTTPreq URL 反応するaction hogehoge_users_path GET /users/hogehoge users#hogehoge foo_user_path GET /users/:id/foo users#foo bar_user_path GET /users/:id/bar users#bar users_path GET /users users#index 〃 POST /users users#create new_user_path GET /users/:id/new users#new edit_user_path GET /users/:id/edit users#edit user_path GET /users/:id users#show 〃 PATCH/PUT /users/:id users#update 〃 DELETE /users/:id users#destroy 以上となります。
最後までお付き合い頂きありがとうございました。
拙い内容だったかと思いますが、
本記事が少しでも名前付きパスで混乱していた方の助けになれば幸いです。またよろしくお願いいたします
ながえもん
- 投稿日:2020-12-15T11:55:12+09:00
railsのArrayでnilと空白文字列の配列を除去して連結して表示する
色々なメソッドの結果を1つの文字列として出したかったが、メソッドの返り値には
nilのケースと""のケースの2種類が存在していた。
railsのArrayの#compactはnilしか省いていくれない。
rejectを使って省いた。pry(main)> ["メソッドA", nil, "", "メソッドB", "メソッドC"].reject { |e| e.to_s.empty? }.join('、') => "メソッドA、メソッドB、メソッドC"
- 投稿日:2020-12-15T11:47:51+09:00
【Ruby】HTTPリクエストでJSONが返る外部APIを使う
やりたいこと
HTTPリクエストをしてJSONが返る外部APIをRubyで使いたい。
JSONからRubyで使えるハッシュを取得したい。結論
#各種ライブラリを読み込む require 'net/http' require 'json' require 'uri' #uriライブラリ>URIモジュール #該当するインスタンスを生成して返す uri = URI.parse('http://hoge.com') # => #<URI::HTTP http://hoge.com> #net/httpライブラリ>Net::HTTPクラス #URL先にGETリクエストを送り、そのボディを"文字列"として返す。 json = Net::HTTP.get(uri) #josonライブラリ>JSONモジュール #取得したJSON形式の文字列を、Rubyオブジェクトに変換して返す JSON.parse(json)以上で、URLにGETリクエストを送り、JSON形式のデータをRubyで使えるハッシュを取得できる。
公式リファレンス
URIモジュール
Net::HTTPクラス
JSONモジュールJSONとは
JavaScript Object Notation(JSON、ジェイソン)はデータ記述言語の1つである。軽量なテキストベースのデータ交換用フォーマットでありプログラミング言語を問わず利用できる[1]。名称と構文はJavaScriptにおけるオブジェクトの表記法に由来する。
https://ja.wikipedia.org/wiki/JavaScript_Object_NotationJavaScript だけではなく、Java, PHP, Ruby, Python など、様々な言語間のデータ交換、特に Ajax や REST API などで使用されています。
これまでは、共通データ定義言語として XML が利用されてきましたが、現在では、簡易的な JSON が利用されるケースが増えてきています。
http://www.tohoho-web.com/ex/json.html異なる言語間でデータをやり取りするための記述形式で、記法はJavaScriptに由来する。
以前は、データをやり取りする時はXMLが使われおり、今はJSONが主流となっている。JSONのルール
キーと値にコロンがつく。
文字列は""ダブルクォーテーションで囲む。
{ "hoge": "fuga" }シングルクォーテーションは使用できない。
❌{ 'hoge': 'fuga' }カンマを使うことで、複数のキーと値を指定することができる
{ "hoge": "fuga", "foo": 123 }配列や値のみでもOK!
["hoge","fuga"]
"hoge"
123まとめ
APIからJSONを取得し、Rubyで使うやり方とJSONについて、簡単にまとめました。
- 投稿日:2020-12-15T08:18:09+09:00
doorkeeper gemの導入手順
doorkeeper gemの用途
doorkeeperはOAuthのプロバイダ機能を提供するためのgemです。
例えば公開用のAPIを作成した場合にアクセストークンを発行するためOAuth2.0で認可しトークン発行させるなどで使用します。この記事の注意点
今回は実際にdoorkeeperを導入してOAuthプロバイダとして機能する確認までを行いますが細かな設定は省略しています。
実際に業務で開発するアプリケーションではroutingの設定や画面のカスタマイズ、doorkeeper.rbのカスタマイズなどもっと丁寧に設定する必要があるのでご注意ください。ドキュメントについて
以下を参考にdoorkeeperの導入を行いました。
doorkeeper gem はドキュメントが整っているので導入しやすいと思います。
https://github.com/doorkeeper-gem/doorkeeper
https://github.com/doorkeeper-gem/doorkeeper/wiki
https://doorkeeper.gitbook.io/guides/doorkeeper導入環境
今回は以下の環境のrailsアプリケーションが既にある想定で進めていきます。
rails: 6.0.3.4 ruby: 2.7.1 その他gem: devise(4.7.3)userテーブルの作成
今回userテーブルを事前に用意してログイン周りはdevise gemで管理します。
userテーブルのschemaは以下になります。
(今回Doorkeeperの導入のために作成した簡易的なテーブルです)create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at # Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip t.string :fullname t.string :fullname_ja t.boolean :admin, null: false, default: false t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: truegemを追加
Gemfileへ以下を追加しbundle installします。
gem 'doorkeeper'bundle install現時点(2020/12/01)では以下のバージョンがinstallされました。
doorkeeper (5.4.0)doorkeeperの環境をセットアップ
doorkeeperをinstallします。
rails generate doorkeeper:install実行結果
create config/initializers/doorkeeper.rb create config/locales/doorkeeper.en.yml route use_doorkeeper =============================================================================== There is a setup that you need to do before you can use doorkeeper. Step 1. Go to config/initializers/doorkeeper.rb and configure resource_owner_authenticator block. Step 2. Choose the ORM: If you want to use ActiveRecord run: rails generate doorkeeper:migration And run rake db:migrate Step 3. That's it, that's all. Enjoy! ===============================================================================以下の2つのファイルの作成とdoorkeeperを使うためのroutingが生成されます。
config/initializers/doorkeeper.rb config/locales/doorkeeper.en.ymlmigrationファイル作成
次にOAuth Applications、 Access Grants、 Access Tokensのテーブル用のmigrationファイルを作成するために以下を実行します。
bundle exec rails generate doorkeeper:migration実行結果
create db/migrate/20201201064749_create_doorkeeper_tables.rb
db/migrate/yyyymmddhhmmss_create_doorkeeper_tables.rbファイルが作成されるのでこれを編集していきます。
今回はuser model が resource ownerとなるように設定していきます。# frozen_string_literal: true class CreateDoorkeeperTables < ActiveRecord::Migration[6.0] def change create_table :oauth_applications do |t| t.string :name, null: false t.string :uid, null: false t.string :secret, null: false # Remove `null: false` if you are planning to use grant flows # that doesn't require redirect URI to be used during authorization # like Client Credentials flow or Resource Owner Password. t.text :redirect_uri, null: false t.string :scopes, null: false, default: '' t.boolean :confidential, null: false, default: true t.timestamps null: false end add_index :oauth_applications, :uid, unique: true create_table :oauth_access_grants do |t| t.references :resource_owner, null: false t.references :application, null: false t.string :token, null: false t.integer :expires_in, null: false t.text :redirect_uri, null: false t.datetime :created_at, null: false t.datetime :revoked_at t.string :scopes, null: false, default: '' end add_index :oauth_access_grants, :token, unique: true add_foreign_key( :oauth_access_grants, :oauth_applications, column: :application_id ) create_table :oauth_access_tokens do |t| t.references :resource_owner, index: true # Remove `null: false` if you are planning to use Password # Credentials Grant flow that doesn't require an application. t.references :application, null: false # If you use a custom token generator you may need to change this column # from string to text, so that it accepts tokens larger than 255 # characters. More info on custom token generators in: # https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator # # t.text :token, null: false t.string :token, null: false t.string :refresh_token t.integer :expires_in t.datetime :revoked_at t.datetime :created_at, null: false t.string :scopes # The authorization server MAY issue a new refresh token, in which case # *the client MUST discard the old refresh token* and replace it with the # new refresh token. The authorization server MAY revoke the old # refresh token after issuing a new refresh token to the client. # @see https://tools.ietf.org/html/rfc6749#section-6 # # Doorkeeper implementation: if there is a `previous_refresh_token` column, # refresh tokens will be revoked after a related access token is used. # If there is no `previous_refresh_token` column, previous tokens are # revoked as soon as a new access token is created. # # Comment out this line if you want refresh tokens to be instantly # revoked after use. t.string :previous_refresh_token, null: false, default: "" end add_index :oauth_access_tokens, :token, unique: true add_index :oauth_access_tokens, :refresh_token, unique: true add_foreign_key( :oauth_access_tokens, :oauth_applications, column: :application_id ) # userがresource_ownerとなるようにしてコメントアウトを外します。 # Uncomment below to ensure a valid reference to the resource owner's table # add_foreign_key :oauth_access_grants, <model>, column: :resource_owner_id # add_foreign_key :oauth_access_tokens, <model>, column: :resource_owner_id ↓↓↓↓↓ add_foreign_key :oauth_access_grants, :users, column: :resource_owner_id add_foreign_key :oauth_access_tokens, :users, column: :resource_owner_id end endmigrationを実行します。
rails db:migrate RAILS_ENV=development次の3つのテーブルが作成されます。
oauth_applications oauth_access_grants oauth_access_tokensdoorkeeper.rb の設定
次にdoorkeeperの設定ファイルである
config/initializers/doorkeeper.rbを編集していきます。
設定方法は
https://doorkeeper.gitbook.io/guides/ruby-on-rails/configuration
を参考に進めていきます。編集した箇所のみ記載しています。
# frozen_string_literal: true Doorkeeper.configure do 〜〜略〜〜 # このブロックは、resource owner が認証されているかどうかを確認するために呼び出されます。 # 今回はdeviseを使用しているので # https://doorkeeper.gitbook.io/guides/ruby-on-rails/configuration # を参考に修正します。 # This block will be called to check whether the resource owner is authenticated or not. resource_owner_authenticator do raise "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}" # Put your resource owner authentication logic here. # Example implementation: # User.find_by(id: session[:user_id]) || redirect_to(new_user_session_url) end ↓↓↓↓ # 以下のように修正します。 resource_owner_authenticator do current_user || warden.authenticate!(scope: :user) end # ここではoauth application のへのアクセスを制御することができます。 # 今回はusersテーブルへadminフラグを追加してadminフラグが立っている場合にのみアクセスできるようにするため # コメントアウト部分を外します。 # admin以外のuserがアクセスした場合は 403 Forbidden レスポンスを返します。 # If you didn't skip applications controller from Doorkeeper routes in your application routes.rb # file then you need to declare this block in order to restrict access to the web interface for # adding oauth authorized applications. In other case it will return 403 Forbidden response # every time somebody will try to access the admin web interface. # # admin_authenticator do # # Put your admin authentication logic here. # # Example implementation: # # if current_user # head :forbidden unless current_user.admin? # else # redirect_to sign_in_url # end # end ↓↓↓↓ admin_authenticator do # Put your admin authentication logic here. # Example implementation: if current_user head :forbidden unless current_user.admin? else redirect_to new_user_session_url end end 〜〜略〜〜 # access_token_expires_in オプション # アクセストークンの有効期限の設定です。 # defaultでは2時間。変更する場合はコメントアウトを外して編集します。 # 有効期限を無効にする場合は、これを「nil」に設定します。 # # 今回は24時間へ変更します。 # Access token expiration time (default: 2 hours). # If you want to disable expiration, set this to `nil`. # # access_token_expires_in 2.hours access_token_expires_in 24.hours 〜〜略〜〜 # scope # プロバイダのアクセストークンスコープの定義 # 詳細については、次のサイトを参照してください。 # https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes # # 今回は # default_scopes :public と # optional_scopes :write # を設定しておきます。 # Define access token scopes for your provider # For more information go to # https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes # # default_scopes :public # optional_scopes :write, :update ↓↓↓↓ default_scopes :public optional_scopes :write # enforce_configured_scopes オプション # 「default_scopes」や「optional_scopes」にない任意のスコープでのアプリケーションの作成や更新を禁止します。 # (デフォルトでは禁止しない) # # 使用するためコメントアウトを外します。 # Forbids creating/updating applications with arbitrary scopes that are # not in configuration, i.e. +default_scopes+ or +optional_scopes+. # (disabled by default) # # enforce_configured_scopes ↓↓↓↓ enforce_configured_scopes # force_ssl_in_redirect_uri オプション # ネイティブではないリダイレクト用の uris で HTTPS プロトコルを強制的に使用します。 # (development環境以外ではデフォルトで有効になっています) # OAuth2 は通信のセキュリティを HTTPS プロトコルに委譲するので、これを有効にしておくのが賢明です。 # proc, lambda, block などの呼び出し可能なオブジェクトは、 # 条件付きチェックを可能にするために使用することができます # (例えば、localhostへの非SSLリダイレクトを許可するなど) # # 今回は使用するため以下の設定を行います。 # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled # by default in non-development environments). OAuth2 delegates security in # communication to the HTTPS protocol so it is wise to keep this enabled. # # Callable objects such as proc, lambda, block or any object that responds to # #call can be used in order to allow conditional checks (to allow non-SSL # redirects to localhost for example). # # force_ssl_in_redirect_uri !Rails.env.development? # # force_ssl_in_redirect_uri { |uri| uri.host != 'localhost' } ↓↓↓↓ #development, test環境ではSSL以外でも許容し、それ以外はSSLのみ許可します。 force_ssl_in_redirect_uri !(Rails.env.development? || Rails.env.test?) # Client Credentials や Resource Owner Password Credentialsのような # URI の無い OAuth グラントフローを使用するように Doorkeeper が設定されている場合に、 # アプリケーションに空のリダイレクト URI を設定できるようにします。 # このオプションはデフォルトではオンに設定されており、設定されたグラントの種類をチェックしますが、 # データベーステーブル「oauth_applications」の「redirect_uri」カラムから「NOT NULL」制約を # 手動で削除する必要があります。 # この機能を完全に無効にするには allow_blank_redirect_uri false のコメントアウトを外すか # カスタムチェックを定義することができます。 # # 今回はredirect_uri にNULLを許可しないのでコメントアウトを外します。 # Allows to set blank redirect URIs for Applications in case Doorkeeper configured # to use URI-less OAuth grant flows like Client Credentials or Resource Owner # Password Credentials. The option is on by default and checks configured grant # types, but you **need** to manually drop `NOT NULL` constraint from `redirect_uri` # column for `oauth_applications` database table. # # You can completely disable this feature with: # # allow_blank_redirect_uri false # # Or you can define your custom check: # # allow_blank_redirect_uri do |grant_flows, client| # client.superapp? # end ↓↓↓↓ allow_blank_redirect_uri false # どのようなgrant flowsを有効にするかを文字列の配列で指定します。 # 有効な文字列と有効なフローは以下の通りです。 # # 今回は authorization_code のみ許可するため以下のように記述します。 # Specify what grant flows are enabled in array of Strings. The valid # strings and the flows they enable are: # # "authorization_code" => Authorization Code Grant Flow # "implicit" => Implicit Grant Flow # "password" => Resource Owner Password Credentials Grant Flow # "client_credentials" => Client Credentials Grant Flow # # If not specified, Doorkeeper enables authorization_code and # client_credentials. # # implicit and password grant flows have risks that you should understand # before enabling: # http://tools.ietf.org/html/rfc6819#section-4.4.2 # http://tools.ietf.org/html/rfc6819#section-4.4.3 # # grant_flows %w[authorization_code client_credentials] ↓↓↓↓ grant_flows %w[authorization_code] endOAuthアプリケーションの作成
(※)事前にadminフラグの付いたアカウントと付いていないアカウントを2つ作っておきます。
> rails sで local Serverを起動します。
http://localhost:3000/users/sign_inへアクセスしadminフラグが付いていないアカウントでログインし
http://localhost:3000/oauth/applicationsへアクセスするとstatus 403でアクセスできないことを確認します。
今後はadminフラグが付いているアカウントでログインし
http://localhost:3000/oauth/applications[New Application]ボタンをクリックして
OAuthのアプリケーションを作成していきます。Name: ExampleApp Redirect URI: http://localhost:3001/callback Confidential: ON Scopes: public writeを設定して[Submit]をクリックします。
OAuthアプリケーションが作成され以下のような画面が表示されると思います。
実際に作成されたデータを rails consoleで見てみると以下のようなデータが存在することが分かります。
irb(main):003:0> Doorkeeper::Application.all => #<ActiveRecord::Relation [#<Doorkeeper::Application id: 1, name: "ExampleApp", uid: "4y5OWnkpJM9wHwk6F9LHTKBB4Daz0x_3cwDdeqCcQD4", secret: "7Ufjfqcdt36rSkvx8PLHVRSHh-IL2KLk_9v1Tp4XBFs", redirect_uri: "http://localhost:3001/callback", scopes: "public write", confidential: true, created_at: "2020-12-14 17:44:08", updated_at: "2020-12-14 17:44:08">]> irb(main):009:0>続いてアクセストークンの発行テストをするためにはDoorkeeperのClientが必要なので↓のclientを使用します。
https://github.com/Nobuo-Hirai/sample_oauth_client以下の手順でセットアップしていきます。
git clone git@github.com:Nobuo-Hirai/sample_oauth_client.git cd sample_oauth_client bundle config set --local path 'vendor/bundle' bundle install # .env.developmentファイルを作成します。 touch .env.development # .env.developmentファイルへ以下の内容を記述します。 # 先ほどOAuthプロバイダの方で作成したOAuthアプリケーションのUIDとSECRET、REDIRECT URIなどを記述します。 ## ID for your app registered at the provider CLIENT_ID=4y5OWnkpJM9wHwk6F9LHTKBB4Daz0x_3cwDdeqCcQD4 ## Secret CLIENT_SECRET=7Ufjfqcdt36rSkvx8PLHVRSHh-IL2KLk_9v1Tp4XBFs ## URL to the provider SITE=http://localhost:3000/ AUTHORIZE_URL=oauth/authorize CALLBACK_URI=http://localhost:3001/callback OAUTH_PROVIDER_URL=http://localhost:3000/oauth/authorize TOKEN_URL=oauth/token SCOPE=public write # port 3001で起動します。 rails s -p 3001http://localhost:3001
へアクセスすると下の画面が表示されるので[Authorize]をクリックします。
http://localhost:3000/
の認可画面が表示されると思います。
これは[public write]という権限を持ったアクセストークンの発行に同意するかどうかという意味になります。
[Authorize]をクリックします。
http://localhost:3001/callback
の画面が表示されアクセストークンが表示されたと思います。
認可画面で[Authorize]をクリック後にcallbackURLへ認可コードパラメータが返ってくるのでそれをもとにアクセストークンを取得しています。
# 認可コードよりaccess_tokenを取得 def callback client = OAuth2::Client.new( ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: ENV["SITE"], authorize_url: ENV["AUTHORIZE_URL"], token_url: ENV["TOKEN_URL"], ) @access_token = client.auth_code.get_token( params[:code], redirect_uri: ENV["CALLBACK_URI"], ) end実際に作成されたAccessGrantとAccessTokenを rails consoleで確認する場合は以下のような感じで確認できます。
irb(main):010:0> Doorkeeper::AccessGrant.all => #<ActiveRecord::Relation [#<Doorkeeper::AccessGrant id: 1, resource_owner_id: 1, application_id: 1, token: "j0UY2MVee0mW9RmjVhPTZ6hY7yzw44nM5YOS51ZhxUk", expires_in: 600, redirect_uri: "http://localhost:3001/callback", created_at: "2020-12-14 17:54:24", revoked_at: "2020-12-14 17:54:24", scopes: "public write">]> irb(main):011:0> Doorkeeper::AccessToken.all => #<ActiveRecord::Relation [#<Doorkeeper::AccessToken id: 1, resource_owner_id: 1, application_id: 1, token: "l2wm7_P1Fk2ZZEAI5jhEFWfsJYUzbZYk_QcpWKZCSFU", refresh_token: nil, expires_in: 86400, revoked_at: nil, created_at: "2020-12-14 17:54:24", scopes: "public write", previous_refresh_token: "">]>最後に
doorkeeper gemはドキュメントが豊富なため導入のハードルは低いと思います。
ただdoorkeeper.rbで設定できるオプションが多いため全て確認するには非常に時間がかかります。
doorkeeper.rbでの設定がこのgemのポイントなので色々試してみようと思います。
- 投稿日:2020-12-15T08:17:12+09:00
プログラミングは神になれる。
プログラミングの楽しさについて書きます。
それはオブジェクト思考がまるで生き物を作る神になった気分になれるところです。
そのコードの例が下記になります。
class Human @name @age @wanryoku def walk end def eat end def age= num if num < 0 puts @age = 0 return end @age = num end end class SuperMan < Human def me_kara_beam end end human = Human.new human.walk #ok human.me_kara_beam #error spMan = SuperMan.new spMan.walk #ok spMan.me_kara_beam #okこれは、ヒューマンは歩くことができるが、目からビームが出せない
スーパーマン(spMan)は歩くこと、目からビームを出すことができる
という処理です。一見難しそうに見えるプログラミングも、こうみると面白いのかなとおもったり。
日々の学習をドキュメントに残してるので、とっても暇な時みてみてください。
https://www.notion.so/Ruby-Ruby-on-Rails-84f5c88189474fa4aae5e00cba856226













