参考: https://ruby-china.github.io/rails-guides/active_record_basics.html
重点在于2.1 2.2两部分
2.1 命名约定
默认情况下,Active Record 使用一些命名约定,查找模型和数据库表之间的映射关系。Rails 把模型的类名转换成复数,然后查找对应的数据表。例如,模型类名为 Book
,数据表就是 books
。Rails 提供的单复数转换功能很强大,常见和不常见的转换方式都能处理。如果类名由多个单词组成,应该按照 Ruby 的约定,使用驼峰式命名法,这时对应的数据库表将使用下划线分隔各单词。因此:
- 数据库表名:复数,下划线分隔单词(例如
book_clubs
) - 模型类名:单数,每个单词的首字母大写(例如
BookClub
) | 模型/类 | 表/模式 | | :—- | :—- | |Article
|articles
| |LineItem
|line_items
| |Deer
|deers
| |Mouse
|mice
| |Person
|people
|
2.2 模式约定
根据字段的作用不同,Active Record 对数据库表中的字段命名也做了相应的约定:
- 外键:使用
singularized_table_name_id
形式命名,例如item_id
,order_id
。创建模型关联后,Active Record 会查找这个字段; - 主键:默认情况下,Active Record 使用整数字段
id
作为表的主键。使用 Active Record 迁移创建数据库表时,会自动创建这个字段;
还有一些可选的字段,能为 Active Record 实例添加更多的功能:
created_at
:创建记录时,自动设为当前的日期和时间;updated_at
:更新记录时,自动设为当前的日期和时间;lock_version
:在模型中添加乐观锁;type
:让模型使用单表继承;(association_name)_type
:存储多态关联的类型;(table_name)_count
:缓存所关联对象的数量。比如说,一个Article
有多个Comment
,那么comments_count
列存储各篇文章现有的评论数量;
ActiveRecord是怎么样表述1-n,1-1,n-n这种形式的
下面用这三个模型来说明
(1-1) 一个学生 student 有一份档案 profile
(1-n) 学校里面有很多班级 teams,每个班级有很多学生 students
(n-n) 学校里有很多老师 teachers,每个老师有很多头衔 titles
这里只做简单模型表述,所以我们用rails g migration 建立迁移文件,及手动建里 model 的方法实现
为了好表述,我们把 1-n左边的这个1对应的表称为左表,n对应的表为右表
1-1关系
> step.1 建模
创建students与profiles的模迁移文件
$ rails g migration create_students_and_profiles
invoke active_record
create db/migrate/20200328011926_create_students_and_profiles.rb
修改迁移文件内容主体如下
create_table :students do |t|
t.string :name, comment: '学生姓名'
t.timestamps
end
create_table :profiles do |t|
t.references :student
t.string :library_card_number, comment: '借书证号码'
t.timestamps
end
同时建立 models/student.rb 与 models/profile.rb 两个模型,如下
models/student.rb
class Student < ApplicationRecord
end
models/profile.rb
class Profile < ApplicationRecord
end
rails db:migrate
后可以看到对应的表结构为
Table "public.students"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+--------------------------------------
id | bigint | | not null | nextval('students_id_seq'::regclass)
name | character varying | | |
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
Indexes:
"students_pkey" PRIMARY KEY, btree (id)
Table "public.profiles"
Column | Type | Collation | Nullable | Default
---------------------+-----------------------------+-----------+----------+--------------------------------------
id | bigint | | not null | nextval('profiles_id_seq'::regclass)
student_id | bigint | | |
library_card_number | character varying | | |
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
Indexes:
"profiles_pkey" PRIMARY KEY, btree (id)
"index_profiles_on_student_id" btree (student_id)
> step.2 在模型中声明
1-1的关系,只需要在左表中声明has_one 右表中声明belongs_to即可
class Student < ApplicationRecord
has_one :profile
end
class Profile < ApplicationRecord
belongs_to :student
end
打开一个rails console即可测试
$ rails c
irb(main):001:0> Student.create(name: 'ysllyfe')
(0.2ms) BEGIN
Student Create (1.8ms) INSERT INTO "students" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "ysllyfe"], ["created_at", "2020-03-28 01:33:21.209412"], ["updated_at", "2020-03-28 01:33:21.209412"]]
(0.6ms) COMMIT
=> #<Student id: 1, name: "ysllyfe", created_at: "2020-03-28 01:33:21", updated_at: "2020-03-28 01:33:21">
irb(main):002:0> student = Student.first
Student Load (0.5ms) SELECT "students".* FROM "students" ORDER BY "students"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Student id: 1, name: "ysllyfe", created_at: "2020-03-28 01:33:21", updated_at: "2020-03-28 01:33:21">
irb(main):003:0> student.profile = Profile.new(library_card_number: 'xxxxxx')
Profile Load (0.7ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."student_id" = $1 LIMIT $2 [["student_id", 1], ["LIMIT", 1]]
(0.1ms) BEGIN
Profile Create (0.8ms) INSERT INTO "profiles" ("student_id", "library_card_number", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["student_id", 1], ["library_card_number", "xxxxxx"], ["created_at", "2020-03-28 01:34:14.112116"], ["updated_at", "2020-03-28 01:34:14.112116"]]
(5.5ms) COMMIT
=> #<Profile id: 1, student_id: 1, library_card_number: "xxxxxx", created_at: "2020-03-28 01:34:14", updated_at: "2020-03-28 01:34:14">
简单的创建student后,只需使用 .profile= 方法,就可以在某个student下建立 1-1 关联的档案
如果我们需要重建这个关联关系,只需要重新调用 .profile= 方法即可
rails5后 belongs_to 声明,强制要求对应的字段有关联值,而默认 .profile= 方法在存在原profile关系时,会把对应的值改为空(默认dependent为nullify),和rails5冲突,我们只需要改为delete或是destroy就可以了
class Student < ApplicationRecord
has_one :profile, dependent: :destroy
end
irb(main):010:0> student.profile = Profile.new(library_card_number: 'ccc')
Profile Load (0.5ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."student_id" = $1 LIMIT $2 [["student_id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
Profile Destroy (0.5ms) DELETE FROM "profiles" WHERE "profiles"."id" = $1 [["id", 1]]
Profile Create (0.4ms) INSERT INTO "profiles" ("student_id", "library_card_number", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["student_id", 1], ["library_card_number", "ccc"], ["created_at", "2020-03-28 01:37:21.835320"], ["updated_at", "2020-03-28 01:37:21.835320"]]
(0.9ms) COMMIT
=> #<Profile id: 2, student_id: 1, library_card_number: "ccc", created_at: "2020-03-28 01:37:21", updated_at: "2020-03-28 01:37:21">
1-n 关系
> step.1 建模
只需要建新表 teams 及修改 students
rails g migration create_teams
在对应迁移文件里面,修改主体
create_table :teams do |t|
t.string :name, comment: '班级名称'
t.timestamps
end
change_table :students do |t|
t.references :team
end
迁移后对应的表结构为
Table "public.students"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+--------------------------------------
id | bigint | | not null | nextval('students_id_seq'::regclass)
name | character varying | | |
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
team_id | bigint | | |
Indexes:
"students_pkey" PRIMARY KEY, btree (id)
"index_students_on_team_id" btree (team_id)
Table "public.teams"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+-----------------------------------
id | bigint | | not null | nextval('teams_id_seq'::regclass)
name | character varying | | |
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
Indexes:
"teams_pkey" PRIMARY KEY, btree (id)
>step.2 在模型中声明
class Team < ApplicationRecord
has_many :students
end
class Student < ApplicationRecord
has_one :profile, dependent: :destroy
belongs_to :team
end
对应has_many 也会有 .students= 方法,因为是1-n,.students=[] 后面添加的是数组,即覆盖students为后面的数组,如果是追加,则使用 .students << 方法,后面为追加单个学生
irb(main):015:0> Team.create(name: '班级1')
(0.1ms) BEGIN
Team Create (1.3ms) INSERT INTO "teams" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "班级1"], ["created_at", "2020-03-28 01:51:45.409300"], ["updated_at", "2020-03-28 01:51:45.409300"]]
(0.3ms) COMMIT
=> #<Team id: 1, name: "班级1", created_at: "2020-03-28 01:51:45", updated_at: "2020-03-28 01:51:45">
irb(main):016:0> team = Team.first
Team Load (0.4ms) SELECT "teams".* FROM "teams" ORDER BY "teams"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Team id: 1, name: "班级1", created_at: "2020-03-28 01:51:45", updated_at: "2020-03-28 01:51:45">
irb(main):017:0> team.students = Student.all
Student Load (0.5ms) SELECT "students".* FROM "students"
Student Load (0.3ms) SELECT "students".* FROM "students" WHERE "students"."team_id" = $1 [["team_id", 1]]
(0.2ms) BEGIN
Student Update (6.4ms) UPDATE "students" SET "team_id" = $1, "updated_at" = $2 WHERE "students"."id" = $3 [["team_id", 1], ["updated_at", "2020-03-28 01:52:16.148332"], ["id", 1]]
(10.6ms) COMMIT
=> #<ActiveRecord::Relation [#<Student id: 1, name: "ysllyfe", created_at: "2020-03-28 01:33:21", updated_at: "2020-03-28 01:52:16", team_id: 1>]>
irb(main):018:0> team.students << Student.new(name: 'test_student')
(0.4ms) BEGIN
Student Create (1.1ms) INSERT INTO "students" ("name", "created_at", "updated_at", "team_id") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "test_student"], ["created_at", "2020-03-28 01:53:07.508108"], ["updated_at", "2020-03-28 01:53:07.508108"], ["team_id", 1]]
(1.0ms) COMMIT
=> #<ActiveRecord::Associations::CollectionProxy [#<Student id: 1, name: "ysllyfe", created_at: "2020-03-28 01:33:21", updated_at: "2020-03-28 01:52:16", team_id: 1>, #<Student id: 2, name: "test_student", created_at: "2020-03-28 01:53:07", updated_at: "2020-03-28 01:53:07", team_id: 1>]>
n-n 关系
N-N关系在active_records中很有意思,但是一般很少用,只做简述
N-N的左表和右表都不是强关联,没有像1-1那种,右边依赖于左表存在,如果左表数据删除,右表数据无根的情况
如teachers 与 titles
声明n-n时,只需要建立对应的中间表 teachers_titles (注意顺序),这个表不需要主键(id),表字段为 teachers_id titles_id
然后在对应的模型 teacher 下声明 has_and_belongs_to_many :titles
在对应的模型 title 下声明 has_and_belongs_to_many :teachers
不需要在models下建立 teachers_title.rb