如果模型位于深层命名空间中,set_table_name不起作用 - rails 2.3.14中的错误?

时间:2012-08-01 03:22:16

标签: ruby-on-rails ruby activerecord

我使用的是Rails 2.3.14

更新3 我在更新2中找到的技巧仅适用于第一次访问关联...所以,我在应用程序控制器中粘贴了set_table_name行。这太奇怪了。我希望这个问题得到解决。 = \

UPDATE 2 如果我手动/通过/ hackily为环境文件底部的麻烦类设置表,我就会停止获取错误。

应用程序/配置/ 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关系使用的表是一个不同的表

我一直收到此错误,当我尝试通过Page::Template::Content关系访问属于Page::Template的{​​{1}}时。

has_many

查看错误日志,我想我需要开始使用一些打印语句来找出为什么rails试图在错误的表中找到关联的对象。

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

我刚决定打印gems_path/activerecord-2.3.14/lib/active_record/associations/association_collection.rb:63:in `find' 对象,因为一切似乎都在发生。我是这样做的:

@reflection

最后' @ 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

上面的代码块中有一些错误。

"-------"
#<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`"
>

我的模型是如何设置的:

应用程序/模型/ page.rb:

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

应用程序/模型/页/ template.rb

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

应用程序/模型/页/模板/ content.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"

协会选择的类(与我上面的页面/模板结构无关):

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

那么......造成这种情况的原因是什么?如何解决?

1 个答案:

答案 0 :(得分:6)

您的问题不是由于 set_table_name ,而是由于Rails如何在关联中找到目标模型类以及您拥有顶级模型(内容)与嵌套在更深的命名空间( Page :: 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控制台,但这次导致模型类内容首先加载(通过引用它),然后再试一次,我看到了这一点(我没有打扰创建内容模型的表格,但不会改变重要行为):

$ ./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