LoginSignup
1
1

More than 5 years have passed since last update.

Active Record AssociationからModelのクラスメソッドを呼んだら想定していなかったクエリが飛んだ

Last updated at Posted at 2015-11-20

端的に言えば、Association からクラスメソッドを呼ぶと、Association に属するレコード群に対してメソッドが呼ばれて少しハマったという話。

やりたかったこと

ユーザーが商品を購入するサービスで、商品毎の購入者番号(Buyer.number)を購入順で振りたい。

実装

Model

サービス利用者であるUser は、購入者情報であるBuyerを複数持ち、BuyerUserProductに結びつく

app/model/user.rb
class User < ActiveRecord::Base
  has_many :buyers
app/model/buyer.rb
class Buyer < ActiveRecord::Base
  belongs_to :user
  belongs_to :product

  # Productに対する購入者番号を自動で割り振りつつRecordを生成
  def self.create_by_product(product)
    create(product_id: product.id, number: current_provided_number(product))
  end

  # 現在割り振られる購入者番号
  # Productの購入者がいないなら1を、いるなら現在の最大購入者番号+1を返す
  def self.current_provided_number(product)
    if exists?(product_id: product.id)
      current_last_buyer = where(product_id: product.id).order('number ASC').last
      current_last_buyer.number + 1
    else
      1
    end
  end

Controller

購入時にUserにひも付ける形でBuyerを生成

app/controller/products_controller.rb
product.with_lock { user.buyers.create_by_product(product) }

実行結果

期待してた挙動

app/model/buyer.rb
if exists?(product_id: product.id)
      current_last_buyer = where(product_id: product.id).order('number ASC').last

のif節内で

WHERE `buyers`.`product_id` = 12 LIMIT 1

のようなクエリが飛ぶと思っていた。

実際の挙動

WHERE `project_sponsors`.`user_id` = 3 AND `project_sponsors`.`project_id` = 12 LIMIT 1

これが飛んだ。

原因と解決策

user.buyers.create_by_product(product)

といった呼び方をしたため、意図せずuser_idでも絞り込みがかかってしまった。

解決策としては、基本的に呼び出す側で気をつけましょうというのが一番正しいと思う。
ただ、絶対にAssociationではなくてActiveRecordそのものに対して呼びたいメソッドである場合、unscoped使うとかもありなのかも。

app/model/buyer.rb
class Buyer < ActiveRecord::Base
  ...

  # 現在割り振られる購入者番号
  # Productの購入者がいないなら1
  # いるなら最大購入者番号+1を返す
  def self.current_provided_number(product)
    unscoped do   # どこから呼ばれようとBuyerそのものに対してクエリを投げる
      if exists?(product_id: product.id)
        current_last_buyer = where(product_id: product.id).order('number ASC').last
        current_last_buyer.number + 1
      else
        1
      end
    end
  end

こんな感じで。
ひょっとしたらアンチパターンかもしれないけど。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1