7

STIを使用しているときにrails3とのhas_manyアソシエーションからコレクションをフェッチすると、奇妙な動作が発生します。私は持っています:

class Branch < ActiveRecord::Base
   has_many :employees, class_name: 'User::Employee'
   has_many :admins, class_name: 'User::BranchAdmin'
end

class User < ActiveRecord::Base
end

class User::Employee < User
  belongs_to :branch
end

class User::BranchAdmin < User::Employee
end

望ましい動作は、branch.employeesブランチ管理者を含むすべての従業員を返すことです。ブランチ管理者は、によってアクセスされた場合にのみ、このコレクションの下に「ロード」されているように見えますbranch.admins。これは、コンソールから出力されます。

Branch.first.employees.count
=> 2

Branch.first.admins.count
=> 1

Branch.first.employees.count
=> 3

これは、生成されたSQLで初めて確認できます。

SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee') AND "users"."branch_id" = 1

そして2回目:

SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = 1

次のように指定するだけで、この問題を解決できます。

class Branch < ActiveRecord::Base
   has_many :employees, class_name: 'User'
   has_many :admins, class_name: 'User::BranchAdmin'
end

それらはすべてbranch_idから検出されますが、これを実行したい場合はコントローラーで問題が発生するためbranch.employees.build、クラスはデフォルトでデフォルトにUserなり、どこかのタイプ列をハックする必要があります。私は今のところこれを回避しました:

  has_many :employees, class_name: 'User::Employee', 
    finder_sql: Proc.new{
      %Q(SELECT users.* FROM users WHERE users.type IN          ('User::Employee','User::BranchAdmin') AND users.branch_id = #{id})
    },
    counter_sql: Proc.new{
      %Q(SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = #{id})
    }

でも、できればこれは避けたいです。誰か、何かアイデアはありますか?

編集:

finder_sqlとcounter_sqlは、親の関連付けがこれを使用していないようで、選択にクラスのみが含まれるため、実際には解決してorganisation.employeesいません。has_many :employees, through: :branchesUser::Employee

4

4 に答える 4

20

基本的に、問題はクラスが必要に応じてロードされる開発環境にのみ存在します。(本番環境では、クラスがロードされ、利用可能に保たれます。)

問題は、インタプリタがまだ見ていなかったために発生します。これは、などの呼び出しを最初に実行したときAdminsのタイプです。EmployeeEmployee.find

(後で使用することに注意してくださいIN ('User::Employee', 'User::BranchAdmin')

これは、複数レベルの深さのモデルクラスを使用するたびに発生しますが、開発モードでのみ発生します。

サブクラスは常に親階層を自動ロードします。基本クラスは、子階層を自動ロードしません。

ハック修正:

基本クラスのrbファイルからすべての子クラスを明示的に要求することにより、devモードで正しい動作を強制できます。

于 2012-11-25T13:50:28.287 に答える
2

:conditionsを使用できますか?

class Branch < ActiveRecord::Base
   has_many :employees, class_name: 'User::Employee', :conditions => {:type => "User::Employee"}
   has_many :admins, class_name: 'User::BranchAdmin', :conditions => {:type => "User::BranchAdmin"}
end

これが私の好みの方法です。これを行うもう1つの方法は、ポリモーフィックモデルにデフォルトのスコープを追加することです。

class User::BranchAdmin < User::Employee
  default_scope where("type = ?", name)
end
于 2012-09-20T22:04:21.667 に答える
0

同様の問題がRails6にも引き続き存在します。

このリンクは、問題と回避策の概要を示しています。次の説明とコードスニペットが含まれています。

Active Recordは、正しいSQLを生成するために、STI階層を完全にロードする必要があります。Zeitwerkでのプリロードは、次のユースケース向けに設計されています。

ツリーの葉をプリロードすることにより、自動ロードはスーパークラスに続く階層全体を上向きに処理します。

これらのファイルは、起動時および再読み込み時にプリロードされます。

# config/initializers/preload_vehicle_sti.rb

autoloader = Rails.autoloaders.main
sti_leaves = %w(car motorbike truck)

sti_leaves.each do |leaf|
  autoloader.preload("#{Rails.root}/app/models/#{leaf}.rb")
end

spring stop構成を変更するには、が必要になる場合があります。

于 2021-10-05T15:55:17.060 に答える
0

確かに、それは宝石の初期の計画でしたが、すぐに放棄されました(2019年、Rails 6がリリースされる前)。プリロードは長い間非推奨であり、次のZeitwerk2.5で削除されました。

Railsアプリケーションでは、次のように実行できます。

# config/initializers/preload_vehicle_sti.rb
Rails.application.config.to_prepare do
  Car
  Motorbike
  Truck
end

つまり、to_prepareブロック内の定数を使用するだけで「プリロード」します。

于 2021-10-12T17:31:17.713 に答える