参考: 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_profilesinvoke active_recordcreate db/migrate/20200328011926_create_students_and_profiles.rb
修改迁移文件内容主体如下
create_table :students do |t|t.string :name, comment: '学生姓名't.timestampsendcreate_table :profiles do |t|t.references :studentt.string :library_card_number, comment: '借书证号码't.timestampsend
同时建立 models/student.rb 与 models/profile.rb 两个模型,如下
models/student.rbclass Student < ApplicationRecordendmodels/profile.rbclass Profile < ApplicationRecordend
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 < ApplicationRecordhas_one :profileendclass Profile < ApplicationRecordbelongs_to :studentend
打开一个rails console即可测试
$ rails cirb(main):001:0> Student.create(name: 'ysllyfe')(0.2ms) BEGINStudent 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.firstStudent 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) BEGINProfile 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 < ApplicationRecordhas_one :profile, dependent: :destroyend
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) BEGINProfile 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.timestampsendchange_table :students do |t|t.references :teamend
迁移后对应的表结构为
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 < ApplicationRecordhas_many :studentsendclass Student < ApplicationRecordhas_one :profile, dependent: :destroybelongs_to :teamend
对应has_many 也会有 .students= 方法,因为是1-n,.students=[] 后面添加的是数组,即覆盖students为后面的数组,如果是追加,则使用 .students << 方法,后面为追加单个学生
irb(main):015:0> Team.create(name: '班级1')(0.1ms) BEGINTeam 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.firstTeam 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.allStudent 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) BEGINStudent 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) BEGINStudent 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
