woshidan's loose leaf

ぼんやり勉強しています

Ransackで関連モデルの検索を行う

Ransackでは特定のモデルのカラムだけでなく、そのモデルと関連する他のモデルのカラムでも検索を行うことができる。

例えば、Userモデルに1対多で紐づくCommentモデルのcontents属性で検索したい場合

<%= search_form_for(@q, url: user_search_path) do |f|
    <%= f.search_field comments_contents_cont %>

逆に、Commentモデルに多対1で紐づくUserモデルのname属性で検索したい場合

<%= search_form_for(@q, url: comment_search_path) do |f|
    <%= f.search_field user_name_cont %>

となる。

User<=(1対多)=>UserGroup<=(1対多)=>Group

といった関連のモデルがある場合、

class User < ApplicationRecord
  has_many :user_groups
end
class UserGroup < ApplicationRecord
  belongs_to :user
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :user_groups
end

以下のようにして、Groupの属性からUserを検索することもできる。

<%= search_form_for(@q, url: users_path) do |f| %>
  <%= f.search_field :user_groups_group_name_cont %>

生成されるクエリは以下のような感じ。

  def index
    @q = User.ransack(params[:q])
    binding.pry
    @users = @q.result
  end
[1] pry(#<UsersController>)> @q.result
  User Load (1.8ms)  SELECT "users".* FROM "users" LEFT OUTER JOIN "user_groups" ON "user_groups"."user_id" = "users"."id" LEFT OUTER JOIN "groups" ON "groups"."id" = "user_groups"."group_id" WHERE "groups"."name" LIKE '%グループ%'

参考

qiita.com

メールの仕組みとDNSのMXレコードについて

ちょっと仕事で調べることがあったのでメモ。

メールサーバには2種類があって、MTA(メール転送サーバ)とMUA(メールユーザエージェント)があって、メール転送サーバはDNSが持っているMXレコードのアドレスのサーバにメールを転送する。

このメールを転送していくことをメールをリレーするといったり、メールリレーと言ったりする。この用語はSendGridなどのサービスを利用しているとエラーメッセージとして出てくるので覚えておく必要がある。

sendgrid.kke.co.jp

MXレコードというのは、メールサーバのアドレスのことであるが、http://example.comtaro@example.comといった具合にドメイン部分( example.com )が同じとき、同じ管理者が管理しているんだろうな、ということは予想されるが、メールサーバとウェブサーバは違うサーバになっているということがよくある。

ということでWEBサーバのアドレスとドメインの対応はAレコード、メールサーバのアドレスとドメインの対応はMXレコードとしてDNSに登録する。

hp-shizuoka.jp

メール転送サーバのプロトコルSMTPMUAの主なプロトコルはPOP, IMAPなので、MTAとMUAとではなく、SMTPサーバ、POPサーバとして呼ばれていることがある。

www.kagoya.jp

メールサービスのプロバイダは迷惑メール扱いされていないか、送ったメールが開封されていないか、エラーになるメールアドレスが多くないかといった事象をもとに迷惑メール判定を行う。

baremail.jp

迷惑メール判定に引っかからないようにメールサービスのプロバイダにホワイトリストとして登録するなどの方法がある。

RansackでRailsの検索を楽に作る

Railsで開発していると検索フォームを作成することがよくあると思います。 今日は、そのときよく使われるRansackについてまとめておこうと思います。

基本的な使い方

  def index
    @q = User.ransack(params[:q])
    @users = @q.result
  end

のように特定のパラメータが入った q というリクエストパラメータのキーを渡すと部分一致検索や冒頭一致検索をしてくれます。 なお、resultメソッドの結果に対して、includesやpagingを利用することもできます。

Ransackのフォーム

Railsでフォームを作成するときはform_with(一昔前はform_forやform_tag)を使いますが、Ransackで上記の特定のパラメータが入った検索フォームを作るときは、search_form_for を使います。

そして、検索用のキーには f.search_field を使います。

<%= search_form_for @q do |f| %>

    <%= f.label :name_cont %>
    <%= f.search_field :name_cont %>

    <%= f.submit %>
<% end %>

部分一致検索

あるキーに対し部分一致検索を行いたい場合、 f.search_fieldxxx_cont を渡します。

    <%= f.label :name_cont %>
    <%= f.search_field :name_cont %>

複数の属性に対して同時に部分一致検索を行わせることができて、その場合、

    <%= f.label :name_or_mail_cont %>
    <%= f.search_field :name_or_mail_cont %>

のように _or_ で属性間を繋ぎます。

冒頭一致検索

あるキーに対し冒頭一致検索を行いたい場合、 f.search_fieldxxx_start を渡します。

    <%= f.label :name_start %>
    <%= f.search_field :name_start %>

冒頭一致検索も複数の属性に対してcontの場合と同様に同時に冒頭一致検索を行わせることができます。

    <%= f.label :name_or_mail_start %>
    <%= f.search_field :name_or_mail_start %>

sort_linkで属性の値によってソートを行った結果を出す

また、Ransack gemでは、sort_link というヘルパーを使って、指定した属性で並び替えた結果を出すリンクを簡単に生成することができます*1

<%= sort_link(@q, :name) %>

参考

github.com

*1:sort_urlヘルパーでurlだけ生成することも可能です

Rails6.1になってコントローラのテンプレート指定に拡張子をつけることが推奨されなくなった

Rails 6.1以上になってCSVなどをダウンロードさせるコントローラのアクションに

def download
    render(
      csv: 'users_list',
      template: 'users/download.csv.ruby'
    )
  end

のように書いているとRSpecの実行時に

DEPRECATION WARNING: Rendering actions with '.' in the name is deprecated: users/download.csv.ruby (called from download at ./app/controllers/users_controller.rb:14)

のように注意されるようになりました。これに対応するには、

def download
    render(
      csv: 'users_list',
      template: 'users/download'
    )
  end

と直せばよいのですが、それだけだと今度は views/users/download.csv.ruby が存在していてもダウンロード時に対応するフォーマットのテンプレートが存在しないというエラーになってしまいます。

なぜかというと、デフォルトではダウンロード時のリンクで

<% link_to 'ダウンロード', download_users_path %>

としたとき、ダウンロードのフォーマットはtext/htmlになっています。コントローラに拡張子を書いていたときはその拡張子に沿ってテンプレートを探してくれていましたが、コントローラから拡張子の指定をとるとデフォルトの設定が優先されます。なので、 users/download.csv.rubycsvのファイルが存在していても探しにいくのはtext/html形式のファイルなので見つからない、ということになります。

text/csv 形式のファイルを探しに行ってもらうためにはダウンロードリンクを用意するときに、

<% link_to 'ダウンロード', download_users_path(format: :csv) %>

と、URLヘルパーにformat属性の指定を行えばよいです。こうすると、コントローラに拡張子を書かなくともテンプレートで用意した拡張子のファイルを探しに行ってくれるようになります。このフォーマットはconifg/routes.rbで

resources :users do
  get :download, on: :collection, default: { format: :csv }
end

のようにconfig/routes.rbの方でも指定できます。

参考

railsguides.jp

Enumerizeでenum値を扱う

Railsのモデルでenum値を扱うとき、enumerizeというgemを使うと便利です。

# Gemfile

gem 'enumerize'
class Task
  extend Enumerize
  enumerize :status, in: { pending: 0, todo: 1, done: 2 }
end

と書くと、

Task = Task.new(status: 'pending') #=> valid
Task = Task.new(status: 'unknown') #=> not valid

のようにバリデーションがかけられたり、

Task.where(status: :pending)

のように検索を行うことが可能です。

また、以下のようにlocaleを設定することで

# ja.yml
enumerize:
  task:
    status:
      pending: 待機中
      todo: 対応する
      done: 対応済
Task.status.options # => [["待機中", "pending"], ["対応する", "todo"], ["対応済", "done"]]

のように値を返すようになり、enum値の入力でよく使うselectフォームで

<%= form_for @task do |f| %>
  <%= f.select :task, Task.status.options %>
<% end %>

のように簡潔に記述できるようになります。

参考

doruby.jp

github.com

定数値を環境ごとに分岐したいだけならRails.env.xxx? やconfig/environments以下のファイルに値を書くより環境変数にくくり出した方がよさそう

Railsで開発していると

if Rails.env.production?
  # production環境用の処理
elsif Rails.env.staging?
  # staging環境用の処理
else
  # それ以外の環境用の処理
end

という風に環境ごとの処理を書いたり、config/environments以下のproduction.rbやstaging.rb, development.rbに config.xxx と各環境用の値を設定して、Rails. configuration.xxxと呼び出すことができます。

しかし、環境ごとに変わる部分が単なる定数値であれば環境変数にして

ENV['SOME_VALUE']

のように参照することでproduction.rbやstaging.rbに値をハードコーディングせずに済ませることができます(代わりにdotenvなどを使い、.envファイルを共有したりします)。

こうしておくと環境ごとの値を変更したい場合リポジトリにコードをコミットせず済ませることができるので、処理の流れは変更しないが利用する外部リソースのURIなどを試行錯誤するときなどでは楽になります。

参考

railsdoc.com

qiita.com

pikawaka.com

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

画面を作成していると単純に要素を横に伸ばしたりでスマホと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