rails:コンソールから生SQLを実行する
以下のように、rails consoleから生SQL実行できる。
戻り値がActiveRecord::Resultなので、hash化すると見やすい。
(hash化しなくても見れる)
sql = 'SELECT staff_type, count(*) FROM staffs where is_valid = true group by staff_type order by staff_type' ActiveRecord::Base.connection.select_all(sql).to_hash
結果0件の場合は、to_hashだとエラーになるので注意
以下でも実行できるが、クエリーの結果をのぞくには扱いにくい。
ActiveRecord::Base.connection.execute(sql)
rails:更新系処理の悲観的ロックのサンプル
掲題を実現する場合のサンプル。
処理で更新されるテーブルのモデルに、以下のようなメソッドを実装。
class BatchExecUpdateDate < ApplicationRecord # batch_exec_update_dates # # id :bigint(8) not null, primary key # proc_type :integer not null # upated_at :datetime not null enum proc_type : { daily: 0, monthly: 1} class << self def transaction_with_lock(proc_type, &block) target_rec = find_or_create_by(proc_type: proc_type) ActiveRecord::Base transacrion do # 行ロック取得 lock_rec = BatchExecUpdateDate.lock('FOR UPDATE NOWAIT').find(target_rec.id) # 引数で渡されたブロックを処理する block.call lock_rec.update(updated_at: Time.zone.now) end true rescue ActiveRecord::StatementInvalid => e return false if e.cause&.kind_of?(PG::LockNotAvailable) raise e end end end
modelでupdate_atを必須valideteかけている場合は、
明示的にupdated_atをセットしないとエラーになる
validate指定を外すと、自動でセットしてcreateしてくれる
validates :updated_at, presence: true target_rec = find_or_create_by(proc_type: proc_type) {|beud| beud.updated_at = Time.zone.now}
以下のような処理があったとして
# 処理クラス class AaaBatch class << self def update_process # 更新処理 end end end
以下のように呼び出すことで、実現できる。
# 起動するとき BatchExecUpdateDate.transaction_with_lock(proc_type) do AaaBatch.update_process(0) BbbBatch.update_process(0) : end
RSpec:よく使うmatcherメモ
付け足し付け足ししていく予定です。
インスタンスの内容のテスト
expect(section).to have_attributes(name: 'default') expect(section.is_valid).to be_truthy expect(section.retired_at).to be nil
件数に変更がない(unchange推奨)
expect{subject}.to change {Company.count}.by(0).and change {Section.count}.by(0) expect{subject}.to unchange {Company.count}.and unchange {Section.count}
参考:毎度多謝)使えるRSpec入門
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
rails:コールバック処理
あらかじめ定義しておき、必要なタイミングで自動で動く処理。
以下だと、Companyがcreateされると、直後にSectionもcreateされる。
(Section.name を仮設定しているのもコールバック)
# app/model/company.rb class Company < ApplicationRecord has_many :sections # callback after_create :create_default_section def create_default_section section.create!( code: Section::DEFAULT_SECTION_NAME, company: self ) end end # app/model/section.rb class Section < ApplicationRecord belong_to :company # callback before_create :set_name DEFAULT_SECTION_NAME = 'DEFAULT'.freeze def set_name self.name ||= '(temporary)' # 未設定の場合は仮名をセット end end
おまけ)create と create! の違い
例外(赤い画面)になるかならないか、が異なる。
create! はバリデーションNGになるとActiveRecord::RecordInvalid例外が発生する。
!がない場合は、保存されないが例外(赤い画面)も出ない。
ruby:ハッシュ操作の応用1
以下のようなハッシュを画面に返すとする。
# 画面に引き渡すインスタンス変数(Hash) # @conpany_inf カンパニー情報(ヘッダ情報) # { # company_name [string] カンパニー名 # company_started_at [datetime] 創設日 # staff_count [integer] スタッフ人数 # } # @staff_inf スタッフ一覧情報 # [{ # staff_no: [string] スタッフNO # staff_name: [string] スタッフ名 # staff_class: [string] 一般職/管理職 # license_info: 資格情報 # [{ # license_name [string] 資格名 # license_get_date [datetime] 取得日 # }] # }]
各モデルは以下のようなイメージ。
# app/model/company.rb class Company < ApplicationRecord has_many :staffs end # app/model/staff.rb class Staff < ApplicationRecord belong_to :company has_many :licenses scope :alive, -> { where(is_retire: false) } scope :manage_alive, -> { alive.where(is_manage: true) } end # app/model/license.rb class License < ApplicationRecord end
コントローラは以下のようなロジックで実装。
# controller/staff_controller.rb class StaffController < ApplicationController # 画面に引き渡すインスタンス変数(Hash) # @conpany_inf カンパニー情報(ヘッダ情報) # { # company_name [string] カンパニー名 # company_started_at [datetime] 創設日 # staff_count [integer] スタッフ人数 # } # @staff_inf スタッフ一覧情報 # [{ # staff_no: [string] スタッフNO # staff_name: [string] スタッフ名 # staff_class: [string] 一般職/管理職 # license_info: 資格情報 # [{ # license_name [string] 資格名 # license_get_date [datetime] 取得日 # }] # }] def index @conpany_inf = conpany_inf_rec(company) @staff_inf = [] staffs = company.staffs.order(:staff_no).alive staffs.each do |s| @staff_inf << staff_inf_rec(s) end end private def company @company ||= Company.find(params[:id]) end def conpany_inf_rec(company) { company_name: company&.name company_started_at: company.&.started_at staff_count: company.staffs.count } end def staff_inf_rec(staff) { staff_no: staff&.staff_no staff_name: staff&.name staff_class: staff&.is_manage_lank & 'manage' : 'normal' license_info: license_info_rec(staff) } end def license_info_rec(staff) staff.licenses.order(:get_date).map do |li| { license_name: li.name license_get_date: li.get_date } end end end
rails:判定ロジックサンプル備忘1
以下のようなケースの判定ロジックを考える。
モデルで以下のように定数定義。
app/models/worker.rb
class Worker < ApplicationRecord WORKER_TYPES = { TEACHER: 1, PILOT: 2 }.each_value(&:freeze).freeze end
デコレータは以下のように継承して定義。
app/decorators/
class WorkerDecorator < ApplicationDecorator end class TeacherDecorator < WorkerDecorator end class PilotDecorator < WorkerDecorator end
ロジックでは以下のように判定して利用する。
decorate_worker = if params[:worker_type] == Worker::WORKER_TYPES[:TEACHER].to_s TeacherDecorator.decorate(worker) else PilotDecorator.decorate(worker) end puts decorate_worker.abillity
上記を、メソッド化して、以下のように整理。
メソッドからは、クラスを返却している。
decorate_worker = worker_decorator(params[:worker_type]).decorate(worker) puts decorate_worker.abillity def teacher?(worker_type) worker_type == Worker::WORKER_TYPES[:TEACHER].to_s end def pilot?(worker_type) !teacher?(worker_type) end def worker_decorator(worker_type) # classを返却 teacher?(worker_type) ? TeacherDecorator : PilotDecorator end
メソッド化しないのであれば、省略記述できるが、可読性は下がる。
以下、記述例。
# ex1 decorator = params[:worker_type] == Worker::WORKER_TYPES[:TEACHER].to_s ? TeacherDecorator : PilotDecorator decorate_worker = decorator.decorate(worker) # ex2 decorator = params[:worker_type] == Worker::WORKER_TYPES[:TEACHER].to_s ? TeacherDecorator : PilotDecorator decorate_worker = decorator.decorate(worker) # ex3 decorate_worker = decorator(params[:worker_type]).decorate(worker) def decorator(worker_type) worker_type == Worker::WORKER_TYPES[:TEACHER].to_s ? TeacherDecorator : PilotDecorator end