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 '%グループ%'
参考
メールの仕組みとDNSのMXレコードについて
ちょっと仕事で調べることがあったのでメモ。
メールサーバには2種類があって、MTA(メール転送サーバ)とMUA(メールユーザエージェント)があって、メール転送サーバはDNSが持っているMXレコードのアドレスのサーバにメールを転送する。
このメールを転送していくことをメールをリレーするといったり、メールリレーと言ったりする。この用語はSendGridなどのサービスを利用しているとエラーメッセージとして出てくるので覚えておく必要がある。
MXレコードというのは、メールサーバのアドレスのことであるが、http://example.com
とtaro@example.com
といった具合にドメイン部分( example.com
)が同じとき、同じ管理者が管理しているんだろうな、ということは予想されるが、メールサーバとウェブサーバは違うサーバになっているということがよくある。
ということでWEBサーバのアドレスとドメインの対応はAレコード、メールサーバのアドレスとドメインの対応はMXレコードとしてDNSに登録する。
メール転送サーバのプロトコルはSMTP、MUAの主なプロトコルはPOP, IMAPなので、MTAとMUAとではなく、SMTPサーバ、POPサーバとして呼ばれていることがある。
メールサービスのプロバイダは迷惑メール扱いされていないか、送ったメールが開封されていないか、エラーになるメールアドレスが多くないかといった事象をもとに迷惑メール判定を行う。
迷惑メール判定に引っかからないようにメールサービスのプロバイダにホワイトリストとして登録するなどの方法がある。
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_field
に xxx_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_field
に xxx_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) %>
参考
*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.ruby
とcsvのファイルが存在していても探しにいくのは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の方でも指定できます。
参考
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 %>
のように簡潔に記述できるようになります。
参考
定数値を環境ごとに分岐したいだけなら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などを試行錯誤するときなどでは楽になります。
参考
画面の横幅が大きい(小さい)ときのみ要素を表示する
画面を作成していると単純に要素を横に伸ばしたりでスマホと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以下になると青い要素が表示される -->