20210502のRailsに関する記事は26件です。

[AWS & Rails & PostgreSQL]EC2のサーバー環境構築

はじめに AWSのEC2でRailsアプリをデプロイしたのでメモとして残しておきます。 今回はEC2のサーバー環境構築をしていきます 自分の手順書としてのメモですので画像はありません。あしからず。 前提としてEC2にログインできていて新しいユーザーを作ってログインできている状態とします。 Nginxを設定する まずサーバーをアップデートします。 サーバー $ sudo yum update Nginxをインストール サーバー $ sudo amazon-linux-extras install nginx1.12 -y Nginxの起動 サーバー $ sudo systemctl start nginx Nginxサービスを有効化します。(インスタンス起動時自動起動) サーバー $ sudo systemctl enable nginx 起動しているか確認(ステータスを表示) サーバー $ systemctl status nginx 必要なプラグインをインストール サーバー $ sudo yum install git make gcc-c++ patch openssl-devel libyaml-devel libffi-devel libicu-devel libxml2 libxslt libxml2-devel libxslt-devel zlib-devel readline-devel ImageMagick ImageMagick-devel epel-release PostgreSQLをインストール サーバー $ sudo amazon-linux-extras install postgresql11 先ほどの方法でインストールされるのは、PostgreSQLのクライアントだけで、サーバはインストールされない。 postgresql-serverをインストールする サーバー $ sudo yum install postgresql-server postgresql-contrib postgresql-devel サーバーを初期化します。 サーバー $ sudo postgresql-setup initdb Node.jsをインストール サーバー # ダウンロード $ curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash - #インストール $ sudo yum install -y nodejs Yarnをインストール サーバー $ curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo $ sudo yum install -y yarn rbenvをインストール サーバー $ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile $ source ~/.bash_profile ruby-buildをインストール サーバー $ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build $ rbenv rehash Rubyをインストール ローカル環境にて制作したアプリをどのバージョンのRubyで制作したか確認してください。 アプリのディレクトリ内でruby -vでバージョン確認です。 ローカル $ ruby -v ruby 2.6.6 筆者は2.6.6でインストールします。 サーバー $ rbenv install -v 2.6.6 $ rbenv global 2.6.6 $ rbenv rehash $ ruby -v 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

URL直打ち対策(ログアウト/ログイン別まとめ)

はじめに アプリ実装中、"URL直打ち"対策を行なったので、記録として残します。 Ruby 2.6.5 目次 1. "URL直打ち"とは 2. 対 ログアウトユーザー 3. 対 ログインユーザー 4. 対 ログインしている自分 5. まとめ 1. "URL直打ち"とは "URL直打ち"とは、その名前の通りURLに直接書き込むことです。 例えば、他のユーザーが投稿したつぶやきや写真・動画などはこちらが勝手に編集・削除はできませんよね。 それに対し、自分が投稿したものに対してはいつでも編集・削除ができます。 これは自分自身と他のユーザーとで画面上でのボタンの表示の切り替えがされるように実装されているからです。 なので、普通なら他ユーザーの投稿は編集・削除ができません。 しかし、URLの仕組みを理解してると、URLに直接書き込むことで他のユーザーの編集画面ページに遷移することができてしまいます! なので、この対策はしっかりおこなわなければなりません! それが"URL直打ち"対策というわけです。 今回登場するモデルとテーブルの関係 モデルとテーブルの関係を簡単にではありますが、記載しておきます。 2. 対 ログアウトユーザー まずは、ログアウトユーザーへの対策です。 これはすごくシンプルで、以下のコードを対策したいコントローラー内に設定していれば対策できています! before_action :authenticate_user!, except: [:index, :show] 実際にコントローラー内に記述したらこんな感じ。 items_controller.rb class ItemsController < ApplicationController before_action :authenticate_user!, except: [:index, :show] def index end def show end end 記述したコードを1つずつ要約すると以下の通りです。 記述 説明 before_action このコードの後ろに書かれたメソッドは、このコントローラー内の処理が動く前に実行される.つまり、一番はじめに実行される. :authenticate_user! ログイン済みユーザーの認証を行う。ログインしてなければ、ログイン画面にとばす!devise機能を実装することで使えるメソッドです。(作成するモデル名によってuserの部分が変わります。今回はモデル名にUserを使用しています) except: [:index, :show] index(一覧)とshow(詳細)は除く これを踏まえて、このコードを翻訳すると 「ログインしてなかったら弾き飛ばす。でも、indexとshowはログインしてなくても見れるよ。」 というような感じです。 大体の投稿アプリは、以下のような機能はログアウトしていてもできると思います。 他の投稿者のツイートや写真・動画投稿の閲覧 その投稿者の詳細ページ閲覧 検索 しかし、いざ投稿に対してのコメントや自分が投稿しようとしたらログイン画面に飛ばされてしまいますよね? それはこの対策がしっかりされているからです。 もちろん、URLを直打ちしてページ遷移をすることも防いでくれています! ちなみに、exceptはその後に指定されたものを「除く」という意味ですが、これと反対でonlyというものもがあります。こちらは、その後に指定したもの「のみ対象」という意味になります。 before_action :authenticate_user!, only: [:index, :show] # indexとshowのみログアウトしてたら見れないよ これで、対ログアウトユーザーへの対策はできました! 3. 対 ログインユーザー 次はログインしている自分以外のユーザーに対しての対策です。 例えば自身が投稿した内容はいつでも編集(edit, update)・削除(destroy)できますよね。 画面上ではログイン/ログアウト、自身と他ユーザーによってボタン表示の切り替えはバッチリできていても、URLの対策をしていないと直接URLに打ち込まれてあっさり編集画面に侵入...なんてことが起きてしまいます。 しっかり対策しておきましょう! 他にも色々あると思いますが、シンプルな例として以下のことが挙げられます。 自身が投稿(出品)した内容を他ユーザーに編集・削除されたくない これに対しては以下のようにコードを記述することで対策ができます。 # 商品情報のidを取得 @item = Item.find(params[:id]) # 出品者本人かどうかの分岐 if @item.user_id != current_user.id redirect_to root_path end 記述したコードを1つずつ要約すると以下の通りです。 記述 説明 @item.user_id itemsテーブルにあるuser_idを取得。itemモデルとuserモデルでアソシエーションを組んでいるため、このような記述ができます。 != 「等しくない時」という意味。 current_user.id 【current_user】devise機能を実装することで使えるメソッドです。(作成するモデル名によってuserの部分が変わります。今回はモデル名にUserを使用しています)ログインしているユーザーを取得してくれます。.idをつけることで現在ログインしているユーザーのidを取得してくれます。 これを踏まえて、このコードを翻訳すると 「投稿(出品)したユーザーと現在のユーザーのidが違えばトップページに飛ばす。」 というふうになります。 実際にコントローラー内のeditだけに適応したければこんな感じになります。 items_controller.rb # editアクションのみに対応 def edit @item = Item.find(params[:id]) if @item.user_id != current_user.id redirect_to root_path end end けれど他にも、destroyアクション、updateアクションにも適応させたいです。 しかし、それぞれのアクション内に上記のコードを記述していくと非常に非常に冗長でかつ見にくいコードになってしまいます。 というか普通に邪魔くさいです。 items_controller.rb # それぞれのアクションに記述 # かなり見にくいですよね def edit if @item.user_id != current_user.id redirect_to root_path end end def update if @item.user_id != current_user.id redirect_to root_path end end def destroy if @item.user_id != current_user.id redirect_to root_path end end なので、複数同じコードを記述する場合は コードを一箇所にまとめて、適応させたいアクションのみ指定してあげる とした方がスッキリます! ちなみに、editアクションを防いだ時点でupdateアクションは呼ばれないはずです。 しかし、検証ツールからボタンを表示させる、URLを直接入力をするといったことにより、updateアクションにたどり着いてしまう可能性があります! なので、 edit・updateアクション new・createアクション はセットで記述した方がいいです。 コードを1つにまとめるとこんな感じになります。 before_actionで呼び出しているprevent_urlは私が勝手に考えた名前ですので、ここの命名は自由で大丈夫です! items_controller.rb class ItemsController < ApplicationController before_action :set_furima, only: [:edit, :update, :destroy] before_action :prevent_url, only: [:edit, :update, :destroy] def edit end def update end def destroy end private def set_furima @item = Item.find(params[:id]) end def prevent_url if @item.user_id != current_user.id redirect_to root_path end end end これで、対ログアウトユーザーへの対策はできました! 4. 対 ログインしている自分 最後は、ログインしている自分自身にもURLを直打ちしてページ遷移させないようにする実装です。 個人的にここの実装がかなり手こずりました。 実装する内容としては、以下の2つです。 ① ログイン状態の出品者が自身の出品した商品の購入画面に遷移できないようにする ② ログイン状態の出品者が売却済みの自身が出品した商品に対して、商品購入画面に遷移できないようにする 特に②の売却済みの商品という表現に苦戦しました。 結論から言うと、以下のコードを記述して①と②の対策をしました。 purchases_controller.rb class PurchasesController < ApplicationController before_action :set_furima, only: [:index, :create] before_action :prevent_url, only: [:index, :create] def index end def create end private def set_furima @item = Item.find(params[:item_id]) end def prevent_url if @item.user_id == current_user.id || @item.purchase != nil redirect_to root_path end end end 上記のコードは論理演算子||(または)を用いて2つに分けています。 ① ログイン状態の出品者が自身の出品した商品の購入画面に遷移できないようにする @item.user_id == current_user.id これは対 ログインユーザーで説明したコードとほぼ同じです。内容としては、 「投稿(出品)したユーザーと現在のユーザーのidが同じであればトップページに飛ばす。」 となります。 ② ログイン状態の出品者が売却済みの自身が出品した商品に対して、商品購入画面に遷移できないようにする @item.purchase != nil このコードを1つずつ要約すると以下の通りです。 記述 説明 @item.purchase itemsモデルに紐づくpurchasesモデルのitem_idを取得 != 「等しくない時」という意味。 nil 「何もない、空」という意味。 つまり、②のコードを翻訳すると 「purchasesテーブルにあるitem_idがnil(空)でない時」 というふうになります。purchasesテーブルは商品の購入情報を保存するテーブルであるため、 itemsテーブルのidとpurchasesテーブルのitem_idが一致する = 商品は購入されている ということになります。 逆にいうと、itemsテーブルのidとpurchasesテーブルのitem_idが一致しなければ、その商品はまだ販売中ということになります。 また、売却済みの自身が出品した商品の編集画面にも遷移できないように実装したいので、itemsコントローラーのところにもコードを追加しました。 items_controller.rb class ItemsController < ApplicationController before_action :set_furima, only: [:edit, :update, :destroy] before_action :prevent_url, only: [:edit, :update, :destroy] def edit end def update end def destroy end private def set_furima @item = Item.find(params[:id]) end def prevent_url if @item.user_id != current_user.id || @item.purchase != nil # コードを追加 redirect_to root_path end end end これで出品者本人に対するURL直打ち対策ができました! 5. まとめ 画面上のボタンの表示切り替えはしっかりできていても、URLの直打ち対策をうっかり忘れてしまう...なんてことがないようにしないといけませんね。 今回の実装でかなり勉強になりました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] deviseによるユーザー管理機能の導入手順とメソッドまとめ

記事の概要 devise gemによるユーザー管理機能の導入手順をまとめました。 ユーザー管理機能を実装するためのdeviseメソッドをまとめました。 deviseとは?  ユーザー管理機能を簡単に実装するためのGemです。 1. devise導入手順まとめ ① deviseのインストール  Gemfileの最下部にdeviseを記述する。 Gemfile gem 'devise'  次に、ターミナルでbundle installを実行する。 % bundle install  ここでGemのインストールを反映するために、ローカルサーバーを再起動すること。 ② deviseの設定ファイル作成  rails g devise:installコマンドを実行し、devise Gemの設定関連に使用するファイルを自動で生成する。 % rails g devise:install ③ deviseのUserモデル作成  rails g deviseコマンドを実行し、ユーザー管理機能のモデルとマイグレーションの生成、ルーティングの設定をまとめて行う。  ※通常のモデル作成方法と異なる点に注意。 % rails g devise user ④ ユーザーテーブル作成  先ほど自動生成されたマイグレーションファイルを確認し、必要に応じてファイルに追記し、テーブル設計を反映させる。メールアドレスとパスワードのカラムはデフォルトで記述されている。 db/migrate/XXXXXXXXX_devise_create_users.rb class DeviseCreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" t.string :nickname, null: false # 左記のように必要に応じてカラム追加 # 省略 end # 省略 end end  テーブル設計を確認後、マイグレーションを実行。 % rails db:migrate  テーブル情報を変更したので、ここでローカルサーバーを再起動すること。 ⑤ deviseのビューファイル作成  rails g devise:viewsコマンドを実行し、deviseが元々用意しているログイン/サインアップ画面のビューファイルを生成する。 % rails g devise:views  サインアップ画面はapp/views/devise/registrations/new.html.erb、ログイン画面のビューはapp/views/devise/sessions/new.html.erbにそれぞれ対応。 ⑥ [補足]コントローラーについて  deviseの処理を行うコントローラーはGem内に記述されているため編集ができない。従い、必要な処理(ストロングパラメーターなど)は全てのコントローラーの継承元であるapplicationコントローラーに追記すること。 2. deviseメソッドまとめ  deviseが提供するメソッドのうち、よく使うもの。これらを用いてユーザー管理機能を実装していく。 メソッド 説明 devise_for ユーザー機能に必要なルーティングを一度に生成する devise_parameter_sanitizer ログインや新規登録などのリクエストからパラメーターを取得する user_signed_in? ユーザーがログイン状態かどうかを判定する current_user 現在ログインしているユーザーの情報を取得する authenticate_user! ユーザーがログインしていなければ、ログイン画面に遷移させる ① devise_for  ユーザー機能に必要なルーティングをまとめて生成する。rails g deviseコマンド実行時に下記のように自動記述される。 config/routes.rb Rails.application.routes.draw do devise_for :users end ② devise_parameter_sanitizer  deviseにおけるparamsのようなメソッドで、ストロングパラメーターの設定に用いる。permitメソッドを組み合わせることで、deviseに定義されているストロングパラメーターに対して、追加したいカラムも指定して含めることもできる。なお、上述の通り、applicationコントローラーに記述すること。 application_controller.rb private def configure_permitted_parameters # ※メソッド名は慣習なのでこの限りでない devise_parameter_sanitizer.permit(:deviseの処理名, keys: [:許可するキー名]) end  deviseの処理名には、sign_in, sign_up, account_updateを選択して指定する。 ③ user_signed_in?  ユーザーのログイン状態を判定する。ユーザーがログイン状態であればtrueを、ログアウト状態であればfalseを返す。用途例として、下記のようにif文と組み合わせることで「ログイン」「ログアウト」「新規登録」などのリンクボタンの切り替え表示が実装できる。 <% if user_signed_in? %> <%= link_to "ログアウト", destroy_user_session_path, method: :delete %> <% else %> <%= link_to "ログイン", new_user_session_path %> <%= link_to "新規登録", new_user_registration_path %> <% end %> ④ current_user  現在ログインしているユーザーの情報を取得できる。例えばログイン中のユーザーのidを取得したい場合、 current_user.id で取得できる。 ⑤ authenticate_user!  このメソッドが呼ばれた時点でユーザーがログインしていなければ、そのユーザーをログイン画面に遷移させる。下記のようにbefore_actionで呼び出すことで、特定のアクションを実行する前にログインしていなければ、ログイン画面に強制遷移させることができる。 xxx_controller.rb class XxxController < ApplicationController before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy] end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【docker】docker上でrails コマンドを使う【rails】

前提 docker でrails アプリを立ち上げて、 Yay! You’re on Rails!を表示できている。 とりあえず"docker compose run web"をつける 例えば、userモデルをdocker上で作りたい場合、 docker compose run web rails g model User user_name:string{64} おまけ こんな記述の仕方だと見やすいかも。 % docker compose run web rails g model Product \ product_name:string{64} \ category_id:bigint \ price:integer \ description:string{256} \ sale_status_id:bigint \ product_status_id:bigint \ regist_date:timestamp \ user_id:bigint \ delete_flag:boolean \ + enterキーで、ターミナル上での改行ができる。 migrationファイル作るときは見やすい。 docker compose run web rails g model モデル名はタイポしないようにしよう。 間違えた名前のファイルができて面倒な事になる。 他は、適宜migrationファイルをいじれば良いのでタイポしてもそこまで影響はない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Activehashとransackを併用する前の注意点

概要 こんばんは。プログラミング初学者です。 Activehashを使用してデータを管理しているカテゴリーがあったのですが、のちに検索機能を実装する際にransackを使用した時に少し面倒なことが起きたので、備忘録的にまとめたいと思います。 少し対象が限定的なので、下記対象となる実装に心当たりがあれば読み進めていただけると幸いです。 タイトルの割に大衆受けするような注意点ではありませんが、どなたかのご参考になれば幸いです。 対象となる実装 Activehashのモデルファイルに、下記のような実装をしている場合 ・「id: 0」などに「---」の項目を設けている。 例 class Displacement < ActiveHash::Base self.data = [ { id: 0, name: '---' }, { id: 1, name: '50cc以下' }, { id: 2, name: '51~125cc' }, { id: 3, name: '126~250cc' }, { id: 4, name: '251~400cc' }, { id: 5, name: '401~750cc' }, { id: 6, name: '751~1000cc' }, { id: 7, name: '1001cc以上' } ] 加えて「---」選択時は保存できないように実装をしている。 validates :displacement, numericality: { other_than: 0 } 表示例 問題点 上記のようにActivehashを用いてデータを保存できるように実装しており、 検索機能時にransackを用いてこのidの中から絞り込み検索できるように実装したい場合。 例えば上記例でいうと、検索機能実装にあたって、Activehashのカテゴリーを一覧化して、「251~400cc」のデータを検索したい、といった機能を考えているとします。 コントローラーファイル例 before_action :search_reviews, only: [:index, :search] def search @results = @r.result end def search_reviews @r = Review.ransack(params[:q]) end ビューファイル例 <div class="refined-title">▶︎ 絞り込み検索</div> <%= search_form_for @r, url: search_reviews_path do |f| %> <%= f.label :displacement_id_eq, '排気量' %> <%= f.collection_select :displacement_id_eq, Displacement.all, :id, :name %> このように実装すると、実際の表示では と、それぞれのActivehashカテゴリーから絞り込んで検索できるように見えますが、問題点が一つあります。 上記例の場合、例えば排気量は指定せずにメーカーと車種タイプを選んで検索をかける場合、 実際に検索にかかるのは メーカーが「HONDA」に該当するidと車種タイプが「スーパースポーツ」に該当するidだけでなく、 排気量のidが「0」に該当するものも検索してしまいます。 しかもransackで使えるメソッドの中に、特定のidを表示させない、取得しないものはありませんでした。 保存時には「numericality」のバリデーションをかけているため、当然「id: 0」に該当するデータはなく、検索結果は「該当なし」となります。 これが今回私の実装にあたって大きな問題となりました。 ではどうすればいいのか? Activehashとransack併用を考えているのであれば、 Avtivehash作成時に「---」の項目を設けるのではなく、 表示するときにpromptを使おう!! というのが私なりの解となります。 詳しく解説します。 まずはActivehashに関する実装部分です。 Activehashモデルファイルの「id: 0」を消し去ります。 class Displacement < ActiveHash::Base self.data = [ { id: 1, name: '50cc以下' }, { id: 2, name: '51~125cc' }, { id: 3, name: '126~250cc' }, { id: 4, name: '251~400cc' }, { id: 5, name: '401~750cc' }, { id: 6, name: '751~1000cc' }, { id: 7, name: '1001cc以上' } ] すると標準表示がこのようになります。 これでは毎回初期表示が「50cc以下」になって、なんか嫌ですよね。 しかも選択し忘れていた場合、全てのデータが「50cc以下」で登録されてしまいます。 したがって、オプションのpromptを使用します。 保存時のビューファイルにて ビューファイル例 <%= f.collection_select(:displacement_id, Displacement.all, :id, :name, prompt: '---') %> activehash表示時に上記のようにプルダウンボタンで実装する場合、各引数と役割は下記の通りです。 <%= form.collection_select(保存されるカラム名, オブジェクトの配列, カラムに保存される項目, 選択肢に表示されるカラム名, オプション, htmlオプション) %> 第5引数に用いるオプションとして、promptを使用します。 htmlオプションはclassの付与などです。 私の場合、うまくいかなかったので、検証ツールを用いてidセレクタでCSSを適用しました。 prompt使用により、初期表示がpromptで設定した文字列となります。 うまくいきました。 さらにモデルファイルにおいて、NOT NULL制約をかけることで、「---」選択時は保存されずエラーメッセージを返すようにできます。 (promptで設定した項目は『nil』として返すため。) 次にransackでの検索機能実装部分です。 検索機能実装のビューファイル例 <div class="refined-title">▶︎ 絞り込み検索</div> <%= search_form_for @r, url: search_reviews_path do |f| %> <%= f.label :displacement_id_eq, '排気量' %> <%= f.collection_select :displacement_id_eq, Displacement.all, :id, :name, prompt: '指定なし' %> ここでも第5引数にpromptを用いて、検索項目「指定なし」を設けました。 (ちなみに私はここで、第2引数に無理矢理exceptメソッドとかを使ってなんとか「id: 0」抜きで表示させようと必死でしたが、NoMethodErrorに怒られてました。) (もっと悪いやり方では、Displacement.find [1, 2, 3, 4, 5, 6, 7]なんてことまで思いつきましたがあまりにもスマートでない笑) 表示例 これで、絞り込み検索時に必要ない情報は「指定なし」を選択することができるし、 「指定なし」とした項目は検索結果に引っかからないので、上記例でいうと3つの項目の内2つだけの項目で絞りたい、なんてことも可能です。 まとめ 結論:ActiveHashの表示には、 promptを積極活用しよう!!! です。 ここまでご覧いただいた方ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【RSpec】モデル単体テスト実行時の「MissingAttributeError」を解決する

はじめに RSpecを用いたテスト時のエラーでつまずいたので、備忘録としてまとめます。 環境 Ruby 2.6.5 Rails 6.0.3.6 状況 Twitterのような投稿アプリを作成中です。FactoryBotを用いて、投稿に関するPostモデルの単体テストを実行したところ、「MissingAttributeError」が出ました。以下に詳細を記します。 FactoryBot posts.rb FactoryBot.define do factory :post do # 省略 association :user    # 省略 end end association :userは、users.rbのFactoryBotとアソシエーションがあることを意味しています。つまり、Postのインスタンスが生成したと同時に、関連するUserのインスタンスも生成されます。 exampleを書き出し、試しにテストを実行するとエラー発生 ActiveModel::MissingAttributeError: can't write unknown attribute `user_id` 「user_idという属性は知らないよ」という内容です。 postsテーブルを確認 テーブルのカラムを確認していくと、user_idカラムがありませんでした。 マイグレーションファイルを確認 t.references :user の記述を忘れており、テーブル作成時にuser_idカラムが作られなかったことがわかりました。 user_idカラムを追加し、エラー解決 カラムの追加方法は、他の方の分かりやすい記事がありますので、ご参考ください。 私はこちらを参考にさせていただきました。 https://qiita.com/kurawo___D/items/e3694f7a870a1cc4738e まとめ postはuserに紐付いているため、FactoryBotでassociation :userと記述し、紐付くuserデータを同時生成しようとしました。 しかし、postテーブルにuser_idカラムがなかったため、紐付かせることができず、エラーが出てしましました。 最後に 初めての投稿で分かりづらい部分が多々あるかと思いますが、どなたかの参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsのJSで.envファイルに書いた環境変数を呼び出す方法

手順 ① gem 'dotenv'をDL(dotenv-railsでも可。どっちか) ② `config/webpack/environments.jsにdotenv呼び出すロジックを追記 ③ .envファイルに呼び出したい環境変数を設置 ④ JSで環境変数を呼び出す 大前提 ・Railsに限らず、.envファイルで設定した環境変数をJSで呼び出すには、webpackとの連携が必要になるみたいです。 Vue.jsでも、なんでも。 ・API連携などで、連携の為に発行されるIDなどを、.envファイルに記述し、dotenvで管理する、という手法もRailsに限らずの話です。 わからない方は調べてみてください。 やり方 STEP1 Gemfileにgem 'dotenv'を追記 STEP2 config/webpack/environments.jsを以下のように編集 config/webpack/environments.js const { environment } = require('@rails/webpacker') const webpack = require('webpack') const dotenv = require('dotenv') const dotenvFiles = [ `.env.${process.env.JSで呼び出したい環境変数}.local`, '.env.local', `.env.${process.env.JSで呼び出したい環境変数}`, '.env' ] dotenvFiles.forEach((dotenvFile) => { dotenv.config({ path: dotenvFile, silent: true }) }) environment.plugins.prepend('Environment', new webpack.EnvironmentPlugin( JSON.parse(JSON.stringify(process.env)) ) ) module.exports = environment STEP3 .envに環境変数を設定 env. NANTKA_ID = xxxxxxxx STEP4 example.js process.env.NANTKA_ID
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSで自動デプロイした時、ArgumentError: Index name 'index_boards_on_user_id' on table 'boards' already existsが出た際の解決法

開発環境 AWSサーバー使用 自動デプロイツールcapistrano使用 状況 ローカル環境で、データベースを一度削除し、作り直して、'bundle exec cap production deploy'コマンドを実行した際に、以下のようなエラーが出た。 Caused by: DEBUG [7ec9cb7c] ArgumentError: Index name 'index_boards_on_user_id' on table 'boards' already exists DEBUG [7ec9cb7c] /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/abstract/schema_statements.rb:1197:in `add_index_options' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/mysql/schema_creation.rb:65:in `index_in_create' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/abstract/schema_creation.rb:49:in `block in visit_TableDefinition' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/abstract/schema_creation.rb:49:in `map' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/abstract/schema_creation.rb:49:in `visit_TableDefinition' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/abstract/schema_creation.rb:14:in `accept' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/abstract/schema_statements.rb:315:in `create_table' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/connection_adapters/mysql/schema_statements.rb:81:in `create_table' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/migration.rb:890:in `block in method_missing' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/migration.rb:858:in `block in say_with_time' /var/www/forum-lastapp/shared/bundle/ruby/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/migration.rb:858:in `say_with_time' 以下省略 解決法 ローカルでデータベース関連の内容を修正した場合の対処が必要となる。 本番環境で以下二つのコマンドを実行する % rails db:drop RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 % rails db:create RAILS_ENV=production その後、一度プロセスをkillした上で「bundle exec cap production deploy」を実行すると、エラーは解決された。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactとの連携3

hello_react.jsx // Run this example by adding <%= javascript_pack_tag 'hello_react' %> to the head of your layout file, // like app/views/layouts/application.html.erb. All it does is render <div>Hello React</div> at the bottom // of the page. import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' const Calc = props => { let n = props.number; let total = 0; for (let i = 0;i <= n;i++){ total += i; } return ( <div>ゼロから{props.number}までの合計は「{total}」です。</div> ); } Calc.defaultProps = { number: 0 } Calc.propTypes = { number: PropTypes.integer } document.addEventListener('DOMContentLoaded', () => { let el = (<div> <Calc number="100" /> <Calc number="200" /> <Calc number="300" /> </div>); let dom = document.querySelector('#hello'); ReactDOM.render(el, dom); }) for文の基本書式 for 文では指定した回数だけ繰り返し処理を行うことができます。 for (初期化式; 条件式; 変化式){ 実行する文1; 実行する文2; ... } 代入演算子 名称 略記演算子 意味 代入 x = y x = y 加算代入 x += y x = x + y 減算代入 x -= y x = x - y 乗算代入 x *= y x = x * y 除算代入 x /= y x = x / y 剰余代入 x %= y x = x % y べき乗代入 x **= y x = x ** y 左シフト代入 x <<= y x = x << y 右シフト代入 x >>= y x = x >> y 符号なし右シフト代入 x >>>= y x = x >>> y ビット論理積 (AND) 代入 x &= y x = x & y ビット排他的論理和 (XOR) 代入 x ^= y x = x ^ y ビット論理和 (OR) 代入 x I= y x = x I y 論理積代入 x &&= y x && (x = y) 論理和代入 x II= y x II (x = y) Null 合体代入 x ??= y x ?? (x = y) 比較演算子 演算子 説明 true を返す例 等価 (==) 被演算子が等しい場合にtrueを返します。 3 == var1,"3" == var1,3 == '3' 不等価 (!=) 被演算子が等しくない場合にtrueを返します。 var1 != 4,var2 != "3" 厳密等価 (===) 被演算子が等しく、かつ同じ型である場合にtrueを返します。Object.is や JavsScript での等価も参照してください。 3 === var1 厳密不等価 (!==) 被演算子が等しくなく、かつ/または同じ型でない場合にtrueを返します。 var1 !== "3",3 !== '3' より大きい (>) 左の被演算子が右の被演算子よりも大きい場合にtrueを返します。 var2 > var1,"12" > 2 以上 (>=) 左の被演算子が右の被演算子以上である場合にtrueを返します。 var2 >= var1,var1 >= 3 より小さい (<) 左の被演算子が右の被演算子よりも小さい場合にtrueを返します。 var1 < var2,"2" < 12 以下 (<=) 左の被演算子が右の被演算子以下である場合にtrueを返します。 var1 <= var2,var2 <= 5 MDN web Docs https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Expressions_and_Operators インクリメント演算子 ++ デクリメント演算子 -- インクリメント演算子は、1を足し、デクリメント演算子は、1を引く、という機能を持ちます。 i++はiに1を加えます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chartkickを使って、ページにグラフを表示させる方法(Ruby on Rails)

プログラミング学習のための備忘録 Ruby on Railsで資産管理アプリを作成しています。 配当金の月別配当金額をグラフ表示するため、chartkickを用いました。 備忘録として、導入から表示させるまでの手順をまとめました。 完成図 以下のような、配当金を記録した仮のテーブルを作成して、グラフ表示させました。 ビューとルーチングを追加する グラフを表示させるビューファイルを作成し、ルーチングを追加します。 app/views/dividends直下にchart.html.erbを作成します。 config/routes.rb Rails.application.routes.draw do #省略 get "/dividends/index" => "dividends#index" get "/dividends/chart" => "dividends#chart" #省略 end chartkickをインストールする Gemfileにchartkickをインストールするためのコマンドを追記します。 追記したら、bundle installを実行します。 groupdateは、データを日毎や月毎などグループ化して表示させることができるようになるgemです。 合わせてインストールしておくと便利です。 Gemfile #省略 gem 'bootsnap', '>= 1.4.4', require: false #ここから追記する gem "chartkick" #chartkickを導入するためのgem gem "groupdate" #グラフの横軸の単位を「日付ごと」で表示できるgem #ここまで追記する group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end #省略 次の手順は、Railsのバージョンが6以降か、5以前かで分岐します。 ターミナルでrails -vを実行し、自分のrailsのバージョンを確認してから選択します。 私は、Rails6以降でしたので、1の手順で進めました。 ターミナル % rails -v Rails 6.1.3 Rails6以降 yarnでchart.jsをインストールします。 ターミナル % yarn add chartkick chart.js その後、app/javascript/packs/application.jsに以下のコマンドを追記します。 app/javascript/packs/application.js // This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. import Rails from "@rails/ujs" import Turbolinks from "turbolinks" import * as ActiveStorage from "@rails/activestorage" import "channels" Rails.start() Turbolinks.start() ActiveStorage.start() import "chartkick/chart.js" //ここを追記する Rails5以前 app/assets/javascripts/application.jsに以下のコマンドを追記します。 (Rails5以前は未検証ですので、これのみで動作するかは未確定です、、、。) app/assets/javascripts/application.js //= require chartkick //= require Chart.bundle ビューにグラフを表示させる 表示させたいページにグラフを埋め込みます。 配当金の合計値を月ごとに棒グラフで表示させています。 オプションを使用すると、表示させるグラフの大きさやグラフの色、軸タイトルなどをカスタマイズできます。 app/views/dividends/chart.html.erb <h1 class="dividends-heading">月別配当金記録</h1> <div class="container"> <%= column_chart Dividend.group_by_month(:month, format:"%Y-%m").sum(:income_based_yen), width: "800px", height: "500px", min: 1, max: 10000, colors: ["#b00","#0b0","#00b"], xtitle: "月" , ytitle: "配当金合計", prefix: "¥", thousands: "," %> </div> オプション chartkickのオプションを設定することで、グラフをカスタマイズできます。 オプションの種類とカスタマイズできる内容をまとめました。 使用するグラフによって、効果のあるオプションが違うみたいですので、色々なグラフで確かめてみたいです。 誤り等ございましたら、教えていただけると助かります。  オプション名    カスタマイズできる項目  width 表示させるグラフの幅 height 表示させるグラフの高さ min 縦軸の最小値 max 縦軸の最大値 xmin 横軸の最小値 xmax 横軸の最大値 colors グラフの色(配列で複数のカラーコードを選択できる) xtitle X軸(横軸)の軸タイトル ytitle Y軸(縦軸)の軸タイトル legend 凡例を表示させるかどうか、表示させる場所を指定できる(top,bottom,left,right) prefix 接頭辞($や¥など、値の前に単位を付けたい場合に使う) suffix 接頭辞(%や℃など、値の後ろに単位を付けたい場合に使う) thousands 数値を3桁ごとに区切る場合に使う(1000→1,000) decimal 小数点を設定する precidion 有効数字を設定する round 値の丸めを設定する bytes 値がキロバイト換算で表示される loading データをロードしている時に表示させるメッセージを指定できる empty データが空である時に表示させるメッセージを指定できる refresh データリフレッシュのタイミング(秒)を指定できる このあたりのオプションは未検証ですので、知っているかたは教えてくださいm(_ _)m id、stacked、discrete、label、curve、points、donut、zeros等々 参考文献 https://chartkick.com/ https://qiita.com/Y_uuu/items/0d57748954c7cdb9bbcb https://wonderwall.hatenablog.com/entry/2015/08/22/225258
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

activesupport::messageverifier::invalidsignature

問題 ActiveStorageを実装したら activesupport::messageverifier::invalidsignature と言うエラーがでる様になった 解決方法 部分テンプレートの見直し 解決手順 createアクションでエラーが起きていたのでbinding.pryでエラー内容を確認してみると imageも他のカラムと同じ様に情報が送られていた 本来であればActiveStorageのテーブルに沿って情報が送られるべき ・controllerのストロングパラメーターに:imageの追加 ・modelにhas_one_attached :imageの追加 ・f.file_filedでフォームの追加 原因はどこに・・・ 確認していると部分テンプレートにした部分のformが重複していることに気付く!!! 通常のデータの保存はできていたが、ActiveStorageが絡むことでエラーが起きるようになっていた!!! 記述を修正して解決!!! ActiveStorage自体の実装自体はきちんと出来ていたが、それが起因して他でエラーが起きていた感じ!!! 原因の発見にかなり手間取りました・・・
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsコードリーデイング② validatesメソッドを深掘りしてみた 【備忘録】

はじめに https://qiita.com/engineer_ikuzou/items/e78fe4d5b4977bbe86c2 こちらの続編です。 前提 名前の長さ30文字制限をかけています。 u = User.new(name: 'sample name') app/models/user.rb class User < ApplicationRecord binding.pry validates :name, length: { maximum: 30 } end validatesメソッド2行目から見ていきます。 rails/activemodel/lib/active_model/validations/validates.rb def validates(*attributes) defaults = attributes.extract_options!.dup validations = defaults.slice!(*_validates_default_keys) defaultsには前回確認した値がは格納されています。 未確認の引数_validates_default_keysが出てきたので、確認してみると、ハッシュで条件文が格納されていました。 https://railsguides.jp/active_record_validations.html 条件付きvalidationの箇所と、そうでない部分を分けるための処理のようです。 [4] pry(User)> defaults => {:length=>{:maximum=>30}} [5] pry(User)> _validates_default_keys => [:if, :unless, :on, :allow_blank, :allow_nil, :strict] validations: dafaultsでsliceの対象外のhashを格納 dafaults: sliceの対象となったifなどがあれば格納 上記のように格納される処理でした。 学び① sliceメソッド 引数で指定されたキーとその値だけを含む Hash を返します。 A=[ a:100, b:300, c:500 ]という配列に対して、A.slice(:a)は{:a=>100}を返します。 https://docs.ruby-lang.org/ja/latest/method/Hash/i/slice.html また、全く意識していなかったのですが破壊的メソッド自体も定義されており、その中でsliceを使用しているのですね。 少し考えたらわかることだと思うのですが、意識できていませんでした。 また破壊的メソッドはそれ自体を更新する一方で、返り値としては更新されなかった部分を返してくれるのですね。 From: rails/activesupport/lib/active_support/core_ext/hash/slice.rb:11 Hash#slice!: 10: def slice!(*keys) => 11: omit = slice(*self.keys - keys) 12: hash = slice(*keys) 13: hash.default = default 14: hash.default_proc = default_proc if default_proc 15: replace(hash) 16: omit 17: end 10: omitに、引数keysを除くhashがあれば避けておき、返り値とする。 15: hashはこちらで更新。 validatesメソッド3,4行目 rails/activemodel/lib/active_model/validations/validates.rb raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? raise ArgumentError, "You need to supply at least one validation" if validations.empty? [:if, :unless, :on, :allow_blank, :allow_nil, :strict]に該当するもののみvalidatesの引数にある場合は、エラーを起こします。 条件付きvalidatesの場合、条件true時のvalidationを記載する必要があるため、記載があるかどうかチェックしています。 validatesメソッド 5,6行目 rails/activemodel/lib/active_model/validations/validates.rb validations.each do |key, options| key = "#{key.to_s.camelize}Validator" validations => {:length=>{:maximum=>30}} なのでkeyは:length, optionsは{:maximum=>30}です。 キャメルケースへの変換が行われ、keyは、LengthValidatorとなります。 学び② camelizeメソッド デフォルトでアッパーキャメルケースに変換します。 https://www.rubydoc.info/gems/activesupport/String:camelize validatesメソッド 7~11行目 rails/activemodel/lib/active_model/validations/validates.rb begin validator = key.include?("::") ? key.constantize : const_get(key) rescue NameError raise ArgumentError, "Unknown validator: '#{key}'" end validatorが既にモジュールの形で表記されているかで条件式が分かれています。 今回は、モジュール形式ではないので、const_getメソッドでActiveRecord::Validations::LengthValidatorが返ります。 学び③ constantizeメソッド 引数の文字列で指定した名前で定数を探す。 https://railsdoc.com/page/constantize 学び④ const_getメソッド name で指定される名前の定数の値を取り出します。 https://docs.ruby-lang.org/ja/latest/method/Module/i/const_get.html  終わりに 今回はここまでです。 メソッドもそれぞれどのような挙動をしているのか確認することもできるのですが、そこの深掘りはしませんでした。あくまでvalidatesメソッドの深掘りです。全てを読み込むと永遠に終わりのない勉強なので、どこをどこまで深掘りするか、学習の目的をしっかり意識することが大事だと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] Rspecでテストコードの実装②

 はじめに テストコードの実装①をみていない方はこちらからご覧ください。 基本的なテストコードの書き方 まずは、どのような形でテストコードを書いていくのか確認です。 Userモデルにはnameカラムとemailカラムがあるとします。 spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do describe 'ユーザー新規登録' do context '登録ができるとき' do    it '全ての項目が正しく入力されている時' do  #その処理を記述    end   end context '登録ができないとき' do    it 'nameが空では登録できない' do # nameが空では登録できないテストコードを記述します  end  it 'emailが空では登録できない' do # emailが空では登録できないテストコードを記述します  end    end end end ひとつづつ確認しましょう。 describe describeとは、テストコードのグループ分けを行うメソッドです。 「どの機能に対してのテストを行うか」をdescribeでグループ分けします。 describeにつづくdo~endの間に、さらにdescribeメソッドを記述することで、入れ子構造をとることもできます。 context 特定の条件を分けたい場合に用います。contextはどのような「状況」を確認したいのかを書きます。 it describeメソッド同様に、グループ分けを行うメソッドです。 itの場合はより詳細に、「describeメソッドに記述した機能において、どのような状況のテストを行うか」を明記します。contextよりもさらに詳細なものを記述します。 example exampleとは、itで分けたグループのことを指します。 以上のような形で、どの機能のどんな状況のときにテストコードが実行されるのか、わかりやすい形でグループ分けしてやります。 テストコードの具体的な実装(正常系) それではグループ分けしたのちに、処理を実際に記述していきます。 spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do describe 'ユーザー新規登録' do before do @user=User.new(name:"tochi",email:"abc@abc") end   context '登録ができるとき' do    it '全ての項目が正しく入力されている時' do  expect(@user).to be_valid    end   end #中略 end end こちらも1つづ解説します。 before do~end これはコントローラーでいうbefore_actionと同様で、予め処理を走らす前に行いたい処理を記述します。今回は@userというインスタンスを作成しておきます。 expect 検証で得られた挙動が想定通りなのかを確認する構文のことです。 expect().to matcher()という基本的な形をもとにに、テストの内容に応じて引数やmatcherを随時変えて記述します。のちに出てくるinclude等がこれにあたります。 matcher(参考) matcherは、「expectの引数」と「想定した挙動」が一致しているかどうかを判断します。 be_valid インスタンスが正しく保存されるかどうか判断します 以上を踏まえると、以下の一文は、@userがちゃんとDBに保存されたかどうかを確認します。 expect(@user).to be_valid テストコードの具体的な実装(異常系) だんだん慣れてきたところで、異常系についてもさらっと触れておきましょう。 spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do describe 'ユーザー新規登録' do #省略 context '登録ができないとき' do    it 'nameが空では登録できない' do @user.name="" @uer.valid? expect(@user.errors.full_messages).to include("名前を入力してください")  end  it 'emailが空では登録できない' do # 自分で考えてみよう!  end    end end end 新しく出てきたものがあるので解説します。 valid? valid?は、バリデーションを実行させて、エラーがあるかどうかを判断するメソッドです。 エラーがない場合はtrueを、ある場合はfalseを返します。 これを実施しないと、errorsが発生しないため、エラー文を引き出すことができません。 errors errorsは、インスタンスにエラーを示す情報がある場合、その内容を返すメソッド full_messages full_messagesは、エラーの内容から、エラーメッセージを配列として取り出すメソッド。だいたい、errorsとセットで使います。 include includeは、「expectの引数」に「includeの引数」が含まれていることを確認するマッチャです。 以上より、以下の処理は、nameカラムが空だったとき、エラーがあるかどうか確認して、発生したエラー文に「名前を入力してください」と出るかどうかを確認しているということになります。 @user.name="" @uer.valid? expect(@user.errors.full_messages).to include("名前を入力してください") ちなみに、エラー文は日本語になっていないことがあるかと思いますので、どんなエラー文が出るかはbinding.pryで止めて確認してください。 終わりに お疲れ様でした。今回、簡単にモデルの単体テストについてまとめてみました。いかがでしたか。 やはり、実際に手を動かしてやってみることが一番いいと思いますので、是非やってみてください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Reat × Rails(api)、フロントとバックエンドを分けるとはどういう事か理解する】

実務未経験の自分が、 現在、Rails(api)とReact.jsでポートフォリオを作成中ですが、 このようなフロントエンドとバックエンドを分けて開発する事の概念的な部分に関して、 イメージがしづらかったので、アウトプットとして整理しておきます。 ※めちゃくちゃ初心者向けの内容になるかと思います。 そもそもフロントエンドとバックエンドを分けるってどういう事?? 凄く簡単に言うと、Railsにおけるviewに当たる部分を、別のフレームワークを使って開発すると理解しています。 Railsでアプリを何個か作成していた為、いわゆるviewにあたるファイルが、〇〇.erbという形式で作成しておりました。 しかし、Rails apiモードではviewというディレクトリはありませんし、.erbというファイルも作成しません。 ※正確にはviewディレクトリはありますが、ここでuserとかpostだったりのディレクトリは作成しません。 一般的には、このviewというディレクトリの代わりに、front又はfrontendというディレクトリでReactやVueを使って作成していきます。 ※viewの代わりにという言い方は語弊あるかもしれませんが、このような理解で良いかと思います。 viewの部分を置き換えるって何?? 当たり前ですが、viewの部分が担っていた役割を、そのままReactやVueが行っていく事になります。 何が変わるのかというと、 基本的にページ遷移した際の画面をリロードする必要が無くなり、 ユーザー(クライアント)側の使い勝手が良くなります。 ※この辺はこちらの記事を読むと良いです。 今さら聞けない!シングルページアプリケーションとは ディレクトリ構造的には、こんな感じになります。 ※frontendのディレクトリ構造は、ReactやVueのチュートリアルや記事を見て下さい。 Railsにおけるviewの主な役割って何だったっけ? ブラウザ画面の描画 何らかのリクエストを投げて、返ってきた値を受け取る 例えば、下記のような記述があった場合 view/user/show.html.erb <p>名前</p> <%= @user.name %> config/routes.rb resources :users, only[:show] controllers/users_controller.rb @user = User.find(params[:id]) まず、 show.html/erbから@userの情報を下さい!というリクエストをresources :user, only[:show]というルーティング宛に送ります。 ※どのルーティングが判別するかはRailsが判断してくれています。 次に、 routes.rbがuser/show.html.erbから来たリクエストは、どのコントローラーのどのアクションに渡すべきか判別します。 最後に controllers/users_controller.rbが受け取ったリクエストを元にUserモデルから該当userを探して、レスポンスを返す という流れになっているかと思います。 フロントエンドとバックエンドを分けての開発の場合は、 このview/user/show.html.erbが@userの情報を下さい!というリクエストを投げる行為を、ReactやらVueやらで行っていきます。 ちなみに、このリクエストを投げる行為の事をapiを叩くと言ったりしています。 ※今回の例はGETリクエストですが。 フロントを分けるとどういう動きになるの?? 実際の記述は下記のようになります。 かなり省略して書いておりますが、流れを掴めれば良いと思っているので、ご容赦下さい。 UserShow.jsx get(lokalhost3000/users/:id) ... return( {user.name} ) ※jsxとはReact.jsのファイル端子ですが、Railsでいうviewにあたる部分と思って下さい。 routes.rb resources :users, only[:show] users_controller.rb def show @user = User.find(params[:id]) render json: { user: @user } end 流れの簡単な説明 ルーティングを指定してGETリクエストを投げる まず、フロントエンドのviewからRailsに向けてuserの情報を下さいとgetリクエストを投げています。 ※jsxの記述はかなりハショッております。 Railsでは、@user.nameと記述すれば勝手にRailsがリクエストを投げて値を返すところまで、やってくれていましたが、 フロントを分ける場合は、このようにリクエストを投げる処理を自分で実装しなければなりません。 ※localhost3000/users/:idはrails routesに記載されているルーティングの事です。 また、Railsでよく使っていた、user_pathのようなルーティング指定も使いません。 ※使う方法もあるのかもしれませんが、自分は知りません。。 ルーティングが実行すべきコントローラーを判断する ここはRails単体の動きとなんら変わらないので、パスします。 コントローラーがリクエストを受け取って値を返す 見慣れない書き方が、render json: { user: @user }こちらかと思います。 Rails単体の場合は、@user = User.find(params[:id])だけでも良いのですが、 フロントを分ける場合は、この@userの値をjson形式というデータ形式に変えて渡す必要があります。 jsonとは、簡単に言うとJavascriptのデータ形式と思って頂ければ良いかなと。 詳しく知りたい方は下記を参照下さい。 https://products.sint.co.jp/topsic/blog/json なぜ、こんな形式に変換しないといけない? 変換をしないままでは、rubyのデータ形式のままでReactやVueはJavaScriptで書くので、 rubyのデータ形式では対応出来ないからです。 Rails単体の場合は、.erbもrubyを元に作成される為、そのままでも対応出来るという事です。 最後に 自分用のアウトプットとはいえ、かなり煩雑に書いてしまったので、 指摘や分かりづらい点があれば、どんどんご指摘下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsで通知機能とその未読管理機能を開発

やりたいこと rails 通知機能 通知の種類は色々増える可能性あり 通知の未読管理 通知一覧を開いた時点で開封済みとする。もし未読のものがあればラベルをつける 仕様 全体の流れ notificationテーブルを作成し、そこで通知情報と未読管理を行う indexを開いたらopen_timeがnilのものを取得し、現在時刻を入れる headerのアイコンを未読(open_timeがnil)ありだったらバッジをつけ、なしだったらバッジをつけない 通知のscheme scheme.rb create_table "notifications", force: :cascade do |t| t.text "url" t.integer "to_user_id" t.integer "from_user_id" t.integer "notification_type" t.datetime "open_time" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end open_timeに開いた時刻を入れてここがnilなら未読とする 通知内容は最初contentカラムを設けてそのまま文言を入れるか迷ったが時間が経ってユーザー情報などが変わった際に変更が効かないのと中の情報に何が入っているのか将来分からなくなるのが嫌だったので変数を取得するように urlで飛び先を指定する。もし特殊なメソッドを使いたい場合はroutesでメソッドを指定する 一覧を開いたらopen_timeに時間を入れる app/controllers/notifications_controller.rb unread_notifications = @notifications.where(open_time: nil) unread_notifications.each do |unread_notification| unread_notification.open_time = Date.today.to_time unread_notification.save end 上記繰り返し処理で現在時刻を入れていく headerにて未読がある場合とアイコンを出しわけ app/controllers/application_controller.rb @notification_count = Notification.where(to_user_id: current_user.id,open_time: nil).count 上記で未読数を取得 app/views/layouts/_header.html.erb <div class="mr-4"> <% if @notification_count > 0 %> <%= image_tag('notification_icon_unread.png',class: "header_icon align-middle") %> <% else %> <%= image_tag('notification_icon.png',class: "header_icon align-middle") %> <% end %> </div> 0より上だったらバッジなし
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CapybaraによるSystem specで使用したコマンド振り返り

この記事について 私が作成したテストを元に、使用したCapybaraのメソッドを解説します。 Capybaraの機能一覧を知りたい場合は下記を参考にすると良いと思います Capybaraチートシート rubydoc Module: Capybara::DSL 環境 Ruby 2.6.5 Rails 6.0.3.5 devise Capybara Capybaraとは? 統合テストを行う際に用いられるWeb上の操作を簡単に実行できるDSLを提供してくれるGemです。 実装内容 context 'コメントを削除できるとき' do it 'ログインしておりコメントを投稿したユーザーだと削除できる' do visit new_user_session_path #1 fill_in 'user_email', with: comment_user.email #2 fill_in 'user_password', with: comment_user.password find('input[name="commit"]').click #3 expect(current_path).to eq(root_path) click_link 'テストタイトル', href: article_path(article) #4 expect(current_path).to eq(article_path(article)) expect(page).to have_link '削除', href: article_comment_path(article, comment) page.accept_confirm('本当に削除しますか?') do #5 click_link '削除', href: article_comment_path(article, comment) end expect(current_path).to eq(article_path(comment.article)) expect(page).to have_content('コメントを削除しました') end end #1 visit visit以降に設定したパスへ移動し、この際のリクエストは必ずGETになります。 今回はgemのdeviseを導入して作成されたログインページへアクセスしています。 #2 fill_in これはtext_fieldやtext_areaなどの入力を必要とする要素に指定した値を挿入するメソッドです 今回の例では'user_email'という属性値を持った要素に、事前に準備したユーザのEメールアドレスを指定しています。 with:はStringであれば良いので以下の様に直接打ち込んでも問題ありません。 fill_in 'user_email', with: 'test@test.mail' #3 find, click まずfindですがこれは指定した要素を見つけるメソッドです そしてclickは文字通りクリックを行うメソッドです findで要素を探して、clickでクリックするという動作になります #4 click_link これは指定した要素を持つリンクをクリックするメソッドです。 今回はテストタイトルという要素を持ったリンク要素をクリックしています。 linkと指定しているだけあってリンクでなければクリックできません。 リンクかボタンか判断に困ったらclick_onを使うのも手です。 これはどちらの要素であってもクリックを行なってくれます。 #5 accept_confirm 大トリです。これはHTML5のHTMLに独自にデータを持たせるデータ属性の一つであるdata-confirm属性の判定に用います。 まずdata-confirm属性ですが、これはaタグやformタグのsubmitボタンの属性に追加することでダイアログを表示してくれるものです accept_confirmですがこれはダイアログを認可つまりOKという判定を行なってくれるメソッドです。 又引数にダイアログに表示される文字を入力する事で、data-confirmで指定した文言と、テストで判定したい文言を照らし合わせて判定してくれます。 記述は以下の様にaccept_confirmに引数を渡し、又ブロック変数でdata-confirmを発生させる要素を指定します。 page.accept_confirm('本当に削除しますか?') do #5 click_link '削除', href: article_comment_path(article, comment) # <-data-confirmを持った要素 end まとめ 便利なGemですがその分覚えることも増えるため、自分のアプリ作成での計画性に合わせて学習していきたいところです。 テストも時間をかけ過ぎて他が疎かになったら、本末転倒ですからね。勿論テストは重要な部分ではありますが。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでorするとき、atter = nil時に不要な sqlを節約する (手元では400倍早くなった)

この記事は備忘録です DB(例) (id, first_name, last_name, email) 1 しのぶ 胡蝶 aaa@aa.com 2 杏寿郎 煉獄 bbb@bb.com 3 義勇 冨岡 ccc@cc.com 4 小芭内 伊黒 ddd@dd.com 5 天元 宇髄 eee@ee.com 6 蜜璃 甘露寺 fff@ff.com モデル(例) # User.rb # id :int # first_name :string # last_name :string # email :string first_name_attr = "しのぶ" last_name_attr = nil email_attr = "fff@ff.com" User.where(first_name: first_name_attr) .or( User.where(last_name: last_name_attr) ) .or( User.where(email: email_attr) ) この場合、last_nameのところ、いらないっすよね〜でもこれって、sql的に SELECT COUNT(*) FROM `users` WHERE (`users`.`first_name` = 'しのぶ' OR `users`.`last_name` IS NULL ... ) となるんですけど、orなのに IS NULL って処理いらないなって。(whereでandとかになるならもちろんいる) そして、この処理が結構重いので、railsの仕組みを使って軽くしよう、というのが今回の目的です。 (※ まあこれは実践的ではないかもしれないのでメンバーと要相談ということでお願いいたします。。) 結論 nilだと IS NULL の処理が走ってしまうので[]にする。 last_name = (last_name == nil ? [] : last_name) nilの場合は[]に変換することで SELECT COUNT(*) FROM `users` WHERE (`users`.`first_name` = 'しのぶ' OR 1=0 ... ) なって処理を飛ばすことができ、大幅に早くなります。 もし開発する上で、表示するとき遅いし、一旦仮で早くしたいとかいう事情がある場合には結構有効かなと思います。 ただし、上記にもあるように、 「実践的ではないかも。。」「ちょっと気持ち悪いな〜」 みたいな意見があり、この実装は見送られておりますのでご注意を。 おそらく、シンプルにifで分岐する方がいいのかなという意見です。 ただし、sqlは一つに纏まるような記述が必要そうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails ヘルスケア ITの普及に向けて サービス開発備忘録 RSpec テスト~model spec

ずっと、サボっていましたが、技術力向上のためには、メモがてら、書き記すことが必要と思い、再開です。 言語化してアウトプットすることで、メタ的に認知し、結果、理解度が上がるんだろうか? model specである。 こちらを参考にさせていただいた。 (https://qiita.com/Jwataru/items/2cac90cc9bca4f76bb11) 13)が通らない patient.email = "Foo@ExAMPle.CoM"としているが、 下記のように出る。 1) Patient emailを小文字に変換後の値と大文字を混ぜて登録されたアドレスは 13) 同じ Failure/Error: expect(patient.reload.email).to eq "foo@example.com" expected: "foo@example.com" got: "test11@example.com" (compared using ==) # ./spec/models/patient_spec.rb:120:in `block (3 levels) in <top (required)>' FactoryBotのemailとなっている。 上書きされていないのか? 15)が通らない 2) Patient passwordとpassward_confirmationの一致 passwordとpassward_confirmationは 15) 不一致 Failure/Error: expect.(patient.errors[:password_confirmation]).to include("の入力が一致しません") ArgumentError: You must pass either an argument or a block to `expect`. # ./spec/models/patient_spec.rb:136:in `block (4 levels) in <top (required)>'``` patient_spec.rb require 'rails_helper' RSpec.describe Patient, type: :model do # FactoryBot patient let(:patient) { create(:patient) } describe '存在性' do context 'patientは' do it '1) 有効である' do expect(patient).to be_valid end end context 'nameが空白は' do it '2) 無効' do patient.patient_name = " " expect(patient).to be_invalid end end context "emailが空白は" do it '3) 無効' do patient.email = " " expect(patient).to be_invalid end end context "password空白は" do it '4) 無効' do patient.password = patient.password_confirmation = " " * 6 expect(patient).to be_invalid end end end describe '長さ' do context "name17文字以上は" do it '5) 無効' do patient.patient_name = "a" * 17 expect(patient).to be_invalid end end context "emailが256字以上" do it '6) 無効' do patient.email = "a" * 244 + "@example.com" expect(patient).to be_invalid end end context "emailが255字以下は" do it '7) 無効' do patient.email = "a" * 243 + "@example.com" expect(patient).to be_valid end end context "password5字以下は" do it '8) 無効' do patient.password = patient.password_confirmation = "a" * 5 expect(patient).to be_invalid end end context "password 6字以上は" do it '9) 有効' do patient.password = patient.password_confirmation = "a" * 6 expect(patient).to be_valid end end end # describe 'フォーマット' do # # context "emailは規定の表記でなければ" do # it '無効' do # invalid_addresses = %wpatient@example,com foo@bar..com user_at_foo.orgpatient.name@example. foo@bar_baz.com foo@bar+baz.com] # invalid_addresses.each do |invalid_address| # patient.email = invalid_address # expect(patient).to be_invalid,"#{invalid_address.inspect}が無効ではありません" # end # end # end # # context "emailは規定の表記であれば" do # it '有効' do # valid_addresses = %wpatient@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn] # valid_addresses.each do |valid_address| # patient.email = valid_address # expect(patient).to be_valid, "#{valid_address.inspect} が有効ではありません" # end # end # end # # end describe '一意性' do context "emailの重複は" do it '12) 無効' do duplicate_user = patient.dup duplicate_user.email = patient.email.upcase patient.save! expect(duplicate_user).to be_invalid end end end context "emailを小文字に変換後の値と大文字を混ぜて登録されたアドレスは" do it '13) 同じ' do patient.email = "Foo@ExAMPle.CoM" patient.save! #全て小文字のemailと等しいかのテスト expect(patient.reload.email).to eq "foo@example.com" end end describe 'passwordとpassward_confirmationの一致' do context 'passwordとpassward_confirmationは' do it "14) 一致" do expect(patient).to be_valid end it "15) 不一致" do patient.password ="password" patient.password_confirmation = "different" patient.valid? expect.(patient.errors[:password_confirmation]).to include("の入力が一致しません") end end end end factory/patients.rb FactoryBot.define do factory :patient do #factory :testuser, class: User do のようにクラスを明示すればモデル名以外のデータも作れます。 patient_name { "山田花子" } sequence(:email) { |n| "TEST#{n}@example.com" } password { "abcder" } password_confirmation { "abcder" } birth_date { "1967-01-01" } sex { "woman" } end end model/patients.rb class Patient < ApplicationRecord # 以下コメントアウト 登録時は患者がclinicに所属していないから(import時にclinic_idが付与される) # belongs_to :clinic has_many :clinics, through: :clinic_patients has_many :clinic_patients has_many :tests #viewに男女を表示 enum sex: { man: 1, woman: 2 } default_scope { where(leave_at: nil) } #devise設定 devise :database_authenticatable, :registerable, :validatable, :recoverable, :rememberable, :confirmable, :lockable, :timeoutable, :trackable # validation validates :patient_name, { presence: true, length: { maximum: 16 } } # format: { # with: /\A[A-Za-z0-9]+\z/, # message: 'は小文字英数字で入力してください' # } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, { presence: true, confirmation: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } } validates :sex, { presence: true } validates :birth_date, { presence: true } end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向とは

オブジェクト指向のメリット 役割ごとにオブジェクトを分けることで、実装がしやすくなる。 役割ごとにオブジェクトを分けることで、あとからコードを改変するときも、他のオブジェクトに影響しなくなる(コードの改変がしやすくなる) オブジェクトとは、Rubyにおけるクラスやインスタンス、その他の値のことを意味します。例えば、文字列や数値、配列やハッシュなど、これまで値と呼んできたものも全てオブジェクトです。この他にも、Rubyでは様々な便利な特徴を持ったオブジェクトが用意されていて、組み込みライブラリという形やGemという形で使用できます。 クラスに含まれる値や処理のまとまり(= オブジェクト)を意識しながら、実装することがオブジェクト指向です。 オブジェクト指向はあくまで設計思想の1つ オブジェクトを綺麗に切り分けなくてもとりあえず動くアプリケーションは完成してしまった 最初のアプリケーション設計から仕様変更が生じて、オブジェクトの切り分けを見直す必要がでてきてしまった AさんとBさんの考えるアプリケーション設計が異なる ということはよくあります。 オブジェクト指向を意識しないデメリット オブジェクト内にさまざまな役割が入り組んでいて、どこに何があるのかわからない 1つの機能を変えた途端、その変更とは関係のないはずの機能が動かなくなってしまった このような問題が生じるコードは、複雑に絡み合うことから「スパゲッティコード」と呼ばれます。 Ruby on Railsにおけるオブジェクト指向 オブジェクトとして存在することは、オブジェクト指向に従えば 「ある役割」 をそれぞれ持っている事になる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】お気に入り済みを一覧表示する

目的 お気に入り済みの商品を一覧表示する機能を実装する 前提条件 既にお気に入り機能を実装済みであること itemsテーブル(商品情報)、usersテーブル(ユーザー情報)、bookmarksテーブル(お気に入り情報)が存在すること。 各テーブルとアソシエーションが組まれていること 手順 1.ルーティングのネスト 2.Usersコントローラーを定義 3.お気に入り済みをビューファイルに表示 1.ルーティングのネスト ユーザーがお気に入りをした商品を表示するために、usersコントローラーにbookmarksコントローラーをネスト(入れ子構造)します。 routes.rb resources :users, only: [:show] do get :bookmarks, on: :collection end 2.Usersコントローラーを定義 Bookmarkモデルからwhereメソッドとpluckメソッドを使い現在ログイン中のuser_idとitem_idを引き出してbookmarksに代入します。 Itemモデルの引数に先ほど定義したbookmarksを指定して、インスタンス変数@bookmark_listに代入します。 users_controller.rb def show @user = User.find(params[:id]) @items = @user.items bookmarks = Bookmark.where(user_id: current_user.id).pluck(:item_id) @bookmark_list = Item.find(bookmarks) pluckの補足情報 https://railsdoc.com/page/model_pluck 3.お気に入り済みをビューファイルに表示 ユーザーのマイページにusersコントローラーで定義した@bookmark_listを使用して、お気に入りした商品を表示していきます。 if文とpresent?メソッドを使い、@bookmark_listにデータが保存されている場合に実行する様にします。 今回は商品の名前と画像を表示するように記述しました。 users/show.html.erb <% if @bookmark_list.present? %> <% @bookmark_list.each do |item| %> <%= item.name %> <%= image_tag item.image%> <% end %> <% end %> さいごに お気に入り一覧表示機能を実装出来たので投稿しました。 最後まで読んで頂きありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クラスの作りかたからオブジェクト指向へ

クラスをどのように作るのか やクラスをどのように活かすのかといった考え方を学びます。今後のWebアプリケーション実装において重要な考え方について紹介します。 No 目的 (1) アプリケーション実装における、クラスの作り方を学ぶこと (2) 複数のクラスが絡みあうアプリケーションの実装を理解すること 飲み物の自販機アプリケーションから学びます。 商品を補充し、→商品を選択、→お金を投入し、→購入のサイクルを考えます。 ファイルを用意しよう 実装に使用するディレクトリとRubyのファイルを用意しましょう。 ruby_application_training application.rb アプリケーション実装 用意すべきクラスを考える アプリケーションを実装する前に、どのようなクラスを用意すべきなのか=このアプリケーションにおいて、どのような処理が存在するのか? No 処理 クラス名 1 商品のデータを作成 Drink 2 ユーザーに販売している商品一覧表示 VendingMachine 3 購入したい商品決定 User 4 お金を投入 User 5 投入金額からお釣りの計算をする(購入処理) Purchase クラス 意味 何をするのか Drink 販売する飲み物 飲み物を実態のあるデータ(インスタンス)に VendingMachine 自販機 ユーザーに販売している商品を見せて、投入金額からお釣り計算 User 購入する人 購入したい商品を決めて、お金を投入する このアプリケーションには、このように少なくとも3つのクラスが必要ということになります。 「自販機アプリケーションなので、自販機クラスだけ用意すればいいのでは?」と考えましたが、その場合、「自販機が行うべきでないこと」も、自販機のクラスに含まれてしまうことになります。 単一責任の原則 アプリケーションの設計を考える上で、必要となる決まりの1つです。上記の自販機アプリケーションでは、自販機・飲み物・購入する人の役割が1つ程度になるように分割されています。このように、「1つのクラスは1つの振る舞いしか持たない」という原則を意識しないと、他者からみた時に意図のわからないアプリケーションが完成します。 application.rbに必要なクラスを定義 ruby_application_training/application.rb class Drink end class VendingMachine end class User end クラスごとの処理を記述 商品が登録(商品となる飲み物のデータを生成) ruby_application_training/application.rb class Drink def initialize(name, fee) @name = name @fee = fee end def name @name end def fee @fee end end class VendingMachine end class User end puts "商品を用意してください。" drinks = [] #配列の空箱を用意 3.times do |i| puts "商品名を入力してください。" drink_name = gets.chomp puts "金額を入力してください。" drink_fee = gets.to_i drinks << Drink.new(drink_name,drink_fee) #<<は配列の要素追加 end puts drinks 着目point Drink.new(drink_name,drink_fee)として、Drinkクラスのインスタンスを生成。実引数として渡しているのは、飲み物の名称と金額です。Drinkクラス内では、与えられた値をもとに、インスタンス変数(@name,@fee)を生成。*drinks << Drink.new(drink_name,drink_fee)の記述で、用意した配列drinksに生成したインスタンスを追加 3つのデータが出力すれば成功です。これはDrinkクラスから生成したインスタンス(飲み物のデータ)です。3.timesを用いて3回入力しているので、3つのデータが生成。 商品を表示 商品の登録ができたので、それらをユーザーが購入できるように表示。商品の情報を表示するのはVendingMachineクラスの役割。(show) ruby_application_training/application.rb class Drink def initialize(name, fee) @name = name @fee = fee end def name @name #商品名表示 end def fee @fee end end class VendingMachine def initialize(drinks) @drinks = drinks end def drinks @drinks end def show_drinks puts "いらっしゃいませ。以下の商品を販売しています" i = 0 self.drinks.each do |drink| puts "【#{i}】#{drink.name}: #{drink.fee}円" i += 1 end end end class User end puts "商品を用意してください。" drinks = [] 3.times do |i| puts "商品名を入力してください。" drink_name = gets.chomp puts "金額を入力してください。" drink_fee = gets.to_i drinks << Drink.new(drink_name,drink_fee) end vending_machine = VendingMachine.new(drinks) vending_machine.show_drinks 着目point vending_machine = VendingMachine.new(drinks)でVendingMachineクラスのインスタンスを生成す。インスタンス生成の際、3つの商品情報を含む配列drinksを、実引数として渡す。initializeメソッドで生成した@drinksには、受け取った配列の情報が代入。 vending_machine.show_drinksで生成したインスタンスに対してshow_drinksメソッドを適用。show_drinksメソッドは、@drinksに含まれる配列の情報を1つずつ表示しています。self.drinks.each do |drink|のselfは、show_drinksメソッドを適用されたインスタンス自身が、同じクラス内で定義したインスタンスメソッドdrinksを利用することを宣言。なお、このタイミングで1つ前のステップで記述したputs drinksは削除。 self Rubyのインスタンスメソッド内でのselfという記述は特別。selfが書かれているインスタンスメソッドを適用したインスタンス自身が代入されている変数だと考える。VendingMachineクラスのインスタンスが代入されています。 puts "【#{i}】#{drink.name}: #{drink.fee}円"という記述は、Drinkクラスのインスタンスがインスタンスメソッド「name」と「fee」を利用しているます。この2つのメソッドの中身はそれぞれ@nameと@feeのみで、つまり自身のインスタンス変数の値を、戻り値とします。インスタンス変数の値のみを戻り値としたメソッドのことを特別にゲッターと呼ぶ。 ゲッター ー クラスに設定したインスタンス変数の値を、インスタンスから読み取って表示するためだけに定義するメソッドです。 上のdrinkクラスのように、インスタンスを生成してそれぞれ別々の値をインスタンス変数に定義し、あとでその情報を利用することはよくあります。なので、クラスを定義する際はゲッターを用意することがほとんどです。 セッター あるインスタンスが持つインスタンス変数の値を更新するためだけのメソッドのことをセッターと呼ぶ。具体的なセッターの定義例を以下に明示。 【例】sample.rb class Human #humanクラスのインスタンス変数は@name, @ageとする。 def initialize puts "名前を入力してください" @name = gets.chomp puts "年齢を入力してください" @age = gets.to_i end #ゲッター def name @name end def age @age end #セッター def name=(set) @name = set end def age=(set) @age = set end end セッターのメソッド名は必ずdef インスタンス変数名=とします。=が名前に含まれているのがポイント。 【例】sample.rb human = Human.new #initializeメソッドで@nameに"takashi", @ageに25を代入したとする #ゲッターでインスタンス変数の値を確認 puts human.name #=> "takashi" #出力値 puts human.age #=> 25     #出力値 #セッターを利用して、インスタンス変数の値を書き換える human.name = "satoshi" human.age = 20 #再びゲッターでインスタンス変数の値を確認 puts human.name #=> "satoshi" puts human.age #=> 20  セッターの使い方は少し特殊。実はRubyには、「名前に=がついたメソッドの呼び出しの際のメソッド名=の前後はいくら半角スペースを空けても構わない」という仕様と、「引数の()は省略できる」という仕様があります。これを利用して、あたかも変数代入かのようにセッターを利用します。  上の例では、human.nameという変数に"satoshi"を再代入しているように感じます。しかし実態はhuman.name=("satoshi")となっており、name=という名前に=を含むメソッドが定義され引数を受け取って、メソッド内でインスタンス変数に再代入しています。 投入金額(商品購入) 登録した商品を表示するところまでできました。続いて、表示された商品から、自販機に投入する金額を決定します。投入金額を決めるのは購入者の役割なので、Userクラスに記述。 ruby_application_training/application.rb class Drink def initialize(name, fee) @name = name @fee = fee end def name @name end def fee @fee end end class VendingMachine def initialize(drinks) @drinks = drinks end def drinks @drinks end def show_drinks puts "いらっしゃいませ。以下の商品を販売しています" i = 0 self.drinks.each do |drink| puts "【#{i}】#{drink.name}: #{drink.fee}円" i += 1 end end end class User def initialize(money) @money = money end def money @money end end puts "商品を用意してください。" drinks = [] 3.times do |i| puts "商品名を入力してください。" drink_name = gets.chomp puts "金額を入力してください。" drink_fee = gets.to_i drinks << Drink.new(drink_name,drink_fee) end vending_machine = VendingMachine.new(drinks) vending_machine.show_drinks puts "あなたはお客さんです。投入金額を決めてください。" money = gets.to_i user = User.new(money) 着目point user = User.new(money)で、Userクラスのインスタンスを生成して変数userに代入。 選んだ商品を購入 投入金額が足りない場合は、「投入金額が足りません」と表示。 ポイントは、「購入する商品の選択は購入者が行い、決済は自販機が行う」という点。UserクラスとVendingMachineクラスそれぞれに処理を記述します。 ruby_application_training/application.rb class Drink def initialize(name, fee) @name = name @fee = fee end def name @name end def fee @fee end end class VendingMachine def initialize(drinks) @drinks = drinks end def drinks @drinks end def show_drinks puts "いらっしゃいませ。以下の商品を販売しています" i = 0 self.drinks.each do |drink| puts "【#{i}】#{drink.name}: #{drink.fee}円" i += 1 end end def pay(user) puts "商品を選んでください" chosen_drink = user.choose_drink change = user.money - self.drinks[chosen_drink].fee if change >= 0 puts "ご利用ありがとうございました!お釣りは#{change}円です。" else puts "投入金額が足りません" end end end class User def initialize(money) @money = money end def money @money end def choose_drink gets.to_i end end puts "商品を用意してください。" drinks = [] 3.times do |i| puts "商品名を入力してください。" drink_name = gets.chomp puts "金額を入力してください。" drink_fee = gets.to_i drinks << Drink.new(drink_name,drink_fee) end vending_machine = VendingMachine.new(drinks) vending_machine.show_drinks puts "あなたはお客さんです。投入金額を決めてください。" money = gets.to_i user = User.new(money) vending_machine.pay(user) VendingMachineクラスに定義した、payメソッドを呼び出しています。実引数としては、直前で定義したインスタンスuserを渡します。 payメソッドに注目しましょう。chosen_drink = user.choose_drinkでは、Userクラスに定義したchoose_drinkメソッドを呼び出しています。change = user.money - self.drinks[chosen_drink].feeでは、「ユーザーの投入金額」と「選ばれた商品の金額」の差分を計算して、その結果を変数changeに代入。changeにはお釣りの金額が含まれる。 VendingMachineクラスのメソッド内で、Userクラスのインスタンスについてメソッドを実行する必要です。Userクラスのインスタンスを、実引数として渡す必要があります。 ファイルを分割 飲み物自販機のアプリケーションですが、コードが長くなり、且つクラスの分かれ目も見分けにくくなっています。したがって、ファイルを分割し、1つのファイル(今回はapplication.rb)で読み込んであげるようにします。 この時に役立つのが、requireです。requireはRubyファイルを読み込むためのメソッドです。*よくライブラリ(ライブラリもあくまでRubyのファイルの集合体ですを読み込むために使用しました。 ライブラリを読み込む際は、requireに続いてライブラリの名前を記述するだけで完了しました。開発者自身で設置したRubyファイルを読み込むには、ディレクトリを明示して記述する必要があります。 クラスごとにファイルを分割し、requireを用いて読み込み ruby_application_training/application.rb require "./drink" require "./vending_machine" require "./user" puts "商品を用意してください。" drinks = [] 3.times do |i| puts "商品名を入力してください。" drink_name = gets.chomp puts "金額を入力してください。" drink_fee = gets.to_i drinks << Drink.new(drink_name,drink_fee) end vending_machine = VendingMachine.new(drinks) vending_machine.show_drinks puts "あなたはお客さんです。投入金額を決めてください。" money = gets.to_i user = User.new(money) vending_machine.pay(user) ruby_application_training/drink.rb class Drink def initialize(name, fee) @name = name @fee = fee end def name @name end def fee @fee end end ruby_application_training/user.rb class User def initialize(money) @money = money end def money @money end def choose_drink gets.to_i end end ruby_application_training/vending_machine.rb class VendingMachine def initialize(drinks) @drinks = drinks end def drinks @drinks end def show_drinks puts "いらっしゃいませ。以下の商品を販売しています" i = 0 self.drinks.each do |drink| puts "【#{i}】#{drink.name}: #{drink.fee}円" i += 1 end end def pay(user) puts "商品を選んでください" chosen_drink = user.choose_drink change = user.money - self.drinks[chosen_drink].fee if change >= 0 puts "ご利用ありがとうございました!お釣りは#{change}円です。" else puts "投入金額が足りません" end end end 機能の追加と単一責任の原則 ここまでで一通りの実装は完了しました。次に、以下のような追加実装を考えましょう。 機能追加:購入後に、スロットゲームを実行し、3桁の数字が揃ったらあたりでもう1本プレゼント 追加機能について考えよう 【例】スロットゲーム機能を追加した自販機 class VendingMachine def initialize(drinks) @drinks = drinks end def drinks @drinks end def play_slot result = [] 3.times do result << rand(0..9) end puts "スロットゲームの結果は#{result.join}です!" if result[0] == result[1] && result[0] == result[2] puts "おめでとうございます!" else puts "残念でした〜" end end def show_drinks puts "いらっしゃいませ。以下の商品を販売しています" i = 0 self.drinks.each do |drink| puts "【#{i}】#{drink.name}: #{drink.fee}円" i += 1 end end def pay(user) puts "商品を選んでください" chosen_drink = user.choose_drink change = user.money - self.drinks[chosen_drink].fee if change >= 0 puts "ご利用ありがとうございました!お釣りは#{change}円です。" play_slot else puts "投入金額が足りません" end end end このスロットゲームの機能も、飲み物・自販機・ユーザーとは異なる別のクラスとして取り扱うことにしましょう。このように、スロットゲームのような目に見えない機能や振る舞いも、役割が異なれば別クラスに分ける必要があります。 slot_game.rbを作成 - ```rb:ruby_application_training/application.rb require "./drink" require "./vending_machine" require "./user" require "./slot_game" puts "商品を用意してください。" drinks = [] 3.times do |i| puts "商品名を入力してください。" drink_name = gets.chomp puts "金額を入力してください。" drink_fee = gets.to_i drinks << Drink.new(drink_name,drink_fee) end vending_machine = VendingMachine.new(drinks) vending_machine.show_drinks puts "あなたはお客さんです。投入金額を決めてください。" money = gets.to_i user = User.new(money) vending_machine.pay(user) 続いて、slot_game.rbに、スロットゲームそのものの処理を記述しましょう。 ```rb:ruby_application_training/slot_game.rb class SlotGame def play_slot result = [] 3.times do result << rand(0..9) end puts "スロットゲームの結果は#{result.join}です!" if result[0] == result[1] && result[0] == result[2] puts "おめでとうございます!" else puts "残念でした〜" end end end 3つのランダムな値を生成して配列resultに代入します。joinメソッドを用いて配列に含まれる値を、ひと続きに表示します。その後、8行目で3つの値が揃っているかどうかを判断。 ruby_application_training/vending_machine.rb class VendingMachine def initialize(drinks) @drinks = drinks end def drinks @drinks end def show_drinks puts "いらっしゃいませ。以下の商品を販売しています" i = 0 self.drinks.each do |drink| puts "【#{i}】#{drink.name}: #{drink.fee}円" i += 1 end end def pay(user) puts "商品を選んでください" chosen_drink = user.choose_drink change = user.money - self.drinks[chosen_drink].fee if change >= 0 puts "ご利用ありがとうございました!お釣りは#{change}円です。" SlotGame.new.play_slot else puts "投入金額が足りません" end end end SlotGameクラスのインスタンスを生成しつつ、play_slotメソッドを呼び出しています。 クラスの決め方を振り返る 単一責任の原則を振り返ろう 単一責任の原則を最初に学び、データと処理のまとまりごとにクラスを分けて実装。上記のアプリケーションでは、飲み物・自販機・ユーザーといったように分けて実装。 追加で機能を実装する際も、既存のクラスの役割と異なれば、別のクラスを作成して実装しました。スロットゲームの機能を持ったSlotGameクラスがこれ. データと処理のまとまりごとに分けて実装する考えを、オブジェクト指向という。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Railsチュートリアル】第10章 ユーザーの更新・表示・削除 演習と解答

Ruby on Railsチュートリアル 第10章の演習問題と解答をまとめました。 第10章 ユーザーの更新・表示・削除 - Railsチュートリアル 10.1 ユーザーを更新する 10.1.1 編集フォーム 10.1.1 - 1 先ほど触れたように、target="_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング(Phising)サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel(relationship)属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。 app/views/users/edit.html.erb <% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <!--省略--> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a> </div> </div> </div> 10.1.1 - 2 リスト 10.5のパーシャルを使って、new.html.erbビュー(リスト 10.6)とedit.html.erbビュー(リスト 10.7)をリファクタリングしてみましょう(コードの重複を取り除いてみましょう)。ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます。 パーシャル作成 コンソール $ touch app/views/users/_form.html.erb app/views/users/_form.html.erb <%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages', object: @user %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit yield(:button_text), class: "btn btn-primary" %> <% end %> new.html.erb, edit.html.erb をリファクタリング app/views/user/new.html.erb <% provide(:title, 'Sign up') %> <!--送信ボタン(submit)にテキストを与えている--> <% provide(:button_text, 'Create my account') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <!--ここでformパーシャルを読み込んでいる--> <%= render 'form' %> </div> </div> app/views/user/edit.html.erb <% provide(:title, 'Edit user') %> <!--送信ボタン(submit)にテキストを与えている--> <% provide(:button_text, 'Save changes') %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <!--ここでformパーシャルを読み込んでいる--> <%= render 'form' %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank">Change</a> </div> </div> </div> 10.1.2 編集の失敗 10.1.2 - 1 編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。 動作確認済み 10.1.3 編集失敗時のテスト 10.1.3 - 1 リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。 editページのフォームに以下の内容で更新させてみる エラーメッセージは4つ name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" test/integration/users_edit_test.rb require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' # エラーメッセージのチェック------------------------------------------ assert_select 'div.alert', 'The form contains 4 errors.' assert_select 'div#error_explanation' do assert_select 'li', count: 4 end # -------------------------------------------------------------------- end end 10.1.4 TDDで編集を成功させる 10.1.4 - 1 実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。 10.1.4 - 2 もしGravatarと紐付いていない適当なメールアドレス(foobar@example.comなど)に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみましょう。 1, 2ともに動作確認。編集に成功し、プロフィール画像も変更された 10.2 認可 10.2.1 ユーザーにログインを要求する 10.2.1 - 1 デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです(結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか(テストが失敗するかどうか)確かめてみましょう。 動作確認済み 10.2.2 正しいユーザーを要求する 何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。 editアクションとupdateアクションではパスが異なるため ターミナル $ rails routes | grep user # 省略 edit_user GET /users/:id/edit(.:format) users#edit user GET # 省略 PATCH /users/:id(.:format) users#update PUT # 省略 DELETE # 省略 上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか? URLを指定することでGETリクエストを送ることができる editアクション 10.2.3 フレンドリーフォワーディング 10.2.3 - 1 フレンドリーフォワーディングで、渡されたURLに初回のみ転送されていることを、テストを書いて確認してみましょう。次回以降のログインのときには、転送先のURLはデフォルト(プロフィール画面)に戻っている必要があります。ヒント: リスト 10.29のsession[:forwarding_url]が正しい値かどうか確認するテストを追加してみましょう。 test/integration/users_edit_test.rb require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest test "successful edit with friendly forwarding" do get edit_user_path(@user) assert_equal session[:forwarding_url], edit_user_url(@user) log_in_as(@user) assert_nil session[:forwarding_url] name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end end テストの流れとしては /users/id/editにアクセス(editアクションはbefore_actionでlogged_in_userメソッドを呼び出す。ここでリクエスト時点のページが保存される) session[:forwarding_url]とedit_user_urlを比較 ログイン(sessions#createアクションが呼び出される。この動作の中でsession[:forwarding_url]はdeleteされるのでnilになる) session[:forwarding_url]はnil ... 10.2.3 - 2 7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください(デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう(デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって(コラム 1.2)、落ち着いて対処してみましょう)。 ターミナル (byebug) session[:forwarding_url] "https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/users/1/edit" (byebug) request.get? true 10.3 すべてのユーザーを表示する 10.3.1 ユーザーの一覧ページ 10.3.1 - 1 レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。 test/integration/site_layout_test.rb require 'test_helper' class SiteLayoutTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "layout links" do get root_path assert_template 'static_pages/home' # header_links assert_select "a[href=?]", root_path, count: 2 assert_select "a[href=?]", help_path assert_select "a[href=?]", login_path # footer_links assert_select "a[href=?]", about_path assert_select "a[href=?]", contact_path assert_select "a[href=?]" , "https://news.railstutorial.org/" # page get contact_path assert_select "title", full_title("Contact") get signup_path assert_select "title", full_title("Sign up") end test "layout links when logged in" do log_in_as(@user) # page get user_path(@user) assert_template 'users/show' assert_select "title", full_title(@user.name) get users_path assert_template 'users/index' assert_select "title", full_title("All users") # headder_links assert_select "a[href=?]", root_path, count: 2 assert_select "a[href=?]", help_path assert_select "a[href=?]", users_path assert_select "ul.dropdown-menu" do assert_select "a[href=?]", user_path(@user) assert_select "a[href=?]", edit_user_path(@user) assert_select "a[href=?]", logout_path end # footer_links assert_select "a[href=?]" , about_path assert_select "a[href=?]" , contact_path assert_select "a[href=?]" , "https://news.railstutorial.org/" end end 10.3.2 サンプルのユーザー 10.3.2 - 1 試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。 動作確認済み /users/2/editにアクセスしようとしようとするとトップページにリダイレクトされる 10.3.3 ページネーション 10.3.3 - 1 Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。 ターミナル $ rails console >> User.paginate(page: nil) User Load (0.2ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 11], ["OFFSET", 0]] (0.1ms) SELECT COUNT(*) FROM "users" => #<ActiveRecord::Relation [#<User id: 1, ...省略] 10.3.3 - 2 先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。 User::ActiveRecord_Relation User.allと同じクラス ターミナル $ rails console >> User.all.class => User::ActiveRecord_Relation >> User.paginate(page: 1).class => User::ActiveRecord_Relation 参考:ActiveRecord::Relationとは一体なんなのか 10.3.4 ユーザー一覧のテスト 10.3.4 - 1 試しにリスト 10.45にあるページネーションのリンク(will_paginateの部分)を2つともコメントアウトしてみて、リスト 10.48のテストが red に変わるかどうか確かめてみましょう。 コメントアウト後にテストを実行 「<div class="pagination">が見つからない」というエラー ターミナル $ rails test test/integration/users_index_test.rb FAIL["test_index_including_pagination", #<Minitest::Reporters::Suite:0x000055945581e568 @name="UsersIndexTest">, 1.442523653000535] test_index_including_pagination#UsersIndexTest (1.44s) Expected at least 1 element matching "div.pagination", found 0.. Expected 0 to be >= 1. test/integration/users_index_test.rb:13:in `block in <class:UsersIndexTest>' 10.3.4 - 2 先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが green のままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。 test/integration/users_index_test.rb require 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' # 確認条件にcountを追加する-------------- assert_select 'div.pagination', count: 2 # --------------------------------------- User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end end 10.3.5 パーシャルのリファクタリング 10.3.5 - 1 リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が red に変わることを確認してみましょう。 動作確認済み renderをコメントアウトすると、ユーザー個別のページを確認できなくなるためエラーとなる 10.4 ユーザーを削除する 10.4.1 管理ユーザー 10.4.1 - 1 Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL(/users/:id)に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は red になるはずです。最後の行では、更新済みのユーザー情報をデータベースから読み込めることを確認します( 6.1.5)。 サンプルの通りにテストを書く。 まずはテスト結果をREDにするためにusers_controller.rbの内容を少し編集する テストがREDなのを確認したらusers_controller.rbを元に戻す test/integration/user_controller.rb require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end # 省略 test "should not allow the admin attribute to be edited via the web" do log_in_as(@other_user) assert_not @other_user.admin? patch user_path(@other_user), params: { user: { password: "password", password_confirmation: "password", admin: true } } assert_not @other_user.reload.admin? end end app/controllers/users_controller.rb class UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update] before_action :correct_user, only: [:edit, :update] # 省略 private # permitメソッドにキー :admin を追加(変更を加えることができる) # def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation, :admin # <-- テストがREDになるのを確認したら消す                      ) end # 省略 end 10.4.2 destroyアクション 10.4.2 - 1 管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか? ターミナル $ rails server # 省略 # ↓ ユーザーID:2のユーザーを削除(destroy) Started DELETE "/users/2" for 119.243.189.30 at 2021-05-01 21:55:46 +0000 Cannot render console from 119.243.189.30! Allowed networks: 127.0.0.0/127.255.255.255, ::1 Processing by UsersController#destroy as HTML Parameters: {"authenticity_token"=>"GF4v92PLxHeSHjD7paJMHMocTX+1PWrE65JmRV7Zyz9KnYFwBZm8qRVNSz/1G+yKZ3aZTQZcQsidj1D0y5tafw==", "id"=>"2"} User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ↳ app/helpers/sessions_helper.rb:18:in `current_user' User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] ↳ app/controllers/users_controller.rb:44:in `destroy' (0.1ms) begin transaction ↳ app/controllers/users_controller.rb:44:in `destroy' User Destroy (1.5ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 2]] ↳ app/controllers/users_controller.rb:44:in `destroy' (8.0ms) commit transaction ↳ app/controllers/users_controller.rb:44:in `destroy' Redirected to https://ac648bab7dd147e29059466b0a50e263.vfs.cloud9.ap-northeast-1.amazonaws.com/users Completed 302 Found in 21ms (ActiveRecord: 10.0ms | Allocations: 4412) # ↓ユーザー一覧ページにリダイレクト(redirect_to users_url) Started GET "/users" for 119.243.189.30 at 2021-05-01 21:55:46 +0000 . . . 10.4.3 ユーザー削除のテスト 10.4.3 - 1 試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が red に変わることを確認してみましょう。 動作確認 結果はRED User.countが期待値と異なる結果となってしまうため、エラー。 ターミナル $ rails t FAIL["test_should_redirect_destroy_when_logged_in_as_a_non-admin", #<Minitest::Reporters::Suite:0x0000559454400400 @name="UsersControllerTest">, 3.2400318309992144] test_should_redirect_destroy_when_logged_in_as_a_non-admin#UsersControllerTest (3.24s) "User.count" didn't change by 0. Expected: 34 Actual: 33 test/controllers/users_controller_test.rb:67:in `block in <class:UsersControllerTest>' test/controllers/users_controller.rb require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end test "should redirect destroy when logged in as a non-admin" do log_in_as(@other_user) assert_no_difference 'User.count' do # before_action :admin_user でdestroyメソッドを保護しなければ、 # 非管理者ユーザーからdeleteリクエストが送ることができてしまう delete user_path(@user) end assert_redirected_to root_url end end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

論理演算子について

演算子 記述例 意味 別の演算子 && a && b aとbが共に真の場合に真 and II a II b aかbの少なくとも1つが真の場合に真 or ! !a aが真の時に偽、偽の時に真 not ※ちなみにマークダウン法では ||は表をかく記号と同じだから *railsとは無関係ですが、マークダウン法でアルファベットアイ「i」の大文字を使うとダミーの演算記号ができ上がります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] Deviseのユーザー登録後失敗後のルーティングが/usersになる件の対処

概要 現在運営しているカフェサークルのサイトをrailsで作成しているのですがdeviseの新規登録が失敗したときのルーティングがusers/sign_in > /users になってしまい、リロードした際にページが見つからないエラーが出ている状態でした。 解決策 deviseのscopeで/usersを新規登録のルーティングに設定してあげればよい。 具体的には以下のようにroutes.rbに設定を追加する。 routes.rb devise_scope :user do # TIPS: ユーザー登録しっぱいのリダイレクトのエラーを防ぐ https://github.com/heartcombo/devise/blob/master/app/controllers/devise/registrations_controller.rb get '/users', to: 'devise/registrations#new' end これで、deviseが/usersを新規登録のルーティングと認識してエラーが起こらなくなる。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsでYou must have ImageMagick or GraphicsMagick installedを解決する

railsでruby:X.X.X-alpineってdocker image使っててYou must have ImageMagick or GraphicsMagick installedってエラーに遭遇した時は、次のコマンドで解決 $ docker exec {container name} apk add imagemagick apkってのがalpine linuxのpackage managerなんだけど知らなくて時間食った。yumが使えなくてずっと泣いてた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】DBにデータを取りに行ったときに、ActiveRecord::RecordNotFound と怒られた時の対処法

症状 Rails6.0.3でcontrollerからDBに対して、findでデータを取りに行ったとき、下記エラーメッセージが出てしまいました。 humans_controller#show def show classroom_id = Classroom.find_by(id: params[:classroomId]) human = Human.find_by(id: params[:id],classroom_id: classroom_id) render json: { human: human   }, status: :ok end findでエラー app/controllers/api/v1/foods_controller.rb:26:in `show' Started GET "/api/v1/classroom/1/human/2" for ::1 at 2021-05-02 00:16:30 +0900 (0.1ms) SELECT sqlite_version(*) Processing by Api::V1::HumansController#show as HTML Parameters: {"classroom_id"=>"1", "id"=>"2"} Restaurant Load (0.3ms) SELECT "classrooms".* FROM "classrooms" WHERE "classroom"."id" = ? LIMIT ? [["id", nil], ["LIMIT", 1]] ↳ app/controllers/api/v1/humans_controller.rb:26:in `show' Completed 404 Not Found in 174ms (ActiveRecord: 1.9ms | Allocations: 2232) ActiveRecord::RecordNotFound (Couldn't find Classroom with 'id'={:id=>nil}): 下記記事で同様のエラーメッセージを発見。 【Rails】find・find_by・whereについてまとめてみた そこには主キーが見つからない場合、上記のActiveRecord::RecordNotFoundと怒られる模様。 解決策 findをfind_byに変更したら、解決しました。 findだと、主キーに対してのみ条件づけするので、find(id)と書けます。 が、find(id: id)とは書けないため、idにnilが入ってしまい、結果として、主キーに該当するデータがないと怒られていたようです。 find #できる id = 1 human = human.find(id) #できない human = human.find(id: 1) find_byだと、主キー以外にも条件づけできるので、カラム名を指定する必要があります。 なので、下記のように条件づけるカラム名も一緒にしてできます。 find_by #できる human = human.find(id: 1) #できる human = human.find_by(name: "太郎") 参考 【Rails】find・find_by・whereについてまとめてみた https://qiita.com/nakayuu07/items/3d5e2f8784b6f18186f2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む