以 belongs_to association 举例:
在添加 belongs_to 关系的时候,需要在 table 中创建 foreign Keys。比如下面的例子:
class Order < ActiveRecord::Base
belongs_to :customer
end
建立上面的关系的时候,order table 是如下方式建立的
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.integer :customer_id
······
end
add_index :orders, :customer_id
end
end
或者是下面的代码
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.belongs_to :customer, index: true
······
end
end
end
通过观察 rails 中 belongs_to 方法的源码发现,这两个代码是等价的。
通过观察发现,belongs_to 的 association 中在 table 上创建 foreign keys,也等同于在 table 中建立一个 存放 id 的列,并在该 column 上建立了一个 index。所以这里的 foreign keys 并不是 database 中的 foreign keys。
其他的 association 的 foreign keys 也是同样的建立方法。
功能上替代的意思:对于 database 中的 foreign key 的作用,(我认为)只是用于 join table。
我不理解你所说的『database 中 foreign keys 』是指什么?跟 column 上建立一个 index 有什么不同,还是说只是字段命名不一样?
@hanluner database 中的 foreign keys 就是通过 add_foreign_key 这些在 database 中添加的 foreign key。我这里的提的问题我换一个叙述可能容易理解一点。 我们只考虑数据库建模的话。如果我们要表示 order 属于 customer 的关系话。一般的实现方式就是在 order 的 table 中去建立一个 customer_id 的 column,并且通过 sql 语句明确指出这是一个 foreign key。 但是这里 rails model 去实现这个关系的时候,在数据库中的操作只是建立了一个 customer_id 的 column,并为这个 column 建立了一个 index。并没有在数据库层面上明确出这是一个 foreign key(也就是没有通过 sql 语句去说明这个 column 是 foreign key)。
所以我就有了上面 3 个问题
#2 楼 @redemption 我理解你的意思了。Rails 其实并没有使用 MySQL 那种标准的外键,它只是在用它自己定义的这种外键。而 MySQL 中一般是不建议使用外键的。我不知道 Rails 是不是出于这种考虑才会仅仅是定义一个字段加上索引就行了。
@hanluner 那我是不是可以这么理解了。
可以这样试试:
config.active_record.schema_format = :sql
然后运行 rake db:structure:dump
应该可以看到 native 的DDL
belongs_to 的主要作用是给 Model 加一些方法。
class Order < ActiveRecord::Base
belongs_to :customer
end
比如这个,会给 Order 加上
Order#customer
Order#build_customer
Order#create_customer
等方法。
index 跟这个应该没有关系。
你可以做一下这个实验,然后把结果发上来:
@qinfanpeng 通过你这种方法,我发现很奇怪的地方。无论是否设置 foreign_key,产生的 sql 是一样的。并没有设置 foreign_key 的 sql 语句。而且我用的数据库是 sqlite,也支持 foreign_key
建立两个 model,Article 与 Comment
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.string :title
t.text :text
t.timestamps null: false
end
end
end
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :article, index: true # ,foreign_key: true
t.timestamps null: false
end
end
end
不设置 foreign_key
CREATE TABLE "schema_migrations" ("version" varchar NOT NULL);
CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
CREATE TABLE "articles" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "text" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "commenter" varchar, "body" text, "article_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE INDEX "index_comments_on_article_id" ON "comments" ("article_id");
INSERT INTO schema_migrations (version) VALUES ('20160105114731');
INSERT INTO schema_migrations (version) VALUES ('20160105120123');
设置 foreign_key 为 true
CREATE TABLE "schema_migrations" ("version" varchar NOT NULL);
CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
CREATE TABLE "articles" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "text" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "commenter" varchar, "body" text, "article_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE INDEX "index_comments_on_article_id" ON "comments" ("article_id");
INSERT INTO schema_migrations (version) VALUES ('20160105114731');
INSERT INTO schema_migrations (version) VALUES ('20160105121912');
你这个我做了实验,只要添加了 belongs_to,就会存在相应的方法。 而且从输出的 sql 来看,可以猜测 belongs_to 添加的方法就是去寻找相应的 column 中的值,并将该值作为去相关 table 中查找的目标。例如我的 Model 中存在正常的 Article 和 Comment 的关系,当我用 comment 去输出 article 时。
comment.article
其输出的 sql 是:
SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT 1 [["id", 1]]
而当不存在 Nowhere table 的时候,直接返回的是 nil,没有输出 sql 语句。所以我猜想 belongs_to 产生的方法,在具体实现的时候会去检查数据库里面是否有相应的 table。
#10 楼 @redemption 看起来 Active Record 并不会在 SQL 里面添加 foreign key constraint。 belongs_to 在实现时应该首先去查找当前 table 是否有对应的 column。 foreign_key: 这个 option 应该是用来指定 column name 的。
还有就是可以试试用 MySQL Workbench 这些的 GUI 工具,里面可以自动画出各个表之间的关联关系,看看能否识别出 Rails 建立的这种“外键”关系。
之前 rails 的 references 关联的确没有做数据库层面的外键约束,rails 偏向使用 validation (rails level) 处理类似的约束。
但 rails 4.2 增加这个功能,可以去看看 add_foreign_key 这个 api.
#9 楼 @redemption 简单点说就是没必要。 如果 Rails 自己能保证外键约束,为什么还要多此一举让数据库去管外键呢? 再加上数据库的外键可能各个系统设计上会不同,甚至还会遇到不支持外键的数据库,总不能为这些数据库分别写几套适配器吧。
#18 楼 @redemption 补充一点:
As we work toward the end of this book’s coverage of Active Record, you might have noticed that we haven’t really touched on a subject of particular importance to many programmers: foreign-key constraints in the database. That’s mainly because use of foreign-key constraints simply isn’t the Rails way to tackle the problem of relational integrity. To put it mildly, that opinion is controversial and some developers have written off Rails (and its authors) for expressing it. There really isn’t anything stopping you from adding foreign-key constraints to your database tables, although you’d do well to wait until after the bulk of development is done. The exception, of course, is those polymorphic associations, which are probably the most extreme manifestation of the Rails opinion against foreign-key constraints. Unless you’re armed for battle, you might not want to broach that particular subject with your DBA.
---- 摘自《The Rails 4 Way》