woshidan's loose leaf

ぼんやり勉強しています

画面の横幅が大きい(小さい)ときのみ要素を表示する

画面を作成していると単純に要素を横に伸ばしたりでスマホとPCの表示が作れず、要素の順番や配置が変わってしまうことがあります。

その度合いがまるきり別物ならPC版とスマホ版で別のテンプレートを使えばよさそうですが、順番が数カ所違う程度ならスマホ版だけ、PC版だけ要素を表示する、という方法が取れればよさそうです。

BootStrapを使っている場合、visible-xx/hidden-xxを使うとxxのところに入った大きさに画面がなったら要素の表示/非表示を切り替えてくれます。

スマホの場合、たとえば画面サイズがxs(767px)以下なら表示するなら、visible-xsをクラスに追加し、画面サイズが768px以上なら表示しないならhidden-xsをクラスに追加することで実現できます。

<div class="hidden-xs" style="background-color: red;"></div><!-- 画面幅が768px以上のときだけ赤い要素が表示される -->
<div class="visible-xs" style="background-color: blue;"></div><!-- 画面幅が767px以下になると青い要素が表示される -->

参考

qiita.com

studio-key.com

少しだけ動作が速くなるコードの書き方3つ

レコードがあることを条件にしたいとき、findの代わりにexists?を使う

if User.find_by(email: "...")
  # something.todo
end

のとき、findやfind_byではActiveRecordインスタンスが生成されてしまう分遅いので、存在確認をしたいだけなら下記のようにexistsを使うとよいようです。

if User.exists?(email: "...")
  # something.todo
end

speakerdeck.com

多数のレコードを処理するとき、eachではなく、find_in_batchesを使って一度に生成するインスタンス数を制限する

User.all.each do |user|
  # something.todo
end

としたいとき、

User.find_in_batches(of: 100) do |users|
  users.each do |user|
    # something.todo
  end
end

qiita.com

正規表現にマッチするかどうか調べるとき、=~よりmatch?の方が速い

str =~ /regexp/

より

str.match?(/regexp/)

speakerdeck.com

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

uniquenessバリデーションを使うと、その項目の中で一意であるようにバリデーションがかけられます。

class User
  validates :email, uniqueness: true
end

とすると、emailはUserの中で一意となるようにRails側でバリデーションをかける。

これを、Userが会社に属していて会社の中で一意になるようにバリデーションをかけようとすると

class User
  validates :email, uniqueness: { scope: :company_id }
end

のように、scopeを使って、一意になる範囲を指定する。これをさらに、(例がいいかは少しおいておいて)会社の中で複数の部署に属するユーザはその都度User登録しようとかいう話になって、ある会社のある部署の中で一意というのが書きたいときは、

class User
  validates :email, uniqueness: { scope: [:company_id, :group_id] }

のように、scopeに配列を渡す形となります。

参考

railsguides.jp

310nae.com

RSpecでテストデータ用にファイルを読み込む

RSpecでテスト用のファイルを読み込む

RSpecでテスト用のファイルを読み込む際はデフォルトでは spec/fixtures/files にファイルを置いて

file_fixture("example.txt").read

とすると、ファイルの中身がspec中で呼び出せる。

ファイル中にHTMLやJSONのレスポンスを入れておいてHTTPリクエストをモックしたい場合などに使える。

既にfixturesに他にファイルがあってそれらに合わせてファイルの場所を既定のパスから変更したい場合は

RSpec.configure do |config|
  config.file_fixture_path = "spec/custom_directory"
end

で可能となる。

RSpecでファイルをアップロードする

テストデータでファイルを使いたい状況としては、HTTPリクエストのモック以外にもファイルのアップロードのテストなどがある。 その場合は fixture_file_upload を以下のように使う。

post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')

このメソッドで使うファイルのパスは config.file_fixture_path ではなく、config.fixture_path で指定するので注意。

参考

relishapp.com apidock.com qiita.com

配列の要素全てが該当する条件を記述するallマッチャ

scopeみたいにフィルタリングした要素について、フィルタリング対象の要素について

let(:included) { Item.new(price: 250) }
let(:not_included) { Item.new(price: 300) }
let(:array) { [included, not_included] }

subject(array.filtering)

it '250円以下の品物が絞り込まれる' do
  expect(subject).to contain_exactly(included)
  expect(subject).not_to contain_exactly(not_included)
end

のように、フィルターの境界値の値を持つ要素をフィルタリング前のコレクションに含めておいて、その要素が含まれる/含まれない、といった形でspecを書いていくこともできますが、allマッチャを使うことで

let(:sample1) { Item.new(price: 250) }
let(:sample2) { Item.new(price: 300) }
let(:array) { [sample1, sample2] }

subject(array.filtering)

it '250円以下の品物が絞り込まれる' do
  expect(subject.map(&:price)).to all(be <= 250)
  expect(subject.count).to eq 1
end

元の要素ではなく、絞り込まれた結果に注目してspecを書くことができます。

参考

relishapp.com

Railsのルーティングの細かいところについて

最近細かく引っかかっていたので。

URLヘルパのnew, editの修飾子は前に付く

xxxx_new_pathかnew_xxxx_pathかよく迷っていたけど、

new_xxxx_path, edit_xxxx_path

のほうが正解。namespaceで

# routes.rb
namespace 'admin' do
  resources xxxxs
end

となって、xxxxの前にadminが付くようになっても

new_admin_xxxx_path, edit_admin_xxxx_path

とnew, editが前に付く。

namespaceとscopeの作るパスとアクションの対応の違い

# routes.rb
namespace 'admin' do
  resources xxxxs
end

とすると、

コントローラのクラスのアクション パス名 名前付きルーティングヘルパー
Admin::XxxxsController#index admin/xxxxs admin_xxxxs_path

という風になるが、

# routes.rb
scope 'admin' do
  resources xxxxs
end

とすると、

コントローラのクラスのアクション パス名 名前付きルーティングヘルパー
XxxxsController#index admin/xxxxs xxxxs_path

という風に、コントローラや名前付きルーティングヘルパーはそのままでパスだけ admin がつくようになる。

配列でのパス指定

<%= link_to 'Edit Xxxx', edit_xxxx_path(@xxxx) %>

というのを

<%= link_to 'Edit Xxxx', [:edit, @xxxx] %>

のように書ける。これは、

form_for [:admin, @article]

のように、名前空間の中に入っているパスをform_forで指定したい場合に覚えておくと便利な気がする。

参考

railsguides.jp

railsguides.jp

テスト中のオブジェクトに実際の動作をさせないで返り値を指定する

特定のオブジェクトにあるメソッドの中身を実行せず結果だけ返して欲しい場合

allowとreceiveを使ってスタブの指定をします。

allow(some_object).to receive(:method_name).and_return(return_value)

特定のクラスのインスタンス全てにあるメソッドの中身を実行せず結果だけ返して欲しい場合

allow_any_instance_of(ClassName).to receive(:method_name).and_return(return_value)

以下のようなコントローラのアクションに対するテストなどでテストコードではないところで生成するインスタンスに対して

# コントローラのコード
def index
  api_client = SomeApiClient.new(token)
  @result = api_client.search # 外部APIへリクエストを飛ばす
end
# テストコード
allow_any_instance_of(SomeApiClient).to receive(:search).and_return(["foo", "bar"])

といった具合に使います。

引数があるメソッドの場合に引数を指定したい場合

メソッドの呼び出しに伴う引数を指定したい場合はwithを使います。

allow(some_object).to receive(:method_name).with(arg).and_return(return_value)

複数の引数を指定したい場合は

allow(some_object).to receive(:method_name).with(arg1, arg2, ...).and_return(return_value)

どの引数を受け取ってもいい場合は

allow(some_object).to receive(:method_name).with(anything()).and_return(return_value)

この引数にはハッシュの一部だけを指定したりいろんなバリエーションがあります。

relishapp.com

例外を起こしたい場合

and_raiseを使います。

allow(some_object).to receive(:method_name).and_raise(SomeError.new)

参考

relishapp.com