20210510のRailsに関する記事は17件です。

【Rails6】deviseで独自カラムを追加して使用する方法 ③

これまで これまでに、deviseのインストール・Userモデルの作成・Viewの作成を行いました。 今回は、コントローラ・ルーティング・モデルのカスタマイズを行っていきたいと思います。 ▽前回の記事はこちら▽ 【Rails6】deviseで独自カラムを追加して使用する方法 ① 【Rails6】deviseで独自カラムを追加して使用する方法 ② ① Controller 作成 View を追加するrails g devise:controllers users コマンドを実行 $ rails g devise:controllers users Running via Spring preloader in process 5360 create app/controllers/users/confirmations_controller.rb #認証メール再送用 create app/controllers/users/passwords_controller.rb #パスワード変更関連用 create app/controllers/users/registrations_controller.rb #ユーザ登録関連用 create app/controllers/users/sessions_controller.rb #ログイン用 create app/controllers/users/unlocks_controller.rb #ロック解除メール再送用 create app/controllers/users/omniauth_callbacks_controller.rb #外部ログイン用 =============================================================================== Some setup you must do manually if you haven't yet: Ensure you have overridden routes for generated controllers in your routes.rb. For example: Rails.application.routes.draw do devise_for :users, controllers: { sessions: 'users/sessions' } end =============================================================================== 上記コマンドで、ひと通り devise 用の Controller ( 各ファイルに # でファイルの用途を記載しました ) が生成されます。 上記のコマンド結果に記載の通り、カスタマイズしたコントローラを反映させるためには、ルーティング設定をしないといけません。 ② routes.rb 編集 /config/routes.rb ファイルを編集していきます。 routes.rb_編集前 Rails.application.routes.draw do devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end routes.rb_編集後 Rails.application.routes.draw do devise_for :users, controllers: { confirmations: 'users/confirmations', passwords: 'users/passwords', registrations: 'users/registrations', sessions: 'users/sessions', unlocks: 'users/unlocks' } # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end 上記のように、カスタマイズするコントローラを記載していきます。 ③ コントローラのカスタマイズ 今回は、独自カラムを追加していますが、特段コントローラーを変更する必要はありませんが、 今回Devise用の レイアウトテンプレート を使いページを表示させるため、コントローラへレイアウトテンプレートを読み込ませる文を追記します。 また、 レイアウトテンプレートのページタイトルを <title><%= @page_title%> | Sample</title としてインスタンス変数を入れページ毎のページタイトルに対応させているので、コントローラ へ ページタイトルも追記します。 同じ作業になるので、例で /users/confirmations_controller.rb に追記します。 confirmations_controller.rb_編集前 # frozen_string_literal: true class Users::ConfirmationsController < Devise::ConfirmationsController # GET /resource/confirmation/new # def new # super # end # POST /resource/confirmation # def create # super # end # GET /resource/confirmation?confirmation_token=abcdef # def show # super # end # protected # The path used after resending confirmation instructions. # def after_resending_confirmation_instructions_path_for(resource_name) # super(resource_name) # end # The path used after confirmation. # def after_confirmation_path_for(resource_name, resource) # super(resource_name, resource) # end end confirmations_controller.rb_編集後 # frozen_string_literal: true class Users::ConfirmationsController < Devise::ConfirmationsController layout "devise" # GET /resource/confirmation/new def new @page_title = "確認メールが届かない場合" super end # POST /resource/confirmation # def create # super # end # GET /resource/confirmation?confirmation_token=abcdef # def show # super # end # protected # The path used after resending confirmation instructions. # def after_resending_confirmation_instructions_path_for(resource_name) # super(resource_name) # end # The path used after confirmation. # def after_confirmation_path_for(resource_name, resource) # super(resource_name, resource) # end end layout "login"とコントローラ内に記載すると、Rails側で devise.html.erbをレイアウトテンプレートとして認識します。 ④ モデルのカスタマイズ モデルへは、Deviseで使用する機能を追記します。 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 end 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, :trackable, :confirmable, :lockable, :timeoutable end No 機能 説明 1 database_authenticatable デフォルト 2 registerable デフォルト 3 recoverable デフォルト 4 rememberable デフォルト 5 validatable デフォルト 6 trackable ログイン回数・ログイン日時・IPアドレスの記録 7 confirmable 認証メールの送信・メール認証の確認 8 lockable ログインの連続失敗によるロック・ロック解除メール送信  9 timeoutable 一定期間動作のないアカウントのログアウト 10 omniauthable 外部サービスの認証 (Twitter・Facebookなど) 上記の機能の説明を表にしてみました。 ここまで行うと、使用する機能・コントローラ・レイアウトテンプレートが反映されます。 長くなりましたので、記事を分けてご紹介しようと思います。 次回は、デバイスの設定 ( 独自カラムでログインできるようにする ) 行っていきたいと思います。 ▽次の記事はこちら▽ 【Rails6】deviseで独自カラムを追加して使用する方法 ④
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ポートフォリオをECSで!】Rails×NginxアプリをFargateにデプロイするまでを丁寧に説明してみた(VPC作成〜CircleCIによる自動デプロイまで) 後編

概要 ↓前編 後編では、独自ドメインの取得〜CircleCIの自動デプロイの設定まで行いたいと思います。 【前編】 1.VPCの設定 2.RDSの設定 3.ECRの設定 4.ECSの設定 【後編】 5.独自ドメインの設定(お名前ドットコム) 6.ACMの設定 7.ALBの設定 8.CircleCIの設定 5.独自ドメインの取得(お名前ドットコム) 前編でパブリックIPでFargateにデプロイしたアプリにアクセスできましたが、ここから独自ドメインを使ってアクセスできるように設定していきます。 以下のお名前ドットコムにアクセスして独自ドメインの設定をしていきましょう。 5.1 独自ドメインの設定 お名前ドットコムにアクセスしたら検索バーに取得したいドメインを入力し検索ボタンを押します。 筆者はmodern-tech.workを選択しました。 次に希望のドメインにチェックを入れてください。2のサーバーは利用しないを選択してください。 お申し込む内容確認画面に飛ぶので、ログインして購入画面に進みます。Whoisメールとドメインプロテクションは好みに合わせてチェックしてください。はじめの利用する場合は初めてご利用する方を選択して、登録を行ってください。 支払い方法を登録して申し込むをクリックすることで申し込みが完了します。 ※ ドメインを取得して一定時間後、ドメインの取得認証のメールアドレスが登録したメールアドレス宛に送信されます。 こちらの認証がメールが届いてから2週間以内に行われない場合、ドメインが使用不可になってしまいますので注意してください。 (筆者はこちらが原因でサーバーが止まり原因特定にかなりの時間を要しました汗) この後、以下のお名前ドットコム.naviにアクセスして操作をしていきますのでリンクを他のタブで開いておいてください。 5.2 Route53のホストゾーンの設定 Route53のホストゾーンの設定を行います。 AWSに戻り、サービスからRoute53を選び、Route53→ホストゾーン→ホストゾーンの作成で選択します。 そして以下のように設定をし、ホストゾーンの作成をクリックします。 タグ 値 ドメイン名 お名前ドットコムで取得したドメイン名 説明 適当 タイプ パブリックホストゾーン 5.3 ネームサーバーの設定 5.2で作成したホストゾーンを選択して詳細画面に飛んでください。 タイプがNSのルーティング先4つを最後の.を省いてコピーしてください。 例えば、xxx-00.org.であればxxx-00.orgという感じです。 コピーが終わったら他のタブで開いておいたお名前ドットコム.naviに移動します。 ドメイン→利用ドメイン一覧→取得したドメイン→ネームサーバー情報→ネームサーバーの変更で選択し、その他→その他ネームサーバーを使うより先程Route53でコピーした4つのネームサーバーを入力します。入力が完了したら確認をクリックしてください。 ネームサーバーの登録完了したら以下のコマンドをローカルターミナルで叩きRoute53のネームサーバーがドメイン名と紐付いているか確認してください。こちらは . が最後に入っています terminal dig 取得したドメイン名 NS +short 6.ACMの設定 AWS Certificate Manager(ACM)はSSL証明書発行サービスです。AWS内のサービスにSSLを適用させなければいけない制約はあるもののACM自体は無料で利用することができます。 6.1 ACMのリクエスト サービスからACMを検索し、ACM→証明書のリクエストを選択します。 パブリック証明書を選択して証明書のリクエストをクリックします。 ドメインの追加画面になったら以下のように設定を行います。 *.取得したドメイン名をドメイン名に追加することで、www.modern-tech.workのようなサブドメインを設定することが可能になります。これにより、サブドメインを設定することで無限にドメインを追加する事ができるようになります。 ドメイン名 取得したドメイン名 *.取得したドメイン名 検証の選択はDNSの検証を選択し次へをクリックします。 タグの追加で以下のようにタグを設定し確認をクリックします。 タグ 値 タグ名 sample-acm 確認画面に行くので、確定とリクエストをクリックしてSSL証明書の発行を行います。 検証保留中の画面になるので続行を押して検証が完了するまで待ちます。 6.2のRoute53のレコードの追加に使いますのでDNS設定をファイルにエクスポートを押してCNAMEレコードの設定ファイルをダウンロードしてください。 そして、取得したDNS設定ファイルのレコード名・レコードタイプ・レコードバリューの3つをコピーしてください。(取得したドメイン名と*.取得したドメイン名で同じになってるはずです) 6.2 ACMにCNAMEレコードを追加 Route53→ホストゾーン→取得したドメイン名を選択し、レコードを作成をクリックします。 タグ 値 レコード名 コピーしたレコード名のドメイン名より前の部分 レコードタイプ CNAME 値 コピーしたレコードバリュー(最後の.も含めます) 7.ALBの設定 ECSのクラスターを形成する際にロードバランサーが必要になります。 ロードバランサーとはサーバーに掛かる負荷を分散することで、1つあたりのサーバーに掛かる負荷を抑えるものです。 これにより、サーバー一つにかかる負荷が小さくなりサーバーダウンが防げます。 7.1 ALBのセキュリティグループの作成 VPC→セキュリティグループ→セキュリティグループの作成を選択します。 タグ 値 名前 sample-alb-security 説明 適当 VPC sample_vpc インバウンドルールは以下のようにhttp通信とhttps通信ができるように2つ設定します。 タグ 値 タイプ HTTP リソースタイプ 任意の場所 タグ 値 タイプ HTTPS リソースタイプ 任意の場所 アウトバウンドルールは以下のようにECSのセキュリティグループへのみ通信を制御するようにデフォルトの設定を削除して以下の設定を追加します。 設定が完了したらセキュリティグループを作成をクリックします。 タグ 値 タイプ HTTP 送信先タイプ カスタム 送信先 sample_ecs_security 7.2 ALBの作成 ※この作業はSSL証明書の発行が完了しないとできないためACMからリクエストした証明書が発行されていることを確認してから実行してください。 EC2→ロードバランサー→ロードバランサーの作成を選択します。 最終的に独自のドメインを取得し、HTTPS通信することを目標とするのでApplication Load Balancer(ALB)を選択します。 ロードバランサーの設定は以下のようにします。 アドオンサービスの設定はチェックせずに次の手順へ進みます。 タグ 値 名前 sample-alb リスナー HTTP, HTTPS(ポートは勝手に選択されます) VPC sample_vpc アベイラビリティゾーン ap-northeast-1a(sample_public_subnet1), ap-northeast-1c(sample_public_subnet2) ログ設定 ON セキュリティ設定の構成は以下のように設定します。 セキュリティポリシーはAWSで推奨されているデフォルトのELBSecurityPolicy-2016-08で次の手順に進みます。 タグ 値 証明書タイプ ACMから証明書を選択します 証明書の名前 発行した証明書 セキュリティグループの設定は7.1で作成したsample-alb-securityを選択して次の手順に進みます。 ルーティングの設定は以下のようにします。ヘルスチェックはルートパスで行われるように設定しています。設定が完了したら次の手順へ進みます。 タグ 値 ターゲットグループ 新しいターゲットグループ 名前 sample-target ターゲットの種類 IP プロトコル HTTP ポート 80 プロトコルバージョン HTTP1 ターゲットは登録せずに確認をして作成を行ってください。 7.3 ALBを含めたECSのサービスの作成 ECS→クラスター→sample-clusterを選択してくだい。 そして、前編の4章で作成したsample-serviceを削除して新しいサービスを作成します。sample-serviceを削除した後、サービス→作成を選択します。 サービス設定は以下のように行い次のステップに進みます。 タグ 値 起動タイプ FARGATE タスク定義 ファミリー sample タスク定義 リビジョン latest クラスター sample-cluster サービス名 sample-alb-service タスクの数 1 ネットワーク構成は以下のように行います。 タグ 値 クラスターVPC sample_vpc サブネット sample_public_subnet1 セキュリティグループ sample_ecs_security パブリック IP の自動割り当て ENABLED 今回はロードバランサーの設定も加えます。 タグ 値 ロードバランシング Application Load Balancer ロードバランサー名 sample-alb ロードバランス用コンテナにnginxが選択されていると思うのでロードバランサーに追加をクリックします。 その後、ターゲットグループ名をsample-targetとして次のステップに行きます。 Auto Scalingの設定は今回はしないでサービスを作成します。 サービスが起動するまで待ちましょう。 7.4 ドメインとロードバランサーの結びつけ 最後に独自ドメインからサービスにアクセスできるようにロードバランサーと結びつけをします。 Route53→ホストゾーン→取得したドメイン名→レコードの作成を選択します。 以下のように設定します。 タグ 値 レコード名 空 レコードタイプ A トラフィックのルーティング先 エイリアスON→Application Loadbalancer→ap-northeast-1→sample-alb また、サブドメインからもアクセスできることを確認するため以下のレコードもついでに追加します。 タグ 値 レコード名 www レコードタイプ A トラフィックのルーティング先 エイリアスON→Application Loadbalancer→ap-northeast-1→sample-alb ここまでの設定が完了したらhttp(s)://ドメイン名で検索してみてください。以下のようにアプリが開ければ成功です。 また、http(s)://www.ドメイン名でも同様にアプリが開けることを確認してください。 ※もし開けない場合、route53の設定が誤っていないか確認してみてください。また、ドメインがお名前ドットコムから発行されてからインターネットで使用できるようになるためタイムラグ(最大1日)が発生するのでしばらく待ってからアクセスを試みてください。 ルーティングの整備 httpsでの通信が確認できたらhttp通信があると暗号化されない通信に間違ってアクセスしてしまう可能性があるので、http通信を遮断します。 ALBのセキュリティグループのsample-alb-securityにアクセスしインバウンドルールの編集を選択します。 インバウンドルールが編集語に以下のようになっているのを確認してください。 再び、https://ドメイン名にアクセスしてアプリが表示されることを確認してください。また、http://ドメイン名にアクセスしてアクセスできなくなっていることを確認してください。 8.CircleCIの設定 最後にCircleCIの設定です。 こちらを設定することによりECSの自動てプロイが可能になります。 8.1 CircleCIのファイルを作成する 以下のようにCircleCIのファイルを作成してください。 今回はcircleciの自動デプロイをmasterブランチに限定しました。 .circleci/config.yml version: 2.1 orbs: aws-ecr: circleci/aws-ecr@6.15 aws-ecs: circleci/aws-ecs@2.0.0 workflows: # Nginxのデプロイ nginx-deploy: jobs: - aws-ecr/build-and-push-image: #AWS_ECR_ACCOUNT_URL => ${アカウントID}.dkr.ecr.${リージョン}.amazonaws.com account-url: AWS_ECR_ACCOUNT_URL # リージョン region: AWS_REGION aws-access-key-id: AWS_ACCESS_KEY_ID aws-secret-access-key: AWS_SECRET_ACCESS_KEY # ECRにリポジトリがなかったら創るか? create-repo: true # Dockerfleのパス(デフォルトはカレントディレクトリのパス) dockerfile: ./nginx/Dockerfile # ECRのリポジトリ repo: nginx-sample tag: "${CIRCLE_SHA1}" filters: branches: only: master - aws-ecs/deploy-service-update: requires: - aws-ecr/build-and-push-image # ECSのタスク定義名 family: 'sample' # ECSのクラスター名かARNのフルパス cluster-name: 'sample-cluster' # サービス名 service-name: 'sample-alb-service' # containerはタスク定義で設定したコンテナ名にする container-image-name-updates: "container=nginx,tag=${CIRCLE_SHA1}" # Railsのデプロイ rails-deploy: jobs: - aws-ecr/build-and-push-image: account-url: AWS_ECR_ACCOUNT_URL region: AWS_REGION aws-access-key-id: AWS_ACCESS_KEY_ID aws-secret-access-key: AWS_SECRET_ACCESS_KEY # ECRにリポジトリがなかったら創るか? create-repo: true dockerfile: ./Dockerfile.prod # ECRのリポジトリ repo: rails-sample tag: "${CIRCLE_SHA1}" filters: branches: only: master - aws-ecs/deploy-service-update: requires: - aws-ecr/build-and-push-image # ECSのタスク定義名 family: 'sample' # ECSのクラスター名かARNのフルパス cluster-name: 'sample-cluster' # サービス名 service-name: 'sample-alb-service' # containerはタスク定義で設定したコンテナ名にする container-image-name-updates: "container=rails,tag=${CIRCLE_SHA1}" 8.2 CircleCIの環境変数を設定 まずは以下のURLからCircleCIのプロジェクトにいき、Set Up Projectをクリックして対象のリポジトリをCircleCIが適用できるように設定します。 そして、環境変数に以下を設定します。 環境変数 値 AWS_ACCESS_KEY_ID IAMのアクセスキーID AWS_SECRET_ACCESS_KEY IAMのシークレットアクセスキー AWS_REGION ECR・ECSを作成したリージョン(ap-northeast-1) AWS_DEFAULT_REGION ECR・ECSを作成したリージョン(ap-northeast-1) AWS_ECR_ACCOUNT_URL {IAMのアカウントID(数字だけのやつ)}.dkr.ecr.{IAMのリージョン}.amazonaws.com 8.3 CircleCIの自動デプロイテスト 試しにヘッダーの色を黄色に変更してgithubにプッシュしてみます。 みなさんも自分のアプリの一部を変更して自動デプロイができるか確かめてみましょう。 変更が済んだら以下のようにコマンドを叩きgithubへプッシュします。 terminal git checkout master git add . git commit -m "ciecleci sample test" git push origin master CircleCIのダッシュボードに行き自動てプロイがsuccessになっているのを確認してください。 以下のようになっていれば成功です。 CircleCIの自動てプロイが完了しているのを確認したらhttp://ドメイン名にアクセスして変更が適用されているか確認しましょう。 これで全ての工程は終了です。お疲れさまでした。 もし、使わなくなった場合は結構お金を持っていかれるので作成した構成を削除してください。 おわり ここまで長かったですがECSの自動デプロイまで説明してきました。 AWSの構成の仕方で途中でエラーが出たり、不具合があった場合はコメントを残してくださると幸いです。 よかったらLGTMをお願いします!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ポートフォリオをECSで!】Rails×NginxアプリをFargateにデプロイするまでを丁寧に説明してみた(VPC作成〜CircleCIによる自動デプロイまで) 前編

概要 現在、運営しているサークルの公式サイトを作成しているのですがデプロイ環境をEC2からECSに引っ越しました。その際に、色々ハマりながらデプロイしたので、方法を忘れないために書きます。 この方法を真似ればFargateへのデプロイが再現できてるので、ぜひ活用してください。 以下のようなインフラ構成を想定しております。 【前編】 1.VPCの設定 2.RDSの設定 3.ECRの設定 4.ECSの設定 【後編】 5.独自ドメインの設定(お名前ドットコム) 6.ACMの設定 7.ALBの設定 8.CircleCIの設定 追記 sample_public_subnetがtypoで写真内でsample_pubnet_subnetとなっていました。sample_public_subnetだと思って進めてください。 前提 Dockerの環境とAWSでIAMを管理者権限で作成していること前提で話を進めます。また、筆者は以下のような技術を使っています。 名前 バージョン リージョン 東京 Docker 20.10.5 Ruby 2.6.5 Rails 6.1.3 (バージョン5でも可) PostgreSQL 9.6.2 注意点 今回のECSの構成をするに当たり、料金が発生します。 お金をかけたくない場合は、すぐに作ったネットワーク構成を削除してください。 1.VPCの設定 Amazon Virtual Private Cloud(VPC)は、AWSの提供する機能の1つで、プライベートな可塑のネットワークを構成できます。このネットワークの中で、仮想サーバ「EC2」など多くのAWSのサービスが起動します。このネットワーク内にEC2やRDSなどを起動させることでアプリケーションを構築することができます。そして、サブネットとはVPCによって作成されるcidrブロックを分割したネットワーク群のことです。サブネット内にEC2やRDSなどを設置します。 以下のような設計で構成します。 1.1 VPCを作成 AWSのサービスよりVPCを選択して、VPCの作成をクリックします。 その後、以下のように設定していきます。 タグ 値 名前 sample_vpc CIDRブロック 10.0.0.0/16 ※テナンシーとは物理的にハードウェアを占領したいかで、必要がないためデフォルトを選択しています。 1.2 サブネットの作成 VPC内にサブネットを構成します。 サービスからサブネットを選択して、サブネットの作成を押します。 そして、以下のようにプライベートサブネット2つとパブリックサブネット2つの4つのサブネットを構成してください。(プライベートサブネットはVPC内部でのみ通信可能なサブネット(送信先が10.0.0.0/16)のことで、パブリックサブネットは外部のインターネットから通信可能なサブネットのことです。1.3のインターネットゲートウェイの作成と1.4のルートテーブルの作成の時にプライベートとパブリックの作成方法の違いを説明します。) サブネットのCIDRブロックはVPCのCIDRブロックの16bit目(10.0)までは等しい必要があります。 それ以降は自由で構いません。 ECS用パブリックサブネット タグ 値 名前 sample_public_subnet1 AZ ap-northeast-1a CIDRブロック 10.0.0.0/24 タグ 値 名前 sample_public_subnet2 AZ ap-northeast-1c CIDRブロック 10.0.1.0/24 RDS用サプネット タグ 値 名前 sample_private_subnet1 AZ ap-northeast-1a CIDRブロック 10.0.10.0/24 タグ 値 名前 sample_private_subnet2 AZ ap-northeast-1c CIDRブロック 10.0.11.0/24 以下はsample_public_subnet1を作成しているときの画面です。 ※VPC IDには作成したVPC(sample_vpc)を選択してください。 1.3 インターネットゲートウェイの作成 インターネットゲートウェイとはVPCとインターネットの通信を可能にするもので、これを設定することによって誰のパソコンからも自分のVPC内のアプリにアクセスできるようになります。 以下の公式リファレンスに詳しい内容が記述されています。 インターネットゲートウェイのサービスを選択して、インターネットゲートウェイの作成を選択します。 タグ 値 名前 sample_igw 作成が終わったら、作成したインターネットゲートウェイにチェックを入れてアクションからVPCにアタッチするを選択します。 以下でsample_vpcが表示されるので選択します。 1.4 ルートテーブルの作成 ルートテーブルとはサブネットのインスタンスがどこと通信するのかを明示的に定めるものです。ルートテーブルの作成により、サブネットがVPC内のどこと通信できるのか、また外部と通信できるのかを選択できます。ここで、インターネットゲートウェイの通信ルールを入れるか否かで先ほど作成したサブネットをプライベートサブネットにするかパブリックサブネットにするかを決められます。 ルートテーブルの作成を選択し、以下のように設定を行います。 タグ 値 名前 sample_route VPC sample_vpc ルートテーブルの作成が完了したら、作成したルートテーブルを選択してアクション→ルートの編集を選択します。 VPCをインターネットから通信できるようにルールを編集するために全てのIPアドレスを選択する0.0.0.0を送信先に設定し、ターゲットにはInternet Gateway→先程作成したターゲットを選択します。 次に、サブネットとの関連付けを行います。 この操作をすることにより1.2で作成したECS用のサブネットをインターネットゲートウェイを介して外部と通信できるようにします。 作成したルートテーブルを選択してアクション→サブネットの関連付けの編集を選択します。 そうしたらsample_public_subnet1とsample_public_subnet2の2つを選択して保存を押します。 1.5 セキュリティグループの作成 セキュリティグループとはファイアウォール機能であり、インスタンスへのアクセスを許可したり、トラフィックを制御することができます。 以下ではECSとRDSのセキュリティグループを設計していきます。 ECSのセキュリティグループ 基本的な詳細 タグ 値 名前 sample_ecs_security 説明 適当 VPC sample_vpc インバウンドルール インバウンドルールはセキュリティグループに関連付けられたインスタンスへのアクセスを制限するルールです。 ※Fargateだとsshアクセスできないのでポート22番を解放する必要がありません。 タグ 値 タイプ HTTP リソースタイプ 任意の場所 アウトバウンドルール アウトバウンドルールはセキュリティグループに関連付けられたインスタンスからどの送信先にトラフィックを送信できるか決めるルールです。 変更なし RDSのセキュリティグループの作成 基本的な詳細 タグ 値 名前 sample_rds_security 説明 適当 VPC sample_vpc インバウンドルール こちらはECSのセキュリティグループからのみアクセスできるように設定します。 ※MySQLや他のデータベースを使用する人は、自分のデータベースにあったタイプを選択してください。 タグ 値 タイプ PostgreSQL リソースタイプ カスタム ソース sample_ecs_security アウトバウンドルール 変更なし 2.RDSの設定 ここからRDS周りの設定に移ります。 Amazon Relational Database Service (RDS)は、AWSのデータベース機能です。こちらを使用することにより、Amazon Aurora、PostgreSQL、MySQL、MariaDB、Oracle データベース、SQL Serveといったデータベースを簡単にセットアップできます。 AWSのアカウントを作成してはじめの1年間は無料枠を使用することによりお金をかけず使用することができます。 2.1 RDS用サブネットグループの作成 RDSの作成には2つ以上のサブネットを束ねたサブネットグループを作成する必要があります。 1.2のサブネットの作成の際に、RDS用のサブネットのAZにap-northeast-1aとap-northeast-1cの2つを選択しているため、マルチAZにできます。(マルチAZとは複数のAZを使用する構成のことで、1つのAZでしょうが板起きても他のAZで稼働を続けることができるため可用性が向上します。) サービス検索よりRDSを検索し、サブネットグループのを選択します。すると、DBサブネットグループを作成をクリックし以下のように設定します。 サブネットグループの詳細 タグ 値 名前 sample_rds_subnet_group 説明 適当 VPC sample_vpc サブネットを追加 タグ 値 アベイラビリティゾーン ap-northeast-1a, ap-northeast-1c サブネット 1.2で作成したRDSのサブネットをそれぞれのAZで選択 2.2 RDSの作成 次にRDSを作成します。 RDSを検索後、データベースを選択します。すると、データベースの作成という項目があるのでクリックして以下のように設定を行います。 タグ 値 エンジンのオプション PostgreSQL(自分の使っているDB) サブネット バージョン(自分の使っているDBのバージョン(異なると動作しない可能性があるのでバージョンによる差異があるのか調べたほうがいいかも)) タグ 値 DBインスタンス識別子 sample-db マスターユーザー名 postgres マスターパスワード 自分で決める DBインスタンスサイズ db.t2.micro(無料枠) タグ 値 ストレージタイプ 汎用(SSD) ストレージ割り当て 20 ストレージの自動スケーリング 有効 最大ストレージしきい値 1000 タグ 値 VPC sample_vpc サブネットグループ sample_rds_subnet_group パブリックアクセス可能 なし VPCセキュリティグループ 既存の選択 既存のVPCセキュリティグループ sample_rds_security アベイラビリティゾーン 指定なし エンドポイントはメモしておいてください。 3.ECRへイメージのプッシュ Amazon Elastic Container Registry (ECR)はDockerイメージのレジストリサービスです。 ここからECRへ自分のDockerイメージのプッシュを行っていきます。 3.1 ECRのリポジトリの作成 まずはDockerイメージをプッシュするためのリポジトリを作成します。 このリポジトリでDockerイメージのバージョン管理を行います。少しGithubに似てますね笑 まずは、Rails用のリポジトリを作成します。 以下のように設定します。 タグ 値 可視性設定 プライベート リポジトリ名 rails-sample 同様にして、nginxのリポジトリを作成してください。 タグ 値 可視性設定 プライベート リポジトリ名 nginx-sample 3.2 Dockerfileの確認 (1) Rails Dockerfileの中身 筆者のDockerfileの中身は以下のようになっています。 Dockerfile(Rails) FROM ruby:2.6.5 # 以下の記述を追加 ENV RAILS_ENV=production RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get update && \ apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN apt-get update -qq && apt-get install -y build-essential libpq-dev sudo vim RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn && apt-get install imagemagick RUN yarn add node-sass WORKDIR /app RUN mkdir -p tmp/pids RUN mkdir -p tmp/sockets COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock COPY yarn.lock /app COPY package.json /app RUN bundle install # ここでyarn installをしないとwebpackerを実行できない RUN yarn install COPY . /app # RUN yarn add jquery popper.js bootstrap # WARNING:webpack:installをするとwebpackの設定ファイルが初期化されてjqueryなどが使えなくなってしまう # RUN rails webpacker:install RUN NODE_ENV=production ./bin/webpack COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] # 以下の記述があることでnginxから見ることができる VOLUME /app/public VOLUME /app/tmp CMD bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb" ※webpackerを使っていますが、sprocketsを使っている場合はrake assets:precompileで置き換えてください。 ポイントとしては、Dockerfile内でVOLUMEを設定することで、nginxコンテナからrailsコンテナのsockファイルが見えるようにしてください。 VOLUME /app/public VOLUME /app/tmp またentrypoint.shにdbのマイグレーションなどを行う設定をしてください。 ※ 2回目以降のタスク実行時にはdb:createとdb:seedを行うとエラーになるのでコメントアウトしてください。 entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /app/tmp/pids/server.pid # WARNING:createとseedはfargateの初回のみ実行 # WARNING:タスクを個々に作って実行の方がいいかも bundle exec rails db:create bundle exec rails db:migrate bundle exec rails db:seed # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" (2) nginx Dockerfile(nginx) FROM nginx:1.16 RUN apt-get update && \ apt-get install -y apt-utils \ locales && \ echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \ locale-gen ja_JP.UTF-8 ENV LC_ALL ja_JP.UTF-8 # 初期状態の設定ファイル ADD ./nginx/nginx.conf /etc/nginx/nginx.conf ADD ./nginx/default.conf /etc/nginx/conf.d/default.conf 3.3 ECRの設定 まずはRailsのイメージをプッシュします。 先程作成したsample-railsというリポジトリを選択するとプッシュコマンドの表示からプッシュコマンドを確認します。 こういう感じでコマンドが出てくると思います。 terminal # awsのログイン aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com # dockerイメージのビルド docker build -t rails-sample . --no-cache # dockerイメージのタグ付け docker tag rails-sample:latest ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/rails-sample:latest # dockerイメージをECRのリポジトリへプッシュ docker push ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/rails-sample:latest ※ aws-cliが入っていない場合は以下のリンクよりaws-cliのインストールを行ってください。 ※ dockerファイルがカレントディレクトリでDockerfileでない場合は、以下のように-fのオプションを付けることでDockerファイル名とパスを選択してください。 (例) nginxディレクトリにあるDockerfile.nginxというdockerファイルを選択したい場合 terminal docker build -f ./nginx/Dockerfile.nginx -t sample-rails . --no-cache 同様にnginxのイメージについても同様の手順でECRのリポジトリへプッシュをしてください。 さらに、リポジトリへプッシュできたことが確認できたら、URIをそれぞれコピーして控えておいてください。(ECSのタスク定義で使います。) 4.ECSの作成 遂にECSの作成に入ります。 Amazon Elastic Container Service (ECS) はクラスターでコンテナをスケーラブルに実行、停止、管理できるサービスです。 簡単に言うと、クラスターと呼ばれる複数のコンテナを束ねることができる基盤の上でコンテナを実行し、サービスを構成するサービスと考えるとわかりやすいかも知れません。 4.1 タスクの作成 ECSのタスクとはコンテナを組み合わせやボリュームの設定などサービスを動作させるための設定を行う部分です。 docker-compose.ymlの役割に似ているかも知れません。 ECS→タスク定義→新しいタスク定義の作成を選択し、タスクの作成を行いましょう。 起動タイプはFARGATEを選択してください。 タグ 値 タスク定義名 sample タスクロール なし タスク実行ロール ecsTaskExecutionRole タスクロールとはECS上のコンテナ中のアプリケーションのIAMロールのようなものでコンテナ中のアプリケーションが他のサービス(S3やElastic Searchなど)にアクセスする権限を設定することができます。 対してタスク実行ロールとはタスク定義がAWSのサービスを利用するためのルールで、タスクのログをCloud Watchに出力したり、ECRのコンテナイメージを使用するために用いられます。 ↓参考 タグ 値 タスクメモリ 0.5GB タスクCPU 0.25vCPU タスクメモリとタスクCPUについては料金を最小限にするために上の設定にしていますが、自分のサービスの規模に合わせて変更してください。 次にコンテナの追加を行います。 railsコンテナとnginxコンテナの2つを追加します。 (1) Railsコンテナ タグ 値 名前 rails イメージ ECRの作成の際メモしたsample-railsのリポジトリURI ポートマッピング 3000 環境変数 docker-compose.ymlや.envに設定している環境変数(RAILS_MASTER_KEYがCircleCIデプロイをする際に必要になるのでmaster.keyの値を入れておいてください) ログ設定 ON (2) Nginxコンテナ ポイントはボリュームソースにRailsコンテナのコンテナ名(今回の場合はRailsコンテナに付けた名前のrails)を設定しています。 これにより、railsコンテナのsockファイルをnginxコンテナから見ることができるようになります。 タグ 値 名前 nginx イメージ ECRの作成の際メモしたsample-nginxのリポジトリURI ポートマッピング 80 ボリュームソース rails ログ設定 ON 残りはデフォルトで作成を押します。 以下のようにsample-taskのステータスがActiveになっていればタスク定義成功です。 4.2 クラスターの作成 次にクラスターの作成を行います。 ECS→クラスター→クラスターの作成を選択する。 Fargateを使ってデプロイするのでネットワーキングのみを選択します。 選択したら次のステップへをクリックします。 クラスターの設定は以下のように行います。 そして作成をクリックします。 タグ 値 名前 sample-cluster CloudWatch Container Insights OFF 4.3 サービスの作成 クラスターが作成できたらサービスを選択し、作成をクリックします。 サービス設定は以下のように行います。今回は料金を最小にするためにタスクの数を1にして行いたいと思います。 設定が終わったら次のステップをクリックします。 タグ 値 起動タイプ FARGATE タスク定義 ファミリー sample タスク定義 リビジョン latest クラスター sample-cluster サービス名 sample-service タスクの数 1 (概要で説明した編成のようにALBによって負荷分散するためには2以上にする必要があります) ネットワーク構成は以下のように行います。 まだ独自ドメインを取っていないためALBを設定していないので今回はロードバランサー無しで次のステップへ進みます。 タグ 値 クラスターVPC sample_vpc サブネット sample_public_subnet1 セキュリティグループ sample_ecs_security パブリック IP の自動割り当て ENABLED Auto Scalingは今回は調整しない設定にします。 選択したら次のステップへ進みます。 最後に確認画面になりますので、内容を確認したらサービスの作成をクリックしてサービスを起動します。 タスクがRUNNINGになっているのを確認したら、railsのLogsを確認して以下の一番下のログのようにdatabaseが作成されているのを確認してください。 ※ データベースとの連携ができない場合、以下のような原因が考えられます。 ① ユーザー名かパスワードが誤っている ② RDSのエンドポイントが間違ってる ③ RDSのインバウンドルールにECSのセキュリティグループが設定されていない ①②の対処法はRDSサービスに一度アクセスしてユーザー名とエンドポイントがあっているか確認してください。パスワードは確認ができないため変更からパスワードの変更を行ってください。 ③の場合はRDSのセキュリティグループのインバウンドルールにECSのセキュリティグループが設定されているか確認してください。また、インバウンドルールのタイプがPostgreSQLになっているか確認してください どちらかわからない場合は、一度RDSのセキュリティグループのソースを任意の場所(0.0.0.0)に変更してタスクを再度実行してください。(確認が終わったらセキュリティ上の問題からすぐにソースをもとに戻してください) ↓のようになっていれば③はOK 4.4 サービスの再作成 4.3でデータベースの作成が終わったので以下のようにentrypoint.shをdb:createとdb:seedをコメントアウトします。 entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /app/tmp/pids/server.pid # WARNING:createとseedはfargateの初回のみ実行 # WARNING:タスクを個々に作って実行の方がいいかも # bundle exec rails db:create bundle exec rails db:migrate # bundle exec rails db:seed # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" その後、ECRのリポジトリに移動しrails-sampleを選択します。 イメージタグがlatestのものを削除します。 削除が終わったら再度ECRにentrypoint.shを変更したコンテナをプッシュします。 キャッシュが残っているとコンテナの内容が変更されていない可能性があるので --no-cacheは必ず付けてbuildを行ってください。 次にタスク定義の変更を行います。 タスクsampleに移動して最新のリビジョンを選択して新しいリビジョンの作成をクリックします。(筆者は一度データベースの生成に失敗しているためリビジョンが2になっていますが皆さんの場合は最新が1になっているはずです) タスク内部に移動したら一度railsコンテナを削除して再度コンテナの追加より設定を行ってください。設定内容は変わらず以下のように行います。 タグ 値 名前 rails イメージ ECRの作成の際メモしたsample-railsのリポジトリURI ポートマッピング 3000 環境変数 docker-compose.ymlや.envに設定している環境変数(RAILS_MASTER_KEYがCircleCIデプロイをする際に必要になるのでmaster.keyの値を入れておいてください) ログ設定 ON 次にクラスターに移動します。 クラスターsample-clusterを選択し、サービス名sample-serviceを選択してください。 そうしたらリビジョンを最新に変更してあとは同じままサービスを更新します。 サービスの更新が完了したらしばらく待って以下のようにタスクからタスク定義が最新になっているのとステータスがRUNNINGになっているのを確認します。 確認が終わったらタスク定義をクリックします。 パブリックIPを確認できるのでアクセスします。 以下のように自分のサービスがパブリックIPからアクセスできることが確認できるはずです。 仮にアクセスできない場合以下の可能性があります。 ①タスク定義のLogsからnginxとrailsのコンテナでエラーが起きている ②タスク定義のサブネットIDをコピーしてVPC→サブネット→ルートテーブルより送信先にインターネットゲートウェイが追加されているか確認する ↓②の確認例 次回、独自ドメインの設定〜CircleCIの自動デプロイの設定まで行います。 ↓後編
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ransackの検索機能をRSpecでテストする方法

はじめに ransackの検索機能をRSpecにてテストする際に、書き方を検索したが思ったような記事がヒットしなかったので備忘録として記事を書きます。 この記事の内容 ransackをRSpecでテストの実施 Companyテーブル構成 Column type name string category integer テストコード RSpec.describe '#index', type: :request do describe '企業名検索のテスト' do context '正しいパラメータが渡されている場合' do it '検索後の対象データに想定している内容があること' do @params = {} @params[:q] = { name_cont: 'test2' } @q = Company.ransack(@params) @companies = @q.result expect(@companies) == ({ name: 'test2' }) end context '不正なパラメータが渡されている場合' do it '検索後の対象データに想定している内容がないこと' do @params = {} @params[:q] = { name_cont: 'test2' } @q = Company.ransack(@params) @companies = @q.result expect(@companies) != ({ name: 'テスト2' }) end end end end end コードの説明 RSpecのcontextやitは下記URLのサイトがわかりやすかったです。 https://qiita.com/jnchito/items/42193d066bd61c740612 まとめ Qiitaの記事を久々に書きましたが、説明を考えながら記事を書くと自分の思考も整理されてスッキリしました。 改めて、テストコードは重要だなと感じたので、少しでも皆さんのお役に立てればと思います。 拙い箇所が多々あるかとは思いますが、ご指摘等あればコメントいただけると幸いです。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

遅延ロードさせてWebアプリを高速化する

遅延ロードとは 基本的にwebアプリは表示するデータを読み込んでからページを表示します。 しかしそれだと重いデータを取得・表示する場合、ページの表示速度が落ちて重たい動作になります。 なので先に軽い部分だけを読み込みそのページに移動してから重いデータを読み込むことで すべてを表示するまでの時間は変わらない(むしろ少し落ちる)がページの移動速度を大幅に上げることができます。 体感上はかなりサクサクした動作になります。 ↓ツイッターの遅延ロード 実装 gemfile gem 'render_async' を書きbundle 次にrenderを書き直します。 変更前: index.html <%= render "data", posts:@posts %> これを index.html <%= render_async data_path %> or <%= render_async data_user_path(@user) %> このようにする。 次にルーティング routes get "data", to: "posts#data", as: "data" #↓パラメーターが必要な場合 get "data_user/:id", to: "posts#data_user", as: "data" controller def index #@posts = Post.all end def data @posts = Post.all render partial: "data", locals: {posts:@posts} end def data_user @user = User.find_by(name: params[:id]) @posts = @user.posts.all render partial: "data_user", locals: {posts:@posts} end 最後はレイアウトに application.html.erb <%= content_for :render_async %> を追加。 これだけで遅延ロードが完成します。 turbolinksを使用している場合 このままだと時々表示されない場合があります。 <%= render_async data_path, html_options: { 'data-turbolinks-track': 'reload' } %> html_options: { 'data-turbolinks-track': 'reload' }を追加して config/initializersにrender_async.rbを作りましょう render_async.rb RenderAsync.configure do |config| config.turbolinks = true # Enable this option if you are using Turbolinks 5+ end jqueyを使う kaminariやjscrollで無限スクロールなどを実装している場合、これをしないと動きません。 config/initializers/render_async.rb RenderAsync.configure do |config| config.jquery = true end まとめ 遅延ロードって大きなサービスだと結構使われているのに日本語の情報が少なくて困ったので書きました。 体感上の動作速度がかなり変わるのでぜひやってみてください。 宣伝 AmmotというSNSを作りました。 文字数制限が6000字まで、画像・動画・PDF・音声は同時に10個まで投稿可能な自由なSNSです。 マークダウンにも対応しているので皆さん大好きなソースコードもきれいに載せれます。 ぜひ使ってみてください。 僕のツイッターアカ https://twitter.com/yamada1531
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションチェックでformの値が空白時にエラーメッセージを出したいが、No Method Errorが出てしまう時の原因と対処法

*この記事の内容はDMM WEBCAMPを受講中に、課題の取り組みで解決するのに時間がかかった問題を取り上げています。 DMM受講生と思われる方で、私が解決できなかった問題と同じ内容の質問が、web上の至る所に転がっていたので、同じところで詰まっている方の問題解決の参考になればと思い、復習も兼ねて投稿してみます。 ・No method errorでつまずいていた理由 ・redirect_toとrenderの違いをわかっていなかった ・@変数としていなかったため、Viewページに渡せていなかった まずredirect_toとrenderについての違いを理解しないといけなかった 簡単にいえば、redirect_toは変数を持ってViewページに行く。renderは何も持たず直接Viewページに行く。 詳しく書くと、redirect_toは、redirect_toの引数にあるurlを見て、そのurlのルーティングをまず見にいく 例えばupdateメソッドでredirect_to  book_path(book)としていたらbook_pathに対応しているshowアクションを一度経由するという事になる(ちなみに 名前付きurl_pathのpathはアクションを指している) そしてコントローラーに戻ってきて、showアクションの中身を見て、showアクションで定義されている変数を持ったまま、名前付きurlの book(詳細ページ)へ行く renderは、render action: :edit としていたら、直接edit(編集)ページに行くため、変数は何も持ってない状態でViewページに行っている ここで問題なのが、バリデーションチェックでエラーを表示したい場合は必ずrenderを使わないといけない でもrenderは変数を持って行けないから、Viewで結局変数に値が入らずに No Method Errorになるんじゃないのか? それはこうすることで回避する qiita.rb : else @books = Book.all render action: :index end end elseになった場合に、renderの上行で渡す変数をあらかじめ定義しておく!これでViewページに行く時に、行った先で使う変数を定義できる(至ってシンプル) もうひとつのミスは Viewページに変数を渡さないといけないのに、変数に@を付けていなかった! destroyメソッドのように、受け取った値を削除して、redirect_toでもう一度ページ表示するだけの、コントローラー内で完結するメソッドならいいが Viewで表示させたい変数は@変数としないといけない qiita.rb def create book = Book.new(book_params) if book.save flash[:notice] = "Book was successfully created." redirect_to book_path(book) else render action: :index end end このようなコードだと、Viewで表示させるために↓こう変更する qiita.rb def create @book = Book.new(book_params)    if @book.save flash[:notice] = "Book was successfully created." redirect_to book_path(@book) else @books = Book.all render action: :index end end 一覧ページのindexでは同じ画面に投稿formも表示するため、二つの変数を定義していた qiita.rb def index @book = Book.new @books = Book.all end だからform_withで投稿できた時用に、詳細ページに行く@bookと、空だった時にindexページに戻って来るように@booksを定義して、eachで繰り返すためにBook.allを入れておかないと、No Method Errorが出てしまう じゃあなんでバリデーションチェックする前までは@を付けずに表示できてたのかというと バリデーションチェックする前はredirect_toで他のアクションを経由して、違うページに推移していたから 今回は同じページに戻ってきてエラーを表示させないといけなかったから。空は保存させない記述をmodelでしたため、if文のelseで引っ掛かり、その結果をrenderで渡すため @books = Book.allでBookテーブルの情報を全て渡さないといけなかった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsのバリデーションとエラーメッセージについて

例えばこれは簡単なToDoアプリですが、新規追加画面でバリデーションをかけて空欄、または5文字以上入力しないと登録できないようにします。 バリデーションとはデータ内容に一定の制限をかけることです。空のデータを登録できないようにする、パスワード入力において文字数制限をかけるなどです。 バリデーションの記述はappディレクトリのmodelsにあるファイルに記述します。 class Task < ApplicationRecord validates :title, presence: { message: 'タイトルを入力してください'}, length: { minimum: 5, message: '5文字以上入力してください'} end validates :title,でtitleカラムにバリデーションをかけるという意味 presenceで空のデータは登録できないという意味{ message: 'タイトルを入力してください'},でエラーメッセージが表示されます。 length: { minimum: 5, message: '5文字以上入力してください'}で5文字以下の場合はエラーメッセージが表示されるようになります。 続いてviewファイルの記述 <p> <%= f.label :title %> <br> <%= f.text_field :title %> <% if @task.errors.any? %> <%= @task.errors.messages[:title][0] %> <% end %> </p> <p> <%= f.submit%> </p> 入力欄text_fieldの下にエラーメッセージを出力させます。 <% if @task.errors.any? %> <%= @task.errors.messages[:title][0] %> <% end %> @taskモデルにエラーがあった時エラーメッセージを表示させる記述になります。 入力が空の場合 5文字未満の場合
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Active Recordのメソッドと実行されるSQL一覧

はじめに 多くのRails初学者はSQL文をあまり意識せずActive Recordを使用してしまっているかと思います。しかしデバッグ作業や複雑な絞り込みには生のSQL文を利用する機会はそれなりに多く、また実行されているSQLを理解していないままだと気づかぬうちに非効率なコードを書いてしまっている可能性があります。 そこで本記事ではActiveReocordメソッドで実行されているSQL文をまとめてみました。 メソッドとSQL文 find User.find(1) # SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 User.find([2,3,4]) # SELECT `users`.* FROM `users` WHERE `users`.`id` IN (2, 3, 4) findメソッドでは「引数をidに持つレコードを取り出す」というSQLが実行されています。 指定したidが単数の場合はSQL文にLIMIT 1 が付加されますが、配列で複数指定した場合は付加されません。 find_by User.find_by(id:3) # SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1 User.find_by(id:[3,4,6]) # SELECT `users`.* FROM `users` WHERE `users`.`id` IN (3, 4, 6) LIMIT 1 find_byメソッドではカラム名を指定してレコードを取り出します。条件ハッシュに配列を渡すことが可能ですが LIMIT 1が付加されるので一つのレコードしか呼び出されません。 where User.where(email:"test@test.com") # SELECT `users`.* FROM `users` WHERE `users`.`email` = 'test@test.com' Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) # SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') whereメソッドは文字通りSQLのWHERE句の部分に相当します。 order User.order(:username) # SELECT `users`.* FROM `users` ORDER BY `users`.`username orderメソッドではSQLのORDER BY句が実行され、指定された属性の昇順でレコードが並び替えられます。 first,last User.first # SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 User.last # SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1 firstメソッドではidで昇順に並べたレコードの最初の一つを取り出すSQLが実行されます。lastは降順の一番最初となります。 save,update user = User.find(2) user.username = "test" user.save # UPDATE `users` SET `users`.`updated_at` = '2021-05-09 07:08:50', `users`.`username` = 'test' WHERE `users`.`id` = 2 saveメソッドではインスタンス変数が変更された場合に、レコードを更新するSQLのUPDATE句が実行されます。 select, pluck User.select(:username) # SELECT `users`.`username` FROM `users` selectメソッドではテーブから対象となるカラムのみを取り出します。pluckも同様のSQL文が発行されますが指定したカラムの値の配列を返すという点が異なります。 User.select("date(created_at) as ordered_date") # SELECT date(created_at) as ordered_date FROM `users` また、文字列でasを使った条件を渡すことでSQLの関数DATEの引数に渡したcreate_atをordered_dateという別の名前を付けて取得することができます。 destroy User.find(3).destroy # DELETE FROM `profiles` WHERE `profiles`.`id` = 3 destoryメソッドではSQLのDELETE文が実行されます。 limit, offset User.limit(2).to_sql # "SELECT `users`.* FROM `users` LIMIT 2" limitメソッドでは指定された件数のみレコードが取り出されます。 User.offset(2).limit(3).to_sql # "SELECT `users`.* FROM `users` LIMIT 3 OFFSET 2" offsetメソッドではレコードを返す前にスキップするレコード数を指定することが可能です。 上のコードでは、2つのレコードがスキップされ、id=3から3つのレコードが取得されます。 また、これらを組み合わせることでページネーション機能を実装することが可能です。 例えば、ページネーションに使用されるgemであるkaminari を使用した場合 User.page(3).per(5) # SELECT `users`.* FROM `users` LIMIT 5 OFFSET 10 このようなSQLが発行されます。 exsits? User.exists?(id:1) # SELECT 1 AS one FROM `users` WHERE `users`.`id` = 1 LIMIT 1 exists?メソッドは、渡された条件ハッシュを満たすレコードが存在するかどうかを確認するメソッドです。 WHERE句をみると。対象となるテーブルから条件を満たすレコードを一つ絞り込んでいます。そして1 AS oneから分かる通り、1をoneという別の名前を付けて取り出しています。 group, count ,having User.select("date(updated_at) as date, sum(id)").group(:date) # SELECT date(updated_at) as date, sum(id) FROM `users` GROUP BY `date` Userの更新日のコレクションごとに更新日、コレクションのidの合計を検索する場合は上記のようなSQL文を発行します。 User.group("date(updated_at)").count # SELECT COUNT(*) AS count_all, date(updated_at) AS date_updated_at FROM `users` GROUP BY date(updated_at) 上記ではgroupメソッドとcountメソッドを組み合わせることで、更新日のコレクションごとのレコード数,更新日を取得するSQL文を発行します。 User.select("date(updated_at) as date, sum(id)").group(:date).having("sum(id)>10") # SELECT date(updated_at) as date, sum(id) FROM `users` GROUP BY `date` HAVING (sum(id)>10) havingメソッドを使うことで集約した結果に対して条件指定を行うHAVING句が付加されます。 joins/left_outer_joins 以下のような二つのテーブルがあるとします。 Owner table id ownername 1 ichijo 2 jiro 3 saburo Dog table id owner_id dogname 1 1 pochi 2 2 maru 3 2 hachi 4 4 ken SELECT * FROM owners INNER JOIN dogs ON owners.id = dogs.owner_id; このようにINNER JOINが実行された場合は、結合キーとなっているそれぞれのテーブルのキー値のうち、両方のテーブルに存在する値を持つレコードのみが結合されます。 上記のSQL文ではそれぞれのキーの共通している値は1,2のみであるので、Dogテーブルからはowner_idが1,2のテーブル、Ownerテーブルからはidが1,2のテーブルが結合され、以下のようなテーブルが出来上がります。 id ownername id owner_id dogname 1 ichiro 1 1 pochi 2 jiro 2 2 maru 2 jiro 3 2 hachi SELECT * FROM owners LEFT OUTER JOIN dogs ON owners.id = dogs.owner_id; 一方でOUTER JOINの場合はINNER JOINで結合されるレコードに加え、基準となるテーブル(LEFT OUTER JOINの場合は左側のテーブル)のキー値を持つテーブルが結合されます。 つまり、今回の場合は基準となるOwnerテーブルのデータはレコードは全て取得され、それに対応するDogsテーブルのレコードが結合されます。 id ownername id owner_id dogname 1 ichiro 1 1 pochi 2 jiro 2 2 maru 2 jiro 3 2 hachi 3 saburo null null null このように、基準となるテーブルのレコードは全件取得され、対応するレコードがない部分はnullとなります。 それぞれの結合に対応するActive Recordメソッドは以下のようになります。 Product.joins(:reviews) # SELECT `products`.* FROM `products` INNER JOIN `reviews` ON `reviews`.`product_id` = `products`.`id` ProductモデルがReviewsモデルに対してhas_manyの関係を持っている場合、joinsメソッドでは内部結合を行うINNER JOIN句を呼び出しています。 Product.left_outer_joins(:reviews) # SELECT `products`.* FROM `products` LEFT OUTER JOIN `reviews` ON `reviews`.`product_id` = `products`.`id` left_outer_joinメソッドでは左のテーブルを基準としたOUTER JOIN句が呼ばれます。 なお、joins, left_outer_joinsメソッドを使用した場合に発行されるSQL文から、SELECTで結合先のテーブルデータを取得していないことがわかります。selectメソッドを使用して以下のようにすると結合した全てのカラムデータを取得することができます。 Product.select("*").joins(:reviews) # SELECT * FROM `products` INNER JOIN `reviews` ON `reviews`.`product_id` = `products`.`id` Product.select("*").left_outer_joins(:reviews) # SELECT * FROM `products` LEFT OUTER JOIN `reviews` ON `reviews`.`product_id` = `products`.`id` 参考 最近の未経験エンジニアはSQLの理解が浅すぎる!!【サーバーサイド言語Ruby/PHPを勉強している人向け】 Active Record クエリインターフェイス SQL素人でも分かるテーブル結合(inner joinとouter join) SQL ゼロからはじめるデータベース操作
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

includesを用いてN+1問題を解消する

関連付によって構文が異なるincludesメソッド N+1問題を解消する目的で、メインページでのfeedメソッドにincludesを導入しました。 伝えられる事としましては、関連付けの種類によってincludesの構文が変わるという事です。 controller.rb def home : @feed_items = current_user.feed.page(params[:page]) : end before user.rb(変更前 def feed Work.where( "user_id = ? OR user_id IN (?)", id, Relationship.where(follower_id: id).select(:followed_id) ) end after user.rb(変更後 def feed Work.includes( [ :comments, :likes, image_attachment: :blob, user: { avatar_attachment: :blob }, illustrations: { photo_attachment: :blob } ] ).where( "user_id = ? OR user_id IN (?)", id, Relationship.where(follower_id: id).select(:followed_id) ) end このアプリケーションのER図です。 (自動生成されたものなので見ずらいかもしれません。すいません。 今回は以上です!! 目を通して頂きありがとうございました!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails の link_to メソッドで :delete や :put を指定しても GET のリクエストが送信されてしまう理由と対策

最近Railsの学習を始めたのですが、ログアウト機能を実装していた際にハマったので、メモとして残します。 問題の概要 railsのlink_toメソッドを使って、以下のようにログアウト用のリンクを実装しました。 application.html.erb <%= link_to 'ログアウト', logout_path, method: :delete, class: 'nav-link' %> method: :delete として、HTTPメソッドをDELETEに指定しているのですが、なぜか最終的にGETになってリクエストが送られるという現象に遭遇しました。 ちなみにlink_toメソッドを使って生成されたHTMLのソースを確認したところ <li class="nav-item"> <a class="nav-link" rel="nofollow" data-method="delete" href="/logout">ログアウト</a> </li> となっており、きちんとaタグでdata-method="delete"となっていました。 解決法 いろいろと情報を探っていると、RailsではJavaScriptを使って、サーバーにリクエストを送信することもあるようです。 なので、JavaScriptを導入できていなかった場合に上記のようなHTTPメソッドのerrorが起こってくるようです。 自分の場合、Webpackerを導入しようとしていて、どこかで、手順の漏れがあったようです。 結局、現在制作中のアプリ(Rails学習のため作成)にはWebpackerを導入する必要性がなさそうだったので、Rails newを実行した時にデフォルトで記載されていた、以下の記述に戻して application.html.erb <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> app/asset/config内のapplication.jsに以下の記述を追加するとしっかりとDELETEでリクエストが送信できました。 application.js //= require jquery //= require jquery_ujs
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devise gemのユーザー情報アップデートの方法

Railsでdeviseのgemをインストールし、ユーザー登録機能は実装し終わった方で、 登録情報の編集機能を実装したい方向けです。 注意点が2つあります。 編集時はcurrent_passwordが要求されること。 application_controller.rbにメソッドを呼び出す記述が必要なこと。 current_passwordの追加 編集画面のビューの入力フォームにcurrent_passwordを追加してください。 その他は設定したカラム名に準じます。 user/registrations/edit.html.erb <%= form_with model: @user, url: user_registration_path, local: true do |f| %> 略 <%= f.password_field :current_password %> しかし、これだけではupdateを実行してもエラーは出ないのですが、updateもされません。 以下の記載が必要になります。 application_controller.rbへ追記 updateアクションを加える場合、以下の追記が必要になります。 controller/application_controller.rb class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_account_update_params, if: :devise_controller? protected def configure_permitted_parameters 登録機能実装時に記述したメソッドです。 devise_parameter_sanitizer.permit(:sign_up, keys: [:カラム名]) end def configure_account_update_params こちらがupdate機能実装の際に追記するメソッドです。 devise_parameter_sanitizer.permit(:account_update, keys: [:カラム名]) end end これでupdateアクションに対してもストロングパラメーターを設定、updateが実行できるようになります。 なお、この追記に関しては rails g devise:controllers users を実行した際に生成される既成のdeviseコントローラー(registration_controller.rb)内に書いてあります。 以上です。 参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのルーティングnamespaceってなんで使うの?

はじめに Vue.jsをRailsで使うときにバックエンドをRailsのAPIモードで作り、 フロントエンドをVue.jsで作る記事をよく見かけた。 その際にルーティングでnamespaceを使っていたけれど なぜnamespaceを使うのかよくわからず、苦手意識があったので理解するためまとめてみました。 まずはAPIとはそもそもなんぞやというところからはじめてみましょう。 APIとは APIはApplication Programming Interfaceの略です。 ソフトウェアの機能を共有できる仕組みのこと。よく使われているものだとGoogle Mapなど。 郵便番号を入れると自動で入力されるなんてことができるのもこれのおかげだったり。 APIは通常Webに公開されていて、誰でも無料で使うことが可能。「WebAPI」と呼ばれたりもします。 一般的にJSONという形式で結果を返します。 RailsのAPIモードとは RailsのAPIモードはそのAPIをRailsで作成できるAPI作成に特化したモードのこと。 APIモードではMVCのViewg作成されません。 そのため、sass-railsやwebpacker、capybabaraなどはGemfaileには生成されません。 Rails4まではrails-apiというgemを入れる必要があったみたいですが 現在のバージョンでは特に何もしなくても使えます。 rails new アプリケーション名 --api と打つだけでAPIモードのRailsアプリケーションが作成されます。 次にそもそもnamespaceってなにという疑問を解消していきます。 Railsのルーティング RailsのルーティングはMVCモデルの基本の基本と言えるもので Viewから送られたリクエストを適切なコントローラーに送り、どんなアクションを行うかを管理するものです。 ルーティングはRailsアプリケーションのconfigディレクトリの中にあるroutes.rbに記述します。 Railsルーティング種類は調べてみるとたくさんありました。 urlを直接指定する root resouce resouces collection member new param shallow namespace scope concern constraints などなどたくさんあります。深そうですね。。。 scopeとnamespaceの違い ルーティングのまとめは別の機会にするとして 今回はその中でnamespaceにフォーカスを当てていきますが、 namespaceと似たようなscopeとの比較してみます。 scope namespace URL 指定のパス          指定のパス コントローラ   通常のcontroller内    controllerの中の指定のフォルダ scopeもnamespaceもURLを指定のパスにできますが namespaceはファイル構成も変えたい場合にいいみたいです。 scopeでもいいような気もしますね。 APIのバージョン railsのAPIを調べているとnamespaceを使ったroutes.rbの Rails.application.routes.draw do namespace 'api' do namespace 'v1' do resources :posts end end end などのところにあるv1という記述。 調べてみるとバージョン1の意味でした。 バージョン管理をするためにscopeではなく、namespaceを使っているのかとAPIのバージョン管理とは なんぞやと調べていきます。 Web APIのバージョン管理の重要性 Web APIとはアプリケーションのインターフェイスの役割を持ち、追加、変更、廃止などされていくものです。 通常のアプリもver.1.0.8とか書かれていますよね。 セマンティックバージョニングと言います。 バージョン管理を特にせず、管理者がこっちの方がいいから変更しよう〜とAPIを書き換えてしまったら そのAPIを使っているユーザーがいきなり使えなくなってしまう可能性があります。 例えばFacebookやGoogleなどのAPIが急に変更されると エラーが出て処理が止まったり、表示がおかしくなるなどの不具合が発生します。 それを防ぐために公開されているAPIは複数のバージョンを提供して、 そういう事態を招くことを防いでいるわけです。 APIのURI一覧 DMMのAPI https://api.dmm.com/affiliate/v3/ItemList?api_id=[APIID]&affiliate_id=[アフィリエトID]&site=DMM.R18&service=digital&floor=videoa&hits=[検索数]&sort=date&keyword=[キーワード]&output=[jsonかxml] TwitterのAPI https://api.twitter.com/2/tweets/search?tweet.fields=created_at,author_id,lang&query=$QUERY" YouTubeのAPI https://developers.google.com/youtube/v3/docs/ FacebookのAPI https://graph.facebook.com/v2.0/me とURIの中を見てみると、v○とあります。 いくつかバージョンの管理方法があるようなのですが URIにバージョンを埋め込むのがよく利用される方法のようです。 まとめ これらから考えられることはWeb APIは一人のユーザーが利用するものでなく、 アプリケーションと同様に多くの人が利用するために作られるためバージョン管理がされている。 その慣例としてv1などのバージョンをつけてルーティングをつけている。 それを実現するにはscopeよりもnamespaceが良いため、namespaceでルーティングをしていることが考えられる。 終わりに RailsでAPIを作るとなったときに 知らないnamespaceなどが出てきてよくわからなくて混乱しましたが なぜそうしているのかを時間はかかりますが紐解くのは大事です。 さまざまな記事を読ませていただいたのですが、それ違うなどもあると思いますので ぜひご指摘いただけたら嬉しいです。 参考サイト Railsのルーティングの種類と要点まとめ 初心者じゃなくても役に立つかもしれないRailsのroutingの記述方をまとめてみた routeのmoduleとnamespaceとscopeの違い Railsのroutingにおけるscope / namespace / module の違い きゃまなかのブログ 【Ruby on Rails】ルーティング scope と namespace の違い RESTfulにするためRailsのルーティングをresources,namespace,scopeだけで頑張る Rubyの名前空間(namespace)について現役エンジニアが解説【初心者向け】 REST API利用時のNamespaceの重要性(RESTとGraphQLの比較とかも) Web API: The Good Partsを読んだまとめ Web API: The Good Partsを読んだので「設計変更しやすいWeb API」についてまとめた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails APIのルーティングnamespaceってなんで使うの?

はじめに Vue.jsをRailsで使うときにバックエンドをRailsのAPIモードで作り、 フロントエンドをVue.jsで作る記事をよく見かけた。 その際にルーティングでnamespaceを使っていたけれど なぜnamespaceを使うのかよくわからず、苦手意識があったので理解するため、まとめてみました。 まずはAPIとはそもそもなんぞやというところからはじめてみましょう。 対象読者 Railsを勉強してたけど、APIモードで課題を提出しなきゃいけなくなった方 Vue.jsなどフロントエンドも勉強している方 APIとは APIはApplication Programming Interfaceの略です。 ソフトウェアの機能を共有できる仕組みのこと。よく使われているものだとGoogle Mapなど。 郵便番号を入れると自動で入力されるなんてことができるのもこれのおかげだったり。 APIは通常Webに公開されていて、誰でも無料で使うことが可能。「WebAPI」と呼ばれたりもします。 一般的にJSONという形式で結果を返します。 RailsのAPIモードとは RailsのAPIモードはそのAPIをRailsで作成できるAPI作成に特化したモードのこと。 APIモードではMVCのViewg作成されません。 そのため、sass-railsやwebpacker、capybabaraなどはGemfaileには生成されません。 Rails4まではrails-apiというgemを入れる必要があったみたいですが 現在のバージョンでは特に何もしなくても使えます。 rails new アプリケーション名 --api と打つだけでAPIモードのRailsアプリケーションが作成されます。 次にそもそもnamespaceってなにという疑問を解消していきます。 Railsのルーティング RailsのルーティングはMVCモデルの基本の基本と言えるもので Viewから送られたリクエストを適切なコントローラーに送り、どんなアクションを行うかを管理するものです。 ルーティングはRailsアプリケーションのconfigディレクトリの中にあるroutes.rbに記述します。 Railsルーティング種類は調べてみるとたくさんありました。 urlを直接指定する root resouce resouces collection member new param shallow namespace scope concern constraints などなどたくさんあります。深そうですね。。。 scopeとnamespaceの違い ルーティングのまとめは別の機会にするとして 今回はその中でnamespaceにフォーカスを当てていきますが、 namespaceと似たようなscopeとの比較してみます。 scope namespace URL 指定のパス          指定のパス コントローラ   通常のcontroller内    controllerの中の指定のフォルダ scopeもnamespaceもURLを指定のパスにできますが namespaceはファイル構成も変えたい場合にいいみたいです。 scopeでもいいような気もしますね。 APIのバージョン railsのAPIを調べているとnamespaceを使ったroutes.rbの Rails.application.routes.draw do namespace 'api' do namespace 'v1' do resources :posts end end end などのところにあるv1という記述。 調べてみるとバージョン1の意味でした。 バージョン管理をするためにscopeではなく、namespaceを使っているのかとAPIのバージョン管理とは なんぞやと調べていきます。 Web APIのバージョン管理の重要性 Web APIとはアプリケーションのインターフェイスの役割を持ち、追加、変更、廃止などされていくものです。 通常のアプリもver.1.0.8とか書かれていますよね。 セマンティックバージョニングと言います。 バージョン管理を特にせず、管理者がこっちの方がいいから変更しよう〜とAPIを書き換えてしまったら そのAPIを使っているユーザーがいきなり使えなくなってしまう可能性があります。 例えばFacebookやGoogleなどのAPIが急に変更されると エラーが出て処理が止まったり、表示がおかしくなるなどの不具合が発生します。 それを防ぐために公開されているAPIは複数のバージョンを提供して、 そういう事態を招くことを防いでいるわけです。 APIのURI一覧 DMMのAPI https://api.dmm.com/affiliate/v3/ItemList?api_id=[APIID]&affiliate_id=[アフィリエトID]&site=DMM.R18&service=digital&floor=videoa&hits=[検索数]&sort=date&keyword=[キーワード]&output=[jsonかxml] TwitterのAPI https://api.twitter.com/2/tweets/search?tweet.fields=created_at,author_id,lang&query=$QUERY" YouTubeのAPI https://developers.google.com/youtube/v3/docs/ FacebookのAPI https://graph.facebook.com/v2.0/me とURIの中を見てみると、v○とあります。 いくつかバージョンの管理方法があるようなのですが URIにバージョンを埋め込むのがよく利用される方法のようです。 まとめ これらから考えられることはWeb APIは一人のユーザーが利用するものでなく、 アプリケーションと同様に多くの人が利用するために作られるためバージョン管理がされている。 その慣例としてv1などのバージョンをつけてルーティングをつけている。 それを実現するにはscopeよりもnamespaceが良いため、namespaceでルーティングをしていることが考えられる。 終わりに RailsでAPIを作るとなったときに 知らないnamespaceなどが出てきてよくわからなくて混乱しましたが なぜそうしているのかを時間はかかりますが紐解くのは大事です。 さまざまな記事を読ませていただいたのですが、それ違うなどもあると思いますので ぜひご指摘いただけたら嬉しいです。 参考サイト Railsのルーティングの種類と要点まとめ 初心者じゃなくても役に立つかもしれないRailsのroutingの記述方をまとめてみた routeのmoduleとnamespaceとscopeの違い Railsのroutingにおけるscope / namespace / module の違い きゃまなかのブログ 【Ruby on Rails】ルーティング scope と namespace の違い RESTfulにするためRailsのルーティングをresources,namespace,scopeだけで頑張る Rubyの名前空間(namespace)について現役エンジニアが解説【初心者向け】 REST API利用時のNamespaceの重要性(RESTとGraphQLの比較とかも) Web API: The Good Partsを読んだまとめ Web API: The Good Partsを読んだので「設計変更しやすいWeb API」についてまとめた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Railsチュートリアル】第11章 アカウントの有効化 演習と解答

Ruby on Railsチュートリアル 第11章の演習問題と解答をまとめました。 第11章 アカウントの有効化 - Railsチュートリアル 11.1 AccountActivationsリソース 11.1.1 AccountActivationsコントローラ 11.1.1 - 1 現時点でテストスイートを実行すると green になることを確認してみましょう。 テストはgreen 11.1.1 - 2 表 11.2の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。 ターミナル $ rails routes | grep account_activations edit_account_activation GET /account_activations/:id/edit(.:format) <- URL account_activations#edit 有効化はユーザーIDごとに個別である必要があるためURLを使う 11.1.2 AccountActivationのデータモデル 11.1.2 - 1 本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。 テストはgreen 11.1.2 - 2 コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると(Privateメソッドなので)NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。 ターミナル $ rails console >> user = User.first => #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-05-01 21:12:55", updated_at: "2021-05-01 21:12:55", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: nil, activated: false, activated_at: nil> >> user.create_activation_digest Traceback (most recent call last): 1: from (irb):2 NoMethodError (private method `create_activation_digest' called for #<User:0x000055d02cc2fe00>) Did you mean? restore_activation_digest! >> user.activation_digest => nil 11.1.2 - 3 リスト 6.35で、メールアドレスの小文字化にはemail.downcase!という(代入せずに済む)メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。 app/models/user.rb class User < ApplicationRecord # 省略 private # メールアドレスをすべて小文字にする def downcase_email self.email.downcase! end  # 省略 end 11.2 アカウント有効化のメール送信 11.2.1 送信メールのテンプレート 11.2.1 - 1 コンソールを開き、CGIモジュールのescapeメソッド(リスト 11.15)でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don't panic!"をエスケープすると、どんな結果になりますか? ターミナル >> CGI.escape("Don't panic!") => "Don%27t+panic%21" URL上で扱えない文字を扱えるようにする '(シングルクォート)は%27 (スペース)は+ !(エクスクラメーションマーク)は%21 11.2.2 送信メールのプレビュー 11.2.2 - 1 Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか? メールの送信日時 11.2.3 送信メールのテスト 11.2.3 - 1 この時点で、テストスイートが green になっていることを確認してみましょう。 テストはgreen 11.2.3 - 2 リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが red に変わることを確認してみましょう。 テストはred 11.2.4 ユーザーのcreateアクションを更新 11.2.4 - 1 新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか? 有効化トークンの値 "authenticity_token"=>"A14cThVK0bXZGhv8V/19L5NfJarOx/FU9Nhimg9d+KfBxa3qPsaLk/ExKB9fmErTaPpGCUGHbKk1+/+hSToBhQ==" ターミナル $ rails server Started POST "/users" for 119.243.189.30 at 2021-05-09 05:12:44 +0000 Cannot render console from 119.243.189.30! Allowed networks: 127.0.0.0/127.255.255.255, ::1 Processing by UsersController#create as HTML Parameters: {"authenticity_token"=>"A14cThVK0bXZGhv8V/19L5NfJarOx/FU9Nhimg9d+KfBxa3qPsaLk/ExKB9fmErTaPpGCUGHbKk1+/+hSToBhQ==", "user"=>{"name"=>"Example User", "email"=>"foobarbaz@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"} (0.1ms) begin transaction ↳ app/controllers/users_controller.rb:16:in `create' User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "foobarbaz@example.com"], ["LIMIT", 1]] ↳ app/controllers/users_controller.rb:16:in `create' User Create (1.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?) [["name", "Example User"], ["email", "foobarbaz@example.com"], ["created_at", "2021-05-09 05:12:44.673369"], ["updated_at", "2021-05-09 05:12:44.673369"], ["password_digest", "$2a$12$7JMqwjV8uRksAi9Gaad5RO6bsUtqCW9Yz6I1veeIRZydtEwaHhRrW"], ["activation_digest", "$2a$12$cndGj/.n1aXZscUu4cu9xeBQAvLqmF9UjsIC13IUoOU/JbFyEHCs6"]] ↳ app/controllers/users_controller.rb:16:in `create' (6.7ms) commit transaction ↳ app/controllers/users_controller.rb:16:in `create' Rendering user_mailer/account_activation.html.erb within layouts/mailer Rendered user_mailer/account_activation.html.erb within layouts/mailer (Duration: 0.3ms | Allocations: 74) Rendering user_mailer/account_activation.text.erb within layouts/mailer Rendered user_mailer/account_activation.text.erb within layouts/mailer (Duration: 0.1ms | Allocations: 58) UserMailer#account_activation: processed outbound mail in 5.0ms Delivered mail 60976f4cec170_183b2aad262d3360977c2@ip-172-31-2-18.mail (2.5ms) Date: Sun, 09 May 2021 05:12:44 +0000 From: noreply@example.com To: foobarbaz@example.com Message-ID: <60976f4cec170_183b2aad262d3360977c2@ip-172-31-2-18.mail> Subject: Account activation Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="--==_mimepart_60976f4ceb707_183b2aad262d336097656"; charset=UTF-8 Content-Transfer-Encoding: 7bit ----==_mimepart_60976f4ceb707_183b2aad262d336097656 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Hi Example User, Welcome to the Sample App! Click on the link below to activate your account: https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/account_activations/j1nbIubbruxZHEHOa2erpA/edit?email=foobarbaz%40example.com ----==_mimepart_60976f4ceb707_183b2aad262d336097656 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style> /* Email styles need to be inline */ </style> </head> <body> <h1>Sample App</h1> <p>Hi Example User,</p> <p> Welcome to the Sample App! Click on the link below to activate your account: </p> <a href="https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/account_activations/j1nbIubbruxZHEHOa2erpA/edit?email=foobarbaz%40example.com">Activate</a> </body> </html> ----==_mimepart_60976f4ceb707_183b2aad262d336097656-- Redirected to https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/ Completed 302 Found in 577ms (ActiveRecord: 8.5ms | Allocations: 13319) 11.2.4 - 2 コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。 ターミナル $ rails console >> user = User.find_by(email: 'foobarbaz@example.com') => #<User id: 102, name: "Example User", email: "foobarbaz@example.com", created_at: "2021-05-09 05:12:44", updated_at: "2021-05-09 05:12:44", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$cndGj/.n1aXZscUu4cu9xeBQAvLqmF9UjsIC13IUoOU...", activated: false, activated_at: nil> >> user.activated => false 11.3 アカウントを有効化する 11.3.1 authenticated?メソッドの抽象化 11.3.1 - 1 コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか? ターミナル $ rails console --sandbox >> user = User.create(name: "Tutotrial Exercise", email: "practice@example.com", ?> password: "foobar", ?> password_confirmation: "foobar") (0.1ms) begin transaction (0.1ms) SAVEPOINT active_record_1 User Exists? (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "practice@example.com"], ["LIMIT", 1]] User Create (2.0ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?) [["name", "Tutotrial Exercise"], ["email", "practice@example.com"], ["created_at", "2021-05-09 06:07:00.098755"], ["updated_at", "2021-05-09 06:07:00.098755"], ["password_digest", "$2a$12$vRV723Df0Gs/8cbAhJEGp.myt2N9jLP56.vMx/gHbPzJClacCzYLK"], ["activation_digest", "$2a$12$RupfD.8MUpgcIfOC3a8j5Op6E9H2ueSThqCjv.2YZHyJWUFBgvxni"]] (0.1ms) RELEASE SAVEPOINT active_record_1 => #<User id: 103, name: "Tutotrial Exercise", email: "practice@example.com", created_at: "2021-05-09 06:07:00", updated_at: "2021-05-09 06:07:00", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$RupfD.8MUpgcIfOC3a8j5Op6E9H2ueSThqCjv.2YZHy...", activated: false, activated_at: nil> >> user.remember_token => nil >> user.remember_digest => nil >> user.activation_token => "CFWtDLZrtEIf66jaLXCbvA" >> user.activation_digest => "$2a$12$RupfD.8MUpgcIfOC3a8j5Op6E9H2ueSThqCjv.2YZHyJWUFBgvxni" 11.3.1 - 2 リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。 ターミナル >> user.authenticated?(:remember, user.remember_token) => false >> user.authenticated?(:activation, user.activation_token) => true 11.3.2 editアクションで有効化 11.3.2 - 1 コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか? ターミナル $ rails server UserMailer#account_activation: processed outbound mail in 5.0ms Delivered mail 60976f4cec170_183b2aad262d3360977c2@ip-172-31-2-18.mail (2.5ms) Date: Sun, 09 May 2021 05:12:44 +0000 From: noreply@example.com To: foobarbaz@example.com Message-ID: <60976f4cec170_183b2aad262d3360977c2@ip-172-31-2-18.mail> Subject: Account activation Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="--==_mimepart_60976f4ceb707_183b2aad262d336097656"; charset=UTF-8 Content-Transfer-Encoding: 7bit ----==_mimepart_60976f4ceb707_183b2aad262d336097656 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Hi Example User, Welcome to the Sample App! Click on the link below to activate your account: https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/account_activations/j1nbIubbruxZHEHOa2erpA/edit?email=foobarbaz%40example.com ----==_mimepart_60976f4ceb707_183b2aad262d336097656 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style> /* Email styles need to be inline */ </style> </head> <body> <h1>Sample App</h1> <p>Hi Example User,</p> <p> Welcome to the Sample App! Click on the link below to activate your account: </p> <a href="https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/account_activations/j1nbIubbruxZHEHOa2erpA/edit?email=foobarbaz%40example.com">Activate</a> </body> </html> ----==_mimepart_60976f4ceb707_183b2aad262d336097656-- https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/account_activations/j1nbIubbruxZHEHOa2erpA/edit?email=foobarbaz%40example.com の account_activations/j1nbIubbruxZHEHOa2erpA/ 11.2.3 - 2 先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。 ターミナル $ rails console >> user = User.find_by(email: "foobarbaz@example.com") => #<User id: 102, name: "Example User", email: "foobarbaz@example.com", created_at: "2021-05-09 05:12:44", updated_at: "2021-05-09 14:57:15", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$cndGj/.n1aXZscUu4cu9xeBQAvLqmF9UjsIC13IUoOU...", activated: true, activated_at: "2021-05-09 14:57:15"> >> user.activated => true 11.3.3 有効化のテストとリファクタリング 11.3.3 - 1 リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう。これでデータベースへの問い合わせが1回で済むようになります(注意!update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なります)。また、変更後にテストを実行し、 green になることも確認してください。 app/models/user.rb class User < ApplicationRecord # 省略 # アカウントを有効にする def activate update_columns(activated: true, activated_at: Time.zone.now) end # 省略 end 11.3.3 - 2 現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。 class UsersController < ApplicationController # 省略 def index @users = User.where(activated: true).paginate(page: params[:page]) end def show @user = User.find(params[:id]) redirect_to root_url and return unless @user.activated? end # 省略 end 11.3.3 - 3 ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。 この問題は難易度が高く、自力でテストを実装することができませんでした。 以下の記事を参考にしてコードを書きました 【RailsTutorial】11章 解答(11.3.3−3のみ) 11.4 本番環境でのメール送信 11.4 - 1 実際に本番環境でユーザー登録をしてみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか? 11.4 - 2 メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。 ターミナル $ heroku logs --tail . . . 2021-05-10T05:23:57.753880+00:00 app[web.1]: D, [2021-05-10T05:23:57.753768 #16] DEBUG -- : [74c07d8b-1601-41b9-9be8-38b017897e71] User Update (12.7ms) UPDATE "users" SET "activated" = $1, "activated_at" = $2 WHERE "users"."id" = $3 [["activated", true], ["activated_at", "2021-05-10 05:23:57.739574"], ["id", 3]] . . . 本番環境でのメールアドレス送信にかなり苦戦しました。 ユーザー作成にはいつも架空のメールアドレスを使ってユーザー登録をしていたので、 本番環境でも同じように架空のアドレスを使ってサインアップしようとした際、 このようにエラーが起こりました。 herokuのログを確認すると、 ターミナル $ heroku logs 2021-05-09T19:44:54.809170+00:00 app[web.1]: [a355f1bd-90fa-4e59-acec-15e85d1df72c] Net::SMTPUnknownError (could not get 3xx (421: 421 Domain sandbox2128f11eaa164b908e0f79ba3d7c93ab.mailgun.org is not allowed to send: Sandbox subdomains are for test purposes only. Please add your own domain or add the address to authorized recipients in Account Settings. メールが届かないというようなエラーだったようですが、意味がわからず4時間近く格闘したのち、 自身のGmailにて本番環境でサインアップしたところ、一発で通りました(笑)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションをまとめる方法

はじめに アウトプット用に書かせて頂きます。 ユーザー管理機能を実装するためにuserテーブルにバリデーションをかけ、コードレビューをして頂いた回答で同じバリデーションをかけるときはまとめる事ができるという事を知りました。 with_options do 下記の記述でまとめる事ができます。 with_options do #記述 end ではまずwith_optionsを使わないコードを記述します。 user.rb class User < ApplicationRecord validates :nickname, presence: true validates :birth_day, presence: true validates :first_name, presence: true validates :last_name, presence: true validates :first_name_kana, presence: true validates :last_name_kana, presence: true end #presence trueとは空では登録できないようにするバリデーション 全てprecence: trueと同じバリデーションをかけているのでひとまとめにします。 user.rb class User < ApplicationRecord with_options presence: true do validates :nickname validates :birth_day validates :first_name validates :last_name validates :first_name_kana validates :last_name_kana end end これでひとまとめにする事ができます。 しかしprecence true以外にもバリデーションをする時が多いと思います。 user.rb class User < ApplicationRecord validates :nickname, presence: true validates :birth_day, presence: true validates :family_name, presence: true, format: { with: /\A[ぁ-んァ-ヶ一-龥々]/ } validates :first_name presence: true, format: { with: /\A[ぁ-んァ-ヶ一-龥々]/ } validates :family_name_kana, presence: true, format: {with: /\A[\p{katakana} ー-&&[^ -~。-゚]]+\z/ } validates :first_name_kana, presence: true, format: {with: /\A[\p{katakana} ー-&&[^ -~。-゚]]+\z/ } end 上記の記述で下記の正規表現を使いました。 正規表現 意味 /\A[ぁ-んァ-ヶ一-龥々]/ 全角ひらがな、全角カタカナ、漢字 /\A[\p{katakana} ー-&&[^ -~。-゚]]+\z/ 全角カタカナ     このような時にwith_optionsの中にwith_optionsを入れ子のようにする事ができます。 user.rb class User < ApplicationRecord #全行にpresence: trueをする with_options presence: true do validates :nickname validates :birth_day #この2行だけformat: { with: /\A[ぁ-んァ-ヶ一-龥々]/ } with_options format: { with: /\A[ぁ-んァ-ヶ一-龥々]/ } do validates :family_name validates :first_name end #この2行だけ {with: /\A[\p{katakana} ー-&&[^ -~。-゚]]+\z/ } with_options format: {with: /\A[\p{katakana} ー-&&[^ -~。-゚]]+\z/ } do validates :family_name_kana validates :first_name_kana end end end 以上です。     
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Amazon SESの署名バージョン4に対応させる

はじめに RailsでAmazon SESの署名バージョンを3から4に対応させたときの備忘録です。 SESのライブラリはgem 'aws-ses'を使っていました。 人によってファイルの設定やリージョンなどは異なるので、あくまで参考程度にしてください。 環境 Rails 6.0.3.7 ruby 2.6.5 MacOS gem 'aws-ses', '~> 0.6' エラー内容 FATAL -- : AWS::SES::ResponseError (InvalidClientTokenId - Signature Version 3 requests are deprecated from March 1, 2021. From that date on, we are progressively rejecting such requests. To resolve the issue you must migrate to Signature Version 4. If you are self-signing your requests, refer to the documentation for Authenticating requests to the Amazon SES API [1] with Signature Version 4 [2]. If you are not self-signing your requests, simply update your SDK/CLI to the latest version. [1] https://docs.aws.amazon.com/ses/latest/DeveloperGuide/using-ses-api-authentication.html [2] https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html): google翻訳 致命的-: AWS :: SES :: ResponseError(InvalidClientTokenId-署名バージョン3のリクエストは2021年3月1日から非推奨になりました。その日以降、このようなリクエストは段階的に拒否されます。問題を解決するには、署名バージョン4に移行する必要があります。 -リクエストに署名するには、署名バージョン4 [2]を使用したAmazonSES API [1]へのリクエストの認証に関するドキュメントを参照してください。リクエストに自己署名しない場合は、SDK / CLIを最新バージョンに更新してください。[ 1] https://docs.aws.amazon.com/ses/latest/DeveloperGuide/using-ses-api-authentication.html [2] https://docs.aws.amazon.com/general/latest/gr/ sigv4-create-canonical-request.html): エラーの内容通り、署名バージョンを4に移行させる必要があるそうです。 やったこと gem 'aws-ses', '~> 0.6'を gem aws-ses-v4に変えた。 aws-sesのissuesをみると、バージョン4に移行することについて結構多くの人が話していました。 gem aws-ses-v4はgem 'aws-ses'を署名バージョン4に対応させたgemです。 他にもAWSが推奨しているaws-sdk-railsというGemがあるそうですが、そちらに移行させる時間はなさそうだったので今回はaws-ses-v4を使うことにしました。 できれば公式が推奨している方を使うべきだと思います。 Gemを変更 Gemfile - gem 'aws-ses', '~> 0.6' + gem "aws-ses-v4", require: "aws/ses" $ bundle install aws.rbの変更 ここら辺は人によって違うかもしれません。 各々のメールの設定に合わせてください。 ちなみに私は最終的に以下のようになりました。 config/initializers/aws.rb ActionMailer::Base.add_delivery_method :ses, AWS::SES::Base, access_key_id: Rails.application.credentials.aws_ses_access_key_id, secret_access_key: Rails.application.credentials.aws_ses_secret_access_key, server: 'email.リージョン名.amazonaws.com', #人によって違います region: 'リージョン名' #SESのリージョンを指定。 例:us-east-2 以下のエラーが出た時はSESのリージョンが間違っている可能性があります。 SignatureDoesNotMatch - Credential should be scoped to a valid region, not 'ap-northeast-1' また、awsのアクセスキーなどを記載するcredentials.yml.encには、以下のコマンドで確認・編集できます。 EDITOR=vim rails credentials:edit 以上です。 参考 aws-ses GitHub aws-ses-v4 GitHub 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

attr_accessorとは

この記事の対象者 railsはある程度経験がある(railsチュートリアルを完走した人) rubyでアクセサメソッドの使用経験のない人 記事の要約 attr_accessorはインスタンス内の属性を変更したり、参照する際に必要なメソッド 検索クエリなどのDBに保存する必要のないモデルを使用する際に使う。検索機能などはどんなwebサービスでも使用されることが多いため、この辺の理解はしっかりしておく必要がある。 attr_accessorとは rubyで使用されるアクセサメソッドのことを指します。 そもそもアクセサメソッドとは? アクセサとは、オブジェクト指向プログラミングで、オブジェクト内部のメンバ変数(属性、プロパティ)に外部からアクセスするために用意されたメソッド。メンバ変数をオブジェクト内部に隠蔽し、外部から直接参照させないようにするために用意される。 参考記事 これはどういうことかと言うと、「クラスから作られたインスタンス内の属性を変更したり、呼び出したりするために必要なメソッド」ということです。 具体例 ここまで文章だけ読んでもピンとこないと思うので、ここからは具体例を用いて説明したいと思います。 Userの名前を管理するモデルを作っていると仮定します。 user.rb class User def initialize(name) @name = name end end ここで試しにuser = User.new(name: '佐藤')と入力してみます。 すると以下のようなオブジェクトが返ってきます。 #<User:0x00007feb6aa43820 @name='佐藤'> ここでインスタンス内のname属性の値を呼び出してみたいと思います。 user = User.new(name: '佐藤') user.@name SyntaxError: unexpected tIVAR user.@name    ^~~~~ インスタンス変数はクラスの外部から参照できないので、エラーが出てしまいましたね。 railsを使った経験のある方ならなんとなくイメージできるかと思いますが、インスタンス内の属性を参照するにはuser.nameと書く必要があります。 user = User.new(name: '佐藤') user.name NoMethodError: undefined method `name' for #<User:0x00007fcee585d520 @name="佐藤"> nameメソッドがないとエラー文で言われているので、nameメソッドを定義する必要があります。 user.rb class User def initialize @name = name end  def name @name end end user = User.new(name: '佐藤') user.name => '佐藤' と出力されたと思います。 今度は佐藤さんではなくて、田中さんに名前を書き換えたいとします。 user = User.new(name: '佐藤') user.name = '田中' NoMethodError: undefined method `name=' for #<User:0x00007fcee585d520 @name="佐藤"> name=というメソッドがないよと言われてますね。 ですので今後は以下のようにuser.rbを書き直していきます。 user.rb class User def initialize @name = name end  def name @name end def name=(value) @name = value end end user = User.new(name: '佐藤') user.name = '田中' => "田中" user.name => '田中' ちゃんと書き変わりましたね。このように、インスタンス内の値を書き換えたり、参照するには以下のようにメソッドを定義してあげる必要があります。しかし、属性が少ないならこれでも良いかもしれませんが、属性が多くなってくるとコードが長くなってしまいますね。 そこで、Rubyではアクセサメソッドというものがあり、先ほどのuser.rbをもっと簡単に書き換えることができます。 class User attr_accessor :name def initialize @name = name end end こう書くだけでUserモデルのインスタンス内のname属性を参照・変更することが出来ます。 こっちの方が記述量も少なくスマートですよね。 他にも参照だけしたい場合はattr_reader、変更だけしたい場合にはattr_writerというアクセサメソッドが存在するので、その時に応じて使い分けましょう。 これってどんな時に使うの? ここまで読んでいただいて、「使い方はわかったけどどんな時に使うの? そもそもrailsでアプリ作った時にこんなの使わなかったよ」と思う方もいらっしゃると思います。ですので、どんな時に使うかも説明します。 DBにデータを保存する必要がないモデル(例えば検索機能の検索パラメータなど)を作成するときに使用します。 通常DBに保存する場合には、railsのApplicationRecordというクラスを継承しているため、アクセサメソッドを定義しなくてもuser.nameと書くだけで、インスタンス内の属性を読み込めてしまいます。しかし、DBにデータを保存しない場合は、自分でアクセサメソッドを定義する必要があります。(実務で実際にやりました。) 最後に アクセサメソッドに関しては、プログラミング学習を始めた初期に見たことがありましたが、正直全く覚えていませんでした。しかし、実務に入って結構使うことが多いと感じたのでこれから転職活動を始められる方はその辺の理解もしっかりしておくことが重要だと感じました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む