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

Nul*_*uli 2 ruby activerecord ruby-on-rails

我正在使用Rails 2.3.14

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

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

应用程序/配置/ environment.rb中:

Page::Template::Content.set_table_name "template_page_contents"
Page::Template::Section.set_table_name "template_page_sections"
Run Code Online (Sandbox Code Playgroud)

为什么我要这样做?这个特殊的用例是否已损坏?

更新:似乎set_table_name在我的Page :: Template :: Content类中最初调用时不起作用.

但如果我在使用该关联之前调用它,它就可以...

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
Run Code Online (Sandbox Code Playgroud)

原始问题:

TL; DR:我有一个我正在尝试访问的has_many关系,但是Rails认为has_many关系使用的表是一个不同的表

我一直在收到这个错误,当我尝试通过关系访问Page::Template::Content那个belongs_to a时.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
Run Code Online (Sandbox Code Playgroud)

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

gems_path/activerecord-2.3.14/lib/active_record/associations/association_collection.rb:63:in `find'
Run Code Online (Sandbox Code Playgroud)

我只是决定打印@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
Run Code Online (Sandbox Code Playgroud)

在错误之前打印的最后一个'@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`"
>
Run Code Online (Sandbox Code Playgroud)

上面的代码块有几个问题.

:klass should be Page::Template::Content
:name should be :contents  
:quoted_table_name should be `contents`
Run Code Online (Sandbox Code Playgroud)

我的模型是如何设置的:

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

class Page < LibrayItem
  belongs_to :template_page, :class_name => "Page::Template"
Run Code Online (Sandbox Code Playgroud)

应用程序/模型/页/ 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"
Run Code Online (Sandbox Code Playgroud)

应用程序/模型/网页/模板/ 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
Run Code Online (Sandbox Code Playgroud)

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

class Content < LibraryItem
  set_table_name "contents"
# no associations to above classes
Run Code Online (Sandbox Code Playgroud)

那么......是什么造成了这种情况,我该如何解决?

Ric*_*ond 6

您的问题不是由于set_table_name,而是由于Rails如何在关联中找到目标模型类,以及您拥有与嵌套在更深的命名空间中的模型共享其名称的顶级模型(内容)这一事实(页面: :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
Run Code Online (Sandbox Code Playgroud)

您可以在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)
Run Code Online (Sandbox Code Playgroud)

我看到结果是,正如我所料,对类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)
Run Code Online (Sandbox Code Playgroud)

如您所见,这次我得到了对Content的引用.

那么你能做些什么呢?

好吧,首先,您应该意识到在Rails中使用命名空间模型类(当然在2.3中)是一个真正的痛苦.在层次结构中组织模型类很好,但它确实会让生活变得更加困难.正如您在上面所看到的,如果一个名称空间中的类与另一个名称空间中的类具有相同的名称,则会变得更加毛茸茸.

如果您仍想忍受这种情况,我可以提出一些建议.以下之一可能会有所帮助:

  1. 在开发模式下启用类缓存.这将导致所有模型类被预加载,而不是像默认情况那样按需加载它们.它将使您的代码更容易预测,但可能会使开发有点不愉快(因为每个请求将不再重新加载类).

  2. 明确要求具有有问题关联的类中的依赖类.例如:

    class Page::Template < LibraryItem        
      require 'page/template/content'
      set_table_name "template_pages"
      has_many :page_template_contents, ...
    end
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)