20200114のRailsに関する記事は11件です。

ArgumentError in GroupsController#create ,wrong number of arguments (given 0, expected 1)の一例

1. どんなエラー?

「GroupsControllerのcreateの部分で引数が1つ返ってくると予想される(記述されている)のに、実行時には0件でしたよ」というエラーです

<エラー文>
スクリーンショット 2020-01-14 22.14.02.png

<エラーに関係したファイル>

groups_controller.rb
class GroupsController < ApplicationController
  def index
  end

  def new
    @group = Group.new
    @group.users << current_user
  end

  def create
    @group = Group.new(group_params)
    if @group.save
      redirect_to root_path, notice:'グループを作成しました'
    else
      render :new
    end
  end

  def edit
    @group = Group.find(params[:id])
  end

  def update
    @group = Group.find(params[:id])
    if @group.update(group_params)
      redirect_to root_path, notice: 'グループを更新しました'
    else
      render :edit
    end
  end

  private
  def group_params
    params.require.permit(:name,user_ids:[])
  end
end

2.原因と解決方法

まず結論から言いますと、'require'の後ろにハッシュ指定がないため、'(:group)'をつけることにより解決します。

groups_controller.rb
  params.require(:group).permit(:name,user_ids:[])

3.原因の見つけ方

 このエラーに対する正攻法として、引数の数がおかしいというエラーに対し、じゃあ〇〇行目(今回は35行目)では何を呼び出すことが可能な状態なのかを調べるという方法があります。(実践方法は下記に記載しますので必要な方はご覧いただければと思います。)

 調べた内容より、間違いの確認と、呼び出したい内容がどのハッシュに格納されているか確認ができます。これにより'require'で呼び出すハッシュを決めることができます。

(※requireを使用するときは多重ハッシュとなっている場合となるため、値の直前のハッシュを指定します。これを使わないと値を格納してほしいところにハッシュ値が入ってしまいエラーとなります。今回の例がまさにそれに当たります。)

<実践方法>
①pry-rails(デバック(確認)用のツール)をインストールするため、Gemfileの最後の行に下記のコマンドを追記する

gem 'pry-rails'

②作業中のファイルのディレクトリで bundle installする(このコマンドがわからない方は検索をかけてもらえばいっぱい出てきます)

Neverland:chat-space-kai kontatomoya$ bundle install

③指定の行を改行してその行に binding.pry を記述する(筆者の場合35行目を改行して、新しい35行目に書きます)

groups_controller.rb
#1~32行目は上記の内容から変化がないので省略
33 private
34   def group_params
35     binding.pry
36     params.require.permit(:name,user_ids:[])
37   end
38 end

④rails s します

Neverland:chat-space-kai kontatomoya$ rails s

⑤エラーを吐いてしまうページにつなげます。(筆者の場合は下記の写真の登録するボタンを押すとエラーページ繋がります。)すると、次のページに飛ばずに読み込み中のままとなります。

スクリーンショット 2020-01-14 22.53.53.png

⑥この状態のままターミナルを確認します。すると下記のようになっています。

#上にもっと記述が出ますが直接関係ないので省略します

From: /Users/kontatomoya/projects/chat-space-kai/app/controllers/groups_controller.rb @ line 35 GroupsController#group_params:

    34: def group_params
 => 35:   binding.pry
    36:   params.require.permit(:name,user_ids:[])
    37: end

[1] pry(#<GroupsController>)> 

⑦ここで [1] pry(#<GroupsController>)>  の隣に params と打ちましょう。すると下記のような回答が返ってきます

#続き
[1] pry(#<GroupsController>)> params   #paramsを打ちました
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"/vrx3ZW1g+kcEgeh8V+FJlqUnBNEIR9jyvGeSl1r22IcMk+1F0I7zRhnKKXWpLNMtwjbmWxBBBaNR9phNk3KOg==", 
"group"=>{"name"=>"", "user_ids"=>["", "2"]},     ←この部分だけ{}で囲まれており、二重(多重)ハッシュとなっています
"commit"=>"登録する", "controller"=>"groups", "action"=>"create"} permitted: false>
[2] pry(#<GroupsController>)> 

 まずこれを見ると呼び出すことができるハッシュがわかります。その中でハッシュ'group'のハッシュだけ2重ハッシュ(ActionController::Parametersに二回囲まれている状態)となっております。
 今回筆者はその中の"name", "user_ids"がほしいと考えています。よって、こちらで'permit'したいハッシュ(:name,:user_ids[])の一つ上のハッシュ(今回は'group')を要求すれば良いとわかるいう流れとなります

(※今回の場合そのほかの値が欲しければ'require'をつけずに'permit(:先ほど調べたハッシュ名)'とすればエラーは無くなります。)

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

【翻訳】Testing best practices - GitLab

GitLabのテストにおけるベストプラクティス記事を日本語訳したものです。

https://docs.gitlab.com/ee/development/testing_guide/best_practices.html

最終閲覧日時: 2020/01/06

途中、おかしな日本語があるかもしれませんので原文と照らし合わせてお読みください

Test Design

GitLabではテストは最優先事項[1]です。機能の設計と同様に、テストの設計を考慮することは重要と考えています。

機能を実装するとき、我々は適切な機能を適切な方法で開発することを検討します。これにより、範囲を管理可能なレベルに絞り込むことができます。機能のテストを実装する場合、適切なテスト実装を検討する必要がありますが、重要な箇所をすべてカバーするテストは難しく、すぐに管理困難な段階にまで拡大するでしょう。

テストヒューリスティック[2]は、この問題の解決に役立ちます。これは、我々のコードに潜むバグを明らかにし、簡潔に対処します。テストを設計する時、既知のテストヒューリスティックの確認に時間をかけ、我々のテスト設計手法を周知してください。Test Engineering sectionで有用なヒューリスティックのドキュメントが読めます。

Test speed

GitLabには大規模なテストスイートがあり、並列化しないと実行に数時間かかることがあります。正確で効果的かつ高速なテストを書く努力をすることが重要です。

テストのパフォーマンスに関する注意事項を次に示します。

  • doublespyFactoryBot.build(...)より速い
  • FactoryBot.build(...).build_stubbed.createより速い
  • build, build_stubbed, attributes_for, spy, doubleを使う時、createでオブジェクトを作成しないこと。DBの永続化は遅い。
  • 本当にテストする必要がある場合以外は、JavaScriptを必要とする機能のテスト(RSpecの:jsのような)を行わないこと。ヘッドレスブラウザでのテストは遅い。

RSpec

rspecでテストするには

# run all tests
bundle exec rspec

# run test for path
bundle exec rspec spec/[path]/[to]/[spec].rb

guardを使用して変更を継続的に監視し、変更されたテストのみを実行します。

bundle exec guard

springとguardを一緒に使用する場合は、代わりにSPRING = 1 bundle exec guardとしてspringを使用してください。

General guidelines

  • トップレベルにはdescribe ClassNameを一つだけ定義すること
  • describe内では、クラスメソッドを.method、インスタンスメソッドを#methodと表記すること
  • ロジック的に分岐する場合はcontextを使うこと
  • テスト項目の順序はプロダクトコードクラス内の順序と一致させること
  • 改行を使用してフェーズを分離し、4フェーズのテストパターンに従うこと
  • localhostのようなハードコーディングはせず、Gitlab.config.gitlab.hostのように設定変数を使うこと
  • sequenceによって生成された変数のような値に対してテストを行わないこと(落とし穴を参照)
  • beforeafterなどのフックの引数に:each(alias :example)与えてもデフォルトで効いているため引数に指定しないこと
  • beforeafterのフックは:allのスコープよりも:contextのスコープのほうが望ましい
  • 指定した要素に作用するevaluate_script("$('.js-foo').testSomething()") (もしくはexecute_script)を使うときは、Capybaraのマッチャー(例えばfind('.js-foo'))であらかじめ要素が確実に存在することを確かめる
  • focus: trueを使ってテストしたい範囲を分離すること
  • テストに複数の期待値がある場合はaggregate_failuresを使用すること

System / Feature tests

Note: 新しいSystem / Feature testを書く前に一度、書かないことを検討してください

  • feature specのファイル名はuser_changes_password_spec.rbのようにROLE_ACTION_spec.rbとすべき
  • シナリオタイトルには成功ケースと失敗ケースを記載すること
  • 「successfully」など、情報がないようなシナリオタイトルは避けること
    • 何がsuccessfullyなのか明記すること
  • 機能のタイトルを繰り返すだけのシナリオタイトルは避けること
  • データベースには必要なレコードのみを作成すること
  • Happy path[3]とless happy pathだけをテストします。
  • 可能な限り単体テストまたは統合テストでテストする必要がある
  • ActiveRecord内部ではなくページに表示されるものを評価すること
    • もしレコードが作成されたことを確認したかったら、Model.count等でモデルが増えたことをテストするのではなく、その項目がページに表示されるというテストを追加すること。
  • DOM要素を探してもよいがテストをより脆弱にするため乱用はしないこと

Live debug

live_debugメソッド[4]を使えば、Capybaraを一時停止して、ブラウザでウェブサイトを表示できます。デフォルトブラウザで開きます。テストの実行を再開するには、任意のキーを押します。

以下のような感じです。

$ bin/rspec spec/features/auto_deploy_spec.rb:34
Running via Spring preloader in process 8999
Run options: include {:locations=>{"./spec/features/auto_deploy_spec.rb"=>[34]}}

Current example is paused for live debugging
The current user credentials are: user2 / 12345678
Press any key to resume the execution of the example!
Back to the example!
.

Finished in 34.51 seconds (files took 0.76702 seconds to load)
1 example, 0 failures

Note: live_debugはJavaScriptが使える場合でのみ動きます。

Run :js spec in a visible browser

以下のようにCHROME_HEADLESS=0を付けてspecを実行します。

CHROME_HEADLESS=0 bundle exec rspec some_spec.rb

このテストはすぐに終わりますが、これにより何が起こっているのかわかります。CHROME_HEADLESS=0を付け live_debugを使って開いているブラウザを一時停止し、再び開くことができません。これは要素のデバッグと検査に使用できます。

byebugまたはbinding.pryを追加して、実行を一時停止し、テストをステップ実行することもできます。

Screenshots

capybara-screenshotgemを使用して失敗時に自動的にスクリーンショットを撮ります。 CIでは、これらのファイルをジョブアーティファクトとしてダウンロードできます。

また、以下のメソッドを追加することにより、テストの任意の時点でスクリーンショットを手動で取得できます。不要になったら削除してください!詳細については、ここを参照してください。

  • screenshot_and_save_page
    • Capybaraが「見ているもの」のスクリーンショットを作成し、ページソース(htmlファイル等)を保存します。
  • screenshot_and_open_image
    • Capybaraが「見ているも」のをスクリーンショット化し、画像を自動的に開きます。

これにより作成されたHTMLダンプにはCSSがありません。これにより、実際のアプリケーションとは大きく異なった外観になります。デバッグを容易にするCSSを追加するsmall hackがあります。

Fast unit tests

一部のクラスはRailsから十分に分離されており、RailsやBunlderの:defaultグループのgem等によって追加されたオーバーヘッドなしでそれらをテストできるはずです。このような場合、テストファイルでspec_helperをreuqireする代わりにfast_spec_helperをreuqireできます。次の理由からテストは非常に高速に実行されるはずです。

  • gemのロードをスキップ
  • Railsアプリの起動をスキップ
  • GitLab ShellとGitallyのスキップ
  • テストリポジトリのセットアップをスキップ

fast_spec_helperはlibディレクトリ配下にある自動ロードクラスもサポートします。つまり、クラス/モジュールがlibディレクトリ配下のコードのみを使用している限り、依存関係を明示的にロードする必要はありません。fast_spec_helperは、Rails環境で一般的に使用されるコア拡張機能を含む、すべてのActiveSupport拡張機能もロードします。

コードがgemを使用している場合、または依存関係がlibにない場合、require_dependencyを使用して依存関係をロードする必要があることに注意してください。

たとえば、Gitlab::UntrustedRegexpクラスを呼び出しているコードをテストする場合は、内部でre2ライブラリを使用します。re2 gemを必要とするライブラリ内のファイルにrequire_dependency 're2'を追加して、この要件を作成する必要があります 明示的に指定するか、仕様自体に追加することもできますが、前者が優先されます。

spec_helperの場合に30秒以上かかるloadが、fast_spec_helperを使うことで1秒程度のロードですみます。

let variables

GitLabのRSpecスイートでは、重複を減らすためにlet(それに加えて、厳密な非遅延バージョンlet!)変数を広範囲に使用しています。しかしながらこれは時々コードを分かりにくくします。そのため、今後の使用に関するガイドラインを設定する必要があります。

  • let!はインスタンス変数よりも好ましい。letlet!よりも好ましい。ローカル変数はletよりも望ましい
  • letを使用してspecファイル全体の重複を減らせる
  • 単一のテストでのみ使用される変数はletを使わずにitブロック内でローカル変数として定義すること
  • 最上位の記述ブロック内でlet変数を定義しないこと。これは、より深くネストされたコンテキストまたは記述ブロックでのみ使用される。定義は使用する場所にできるだけ近づけること。
    • 解せない
  • あるlet変数の定義を別のlet変数の定義で上書きしないようにする
    • これもスコープが異なっていればいいのでは?
  • 別で定義されているlet変数を定義するな(たぶん糸の異なる重複定義するなってことだと思われ)
    • 代わりにヘルパーメソッドを使うべき
  • let!変数は定義された順序が重要な場合にのみ使用すること。それ以外はletで十分
    • letは遅延評価であり、参照されるまで評価されないことに注意して

Common test setup

場合によっては、各exampleでテスト用に同じオブジェクトを生成する必要はありません。たとえば、プロジェクトとのそのプロジェクトのゲストは、プロジェクトとゲストが関係するすべてのファイルに対してテストするために必要となります。これは、test-profgemで導入できるlet_it_be変数とbefore_allフックを使うことで達成できます。

let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }

before_all do
  project.add_guest(user)
end

これにより、このcontextに対して作成されるProject, User, ProjectMemberが一つだけになります。

let_it_beおよびbefore_allは、ネストされたcontext内でも使用できます。トランザクションロールバックにより、contextが処理された後に自動的にクリーンアップされます。

let_it_beブロック内で定義されたオブジェクトを変更する場合、必要に応じてオブジェクトをリロードするか、すべての例でリロードするリロードオプションを指定する必要があることに注意してください。

let_it_be(:project, reload: true) { create(:project) }

また、再検索オプションを指定して、新しいオブジェクトを完全にロードすることもできます。

let_it_be(:project, refind: true) { create(:project) }

set variables

Note: Gitlabではlet_it_beを支持しているため、setを削除しつつあります。詳細はこちら参照してください。

場合によっては、各exampleでテスト用に同じオブジェクトを生成する必要はありません。たとえば、プロジェクトとのそのプロジェクトのゲストは、プロジェクトとゲストが関係するすべてのファイルに対してテストするために必要となります。これは、letを使用するのと同じ方法でsetを使用することで実現できます。

rspec-setはActiveRecordオブジェクトでのみ機能し、新しいサンプルの前に必要な場合にのみモデルをリロードまたは再作成します。つまり、プロパティを変更したときまたはオブジェクトを破棄したときです。

setブロック内のletブロックで定義されたモデルを参照することはできませんので注意してください。

また、:jsスペックでは、各exampleの後にデータベースの状態をクリーンアップするためにトランザクションを使用しないため、setはサポートされていません。

Time-sensitive tests

TimecopはRubyベースで利用でき、時間依存のテストケースに有効です。時間に依存するテストを実行・検証する場合、Timecopを使用して一時的なテストの失敗を防ぎます。

例えば、

it 'is overdue' do
  issue = build(:issue, due_date: Date.tomorrow)

  Timecop.freeze(3.days.from_now) do
    expect(issue).to be_overdue
  end
end

Feature flags in tests

すべての機能フラグはRubyベースのテストにおいて、デフォルトで有効になるようにスタブ化されています。

テストで機能フラグを無効にするには、stub_feature_flagsヘルパーを使用します。たとえば、テストでci_live_trace機能フラグをグローバルに無効にするには、

stub_feature_flags(ci_live_trace: false)

Feature.enabled?(:ci_live_trace) # => false

一部のアクターに対して機能フラグを無効にし、他のアクターに対しては無効にしないテストを設定する場合、ヘルパーに渡すオプションでこれを指定できます。たとえば、特定のプロジェクトのci_live_trace機能フラグを無効にするには、

project1, project2 = build_list(:project, 2)

# Feature will only be disabled for project1
stub_feature_flags(ci_live_trace: { enabled: false, thing: project1 })

Feature.enabled?(:ci_live_trace, project1) # => false
Feature.enabled?(:ci_live_trace, project2) # => true

Pristine test environments

1つのGitLabテストで実行されるコードは、多くのデータにアクセスして変更する場合があります。テストを実行する前に慎重に準備し、その後クリーンアップしないと、データはテストによって変更され、次のテストの動作に影響を与える可能性があります。これは必ず避けてください。幸いなことに、既存のテストフレームワークのほとんどはこのようなケースを回避しています。

テスト環境が汚染されると、一般的には不安定なテストになります。テスト環境汚染の多くの場合は、スペックAの後にスペックBを実行すると確実に失敗するが、スペックBの後にスペックAを実行すると確実に成功するといった、順序の依存関係として現れます。このような場合、rspec --bisect5を使用して、どのスペックに問題があるかを判断できます。問題を解決するには、テストスイートで環境がどのように維持されているかをある程度理解する必要があります。続きを読んで各データストアの詳細をご覧ください。

SQL database

database_cleanergemによって管理しています。各スペックはトランザクションに囲まれ、テストが完了するとロールバックされます。特定のスペックでは、完了後にすべてのテーブルに対してDELETE FROMクエリが発行されます。これにより複数のデータベース接続(ブラウザからの操作やマイグレーションスペックなどのスペックにとって重要な)から作成された行を表示できます。

よく知られているTRUNCATE TABLESアプローチの代わりにこれらの戦略を使用した結果の1つに、主キーと他のシーケンスがスペック間でリセットされないことがあります。したがって、スペックAでプロジェクトを作成してからスペックBでプロジェクトを作成すると、最初のプロジェクトはid = 1になり、2番目のプロジェクトはid = 2になります。

これは、スペックがIDの値またはその他のシーケンス生成列に依存しないことを意味します。偶発的な競合を避けるため、スペックではこれらの種類の列に値を手動で指定することも避けてください。代わりに、未指定のままにして、行の作成後に値を検索します。

Redis

GitLabではRedisに、キャッシュされたデータとSidekiqジョブの2つのデータカテゴリを保存します。殆どのスペックではRailsキャッシュはメモリ内に存在しています。これはスペック間で置き換えられるため、Rails.cache.readRails.cache.writeの呼び出しは安全です。ただし、スペックが直接Redis呼び出しを行う場合は、必要に応じて:clean_gitlab_redis_cache:clean_gitlab_redis_shared_state:clean_gitlab_redis_queuestraitを適切に使用する必要があります。

Background jobs / Sidekiq

デフォルトでは、Sidekiqジョブはジョブ配列にキューイングされ、処理されません。テストがSidekiqジョブをキューに入れて処理する必要がある場合は、:sidekiq_inlinetraitを使用できます。

:sidekiq_might_not_need_inlinetraitは、Sidekiqのインラインモードがフェイクモードに変更されたときに、Sidekiqが実際にジョブを処理するのに必要なすべてのテストに追加されました。このtraitを持つテストは、Sidekiq処理ジョブに依存しないように修正するか、バックグラウンドジョブの処理が必要/予想される場合、:sidekiq_might_not_need_inlinetraitを:sidekiq_inlineに更新する必要があります。

Note: ワーカーはApplicationJob/ActiveJob::Baseを継承していないため、perform_enqueued_jobsは現在使用できません。

Filesystem

ファイルシステムのデータは、「リポジトリ」と「その他すべて」に大まかに分けることができます。リポジトリはtmp/tests/repositoriesに保存されます。このディレクトリは、テストが実行される前、およびテストが終了した後に空になります。スペック間では空にならないため、作成されたリポジトリはプロセスの存続期間中、このディレクトリ内に蓄積されます。それらを削除するのはコストがかかりますが、注意深く管理しないと汚染につながる可能性があります。

これらを回避するには、テストスイートでハッシュストレージを有効にします。つまり、リポジトリにはプロジェクトのIDに依存する一意のパスが与えられます。プロジェクトIDはスペック間でリセットされないため、各スペックがディスク上の独自のリポジトリを取得することが保証され、スペック間で変更が表示されないようにします。

スペックでプロジェクトIDを手動で指定する場合、またはtmp/tests/repositories/ディレクトリの状態を直接検査する場合、実行の前後にディレクトリをクリーンアップする必要があります。一般的に、これらのパターンは完全に回避する必要があります。

アップロードなど、データベースオブジェクトにリンクされた他のクラスのファイルは、通常同じ方法で管理されます。スペックでハッシュストレージが有効になっている場合、IDによって決定される場所のディスクに書き込まれるため、競合は発生しません。

一部のスペックでは、projectsfactoryに:legacy_storagetraitを渡すことで、ハッシュストレージを無効にします。これを行うスペックは、プロジェクトまたはそのグループのパスをオーバーライドしてはなりません。デフォルトのパスにはプロジェクトIDが含まれているため、競合しません。ただし、2つの仕様が同じパスを持つ:legacy_storageプロジェクトを作成する場合、ディスク上の同じリポジトリを使用し、環境汚染をテストします。

その他のファイルは、スペックによって手動で管理する必要があります。たとえば、tmp/test-file.csvファイルを作成するコードを実行する場合、スペックでは、クリーンアップの一環としてファイルが削除されるようにする必要があります。

Persistent in-memory application state

Rspecによるスペックはすべて同じRubyプロセスを共有します。つまり、スペック間でアクセス可能なRubyオブジェクトを変更することで、互いに影響を与えることができます。これはグローバル変数、および定数(クラス、モジュールなどを含む)であることを意味しています。

通常、グローバル変数は変更しないでください。どうしても必要な場合、以下のようなブロックを使用して、変更を後でロールバックできます。

around(:each) do |example|
  old_value = $0

  begin
    $0 = "new-value"
    example.run
  ensure
    $0 = old_value
  end
end

スペックで定数を変更する必要がある場合は、stub_constヘルパーを使用して、変更が確実にロールバックされるようにする必要があります。

ENV定数を変更する必要がある場合は、代わりにstub_envヘルパーメソッドを使用できます。

ほとんどのRubyインスタンスはスペック感で共有されませんが、クラスとモジュールは一般的に共有されます。クラスおよびモジュールのインスタンス変数、アクセサー、クラス変数、およびその他のステートフルイディオムはグローバル変数と同じように扱われるべきです。必要がない限り変更しないでください。とくに、変更の必要性を排除するために、expectまたはstubに沿った依存関係の代入かを使うのことが望ましいです。他に選択肢がない場合は、上記のグローバル変数と同様にaroundブロックが使用できますが、可能な限り回避する必要があります。

Table-based / Parameterized tests

このスタイルのテストは、包括的な入力範囲で1つのコードを実行するために使用されます。テストケースを1回指定するだけで、入力のテーブルとそれぞれの予想出力とともに、テス​​トを読みやすく、コンパクトにすることができます。

GitLabではrspec-parameterizedgemを使っています。テーブル構文を使用し、Rubyの入力範囲をチェックする短い例は、次のようになります。

describe "#==" do
  using RSpec::Parameterized::TableSyntax

  where(:a, :b, :result) do
    1         | 1        | true
    1         | 2        | false
    true      | true     | true
    true      | false    | false
  end

  with_them do
    it { expect(a == b).to eq(result) }

    it 'is isomorphic' do
      expect(b == a).to eq(result)
    end
  end
end

Caution: whereブロックの入力として単純な値のみを使用します。プロシージャ、ステートフルオブジェクト、FactoryBotで作成されたオブジェクトなどを使用すると、予期しない結果が生じる可能性があります。

Prometheus tests

Prometheusメトリクス[6]は、テストの実行ごとに保持される場合があります。各サンプルの前にメトリクスが確実にリセットされるようにするには、Rspecテストに:prometheusタグを追加します。

Matchers

カスタムマッチャーを作成して、意図を明確にし、RSpecの予想の複雑さを隠す必要があります。これはspec/support/matchers/に配置する必要があります。マッチャーは、特定のタイプのスペック(機能スペック、リクエストスペックなど)にのみ適用される場合はサブフォルダーに配置できますが、複数のタイプの仕様に適用する場合は配置しないでください。

be_like_time

データベースから返される時間は、Rubyの時間オブジェクトと精度が異なる場合があります。そのため、スペックを比較する際に柔軟な許容範囲が必要です。be_like_timeを使用して、時間が1秒以内であることを比較できます。

expect(metrics.merged_at).to be_like_time(time)

have_gitlab_http_status

have_http_statusよりもhave_gitlab_http_statusをお勧めします。have_gitlab_http_statusは、ステータスが一致しない場合に常に応答本文も表示できるためです。これは、テストが落ちたときにソースコードを編集せず、テストを再実行せずとも落ちた原因を知るのに非常に役に立ちます。

特に500サーバーエラーが表示されている場合に便利です。

Shared contexts

すべてのshared contextは、spec/support/shared_contexts/に配置する必要があります。shared contextは、特定のタイプのスペック(機能スペック、リクエストスペックなど)にのみ適用される場合サブフォルダーに配置できますが、複数のタイプの仕様に適用される場合はそうではありません。

各ファイルにはコンテキストが1つだけ含まれ、わかりやすい名前を付ける必要があります。
(e.g. spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb.)

Shared examples

すべてのshared exampleは、spec/support/shared_exampless/に配置する必要があります。shared examplesは、特定のタイプのスペック(機能スペック、リクエストスペックなど)にのみ適用される場合サブフォルダーに配置できますが、複数のタイプの仕様に適用される場合はそうではありません。

各ファイルにはコンテキストが1つだけ含まれ、わかりやすい名前を付ける必要があります。
(e.g. spec/support/shared_exampless/controllers/githubish_import_controller_shared_example.rb.)

Helpers

ヘルパーは通常、特定のRSpecのexampleの複雑さを隠すためのメソッドを提供するモジュールです。他のスペックと共有することを意図していない場合、RSpecファイルでヘルパーを定義できます。それ以外の場合は、spec/support/helpers/に配置する必要があります。

特定のタイプのスペック(機能スペック、リクエストスペックなど)のみに適用される場合、ヘルパーはサブフォルダーに配置できます。

ヘルパーはRailsの命名規則/名前空間規則に従う必要があります。たとえば、spec/support/helpers/cycle_analytics_helpers.rbは以下のように定義する必要があります。

module Spec
  module Support
    module Helpers
      module CycleAnalyticsHelpers
        def create_commit_referencing_issue(issue, branch_name: random_git_name)
          project.repository.add_branch(user, branch_name, 'master')
          create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
        end
      end
    end
  end
end

ヘルパーでRSpecの設定を変更しないでください。たとえば、上記のヘルパーモジュールには以下を含めないでください。

RSpec.configure do |config|
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers
end

Factories

GitLabはテスト用のFixture[7]の代替としてfactory_botを使用します。

  • Factoryはspec/factories/で定義し、対応するモデルの複数形を使用して命名します(Userのfactoryはusers.rb)。
  • ファイルごとにトップレベルのファクトリ定義は1つだけにする必要があります。
  • FactoryBotメソッドは、すべてのRSpecグループに混在しています。つまりFactory.create(...)の代わりにcreate(...)を呼び出すことができます(そして呼び出す必要があります)。
  • traitを使用して定義と使用方法をクリーンアップします。
  • ファクトリを定義するとき、モデルに関係のないカラムを定義しないでください。
  • ファクトリをインスタンス化するときは、不要なカラムを指定しないでください。
  • ファクトリはActiveRecordオブジェクトに限定される必要はありません。を参照してください。

Fixtures

すべてのFixtureはspec/fixtures/の下に配置する必要があります。

Repositories

マージリクエストのマージなどの一部の機能をテストするには、特定の状態のGitリポジトリがテスト環境に存在する必要があります。GitLabは、特定の一般的なケースに対してgitlab-testリポジトリを維持します。プロジェクトファクトリの:repositoryトレイトでリポジトリのコピーが使用されていることを確認できます

let(:project) { create(:project, :repository) }

可能な場合は、:repositoryではなく:custom_repoトレイトの使用を検討してください。これにより、プロジェクトのリポジトリのmasterブランチに表示されるファイルを正確に指定できます。

let(:project) do
  create(
    :project, :custom_repo,
    files: {
      'README.md'       => 'Content here',
      'foo/bar/baz.txt' => 'More content here'
    }
  )
end

これにより、デフォルトの権限と指定されたコンテンツを持つ2つのファイルを含むリポジトリが作成されます。

Config

RSpecの設定ファイルは、RSpecのコンフィグ(すなわちRSpec.configure do |config|ブロック)を変更するファイルです。これはspec/support/に配置する必要があります。

各ファイルには特定のドメインに関連する必要があります。たとえば、spec/support/capybara.rb, spec/support/carriewave.rbなどです。

ヘルパーモジュールが特定の種類のスペックにのみ適用される場合、config.include呼び出しに修飾子を追加する必要があります。たとえば、spec/support/helpers/cycle_analytics_helpers.rb:libおよびtype: :modelスペックにのみ適用される場合、次のように記述します。

RSpec.configure do |config|
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model
end

構成ファイルがconfig.includeのみで構成されている場合、これらのconfig.includespec/spec_helper.rbに直接追加できます。

汎用的なヘルパーについては、spec/fast_spec_helper.rbファイルで使用されるspec/support/rspec.rbファイルに含めることを検討してください。spec/fast_spec_helper.rbファイルの詳細については、高速ユニットテストを参照してください。

注釈

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

お名前.comを使い、独自ドメイン取得し、herokuデプロイする

こんにちは。
初学者の方で転職活動などの為にポートフォリオを作成している方も多いと思います。

その際にherokuを使ってデプロイする人が多いと思うのですが、

URLが、
〜herokuapp.com

となってしまい、なんかカッコ悪いなあって思いまして、
AWSを使って色々やろうとしたのですが、

ポートフォリオだからそこまで口数をかけたくないと思い

独自ドメインを使い、herokuで、デプロイすることにしました。

その手順を載せさせていただきます。
初学者の方への参考になれたら幸いです。

※前提として
Herokuにアプリをデプロイ済みであることとします

1.お名前.comで独自ドメインを取得する

https://www.onamae.com/

にて
取得したいドメイン名を入れて検索する

スクリーンショット 2020-01-14 12.17.33.png

追加できるものを探し、

スクリーンショット 2020-01-14 12.19.16.png

個人情報、クレジット情報を入力する

ここでは
test.work
を取得したことにして進めます。

2.herokuの設定

herokuはhobbyプラン(無料の一つ上の7$のプラン)以上じゃないと独自ドメインが使えないためです。

ちなみにこちらは
7$/月の料金がかかってしまいます。

Herokuのプランはアカウントごとではなく、アプリケーションごとに設定します。例えば、2つのアプリケーションをHerokuにデプロイしている場合、それぞれのアプリケーションに対して無料・有料プランにするかどうかを設定することになります。

herokuをhobbyプランにする

https://dashboard.heroku.com/apps

より、
デプロイするアプリを押下、

詳細画面で、
Resources > [Change Dyno Type]を押下し、
[Hobby]を選択し[save]を押下します。

スクリーンショット 2020-01-14 20.37.33.png

スクリーンショット 2020-01-14 20.40.34.png

↓のように表示されたら、完了です
スクリーンショット 2020-01-14 20.41.32.png

heroku側のドメイン設定とSSL設定

[Settings]を押下し、[Add domain]を押下します。

スクリーンショット 2020-01-14 20.47.46.png

お名前.comで取得したドメインに
www.
を追加し入力し、[Next]を押下します。

スクリーンショット 2020-01-14 20.54.38.png

DNS target
が表示されるので、コピーします。

スクリーンショット 2020-01-14 20.56.08.png

次に[Configure SSL]を押下し、

Automatic Certificate Management (ACM)
を選択し、[Next]を押下します。
スクリーンショット 2020-01-14 20.58.41.png

確認画面になるので、もう一度[Next]を押下します。

最後に[Finish]を押下します。

3.お名前.comの設定

再びお名前.comにいき、
[ドメイン設定]を押下、
[DNS関連機能の設定]を押下、
[DNS設定/転送設定]を押下します。

ドメイン名を選択し[次へ]を押下します。
スクリーンショット 2020-01-14 21.07.32.png

DNSレコード設定を利用するの
[設定する]を押下します。
スクリーンショット 2020-01-14 21.08.40.png

TYPE→CNAME
ホスト名→wwwを入力
VALUEには、先程herokuでコピーしたDNS Targetを貼り付け
[追加]を押下します。

スクリーンショット 2020-01-14 21.14.06.png

画面下部まで進み確認へ進み、
[確認内容へ進む]を押下します。

内容を確認し、[設定する]を押下します。
スクリーンショット 2020-01-14 21.16.53.png

これで、設定は完了です!!

※UI上でうまくいかない場合は
コマンドでの設定をおすすめします
こちらを参考に!
https://medium.com/@kjmczk/heroku-cdomain-ssl-1b4cae424e61

反映まで数時間かかるようなので、
メールが来るまで待ちましょう!!

ドメインが反映される前に取得したドメインにアクセスするとお名前.comのページに飛ばされ『このドメインは取得されています』と表示される思いますが、心配せず気長に待ってください。

24〜72時間の間でドメインが反映されるらしいのですが、
自分の場合は数十分で
反映されました!

無事に公開されました!!

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

Rails エラーメッセージの表示と日本語化

エラーメッセージの表示

image.png

上記の画像のようにタイトルとブログ本文が空だった場合はエラー表示される様に実装する事。

モデルにバリデーションを設定

バリデーションを付けたいモデル(今回はpost.rb)に記載。

post.rb
class Post < ApplicationRecord
  validates :title, :content, presence: true
end

titleカラムとcontentカラムが空を防ぐ。

エラーメッセージのファイルの作成

エラーメッセージはエラーが発生すると、_error_messages.html.erb内に格納される。

layouts/_error_messages.html.erb
% if model.errors.any? %>
  <div class="alert alert-warning">
    <ul>
      <% model.errors.full_messages.each do |message| %>
        <li><%= message %></li> 
      <% end %>
    </ul>
  </div>
<% end %>

フォーム(form_with)の中にrenderで挿入

posts/new.html.erb
<%= form_with model: @post, class: :form, local: true do |form| %>
 #renderメソッドで_error_messages.html.erbを呼び出す。
  <%= render 'layouts/error_messages', model: form.object %> 
  <%= form.text_field :title, placeholder: :タイトル, class: :form__title %>
  <%= form.text_area :content, placeholder: :ブログ本文, class: :form__text %>
  <%= form.submit '投稿する', class: :form__btn %>
<% end %>

これで表示は完了。
次は日本語化実装へ。

エラーメッセージの日本語化

Gemfile
gem 'rails-i18n'

Gemfileに以下の一文を追加して、bundle install。

日本語化の基となるファイルを作成する

Railsの多言語化対応は、ymlファイルで管理。
config/locales ディレクトリ直下に、ja.ymlを作成。

ターミナル
$ touch config/locales/ja.yml

カラム名の日本語化

config/locales/models/ja.yml
ja:
  activerecord:
    attributes:
      post:
        title: 名前
        content: ブログ本文

ja.ymlの注意点

Railsはymlファイルの改行とインデントで日本語化のパスを参照している為、
attributes: => モデル名 => カラム名 の順に改行とインデントを入れる必要がある。

config/locales/models/ja.yml
attributes:         # attributes:の直下に
  post:            # モデル名を指定し
    title: 名前        # カラム名を指定する。
   content:ブログ本文    # カラム名を指定する。

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

RSpecでActiveRecordに依存しているConcernのspecを書く。

例えばこのようなActiveRecordのschemaに依存しているConcernがある。

app/models/concerns/printable.rb
module Printable
  extend ActiveSupport::Concern

  def print_id
    puts self.id
  end

  def print_name
    puts self.name
  end
end

そのような場合、どのようにspecを書くのが良いでしょうか。
何も考えずにやるなら既存のActiveRecordのModelを利用することでしょうか。

app/models/user.rb
class User < ApplicationRecord
  include Printable
end
spec/models/user_spec.rb
describe User, type: :model do
  before do
    record.save
  end

  describe '#print_id' do
    let(:record) { User.new }

    subject { record.print_id }

    it do
      expect { subject }.to output("#{nil}\n").to_stdout
    end
  end

  describe '#print_name' do
    let(:record) { User.new(name: 'hoge') }

    subject { record.print_name }

    it do
      expect { subject }.to output("hoge\n").to_stdout
    end
  end
end

が、これはPrintableModuleのspecを書きたいのにUserModelに依存しており、責務が分けられておらず良くないです。
なので、RSpec上で仮のModelを作ってPrintableModuleだけをテストします。

spec/models/concerns/printable.rb
describe Printable, type: :model do
  before(:all) do
    m = ActiveRecord::Migration.new
    m.verbose = false
    m.create_table :pritable_tests do |t|
      t.string :name
    end
  end

  after(:all) do
    m = ActiveRecord::Migration.new
    m.verbose = false
    m.drop_table :pritable_tests
  end

  class PrintableTest < ApplicationRecord
    include Printable
  end

  before do
    record.save
  end

  describe '#print_id' do
    let(:record) { PritableTest.new }

    subject { record.print_id }

    it do
      expect { subject }.to output("#{nil}\n").to_stdout
    end
  end

  describe '#print_name' do
    let(:record) { PritableTest.new(name: 'hoge') }

    subject { record.print_name }

    it do
      expect { subject }.to output("hoge\n").to_stdout
    end
  end
end

更にリファクタリングをしてみます。

spec/spec_helper.rb
# ...
# ...
# ...
def create_spec_table(name, &block)
  before(:all) do
    m = ActiveRecord::Migration.new
    m.verbose = false
    m.create_table name, &block
  end

  after(:all) do
    m = ActiveRecord::Migration.new
    m.verbose = false
    m.drop_table name
  end
end
spec/models/concerns/printable.rb
describe Printable, type: :model do
  create_spec_table :printable_tests, do |t|
    t.string :name
  end

  class PrintableTest < ApplicationRecord
    include Printable
  end

  before do
    record.save
  end

  describe '#print_id' do
    let(:record) { PritableTest.new }

    subject { record.print_id }

    it do
      expect { subject }.to output("#{nil}\n").to_stdout
    end
  end

  describe '#print_name' do
    let(:record) { PritableTest.new(name: 'hoge') }

    subject { record.print_name }

    it do
      expect { subject }.to output("hoge\n").to_stdout
    end
  end
end

これで綺麗に書くことが出来ました。

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

devise のコントローラーに追加したインスタンス変数が view で参照できない件の対応メモ

発生した問題

Devise gem が自動生成するアカウント登録画面をカスタマイズしようとする

$ rails generate devise:controllers users

とりあえずコントローラーにインスタンス変数を定義してみる

app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  # GET /resource/sign_in
  def new
    super
    @foo = "This is a foo."
  end
  ...
end

なぜか view で参照できない :cry:

app/views/devise/sessions/new.html.erb
<%# 何も表示されない %>
<p>
  FOO: <%= @foo %>
</p>

解決方法

Devise のコントローラーをオーバーライドしたい場合は super にブロックを渡すのが正規のやり方だった。

  # GET /resource/sign_in
  def new
    super do |resource|
      @foo = "aaa"
    end
  end

ちなみにブロック変数 resource には新規作成する対象のモデルのインスタンスが設定されている。

原因など

継承元の Devise::SessionsController の定義がこう。

  def new
    self.resource = resource_class.new(sign_in_params)
    clean_up_passwords(resource)
    yield resource if block_given?
    respond_with(resource, serialize_options(resource))
  end

最後の respond_with は responders という別の gem のメソッドで、ここで view のレンダリングを行っている。

なので、super の後にインスタンス変数を設定しても、既に view の描画は終わっているため、view に反映されない。

--

yield if block_given?

でコードの差し込みポイント作るのたまにやるけど、継承でも使えるのは盲点だったなぁ。

割と使えそうなテクニックなので覚えておこう。

参考資料

継承元(Devise::SessionsController)のソースコード
https://github.com/heartcombo/devise/blob/master/app/controllers/devise/sessions_controller.rb

Configuring controllers | heartcombo/devise - github
https://github.com/heartcombo/devise#configuring-controllers

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

【Ruby on Rails】デフォルトメッセージの日本語化

エラーメッセージなどのデフォルトメッセージを日本語化する設定です。

バージョン情報

Ruby 2.6.3
Ruby on Rails 5.2.3

設定方法

config/initializers/配下にlocale.rbというファイルを作成します。ファイル名は何でも良いですが習慣上locale.rbにしてあります。

下記の2文を記載します。

config/initializers/locale.rb
I18n.config.available_locales = :ja
I18n.default_locale = :ja

日本語の辞書ファイルを作成します。
config/locales/ja.ymlというファイルを作成し、下記のURLの本文を作成したファイルにすべてコピペします。

https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml

モデル名などはまだ英語のままなので、個別で日本語化していきます。
config/locales/model.ja.ymlというファイルを作成します。

config/locales/model.ja.yml
ja:
  activerecord:
    models:
      post: 投稿
      user: ユーザー
    attributes:
      post:
        content: 内容
        image: 画像
      user:
        name: 名前
        email: メールアドレス
        current_password: 現在のパスワード
        password: パスワード
        password_confirmation: 確認用パスワード

以上です。

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

#Rails ActionMailer + #Rspec でメール送信数 / 送信先をテストする例

subject { something }

it do
  expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(3)
end

before { subject }

it do
  expect(ActionMailer::Base.deliveries.map(&:to)).to include ['alice@example.com']
  expect(ActionMailer::Base.deliveries.map(&:to)).to include ['bob@example.com', 'carol@example.com']
end

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2943

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

ページネーションと並び替えに対応した投稿一覧画面とAPIの実装【初学者のReact×Railsアプリ開発 第10回】

やったこと

  • Reactでの投稿一覧画面の実装と並び替えに対応するためのRails APIの実装
  • ラジオボタンの変更によるAPIからの投稿の取得と表示
  • reduxを使った表示する投稿の状態管理
  • material-ui-flat-paginationを用いたページネーションの実装

成果物

qkb5r-7d5kg.gif

Rails APIの実装手順

route.rb: ルートの編集

route.rb
Rails.application.routes.draw do
 namespace :api, defaults: { format: :json } do
    namespace :v1 do

      get 'posts', to: 'posts#index'
      get 'posts_suki', to: 'posts#suki_index'
      get 'posts_allcount', to: 'posts#all_count_index'

    end
 end
end

posts_controller

  • API側のページネーションの実装として、kaminariを用いています。
  • ポストは1ページあたり10個ずつ返すようにしています。新着順や投票数順など、order('...')で、postsテーブルのどのカラムで並び替えするかを記述しています。
  • page_lengthは、React側でページ数を何ページまで表示するかを確定させるために必要な情報です。46個の投稿なら5ページまでなど...
posts_controller.rb
     def index
        posts = Post.page(params[:page] ||= 1).per(10).order('created_at DESC')
        page_length = Post.page(1).per(10).total_pages
        json_data = {
          'posts': posts,
          'page_length': page_length,
        }
        render json: { status: 'SUCCESS', message: 'Loaded posts', data: json_data}
      end

      def suki_index
        posts = Post.page(params[:page] ||= 1).per(10).order('suki_count DESC')
        page_length = Post.page(1).per(10).total_pages
        json_data = {
          'posts': posts,
          'page_length': page_length,
        }
        render json: { status: 'SUCCESS', message: 'Loaded posts', data: json_data}
      end

      def all_count_index
        posts = Post.page(params[:page] ||= 1).per(10).order('all_count DESC')
        page_length = Post.page(1).per(10).total_pages
        json_data = {
          'posts': posts,
          'page_length': page_length,
        }
        render json: { status: 'SUCCESS', message: 'Loaded posts', data: json_data}
      end

React実装手順

App.js

  • ルートの編集です。
App.js
import PostsList from './containers/PostsList';

            <Auth>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path='/create' component={Create} />
                <Route path='/postslist' component={PostsList} />
              </Switch>
            </Auth>

PostsList.js

  • ここでは一部のコードのみ紹介します。
  • 表示させるポストなどの情報は、Redux(PostListReducer)で管理しています。
  • componentdidmountで、初期描画の際に表示させるポストの取得を行っています。前回描画時の情報を保存しておくためにreduxでの状態管理を行っています。
  • handleChangeらラジオボタンの変更に対応しています。
  • handlePaginationClickは、ページリンクの変更に対応しています。offsetは1ページ目なら0、2ページ目をクリックしたときは10、3ページ目なら20...です。この情報で、APIで何ページ目の情報をもらうか確定させています。
Postslist.js
//module import, css部分は省略

class PostsList extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handlePaginationClick = this.handlePaginationClick.bind(this);
  }

  componentDidMount() {
    const { PostsListReducer } = this.props;
    if (PostsListReducer.selected === "新着順") {
      this.props.actions.getPostsList("", PostsListReducer.offset, "新着順")
    } else if (PostsListReducer.selected === "スキが多い順") {
      this.props.actions.getPostsList("_suki", PostsListReducer.offset, "スキが多い順")
    } else if (PostsListReducer.selected === "投票数が多い順") {
      this.props.actions.getPostsList("_allcount", PostsListReducer.offset, "投票数が多い順")
    }
  }

  handleChange(e) {
    if (e.target.value === "新着順") {
      this.props.actions.getPostsList("", 0, "新着順")
    } else if (e.target.value === "スキが多い順") {
      this.props.actions.getPostsList("_suki", 0, "スキが多い順")

    } else if (e.target.value === "投票数が多い順") {
      this.props.actions.getPostsList("_allcount", 0, "投票数が多い順")
    }
  }

  handlePaginationClick(offset) {
    const { PostsListReducer } = this.props;
    if (PostsListReducer.selected === "新着順") {
      this.props.actions.getPostsList("", offset, "新着順")
    } else if (PostsListReducer.selected === "スキが多い順") {
      this.props.actions.getPostsList("_suki", offset, "スキが多い順")
    } else if (PostsListReducer.selected === "投票数が多い順") {
      this.props.actions.getPostsList("_allcount", offset, "投票数が多い順")
    }
  }

PostsList.js(render)

  • 続いて、レンダーの部分です。
  • PostListReducerの情報を使って、表示を制御しています。
  • 各ポストには、リンク("/posts/post.id")を貼って、詳細ページに飛べるようにしています。
  • RadioGroupタグと、Paginationタグの設定が多少頭を使います。
PostsList.js
  render() {
    const { CurrentUserReducer } = this.props;
    const { PostsListReducer } = this.props;

    const { classes } = this.props;

    return (
      <Scrollbars>
        <div className={classes.container}>
          <FormControl component="fieldset">
            <FormLabel component="legend"></FormLabel>
            <RadioGroup aria-label="position" name="position" value={PostsListReducer.selected} onChange={this.handleChange} row>
              <FormControlLabel
                value="新着順"
                control={<Radio color="primary" />}
                label="新着順"
                labelPlacement="end"
              />
              <FormControlLabel
                value="スキが多い順"
                control={<Radio color="primary" />}
                label="スキが多い順"
                labelPlacement="end"
              />
              <FormControlLabel
                value="投票数が多い順"
                control={<Radio color="primary" />}
                label="投票数が多い順"
                labelPlacement="end"
              />
            </RadioGroup>
          </FormControl>

          <ul className={classes.ul}>
            {PostsListReducer.items.map((post) => (
              <Link to={"/posts/" + post.id} className={classes.link}>
                <li className={classes.li} key={post.id}>
                  <div className={classes.licontent}>
                    <h3 className={classes.lih3}>{post.content}</h3>
                  </div>
                </li>
              </Link>
            ))}
          </ul>
          <MuiThemeProvider theme={pagitheme}>
            <CssBaseline />
            <Pagination
              limit={10}
              offset={PostsListReducer.offset}
              total={PostsListReducer.page_length * 10}
              onClick={(e, offset) => this.handlePaginationClick(offset)}
            />
          </MuiThemeProvider>
        </div>
      </Scrollbars>
    )
  }
}

actions/index.js

  • ここでは、APIから投稿を取得しています。
  • PostsListReducer.jsでstateを変更するためのactionの内容の記述とdispatchをしています。
index.js
export const getPostsList = (fetchlink, offset, selected) => {
  return (dispatch) => {
    dispatch(getPostsListRequest())
    const auth_token = localStorage.auth_token
    const client_id = localStorage.client_id
    const uid = localStorage.uid
    const page_url = offset / 10 + 1

    return axios.get(process.env.REACT_APP_API_URL + `/api/v1/posts${fetchlink}?page=${page_url}`, {
      headers: {
        'access-token': auth_token,
        'client': client_id,
        'uid': uid
      }
    })
      .then(response => dispatch(getPostsListSuccess(response.data.data.posts, offset, response.data.data.page_length, selected)))
      .catch(error => dispatch(getPostsListFailure(error, offset, selected)))
  };
};

export const getPostsListRequest = () => ({
  type: 'GET_POSTSLIST_REQUEST',
})

export const getPostsListSuccess = (json, offset, page_length, selected) => ({
  type: 'GET_POSTSLIST_SUCCESS',
  items: json,
  offset: offset,
  page_length: page_length,
  selected: selected,
})

export const getPostsListFailure = (error, offset, selected) => ({
  type: 'GET_POSTSLIST_FAILURE',
  items: error,
  offset: offset,
  selected: selected,
})

reducers/PostListReducer.js

  • ここでreduxのstateの変更を行っています。
  • initialStateに記述の通り、初期状態では新着順の1ページ目が表示されるようになっています。
PostListReducer.js
const initialState = {
  isFetching: false,
  items: [],
  offset: 0,
  page_length: 1,
  selected: "新着順",
};

const PostsListReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'GET_POSTSLIST_REQUEST':
      return {
        ...state,
        isFetching: true,
        items: [],
        offset: "",
        page_length: "",
      };
    case 'GET_POSTSLIST_SUCCESS':
      return {
        ...state,
        isFetching: false,
        items: action.items,
        offset: action.offset,
        page_length: action.page_length,
        selected: action.selected,
      };
    case 'GET_POSTSLIST_FAILURE':
      return {
        ...state,
        isFetching: false,
        error: action.error,
        selected: action.selected,
        offset: action.offset,
      };
    default:
      return state;
  }
};

export default PostsListReducer;

reducers/rootReducer.js

  • rootReducerにPostsListReducerを追加しています。
rootReducer.js
import { combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
import { routerReducer } from 'react-router-redux'
import CurrentUserReducer from './CurrentUserReducer'
import PostsListReducer from './PostsListReducer'

const rootReducer = combineReducers({
  CurrentUserReducer,
  form: formReducer,
  router: routerReducer,
  PostsListReducer
})

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

ActionView::MissingTemplate in Groups#newの一例

1.どんなエラー?

renderメソッドで表示しようとしている、viewファイルがありませんという内容です

筆者の場合は同じgroupsフォルダ内のformというファイルで記載したものを引用しnewファイルで表示しようとしていました。(下記参照)

<エラー文>

スクリーンショット 2020-01-14 2.23.39.png

<エラーに関係したgroupsフォルダ内のファイル>
new.html.haml
.chat-group-form
  %h1 新規チャットグループ
  = render partial: 'form', locals: { group: @group }
form.html.haml
= form_for group do |f|
  .chat-group-form__errors
    %h2 10件のエラーが発生しました
    %ul
      %li nameを入力してください
  .chat-group-form__field
    .chat-group-form__field--left
      = f.label :name, class: 'chat-group-form__label'
    .chat-group-form__field--right
      = f.text_field :name, class: 'chat__group_name chat-group-form__input', placeholder: 'グループ名を入力してください'
  .chat-group-form__field.clearfix
    / この部分はインクリメンタルサーチ(ユーザー追加の非同期化のときに使用します
  .chat-group-form__field.clearfix
    .chat-group-form__field--left
      %label.chat-group-form__label{:for => "chat_group_チャットメンバー"} チャットメンバー
    .chat-group-form__field--right
      / グループ作成機能の追加時はここにcollection_check_boxesの記述を入れてください
      = f.collection_check_boxes :user_ids, User.all, :id, :name
      / この部分はインクリメンタルサーチ(ユーザー追加の非同期化のときに使用します
  .chat-group-form__field.clearfix
    .chat-group-form__field--left
    .chat-group-form__field--right
      = f.submit class: 'chat-group-form__action-btn'

2.原因

 構文を確認してみるとどこにも間違いがないため、悩んでいたところ、ありました、partial: 'form'という一文が。
 partialというのはフォルダ内の"部品名となっているファイル"を引用しますよという意味があり、通常のファイルに対してpartialという縛りを増やすと読み込みを行いません。
 つまるところpartialで参照したファイルのファイル名は"部品"であることを意味する'_(アンダーバー)'から始めなければならないのです。

3.解決方法

結論としてはpartialを使って呼び出すファイルは"_(アンダーバー)"から始まるファイル名とすればエラー要因の一つが取り除かれるということになります。

ちなみに筆者の場合の例を確認してみましょう。上記に添付した筆者のファイル名を見てみるとformのファイル名が部品の形となっていませんね。なのでform.html.haml→_form.html.hamlとすることによりエラー文は解消されます。

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

Bugsnagで特定のエラーの通知を無効化する

最近の勉強で学んだ事を、ノート代わりにまとめていきます。
主に自分の学習の流れを振り返りで残す形なので色々、省いてます。
Webエンジニアの諸先輩方からアドバイスやご指摘を頂けたらありがたいです!

BugsnagでRailsの不具合を通知

Railsのエラー監視にBugsnagを利用しています!Slackとの連携をしていてエラーが発生したら通知が飛ぶ様にしております。
今回は、Bugsnagのエラーで通知されるエラーは優先度が高いもののみにしたかったのサービス自体に影響がないエラーの通知を一旦無効化することになりました。
Bugsnagと黒魔術で、Railsの不具合調査を楽にする仕組み! | 株式会社スタメン

Bugsnagで特定のエラーを無効化する

Bugsnagの公式ドキュメントを確認してみると以下の様な記述がありました!
これによると個々のエラーは破棄できます。 破棄されると、エラーはダッシュボードからすぐに削除され、受信した後続のイベントは保存されなくなる様です!

Discard individual errors
#
Individual errors can be discarded. When discarded, errors are immediately deleted from the dashboard and subsequent events that we receive are not stored.

image.png
Bugsnag docs › Product › Managing event usage

特定のエラーを破棄する

今回、通知を無効化したいので特定のエラーを選択し画像の様に削除しました!
Screen Shot 2020-01-13 at 10.04.42 PM.png

エラーを削除したら、このエラーの今後の発生は保存されず、イベント制限にカウントされません。とのメッセージが表示されました!

Discard specific errors via the additional actions (...) menu in the dashboard. Future occurrences of these errors won't be stored or count towards your event limit.

この対応で無事、無効化できました!

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