参考: 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_idorder_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的模迁移文件

  1. $ rails g migration create_students_and_profiles
  2. invoke active_record
  3. create db/migrate/20200328011926_create_students_and_profiles.rb

修改迁移文件内容主体如下

  1. create_table :students do |t|
  2. t.string :name, comment: '学生姓名'
  3. t.timestamps
  4. end
  5. create_table :profiles do |t|
  6. t.references :student
  7. t.string :library_card_number, comment: '借书证号码'
  8. t.timestamps
  9. end

同时建立 models/student.rb 与 models/profile.rb 两个模型,如下

  1. models/student.rb
  2. class Student < ApplicationRecord
  3. end
  4. models/profile.rb
  5. class Profile < ApplicationRecord
  6. end

rails db:migrate 后可以看到对应的表结构为

  1. Table "public.students"
  2. Column | Type | Collation | Nullable | Default
  3. ------------+-----------------------------+-----------+----------+--------------------------------------
  4. id | bigint | | not null | nextval('students_id_seq'::regclass)
  5. name | character varying | | |
  6. created_at | timestamp without time zone | | not null |
  7. updated_at | timestamp without time zone | | not null |
  8. Indexes:
  9. "students_pkey" PRIMARY KEY, btree (id)
  10. Table "public.profiles"
  11. Column | Type | Collation | Nullable | Default
  12. ---------------------+-----------------------------+-----------+----------+--------------------------------------
  13. id | bigint | | not null | nextval('profiles_id_seq'::regclass)
  14. student_id | bigint | | |
  15. library_card_number | character varying | | |
  16. created_at | timestamp without time zone | | not null |
  17. updated_at | timestamp without time zone | | not null |
  18. Indexes:
  19. "profiles_pkey" PRIMARY KEY, btree (id)
  20. "index_profiles_on_student_id" btree (student_id)

> step.2 在模型中声明

1-1的关系,只需要在左表中声明has_one 右表中声明belongs_to即可

  1. class Student < ApplicationRecord
  2. has_one :profile
  3. end
  4. class Profile < ApplicationRecord
  5. belongs_to :student
  6. end

打开一个rails console即可测试

  1. $ rails c
  2. irb(main):001:0> Student.create(name: 'ysllyfe')
  3. (0.2ms) BEGIN
  4. 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"]]
  5. (0.6ms) COMMIT
  6. => #<Student id: 1, name: "ysllyfe", created_at: "2020-03-28 01:33:21", updated_at: "2020-03-28 01:33:21">
  7. irb(main):002:0> student = Student.first
  8. Student Load (0.5ms) SELECT "students".* FROM "students" ORDER BY "students"."id" ASC LIMIT $1 [["LIMIT", 1]]
  9. => #<Student id: 1, name: "ysllyfe", created_at: "2020-03-28 01:33:21", updated_at: "2020-03-28 01:33:21">
  10. irb(main):003:0> student.profile = Profile.new(library_card_number: 'xxxxxx')
  11. Profile Load (0.7ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."student_id" = $1 LIMIT $2 [["student_id", 1], ["LIMIT", 1]]
  12. (0.1ms) BEGIN
  13. 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"]]
  14. (5.5ms) COMMIT
  15. => #<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就可以了

  1. class Student < ApplicationRecord
  2. has_one :profile, dependent: :destroy
  3. end
  1. irb(main):010:0> student.profile = Profile.new(library_card_number: 'ccc')
  2. Profile Load (0.5ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."student_id" = $1 LIMIT $2 [["student_id", 1], ["LIMIT", 1]]
  3. (0.2ms) BEGIN
  4. Profile Destroy (0.5ms) DELETE FROM "profiles" WHERE "profiles"."id" = $1 [["id", 1]]
  5. 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"]]
  6. (0.9ms) COMMIT
  7. => #<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

在对应迁移文件里面,修改主体

  1. create_table :teams do |t|
  2. t.string :name, comment: '班级名称'
  3. t.timestamps
  4. end
  5. change_table :students do |t|
  6. t.references :team
  7. end

迁移后对应的表结构为

  1. Table "public.students"
  2. Column | Type | Collation | Nullable | Default
  3. ------------+-----------------------------+-----------+----------+--------------------------------------
  4. id | bigint | | not null | nextval('students_id_seq'::regclass)
  5. name | character varying | | |
  6. created_at | timestamp without time zone | | not null |
  7. updated_at | timestamp without time zone | | not null |
  8. team_id | bigint | | |
  9. Indexes:
  10. "students_pkey" PRIMARY KEY, btree (id)
  11. "index_students_on_team_id" btree (team_id)
  12. Table "public.teams"
  13. Column | Type | Collation | Nullable | Default
  14. ------------+-----------------------------+-----------+----------+-----------------------------------
  15. id | bigint | | not null | nextval('teams_id_seq'::regclass)
  16. name | character varying | | |
  17. created_at | timestamp without time zone | | not null |
  18. updated_at | timestamp without time zone | | not null |
  19. Indexes:
  20. "teams_pkey" PRIMARY KEY, btree (id)

>step.2 在模型中声明

  1. class Team < ApplicationRecord
  2. has_many :students
  3. end
  4. class Student < ApplicationRecord
  5. has_one :profile, dependent: :destroy
  6. belongs_to :team
  7. end

对应has_many 也会有 .students= 方法,因为是1-n,.students=[] 后面添加的是数组,即覆盖students为后面的数组,如果是追加,则使用 .students << 方法,后面为追加单个学生

  1. irb(main):015:0> Team.create(name: '班级1')
  2. (0.1ms) BEGIN
  3. 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"]]
  4. (0.3ms) COMMIT
  5. => #<Team id: 1, name: "班级1", created_at: "2020-03-28 01:51:45", updated_at: "2020-03-28 01:51:45">
  6. irb(main):016:0> team = Team.first
  7. Team Load (0.4ms) SELECT "teams".* FROM "teams" ORDER BY "teams"."id" ASC LIMIT $1 [["LIMIT", 1]]
  8. => #<Team id: 1, name: "班级1", created_at: "2020-03-28 01:51:45", updated_at: "2020-03-28 01:51:45">
  9. irb(main):017:0> team.students = Student.all
  10. Student Load (0.5ms) SELECT "students".* FROM "students"
  11. Student Load (0.3ms) SELECT "students".* FROM "students" WHERE "students"."team_id" = $1 [["team_id", 1]]
  12. (0.2ms) BEGIN
  13. 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]]
  14. (10.6ms) COMMIT
  15. => #<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>]>
  1. irb(main):018:0> team.students << Student.new(name: 'test_student')
  2. (0.4ms) BEGIN
  3. 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]]
  4. (1.0ms) COMMIT
  5. => #<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