20210425のRailsに関する記事は28件です。

テーブルのデータをビューに表示させる方法(Ruby on Rails)

プログラミング学習のための備忘録 2021/04/25 Ruby on Railsで資産管理アプリを作成しています。 備忘録として、作成したテーブルのデータをWebページに表示させる流れをまとめました。 コントローラを作成しよう アクションを定義するためのコントローラとビューファイルを作成します。以下のコマンドを実行する。 ターミナル $ rails g controller dividends index テーブルに保存されたデータをビューに表示しよう 以下のステップを踏むことで、テーブルに保存されたデータをビューに表示することができる。 ・コントローラのアクションで、モデルからテーブルのデータを取得する。 ・受け取ったデータを変数に代入する。 ・変数をビューに表示させる。 テーブルのデータを取得する  コントローラのアクションで、モデルからテーブルのデータを取得する処理を定義する。今回は、テーブルに保存されているデータ全てを取得する。テーブルに保存されている全てのデータを取得する場合、モデル名.allで取得することができる。  取得したデータをdividendsという変数に代入する。 dividends_controller.rb class DividendsController < ApplicationController def index @dividends = Dividend.all end end ビューに表示させる eachメソッドを用いて、取得したデータの内、nameカラムのデータを全て表示させる。 index.html.erb <div class="main posts-index"> <div class="container"> <% @dividends.each do |dividend| %> <div class="dividends-index-item"> <div class="dividend-name"> <%= dividend.name %> </div> </div> <% end %> </div> </div> テーブルに保存されたデータをビューに表示するための処理は以上になります。最後に、指定のURLにアクセスした時に、ビューファイルを表示させるようにルーチングを追加する。 route.rb Rails.application.routes.draw do get "/dividends/index" => "dividends#index" # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end 結果 今回は、仮のデータをコンソールで入力してみました。無事に銘柄名が表示されました! 次にやること データを入力できるフォームを作成する! 参考文献 https://qiita.com/Hal_mai/items/aed97a6aba2293450a74
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル 第6版 振り返り 第11章

はじめに このまとめ記事はRailsチュートリアルを1周終えた私が1周目で分からなかった所や記憶に残したい箇所のみをピックアップして記述しています。完全解説記事ではないので注意して下さい。 私と同じく2周目の方、たまに復習したいなと振り返りを行う方等におすすめです。 とは言いつつも、9章あたりから難しくなったのでこの章も全て書き残しています。 アカウントの有効化 現時点のアプリケーションは、新規登録したユーザーは初めからすべての機能にアクセスできるようになっている。 本章では、アカウントを有効化するステップを新規登録の途中に差し込むことで、本当にそのメールアドレスの持ち主なのかどうかを確認できるようにする。 基本的な流れは、こんな感じ。 ①有効化トークンやダイジェストを関連付けておいた状態する。 ②有効化トークンを含めたリンクをユーザーにメールで送信する。 ③ユーザーがそのリンクをクリックすると有効化できるようにする。 これらの機能ごとに新しいリソースを作成し、コントローラ/ルーティング/データベース移行の例について1つずつ学んでいく。 アカウントを有効化する段取りは、ユーザーログイン、特にユーザーの記憶と似ているらしい。 ①ユーザーの初期状態は「有効化されていない」(unactivated)にしておく。 ②ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。 ③有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく。 ④ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。 ⑤ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated)に変更する。 都合の良いことに、今回実装するアカウント有効化やパスワード再設定の仕組みと、以前に実装したパスワードや記憶トークンの仕組みにはよく似た点が多いらしく、多くのアイデアを使い回すことができる。 それぞれの仕組みの似ている点をまとめ 検索キー string digest authentication email password password_digest authenticate(password) id remember_token remember_digest authenticated?(:remember, token) email activation_token activation_digest authenticated?(:activation, token) email reset_token reset_digest authenticated?(:reset, token) 引用:Railsチュートリアル11表11.1 AccountActivationリソース(11.1) セッション機能を使ってアカウントの有効化という作業を「リソース」としてモデル化することにする。 アカウントの有効化リソースはActive Recordのモデルとは関係ないので、両者を関連付けることはしない。 その代わりに、この作業に必要なデータ(有効化トークンや有効化ステータスなど)をUserモデルに追加する。 アカウント有効化もリソースとして扱いたいのだが、少し使い方が異なる点に注意。 例えば、有効化用のリンクにアクセスして有効化のステータスを変更する部分では、RESTのルールに従うとPATCHリクエストとupdateアクションになるべき。 しかし、有効化リンクはメールでユーザーに送られる。 ユーザーがこのリンクをクリックすれば、それはブラウザで普通にクリックしたときと同じであり、その場合ブラウザから発行されるのは、updateアクションで使うPATCHリクエストではなくGETリクエストになってしまう。 このため、ユーザーからのGETリクエストを受けるために、本来であればupdateのところをeditアクションに変更して使う。 AccountActivationsコントローラ(11.1.1) UsersリソースやSessionsリソースのときと同様に、AccountActivationsリソースを作るために、まずはAccountActivationsコントローラを生成。 $ rails generate controller AccountActivations そして有効かのメールには次のURLが含まれている。 edit_account_activation_url(activation_token, ...) これは、editアクションへの名前付きルートが必要になるということ。 そこでまずは、名前付きルートを扱えるようにするため、ルーティングにアカウント有効化用のresources行を追加。 config/routes.rb resources :account_activations, only: [:edit] HTTPリクエスト URL Action 名前付きルート GET /account_activation/トークン/edit edit edit_account_activation_url(token) 引用:Railsチュートリアル11.1.1表:11.2 次は、アカウント有効化用のデータモデルとメイラーを作成していく。 AccountActivationのデータモデル(11.1.2) 有効化のメールには一意の有効化トークンが必要。 パッと思いつくのは、送信メールとデータベースのそれぞれに同じ文字列を置いておく方法。 しかし、この方法では万が一データベースの内容が漏れたとき、多大な被害に繋がってしまう。 例えば、攻撃者がデータベースへのアクセスに成功した場合、新しく登録されたユーザーアカウントの有効化トークンを盗み取り、本来のユーザーが使う前にそのトークンを使ってしまうケースが考えられる。 このような事態を防ぐためにパスワードや記憶トークンと同じくハッシュ化した文字列をデータベースに保存するようにする。 具体的には、次のように仮想属性の有効化トークンにアクセスし、 user.activation_token このようなコードでユーザーを認証できるようにする。 user.authenticated?(:activation, token) これは9章で作ったauthenticated?メソッド def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end を少し改変して使っている。 具体的には、activated属性を追加して論理値を取るようにしている。 これで、自動生成の論理値メソッドと同じような感じで、ユーザーが有効であるかどうかをテストできるようになる。 if user.activated? ... 次にマイグレーションでデータモデルを追加。 $ rails generate migration add_activation_to_users \ activation_digest:string activated:boolean activated_at:datetime 3つの属性が新しく追加された。 次に、admin属性の時と同様にactivated属性のデフォルトの論理値をfalseにしておく。 db/migrate/[timestamp]_add_activation_to_users.rb class AddActivationToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :activation_digest, :string add_column :users, :activated, :boolean, default: false add_column :users, :activated_at, :datetime end end Activationトークンのコールバック ユーザーが新しい登録を完了するためには必ずアカウントの有効化が必要になる。 そのため、有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく必要がある。 メールアドレスを保存する際にもデータベースに保存する前に、メールアドレスを全部小文字に変換する必要があった。 あのときは、before_saveコールバックにdowncaseメソッドをバインドした。 オブジェクトにbefore_saveコールバックを用意しておくと、オブジェクトが保存される直前、オブジェクトの作成時や更新時にそのコールバックが呼び出される。 しかし今回は、オブジェクトが作成されたときだけコールバックを呼び出し、それ以外のときには呼び出したくない。 この場合は、before_createコールバックを使用する。 before_create :create_activation_digest コールバックはこのように定義され、メソッド参照と呼ばれている。 こうするとRailsはcreate_activation_digestというメソッドを探し、ユーザーを作成する前に実行するようになる。 create_activation_digestメソッド自体はUserモデル内でしか使わないため、外部に公開する必要はない。 そのため、privateキーワードを指定して、このメソッドをRuby流に隠蔽。 private def create_activation_digest # 有効化トークンとダイジェストを作成および代入する end クラス内でprivateキーワードより下に記述したメソッドは自動的に非公開となる。 これはコンソールですぐに確認できる。 $ rails console >> User.first.create_activation_digest NoMethodError: private method `create_activation_digest' called for #<User> 今回before_createコールバックを使う目的は、トークンとそれに対応するダイジェストを割り当てるため。 実際の割り当ては、この通り。 self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) このコードでは、記憶トークンや記憶ダイジェストのために作ったメソッドを使いまわしている。 9章のrememberメソッドと比べてみる。 # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end 主な違いは、後者のupdate_attributeの使い方にある。 この違いは、記憶トークンやダイジェストは既にデータベースにいるユーザーのために作成されるのに対し、before_createコールバックの方はユーザーが作成される前に呼び出されること。 このコールバックがあることで、User.newで新しいユーザーが定義されると、activation_token属性やactivation_digest属性が得られるようになる。 後者のactivation_digest属性は既にデータベースのカラムとの関連付けができあがっているため、ユーザーが保存されるときに一緒に保存される。 これまで説明したことをUserモデルに実装する。 app/models/user.rb class User < ApplicationRecord attr_accessor :remember_token, :activation_token before_save :downcase_email before_create :create_activation_digest validates :name, presence: true, length: { maximum: 50 } . . . private # メールアドレスをすべて小文字にする def downcase_email self.email = email.downcase end # 有効化トークンとダイジェストを作成および代入する def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end end 有効化トークンは本質的に仮のものでなければならないため、このモデルのattr_accessorにもう1つ追加した。 また、以前に実装したメールアドレスを小文字にするメソッドも、メソッド参照に切り替えた。 サンプルユーザーの生成とテスト 先に進む前に、サンプルデータとfixtureも更新し、テスト時のサンプルとユーザーを事前に有効化する。 db/seeds.rb # メインのサンプルユーザーを1人作成する User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true, activated: true, activated_at: Time.zone.now) # 追加のユーザーをまとめて生成する 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password, activated: true, activated_at: Time.zone.now) end michael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> admin: true activated: true activated_at: <%= Time.zone.now %> archer: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> lana: name: Lana Kane email: hands@example.gov password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> malory: name: Malory Archer email: boss@example.gov password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> <% 30.times do |n| %> user_<%= n %>: name: <%= "User #{n}" %> email: <%= "user-#{n}@example.com" %> password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> <% end %> Time.zone.nowはRailsの組み込みヘルパーであり、サーバーのタイムゾーンに応じたタイムスタンプを返してくれる。 そしてDBを初期化してこの節は終了。 $ rails db:migrate:reset $ rails db:seed アカウント有効化メールの送信(11.2) データのモデル化が終わったので、今度はアカウント有効化メールの送信に必要なコードを追加。 このメソッドではAction Mailerライブラリを使ってUserのメイラーを追加する。 このメイラーはUsersコントローラのcreateアクションで有効化リンクをメール送信するために使用する。 メイラーの構成はコントローラのアクションとよく似ており、メールのテンプレートをビューと同じ要領で定義できる。 このテンプレートの中に有効化トークンとメールアドレスのリンクを含め、使用する。 送信メールのテンプレート(11.2.1) メイラーは、モデルやコントローラと同様にrails generateで生成可能。 $ rails generate mailer UserMailer account_activation password_reset これにより、今回必要となるaccount_activationメソッドと、12章で使用するpassword_resetメソッドが生成された。 また、generateコマンドによって、生成したメイラーごとに、ビューのテンプレートが2つずつ生成されている。 1つはテキストメール用のテンプレート、1つはHTMLメール用のテンプレートである。 ここでは、アカウント有効化に使うテンプレートを確認。 app/views/user_mailer/account_activation.text.erb UserMailer#account_activation <%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb app/views/user_mailer/account_activation.html.erb <h1>UserMailer#account_activation</h1> <p> <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb </p> 生成されたメイラーの動作を簡単に追ってみる。 app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base default from: "from@example.com" layout 'mailer' end app/mailers/user_mailer.rb class UserMailer < ApplicationMailer # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.user_mailer.account_activation.subject # def account_activation @greeting = "Hi" mail to: "to@example.org" end # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.user_mailer.password_reset.subject # def password_reset @greeting = "Hi" mail to: "to@example.org" end end application_mailer.rbには、デフォルトのfromアドレスがあり、メールのフォーマットに対応するメイラーレイアウトも使われている。 user_mailer.rbの各メソッドには宛先メールアドレスもある。 また、生成されるHTMLメイラーのレイアウトやテキストメイラーのレイアウトはapp/views/layoutsで確認可能。 生成されたコードにはインスタンス変数@greetingも含まれていて、このインスタンス変数は、ちょうど普通のビューでコントローラのインスタンス変数を利用できるのと同じように、メイラービューで利用可能。 最初に、、生成されたテンプレートをカスタマイズして、実際に有効化メールで使えるようにする。 次に、ユーザーを含むインスタンス変数を作成してビューで使えるようにし、user.emailにメール送信。 app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base default from: "noreply@example.com" layout 'mailer' end app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def account_activation(user) @user = user mail to: user.email, subject: "Account activation" end def password_reset @greeting = "Hi" mail to: "to@example.org" end end user_mailer.rbでは、mailにsubjectキーを引数として渡していて、この値は、メールの件名にあたる。 テンプレートビューは、通常のビューと同様ERBで自由にカスタマイズ可能。 ここでは挨拶文にユーザー名を含め、カスタムの有効化リンクを追加する。 この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにするため、リンクにはメールアドレスとトークンを両方含めておく必要がある。 AccountActivationsリソースで有効化をモデル化したので、トークン自体は、定義した名前付きルートの引数で使われる。 edit_account_activation_url(@user.activation_token, ...) ちょっと復習。 edit_user_url(user) 上のメソッドでは次の形式のURLを生成していた。 https://www.example.com/users/1/edit これに対応するアカウント有効化リンクのベースURLは次のようになる。 https://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit 上のURLの「q5lt38hQDc_959PVoo6b7A」という部分は、new_tokenメソッドで生成されたもの。 URLで使えるようにBase64でエンコードされている。 これはちょうど/users/1/editの「1」のようなユーザーIDと同じ役割を果たす。 このトークンは、特にAccountActivationsコントローラのeditアクションではparamsハッシュでparams[:id]として参照できる。 クエリパラメータを使って、このURLにメールアドレスもうまく組み込んでみる。 クエリパラメータとは、URLの末尾で疑問符「?」に続けてキーと値のペアを記述したもの。 account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com このとき、メールアドレスの「@」記号がURLでは「%40」となっている点に注目。 これは「エスケープ」と呼ばれる手法で、通常URLでは扱えない文字を扱えるようにするために変換されている。 Railsでクエリパラメータを設定するには、名前付きルートに対して次のようなハッシュを追加する。 edit_account_activation_url(@user.activation_token, email: @user.email) このようにして名前付きルートでクエリパラメータを定義すると、Railsが特殊な文字を自動的にエスケープしてくれ、コントローラでparams[:email]からメールアドレスを取り出すときには、自動的にエスケープを解除してくれる。 ここまでくると、user_mailer.rbで定義した@userインスタンス変数、editへの名前付きルート、ERBを組み合わせて、必要なリンクを作成できる。 アカウント有効化のHTMLビューでは、正しいリンクを組み立てるためにlink_toメソッドを使用している。 app/views/user_mailer/account_activation.text.erb Hi <%= @user.name %>, Welcome to the Sample App! Click on the link below to activate your account: <%= edit_account_activation_url(@user.activation_token, email: @user.email) %> app/views/user_mailer/account_activation.html.erb <h1>Sample App</h1> <p>Hi <%= @user.name %>,</p> <p> Welcome to the Sample App! Click on the link below to activate your account: </p> <%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %> 送信メールのプレビュー(11.2.2) ここまでで定義したテンプレートの実際の表示を簡単に確認するために、メールプレビューという裏技を使用する。 Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができる。 メールを実際に送信しなくてもよいので大変便利。 これを利用するには、アプリケーションのdevelopment環境の設定に手を加える必要がある。 config/environments/development.rb Rails.application.configure do . . . config.action_mailer.raise_delivery_errors = false host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。 # クラウドIDEの場合は以下をお使いください config.action_mailer.default_url_options = { host: host, protocol: 'https' } # localhostで開発している場合は以下をお使いください # config.action_mailer.default_url_options = { host: host, protocol: 'http' } . . . end 上記のホスト名'example.com'の部分を、自分のdevelopment環境に合わせて変更する。 developmentサーバーを再起動して設定を読み込んだら、次はUserメイラーのプレビューファイルの更新が必要となる。 test/mailers/previews/user_mailer_preview.rb # Preview all emails at http://localhost:3000/rails/mailers/user_mailer class UserMailerPreview < ActionMailer::Preview # Preview this email at # http://localhost:3000/rails/mailers/user_mailer/account_activation def account_activation UserMailer.account_activation end # Preview this email at # http://localhost:3000/rails/mailers/user_mailer/password_reset def password_reset UserMailer.password_reset end end user_mailer.rbで定義したaccount_activationの引数には有効なUserオブジェクトを渡す必要があるため、上記のままでは動かない。 これを回避するために、user変数が開発用データベースの最初のユーザーになるように定義して、それをUserMailer.account_activationの引数として渡す。 test/mailers/previews/user_mailer_preview.rb # Preview this email at # http://localhost:3000/rails/mailers/user_mailer/account_activation def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end 上記では、user.activation_tokenの値にも代入している点に注目。 テキストビュー、HTMLビューのテンプレートでは、アカウント有効化のトークンが必要となるため、ここで代入しなければならない。 なお、activation_tokenは仮の属性でしかないため、データベースのユーザーはこの値を実際には持っていない。 そして実際にプレビューコードを入力するとHTMLメールとテキストメールのプレビューを確認することができる。 ここ1週目だとエラー連発しまくっつて完全に挫折したんだけど今回はスムーズに行けて良かった。 送信メールのテスト(11.2.3) 最後に、このメールプレビューのテストも作成して、プレビューをダブルチェックできるようにする。 generateで便利なテスト例がRailsによって自動生成されているので割と簡単らしい。 test/mailers/user_mailer_test.rb require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "account_activation" do mail = UserMailer.account_activation assert_equal "Account activation", mail.subject assert_equal ["to@example.org"], mail.to assert_equal ["from@example.com"], mail.from assert_match "Hi", mail.body.encoded end test "password_reset" do mail = UserMailer.password_reset assert_equal "Password reset", mail.subject assert_equal ["to@example.org"], mail.to assert_equal ["from@example.com"], mail.from assert_match "Hi", mail.body.encoded end end これはRailsによって自動生成されたテスト。 assert_matchと呼ばれる強力なメソッドが使用されている。 これを使うと正規表現で文字列をテストできる。 assert_match 'foo', 'foobar' # true assert_match 'baz', 'foobar' # false assert_match /\w+/, 'foobar' # true assert_match /\w+/, '$#!*+@' # false またテストの完成形では、assert_matchメソッドを使って名前、有効化トークン、エスケープ済みメールアドレスがメール本文に含まれているかどうかをテストしている。 CGI.escape(user.email) また、上記のメソッドを使用すると、テスト用のユーザーのメールアドレスをエスケープすることも可能。 test/mailers/user_mailer_test.rb require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "account_activation" do user = users(:michael) user.activation_token = User.new_token mail = UserMailer.account_activation(user) assert_equal "Account activation", mail.subject assert_equal [user.email], mail.to assert_equal ["noreply@example.com"], mail.from assert_match user.name, mail.body.encoded assert_match user.activation_token, mail.body.encoded assert_match CGI.escape(user.email), mail.body.encoded end end 上記のテストでは、fixtureユーザーに有効化トークンを追加している点に注目。 これは、追加しない場合は空白になります。 しかし現状では、このテストは失敗する。 このテストを成功させるには、テストファイル内のドメイン名を正しく設定する必要がある。 config/environments/test.rb config.action_mailer.delivery_method = :test config.action_mailer.default_url_options = { host: 'example.com' } ユーザーのcreateアクションを更新(11.2.4) あとはユーザー登録を行うcreateアクションに数行追加するだけで、メイラーをアプリケーションで実際に使うことが可能となる。 app/controllers/users_controller.rb class UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save UserMailer.account_activation(@user).deliver_now flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end . . . end 登録後のリダイレクト先が、ユーザーのプロフィールページからルートURLに変更。 さらに、作成した段階で以前のようにログインしないようになった。 したがって、アプリケーションの動作が仮に正しくても、現在のテストは、失敗してしまう。 今回は、失敗が発生するテストの行をひとまずコメントアウトして後で元に戻す。 test/integration/users_signup_test.rb follow_redirect! # assert_template 'users/show' # assert is_logged_in? end この状態で実際に新規ユーザーとして登録してみると、リダイレクトされてメールが生成されるようになる。 ただし、実際にメールが生成されるわけではないので注意。 サーバーログに出力されるだけとなっている。 アカウントを有効化する(11.3) ここまででメールを作成することができた。 今度はAccountActivationsコントローラのeditアクションを書いていく。 authenticated?メソッドの抽象化(11.3.1) 有効化トークンとメールは、params[:id]とparams[:email]で参照することができた。 過去の章で学んだことを元に、次のようなコードでユーザーを検索して認証する。 user = User.find_by(email: params[:email]) if user && user.authenticated?(:activation, params[:id]) 上のコードで使っているauthenticated?メソッドは、アカウント有効化のダイジェストと、渡されたトークンが一致するかどうかをチェックしている。 ただし、このメソッドは記憶トークン用のため今は正常に動作しない。 # トークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end remember_digestはUserモデルの属性なため、モデル内では次のように書き換えることができる。 self.remember_digest 今回は、上のコードのrememberの部分をどうにかして変数として扱いたい。 つまり、次のコード例のように、状況に応じて呼び出すメソッドを切り替えたい。 self.FOOBAR_digest これから実装するauthenticated?メソッドでは、受け取ったパラメータに応じて呼び出すメソッドを切り替える手法を使う。 この手法はメタプログラミングと呼ばれている。 メタプログラミングを一言で言うと「プログラムでプログラムを作成する」ことらしい。 メタプログラミングはRubyが有するきわめて強力な機能で、Railsの機能の多くは、Rubyのメタプログラミングによって実現されている。 今回、重要なのがsendメソッド。 このメソッドは、渡されたオブジェクトに「メッセージを送る」ことによって、呼び出すメソッドを動的に決めることができる。 コンソールで例を確認してみる。 $ rails console >> a = [1, 2, 3] >> a.length => 3 >> a.send(:length) => 3 >> a.send("length") => 3 このときsendを通して渡したシンボル:lengthや文字列"length"は、いずれもlengthメソッドと同じ結果となった。 つまり、どちらもオブジェクトにlengthメソッドを渡しているため、等価なのである。 もう1つ、データベースの最初のユーザーが持つactivation_digest属性にアクセスする例を確認。 >> user = User.first >> user.activation_digest => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" >> user.send(:activation_digest) => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" >> user.send("activation_digest") => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" >> attribute = :activation >> user.send("#{attribute}_digest") => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" 最後の列では、シンボル:activationと等しいattribute変数を定義し、文字列の式展開(interpolation)を使って引数を正しく組み立ててから、sendに渡している。 文字列'activation'でも同じことができるが、Rubyではシンボルを使うのが一般的。 "#{attribute}_digest" シンボルと文字列どちらを使った場合でも、上のコードは次のように文字列に変換される。 "activation_digest" sendメソッドの動作原理がわかったので、この仕組みを利用してauthenticated?メソッドを書き換えてみる。 def authenticated?(remember_token) digest = self.send("remember_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(remember_token) end 上のコードの各引数を一般化し、文字列の式展開も利用すると、次のようなコードにできる。 def authenticated?(attribute, token) digest = self.send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end 他の認証でも使えるように、上では2番目の引数tokenの名前を変更して一般化している点に注目。 また、このコードはモデル内にあるのでselfは省略することも可能で、最終的にRubyらしく書かれたコードは、次のようになる。 def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end ここまでできれば次のように呼び出すことでauthenticated?の従来の振る舞いを再現できる。 user.authenticated?(:remember, remember_token) 以上の説明を実際のUserモデルに適用した、抽象化したauthenticated?メソッドを作成する。 class User < ApplicationRecord . . . # トークンがダイジェストと一致したらtrueを返す def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end . . . end この段階でテストを行うと失敗する。 理由は、current_userメソッドと、nilダイジェストのテストの両方で、authenticated?が古いままになっており、引数も2つではなくまだ1つのままだから。 これを解消するため、両者を更新して、新しい一般的なメソッドを使うようにする。 app/helpers/sessions_helper.rb module SessionsHelper . . . # 現在ログイン中のユーザーを返す(いる場合) def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(:remember, cookies[:remember_token]) log_in user @current_user = user end end end . . . end test/models/user_test.rb test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?(:remember, '') end これでテストは成功する。 今回のように、リファクタリングを施すとエラーが発生しやすくなるため、しっかりしたテストスイートが不可欠。 editアクションで有効化(11.3.2) authenticated?が使用できるようになったため、editアクションを書く準備が整った。 このアクションは、paramsハッシュで渡されたメールアドレスに対応するユーザーを認証する。 ユーザーが有効であることを確認する中核は、次の部分。 if user && !user.activated? && user.authenticated?(:activation, params[:id]) !user.activated?という記述に注目。 このコードは、既に有効になっているユーザーを誤って再度有効化しないために必要。 正当であろうとなかろうと、有効化が行われるとユーザーはログイン状態になる。 もしこのコードがなければ、攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、本当のユーザーとしてログインできてしまう。 そうした攻撃を防ぐためにこのコードは非常に重要です。 上の論理値に基いてユーザーを認証するには、ユーザーを認証してからactivated_atタイムスタンプを更新する必要がある。 user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) 上のコードをeditアクションで使用する。 app/controllers/account_activations_controller.rb class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end 上記のコードでは、有効化トークンが無効だった場合の処理も行われている点に注目。 トークンが無効になるようなことは実際にはめったにありませんが、もしそうなった場合はルートURLにリダイレクトされる仕組みとなっている。 このコードを実装すると、ユーザー作成時にコンソールに表示されているURLで有効化することができるようになる。 この時点では、ユーザーのログイン方法を変更していないため、ユーザーの有効化にはまだ何の意味もない。 ユーザーの有効化が役に立つためには、ユーザーが有効である場合にのみログインできるようにログイン方法を変更する必要がある。 これを行うにはuser.activated?がtrueの場合にのみログインを許可し、そうでない場合はルートURLにリダイレクトしてwarningで警告を表示させるようにする。 app/controllers/sessions_controller.rb def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) if user.activated? log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else message = "Account not activated. " message += "Check your email for the activation link." flash[:warning] = message redirect_to root_url end else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end これで、ーザー有効化機能のおおまかな部分については実装できた。 次の項では、テストをもう少し追加し、リファクタリングを行う。 有効化のテストとリファクタリング(11.3.3) この項では、アカウント有効化の統合テストを追加する。 正しい情報でユーザー登録を行った場合のテストは既にあるため、このテストに若干手を加える。 test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest def setup ActionMailer::Base.deliveries.clear end test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' assert_select 'div#error_explanation' assert_select 'div.field_with_errors' end test "valid signup information with account activation" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end assert_equal 1, ActionMailer::Base.deliveries.size user = assigns(:user) assert_not user.activated? # 有効化していない状態でログインしてみる log_in_as(user) assert_not is_logged_in? #上のログインが失敗していればtrue # 有効化トークンが不正な場合 get edit_account_activation_path("invalid token", email: user.email) assert_not is_logged_in? # トークンは正しいがメールアドレスが無効な場合 get edit_account_activation_path(user.activation_token, email: 'wrong') assert_not is_logged_in? # 有効化トークンが正しい場合 get edit_account_activation_path(user.activation_token, email: user.email) assert user.reload.activated? follow_redirect! assert_template 'users/show' assert is_logged_in? end end 追加したコードは多いが、本当に重要な部分は次の1行。 assert_equal 1, ActionMailer::Base.deliveries.size 上のコードは、配信されたメッセージがきっかり1つであるかどうかを確認している。 配列deliveriesは変数なので、setupメソッドでこれを初期化しておかないと、並行して行われる他のテストでメールが配信されたときにエラーが発生してしまう。 ちなみにassignsメソッドを使うと対応するアクション内のインスタンス変数にアクセスできるようになる。 例えば、Usersコントローラのcreateアクションでは@userというインスタンス変数が定義されているが、 テストでassigns(:user)と書くとこのインスタンス変数にアクセスできるようになる、といった具合。 (post users_pathでUserコントローラの'createアクション'にアクセスしているからUserコントローラの'createアクション'の@user`が読み込まれている。) なお、このassignsメソッドはRails 5以降はデフォルトのRailsテストで非推奨化されている。 しかし、rails-controller-testingというgemを用いることで現在でも利用可能。 ここでテストようやく成功となる。 このテストができたため、ユーザー操作の一部をコントローラからモデルに移動するというささやかなリファクタリングを行う準備ができた。 ここでは特に、activateメソッドを作成してユーザーの有効化属性を更新し、send_activation_emailメソッドを作成して有効化メールを送信しする。 app/models/user.rb class User < ApplicationRecord . . . # アカウントを有効にする def activate update_attribute(:activated, true) update_attribute(:activated_at, Time.zone.now) end # 有効化用のメールを送信する def send_activation_email UserMailer.account_activation(self).deliver_now end private . . . end app/controllers/users_controller.rb class UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save @user.send_activation_email flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end . . . end app/controllers/account_activations_controller.rb class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.activate log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end user.rbでは、user.という記法を使っていない点に注目。 Userモデルにはそのような変数はないため、これがあるとエラーになる。 -user.update_attribute(:activated, true) -user.update_attribute(:activated_at, Time.zone.now) +update_attribute(:activated, true) +update_attribute(:activated_at, Time.zone.now) userをselfに切り替えるという手もあるが、Userモデルの中では右式のselfを省略できるため、必須ではない。 また、Userメイラー内の呼び出しでは、@userがselfに変更されている点にも注目。 -UserMailer.account_activation(@user).deliver_now +UserMailer.account_activation(self).deliver_now これでテストは成功となる。 演習 1 user.rbのactivateメソッドは、update_attributeを2回呼び出しているが、これは各行で1回ずつデータベースへ問い合わせしていることになる。 update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみる。 app/models/user.rb def activate update_columns(activated: true, activated_at: Time.zone.now) end 注意として、update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なる。 2 現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できる。 しかし、有効でないユーザーも表示されているため、表示させないようにする。 app/controllers/users_controller.rb 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 3 ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成。 演習課題で行ったのはindexとshowアクションの変更。 index → activatedがfalseのユーザの非表示。 show → アクセスした先のユーザーがアクティブで無かった場合、ホームに戻る。 まずはindexアクション。 アクティブ化が終わっていないuserを一人追加して、そのユーザーが表示されていないか確認をするテストを書けば良い。 まずはユーザーの追加。 test/fixtures/users.yml saitou: name: Saitou Yuki email: hands1@example.gov password_digest: <%= User.digest('password') %> activated: false activated_at: <%= Time.zone.now %> この段階でテストを行うと、上記で作成したユーザーがいるのに演習2の変更によってそのユーザーが表示されなくなりusers_index_test.rbでエラーが発生。 users_index_test.rb first_page_of_users = User.where(activated: true).paginate(page: 1) 上記のように変更してアクティブ化が行われているユーザーのみが表示されるように変更する。 これによってエラーは解消するが、非アクティブのユーザーの表示が無いことを確認するテストも追加する。 users_index_test.rb def setup @admin = users(:michael) @non_admin = users(:archer) @non_actuvated_user = users(:saitou) end users_index_test.rb test "index as admin including pagination and delete links" do log_in_as(@admin) get users_path assert_template 'users/index' assert_select 'div.pagination' first_page_of_users = User.where(activated: true).paginate(page: 1) first_page_of_users.each do |user| assert_select 'a[href=?]', user_path(user), text: user.name unless user == @admin assert_select 'a[href=?]', user_path(user), text: 'delete' end end assert_select 'a[href=?]',user_path(@non_actuvated_user), count:0 assert_difference 'User.count', -1 do delete user_path(@non_admin) end end assert_select 'a[href=?]',user_path(@non_actuvated_user), count:0によってアクティブ化されていないユーザーの投稿が見えていないことが確認できた。 次に、showアクションの非アクティブのユーザーページに飛ぼうとした際に、ホームに戻るテストを書く。 users_index_test.rb test "zVisit the page of an inactive user" do get user_path(@non_actuvated_user) assert_redirected_to root_url end これでテストが成功すれば演習は終了。 本番環境でのメール送信(11.4) ここまでの実装で、development環境におけるアカウント有効化の流れは完成した。 次は、サンプルアプリケーションの設定を変更し、production環境で実際にメールを送信できるようにする。 具体的には、まずは無料のサービスを利用してメール送信の設定を行い、続いてアプリケーションの設定とデプロイを行う。 本番環境からメール送信するために、「Mailgun」というHerokuアドオンを利用してアカウントを検証する。 アプリケーションでMailgunアドオンを使うには、production環境のSMTPに情報を記入する必要がある。 以下の<あなたのHerokuサブドメイン名>を自分のHerokuのURLに設定する。 config/environments/production.rb Rails.application.configure do . . . config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp host = '<あなたのHerokuサブドメイン名>.herokuapp.com' config.action_mailer.default_url_options = { host: host } ActionMailer::Base.smtp_settings = { :port => ENV['MAILGUN_SMTP_PORT'], :address => ENV['MAILGUN_SMTP_SERVER'], :user_name => ENV['MAILGUN_SMTP_LOGIN'], :password => ENV['MAILGUN_SMTP_PASSWORD'], :domain => host, :authentication => :plain, } . . . end そしてブランチをmasterにマージ。 $ rails test $ git add -A $ git commit -m "Add account activation" $ git checkout master $ git merge account-activation 続いてリモートにプッシュしてHerokuにデプロイ。 $ rails test $ git push $ git push heroku $ heroku run rails db:migrate MailgunのHerokuアドオンを追加するために、次のコマンドを実行。 $ heroku addons:create mailgun:starter 最後に、受信メールの認証を行う。 以下のコマンドを打つとMailgun ダッシュボードのURLが表示されるためブラウザで開く。 $ heroku addons:open mailgun 設定が完了させて本番環境で設定したメールでユーザー登録を行うと実際にメールが届く。 そしてメールのリンクから実際に有効化が成功した。 これでこの章は終了。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザーを検索する機能を実装するの巻 その2

この記事について 前回の記事 https://qiita.com/Shinsama12123/items/fca4427a3611b5711bb0 ユーザーを検索する機能を実装することができたので、次にテストの記事をあげるみたいなことを書いていました。 早速...。 テスト spec/integration/user_index_spec.rb require 'rails_helper' RSpec.describe "Search",type: :integration do #ユーザー検索 it("users search") do log_in_as(@admin) #全てのユーザー get(users_path, :params => ({ :q => ({ :name_cont => "" }) })) User.paginate(:page => 1).each do |user| assert_select("a[href=?]", user_path(user), :text => user.name) end assert_select("title", "ユーザー検索?パパっ子") #ユーザーの検索結果 get(user_path, :params => ({ :q => ({ :name_cont => "a" }) })) q = User.ransack(:name_cont => "a", :activated_true => true) q.result.paginate(:page => 1).each do |user| assert_select("a[href=?]", user_path(user), :text => user.name) end assert_select("title", "検索結果?パパっ子") #見つからなかった場合 get(users_path, :params => ({ :q => ({ :name_cont => "abcdefghijk" }) })) expect(response.body).to(match("ユーザーは見つかりませんでした。")) #'全てのユザー'に戻っていることの確認 get(users_path, :params => ({ :q => ({ :name_cont => "" }) })) assert_select("title", "ユーザー検索?パパっ子") end end テストについて 1.全ユーザーの表示  2.検索結果(あり) 3.検索結果(なし) 1、2についてはタイトルのチェック、3についてはメッセージ確認を行うことにします。 テストが通ったことを確認して終了です。 実は、私はテストはまだ通ってはいません。log_in_as(@admin)の部分にエラーがあるようで、なので実際にテストが通ったら、また編集します。すみません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Formオブジェクトではrailsで仕組まれた「save」設定ではない

バリデーションは、データベースを永続化するうえで極めて重要です。そのため、save、updateメソッドは、バリデーションに失敗するとfalseを返します。このとき実際のデータベース操作は行われません。 →そのように仕組まれているということです。 Railsガイド https://railsguides.jp/active_record_basics.html#%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%EF%BC%88%E6%A4%9C%E8%A8%BC%EF%BC%89 Formオブジェクトでは 基本自作したフォームオブジェクトではsaveやupdateの際に上記で書いた付加されたrails独自の設定は全くない(=バリデーションに失敗するとfalseを返したりはしない)ので基本、下に示した上部と下部は無関係なので関連性なくプログラムが通リます。 address_purchase.rb(上部) with_options presence: true do validates :address validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/, message: "は登録できません。ハイフン-(半角)を含みます。"} validates :town, format: { with: /\A[ぁ-んァ-ン一-龥々]/, message: "は全角ひらがな、全角カタカナ、漢字で入力して下さい" } validates :phone_number, format: { with: /\A\d{10,11}\z/, message: "は半角数値で入力して下さい" } validates :prefecture_id, numericality: { other_than: 1} end address_purchase.rb(下部) def save purchase = Purchase.create(user_id: user_id, item_id: item_id) Address.create(postal_code: postal_code, prefecture_id: prefecture_id, town: town, address: address, building: building, phone_number: phone_number, purchase_id: purchase.id) end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Formオブジェクトでは通常付加された「save」設定はない

バリデーションは、データベースを永続化するうえで極めて重要です。そのため、save、updateメソッドは、バリデーションに失敗するとfalseを返します。このとき実際のデータベース操作は行われません。 →そのように仕組まれているということです。 Railsガイド https://railsguides.jp/active_record_basics.html#%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%EF%BC%88%E6%A4%9C%E8%A8%BC%EF%BC%89 Formオブジェクトでは 基本自作したフォームオブジェクトではsaveやupdateの際に上記で書いた付加されたrails独自の設定は全くない(=バリデーションに失敗するとfalseを返したりはしない)ので基本、下に示した上部と下部は無関係なので関連性なくプログラムが通リます。 address_purchase.rb(上部) with_options presence: true do validates :address validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/, message: "は登録できません。ハイフン-(半角)を含みます。"} validates :town, format: { with: /\A[ぁ-んァ-ン一-龥々]/, message: "は全角ひらがな、全角カタカナ、漢字で入力して下さい" } validates :phone_number, format: { with: /\A\d{10,11}\z/, message: "は半角数値で入力して下さい" } validates :prefecture_id, numericality: { other_than: 1} end address_purchase.rb(下部) def save purchase = Purchase.create(user_id: user_id, item_id: item_id) Address.create(postal_code: postal_code, prefecture_id: prefecture_id, town: town, address: address, building: building, phone_number: phone_number, purchase_id: purchase.id) end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

modelではdeviseで利用できるcurrent_userは使えない

modelではcurrent_userは使えない teratail https://teratail.com/questions/272877 変数やmethodはそれが参照可能な範囲があります。 「変数やmethodはそれが参照可能な範囲があります。current_user はmethodです。paramsもHashの様な顔をしてますが、振る舞いをみるとmethodっぽいです。methodは 単独で使う場合は、それが定義されているインスタンスでないと参照できません。これらはcontrollerのインスタンスのものなので、modelでは参照できません。 必要なときは、modelのmethodを呼ぶときに引数で渡す必要があります。」 なのでコントローラーで使う だからmodelにおいたaddress_purchase.rbで以下のようにコーディングしました。 app/models/address_purchase.rb def item_params params.require(:item).permit(:item_name, :description, :category_id, :status_id, :delivery_fee_payment_id, :prefecture_id, :delivery_prepare_id, :price, :image).merge(user_id: current_user.id) end モデル内ではうまく動きませんでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactとRailsのアプリを一つのEC2にデプロイする。React編

今回は、ReactとRailsを使ってSPAを作成した際に一つのEC2インスタンスにデプロイする方法を忘備録としてまた、同じ事象で悩んでいる方のために残しておきます。 フロント側をAmplifyやnetlifyでデプロイしようと考えていましたが、RailsのAPI側をhttps化しなければリクエストを受け付けてくれなかったので、EC2に両方ぶち込んじゃえという手法になります。 概要 前提として、今回はフロント側について記述するため、RailsAPI側の方法は後日記述します。 EC2のインバウンドルールを編集しましょう。 まずは、EC2のセキュリティグールプからインバウンドを画像のように編集しましょう。 簡単な説明として、API側のポートとして3000番を設定しています。 また、IPV4とIPV6すべてのリクエストに対応するため、::/0と0.0.0.0/0は2つずつ設定しています。 EC2インスタンスにsshで接続し、ディレクトリを作成しましょう。 私の場合は、~/var/wwwというディレクトリを作成しました。 作成したディレクトリにReactアプリをgit cloneしましょう。 先程作成したディレクトリにgit cloneをしましょう。 EC2インスタンスにgit cloneをする場合は、作成したEC2インスタンスのssh公開鍵をGitHubに登録する必要があります。 EC2サーバーのssh鍵のペアを作成し、GitHubにssh鍵を登録しましょう。 [ec2-user@ip-192-32-33-129 ~]$ ssh-keygen -t rsa -b 4096 Generating public/private rsa key pair. Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/ec2-user/.ssh/id_rsa. Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub. The key fingerprint is: 3a:8c:1d:d1:a9:22:c7:6e:6b:43:22:31:0f:ca:63:fa ec2-user@ip-172-31-23-189 The key's randomart image is: +--[ RSA 4096]----+ | + | | . . = | | = . o . | | * o . o | |= * S | |.* + . | | * + | | .E+ . | | .o | +-----------------+ ※Enterを押せば画像のような表記になります。 ssh公開鍵の値をGitHubに登録しましょう。 catコマンドで、公開鍵が含まれているファイルid_rsa.pubの中身をターミナル上に表示します。 [ec2-user@ip-192-32-33-129 ~]$ cat ~/.ssh/id_rsa.pub すべて(ssh-rsaから最後の文字まで)コピーします。 ssh-rsa AAAs3NzaC1yc2EAAASADAQABavasAQDLwt...... https://github.com/settings/keys 上記のURLからkeyを追加しましょう。(タイトルはなんもでもOKです。) git cloneが出来たらyarn run buildを実行しましょう。 先程の作成したディレクトリにgit cloneが完了しましたら、cloneしたアプリに移動し、yarn run buildを実行しましょう。 実行すると、アプリ内にbuildディレクトリが作成され、その中にindex.htmlが生成されていればOKです。 ※yarn run buildを実行してもindex.htmlが生成されない場合はメモリ不足の可能性があります。 yarn run buildコマンドはメモリをかなり食うらしく、何度やっても生成されない場合は、一度EC2を停止させて、再起動し、再度実行してみましょう。 Nginxの設定ファイルを作成し編集しましょう。 Nginxの設定ファイルは/etc/nginx/conf.d/***.confに作成しましょう。(ファイル名は何でもOKです) 作成できましたら、下記のように編集しましょう。 /etc/nginx/conf.d/***.conf server { listen 80; server_name IPアドレス; charset utf-8; # ドキュメントルートを指定. root /var/www/アプリ名/build; index index.html; # リクエストされたリソースがなければ、index.htmlを返却. location / { try_files $uri /index.html; } } 上記でフロントエンド側の設定は終了です。 IPアドレスに接続し、アプリが表示されていればOKです。 ※Nginxの設定ファイルを編集した跡は必ずrestartしましょう。 sudo systemctl restart nginx ありがとうございました。 参考文献リスト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カラムの型_integer型とdecimal型について

はじめに オリジナルアプリを作成中に、カラムの型について学んだので、投稿する。 integre型について integer型は、金額や数値などを扱いたい時に使う。 やりたかったこと テーブルに、小数点が含まれた数値を保存したかった。 なので、integre型を使うのかと思い、カラムの型をinteger型で指定した。 t.decimal :energy, integer # エネルギー(kcal) t.decimal :protein, integer # タンパク質(g) t.decimal :lipid, integer # 脂質(g) t.decimal :carbohydrate, integer # 炭水化物(g) t.decimal :salt_equivalent, integer# 食塩相当量(g) しかし、テーブルにデータは保存できたものの、小数点以下の数値は保存されていなかった。 例: 152.3としたかったのに、実際は152のような形式になってしまう。 改善したこと integer型からdecimal型に変更した。 t.decimal :energy, precision: 4, scale: 1 # エネルギー(kcal) t.decimal :protein, precision: 4, scale: 1 # タンパク質(g) t.decimal :lipid, precision: 4, scale: 1 # 脂質(g) t.decimal :carbohydrate, precision: 4, scale: 1 # 炭水化物(g) t.decimal :salt_equivalent, precision: 4, scale: 1 # 食塩相当量(g) decimal型を使うことで、小数点が含まれた数値を扱うことができる。 precisionは、小数点を含めた全ての桁数のを指定する。 scaleは、小数点の桁数を指定する。 実は、integer型は整数しか扱えない性質のようだ。 最後に テーブルで小数点を扱いたい場合は、decimal型を指定して、raild db:migrateを行う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntu 20.04.1 LTS上にRuby3.0.0とRails6

Ubuntu 20.04.1 LTS Ruby 3.0.0 Rails 6.1.0 現在の環境にインストールします sudo apt update -y sudo apt upgrade -y 開発に必須のパッケージとRuby on Railsで必要なパッケージをインストール sudo apt-get -y install git curl g++ make libyaml-dev libxml2-dev libxslt-dev sqlite3 libsqlite3-dev nodejs npm sudo apt install build-essential -y sudo apt install -y libssl-dev libreadline-dev zlib1g-dev git clone https://github.com/sstephenson/rbenv.git ~/.rbenv 環境変数Pathを設定します。 echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(rbenv init -)"' >> ~/.bashrc シェルを再起動します。 exec $SHELL -l ruby-buildをインストールします。 git clone https://github.com/sstephenson/ruby-build.git~/.rbenv/plugins/ruby-build Rubyをインストール rbenv install 3.0.0 rbenv global 3.0.0 yarnのインストール sudo apt install curl Railsのインストール gem install -v 6.1.0 rails
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseを使った複数モデルログイン実現後の他のモデルの扱い

記事の目的 railsのgemであるdeviseはユーザー登録機能を実現するために使用されている最もポピュラーなライブラリの一つかと思いますが、 アプリケーションを作成する上で複数モデルログインをしたいと考える場合もあるかと思います。 (よく見るのは管理者と一般ユーザーを切り分けるタイプのものですかね。) 今回はdeviseを活用した複数モデルログインを実現した後、他のモデルをどのように紐付けていくのかをまとめました。 本論 複数モデルログイン この記事は複数モデルログインの機能自体は実現した前提での内容となります。 複数モデルログイン機能の構築には、勝手に引用してしまい恐縮ですが、良記事がございますので下記ご参考いただければ。 http://freecamp.life/rails-devise-admin1/ 筆者のケース 私は現在日本にいる外国人が日本でチューターを探すためのマッチングアプリのようなものを作っています。 そのため「外国人」と「チューター」としての会員登録、ログイン機能をつけています。 なのでコントローラーの構成は app |- controller |- foreigner #(外国人) |- tutor #(チューター) となっており、ルーティングは devise_for :foreigners, skip: :all devise_scope :foreigner do get 'foreigners/sign_in' => 'foreigners/sessions#new', as: 'new_foreigner_session' post 'foreigners/sign_in' => 'foreigners/sessions#create', as: 'foreigner_session' delete 'foreigners/sign_out' => 'foreigners/sessions#destroy', as: 'destroy_foreigner_session' get 'foreigners/sign_up' => 'foreigners/registrations#new', as: 'new_foreigner_registration' post 'foreigners' => 'foreigners/registrations#create', as: 'foreigner_registration' get 'foreigners/password/new' => 'foreigners/passwords#new', as: 'new_foreigner_password' end devise_for :tutors, skip: :all devise_scope :tutor do get 'tutors/sign_in' => 'tutors/sessions#new', as: 'new_tutor_session' post 'tutors/sign_in' => 'tutors/sessions#create', as: 'tutor_session' delete 'tutors/sign_out' => 'tutors/sessions#destroy', as: 'destroy_tutor_session' get 'tutors/sign_up' => 'tutors/registrations#new', as: 'new_tutor_registration' post 'tutors' => 'tutors/registrations#create', as: 'tutor_registration' get 'tutors/password/new' => 'tutors/passwords#new', as: 'new_tutor_password' end となっております。 別のモデルの追加 ここからは別のモデルを追加する方法について説明していきます。 今回は、外国人登録したユーザーが、チューターとのマッチング希望を出すためのモデルを作成していきます。 モデル作成 ここではneedモデルというものを作成します。 モデルの作成は特段代わりはなく、 rails g model need でOKです。 その後のマイグレーションファイルやモデルファイルのバリデーション/アソシエーション等の設定も普通通り行っていきます。 コントローラー作成 さてここからが本題です。 今回はforeignerに紐づくモデルだったので、コントローラーはforeignerコントローラーフォルダの中に作成していきます。 下記の通りコマンドを打ち込んでください。 rails g controller foreigners/needs するとapp/controller/foreignersの下にneeds_controller.rbが作成されるかと思います。 ファイルの中身はこんな感じで作成されるはずです。Foreignersの中のNeedsControllerという内容になっていますね。 class Foreigners::NeedsController < ApplicationController (略) end ルーティング 続いてルーティングです。 ルーティングに関してはforeignerの名前空間の中で設定していきたいので、 (略) namespace :foreigners do resources :needs end を追記します。 こうすることでdeviseを活用した複数モデルログイン機能と、それに紐づいたモデルの設定が完了です。 あとはアクションなりviewの設定をよしなにやっていけばOKです! ※viewもapp/views/foreignersの下にneedsディレクトリが生成されているので、そこにファイルを作成していきましょう。 ご指摘等ございましたらコメント頂けると幸いです!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hotwire(Turbo)を試す その1: 導入、作成・更新フォーム

環境: Rails 6.1、Turbo 7.0.0-beta.5 Hotwire(Turbo)とは Hotwireは、Railsの作者であるBasecampのDHHが昨年発表したものです。「モダンなウェブアプリケーションを、たくさんのJavaScriptなしで、JSONの代わりにHTMLを送って作るもの」だそうです。 Hotwireは次の3つからなります。 Turbo Stimulus Strada といっても、Stimulusは前からありますし、Stradaはまだできていないので、実際に試してみるのはもっぱらTurboということになります。Turboは、簡単に言えば今までのTurbolinksを発展させたものです。「Turbolinksがなんかすごくなったもの」です。 参考にしたサイト Hotwireのサイトの他に: Hotwire: Reactive Rails with no JavaScript? — Martian Chronicles, Evil Martians’ team blog : by Vladimir Dementyev 2021-04-12 How to use Hotwire in Rails (Example) | GoRails : by Chris Oliver 2020-12-23 turbo-railsのソースコード : app/ の下あたり Hotwire (Turbo)の導入 では試してみましょう。Rails 6.1でアプリケーションを作ります。 % rails new hotwire-sample % cd hotwire-sample Gemfileにhotwire-railsを加えます。 Gemfile gem 'hotwire-rails' Gemをインストールします。 % bundle install 次のコマンドを実行すると、RailsアプリケーションにTurboとStimulusが入ります。 % bin/rails hotwire:install Gemfileが自動的に修正され、Turbolinksが外されてRedisが必須になります。もう一度 bundle install を実行します。 Gemfile - gem 'turbolinks', '~> 5' + - # gem 'redis', '~> 4.0' + gem 'redis', '~> 4.0' package.jsonでは、Turbolinksの代わりにTurboが入ります。 package.json "dependencies": { + "@hotwired/turbo-rails": "^7.0.0-beta.5", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "5.2.1", - "turbolinks": "^5.2.0" + "stimulus": "^2.0.0" }, cable.ymlが修正されています。ActionCableを使うときは開発環境でRedisが必要なようです。なお、Turboを使うにはActionCableが必須というわけではありません。この「その1」のサンプルではActionCableもRedisも要りません。 config/cable.yml development: - adapter: async + adapter: redis + url: redis://localhost:6379/1 application.jsでも、Turbolinksの代わりにTurboが入り、Stimulusの初期化(import "controllers")が加わります。 app/javascript/packs/application.js import Rails from "@rails/ujs" - import Turbolinks from "turbolinks" + import "@hotwired/turbo-rails" import * as ActiveStorage from "@rails/activestorage" import "channels" Rails.start() - Turbolinks.start() ActiveStorage.start() + import "controllers" レイアウトテンプレートでdata-turbolinks-trackをdata-turbo-trackに変える必要がありますが、ここは自動的に修正されないので手動で直します。 app/views/layouts/applicaton.html.erb <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %> なお、hotwire-railsはturbo-railsとstimulus-railsをまとめたものです。Gemfileにhotwire-railsの代わりにgem 'turbo-rails' を加えて、次のコマンドを実行しても同じです。webpacker:install:stimulus を実行しなければ、StimulusなしでTurboだけ使えます。 % bundle install % bin/rails turbo:install % bin/rails webpacker:install:stimulus 作成・更新フォーム Turboの機能を調べるために、作成・更新フォームを作ってみます。scaffoldで手抜きします。 % bin/rails g scaffold entry title:string body:text % bin/rails db:migrate エラー表示を試すために、バリデーションを加えます。 app/models/entry.rb class Entry < ApplicationRecord validates :title, :body, presence: true end http://localhost:3000/entries から新規作成と編集画面を試すと、Turboのために何も手を加えなくても、そのまま動作します。 バリデーションエラーのときの挙動は、これまでのRailsとは変わります。POSTでブラウザーが画面遷移せずに、TurboのJavaScriptが画面を差し替えます。URLは /entries/new や /entries/:id/edit のままです。 注意しなければならないのは、Turboを使っているときにPOST(PATCH、DELTE)メソッドでリクエストを送ったら、リダイレクトするかHTTPステータスをエラーにする(300番台か400番台のステータスを返す)必要があることです。200を返すと画面が変わりません。 app/controllers/entries_controller.rb def create @entry = Entry.new(entry_params) if @entry.save redirect_to @entry, notice: "Entry was successfully created." else render :new, status: :unprocessable_entity # statusが必須 end end POSTのときにバグによる500エラーが出たときは、TurboはRailsのいつものエラー画面を出してくれません。これはそういうものなのか、これから変わるのか分かりません。 IE対応 TurboはIE 11で動きません。Polyfillをあれこれ入れて試してみましたが、無理そうです。 ここまでの感想 Turboは、Turbolinksよりもめっちゃ速い。 バリデーションエラーでPOSTによる画面遷移をしないのは、たいへんありがたい。このためだけでもTurboを使いたい。 今年はいよいよIEを完全に切る年になるか。 続く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]kaminariでページネーション機能を作る

はじめに 最近、ページネーション機能を実装したので、本記事に簡単にまとめます。 ページネーション機能 ページネーションとは、内容の多いページを複数のWebページに分割し、各ページへのリンクを並べてアクセスしやすくするために設置するものです。よく比較されるものとして、無限スクロールがありますが、どちらを実装するかは各々のメリットデメリットをよく理解してから実装するのが良いかと思われます。場合によっては、実装することがマイナスとなりうるためです。(例えば、チャット画面でページネーション 機能があると切り替えが面倒ですよね) 実装 実装方法は簡単で、kaminariというgemを使用します。 gem `kaminari` ※bundle install忘れないように!!! 次にコントローラを実装します。 controller.rb class HomeController < ApplicationController def index @posts = Post.all @posts = Post.page(params[:page]).per(20) end end per()については、何投稿ごとにページを切り替えるか指定しています。 あとは、ビューに以下を設置します。   index.html.erb <%= paginate @posts %> 極論、これで完成です。めちゃくちゃ簡単ですね。 とはいえ、細かい設定したいですよね。 そうした場合は以下のコマンドを叩いてください。 rails g kaminari:config そうすると、以下のような設定ファイルが生成されます。 コメントアウトを外して、変えたい部分を変えてください Kaminari.configure do |config| # config.default_per_page = 25 # 1ページ辺りの項目数 # config.max_per_page = nil # 1ページ辺りの最大数 # config.window = 4 # ex 値が2の場合 .. 2 3 (4) 5 6 .. # config.outer_window = 0 # ex 値が2の場合 .. (4) .. 99 100 # config.left = 0 # ...になったときの左側の表示数 # config.right = 0 # ...になったときの右側の表示数 # config.page_method_name = :page # メソッド名 # config.param_name = :page # ページネーションのパラメーターの名前 end あと、個人的に次へボタンを消したいなと思ったのですが、これはビューファイルごと消してやるといいかと思います。 その場合は、以下のコマンドを叩いてビューファイルを生成します。 rails g kaminari:views default なお、bootstrapをkaminariに適用する場合は以下のようにします rails g kaminari:views bootstrap3 あとは、個人でCSSを当ててもらえれば、それらしきものができるかと思います。 おわりに ざっくりとkaminariのまとめをしました。個人的には簡単で嬉しいものの、やっぱり作った感がなくて、、、、なので一から自分で作ってみようかな〜なんて考えています。 みなさんも是非試してみてはいかがでしょうか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PAY.JPを利用した際のtokenとSECRET_KEYとPUBLIC_KEYについて

Token PAYJP側で発行しています。 createTokenメソッドが通った時に発行してもらってます。 カード番号やCVCをクレジット決済代行サービスに渡すさずに、別に登録保存されているカード情報を呼び出す鍵として発行されています。 card.js Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token' type="hidden"> `; renderDom.insertAdjacentHTML("beforeend", tokenObj); } SECRET_KEYとPUBLICキー これは購入サイトがクレジット代行サービスにデータ提供する際に必要なログイン情報「身分証明書」となっています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Formオブジェクト作成手順(備忘録)

手順1.新たにmodelsディレクトリ直下にファイルを作成し、クラスを定義する 手順2.作成したクラスにform_withメソッドに対応する機能とバリデーションを行う機能を持たせる 手順3.保存したい複数のテーブルのカラム名全てを属性値として扱えるようにする 手順4.バリデーションの処理を書く 手順5.データをテーブルに保存する処理を書く 手順6.コントローラーのnewアクション、createアクションでFormオブジェクトのインスタンスを生成するようにする 手順7.フォーム作成の部分をFormオブジェクトのインスタンスを引数として渡す形に変更する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

binding.pryの使い方

binding.pry自分の知らない新しい使い方 13: def save => 14: binding.pry 15: purchase = Purchase.create(user_id: user_id, item_id: item_id) 16: Address.create(postal_code: postal_code, prefecture_id: prefecture_id, town: town, address: address, building: building, phone_number: phone_number, purchase_id: purchase.id) 17: end binding.pryで止まったところでその先の行を丸ごと入れます 15行目 [1] pry(#)> purchase = Purchase.create(user_id: user_id, item_id: item_id) terminal (19.1ms) BEGIN ↳ (pry):3:in save' User Load (0.7ms) SELECTusers.* FROMusersWHEREusers.id= 2 LIMIT 1 ↳ (pry):3:insave' (0.5ms) ROLLBACK ROLLBACKでその行が差し戻されているのでうまくいかないということが行ごとで明らかになります。 便利〜〜〜
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ストロングパラメーターとViewのform_withに関係に着目

現在、FormObjectに取り組んでいます。 自分がエラーから以下のことがわかりました。 環境 ①addressテーブル ③(FormObject)address_purchase.rb ④(View)purchasesフォルダindex.html.erb ②purchaseテーブル 上記 上記 ④のViewのフォームから来たデータを①テーブルと②版テーブルに分けて保存することを目指しています。 問題:1つだけ変数を受け取れない場合があります。 1:form_with model:@インスタンス変数 2:form_with url ✖️✖️_path 3:form_with model:@インスタンス変数, url ✖️✖️_path 1番が受け取れない場合があります。 address_purchase.rb private def address_params params.require(:address_purchase).permit(:postal_code, :prefecture_id, :town, :address, :building, :phone_number).merge(user_id: current_user.id, item_id: params[:item_id], purchase_id:params[:id]) end 上に対応するviewの書き方が以下 app/views/purchases/index.html.erb <%= form_with model: @address_purchase,url: item_purchases_path, id: 'charge-form', class: 'transaction-form-wrap',local: true do |f| %> A:require(:adress_purchase)部分が model: @address_purchase部分と対応してます address_purchase.rb private def address_params params.permit(:postal_code, :prefecture_id, :town, :address, :building, :phone_number).merge(user_id: current_user.id, item_id: params[:item_id], purchase_id:params[:id]) end 上に対応するviewの書き方が以下 app/views/purchases/index.html.erb <%= form_with url: item_purchases_path, id: 'charge-form', class: 'transaction-form-wrap',local: true do |f| %> B:permit( )部分が model: url: item_purchases_path部分と対応してます address_purchase.rb private def address_params params.require(:address_purchase).permit(:postal_code, :prefecture_id, :town, :address, :building, :phone_number).merge(user_id: current_user.id, item_id: params[:item_id], purchase_id:params[:id]) end 上に対応するviewの書き方が以下 app/views/purchases/index.html.erb <%= form_with url: item_purchases_path, id: 'charge-form', class: 'transaction-form-wrap',local: true do |f| %> C:require(:address_purchase)が model: @address_purchase部分がないためにエラーが出ます。 Cの場合のエラーの確認方法 エラーメッセージ画像 取れるパワーメータも{}1重カッコのハッシュ。 {"authenticity_token"=>"rSIr68JcqA8eDoKKyZJKqw6boN9tEiBhVKCu2k1vhvW08xn+gVor7jmy+pCWqWkTXLydOrrP/MU0IpDpwJG18Q==", "card_number"=>"4242424242424242", "month"=>"4", "year"=>"35", "cvc"=>"123", "postal_code"=>"123-4567", "prefecture_id"=>"3", "town"=>"あわわわ", "address"=>"あわ1", "building"=>"", "phone_number"=>"09077778888", "commit"=>"購入", "item_id"=>"24"} 違う形に直すと 取れるパワーメータも{ }2重カッコのハッシュ。これが正解 Parameters: {"authenticity_token"=>"UjD+CkBD6QzxFEd1vJgxgQFKE0ifhoSE0GUnUrdwMSFL4cwfA0Vq7daoP2/joxI5U20urUhbWCCw5xlhOo4CJQ==", "address_purchase"=>{"card_number"=>"4242424242424242", "month"=>"3", "year"=>"34", "cvc"=>"123", "postal_code"=>"123-4567", "prefecture_id"=>"2", "town"=>"あああ", "address"=>"あああ", "building"=>"", "phone_number"=>"09011112222"}, "commit"=>"購入", "item_id"=>"24"}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withでのmodelとurlの指定の違いはSr?

はじめに form_withを使用中に気になったので調べてまとめてみました。 目次 urlの指定 modelの指定 modelとurlの指定の違い まとめ 参考 urlの指定 urlの書き方 form_with url: url_path 例) <%= f.text_field :emai%> <%= f.submit %> urlでは指定したurlに対してPOSTメソッドを行う。 :scope scope指定すると「指定すると現在のURLに対応したメソッドにフォームを送信」してくれる つまり、loinフォームを作成していてsessionControllerを使っている場合はsessionControllerのnewメソッドに送信してくれることになる ストロングパラメーターの設定 params.permit(:email) urlの場合は構造階層を意識することなくストロングパラメーターを設定できる。 modeleの指定 modelの書き方 form_with model: @model 新しく作られたオブジェクトの場合 例) user = User.new controller間の処理の流れ new => create この場合は渡されたオブジェクトの値が空の場合Rallsが自動でcreateメソッドを呼び出してくれる。 ストロングパラメーターの値指定 urlを指定して値を渡した場合は直接値を指定しできたが moelの場合は構造階層になるので指定方法が変わる 「Userモデルのemailを指定する」といった形に切り替える params.requitre(:user).permit(:email) となり、params[:user][:email]という構造階層での指定が必要になる。 modelとurlの指定の違い modelは中身が入っていると自動でeditメソッドを呼び出す ストロングパラメーターの指定方法が違う まとめ 個人的両方の使いどころ ログインフォームはurl指定 登録フォームはmodel指定 参考 【Rails】form_with/form_forについて【入門】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withでのmodelとurlの指定の違いは?

はじめに form_withを使用中に気になったので調べてまとめてみました。 目次 urlの指定 modelの指定 modelとurlの指定の違い まとめ 参考 urlの指定 urlの書き方 form_with url: url_path 例) <%= f.text_field :emai%> <%= f.submit %> urlでは指定したurlに対してPOSTメソッドを行う。 :scope scope指定すると「指定すると現在のURLに対応したメソッドにフォームを送信」してくれる つまり、loinフォームを作成していてsessionControllerを使っている場合はsessionControllerのnewメソッドに送信してくれることになる ストロングパラメーターの設定 params.permit(:email) urlの場合は構造階層を意識することなくストロングパラメーターを設定できる。 modeleの指定 modelの書き方 form_with model: @model 新しく作られたオブジェクトの場合 例) user = User.new controller間の処理の流れ new => create この場合は渡されたオブジェクトの値が空の場合Rallsが自動でcreateメソッドを呼び出してくれる。 ストロングパラメーターの値指定 urlを指定して値を渡した場合は直接値を指定しできたが moelの場合は構造階層になるので指定方法が変わる 「Userモデルのemailを指定する」といった形に切り替える params.requitre(:user).permit(:email) となり、params[:user][:email]という構造階層での指定が必要になる。 modelとurlの指定の違い modelは中身が入っていると自動でeditメソッドを呼び出す ストロングパラメーターの指定方法が違う まとめ 個人的両方の使いどころ ログインフォームはurl指定 登録フォームはmodel指定 参考 【Rails】form_with/form_forについて【入門】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

is out of range for ActiveModel::Type::Integer with limit 4 bytesエラー(ActiveRecordのint型項目の最大値超過エラー)

ActiveRecordでinteger型の項目をマイグレして、int型の最大値以上の数を入れて保存しようとすると、モデル側で以下のエラーが出ます。 09077778888で電話番号に設定した数字が大きすぎるって怒られてます。 電話番号(ハイフンなし10桁) /\A\d{10}\z/ 携帯番号(ハイフンなし11桁) /\A\d{11}\z/ 携帯番号(ハイフンなし10桁or11桁) /\A\d{10,11}\z/ さらにデータカラムの設定ミス 20210424083812_create_addresses.rb class CreateAddresses < ActiveRecord::Migration[6.0] def change create_table :addresses do |t| t.string :postal_code ,null:false t.integer :prefecture_id ,null:false t.string :town ,null:false t.string :address ,null:false t.string :building t.integer :phone_number ,null:false t.references :purchase ,null:false, foreign_key: true t.timestamps end end end 電話番号の欄がintegerになっていたので入りませんでした。 バリデーションの設定が数値設定になっていたので直したのですが、マイグレーションファイルも数値型になっていたのでどうしても保存か不可能でした。ファイグレーションファイルをロールバックして修正してのち再度、string型に変更して直りました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Viewをリファクタリングする方法

はじめに 今回はviewをリファクタリングする方法について学習したので、まとめていこうと思います。 具体的にはパーシャルテンプレート(共通化)についてとカスタムヘルパー についてです。 少しでも参考になれば幸いです。 本記事で知れること ☑️viewのリファクタリング方法 ☑️パーシャルテンプレートとは&方法 ☑️カスタムヘルパーとは&作成方法 ※初心者向けな内容になっておりますので、既に意味・作成方法を知っている方であればあまり有益な記事ではないかもしれません。 ご了承ください。 Viewをリファクタリングするには 主に以下二つの方法があります。 ①パーシャルテンプレートを作成し、共通化する ②カスタムヘルパーを作成する 他にも方法はあると思いますが、今回はこの二つの方法について、解説していきます。 ①パーシャルテンプレート パーシャルテンプレートとは? パーシャルを使用すると、レスポンスで表示するページの特定部分をレンダリングするためのコードを別ファイルに保存しておくことができます。 railsガイド 表示するページの特定部分を別ファイルに保存し、呼び出すことができるものになります。 メリット ・ファイルを分割することでコードの可読性が上がる。 ・重複しているコードを共通ファイルとして作成し、利用することができる 使い方 手順はテンプレートファイルの作成し、呼び出すだけなのでそこまで難しくはないと思います。 ①テンプレートファイルの作成 ファイル名の冒頭は[ _ ]アンダースコアから作成する必要がある。 (例)_user.html.erb 作成したら、そのファイルに共通化したい記述を移動させます。 今回はユーザーのプロフィールを移動させました。 [_user_prof.html.erb] <%if @user.image_icon? %> <img src="<%= @user.image_icon.url %>" class="img-icon" alt="ユーザーアイコン"> <% else %> <%= image_tag '/assets/default.jpeg' ,class:"img-icon", :alt => 'ユーザーアイコン' %> <% end %> <div class="user-text"> <h5 class="user-name"><%= @user.name %></h5> <% if @current_user== @user%> <%= link_to("編集", edit_user_path(@user),class:"user-edit-btn btn btn-primary") %> <% end %> </div> ②テンプレートファイルを呼び出す <%= render 'user_prof'%> これで呼び出すことができます。 render 'ファイル名'とすることで、パーシャルしたファイルの内容をレンダリングして表示することができます。 呼び出すときは_不要。 [補足] ファイル名を指定する ※render 'ファイル名'と挙動は一緒? render partial:'ファイル名' `render partial:'ファイル名' レンダリングする内容のファイルを指定する layout: 'ファイル名' `render partial:'ファイル名',layout: 'ファイル名' ローカル変数を渡す locals: {変数名: 値} `render partial:'ファイル名',layout: 'ファイル名',locals: {変数名: 値} 注意点 作成するディレクトリの場所が異なる場合 今回はuserディレクトリの中でファイルを作成したが、異なるディレクトリにファイルを作成した場合、render 'ディレクトリ名/user_prof'のようにディレクトリ も指定する必要がある。 ②カスタムヘルパー カスタムヘルパー とは? 複数のビューで共通化したい処理をメソッドとして、作成することのできるもの。 言葉では少しわかりづらいので、早速使い方の説明に移ります。 メリット ・処理をひとまとめにできる ・コードの可読性が上がる ・本来はビューで使用するものだが、コントローラやモデルでも使用することができる 使い方 使い方としては、「カスタムヘルパーを定義」して「ビューで呼び出す」ような手順になります。 カスタムヘルパーはapp/helperディレクトリに作成します。rails g controllerで既に自動で作成されています。 (例) このような記述があったとして、これをカスタムヘルパーを使用してリファクタリング してみます。 <% if @user.goal.blank? %> <h3 class="g-text">目標はまだ決めていません!</h3> <% else %> <h2 class="g-text"><%= @user.goal %></h2> <% end %> ①カスタムヘルパーを作成 今回は、ユーザーに関するカスタムヘルパーなのでapp/helpers/user_helper.rbに作成します。 module UserHelper def goal_text(user) if @user.goal.blank? "目標はまだ決めていません!" else @user.goal end end end ②カスタムヘルパーを呼び出す <h3 class="g-text"><%= goal_text(@user.goal) %></h3> このように、ビューでは一行で表示することができ、リファクタリングすることができました。 注意点 ・引数にはインスタンス変数ではなく、ローカル変数を使用する ・helperで定義したメソッドはどのビューでも使用できてしまうため、思わぬ挙動になる可能性がある。 ・命名するときは、具体的な名前を使用し、バッティングを避ける。 ・一つのカスタムヘルパーが行う処理は、細かく設定すること。 ・複数の処理をまとめてしまうと、複雑なコードになってしまうため、いくつかのヘルパーを組み合わせて使用することがベスト。 まとめ 簡単ではありましたが、ビューのリファクタリング方法についてまとめてみました。 パーシャルやカスタムヘルパーを使用することでかなりリファクタリングできるのではないかと思います! 内容に誤りがありましたら、ご指摘お願いいたします。 最後まで読んでいただきありがとうございました! 参考記事 参考になりました。ありがとうございます! Railsガイド ヘルパーメソッドを作ろう カスタムヘルパーってなんぞ?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Viewをリファクタリングする方法(パーシャル、カスタムヘルパー )

はじめに 今回はviewをリファクタリングする方法について学習したので、まとめていこうと思います。 具体的にはパーシャルテンプレート(共通化)についてとカスタムヘルパー についてです。 少しでも参考になれば幸いです。 本記事で知れること ☑️viewのリファクタリング方法 ☑️パーシャルテンプレートとは&方法 ☑️カスタムヘルパーとは&作成方法 ※初心者向けな内容になっておりますので、既に意味・作成方法を知っている方であればあまり有益な記事ではないかもしれません。 ご了承ください。 Viewをリファクタリングするには 主に以下二つの方法があります。 ①パーシャルテンプレートを作成し、共通化する ②カスタムヘルパーを作成する 他にも方法はあると思いますが、今回はこの二つの方法について、解説していきます。 ①パーシャルテンプレート パーシャルテンプレートとは? パーシャルを使用すると、レスポンスで表示するページの特定部分をレンダリングするためのコードを別ファイルに保存しておくことができます。 railsガイド 表示するページの特定部分を別ファイルに保存し、呼び出すことができるものになります。 メリット ・ファイルを分割することでコードの可読性が上がる。 ・重複しているコードを共通ファイルとして作成し、利用することができる 使い方 手順はテンプレートファイルの作成し、呼び出すだけなのでそこまで難しくはないと思います。 ①テンプレートファイルの作成 ファイル名の冒頭は[ _ ]アンダースコアから作成する必要がある。 (例)_user.html.erb 作成したら、そのファイルに共通化したい記述を移動させます。 今回はユーザーのプロフィールを移動させました。 [_user_prof.html.erb] <%if @user.image_icon? %> <img src="<%= @user.image_icon.url %>" class="img-icon" alt="ユーザーアイコン"> <% else %> <%= image_tag '/assets/default.jpeg' ,class:"img-icon", :alt => 'ユーザーアイコン' %> <% end %> <div class="user-text"> <h5 class="user-name"><%= @user.name %></h5> <% if @current_user== @user%> <%= link_to("編集", edit_user_path(@user),class:"user-edit-btn btn btn-primary") %> <% end %> </div> ②テンプレートファイルを呼び出す <%= render 'user_prof'%> これで呼び出すことができます。 render 'ファイル名'とすることで、パーシャルしたファイルの内容をレンダリングして表示することができます。 呼び出すときは_不要。 [補足] ファイル名を指定する ※render 'ファイル名'と挙動は一緒? render partial:'ファイル名' `render partial:'ファイル名' レンダリングする内容のファイルを指定する layout: 'ファイル名' `render partial:'ファイル名',layout: 'ファイル名' ローカル変数を渡す locals: {変数名: 値} `render partial:'ファイル名',layout: 'ファイル名',locals: {変数名: 値} 注意点 作成するディレクトリの場所が異なる場合 今回はuserディレクトリの中でファイルを作成したが、異なるディレクトリにファイルを作成した場合、render 'ディレクトリ名/user_prof'のようにディレクトリ も指定する必要がある。 ②カスタムヘルパー カスタムヘルパー とは? 複数のビューで共通化したい処理をメソッドとして、作成することのできるもの。 言葉では少しわかりづらいので、早速使い方の説明に移ります。 メリット ・処理をひとまとめにできる ・コードの可読性が上がる ・本来はビューで使用するものだが、コントローラやモデルでも使用することができる 使い方 使い方としては、「カスタムヘルパーを定義」して「ビューで呼び出す」ような手順になります。 カスタムヘルパーはapp/helperディレクトリに作成します。rails g controllerで既に自動で作成されています。 (例) このような記述があったとして、これをカスタムヘルパーを使用してリファクタリング してみます。 <% if @user.goal.blank? %> <h3 class="g-text">目標はまだ決めていません!</h3> <% else %> <h2 class="g-text"><%= @user.goal %></h2> <% end %> ①カスタムヘルパーを作成 今回は、ユーザーに関するカスタムヘルパーなのでapp/helpers/user_helper.rbに作成します。 module UserHelper def goal_text(user) if @user.goal.blank? "目標はまだ決めていません!" else @user.goal end end end ②カスタムヘルパーを呼び出す <h3 class="g-text"><%= goal_text(@user.goal) %></h3> このように、ビューでは一行で表示することができ、リファクタリングすることができました。 注意点 ・引数にはインスタンス変数ではなく、ローカル変数を使用する ・helperで定義したメソッドはどのビューでも使用できてしまうため、思わぬ挙動になる可能性がある。 ・命名するときは、具体的な名前を使用し、バッティングを避ける。 ・一つのカスタムヘルパーが行う処理は、細かく設定すること。 ・複数の処理をまとめてしまうと、複雑なコードになってしまうため、いくつかのヘルパーを組み合わせて使用することがベスト。 まとめ 簡単ではありましたが、ビューのリファクタリング方法についてまとめてみました。 パーシャルやカスタムヘルパーを使用することでかなりリファクタリングできるのではないかと思います! 内容に誤りがありましたら、ご指摘お願いいたします。 最後まで読んでいただきありがとうございました! 参考記事 参考になりました。ありがとうございます! Railsガイド ヘルパーメソッドを作ろう カスタムヘルパーってなんぞ?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのアップグレード方法

概要 あるプロジェクトで5.2.3 → 5.2.5のアップグレードをする必要あり メジャーバージョンは変わらずだったので今回は大きな問題なく終えたが手順をまとめておく 手順 基本的にはRails アップグレードガイド - Railsガイドの通り作業をすれば良い 1. Gemのアップデート GemfileのRailsバージョンを5.2.5に修正後、以下のコマンドを実行 bundle update 失敗した場合は、該当Gemのバージョンを固定し依存を解決する 2. Railsのconfigファイル群のアップデート bundle exec rails app:update configファイルの内、Railsアップグレードに伴う変更があるものについてconflictするはず identical config/boot.rb exist config conflict config/routes.rb Overwrite /Users/your_name/project_root/config/routes.rb? (enter "h" for help) [Ynaqdhm] helpを見てみると Y - yes, overwrite n - no, do not overwrite a - all, overwrite this and all others q - quit, abort d - diff, show the differences between the old and the new h - help, show this help m - merge, run merge tool dでdiffをみることができるので、もし修正すべきところがあれば修正する。なければnで進んでOK ちなみに、以下のようにTHOR_DIFFを設定するとnvimでdiffを開くことができる。 export THOR_DIFF="nvim -d " # vimの場合は `export THOR_DIFF=vimdiff` bundle exec rails app:update nvimでファイルを開いて、古い方のファイルを編集する(新しいファイルから移植すべきところがあれば移植する)。その後、nを選択すれば古いファイルが残る。(新しいファイルを編集してからYを押しても最初に提示された新ファイルで上書かれるのでやらないように) また、THOR_MERGE変数を設定することによって、mで編集することもできる(こっちが正統派?今回は上でやってしまった) export THOR_MERGE="code -d $1 $2" 3. 設定の変更を順次適用していく 上記の手順でconfig/application.rbファイルにconfig.load_defaults 5.0のような記述を加えた場合、Railsは古いバージョン(この場合は5.0)の設定で動く。 config/initializers/new_framework_defaults.rbには新しいバージョンの設定が書かれていて、この設定を一行ずつアンコメントしていくことで新しいバージョンの設定を順に適用していくことができる。 例として、以下のような記述があり、これの一番下の行をアンコメントするとこの項目についてアップグレード後の設定が適用されるようになるので、その状態でテストを行う。(テストがなければ手動で動作の確認を行う。) # Make Active Record use stable #cache_key alongside new #cache_version method. # This is needed for recyclable cache keys. # Rails.application.config.active_record.cache_versioning = true この手順を全設定に対して行い、全てクリアしたらこのファイルは削除して良い。 またconfig/application.rb内のconfig.load_defaults 5.0も削除する。 4. その他の作業 3まででバージョンバップは完了。 あとはデプロイするなどして動作確認していき、本番環境で無事動けばOK 参考Links Rails アップグレードガイド - Railsガイド vimdiff を neovimでやるには - それマグで! bin/rails app:update の diff を vim で見る方法 - Qiita
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FormObjectの場合はカラムリネームではない

Formオブジェクトを使用している時はの下記のエラーは リネームするのではなくて attr_accessor許可しないと使えないという意味です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リネームするのではない

Formオブジェクトを使用している時はの下記のエラーは リネームするのではなくて attr_accessor許可しないと使えないという意味です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験者向け】Rails api×React×Dockerで開発環境構築

Rails×React×Dockerにて開発環境を構築したので、アウトプット用に残しておきます。 下記の記事を参考にして構築していきました。 DockerでRuby on Rails + Reactを別々にアプリ作成する環境構築手順 Docker使ってReact × Rails(API)の環境構築 前提知識の確認 自分は初学者でrailsでしかWebアプリを構築した事がなかったのですが、Dockerで開発環境を構築する場合は、 rails newをする前に、いくつかのファイルを用意する必要があります。 ※詳しくはこちらが分かり易い Docker 公式ドキュメントの Rails Quickstart 完全解説 簡単に伝えると、プロジェクトのディレクトを作成した後rails newをする前にDocker環境構築用のファイルをいくつか作成した後で、 rails newやcreate-react-app行う。 フォルダ構造は下記のような形からスタートする アプリ名 backend Dockerfile entrypoint.sh Gemfile Gemfile.lock frontend Dockerfile docker-compose.yml ①ファイルを用意していく 上記構造のディレクトリとファイルを作成したら、中身を記述していく。 ※アプリ名と記載がある部分は、ご自分で作成中のアプリ名に変更して下さい。 docker-compose.yml docker-compose.yml version: "3" services: db: image: mariadb command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci environment: MYSQL_DATABASE: "アプリ名_development" MYSQL_ROOT_PASSWORD: "password" volumes: - mysql-data:/var/lib/mysql/data - /tmp/dockerdir:/etc/mysql/conf.d/ ports: - 3306:3306 backend: build: context: ./backend/ dockerfile: Dockerfile command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3001 -b '0.0.0.0'" volumes: - .:/アプリ名 ports: - "3001:3001" depends_on: - db frontend: build: context: ./frontend/ dockerfile: Dockerfile volumes: - ./frontend:/usr/src/app/frontend working_dir: /usr/src/app/frontend command: sh -c "npm start --host 0.0.0.0 --port 3000" ports: - "3000:3000" stdin_open: true volumes: mysql-data: {} dbには、mysqlを使用しております。 rails(backend)側では、3001番ポートで立ち上がるよう設定しており、react(frontend)側では3000番ポートにしてあります。 docker-compose.yml backend: build: ports: - "3001:3001" frontend: build: ports: - "3000:3000" rails側のファイル用意(全部で4つ) Dockerfile entrypoint.sh Gemfile Gemfile.lock Dockerfile(アプリ名/backend/Dockerfile) Dokcerfile. FROM ruby:2.7.2 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /アプリ名 WORKDIR /アプリ名 COPY Gemfile /アプリ名/Gemfile COPY Gemfile.lock /アプリ名/Gemfile.lock RUN bundle install COPY . /アプリ名 # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3001 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] Gemfile Gemfile. source 'https://rubygems.org' gem 'rails', '5.2.5' railsのバージョンは5.2.5にしてあります。 最近発生していたmimemagi関連のエラーを回避する為ですが、 rails6系が良い方は、6.0.3.6又は6.1.3.1であれば、エラー回避出来るようなので、どちらかを指定して下さい。 ※詳しくは下記を参照下さい RailsのGPL混入問題についてまとめ(mimemagic) Gemfile.lock touch Gemfile.lockで作成したら、空ファイルのままで大丈夫です。 entrypoint.sh entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /アプリ名/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" これで、rails側の準備はOKです。 react側のファイル用意 Dockerfile(アプリ名/frontend/Dockerfile) FROM node:14.15.1-alpine ※この記述だけで大丈夫です。 ②Dockerコマンドを実行する 順番に実行して下さい。 アプリ名 $ docker-compose run backend rails new . --force --no-deps --database=mysql --api アプリ名 $ docker-compose build アプリ名 $ docker-compose run --rm frontend sh -c "npm install -g create-react-app && create-react-app frontend" 成功すると、見慣れたディレクトリ構造になってるかと思います。 database.yml(backend/config/database.yml)を修正する database.yml default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: 自分で設定 password: "自分で設定" host: db socket: /var/run/mysqld/mysqld.sock development: <<: *default database: アプリ名_development test: <<: *default database: アプリ名_test usernameとpasswordは、お好きに設定して下さい。mysqlをターミナルから使用する際に求められます。 再びDockerコマンドを実行 アプリ名 $ docker-compose up アプリ名 $ docker-compose run api rake db:create 下記の画面が出ていれば、成功 rails(localhost:3001) react(localhost:3000) 最後に 記事をまとめるのは、難しいですね・・・ 初めてなので、稚拙な部分も沢山あったかと思いますが、 不明点や記述の仕方に対するフィードバックが御座いましたら、お教え頂けますと幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js + Ruby on Rails + RedisでDocker環境を構築する

はじめに  今回は、Docker環境を構築しようと思う。私自身、フロントエンドとバックエンドを一つのリポジトリに管理して、そこでDocker環境を構築した経験はないため、それなりの苦労を強いられた。今回は、私のように一つのリポジトリにフロントとバックを管理する環境下でDockerを導入することになった際に取る手順を紹介していきたいと思う。  皆さんの開発環境は、私のポートフォリオの環境とは異なる部分もあるだろうから、適宜調整しながら参考にしてもらえたら幸いである。 環境 Redis 6.2 Ruby on Rails 6.1.3.1 Next.js Docker 20.10.0-rc1 MySQL 8.0.23 追記:今回は、フロントとバックエンドを一つのディレクトリで管理しているので注意してもらいたい。 Github等で別々のリポジトリで管理している場合は、リポジトリを統一するか別々のリポジトリでやる方法を調べていただけたら幸いである。 階層構造 │ ├ m-api(API側) │  └ config │ └ database.yml │ └ Dockerfile ├ m-front(クライアント側) │ └ Dockerfile │ ├ mysql-confd │ └ default_authentication.cnf ├ docker-compose.yml │ ├ .env (環境変数を管理するファイル) 1. database.ymlの設定 database.yml default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV['DB_USERNAME'] %> #.envにDB_USERNAMEを記入 password: <%= ENV['DB_PASSWORD'] %> #.envにDB_PASSWORDを記入 + host: db - socket: /tmp/mysql.sock development: <<: *default database: m_api_development test: <<: *default database: m_api_test production: <<: *default database: m_api_production username: m_api password: <%= ENV['M_API_DATABASE_PASSWORD'] %> ここでは、hostを次に書くdocker-compose.ymlのdbのservice名に合わせる。 2.docker-compose.ymlの作成 フロントエンドとバックエンドと同じ階層にdocker-compose.ymlを作成する。 docker-compose.yml version: '3' services: redis:   image: redis:6.2 command: ["redis-server"] ports: - "6379:6379" volumes: - "./data/redis:/data" command: redis-server --appendonly yes ##MySQLとredis-serverの永続化 db: image: mysql:8.0.23 container_name: api-container command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci environment: MYSQL_ROOT_PASSWORD: $DB_PASSWORD MYSQL_DATABASE: sample MYSQL_PASSWORD: $DB_PASSWORD ports: - ${DB_PORT}:3306 volumes: - ./mysql-confd:/etc/mysql/conf.d  api:   std_open: true #binding.irbでデバックする際に必要となる tty: true      #binding.irbでデバックする際に必要となる depends_on: - db build: context: ./m-api/ #m-apiの部分は自身の環境に合わせて変える dockerfile: Dockerfile ports: - "3000:3000" links: - db - redis volumes: - ./m-api:/app #m-apiの部分は自身の環境に合わせて変える - bundle_path:/bundle #Gemの永続化 command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" environment: REDIS_URL: redis://redis:6379 front: build: context: ./m-front/  #m-frontの部分は自身の環境に合わせて変える dockerfile: Dockerfile volumes: - ./m-front/app:/usr/src/app  #m-frontの部分は自身の環境に合わせて変える command: 'yarn dev' ports: - "8080:8080" volumes: bundle_path: #Gemの永続化 mysql-data: driver: local 今回、Redis-server、MySQL、Next.js、Rails(APIモード)を使うということで、上記のようにする。 私は、Firebase Authでredis-serverを使っているため、今回はRedisをdocker-compose.ymlに記載したが、そうではない場合は書かなくてよい。 なお、$DB_PASSWORDとDB_PORTは.env(docker-compose.ymlと同じ階層)で環境変数を設定してもらいたい。 また、Redis-serverを使う際に、 command: redis-server --appendonly yes の1行がなければ、Railsのサーバーを再起動するたびにUserやPost等のデータが消えてしまうため、データの永続化ができなくなる。そのため、redisとMySQLを使う場合は必ずこの1行は入れること。 3. mysql-confの作成 MySQLのvolumeを管理するため、docker-compose.ymlと同じ階層にmysql-confdを作成し、その中にdefault_authentication.cnfというファイルを作成する。 default_authentication.cnf [mysqld] default_authentication_plugin= mysql_native_password 4. Dockerfileの作成(クライアント側、API側) まずはクライアント側からm-front/Dockerfileを作成する FROM node:14-alpine WORKDIR /usr/src/app なお、1行目のnodeは Docker Hubからnodeのバージョンを選択するのが望ましい。 次に、API側でm-api/Dockerfileを作成する FROM ruby:2.7.3 ENV LANG=C.UTF-8 \ TZ=Asia/Tokyo RUN apt-get update -qq && apt-get install -y nodejs default-mysql-client RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN bundle install COPY . /app # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] ここも、Docker HubからRubyのバージョンを選択するのが望ましい。 5.Dockerコマンドの実行 まずは、 $ docker-compose build でDocker環境をビルドする。 次に、Docker環境の中にデータベースを作成するために、 $ docker-compose run api rails db:create を実行する。 なお、ここでのapiは、docker-compose.yml内で指定したAPI側のservice名である。 適宜調整してコマンドを実行すること。 そして、databaseを作ることができたら、 $ docker-compose run api rails db:migrate を実行して、マイグレーションを実行する。 これでローカルで作成したデータベースはDocker環境でも再現できたことになる。 そして、 $ docker-compose up -d または $ docker-compose up を実行して、Dockerコンテナをupする。      http://localhost:3000/          にアクセスし、下記の画面が出てきたら成功である。 これで環境構築は終了である。 おわりに Dockerは一度作成するまでにかなりの時間を要するが、一度作ればずっと使える。そのため、エラーが出ても途中で折れずに進めることを推奨する。 また、今回の記事でどこか間違っている部分があれば指摘していただけると幸いである。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsを使ってapiを作成する時に特有の振る舞いをどうかいたらいいのか学んだことの備忘録

この記事で書く事 railsを使って外部から参照されるapiを実装しようとしたときに、モデルに書きたくない処理が必要になり、それをどこにおくか、を考えた時に学んだ事。 この記事で書かない事 細かいrailsでのそもそものapiの実装方法について 悩んだこと、考えたこと アプリケーションA自体が持つモデルに対しての処理に関係ない、外部アプリケーションBから呼び出されるapiに対しての処理を、モデルに書いてもいいのかどうか。 限定的に呼び出される処理を隔離したかった。 moduleに切り出す方法が最初はパッと浮かんだ。 ただ、それをやると、他のapiでもそのモデルに対しての処理でその内容が参照されてしまう。 そしてアプリケーションで使用されるこのモデルのインスタンス全てにその属性が参照可能になってしまう。 active_model_serializersを使ってみる controller class API::UsersController def index render json: @users, serializer: ActiveModelSerializers, each_serializer: user_client_serializer end end model # :id, :name, :imageを列にもつ class User # 細かいmodelの中身は省略 end serializer class UserClientSerializer attributes :id, :name, :image # 全てのuserにひもづくattrを参照可能に attribute :user_type # このserializerだけが必要とするattrを追加 def user_type # 特定のapi固有の属性で、モデルには書きたくない.... return 0 if object.image.present? 1 end end 簡易な表記にしたのでこのままでは動きませんが、こうすることで、model自体にapi固有の処理を書かずに済みました。 別のapi経由でもuserを渡すことになったときに、今回例としてあげたuser_typeが不要だった場合は、以下のserializerを準備してあげることで、モデルに手を加えることなく、対応が可能です。 serializer class UserSerializer attributes :id, :name, :image end この作業をするまで、active_model_serializersを使ったことはなく、勉強になりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

active model serializers のレシピ集 & 個人的ベストプラクティス集

はじめに 他に便利な使い方があれば教えてもらえると幸いです 間違っている箇所があれば教えてもらえると幸いです レシピ 条件付き attribute 条件でキーの有無が変わるときは下記のように書けます。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :posts_count, if: -> { object.posts.present? } do object.posts.count end end シリアライザ内で使えるパラメーターを渡す シリアライザにキー、バリューを渡すことで app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show render json: current_user, serializer: UserSerializer, hoge: 'test' end end or ActiveModelSerializers::SerializableResource.new( user, serializer: UserSerializer, adapter: :attributes, hoge: 'test' ).serializable_hash シリアライザ内において instance_options からその値を参照することができます。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :test_val do instance_options[:hoge] end end 個人的ベストプラクティス シリアライザは明示的に指定した方が良さそう シリアライザを明示的に指定なくてもモデルのクラス名から推測して UserSerializer を使ってくれますが、 app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show render json: current_user end end UserSerializer を変更したときの影響範囲を調べるときにシリアライザ名で grep しにくい 一目でどのシリアライザを使っているかわかりにくい ことからシリアライザを明示的に指定した方が良さそうです。 app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show render json: current_user, serializer: UserSerializer end end attribute とメソッドを2つ使うよりは attribute のみでキーを出力した方が良さそう attribute とメソッド定義を2つ書くより app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :name_hoge def name_hoge "#{object.name}_hoge" end end attribute ブロックのみを使った方がシンプルになります。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :name_hoge do "#{object.name}_hoge" end end proc のため return は使えないことに注意してください メソッドの処理そのままでメソッド名と異なるキー名を使いたいときは attribute を使った方が良さそう attribute ブロックを書くより attribute :is_admin do object.admin? end attribute メソッドの key オプションを使った方がシンプルに書けます。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :admin?, key: :is_admin end リソースのハッシュ出力では adapter: :attributes オプションを使ってルートキーを出力させない方が良さそう serializable_hash[:post] のようにキー名を指定する書き方をするとシリアライザの名前空間が変わったり、type メソッドに渡す値が変わるとキー名が変わってしまいデグレが起こる可能性があるので、ルートキーが必要ない場合は adapter: :attributes を使用してキー名を記載しない方が良いと思います。 単一リソース ActiveModelSerializers::SerializableResource#serializable_hash を使います。 serializer オプションにシリアライザを指定します。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :single_post do ActiveModelSerializers::SerializableResource.new( object.single_post, serializer: PostSerializer, adapter: :attributes ).serializable_hash end end 複数リソース each_serializer オプションにシリアライザを指定します。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :posts do ActiveModelSerializers::SerializableResource.new( object.posts, each_serializer: PostSerializer, adapter: :attributes ).serializable_hash end end アソシエーションをシリアライズするときは has_many, has_one, belongs_to を使うよりも手動でシリアライズした方がパラメーターを渡すことができるので良さそう アソシエーションも返却するときは has_many などのメソッドを使えばシンプルに書くことができますが、 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer has_many :posts, serializer: PostSerializer end パラメーターを渡すことはできません app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer # hoge_param は PostSerializer に渡らない has_many :posts, serializer: PostSerializer, hoge_param: 'hoge' end アソシエーションのシリアライザにパラメーターを渡したいときは手動でシリアライザを展開しなければなりません app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :posts do ActiveModelSerializers::SerializableResource.new( object.posts, each_serializer: PostSerializer, hoge_param:'hoge', adapter: :attributes ).serializable_hash end end ルートキーにメタ情報を追加したいときは meta オプションを使う方が良さそう キー名にこだわりがなくルートキーと同じ階層にメタ情報を追加したいときは meta を使うと楽です。 app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show render json: current_user, serializer: UserSerializer meta: {time: Time.zone.now} end end meta を使わない場合は hash を自前で作ることになるのでコードが煩雑になると思われます。 app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show hash = { user: ActiveModelSerializers::SerializableResource.new(current_user, serializer: UserSerializer, adapter: :attributes).serializable_hash, meta: {time: Time.zone.now} } render json: hash end end ディレクトリの切り方はコントローラーに準じた方が良さそう 使いたい user serializer がコントローラーのメソッドごとに違う場合は app/controllers/users_controller.rb class UsersController < Api::ApplicationController def index ... end def show ... end end serializers 配下にフラットにシリアライザファイルを作成するよりも app/serializers/index_user_serializer.rb app/serializers/show_user_serializer.rb 返却されるオブジェクトはAPIのエンドポイントに依存しているので、コントローラーのクラス名の名前空間、メソッドに応じてディレクトリを切った方がわかりやすいと思っています。 app/serializers/users/index/user_serializer.rb app/serializers/users/show/user_serializer.rb 汎用的ではなく1つのエンドポイントでしか使われないシリアライザはシリアライザ内にクラス定義した方が良さそう 1つのエンドポイントでしか使われない特殊なシリアライザならばファイルに切り出して、コードを参照するときに別ファイルを見にいくよりも app/serializers/special_post_serializer.rb シリアライザクラス内にシリアライザを定義した方がコードを追いやすいかもしれません。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :special_post do ActiveModelSerializers::SerializableResource.new( object.posts, serializer: UserSerializer::SpecialPostSerializer, adapter: :attributes ).serializable_hash end class SpecialPostSerializer < ApplicationSerializer attribute :special_post_key do 'value' end end end 名前空間が変わらないので基本的には type を指定した方が良さそう type メソッドを使うことでルートキーの名前を固定することができます。 app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer  type 'user' attribute :name end ActiveModelSerializers::SerializableResource.new( user, serializer: UserSerializer, ).serializable_hash => {user: {...}} type を指定しないとシリアライザの名前空間によってルートキーの名前が変わってしまいます。 app/serializers/v2/user_serializer.rb class V2::UserSerializer < ApplicationSerializer attribute :name end ActiveModelSerializers::SerializableResource.new( user, serializer: V2::UserSerializer, ).serializable_hash => {'v2/user' => {...}} 名前空間に関わらず一意のリソースの名前を使うことの方が多いと思うので type は指定した方が良いと思われます。 # ルートキーが変わるとこのようなコードがデグレを起こす可能性がある user_res = ActiveModelSerializers::SerializableResource.new(...).serializable_hash user_res[:user][:name] シリアライザ内でデータの出どころがわかりにくいので scope は使わない方が良さそう scope を使用すると指定した情報をそのままシリアライザに持っていくことができますが、下記の例の場合 current_post がどこから来たのかがわかりにくいため app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show render json: current_user, serializer: UserSerializer, scope: current_user.current_post, scope_name: :current_post end end app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :post_name do current_post.name end end 通常のパラメーターで渡した方がわかりやすいと思っています。 app/controllers/users_controller.rb class UsersController < Api::ApplicationController def show render json: current_user, serializer: UserSerializer, current_post: current_user.current_post end end app/serializers/user_serializer.rb class UserSerializer < ApplicationSerializer attribute :post_name do instance_options[:current_post].name end end has_many のアソシエーションで order を使うときはそれ専用のアソシエーションをモデルで定義した方が良さそう 下記記事を参照してください。 has_manyで紐づくモデルをSerializerの中でorderするとSQLキャッシュのN+1が起きる問題をなんとかしたい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む