20211015のRailsに関する記事は10件です。

collectionとmemberについて!

①.結論! collectionとmemberは、ルーティングを設定する際に使用でき、生成されるURLとコントローラーを 任意にカスタムできるメソッドのことです! collectionはルーティングに:idがつかない! memberは:idがつくという違いがあります! 1つずつ使用例を見ていきましょう! ②.collection collectionで定義した場合は、以下のように例になります! routes.rb Rails.application.routes.draw do resources :eats do collection do get 'search' end end end collectionのルーティング Prefix Verb URI Pattern search_eats GET /eats/search(.:format) eats#search ルーティングに:idが付いていないことがわかります! 続いてmemberの場合を見てみましょう! ②member. routes.rb Rails.application.routes.draw do resources :eats do member do get 'search' end end end memberのルーティング Prefix Verb URI Pattern search_eat GET /eats/:id/search(.:format) eats#search URLの指定先が、collectionは:idなし、memberが:idありとなっていることが確認できます! 例のような検索機能の場合、詳細ページのような:idを指定して特定のページにいく必要がないため、 collectionを使用してルーティングを設定します! ③.まとめ 詳細ページなどidが必要な際はmember、必要ない時はcollectionと言う事ですね! ルーティングに対して、「このパスは可読性が高いか」などの視点も非常に大切だと思いました! collectionやmemberもしっかりと理解して行こうと思います! 何か説明で間違っていたらご指導お願い致します(_ _)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devise_token_authの使い方

devise_token_authとは 新規登録、ログイン、ログアウトなどの機能を実装する時に、 使用し、トークン認証という、ユーザー認証ができるgemです。 この記事ではdevise_token_authの設定を説明していきます。 Gemfileの設定 まずはGemfileの設定からです。 下のgemをGemfileに追記します。 gem "devise" gem "devise_token_auth" gem 'rack-cors'    #URLのアクセスが重複することを許可してくれる。 その後bundle installをします。 インストール 下のコマンドでインストールします。 rails g devise:install でdeviseをインストールします。 rails g devise_token_auth:install User auth devise_token_authをインストールします。 authは認証のことです。 認証機能を実装 環境設定は終わったので、認証機能を実装をします。 まずは認証コントローラーを実装します。 rails g controller api/v1/registrations すると、api/v1/auth/registrations_controller.rbが作成されます。 classの部分を以下のように修正します。 api/v1/auth/registrations_controller.rb class Api::V1::Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController end ルーティング設定 api/v1/authでコントローラーを作ったので、ルーティングは下のようになります。 config_routes.rb Rails.application.routes.draw do namespace :api do namespace :v1 do mount_devise_token_auth_for 'User', at: 'auth', controllers: { registrations: 'api/v1/auth/registrations' } end end end ⚫︎補足 namespaceはapi/v1の名前空間を作る時に使います。Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'auth'とは サインインやサインアウトなどの認証ができるようにするためのルーティングです。 api/v1/registration_controller.rbをmount_devise_token_auth_for 'User', at: 'auth'で 認証するためという意味のコードです。 devise_token_auth.rbの設定 initializerの中にdevise_token_auth.rbというファイルがありますが、 ここを公式ドキュメントを参照して必要なコードをコメントアウトしていきます。 https://devise-token-auth.gitbook.io/devise-token-auth/config/initialization config/initializers/devise_token_auth.rb DeviseTokenAuth.setup do |config| #リクエストごとに、headersの情報を変える。 config.change_headers_on_each_request = false #trueからfalseに変更 #リクエストごとにheadersが変わるのはかなり面倒。 #ユーザーは2週間後に再認証する必要があり、 #トークンが発行された後、トークンが有効であり続ける期間が2週間とする        config.token_lifespan = 2.weeks   # headersに含まれている名前を下のように変える config.headers_names = { 'access-token': "access-token", client: "client", expiry: "expiry", uid: "uid", 'token-type': "token-type" } end 最後に この辺を実装するとログイン機能は作れましたが、 状況によって実装内容が変わることがあるのでコピペだけにはならないようにお願いします。 あくまで参考資料としてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションエラーを日本語にする

目的 ここでは、バリデーションエラーを日本語にする方法の大枠を記述します。(モデル名以外) ちなみに、モデル名やdeviseのエラー文を日本語設定したい場合は、参考文献からのリンク記事を参考にしてください! 開発環境 rails6.0.0 mysql 参考文献 こちらを参考にさせていただいております! 本当にわかりやすくてありがとうございました!! ■Railsのバリデーションエラーのメッセージの日本語化 →モデル名まで日本語にしたい方は、こちらをご参考ください! ■translation missing: ja.activerecord〜のエラー対応 ■https://github.com/tigrish/devise-i18n/blob/master/rails/locales/ja.yml →deviseのバリデーションエラーの日本語化は、上記2つをご参考ください! エラーメッセージを日本語にする ① gem 'rails-i18n' gemを使います! Gemfileに上記記述を行い、bundle installする。 ② config.i18n.default_locale = :ja config/application.rbにて上記記述すれば完了! サーバー再起動すれば、エラーメッセージが日本語になります。 注意点 日本語になるため、テストコードもそれに応じて日本語に変更をしましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリアル第八章 基本的なログイン機構

今回はユーザーがログインやログアウトを行う。 本章では、ログインの基本的な仕組みを実装 ブラウザがログインしている状態を保持し、ユーザーによってブラウザが閉じられたら状態を破棄するといった仕組み ログイン済みのユーザー(current user)だけがアクセスできるページや、扱える機能などを制御していきます。 このような制限や制御の仕組みを認可モデル(Authorization Model)という。 セッション ユーザーログインの必要なWebアプリケーションでは、セッション(Session)と呼ばれる半永続的な接続をコンピュータ間(ユーザーのパソコンのWebブラウザとRailsサーバーなど)に別途設定します。 Railsでセッションを実装する方法として最も一般的なのは、cookiesを使う方法 ....。 新しいブランチを作る $ git checkout -b basic-login Sessionsコントローラ ここではnewアクションを作る ubuntu:~/environment/sample_app (basic-login) $ rails generate controller Sessions new Running via Spring preloader in process 9802 create app/controllers/sessions_controller.rb route get 'sessions/new' invoke erb create app/views/sessions create app/views/sessions/new.html.erb invoke test_unit create test/controllers/sessions_controller_test.rb invoke helper create app/helpers/sessions_helper.rb invoke test_unit invoke assets invoke scss create app/assets/stylesheets/sessions.scss ファイルを作成 リソースを追加して標準的なRESTfulアクションをgetできるようにする config/routes.rb Rails.application.routes.draw do get 'sessions/new' root 'static_pages#home' # 何の意味がわからん # ホーム画面がhomeページなるらしい get '/help', to: 'static_pages#help' # static_pagesコントローラからhomeアクションに紐付けされる # getでアクセルすることでページを取得することができる get '/about', to: 'static_pages#about' # 何を書いているかはわからない get '/contact', to: 'static_pages#contact' # aboutアクションにGETリクエストを送る get '/signup', to: 'users#new' # urlに/signupと書くとuserコントローラのnewアクションを起こす get '/login', to: 'sessions#new' # データを取得 post '/login', to: 'sessions#create' # データを投稿 delete '/logout', to: 'sessions#destroy' # 削除 resources :users # /users/1の有効にするため end リクエスト url   名前付きルート アクション   用途 GET    /login   login_path   new      新しいセッションのページ(ログイン) POST   /login   login_path   create     新しいセッションの作成(ログイン) DELETE  /logout  logout_path   destroy     セッションの削除(ログアウト) rails routes login GET /login(.:format) sessions#new POST /login(.:format) sessions#create logout DELETE /logout(.:format) sessions#destroy 演習 1.GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。 GET login_pathはloginページを表示させる POST login_pathはloginのためにデータを送信する 2.ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか? ヒント: パイプやgrepの使い方が分からない場合は 『コマンドライン編』の 「grepで検索する」を参考にしてみてください。 ubuntu:~/environment/sample_app (basic-login) $ rails routes | grep sessions# sessions_new GET /sessions/new(.:format) sessions#new login GET /login(.:format) sessions#new POST /login(.:format) sessions#create loguniELETE /logout(.:format) sessions#destroy 何がしたいのかわからない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリアル第七章 プロのデプロイ

プロのデプロイ $ git add -A $ git commit -m "Finish user signup" $ git checkout master $ git merge sign-up 本番環境でのSSL ユーザー情報が流出する可能性がある。 ネットワークに流れる前に情報を暗号化させる技術TLS(略称)がある。 今回はユーザー登録ページのためだけにSSLを導入できる。以下利点 ・Webサイト全体で適用 ・ログイン機構をセキュア ・セッションハイジャック(Session Hijacking)の脆弱性に対しても多くの利点 SSLを有効化するのも簡単です。production.rbという本番環境の設定ファイルの1行を修正するだけで済みます。 configに「本番環境ではSSLを使うようにする」という設定をするだけです 演習 1.ブラウザから本番環境(Heroku)にアクセスし、SSLの鍵マークがかかっているか、URLがhttpsになっているかどうかを確認してみましょう。 確認 2.本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか? 作成 成功
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリアル第七章 実際のユーザー登録 成功時のテスト

実際のユーザー登録 今まで実験でユーザー登録をしているので一度リセット $ rails db:migrate:reset rails sでサーバーを起動させる。 新しくユーザー情報を登録し、flashのメッセージを表示させる 再読み込みするとメッセージがなくなる。 演習 1.Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.30のようになるはずです。 >> User.all User Load (0.2ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]] => #<ActiveRecord::Relation [#<User id: 1, name: "a", email: "abc@def.com", created_at: "2021-10-05 04:11:41", updated_at: "2021-10-05 04:11:41", password_digest: [FILTERED]>]> 2.自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください 上と同じことだと思ってやらなかった。 成功時のテスト 有効なユーザー登録に対するテスト test/integration/users_signup_test.rb require 'test_helper' . . . test "valid signup information" do get signup_path assert_difference 'User.count', 1 do # 一つだけしか登録されてないことを確かめるのかな? # 第二引数でデータベースとの差異は1である。 post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect! # POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド assert_template 'users/show' # showアクションのビューを表示させる。 end end 演習 1.7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.32に最小限のテンプレートを用意しておいたので、参考にしてください((コードを書き込む)の部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest . . . test "valid signup information" do get signup_path assert_difference 'User.count', 1 do # # 第二引数でデータベースとの差異は1である。 post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect! # POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド assert_template 'users/show' # showアクションのビューを表示させる。 assert_not flash.nil? #flashがnilクラスじゃないよね? flashはハッシュだからかな? end end 2.本文中でも指摘しましたが、flash用のHTML(リスト 7.29)は読みにくいです。より読みやすくしたリスト 7.33のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。 <!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= render 'layouts/rails_default' %> <%= render 'layouts/shim' %> </head> <body> <%= render 'layouts/header' %> <div class="container"> <% flash.each do |message_type, message| %> <%= content_tag(:div, message, class: "alert alert-#{message_type}") %> <!--これがデータ送信成功時メッセージのためのHTML--> <!--message_typeにsuccess, messageに成功時メッセージ--> <% end %> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> <!--デバッグ情報はRailsの3つのデフォルト環境のうち、開発環境(development)だけで表示されるようになります--> </div> </body> </html> 3.リスト 7.26のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。 ERROR["test_valid_signup_information", #<Minitest::Reporters::Suite:0x000055cc8f4de6d8 @name="UsersSignupTest">, 1.726447963000055] test_valid_signup_information#UsersSignupTest (1.73s) RuntimeError: RuntimeError: not a redirect! 204 No Content test/integration/users_signup_test.rb:30:in `block in <class:UsersSignupTest>' 21/21: [==============================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.80173s 21 tests, 41 assertions, 0 failures, 1 errors, 0 skips RuntimeError: not a redirect! 204 No Content エラーの意味がちょっとわからない。 4.リスト 7.26で、@user.saveの部分をfalseに置き換えたとしましょう(バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。 ubuntu:~/environment/sample_app (sign-up) $ rails t Running via Spring preloader in process 6196 Started with run options --seed 35811 FAIL["test_valid_signup_information", #<Minitest::Reporters::Suite:0x000055cc8fc47208 @name="UsersSignupTest">, 1.7643740539997452] test_valid_signup_information#UsersSignupTest (1.76s) "User.count" didn't change by 1. Expected: 1 Actual: 0 test/integration/users_signup_test.rb:22:in `block in <class:UsersSignupTest>' 21/21: [==============================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.87888s 21 tests, 41 assertions, 1 failures, 0 errors, 0 skips "User.count" didn't change by 1. Expected: 1 Actual: 0 @user.saveで保存してないから0になるからかな?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】モノやサービスを供養できるアプリを作ってみた。

はじめに ・買ったばかりのスマホを落として画面バキバキになってしまった ・サポート終了になるInternetExplorer ・元カレのくれたプレゼント 「どうしよう…」 そのあと湧き上がる 「やり場のない気持ちをぶつけたい」 みなさんにもそんな時があると思います。 お世話になったモノやサービスに敬意を込めて供養してあげる。 とても慈悲深いアプリが出来ました。 サービス概要 投稿されたタイトルをクリックすると、専用のお墓に見立てた位牌が鎮座しています。 「御参り」ボタンを押すと黙祷タイムです。 きちんと御参りができればTwitterシェア出来ます。 シェアすることで他の方から気持ちを可視化したと気付いてもらうことができます。 ヘッダーからタイトル検索もできます。 こだわったポイント 1.見た目 「供養」と聞くと思い浮かぶのは大抵の場合ですと神社・仏閣・お墓でしょう。 そんな厳かなイメージを崩さないためにカラーは黒を基調としています。桜も舞います。 2.形態素解析 特定の人の名前が入ると色々と問題になる可能性があるので【人名】はタイトル部分で対象外としました。 mecabというgemと、mecab-ipadic-NEologdというmecabのシステム辞書を用いています。 また、辞書に登録されていない珍しい名前の方を除外するため別途でcsvファイル作成し読み込んでいます。 参考: https://qiita.com/sho-jp/items/50243956373f21b1e138 3.動的OGP出力 供養されたのが何なのかを分かるようにするために固定ではなく動的に反映される様にしました。 Cloudinaryを使っています。 参考: https://qiita.com/yuppymam/items/21a65d32fed9d7ecceba 今後実装したいこと 焼香か線香のlikeボタン 投稿のジャンル分け 主な技術 Ruby 2.7.3 Rails 6.1.4 JavaScript jQuery 主なGem cloudinary mecab natto pagy meta-tags rubocop おわりに 最後まで目を通していただきありがとうございました。 色々な方からアドバイスをいただけたり、一緒に考えてもらいながら作り終えることが出来ました。 本当に感謝しています。 これからも技術を追求してより良いアプリを作れるように頑張っていきます。 ありがとうございました。 twitter: https://twitter.com/Find_michi お盆に間に合わなかった為、きちんと供養しておきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

商品購入機能の実装 〜「商品購入ページへの遷移」の実装①〜

はじめに 先日、最終課題アプリ作成において「商品購入機能」の実装を開始しました。 最大のヤマ場といわれており、恐らく実装に時間がかかると思います。 そこで、これまで自身が学んできた内容や考えた過程などを、記事として残していこうと思います。駄文・長文になること請け合いですが、ご覧いただけると幸いです。 ただし、現時点での私の知識・技術についてはプログラミングという大海原に片足の親指を突っ込んでちょんちょん水面を揺らしている程度(だと思っている)ので、「それ、ちょっと間違っているよ!」というご指摘や、「こうした方がいいよ!」というご意見をいただけると、非っっっっっ常ありがたいです。泣きながら土下座して喜びます。 「商品購入ページへの遷移」の実装① とりあえず商品購入ページに遷移できないと始まらないだろ!!! ということで、商品購入ページへ遷移できるようにしました。 見本サイトの商品購入ページのURLを確認すると、 以下のとおりになっていました。 (省略)/items/○○/orders ここで気になったのが、items の後ろに orders と表示されること。 このような表記はこれまでの実装では見られなかったことから、 ordersモデル と itemモデルに対して、 これまでの機能の実装とは別の形で関連づける必要があると考えました。 belongs_to や has_many 、has_one などではないってことですね。 だとすれば、どのように関連づけようか。 ここで、神が舞い降りました。 items の後ろに orders が表示されているから、 itemsモデルが親、ordersモデルが子の関係になっているのでは??? 実際、この形の実装は他機能の実装ではありませんでした。 私が知っていて、かつこの関係に該当するものは、ルーティングのネスト。 早速、下記のとおりにルーティングを記述しました。 ネストとかしばらく使っとらんかったのによく思いついたな自分。 こんなの普段なら思いつかんのやけど。 routes.rb Rails.application.routes.draw do get 'articles/index' get 'articles/new' devise_for :users root to: "items#index" +++ resources :items do ##「do」のみ追加 +++ resources :orders, only: [:index, :create] ##ここを追加 end end 6行目 resources :items に対して「do」を追加したうえで、 7行目 resources :orders をネストしました。 これにより、これ以上テーブルを無駄に増やすことなく、 items と関連づけることができました。 また、現状必要そうなものとして、以下のアクションを追加しておきました。 商品一覧ページを表示させる → :index 購入機能を追加する → :create この後、ターミナルにてrails routesを行い、下のパスを取得できました。 item_orders GET /items/:item_id/orders(.:format) orders#index POST /items/:item_id/orders(.:format) orders#create あとはターミナルで rails g controller orders と入力してorders_controllerを作成し、 orders_controller.rb class OrdersController < ApplicationController def index end def create ##商品購入ページに遷移するだけなら不要 end ##商品購入ページに遷移するだけなら不要 private ##商品購入ページに遷移するだけなら不要 end と記述しました。 その結果、商品詳細ページからURLで直接「/orders」を追加入力することで、 商品購入ページを表示することができました。 「は???リンクから遷移できなきゃでしょ」ってツッコミはやめて! そこ詰まってるところなの!!!(汗) belongs_to や has_many 、has_one でのモデルの関連づけはよくありましたが、 それ以外の方法を…と考えた時、ネストが思い浮かんだのは奇跡でした。 「自分すげーな!!!」ってなりました(笑) 奇跡ではなく、ちゃんと力がついてきているんだと思えるように、 以後の実装も頑張っていきたいと思います! 最後までお付き合いいただき、ありがとうございました?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails RSpec Twitter API モック

概要 Twitter APIを使用したメソッドのモックを作成したい。 モックとは モックとはざっくりいうと 「本物のふりをするニセモノのプログラム」 のことです。 何らかの理由で本物のプログラムが使えない、もしくは使わない方がよいケースでモックが使われます。 たとえば、外部のAPIを利用しなければならない場合です。 実際、このあとで紹介するサンプルプログラムでもTwitterのAPI(gem)経由でツイートするプログラムを使っています。 参考 今回モックを作成したいメソッド # Twitterclient def twitter_client Twitter::REST::Client.new do |config| config.consumer_key = ENV["CONSUMER_KEY"] config.consumer_secret = ENV["CONSUMER_SECRET"] config.access_token = ENV["ACCESS_TOKEN"] config.access_token_secret = ENV["ACCESS_SECRET"] end end # 指定したidのアカウントのツイート検索 def search(id) @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended") end 完成したモック #--- Twitter API用モック describe "Twitter API" do # searchメソッドの引数で必要なid id = "1193544870444429313" it 'twitter_searchメソッドでTwitter::searchオブジェクトを返すこと' do twitter_client_mock = double('Twitter client') #❶ expect(twitter_client_mock).to receive(:user_timeline).with(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended").and_return("#<Twitter::Tweet id=1436988816967933955>") #❷ article = Article.new #❸ expect(article).to receive(:twitter_client).and_return(twitter_client_mock) #❸ expect(article.search(id)).to eq ("#<Twitter::Tweet id=1436988816967933955>") #❹ end end モックの基本的な使い方 ❶空のモックオブジェクトを作成する Twitter::REST::Client の偽物(=モック)を作成する twitter_client_mock = double('Twitter client') #❶ doubleメソッドを使用するとモックオブジェクトを作成できる。 引数で渡す文字列は任意。今回はTwitter clientを使用した。省略も可能。 この文字はテスト失敗の際にメッセージに表示されるので、できるだけわかりやすい名前をつけておくと良い。 こんな感じでエラーに表示される RSpec::Mocks::MockExpectationError: Double "Twitter client" received unexpected なんちゃらかんちゃら~~~ ❷モックに返事の仕方を教える doubleメソッドで作成されたモックは何も知らないので、返事の仕方を教えてあげる。 上にも載せたが今回のアプリケーション側のメソッド部分 def twitter_client Twitter::REST::Client.new do |config| config.consumer_key = ENV["CONSUMER_KEY"] config.consumer_secret = ENV["CONSUMER_SECRET"] config.access_token = ENV["ACCESS_TOKEN"] config.access_token_secret = ENV["ACCESS_SECRET"] end end # 指定したidのアカウントのツイート検索 def search(id) @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended") end searchメソッドが呼び出されると、メソッド内でtwitter_client.user_timelineを呼び出している。 twitter_clientの部分が今回モックに変わる予定なので、モックはuser_timelineというメソッドを呼び出せるようにする必要がある。 その設定をテスト側で行う RSpec側再記 expect(twitter_client_mock).to receive(:user_timeline).with(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended").and_return("#<Twitter::Tweet id=1436988816967933955>") #❷ この形でモックに呼び出し可能なメソッドを設定し、今回は結果部分まで設定した。 expect(モックオブジェクト).to receive(:メソッド名).with(引数部分).and_return(結果) メソッドが呼び出されないとテスト失敗になる。 この場合は、user_timelineが呼び出されないとエラー発生。 この状態ではuser_timeline以外のメソッドを呼び出すことはできない。もし他のメソッドが呼び出されるとエラーが発生し、テストが失敗する。 他のメソッドも呼ばれるのであれば、同じ構文を使用してメソッドを追加する必要がある。 ❸アプリケーションコードにモックを送り込む このままではアプリケーション側のコードとモックは関係性がない。 そこで、アプリケーション側の実装をこっそりモックに置き換える。 それが以下のコード(テスト側に実装) RSpec側再記 article_bot = Article.new #❸ expect(article_bot).to receive(:twitter_client).and_return(twitter_client_mock) #❸ ここで行ったことは アプリケーション側のtwitter_clientメソッドの置き換え。 上にも載せたが今回のアプリケーション側のメソッド部分 def twitter_client Twitter::REST::Client.new do |config| config.consumer_key = ENV["CONSUMER_KEY"] config.consumer_secret = ENV["CONSUMER_SECRET"] config.access_token = ENV["ACCESS_TOKEN"] config.access_token_secret = ENV["ACCESS_SECRET"] end end # 指定したidのアカウントのツイート検索 def search(id) @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended") end twitter_clientメソッドが呼び出されたら、アプリケーション側のTwitter::REST::Client.newでなはくテスト側のtwitter_client_mockを返すように変更した。 expect(実装を置き換えたいオブジェクト).to receive(置き換えたいメソッド名) ❹テストしたいメソッドを呼び出す モック(twitter_client_mock)にはuser_timelineというメソッドが呼ばれることを教え、アプリケーション側(article_bot)は本来の実装を置き換えられてモックを使用するようになった。 あとは普通にアプリケーション側のメソッドを呼び出せばいい。 expect(article_bot.search(id)).to eq ("#<Twitter::Tweet id=1436988816967933955>") #❹ article_botはモックを使用するように❸で設定されているため、実際に実行してもTwitter APIが使用されることはない。 ざっとまとめてみました。 間違い等ありましたらコメントいただけるとありがたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails RSpec Twitter API モックを作成してみた

概要 Twitter APIを使用したメソッドのモックを作成したい。 モックとは モックとはざっくりいうと 「本物のふりをするニセモノのプログラム」 のことです。 何らかの理由で本物のプログラムが使えない、もしくは使わない方がよいケースでモックが使われます。 たとえば、外部のAPIを利用しなければならない場合です。 実際、このあとで紹介するサンプルプログラムでもTwitterのAPI(gem)経由でツイートするプログラムを使っています。 参考 今回モックを作成したいメソッド model.rb # Twitterclient def twitter_client Twitter::REST::Client.new do |config| config.consumer_key = ENV["CONSUMER_KEY"] config.consumer_secret = ENV["CONSUMER_SECRET"] config.access_token = ENV["ACCESS_TOKEN"] config.access_token_secret = ENV["ACCESS_SECRET"] end end # 指定したidのアカウントのツイート検索 def search(id) @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended") end 完成したモック RSpec側 #--- Twitter API用モック describe "Twitter API" do # searchメソッドの引数で必要なid id = "1193544870444429313" it 'twitter_searchメソッドでTwitter::searchオブジェクトを返すこと' do twitter_client_mock = double('Twitter client') #❶ expect(twitter_client_mock).to receive(:user_timeline).with(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended").and_return("#<Twitter::Tweet id=1436988816967933955>") #❷ article_bot = Article.new #❸ expect(article).to receive(:twitter_client).and_return(twitter_client_mock) #❸ expect(article.search(id)).to eq ("#<Twitter::Tweet id=1436988816967933955>") #❹ end end モックの基本的な使い方 ❶空のモックオブジェクトを作成する Twitter::REST::Client の偽物(=モック)を作成する RSpec側再記 twitter_client_mock = double('Twitter client') #❶ doubleメソッドを使用するとモックオブジェクトを作成できる。 引数で渡す文字列は任意。今回はTwitter clientを使用した。省略も可能。 この文字はテスト失敗の際にメッセージに表示されるので、できるだけわかりやすい名前をつけておくと良い。 こんな感じでエラーに表示される RSpec::Mocks::MockExpectationError: Double "Twitter client" received unexpected なんちゃらかんちゃら~~~ ❷モックに返事の仕方を教える doubleメソッドで作成されたモックは何も知らないので、返事の仕方を教えてあげる。 上にも載せたが今回のアプリケーション側のメソッド部分 def twitter_client Twitter::REST::Client.new do |config| config.consumer_key = ENV["CONSUMER_KEY"] config.consumer_secret = ENV["CONSUMER_SECRET"] config.access_token = ENV["ACCESS_TOKEN"] config.access_token_secret = ENV["ACCESS_SECRET"] end end # 指定したidのアカウントのツイート検索 def search(id) @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended") end searchメソッドが呼び出されると、メソッド内でtwitter_client.user_timelineを呼び出している。 twitter_clientの部分が今回モックに変わる予定なので、モックはuser_timelineというメソッドを呼び出せるようにする必要がある。 その設定をテスト側で行う RSpec側再記 expect(twitter_client_mock).to receive(:user_timeline).with(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended").and_return("#<Twitter::Tweet id=1436988816967933955>") #❷ この形でモックに呼び出し可能なメソッドを設定し、今回は結果部分まで設定した。 expect(実装を置き換えたいオブジェクト).to receive(置き換えたいメソッド名).with(引数部分).and_return(返却したい値やオブジェクト) メソッドが呼び出されないとテスト失敗になる。 この場合は、user_timelineが呼び出されないとエラー発生。 この状態ではuser_timeline以外のメソッドを呼び出すことはできない。もし他のメソッドが呼び出されるとエラーが発生し、テストが失敗する。 他のメソッドも呼ばれるのであれば、同じ構文を使用してメソッドを追加する必要がある。 ❸アプリケーションコードにモックを送り込む このままではアプリケーション側のメソッドとRSpec(テスト)側のモックは関係性がない。 そこで、アプリケーション側の実装をこっそりモックに置き換える。 それが以下のコード(テスト側に実装) RSpec側再記 article_bot = Article.new #❸ expect(article_bot).to receive(:twitter_client).and_return(twitter_client_mock) #❸ ここで行ったことは アプリケーション側のtwitter_clientメソッドの置き換え。 上にも載せたが今回のアプリケーション側のメソッド部分 def twitter_client Twitter::REST::Client.new do |config| config.consumer_key = ENV["CONSUMER_KEY"] config.consumer_secret = ENV["CONSUMER_SECRET"] config.access_token = ENV["ACCESS_TOKEN"] config.access_token_secret = ENV["ACCESS_SECRET"] end end # 指定したidのアカウントのツイート検索 def search(id) @tweets = twitter_client.user_timeline(user_id: id, count: 1, exclude_replies: false, include_rts: false, contributor_details: false, result_type: "recent", locale: "ja", tweet_mode: "extended") end twitter_clientメソッドが呼び出されたら、アプリケーション側のTwitter::REST::Client.newでなはくテスト側のtwitter_client_mockを返すように変更した。 expect(実装を置き換えたいオブジェクト).to receive(置き換えたいメソッド名).and_return(返却したい値やオブジェクト) ❹テストしたいメソッドを呼び出す モック(twitter_client_mock)には(user_timeline)というメソッドが呼ばれることを教えた。 アプリケーション側(article_bot)は本来の実装を置き換えられてモックを使用するようになった。(article_bot = Article.newの部分) あとは普通にアプリケーション側のメソッドを呼び出せばいい。 RSpec側再記 expect(article_bot.search(id)).to eq ("#<Twitter::Tweet id=1436988816967933955>") #❹ article_botはモックを使用するように❸で設定されているため、実際に実行してもTwitter APIが使用されることはない。 ざっとまとめてみました。 間違い等ありましたらコメントいただけるとありがたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む