20211008のRailsに関する記事は18件です。

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

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

Railsで、view内でrenderしてActionView::MissingTemplate in Users#followingとなった時の対処方法

エラー内容 app/views/users/_follow_form.html.erb <%= render @users %> したら ActionView::MissingTemplate in Users#following といわれた 解決策 app/views/users配下に_user.html.erbを作成する 意味 Railsでは、<%= render @hoges %>を書くと自動的に_hoge.html.erbというファイルを読み込んでくれる なので、そのファイルがないとmissing templateになってしまう →作成すればOK 参考サイト Railsガイド レイアウトとレンダリング 【110日目】【1日20分のRailsチュートリアル】【第9章】パーシャルのリファクタリングを行う -はてなブログ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【エラー】Mysql2::Error: Duplicate entry ' ' for key ' '

環境 Ruby 3.0.2 Rails 6.1.4.1 omniauth-google-oauth2 1.0.0 devise 4.8.0 状況 Rspecを実行した際にエラーを吐かれた ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry '100000000000000000000' for key 'users.index_users_on_uid' spec/factories/users.rb FactoryBot.define do factory :user do name { 'test employee' } sequence(:email) { |n| "tester#{n}@example.com" } provider { 'google_oauth2' } uid { '100000000000000000000' } end end 解決法 uidがかぶっていたのでエラーが出ていた。sequenceを使って一意にする spec/factories/users.rb FactoryBot.define do factory :user do name { 'test employee' } sequence(:email) { |n| "tester#{n}@example.com" } provider { 'google_oauth2' } sequence(:uid) { |n| "1#{n}00000000000000000000" } end end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Postモデルについて

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails×Vue】Error: Can't resolve '@smartweb/vue-flash-message' の解決

Rails×VueでSPA化に取り組んでいる初学者の備忘録です。 エラー詳細 Vueを用いてフラッシュメッセージを表示するため@smartweb/vue-flash-messageを導入。 https://www.npmjs.com/package/@smartweb/vue-flash-message/v/next npm i @smartweb/vue-flash-message@next 開発環境で問題なく動作したためherokuにデプロイしようとしたところ次のエラーが発生した。 ModuleNotFoundError: Module not found: Error: Can't resolve '@smartweb/vue-flash-message' in '/tmp/build_7d25413c/app/javascript/packs' at . . . . . Field 'browser' doesn't contain a valid alias configuration resolve as module looking for modules in /tmp/build_7d25413c/app/javascript using description file: /tmp/build_7d25413c/package.json (relative path: ./app/javascript) Field 'browser' doesn't contain a valid alias configuration using description file: /tmp/build_7d25413c/package.json (relative path: ./app/javascript/@smartweb/vue-flash-message) no extension Field 'browser' doesn't contain a valid alias configuration . . . . /tmp/build_7d25413c/app/javascript/@smartweb/vue-flash-message doesn't exist エラー原因 本番環境上で@smartweb/vue-flash-messageが読み込まれていなかった。 package.json "dependencies": { "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "^5.4.3", "@smartweb/vue-flash-message": "^1.0.0-alpha.12", "@vue/babel-plugin-jsx": "^1.1.0", "@vue/compiler-sfc": "^3.2.19", "axios": "^0.22.0", "turbolinks": "^5.2.0", "vue": "^3.2.19", "vue-loader": "^16.8.1", "vue-router": "^4.0.11", "vuex": "^4.0.2" }, package.jsonを見るとdependencesに記載があるので問題ないと思っていたがyarnでプラグインをインストールする必要があった。 解決 一度, プラグインをアンインストールし、 yarn add @smartweb/vue-flash-message@next で再インストール。herokuにデプロイしたところ、package.jsonとyarn.lockが混在していると表示が出たので git rm package-lock.json でyarn.lockを残すようにしてデプロイを実行。 エラーが解決した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ralis] パンクズ機能

ポートフォリオにgretelを使ってパンくずリスト機能を実装した時のメモです。 完成イメージ 実装 まずはGemをインストールします。 gretel 導入 Gemfile gem 'gretel' ターミナル $ bundle install インストールできたらgretelを導入していきます。 ターミナル $ rails g gretel:install configにbreadcrumbs.rbが作成されていれば準備完了です。 パンくずリストの書き方 作成されたconfig/breadcrumbs.rbにパンクズリストを書いていきます。 書き方の基本は、 crumb "ページ名" do link "viewに表示される名前", "URLパス" parent :現在の1つ前のページ end という感じです。 これだけ見てもイマイチ分かりにくいので実装しながら覚えていきましょう! パンくずリスト実装 今回実装する部分は完成イメージのように、 TOPページ → イベント検索ページ → イベント詳細ページのような流れでパンくずリストを作成していきます。 TOPページ config/breadcrumbs.rb crumb :root do link 'TOP', root_path end 検索ページ config/breadcrumbs.rb crumb :event_search do link "イベントを探す", events_search_path parent :root end parentに最初に設定したrootを設定することでTOPページに戻れます。 イベント詳細ページ config/breadcrumbs.rb crumb :event_show do |event| link event.name, event_path(event) parent :event_search, event end 詳細ページにはイベントのidを含める必要があるのでブロック変数を使って記述をします。 また、ブロック変数を使うことでevent.nameのようにしてイベント名でリンク名を設定することができます。 Viewに反映 あとは設定したパンくずリストをviewに反映していきます。 設定したページ名で反映できます。 検索ページ部分 - breadcrumb :event_search = breadcrumbs separator: " &rsaquo; " 詳細ページ部分 - breadcrumb :event_show, @event = breadcrumbs separator: " &rsaquo; " ブロック変数で定義した部分にはインスタンス変数を渡す必要があるので忘れずに設定しましょう! 最後にスタイル調整をすれば完成です。 breadcrumbsというクラスがデフォルトで設定されているので、そこにスタイルを当てていきます。 .breadcrumbs { margin-top: 15px; font-size: 15px; &.breadcrumbs a { color: red; &:hover { opacity: 0.5; } } } これで完成!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

webmockのgem使用時にhashdiffの警告ログが吐かれる

環境情報 Ruby 2.6.1 Rails 5.2.6 起きたこと webmockのgemを使用しているRailsプロジェクトを rails s などで立ち上げた際、下記ログが吐き出される The HashDiff constant used by this gem conflicts with another gem of a similar name. As of version 1.0 the HashDiff constant will be completely removed and replaced by Hashdiff. For more information see https://github.com/liufengyun/hashdiff/issues/45. 解決方法 ログの内容にもあるように、hashdiffを1.0.0以上に更新する ただ、webmockの最新(3.14.0)でもhashdiffの最低バージョンは0.4.0になっていたため、Gemfileに追記する gem "hashdiff", ">= 1.0.0" その後、 bundle update hashdiff でgemを更新すれば解消されます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rspecでhave_receivedのテストにハマった~subjectはit内で必ず呼び出さなければいけない~

はじめに 何回も同じ失敗を繰り返さないように自分を戒めるためのメモ書きです テストが通らない テストしたメソッド def user_update ~何らかの処理~ update(params: params) end 記述したテスト let_it_be(:user) { create(:user) } describe "#user_update" do subject{ user.user_update } before do allow(user).to receive(:update).whith(params: anything) end it { expect(user).to have_received(:update).with(params: anything).once } end 実行結果 expected: 1 time with arguments: ({:params=>anything}) received: 0 times 調査 メソッドの最初とupdateの直後にpryして調査したところ、updateは実行されており、allowかitに問題があるのではないかと踏んで、subject!にしたり、beforeをdescribeの上に記述したりしたが、同じエラーが出てしまった 動いたテスト let_it_be(:user) { create(:user) } describe "#user_update" do subject{ user.user_update } before do allow(user).to receive(:update).whith(params: anything) end it do expect(subject).to eq(nil) #subjectを呼び出している expect(user).to have_received(:update).with(params: anything).once end end 解説 subjectはit内で呼び出さなければ実行されず、今回のようにテストが通らない is_expected.toは暗黙的にsubjectを呼び出しているので、subjectを別で呼び出す必要はないが、expect(user)はsubjectを呼び出していないので、別途呼び出す必要がある もちろん、subjectを呼び出せばいいので、expect(subject)ではなく、subject単体で呼び出したほうが簡潔な記述かもしれない it do subject #subjectを呼び出している expect(user).to have_received(:update).with(params: anything).once end まとめ subject { ◯◯ } は単に定義しているだけで、別途呼び出さないと実行されない is_expected.toは暗黙的にsubjectを呼んでいる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

belongs_toのバリデーションについて

以下のような中間テーブルを作成した。 tagging.rb class Tagging < ApplicationRecord belongs_to :post belongs_to :programming_language end schema.rb create_table "taggings", force: :cascade do |t| t.bigint "post_id" t.bigint "programming_language_id" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["post_id"], name: "index_taggings_on_post_id" t.index ["programming_language_id"], name: "index_taggings_on_programming_language_id" end add_foreign_key "taggings", "posts" add_foreign_key "taggings", "programming_languages" このテーブルに対するバリデーションテストを作成している時、現在何もバリデーションを実行するコードを書いていないのにバリデーションが発生したので原因を探った。 belongs_to単体のデフォルト設定について 今回の場合のように単体で用いた場合外部キーカラムに値を設定していなければバリデーションが実行される。 optional: true このoptionを渡すことによって外部キーカラムに値が入っていなかったり関連先に存在しないidを設定してもバリデーションが発生しない。 required: false optional: trueと同様の機能を果たすオプションとしてrequired: falseが存在する。 これは、Rails5以前ではデフォルトでrequired: falseになっていたもので当時は外部キーに対するバリデーションを行うためにこのオプションをtrueにしなければならなかった。 しかし、dhh曰くこの挙動はおかしいのでデフォルトでtrueにしようとなったが、デフォルトでtrueとなるオプションはダメなんじゃないの?ということでoptionalという代替オプションが誕生したっぽい これがその時のissue?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【omniauth-google-oauth2+devise】Rspecのリクエストテスト

deviseのみ使用時のリクエストテストではsign_in userでログインできていたが、 omniauth-google-oauth2+devise使用時のテストの書き方で少し苦戦したので書き残す。 環境 Ruby 3.0.2 Rails 6.1.4.1 やり方 sign_inが使えるようにdeviseでのテストの設定をする omniauth-google-oauth2が使用できるように設定 spec/rails_helper.rb RSpec.configure do |config| config.include Devise::Test::IntegrationHelpers, type: :request end OmniAuth.configure do |c| c.test_mode = true c.mock_auth[:google_oauth2] = OmniAuth::AuthHash.new({ "provider" => "google_oauth2", "uid" => "100000000000000000000", "info" => { "name" => "test employee", "email" => "tester1@example.com", "first_name" => "test", "last_name" => "employee" } }) end spec/requests/XXXXX_spec.rb RSpec.describe "/XXXXX", type: :request do describe 'GET /XXXXX' do subject { get '/XXXXX', params: { provider: "google_oauth2" } } before do Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2] sign_in user subject end it 'レスポンスが200を返すこと' do expect(response).to have_http_status(200) end end 参考
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【Rspec】共通処理concernsのテストはStructを使うといい

環境 Ruby 3.0.2 Rails 6.1.4.1 Structについて Rspecで共通処理concernsのメソッドテストを実行する際はStructを使うとテストできる 下記のようなconcernsがあったとする concerns/name_arrangeable.rb module NameArrangeable def full_name "#{first_name} #{last_name}" end end 1. Structを使ってテスト用のクラスをつくる。moduleをincludeしておく 2. 作ったクラスをnewする →name_arrangeable.full_nameでメソッドを呼び出して使えるようになる spec/models/name_arrangeable_spec.rb let!(:module_test_class) { Struct.new(:name_arrangeable) { include NameArrangeable } } let!(:name_arrangeable) { module_test_class.new } describe '#full_name' do name_arrangeable.full_name ... end 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumの値によって遷移先を変えたい

はじめに 現在プログラミング学習を始めて3ヶ月目の初学者です。 学んだことをqiitaに投稿という形でアウトプットするため、また備忘録として記事を作成しました。 考えた方法 if文で条件分岐 if文 パッと思いつくほどif文を使いこなせていないので、とにかく調べる https://kurose.me/rails-enum-login/ こちらの記事を参考にして 作ったif文がこちら def create @task = Task.new(task_params) respond_to do |format| if @task.save format.html { if @task.category(0) == "時間が決まったタスク" redirect_to time_path elsif @task.category(1) == "よく使うタスク" redirect_to every_path elsif @task.category(2) == "たまたま行ったタスク" redirect_to by_chance_path elsif @task.category(3) == "ToDo" redirect_to todo_path else render :root end } format.json { render :time, status: :created, location: @task } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @task.errors, status: :unprocessable_entity } end end end ※controllerはscaffoldコマンドで作成 わかっていたがエラー ArgumentError in TasksController#create wrong number of arguments (given 1, expected 0) 引数の値が0を期待しているが、1つ送られてきてる  記事をよく確認したら引数入れてなかった! なのでcategoryの引数を消したら無事遷移できました! 最後に qiitaへの投稿に慣れていないので、分かりづらい上に情報が少ないですが、これから欠かさず投稿していこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails CSRF対策について

CSRFとは Cross Site Request Forgeryのことで、 Webアプリに知らない間にリクエストが送られる不正攻撃のことです 例えばインスタで知らない間に自分の投稿を消されていたりしたら CSRFです。 CSRF対策の設定 application_controller.rbに下のような記述をすると、 Can't verify CSRF token authenticity というエラーが出てきます。このエラーが出たらCSRFのエラーで Railsがちゃんとしたリクエストがいっているかどうかを CSRF Tokenというもので 確認するのですがそれが確認できないというエラーです。 application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception end 解決策 application_controller.rbに下のような記述をします。 application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: null_session end これは公開する時にはあまり役に立たないです。CSRF対策をOFFにしてしまうからです。 他にも class ApplicationController < ActionController::Base skip_before_action :verify_authenticity_token end このような方法もあるみたいです。 CSRF 対策を放棄する時に使います。 最後に 上のやり方はあくまでエラーを解決するための方法です。 アプリを公開したりする場合は、CSRF Tokenの設定が必要になります。 下の参考資料に載っているので調べてみてください。 ⚫︎参考資料 https://midorimici.com/posts/rails-api-csrf
  • このエントリーをはてなブックマークに追加
  • 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 + Vue】変更が反映されない!?と思ったら bin/webpack-dev-server 忘れだった人の記事(foremanで一元管理する)

久々に開発を再開しようとしたら、変更したコードがビューに反映されなくて、てんやわんやしました。 Rails6 + Vue3で、webpackerを使って環境構築しており、rails sと bin/webpack-dev-serverを、それぞれ手動で立ち上げていたことをすっかり忘れていただけだったのですが、いっそまとめて管理しようと心に誓いましたので、まとめます。 gem foreman を使ってみる インストールする gem foreman を使って一元管理を試みます。アプリケーション実行の際のコマンドをまとめて管理できるという、ありがたgemです。 Gemfileに書いて bundle install しようとしましたが、この方法は非推奨とのこと。なので、以下を実行しましょう。 $ gem install foreman Procfileを新たに作成する foremanは、Procfile の内容を継承するようになっているので、ここにコマンドを書いていきます。アプリケーション直下にProcfileという名前のファイルを作成します。拡張子はいりません。 rails: rails s webpack: bin/webpack-dev-server 左側の名前は自由に変えてOK、右側が立ち上げる時に打っているコマンドですね。 Procfileの準備ができたら、あとは以下を打つだけで2つ同時に立ち上がります! $ foreman start ログも、1つのターミナルに2つまとめて表示されます。 02:48:15 rails.1 | started with pid 74544 02:48:15 webpack.1 | started with pid 74545 02:48:16 rails.1 | => Booting Puma 02:48:16 rails.1 | => Rails 6.0.4 application starting in development 02:48:16 rails.1 | => Run `rails server --help` for more startup options 02:48:17 webpack.1 | ℹ 「wds」: Project is running at http://localhost:3035/ 以上。ちょっと平和になったかもしれない記事でした。 少しでも役に立つことがあればうれしいです。
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む