前提・実現したいこと
ポリモーフィック関連付け を用いてアプリケーションを作成しております。
この時、発行されるクエリが非効率なものではないか、と思うのですが、より適切な方法があるかアドバイスを頂けないでしょうか。
サンプルアプリケーション
会社(Companyクラス
)は社員(Employeeクラス
)を複数もち、
社員は正社員(Permanentクラス
), 契約社員(Temporaryクラス
)の種類があり、給与計算(salaryメソッド
)のロジックが異なります。
異なるロジックを隠蔽して扱うためにポリモーフィック関連付けを利用できないかと考えました。
rb
1# 会社 2class Company < ApplicationRecord 3 has_many :employees 4end 5 6# 社員 7class Employee < ApplicationRecord 8 belongs_to :company 9 belongs_to :employable, polymorphic: true 10 11 # 基本給(社員種別を問わず一緒) 12 def base_salary 13 100 14 end 15 16 # 給与計算 17 def salary 18 employable.salary 19 end 20end 21 22# 正社員 23class Permanent < ApplicationRecord 24 has_one :employee, as: :employable, dependent: :destroy 25 26 # 給与計算 27 def salary 28 # 基本給 + 100円とする 29 employee.base_salary + 100 30 end 31end 32 33# 契約社員 34class Temporary < ApplicationRecord 35 has_one :employee, as: :employable, dependent: :destroy 36 37 # 給与計算 38 def salary 39 # 基本給 + 80円とする 40 employee.base_salary + 80 41 end 42end
以上のソースコードでEmployee.salary
で社員の種別を意識せず給与計算を行うことができるようになりました。
しかし、発行されるSQLを確認したところ、
irb(main):001:0> Employee.first.salary (2.0ms) SELECT sqlite_version(*) Employee Load (0.5ms) SELECT "employees".* FROM "employees" ORDER BY "employees"."id" ASC LIMIT ? [["LIMIT", 1]] Temporary Load (0.6ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] Employee Load (0.6ms) SELECT "employees".* FROM "employees" WHERE "employees"."employable_id" = ? AND "employees"."employable_type" = ? LIMIT ? [["employable_id", 1], ["employable_type", "Temporary"], ["LIMIT", 1]] => 180
Employee
が二度取得されてしまいました。
全社員を給与計算を行った場合も同様です。
rb
1Company.first.employees.map {|x| x.salary} 2 Company Load (0.3ms) SELECT "companies".* FROM "companies" ORDER BY "companies"."id" ASC LIMIT ? [["LIMIT", 1]] 3 Employee Load (0.3ms) SELECT "employees".* FROM "employees" WHERE "employees"."company_id" = ? [["company_id", 1]] 4 Temporary Load (0.2ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 5 Employee Load (0.2ms) SELECT "employees".* FROM "employees" WHERE "employees"."employable_id" = ? AND "employees"."employable_type" = ? LIMIT ? [["employable_id", 1], ["employable_type", "Temporary"], ["LIMIT", 1]] 6 Temporary Load (0.1ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] 7 Employee Load (0.9ms) SELECT "employees".* FROM "employees" WHERE "employees"."employable_id" = ? AND "employees"."employable_type" = ? LIMIT ? [["employable_id", 2], ["employable_type", "Temporary"], ["LIMIT", 1]] 8 Permanent Load (0.1ms) SELECT "permanents".* FROM "permanents" WHERE "permanents"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 9 Employee Load (0.1ms) SELECT "employees".* FROM "employees" WHERE "employees"."employable_id" = ? AND "employees"."employable_type" = ? LIMIT ? [["employable_id", 1], ["employable_type", "Permanent"], ["LIMIT", 1]] 10 Permanent Load (0.1ms) SELECT "permanents".* FROM "permanents" WHERE "permanents"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] 11 Employee Load (0.1ms) SELECT "employees".* FROM "employees" WHERE "employees"."employable_id" = ? AND "employees"."employable_type" = ? LIMIT ? [["employable_id", 2], ["employable_type", "Permanent"], ["LIMIT", 1]] 12=> [180, 180, 200, 200]
給与計算のロジック内で、Employee.base_salary
を利用するためなのですが、取得済みのEmployee
を利用させることはできないのでしょうか。
試したこと
ポリモーフィック関連付けを利用しない場合では、何度もクエリが発行されている様ではありませんでした。
rb
1# 契約社員管理者 2class TemporaryEmployeeManager < ApplicationRecord 3 has_many :temporaries 4 5 # 何らかのロジックfoo 6 def foo 7 'foo' 8 end 9end 10 11# 契約社員 12class Temporary < ApplicationRecord 13 belongs_to :temporary_employee_manager 14 15 # 何らかのロジックfoo 16 def foo 17 # 管理者のfooに依存している 18 temporary_employee_manager.foo + 'bar' 19 end 20end
rb
1irb(main):002:0> TemporaryEmployeeManager.first.temporaries.map { |x| x.foo } 2 TemporaryEmployeeManager Load (0.6ms) SELECT "temporary_employee_managers".* FROM "temporary_employee_managers" ORDER BY "temporary_employee_managers"."id" ASC LIMIT ? [["LIMIT", 1]] 3 Temporary Load (0.8ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."temporary_employee_manager_id" = ? [["temporary_employee_manager_id", 1]] 4=> ["foobar", "foobar"]
補足情報(FW/ツールのバージョンなど)
Gemfile
1ruby '2.6.5' 2 3# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 4gem 'rails', '~> 6.0.3', '>= 6.0.3.2'
回答1件
あなたの回答
tips
プレビュー