【问题标题】:Why does ActiveRecord not use the primary_key and foreign_key columns when generating the SQL for a join in an association?为什么 ActiveRecord 在为关联中的联接生成 SQL 时不使用 primary_key 和 foreign_key 列?
【发布时间】:2010-02-04 00:10:52
【问题描述】:

我知道我可以使用 :finder_sql 手动定义用于获取关联记录的 SQL,但我想知道 ActiveRecord 是否使用关联上的 :primary_key 和 :foreign_key 选项来生成连接 SQL。似乎没有,但我只是在这里遗漏了什么吗?


更新:更明确地说,我的问题是:有没有办法将 id 列保留为主键,但在我指定时让 AR 使用不同的列进行连接我的关系?


这是示例模型定义和示例控制台会话...

型号

class Post < ActiveRecord::Base
  #Columns are: id:integer uuid:string name:string
  has_many :assignments, :foreign_key => 'post_uuid', :primary_key => 'uuid'
  has_many :categories, :through => :assignments
end

class Category < ActiveRecord::Base
  #Columns are id:integer uuid:string name:string
  has_many :assignments, :foreign_key => 'category_uuid', :primary_key => 'uuid'
end

class Assignment < ActiveRecord::Base
  #Columns are post_uuid:string category_uuid:string
  belongs_to :post, :foreign_key => 'uuid', :primary_key => 'post_uuid'
  belongs_to :category, :foreign_key => 'uuid', :primary_key => 'category_uuid'
end

控制台会话

#Make a Post
>> p = Post.create(:uuid => '123', :name => 'The Post')
  Post Create (0.9ms)   INSERT INTO "posts" ("name", "created_at", "uuid", "updated_at") VALUES('The Post', '2010-02-04 00:05:13', '123', '2010-02-04 00:05:13')
=> #<Post id: 2, uuid: "123", name: "The Post", created_at: "2010-02-04 00:05:13", updated_at: "2010-02-04 00:05:13">

#Make a Category
>> c = Category.create(:uuid => '456', :name => 'The Category')
  Category Create (0.5ms)   INSERT INTO "categories" ("name", "created_at", "uuid", "updated_at") VALUES('The Category', '2010-02-04 00:05:30', '456', '2010-02-04 00:05:30')
=> #<Category id: 2, name: "The Category", uuid: "456", created_at: "2010-02-04 00:05:30", updated_at: "2010-02-04 00:05:30">

#Make an Assignment, associating the post and the category
>> a = Assignment.create(:post_uuid => p.uuid, :category_uuid => c.uuid)
  Assignment Create (0.4ms)   INSERT INTO "assignments" ("created_at", "updated_at", "post_uuid", "category_uuid") VALUES('2010-02-04 00:05:50', '2010-02-04 00:05:50', '123', '456')
=> #<Assignment id: 2, post_uuid: "123", category_uuid: "456", created_at: "2010-02-04 00:05:50", updated_at: "2010-02-04 00:05:50">

#Try to fetch the category from the post object, which generates the wrong SQL
>> p.categories
  Category Load (0.0ms)   SQLite3::SQLException: no such column: assignments.uuid: SELECT "categories".* FROM "categories" INNER JOIN "assignments" ON "categories".id = "assignments".uuid WHERE (("assignments".post_uuid = 2)) 
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: assignments.uuid: SELECT "categories".* FROM "categories"  INNER JOIN "assignments" ON "categories".id = "assignments".uuid    WHERE (("assignments".post_uuid = 2)) 
[...Stack trace edited out...]

#Also odd: Try to fetch the post from the Association, no SQL generated, we just get nil
>> a.post
=> nil

【问题讨论】:

    标签: ruby-on-rails activerecord associations


    【解决方案1】:

    我能够通过更改您的模型定义来让您的代码正常工作。

    class Post < ActiveRecord::Base
      set_primary_key :uuid  # tell the model about the non standard primary key
      has_many :assignments, :foreign_key => 'post_uuid'
      has_many :categories, :through => :assignments
    end
    
    class Category < ActiveRecord::Base
     set_primary_key :uuid # tell the model about the non standard primary key
     has_many :assignments, :foreign_key => 'category_uuid'
    end
    
    class Assignment < ActiveRecord::Base
      belongs_to :post, :foreign_key => 'post_uuid' # notice the value for the foreign_key
      belongs_to :category, :foreign_key => 'category_uuid'  # notice the value for the foreign_key
    end
    

    除此之外,更改迁移文件以在 Category 和 Post 模型中指定非标准主键(uuid)。

    class CreatePosts < ActiveRecord::Migration
      def self.up
        create_table :posts, {:id => false} do |t|
          t.string :uuid, :primary => true, :limit => 20 
          t.string :name
          t.timestamps
        end
      end
    
      def self.down
        drop_table :posts
      end
    end
    
    class CreateCategories < ActiveRecord::Migration
      def self.up
        create_table :categories, {:id => false} do |t|
          t.string :uuid, :primary => true, :limit => 20
          t.string :name
    
          t.timestamps
        end
      end
    
      def self.down
        drop_table :categories
      end
    end
    

    在调用 new/create 方法时,您不能通过批量分配来设置 uuid 的值。您必须明确设置 primary_key 的值。

     # this will not work. uuid will be set to 0
     p = Post.create(:uuid => '123', :name => 'The Post') 
    
     # this will work
     p = Post.new(:name => 'The Post')
     p.uuid = '123'
     p.save 
    

    【讨论】:

    • 谢谢。我知道我可以明确声明一个不同的列作为我的模型中的主键。但我想知道的是,我能否在不更改模型主键的情况下 加入关联?
    • 好的。我现在明白你的问题了。生成的 SQL 语句在 categories.id 而不是 categories.uid 上创建为 JOIN。我想不出头顶上的解决方案。我的解决方案确实解决了您的一个问题,即 a.post/a.category 为零。在您的分配模型中,belongs_to 指令是错误的。
    • @KandaBoggu,您上面提到的 belongs_to 指令更改只有在我将 post_uuid/category_uuid 声明为 post/category 模型的主键时才有效。在我的情况下,我不想这样做。我只是询问是否有某种方法可以将 id 列保留为主键,但在我指定我的关系时让 AR 使用不同的列进行连接。
    • 文档指出,对于“has_many through”关联,foreign_key 和 class_name 参数被忽略。我希望 primary_key 参数得到尊重。当我将指令更改为 'has_many :categories, :through => :assignments, :primary_key => :uuid' 时出现堆栈溢出错误。奇怪..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多