Railsでの範囲ループ
日付での範囲指定ループ
occurred_on = "2022-09-24" today = "2022-09-27" (occurred_on..today).each do |date| puts date end
出力
2022-09-24 2022-09-25 2022-09-26 2022-09-27
アルファベットでの範囲指定ループ
("a".."z").each do |char| puts char end
出力
a b c ... z
Mysql2::Error: Can't DROP ''; check that column/key existsが出た時
[状況]
circle ciでridgepoleを使用してのデプロイでindexの張り替えをしたところ
[ERROR] Mysql2::Error: Duplicate entry が発生してindex作成途中にビルドが失敗し
rerunしたところタイトルのエラーが発生しました
## [原因]
index作り直しの途中で失敗してしまったため
indexの整合性が保てず、本来存在してremoveするはずのindexが既にがなくなっていたためエラーになっていました
1: remove_index("baw", name: "index_baw_01") 2: remove_index("baw", name: "index_baw_02") 3: remove_index("baw", name: "index_baw_03") * 4: add_index("baw", ["organization_id", "member_type", "member_id", "target_type", "target_id", "wholesaler_id"], **{:unique=>true, :name=>"unique_baw1"})
removeした後のところでエラーが発生していた
[対応]
データベースで 削除されてしまった本来存在するはずのindexを手動で作成してrerunしたら無事ビルドに成功しました。
各種コマンド
インデックス確認
SHOW INDEX FROM テーブル名;
インデックス作成
CREATE INDEX インデックス名 ON テーブル名(カラム名1, カラム名2, ...);
インデックス削除
DROP INDEX インデックス名 ON テーブル名;
Docker rails 環境でのcredentials:edit
今回書くのは下記の状態になってviが開かない場合です。
docker-compose run -e EDITOR="vim" web rails credentials:edit Starting photo-app_db_1 ... done New credentials encrypted and saved.
上記の様に表示されviが開かない場合viがコンテナにインストールされていない様です。
apt-get install -y vim
これで
E: Unable to locate package vim
になってしまう場合
apt-get update apt-get install -y vim
の順番で行うとvimがインストール出来るのでその後コンテナのCLIで
EDITOR=vi rails credentials:edit
としてやればvimが起動して編集することが出来ました。 他docker-compose.yamlに追記する方法もある様です。
railsで生SQLをスッキリ使う方法
ActiveRecord::Base.sanitize_sql_array
を使用するかと思いますが頻繁に使用するためmoduleとして 切り出して使う方法を紹介します。
module Sanitizable def sanitize_sql(sql, placeholders) squish_sql = sql.split("*/").map { |str| str.squish.gsub(%r(/\*.*\*), '') }.join(' ') ActiveRecord::Base .sanitize_sql_array([squish_sql, placeholders]) end end
squish_sqlはgsubにて除去する記述になりログにコメントが出なくなり見やすくなります。
このモジュールを作成してSQLを使いたいserviceクラスやformクラスにて
include Sanitizable
としてやりSQLを変数などに入れて別途定義したplaceholdersと一緒に
def set_hoge sql = sanitize_sql(HOGE_SQL, placeholders) @fuga = Model名 .from("#{sql} as hoge") end private def placeholders { id: hoge_id, date: date } end
またSQLの規模が大きい場合別途.sqlファイルなどを作成し上記の様な形で渡すことも出来ます。
TEST_SQL = IO.read(File.expand_path('./index_form/test.sql', __dir__))
定数にするのは実行回数ではなく、クラスロード時に一回だけ処理になる=IOの負担を軽減するためです。
rails でバッチ処理を行いたい時
業務だと時々バッチ処理を行う機会があるので その方法をメモしておきます
バッチ処理とは
- ひとまとまりのデータに一括で処理を行うこと
どんな時に行う
途中でロジック変更などを行った際に、バッチ処理を行い既にデータベースなどに保存されているデータを書き換える
応急処置のため、デプロイフローなどを省略してhotfix的に対処したい(弊社はこっちが多い)
方法
現在のプロジェクトの/tmp などに移動し
ruby vim batch.rb
などで行いたい処理を記述する保存する
bundle exec rails r tmp/batch.rb
バッチ処理記述の際
実際にデータを実行する前に下記の様に 確認と選択を行う様にするとミス防止に良いです
def yes_or_no puts "yes or no?" case gets.chomp when "yes" puts "スクリプトを実行します." when "no" puts "スクリプトを終了します." exit 1 else puts "yes または no を入力して下さい.スクリプトを終了します." exit 1 end end def main # 対象のデータを取ってくる処理 target = Model名.Where(name: hoge) # 確認する p target.name yes_or_no # データベースなどに実際に行う処理など target.update!(name: huga) end
rails mysqlでgroupとorderを同時にしたい時
環境
ruby 3.0.3p157
gem 'rails', '~> 6.1.0'
gem 'mysql2', '>= 0.3.18'
やりたかったこと
発注データから商品ごとに最新の発注で使われた発注先を取り出す
結論
MIN,MAXを絡めてselectで発注日.MAXとしつつ、orderで指定、groupで纏める、とすることで取得できた
テーブル構成
店舗テーブル(pharmacy)
id | name | ... |
---|---|---|
1 | 新宿店 | ... |
発注先マスタ(wholesaler)
id | code | name |
---|---|---|
1 | 1 | 卸1 |
2 | 2 | 卸2 |
3 | 3 | 卸3 |
発注テーブル(OrderProduct)
id | 店舗ID | 商品ID | 発注状態 | 発注日 | 発注先ID |
---|---|---|---|---|---|
1 | 1 | ロキソニン | ordered | 2021-01-01 | 1 |
2 | 1 | ロキソニン | ordered | 2021-01-02 | 2 |
3 | 1 | バファリン | ordered | 2021-01-01 | 1 |
商品マスター(master_product)
id | JANコード | 商品名 | ... |
---|---|---|---|
1 | 0000000000001 | ロキソニン | ... |
2 | 0000000000002 | バファリン | ... |
失敗ケース:
普通にorderとgroupをメソッドチェーンで繋ぐ
@last_wholesaler_id = OrderProduct .where(pharmacy_id: @pharmacy.id, 発注状態: 'ordered', master_product_id: test) .order(order_appointed_on: :desc)) .group(:master_product_id)
サブクエリでやる
@last_wholesaler_id = OrderProduct .from(OrderProduct .where(pharmacy_id: @pharmacy.id, 発注状態: 'ordered', master_product_id: test) .order(order_appointed_on: :desc)) .group(:master_product_id)
これらだと意図した結果は得られなかった。
id | 店舗ID | 商品ID | 発注状態 | 発注日 | 発注先ID |
---|---|---|---|---|---|
1 | 1 | ロキソニン | ordered | 2021-01-01 | 1 |
3 | 1 | バファリン | ordered | 2021-01-01 | 1 |
なぜ?
ORDER BYはGROUP BYの後で処理されるため
FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
この場合だと商品ごとに纏められた後、日付での並び替えが発生するため古い発注日のレコードが取得されてしまった。
最終的な形
@last_wholesaler_id = OrderProduct .includes(:wholesaler) .select('master_product_id, wholesaler_id, max(order_appointed_on), max(created_at)') .where(pharmacy_id: @pharmacy.id, order_state: 'ordered', master_product_id: products_ids) .order('max(order_appointed_on)') .order('max(created_at)') .group('master_product_id')
id | 店舗ID | 商品ID | 発注状態 | 発注日 | 発注先ID |
---|---|---|---|---|---|
2 | 1 | ロキソニン | ordered | 2021-01-02 | 2 |
3 | 1 | バファリン | ordered | 2021-01-01 | 1 |
ロキソニンの最新日付のレコードが取れているため、これでOKです。
サブクエリでMAXなどを使う形でも取得出来る
SELECT * FROM table WHERE created_at IN(SELECT MAX(created_at) FROM table GROUP BY ...)