2

Rails2.3.14を使用しています

UPDATE 3 Update 2で見つけたトリックは、関連付けの最初のアクセスに対してのみ機能します...したがって、アプリケーションコントローラーにset_table_name行を貼り付けます。これはとても奇妙です。これが修正されることを願っています。= \

UPDATE 2環境ファイルの下部にある厄介なクラスのテーブルを手動で/厄介に/ハッキーに設定すると、エラーが発生しなくなります。

app / config / environment.rb:

Page::Template::Content.set_table_name "template_page_contents"
Page::Template::Section.set_table_name "template_page_sections"

なぜ私はこれをしなければならないのですか?この特定のユースケースではレールが壊れていますか?

更新:私のPage :: Template :: Contentクラスで最初に呼び出されたとき、set_table_nameが機能していないようです。

しかし、アソシエーションを使用する直前に呼び出すと、機能します...

ap Page::Template::Content.table_name # prints "contents"
Page::Template::Content.set_table_name "template_page_contents"
ap Page::Template::Content.table_name # prints "template_page_contents"

return self.page_template_contents.first # doesn't error after the above is exec'd

元の質問

TL; DR:アクセスしようとしているhas_manyリレーションシップがありますが、Railsは、has_manyリレーションシップが使用するテーブルは別のテーブルであると考えています。

リレーションシップを介してaPage::Template::Contentにbelongs_toにアクセスしようとすると、このエラーが発生します。Page::Templatehas_many

Mysql2::Error: Unknown column 'contents.template_page_id' in 'where clause': SELECT * FROM `contents` WHERE (`contents`.template_page_id = 17)  LIMIT 1

エラーログを見ると、railsが間違ったテーブルで関連するオブジェクトを見つけようとした理由を見つけるために、いくつかのprintステートメントの使用を開始する必要があることがわかりました。

gems_path/activerecord-2.3.14/lib/active_record/associations/association_collection.rb:63:in `find'

@reflectionすべてがその周りで起こっているように見えるので、私はちょうどオブジェクトを印刷することにしました。これが私がそれをした方法です:

  require "awesome_print" # best console printer (colors, pretty print, etc)
  def find(*args) # preexisting method header
     ap "-------" # separator so I know when one reflection begins / ends, etc
     ap @reflection # object in question
     ... # rest of the method

エラーの前に出力された最後の「@reflection」:

"-------"
#<ActiveRecord::Reflection::AssociationReflection:0x108d028a8
    @collection = true,
    attr_reader :active_record = class Page::Template < LibraryItem {...},
    attr_reader :class_name = "Page::Template::Content",
    attr_reader :klass = class Content < LibraryItem {...},
    attr_reader :macro = :has_many,
    attr_reader :name = :page_template_contents,
    attr_reader :options = {
        :foreign_key => "template_page_id",
         :class_name => "Page::Template::Content",
             :extend => []
    },
    attr_reader :primary_key_name = "template_page_id",
    attr_reader :quoted_table_name = "`contents`"
>

上記のコードブロックにはいくつか問題があります。

:klass should be Page::Template::Content
:name should be :contents  
:quoted_table_name should be `contents`

モデルの設定方法:

app / models / page.rb:

class Page < LibrayItem
  belongs_to :template_page, :class_name => "Page::Template"

app / models / page / template.rb

class Page::Template < Library Item
  set_table_name "template_pages"
  has_many :page_template_contents,
    :class_name => "Page::Template::Content",
    :foreign_key => "template_page_id"

app / models / page / template / content.rb

class Page::Template::Content
  set_table_name "template_page_contents"
  belongs_to :template_page,
    :class_name => "Page::Template",
    :foreign_key => "template_page_id"



class Page::Template
...
    return self.page_template_contents.first

アソシエーションが選択しているクラス(上記の私のページ/テンプレート構造とは関係ありません):

class Content < LibraryItem
  set_table_name "contents"
# no associations to above classes

だから...これを引き起こしているのは何ですか?どうすれば修正できますか?

4

1 に答える 1

6

問題はset_table_nameによるものではなく、Railsが関連付け内のターゲットモデルクラスを見つける方法と、より深い名前空間にネストされたモデルとその名前を共有するトップレベルモデル( Content )があるという事実によるものです(ページ: :Template :: Content)。動作の奇妙な違いは、関連付けが調べられるときに実際にロードされるクラスの違いが原因である可能性があります(デフォルトでは、開発モードでは、Railsは最初に参照されたときにモデルクラスをオンデマンドでロードします)。

アソシエーションを定義する場合(アソシエーションのターゲットモデルのクラス名を明示的に指定するか、デフォルトを受け入れるかにかかわらず)、Railsはターゲットモデルクラスの名前をRuby定数に変換して、参照できるようにする必要があります。そのターゲットクラスに。これを行うには、関連付けを定義しているモデルクラスで保護されたクラスメソッドcompute_typeを呼び出します。

たとえば、あなたは

class Page::Template < LibraryItem
  set_table_name "template_pages"
  has_many :page_template_contents,
    :class_name => "Page::Template::Content",
    :foreign_key => "template_page_id"
 end

Railsコンソールで、このクラスでcompute_typeがどのように機能するかをテストできます。

$ ./script/console 
Loading development environment (Rails 2.3.14)
> Page::Template.send(:compute_type, "Page::Template::Content")
=> Page::Template::Content(id: integer, template_page_id: integer)

結果は、予想どおり、 Page :: Template::Contentクラスへの参照であることがわかります。

ただし、Railsコンソールを再起動した後、今回はモデルクラスContentを最初に(参照して)ロードしてから再試行すると、これが表示されます(Contentモデルのテーブルを作成する必要はありませんでしたが、重要な動作を変更しないでください):

$ ./script/console 
Loading development environment (Rails 2.3.14)
>> Content # Reference Content class so that it gets loaded
=> Content(Table doesn't exist)
>> Page::Template.send(:compute_type, "Page::Template::Content")
=> Content(Table doesn't exist)

ご覧のとおり、今回は代わりにコンテンツへの参照を取得します。

それで、あなたはこれについて何ができますか?

まず第一に、Railsで名前空間化されたモデルクラスを使用すること(確かに2.3では)は本当に苦痛であることを理解する必要があります。モデルクラスを階層に編成するのは良いことですが、それは確かに人生をより困難にします。上記のように、ある名前空間に別の名前空間のクラスと同じ名前のクラスがある場合、これはさらに厄介になります。

それでもこの状況で生きたいのであれば、私はいくつかの提案をすることができます。次のいずれかが役立つ場合があります。

  1. 開発モードでクラスキャッシュをオンにします。これにより、デフォルトのようにオンデマンドでロードするのではなく、すべてのモデルクラスがプリロードされます。上記のコードはより予測可能になりますが、開発がやや不快になる可能性があります(各リクエストでクラスがリロードされなくなるため)。

  2. 問題のある関連付けを持つクラスには、明示的に依存クラスが必要です。例えば:

    class Page::Template < LibraryItem        
      require 'page/template/content'
      set_table_name "template_pages"
      has_many :page_template_contents, ...
    end
    
  3. 関連付けられたクラスの参照がうまくいかない可能性があることがわかっているクラスのcompute_typeメソッドをオーバーライドして、名前空間化されたクラスが何であれ返されるようにします。クラスの階層をより詳細に知らなければ、これを行う方法について完全な提案をすることはできませんが、簡単で汚い例を次に示します。

    class Page::Template < LibraryItem
      set_table_name "template_pages"
      has_many :page_template_contents,
        :class_name => "Page::Template::Content",
        :foreign_key => "template_page_id"
    
      def self.compute_type(type_name)
        return Page::Template::Content if type_name == "Page::Template::Content"
        super
      end
    end
    
于 2012-08-08T04:17:16.467 に答える