20211008のRubyに関する記事は7件です。

リクエストとレスポンスについて!

①結論! リクエストとは、「データや情報を要求する」こと! レスポンスとは、「リクエストに対応するデータや情報を返却する」こと! データのやり取り自体にも名前がついています! それをリクエストとレスポンスと言います! ②.リクエスト 主に、クライアントサイドから送られてくるのがリクエストです! 簡単にいうと、データを見たいから要求するという事です! ③.レスポンス 主にデータベースから、クライアントサイドに送るデータのことです! 簡単にいうと、要求されたデータを送るという事です! ④.まとめ MVCの流れで1番最初の初歩的な事ですね! これ自体の意味は難しくは無いので、きちんと覚えておくべきですね! 次回から更にMVCについても詳しくアウトプットして行こうと思います! 何か説明で間違っていたらご指導お願い致します(_ _)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails で楽天APIを使ってみる(gem無し)

楽天apiを初めて使ったので忘れないようにメモとして残しときます。 前提条件 Ruby: 2.6.5 Rails: 5.2.6 使用するgem インストールしてください gem 'dotenv-rails' <- 環境変数を使えるようにする gem 'faraday' <- httpクライアントとして導入する gem 'faraday_middleware' 楽天APIのセットアップ まずはここから使いたいAPIを選んでください。 僕は商品検索APIを使いました。 選んだら、APIの使用に必要なIDを発行します。 アプリ名とリンクを記載する必要があるので準備しておいてください。 ログインしてない人はログインを促されますのでログインしてください。 必要な情報を入れたら、作成されたIDを確認できます。 これで完了です。 実装 必要なidを取得できたので、まずはenvファイルにidを設定します。 env RAKUTEN_APP_ID = 'xxxxxxxxxxxxxxxx' RAKUTEN_AFFILIATE_ID = 'xxxxxxxxxxxxxxxx'  #アフィリエイトidは任意 呼び出す時は以下のように書く ENV['RAKUTEN_APP_ID'] と ENV['RAKUTEN_AFFILIATE_ID'] 今回は動作確認できればいいので、適用にconfig/initializers配下に rakuten_api.rbを作ります。 コンソールでapiを通じて情報を取得できれば良いので、入力を自分でするようにします。 config/initializers.rakuten_api.rb puts '検索キーワードを入力してください' search_keyword = gets.chomp ファイルを実行すると,入力を促すような動作をします。 config/initializers.rakuten_api.rb puts '検索キーワードを入力してください' search_keyword = gets.chomp app_id = ENV['RAKUTEN_APP_ID'] 一旦変数に入れます。 後はFaradayを使いますが、 Faradayはruby上でHTTPリクエストを実行できるようにしてくれるライブラリです。 rubyは標準でNet::HTTPというクライアントがありますが、簡単にFaradayを使ってみようと思います。 使い方としては、大雑把ですがこんな感じです。 res = Faraday.get('https://hoge.com') こう書くと、サイトの情報が取得できます。 こんな感じで楽天のサイトにアクセスして対応する商品の情報をgetします。 config/initializers.rakuten_api.rb puts '検索キーワードを入力してください' search_keyword = gets.chomp app_id = ENV['RAKUTEN_APP_ID'] res = Faraday.get("https://app.rakuten.co.jp/services/api/IchibaItem/Search/20140222?applicationId=#{app_id}&keyword=#{search_keyword}") res_json = JSON.parse(res.body) puts res_json getの対象リンクにキーワードと発行したIDを展開します。 これで実際にviewから使う時もkeywordとしてformで送信を行えば使えます。 情報が取れているか確認したいので、一旦実行してみます。 ruby config/initializers.rakuten_api.rb キーワードを入力したら、、 {"Item"=> {"mediumImageUrls"=> [{"imageUrl"=> "http://thumbnail.image.rakuten.co.jp/@0_mall/ledled/cabinet/thumnail/wineset/felicity-set-041_1.jpg?_ex=128x128"}, {"imageUrl"=>"http://thumbnail.image.rakuten.co.jp/@0_mall/ledled/cabinet/thumnail/wineset/felicity-set-041_2.jpg?_ex=128x128"}, {"imageUrl"=>"http://thumbnail.image.rakuten.co.jp/@0_mall/ledled/cabinet/thumnail/wineset/felicity-set-041_3.jpg?_ex=128x128"}], "pointRate"=>5, "shopOfTheYearFlag"=>0, "affiliateRate"=>4, "shipOverseasFlag"=>0, "asurakuFlag"=>1, "endTime"=>"", "taxFlag"=>0, "startTime"=>"", "itemCaption"=>"【送料無料対象外地域について】 北海道・沖縄・離島のお客様は対象外とさせていただきます。 ご注文内容確認後、こちらで送料部分を修正して「御注文確認メール」にて、その旨ご連絡させていただきますのでお支払金額の変更を必ずご確認ください。 【クール便のご指定について】 送料無料商品のクール便のご指定に関しましては、お客様のご負担となります。 自動的にクール便代金が300円(税抜)が加算されます。 予めご了承ください。 【送料無料商品との同梱について】 1梱包 こんな感じでデータが取れました! ただ、めちゃめちゃ見づらいのでどうにかします。 最後のputsの部分を puts JSON.pretty_generate(res_json) として再度実行します。 今更ですが、検索キーワードはワインです。笑 "Item": { "mediumImageUrls": [ { "imageUrl": "http://thumbnail.image.rakuten.co.jp/@0_mall/ledled/cabinet/thumnail/wineset/felicity-set-038_1a.jpg?_ex=128x128" }, { "imageUrl": "http://thumbnail.image.rakuten.co.jp/@0_mall/ledled/cabinet/thumnail/wineset/felicity-set-038_2.jpg?_ex=128x128" }, { "imageUrl": "http://thumbnail.image.rakuten.co.jp/@0_mall/ledled/cabinet/thumnail/wineset/felicity-set-038_3a.jpg?_ex=128x128" } ], "pointRate": 5, "shopOfTheYearFlag": 0, "affiliateRate": 4, "shipOverseasFlag": 0, "asurakuFlag": 1, "endTime": "", "taxFlag": 0, "startTime": "", "itemCaption": "【送料無料対象外地域に ちょっとjson形式の見やすいファイルになりましたね! これで完了です。 後は用途に合わせてカスタマイズしてください。 あとがき 正直gem使った方が100倍早いです。 今回は試しにやってみようと思ったので使わずにやってみました。 おかげでfaradayやhttpリクエストを活用した使い方もできるようになったのでよかったです。 以下参考にさせていただいた記事を貼ります。 ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby] tallyメソッド

はじめに Rubyで便利なメソッドを見つけたので記事にしておきます。 tallyメソッド リファレンスマニュアルではEnumerable#tallyとして掲載されています。 普段は配列に対して使うことが多そうです。 その辺はまだまだ勉強不足なため、配列を使って説明します。 配列に対して使用し、ハッシュで配列の中のそれぞれの要素が何個ずつあるか教えてくれます。 例えば[1, 2, 2, 2, 3]という配列があるとして、tallyを使うと{1=>1, 2=>3, 3=>1}というように返してくれます。 [1, 2, 2, 2, 3].tally => {1=>1, 2=>3, 3=>1} 重複している要素を数えたりするときに使えます。 似たメソッドにgroup_byメソッドがあります. group_byはブロックを必要とし、いろんな使い方ができるみたいです。 tallyのように出力させる方法もあれば、さっきの配列を{1=>[1], 2=>[2, 2, 2], 3=>[3]}のように出力させることができるみたいです。使い方には少しコツがいるようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails LINE messaging APIを使用してLINEbot通知してみた ブロードキャストメッセージ プッシュメッセージ

LINE messaging APIを使用してメッセージを送信する 私は、Twitter APIで取得した内容をLINEで通知したかった!! 目標 友達登録している方全員に通知を送信したい 結論 ・LINE messaging API を使用して通知を送る ・broadcast通知を使用する LINE messaging API Messaging APIを使って、ユーザー個人に合わせた体験をLINE上で提供するボットを作成できます。 作成したボットは、LINEプラットフォームのチャネルに紐づけます。チャネルを作成すると生成されるLINE公式アカウントをボットモードで運用すると、LINE公式アカウントがボットとして動作します。 使用してみる メッセージの種類は様々ある プッシュメッセージ(1対1) マルチキャストメッセージ(1対多:ユーザーID指定) ナローキャストメッセージ(1対多:絞り込み配信) ブロードキャストメッセージ(1対多:すべての友だち) ブロードキャストメッセージ 今回はこちらを使用していく。 1.LINE for Business を登録する 登録手順とトークン取得はこちらを参考にさせていただきました。 2.LINE 通知を送信する 1.で取得したchannel secretとchannel access tokenをセットし、broadcastで送信する article.rb client = Line::Bot::Client.new { |config| config.channel_secret = "<channel secret>" config.channel_token = "<channel access token>" } message = { type: 'text', text: 'hello' } response = client.broadcast(message) p response 3.LINEbotから通知が届く プッシュメッセージ こちらを最初使用していたが、1対1になってしまうので使用しない。 コードの書き方としては、ブロードキャストメッセージと少しだけ違うところがある。 article.rb client = Line::Bot::Client.new { |config| config.channel_secret = "<channel secret>" config.channel_token = "<channel access token>" } message = { type: 'text', text: 'hello' } # ここに違いがある。 "<to>"に送信先のIDを指定しなければならない。 response = client.push_message("<to>", message) p response
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OmniAuthの公式のwikiを読んでみた。

はじめに OmniAuthの公式のwikiを読んで学習した内容を備忘録としてこちらに投稿します。 OmniAuthって? Deviseのバージョン1.2から追加された、OAuthに関するモジュールのこと。 このモジュールを利用すれば、twitterやfacebookといったプロバイダーに登録されている情報でユーザー認証を行うことができます。 Before you start config.omniauthは、アプリケーションにomniauth プロバイダーのミドルウェアを追加します。これは、config/initializers/omniauth.rbにconfig.omniauthを記述するべきではないことを意味します。なぜなら、そうしてしまうとお互いのomniauth プロバイダーのミドルウェアが衝突し、認証ができない事態に陥ってしまうからです。 config/initializers/devise.rbにだけ、config.omniauthを記述するようにしましょう。 Facebook example まずは、以下のgemをGemfileに追加してください。 1. gemの追加 gemfile gem 'omniauth-facebook' gem 'omniauth-rails_csrf_protection' gem 'omniauth-facebook' facebookのomniauth機能を追加するためのgem "omniauth-#{provider}"の形で各プロバイダーのomniauthのgemが提供されている。 各プロバイダーについてはこちらから確認してください。 gem 'omniauth-rails_csrf_protection' OmniAuth2.0から CSRF脆弱性 CVE-2015-9284の対応のためにインストールが必要なgem これがないと「Not found. Authentication passthru.」と表示され認証が失敗します。 参考:OmniAuth - Rails CSRF Protection バージョン1系からの変更点については、こちらでご確認ください。 変更点について簡単に紹介しておくと、 サービスプロバイダーのサービス認可画面へリダイレクトするエンドポイントはデフォルトでGET/POSTどちらも有効でしたが、2.0からPOSTのみに変更となりました。 gem omniauth-rails_csrf_protection'のようなCSRF用validatorを使用すること これらの変更は、1系でCSRF脆弱性がみつかったため、それに対応するためのものとのことです。 1.について、どうしてgetメソッドがダメなのか調べてみたところ、どうやらgetメソッドはCSRFtokenの漏洩リスクが高いようです。 参考:Cross-Site Request Forgery Prevention Cheat Sheet 2.検索用カラムの追加 Deviseは、uidとproviderの情報を使って、DBからデータを検索するので、テーブルに存在していない場合追加する必要があります。 rails g migration AddOmniauthToUsers provider:string uid:string rake db:migrate 3.認証用プロバイダーの宣言 deviseが認証で利用するプロバイダーを識別できるように、config/initializers/devise.rbに以下のように追記します。 config/initializers/devise.rb config.omniauth :facebook, "APP_ID", "APP_SECRET" “Invalid credentials”等の理由でFacebookで認証ができなかった場合は、token_params: { parse: :json }を追記してください。 config/initializers/devise.rb config.omniauth :facebook, "APP_ID", "APP_SECRET", token_params: { parse: :json } 4.モデルの設定 deviseにOmniAuthのプロバイダーを識別させるためには、config/initializers/devise.rbへの設定と別にモデルへの設定も必要です。以下のように記述してください。 app/models/user.rb devise :omniauthable, omniauth_providers: %i[facebook] ここまで、できたら変更をdeviseに知らせるためにrailsをリスタートさせましょう。 認証に複数のプロバイダーを利用したい場合は、こちらを参照してください。 5.Deviseのurlメソッドについて config/routes.rb Rails.application.routes.draw do devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end app/models/user.rb devise :omniauthable, omniauth_providers: %i[facebook] Userモデルでomniauthable、routes.rbでdevise_for :users の設定ができたらdeviseは以下2つのurlメソッドを作成します。この時、*_urlというメソッドは作成されません。 user_{provider}_omniauth_authorize_path user_{provider}_omniauth_callback_path OmniAuth 2.0+からは、HTTP GETは許可されておらず、HTTP POSTを使う必要があります。button_toヘルパーを使用するか、link_toヘルパーを使用する際は、method: :postの記述を入れるようにしましょう。 app/views/devise/shared/_links.html.erb <%= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path, method: :post %> # you can also switch to using `button_to`, which doesn't require rails-ujs for performing POST requests: <%= button_to "Sign in with Facebook", user_facebook_omniauth_authorize_path %> HTTP GETは、CSRF脆弱性をついた攻撃を受ける可能性があるようです。 参考:possibility of CSRF attacks. 6.Omniauth callbacksの設定 1. routes.rbの設定 link_to,button_toがクリックされると、ユーザーは、Facebookのページにリダイレクトされ、そこから認証情報を取得します。 認証情報の取得後に、元のアプリケーションにリダイレクトバックする必要があるため、config/routes.rb以下のように編集します。 config/routes.rb devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' } これで、Deviseは、Devise側のOmniauthCallbacksControllerではなく、自身のusersフォルダー配下のOmniauthCallbacksControllerを参照するようになります。 2.omniauth_callbacks_controller.rbの設定 以下のファイルを作成します。 app/controllers/users/omniauth_callbacks_controller.rb class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController end Devise::OmniauthCallbacksControllerをUsers::OmniauthCallbacksControllerが継承しています。これで、devise側のコントローラーをオーバーライドできます。 3.OmniauthCallbacksControllerのオーバーライド コールバックコントローラーには、OmniAuthで利用するプロバイダーと同名のアクション(メソッド)を定義する必要があります。今回は、Facebookを利用しているので以下のように編集します。 app/controllers/users/omniauth_ca class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController # See https://github.com/omniauth/omniauth/wiki/FAQ#rails-session-is-clobbered-after-callback-on-developer-strategy skip_before_action :verify_authenticity_token, only: :facebook def facebook # You need to implement the method below in your model (e.g. app/models/user.rb) @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format? else session["devise.facebook_data"] = request.env["omniauth.auth"].except(:extra) # Removing extra as it can overflow some session stores redirect_to new_user_registration_url end end def failure redirect_to root_path end end 1.facebookアクションについて特筆すべき点 OmniAuthで取得した、Facebookの全ての情報は、request.env["omniauth.auth"]に格納され、ハッシュとして利用できます。request.env["omniauth.auth"]の中身についてはこちらで確認ください。 Userモデルに既にユーザーが登録されていた場合は、sign_in もしくは sign_in_and_redirectのいずれかでサインインさせます。Warden callbacks.を利用したい場合は、:event => :authenticationオプションを使います。 あなた次第ですが、flashメッセージについては、deviseのデフォルトのものを使用することができます。 Userモデルにユーザーが登録されていなかった場合は、sessionにOmniAuthで取得したFacebookの全ての情報を保存します。この時、sessionのキーを"devise."で始まるように命名していれば、ユーザーのサインイン後に自動でこのsession情報を削除してくれます。そして最終的には、ユーザーをregistration formにリダイレクトさせます。 2.CookieOverflow ちなみに、以下の部分を実行した際にエラーが発生しました。 session["devise.facebook_data"] = request.env["omniauth.auth"].except(:extra) # Removing extra as it can overflow some session stores こんなエラーが発生します。#twitterとなっているのは、私が、twitterOmniAuthの実装をしていたからです。facebookでも同様のエラーが起きます。 エラー原因について railsのデフォルトのsession storeは、cookieベースとなっています。 cookieの場合、約4KB(4,096byts)がデータを保存できる限界容量となっています。限界容量を超えてしまうとオーバーフローしてしまいます。 今回は、request.env["omniauth.auth"].except(:extra)の情報がcookieの限界容量を超えたため、オーバーフローによるエラーが発生してしまいました。 オーバーフローしないようにデータ容量の大きい(:extra)の部分をexceptしていますが、cookieではそれでも限界容量を超えてしまうようです。 参考:ActionDispatch::Session::CookieStore cookieオーバーフローの解決策としては、session storeをより容量のあるストアに変更することです。 Active Recordを用いてデータベースに保存する 方法が一番いいかと思います。(activerecord-session_store gemが必要)これについては、別記事に載せる予定です。 詳細については、以下でご確認ください。 参考:Ruby on Rails ガイド 7.self.from_omniauth(auth)メソッドの定義 コントローラーの設定完了後、app/models/user.rbにfacebookアクションで登場した、self.from_omniauth(auth)メソッドを定義します。 app/models/user.rb def self.from_omniauth(auth) where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.email = auth.info.email user.password = Devise.friendly_token[0, 20] user.name = auth.info.name # assuming the user model has a name user.image = auth.info.image # assuming the user model has an image # If you are using confirmable and the provider(s) you use validate emails, # uncomment the line below to skip the confirmation emails. # user.skip_confirmation! end end このメソッドは、providerとuidの情報で、Userモデルからユーザーを探します。 もし、ユーザーが見つからなかった場合、ランダムなパスワードと、その他の情報を持った新たなユーザを作成します。 注意するべき点として、first_or_createメソッドは新たにユーザーを作成する場合、providerとuidの情報を以下のように自動で登録してくれます。 user.provider=auth.provider user.uid=auth.uid first_or_create! メソッドは、first_or_createメソッドと同様に動作しますが、ユーザーレコードがバリデーションに引っかかる等で正常に登録できない場合は、例外エラーを発生させます。 加えて、first_or_createメソッドについて注意するべき点としては、ユーザーが見つかった場合、そのユーザーのプロバイダーから取得したデータが前回登録時から更新されていたとしても、そのデータを更新できないということです。このことについては、こちらでご確認ください。 実は、今回紹介した、first_or_createメソッドは、railsの過去のパブリックAPIには載っているのですが、最新のパブリックAPIには、載っていないんです。代わりに、最新のパブリックAPIには、find_or_create_byが載っています。 このことについて、調べたところ、first_or_createメソッドは、where句を伴わないと期待する動作にならず、分かりずらいのが原因ではないかとのことでした。 参考:Rails first_or_create vs find_or_create_by on ActiveRecord 参考:first_or_create vs find_or_create_by 以上を踏まえて、今風にself.from_omniauthメソッドを定義すると以下のようになるかと思います。 def self.from_omniauth(auth) find_or_create_by(provider: auth.provider, uid: auth.uid) do |user| user.email = auth.info.email user.password = Devise.friendly_token[0, 20] user.name = auth.info.name # assuming the user model has a name user.image = auth.info.image # assuming the user model has an image # If you are using confirmable and the provider(s) you use validate emails, # uncomment the line below to skip the confirmation emails. # user.skip_confirmation! end end 8.new_with_sessionについて DeviseのRegistrationsControllerは、デフォルトの挙動として、リソースをビルドする前にUser.new_with_sessionを実行します。これは、次のことを意味します。 サインアップ前に常にユーザーは初期化されてしまいます。そのため、セッションからデータをコピーしておく必要がある場合は、ユーザーモデルにnew_with_sessionメソッドを定義する必要があります。 app/models/user.rb class User < ApplicationRecord def self.new_with_session(params, session) super.tap do |user| if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"] user.email = data["email"] if user.email.blank? end end end end このコードを理解するためには、以下の二つのメソッドを理解する必要があります。 super tap 1.superについて superクラスとは、オーバーライドされる前のメソッドを呼び出すことができるメソッドです。言い換えれば、継承元のメソッドを出力することが可能です。 引用:superクラスとは 2.tapについて tap {|x| ... } -> self self を引数としてブロックを評価し、self を返します。 メソッドチェインの途中で直ちに操作結果を表示するためにメソッドチェインに "入り込む" ことが、このメソッドの主目的です。 引用:Ruby 3.0.0 リファレンスマニュアル hash = {} # => {} hash.tap{ |h| h[:value] = 42 } # => {:value=>42} hash # => {:value=>42} tap の場合は、ブロックの内容にかかわらずレシーバー自身が返り 引用:Ruby: Object#tap、Object#then を使ってみよう super.tapで継承元のself.new_with_sessionメソッドの内容を上書きしているようです。 参考:What does self.new_with_session(params, session) do in this case ここまでを理解したうえで、もう一度コードを見てみましょう。 class User < ApplicationRecord def self.new_with_session(params, session) super.tap do |user| if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"] user.email = data["email"] if user.email.blank? end end end end まず、superについて、 User>ApplicationRecord Userモデルは、ApplocationRecordモデルを継承しています。 ApplicationRecordは、deviseインストール時に、deviseのモジュールをインクルードしています。 つまり、 superとはdevise/lib/devise/models/registerable.rbで定義された以下のnew_with_sessionメソッドのことを表しています。 devise/lib/devise/models/registerable.rb # A convenience method that receives both parameters and session to # initialize a user. This can be used by OAuth, for example, to send # in the user token and be stored on initialization. # # By default discards all information sent by the session by calling # new with params. def new_with_session(params, session) new(params) end 次に、tap以下の処理について、 tapメソッドの処理に当てはめると、、、 super を引数としてブロックを評価し、super を返します。 このtapメソッドの戻り値は、以下のブロック処理の中身となります。 if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"] user.email = data["email"] if user.email.blank? ここまで来れば、以下のメソッドの意味がわかりますね。 class User < ApplicationRecord def self.new_with_session(params, session) super.tap do |user| if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"] user.email = data["email"] if user.email.blank? end end end end つまり、devise/lib/devise/models/registerable.rbで定義されたnew_with_sessionメソッドの内容をブロック処理の内容で上書きするということになります。 メソッドの挙動としては、サインアップページにリダイレクトバック後に、フォームをemail入力部が空白であった場合、session["devise.facebook_data"]内のemailの情報を補完するというもの。 自動入力が実現できます。 9.Facebook認証でのサインアップを取消す設定 ユーザーにFacebook認証でのサインアップをキャンセルすることを許可したい場合は、cancel_user_registration_pathにリダイレクトさせるようにします。そうすることで、"devise."で始まるsessionの情報を削除することができ、それ以降new_with_sessionメソッドが呼び出されることはなくなります。 10.Logout linksの設定 ログアウト用のリンクを設定します。  config/routes.rb devise_scope :user do delete 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session end これで、お終いです!お疲れ様でした! 実装がうまくいっていることができれば、次は、実装テストに移りましょう。 https://github.com/omniauth/omniauth/wiki/Integration-Testing 参考 OmniAuth: Overview ActionDispatch::Session::CookieStore
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのform_withの使い方を素人ながらまとめました。

form_withはフォームにす関するHTML要素を簡単に生成することができる便利なメソッド まずフォームの値をデータベースに保存する際の書き方です。 ↓railsでこう書くと <%= form_with(model: @user, local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> ↓HTMLを生成してくれます。 <form accept-charset="UTF-8" action="/users" method="post"> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Create my account" /> </form> では、内部構造を見ていきます。 <%= form_with(model: @user, local: true) do |f| %> model @userの部分はviewを呼び出したコントローラー側で、作成したインスタンスです。 コントローラー def new @user = User.new #新しいuserインスタンスを作成。 end つまり「model: @user」の@userは空のインスタンスです。 railsは空のインスタンスをmodelに渡すと、「フォームの内容で新しくユーザーを作成したいだろうからcreateアクションにPOSTリクエスト投げるね」と勝手に解釈してくれます。 逆に言うと@userにデータがある場合は「ユーザー情報の更新がしたいんだね」と解釈してupdateアクションにPATCHリクエストをなげてくれます。 ↓createアクションにPOSTリクエストを投げる時は、このように作成してくれます。 <form accept-charset="UTF-8" action="/users" method="post"> ↓id:1のユーザー情報を編集する時は、このように生成してくれます。 <form accept-charset="UTF-8" action="/users/1" method="post"> <input type="hidden" name="_method" value="patch"> httpリクエストでPATCHリクエストは存在しないので、 inputタグで偽造します、 これも自動生成されます。 ブロック引数のfは、HTML要素を生成するメソッドを持っています。 引数は「:カラム名」とします。 <%= f.label :name %> <%= f.text_field :name %> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> 勝手にfor,id,name,type名を設定してくれます。 name属性が「user[name]」となっているので、コントローラーで値を受け取る場合は、「params[:user][:name]」とすると取得できます。 email,passwordも同じ様な感じで生成されます。 <%= f.label :email %> <%= f.email_field :email %> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <%= f.label :password %> <%= f.password_field :password %> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> モデルのインスタンスを渡さないでパスを書くこともできます。 <%= form_with(url: "/users", local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> ↓このようなHTMLが生成される。 <form accept-charset="UTF-8" action="/users" method="post"> <label for="name">Name</label> <input id="name" name="name" type="text" /> <label for="email">Email</label> <input id="email" name="email" type="email" /> <label for="password">Password</label> <input id="upassword" name="password" type="password" /> <label for="password_confirmation">Confirmation</label> <input id="password_confirmation" name="password_confirmation" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Create my account" /> </form> url指定、user[name]みたいなname属性にはなりません。 ただしform_withの引数に「scope: :スコープ名」を追加するとname属性は「スコープ名「name」」のようになりモデルを渡した時と同じ実装ができます。 ↓試しにスコープを追加してみます。 <%= form_with(url: "/users",scope: :user, local: true) do |f| %> ↓先ほどのモデルを渡した時と同じようなHTMLが作成されました。 <form accept-charset="UTF-8" action="/users" method="post"> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Create my account" /> </form> コントローラー名やアクション名を指定することもできます。 基本はモデルを渡せばrailsが自動で振り分けてくれますが、ルーティングがうまく動かない時は、直接コントローラー名やアクション名を指定できます。 <%= form_with @user, url: {controller: 'users', action: 'index' } do |f| %> <%= f.text_field :name %> <%= f.submit %> <% end %> view側でformに渡すモデルを作成しても動きます。 先ほどはコントローラーでモデルを作成し、viewに渡していました。 コントローラー def new @user = User.new #新しいuserインスタンスを作成。 end view <%= form_with(model: @user, local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> ですがモデルはコントローラーで作成しなければいけないわけではないので、以下のようにviewでモデルを作成することもできます。 view <%= form_with(model: User.new, local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> 参考 https://pikawaka.com/rails/form_with https://railstutorial.jp/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsのN+1問題をincludesメソッドで解決する

アプリの概要(前提) ・こんな感じで、ユーザーaaaの投稿に対してコメントが6件ついていて、そのコメントと共に、コメントした人のユーザー情報(名前とプロフ画像)が載っている ・/posts/8にGETリクエストを送るとviews/posts/show.html.slimが返される ・ユーザーaaaのidは16 ・ユーザーoooのidは17 ・Commentはbodyカラムを、Userはnameカラムとimageカラムを持つ N+1問題とは N+1問題とは、データベースからデータを取り出す際に、必要以上にSQLが発行されることで、パフォーマンスが悪くなる(処理速度が遅くなる)問題のこと 具体例 先程のアプリのpostsコントローラー posts_controller.rb def show @post = Post.find(params[:id]) @comments = @post.comments.all.order(created_at: :desc) end view / 投稿一覧 / コメント一覧 <% @comments.each do |comment| %> <%= comment.user.name %> <% end %> <% @comments.each do |comment| %>のところでpost_id: 8の全てのComment情報(body)及びコメントしたUserの情報(nameとimage)のSQLが発行される(allメソッド実行時ではないから注意) その時に発行されたSQL(rails sの画面で確認できる) まず、オレンジ色の線が post_id: 8のCommentを全件取得(今回は6件) 次に、赤色の線が 1番目のcommentに紐づいているuserを1人取得(id: 17) 2番目のcommentに紐づいているuserを1人取得(id: 17) 3番目のcommentに紐づいているuserを1人取得(id: 17) 4番目のcommentに紐づいているuserを1人取得(id: 16) 5番目のcommentに紐づいているuserを1人取得(id: 16) 6番目のcommentに紐づいているuserを1人取得(id: 16) を表している。 このように、post_id: 8にされたコメントの数(6件)+Commentを全件取得の1回で、合計7回SQLが発行されてしまう(usersテーブルに対してcommentの数(6回) + commentsテーブルに対して全件取得(1回)) 。 これがN+1問題と呼ばれている。 このままでは、もしpost_id: 8にされたコメントの数が10000件になれば10001回SQLが発行されることになってしまい、アプリのパフォーマンスの低下につながる。 解決方法 post_id: 8のCommentを全件取得し、その段階でそれに紐づいているuserを取得すれば、テーブルを参照する回数は2回で済み、コメントが何件になろうともSQLの発行回数は2回で済む。 具体的には、allメソッドをincludesメソッドに変えるだけである。 posts_controller.rb def show @post = Post.find(params[:id]) @comments = @post.comments.includes.order(created_at: :desc) end ※ includesメソッドとは、関連している複数のテーブルからデータを取得してくるときのアクセス回数を大きく減らすことができるメソッド。また、事前に検索やフィルタリング、ソートなどをしたデータを取得することもできるため、アプリケーション側でそれらの処理を行う必要がなくなる ※ 書き方は、モデル名.includes(:関連名) includesメソッドに渡す引数は、テーブル名ではなくアソシエーションで定義した関連名を指定する(テーブル名でないから注意) includesメソッドを使った場合に発行されるSQL ※ オレンジ色の線で、post_id: 8のCommentを全件取得し、赤色の線でcommentに紐付くuserのデータを取得している。これなら、コメントが10000件になってもSQLの発行回数は2回で済む。 (参考:Railsガイド)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む