ActiveRecord::Relation#or
弊社の新規プロジェクトでは、Rails5が使われていてそのおかげで、or
が利用できた。
今日は、その辺りをまとめてみる。
ActiveRecord::Relation#or
Rails 4系では、sqlのor
をアプリで実装しようとするとfind_by_sql
、where
、joins
などに直接sqlを書くか。Arel
を利用して実装を進めていた。
Rails 5系からor
が利用できるようになっているので、直接sql
を記述することなく、ActiveRecord
の操作で完結できる。
# A || B Model.where(name: 'hoge').or(Model.(name: 'fuga')) # A && B || C Model.where(name: 'hoge').where(age: 10).or(Model.where(name: 'fuga')) # A ‖ B && C Model.where(name: 'hoge').or(Model.where(age: 10)).where(name: 'fuga'))
という結果が返ってくる。
must be structurally compatible
構造互換性が必要なので、orに指定するActiveRecord::Relation
とレシーバの構造は統一する。
互換性がなければ、下記のArgumentError
が発生する。
ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:limit]
サンプル
Model.joins(:hoges) .merge(Model.where(name: nil) .or(Hoge.where(fuga: nil) => SELECT "model".* FROM "model" INNER JOIN "hoges" ON "hoges"."model_id" = "models"."id" WHERE ("models"."name" IS NULL OR "hoges"."fuga" IS NULL)
これで、and
とor
の条件を切り換えられるロジックを書くことが容易になった。
参考
form_tag、form_forからutf8というパラメータをはずす
検索の機能で、HTTP methodsをpost
からget
に切り替えた際に、utf8
というパラメータが含まれていたので、これを取り除いた。
form_for
, form_tag
から自動で生成されるinput
タグだが、不要だったので、自動生成されないように変更した。
モンキーパッチ
module ActionView module Helpers module FormTagHelper def utf8_enforcer_tag ''.html_safe end end end end
上記で、form_for
とform_tag
から、utf8のinput
タグは生成されない。
オプションで指定することもできるようだが、今回一部対応ではなく、プロジェクト全体から取り除いた。
参考
vim snippetの利用
vimからsnippetは以前から利用していますが、環境毎に設定していることがあり、再設定していたので こちらにまとめておく。
環境
.vimrc
.vimrc
に追加
... if &compatible set nocompatible " Be iMproved endif execute 'set runtimepath^=~/.vim/repos/github.com/Shougo/dein.vim' " Required: call dein#begin('~/cache/dein') call dein#add('Shougo/dein.vim') call dein#add('Shougo/neocomplete.vim') " ここから call dein#add('Shougo/neosnippet') call dein#add('Shougo/neosnippet-snippets') " ここまで call dein#end() filetype plugin indent on " ここから " Plugin key-mappings. " <C-k>でsnippetの展開 imap <C-k> <Plug>(neosnippet_expand_or_jump) smap <C-k> <Plug>(neosnippet_expand_or_jump) xmap <C-k> <Plug>(neosnippet_expand_target) let g:neosnippet#snippets_directory = '~/.vim/snippets/' if has('conceal') set conceallevel=2 concealcursor=niv endif " ここまで ...
dein#install()
vimを起動して、:call dein#install()
もしくは、vimrc
に下記を記述する。
" pluginsがインストール済みか確認 if dein#check_install() call dein#install() endif
使い方
Ctrl + k
で利用する。
snippetの追加
独自のsnippetを利用する場合には、snippets_directory
で指定したフォルダにsnip
ファイルを作成して、追加していくか。
vimを起動してNeoSnippetEdit
コマンドを叩く。
vim hoge.rb
でrubyのファイルを開いているなら、NeoSnippetEdit
では、ruby.snip
が開かれる。
ファイルがなければ、新規作成する。
参考
GitHub - Shougo/dein.vim: Dark powered Vim/Neovim plugin manager
GitHub - Shougo/neosnippet.vim: neo-snippet plugin contains neocomplcache snippets source
Rubyのモジュール
コードを整理する際に、classではなく、moduleで整理したいと思う場面がありました。
Railsで書いていることもあり、ActiveSupport::Concernにまとめる形にします、 moduleクラスに機能を混ぜ合わせることで複数のクラスで機能を共有する(Mix-in)という機能を提供しています。
さらにActiveSupport::Concernは、関心事を分離するために独立した機能を定義したモジュールが配置されますので、これを利用することにします。
ActiveSupport::Concern
module DataAble extend ActiveSupport::Concern included do scope :with_deleted -> { where.not(deleted_at: nil) } scope :without_deleted -> { where(deleted_at: nil) } end def set_data @data = Data.pluck(:id, :name) end end
class Hoge include SetDataAble end
これで, できるようになります。
@hoge = Hoge.new @hoge.set_date Hoge.with_deleted
参考
ruby on rails - なぜRailsのモジュールでは"able"をサフィックスとして付けるのでしょうか? - スタック・オーバーフロー
複数ファイル内の置換
複数ファイルを一度に置換したくて使っているコマンドです。 railsのroutes.rbを修正して、ルーティングヘルパが変わったので一斉に置換しました。
find . -type f -print0 | xargs -0 sed -i -e “s/info_salutation_path/salutation_path/
macOSの場合は、iオプションの挙動が違うので注意です。
参考
tmux : unknown option: status-utf8というエラーメッセージ
warningがでていたので、tmux.confを更新した。
status-utf8 has been removed after version 2.2.
ということで、setw -g status-utf8 on;
を削除しましょう。
参考
constraintsの利用について
後輩にvimの折り畳み開閉のショートカットキーを聞かれてて、忘れてたのに落ち込んだり、
参加したセミナーでテストコードに関する有益な話を聞けてほっこりしてます。
コミュ力って重要。
Railsのroutes.rbで、ルーティングを無条件で利用すると、public下の置いているファイルとルーティングが当たってしまうので、
これを回避するためにURIを変えるか。制限を入れるかを考えていて、制限を入れることにしました。
constraints
を使って定義する。
環境
constraints
定義したルーティングに制限を追加することできます。
# config/routes.rb Rails.application.routes.draw do get 'users/:user_id' => 'users#show', constraints: /\d+/ end
これは、ブロックも利用できて、複数のルーティングに制限を加える際に利用できる。
Rails.application.routes.draw do constraints /\d+/ do get 'welcome/:id' => 'welcome#show' ... end end
今回は、DBの値を利用した制限を追加する形で実装を進めていてconstraints
のファイル保存場所について
app
を検討したが、ひとまずlib
に保存しています。
まず、lib/autoload
を起動時に自動で読み込むようにします。
# config/application.rb ... config.autoload_paths += %W(#{Rals.root}/lib) ...
# lib/autoload/constraints/zone_constraint.rb class ZoneConstraint def matches?(request) paths = request.path_info&.split('/')&.reject(&:blank?) paths.size.times do |i| case i when 0 fail if ExampleA.find_by(key: areas[i]).blank? when 1 fail if ExampleB.find_by(key: areas[i]).blank? when 2 fail if ExampleC.find_by(key: areas[i]).blank? else fail end end true rescue false end end
最後にroutes.rbに制限を加えます。
# config/routes.rb constaints ZoneConstraint do get ':example_a/:example_b/:example_c' => 'example#index ... end
データベースに保存されていない場合、ルーティングを参照しないようにします。
参考
Rails3 事始め: [Rails3] 現在のURLを取得(request オブジェクト)
rails routing constraintsについて | 日々雑記
friendly idを利用したURI設計
プロジェクトでSEO対策と数値を含めずにURIを設計するため、friendly_idを採用してURIの再設計をしました。
railsではURIにmodelの主キーを含める形の設計が主になりますが、数値では分かりづらくなります。(resourceでは作成されませんが)
それほど複雑な仕様でもないため、gemを使わず、参照しているmodelのnameカラムがありましたので英語名を追加することで実現しました。
サンプルコードは、実際に実装したコードとは異なります。
環境
Rails appの作成
# rails new friendly_app # cd friendly_app # ./bin/rails g scaffold users name_en:string # ./bin/rails db:migrate
実装
app/models/user.rb
に、id
ではなくname_en
を利用するように書き換える。
# app/models/user.rb モデルに'#to_param'を追記する。 class User < ApplicationRecord validates :canonical_name, uniqueness: { case_sensitive: false }, format: { with: /\A[A-Za-z][\w-]\z/ }, length: { minimum: 3, maximum: 25 } def to_param name_en ? name_en : id.to_s end def self.find(input) find_by(name_en: input) || super end end
rails s
app serverを起動して。
# ./bin/rails s
動作確認
users/new
でユーザを作成する。users/:name_en
に移動する。
まとめ
route.rbがresources
のままになっており
params[:id]でname_en
の値を受け渡すことになりわかりずらくなるので、idを受け渡さない画面ではroute.rbを変更しました。
参考URL
Friendly URLs in Rails · GitHub
Rspecでseeds.rbをリセット時に読み込む。
プロジェクトにrspecとfactory girlのgemを追加して seeds.rbを読み込んでから、毎回テストを起動したかったのでこのあたりをまとめておく。
環境
rspecとfactory girlの導入
まずは、Gemfileにrspec_rails
とfactory_girl_rails
を追加してbundle install
します
# Gemfileにgemを追加 group :development, :test do gem 'rspec_rails' gem 'factory_girl_rails' end group :test do gem 'database_clear' end
bundle install
rspec install
$ rails g rspec:install
環境別のseedを作成する
これはRails.env.hoge?
で切り分ければ、seeds.rbにまとめることもできる。
env毎のファイルを作成する。
mkdir db/seeds touch db/seeds/test.rb touch db/seeds/development.rb touch db/seeds/production.rb
db/seeds/test.rb
とdb/seeds/development.rb
に記述を追加
values = { email: 'hoge@example.com'... } # attriubutesを設定する。 Admin.create(values)
seeds.rb
に下記のコードを追加。
load(Rails.root.join( 'db', 'seeds', "#{Rails.env.downcase}.rb"))
テスト時にdbをリセットして、seedsを読み込む。
#clean_with
の引数に、exceptでクリーンにしないmodel名を記述することもできます。
config.before(:suite) do load Rails.root.join('db', 'seeds.rb') DatabaseCleaner.clean_with :truncation # DatabaseCleaner.clean_with :truncation, { except: %w(categories brands) } end