🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

Q&A

解決済

1回答

1306閲覧

Ruby on Rails のポリモーフィック関連時に発行されるクエリの効率について

qtv28199

総合スコア0

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

0グッド

0クリップ

投稿2021/01/05 09:55

編集2021/01/05 14:46

前提・実現したいこと

ポリモーフィック関連付け を用いてアプリケーションを作成しております。
この時、発行されるクエリが非効率なものではないか、と思うのですが、より適切な方法があるかアドバイスを頂けないでしょうか。

サンプルアプリケーション

会社(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'

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

winterboum

2021/01/05 11:51

シンプルな関連ではないので、 inverse_of を明示的に設定が必要なのではないでしょうか。 ただ、、、 belongs_to :employable, polymorphic: true に対しなにをinverse_ofしたらよいものか、、
guest

回答1

0

自己解決

winterboumさん。コメントを下さりありがとうございます!

inverse_ofを指定し、生成されるクエリが変わることを確認できました。

rb

1class Employee < ApplicationRecord 2 belongs_to :company 3 belongs_to :employable, polymorphic: true, inverse_of: :employee 4 5 def base_salary 6 100 7 end 8 9 def salary 10 employable.salary 11 end 12end

rb

1irb(main):001:0> Employee.first.salary 2 (3.3ms) SELECT sqlite_version(*) 3 Employee Load (0.1ms) SELECT "employees".* FROM "employees" ORDER BY "employees"."id" ASC LIMIT ? [["LIMIT", 1]] 4 Temporary Load (0.3ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 5=> 180 6irb(main):002:0> Company.first.employees.map {|x| x.salary} 7 Company Load (0.1ms) SELECT "companies".* FROM "companies" ORDER BY "companies"."id" ASC LIMIT ? [["LIMIT", 1]] 8 Employee Load (0.2ms) SELECT "employees".* FROM "employees" WHERE "employees"."company_id" = ? [["company_id", 1]] 9 Temporary Load (0.1ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 10 Temporary Load (0.1ms) SELECT "temporaries".* FROM "temporaries" WHERE "temporaries"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] 11 Permanent Load (0.1ms) SELECT "permanents".* FROM "permanents" WHERE "permanents"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 12 Permanent Load (0.1ms) SELECT "permanents".* FROM "permanents" WHERE "permanents"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] 13=> [180, 180, 200, 200]

投稿2021/01/05 15:44

qtv28199

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問