Active Record 迁移与关联
按时间顺序管理数据库模式,不必用纯SQL来修改数据库模式。
更新db/schema.rb文件,以匹配最新的数据库结构。
class CreateProducts def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
创建数据表的时候默认创建主键为id
迁移文件储存在db/migrate文件夹中,一个迁移文件包含一个迁移类。
文件名采用yyyymmddhhmmss_create_products.rb形式,即UTC时间戳加上下划线在加上迁移的名称。
Add_foreign_key: 外键 以fk_rails_开头并且后面紧跟着10个字符的外键名。
提供一堆的辅助方法使用,如果辅助方法不够用,可以使用excute方法执行任意的SQL语句。
Product.connection.execute("UPDATE products SET price='free' where 1=1")
validate:return_amount_cannot_be_greater_than_loan_amount
def return_amount_cannot_be_greater_than_loan_amount
if return_amount > loan_amount
errors.add("return_amount can't be greater than loan_amount")
end
end
使用up和down方法
up方法用于描述对数据库模式所做的改变,down方法用于撤销up方法所做的改变。换句话说,如果调用up方法之后紧跟着调用down方法,数据库模式不会改变。
class ExampleMigration < ActiveRecord::Migration[5.0]
def up
create_table :distributors do |t|
t.string :zipcode
end
# 添加 CHECK 约束
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5);
SQL
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
SQL
drop_table :distributors
end
end
运行迁移:
db:migrate version=
回滚:
db:rollback
?
db:rollback STEP=3
安装数据库:
db:setup
重置数据库:
db:reset
运行指迁移:
db:migrate:up VERSION=
在不同环境中运行迁移:
db:migrate RAILS_ENV=test
Active Record关联
为什么要使用关联?因为关联能让常规操作变得更简单。
关联的类型:
rails支持六种关联:
-
belongs_to 两个模型间一对一关系
-
has_one 两个模型间一对一关系
-
has_many 两个模型之间一对多关系
-
has_many:through 借助第三个模型,创建两个模型之间的多对多关系
-
has_one:through 建立两个模型之间的一对一关系,一个模型通过第三个模型拥有另一模型的实例。
-
has_and_belongs_to_many 不借助第三个模型,直接建立两个模型之间的多对多关系,但是有一个join_table产生。
多态关联:
在同一个关联中,一个模型可以属于多个模型。
polymorphic:true
自联结:
在一个数据表中保存所有雇员的信息,但要建立经理和下属之间的关系。在迁移模式中,要添加一个引用字段,指向模型自身。
class Employee < ApplicationRecord
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
belongs_to :manager, class_name: "Employee"
end
小技巧和注意事项
-
控制缓存
-
避免命名冲突
-
更新模式
-
控制关联的作用域
-
双向关联
控制缓存:
关联添加的方法都会使用缓存,记录最近一次查询的结果,以备后用。缓存还会在方法之间共享。
如果想要重载缓存,在关联上调用reload即可。
避免命名冲突:
关联的名称并不能随意使用。因为创建关联时,会向模型添加同名方法,所以关联的名字不能和ActiveRecord::Base中的实例方法同名。如果同名,关联方法会覆盖ActiveRecord::Base中的实例方法,导致错误。
更新模式:
关联非常有用,但是没什么魔法。关联对应的数据库模式需要你自己编写。不同的关联类型,要做的事也不同。对belongs_to关联来说,要创建外键;对has_and_belongs_to_many关联来说,要创建相应的关联表。
控制关联的作用域:
默认情况下,关联只会查找当前模块作用域中的对象。要想让处在不同命名空间中的模型正常建立关联,声明关联时要指定完整的类名。
双向关联:
一般情况下,都要求能在关联的两端进行操作,即在两个模型中都要声明关联。
通过关联的名称,Active Record能探知这两个模型之间建立的是双向关联。这样,Active Record只会加载一个对象副本,从而确保应用运行效率更高效,并避免数据不一致。
Active Record 提供了 :inverse_of
选项,可以通过它明确声明双向关联:
class Author < ApplicationRecord
has_many :books, inverse_of: 'writer'
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
在has_many声明中指定:inverse_of选项后,Active Record便能识别双向关联。
关联详解
belongs_to关联详解:
belongs_to关联创建一个模型与另一个模型之间的一对一关系。用数据库术语来说,就是这个类中包含外键。如果外键在另一个类中,应该使用has_one关联。
声明belongs_to关联后,所在的类自动获得了五个和关联相关的方法:
association
association=(associate)
build_association(attributes={})
create_association(attributes={})
create_association!(attributes={})
例如:
class Book < ApplicationRecord
belongs_to :author
end
?
?
book模型的每个实例都获得了这些方法:
author
author=
build_author
create_authorcreate_association 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,对象的外键会自动设置,只要能通过所有数据验证,就会把关联对象存入数据库。
create_author! 与 create_association 方法作用相同,但是如果记录无效,会抛出 ActiveRecord::RecordInvalid 异常。
定制belongs_to关联行为。定制的方法很简单,声明关联时传入选项或者使用代码块即可。
如:
class Book < ApplicationRecord
belongs_to :author, dependent: :destroy,
counter_cache: true
end
belongs_to关联支持下列选项:
- :autosave
- :class_name
- :counter_cache
- :dependent
- :foreign_key
- :primary_key
- :inverse_of
- :polymorphic
- :touch
- :validate
- :optional
解析:
:autosave
把他的选项值设置为true,保存父对象,会自动保存所有的子对象,并把标记为析构的子对象销毁。
:class_name
如果另一个模型无法从关联的名称获取,可以使用:class_name选项指定模型名。
:counter_name
提高统计所属对象数量操作的效率。声明belongs_to关联的模型中加入计数缓存功能,这样声明关联后,rails会及时更新缓存,调用size方法时会返回缓存中的值。
:dependent
选项控制属主销户后怎么处理关联的对象。
-
destory;也销毁关联的对象
-
delete_all:直接从数据库中删除关联的对象(不执行回调)
-
nullify:把外键设为NULL(不执行回调)
-
restrict_with_exception:如果有关联记录,抛出异常
-
restrict_with_error:如果有关联的对象,为属主添加一个错误
:foreign_key
按照约定,用来存储外键的字段名是关联名后加_id。:foreign_key选项可以设置要使用的外键名。
:primary_key
按照约定,rails假定使用表中的id列保存主键。使用:primary_key选项可以指定使用其他列。
:inverse_of
:inverse_of选项指定belongs_to关联另一端的has_many和has_one关联名。不能和:polymorphic选项一起使用。
:polymorphic
选项为true时,表明这是个多态关联。
:touch
如果把:touch选项设为true,保存或销毁对象时,关联对象的updated_at或updated_on字段会自动设为当前时间。
:validate
选项设置为true,保存对象时,会同时验证关联的对象。该选项的默认值是false,保存对象时不验证关联的对象。
:optional
选项设置为true,不会验证关联的对象是否存在,默认值是false。
belongs_to的作用域
有时可能需要定制belongs_to关联使用的查询,定制的查询可在作用域代码块中指定。
如:
class Book < ApplicationRecord
belongs_to :author, -> { where active: true },
dependent: :destroy
end
在作用域代码块中可以使用任何一个标准的查询方法,如:
where
指定关联对象必须满足的条件。
includes
指定使用关联时要及早加载的间接关联。
readonly
通过关联的对象只能是只读的。
select
用于覆盖检索关联对象使用的SQL SELECT字句。默认情况下,rails检索所有字段。
如果在belongs_to关联中使用select方法,应该同时设置:foreign_key选项,确保返回的结果正确。
把对象赋值给belongs_to关联不会自动保存对象,也不会保存关联的对象。
has_one关联详解
:as
:as选项表明这是多态关联。
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
把对象赋值给 has_one
关联时,那个对象会自动保存(因为要更新外键)。而且所有被替换的对象也会自动保存,因为外键也变了。
如果由于无法通过验证而导致上述保存失败,赋值语句返回 false
,赋值操作会取消。
如果父对象(has_one
关联声明所在的模型)没保存(new_record?
方法返回 true
),那么子对象也不会保存。只有保存了父对象,才会保存子对象。
如果赋值给 has_one
关联时不想保存对象,使用 association.build
方法。
has_many关联详解
声明之后,所在的类自动获得了16个关联相关的方法:
-
collection
-
collection<<(object, …)
-
collection.delete(object, …)
-
collection.destroy(object, …)
-
collection=(objects)
-
collection_singular_ids
-
collection_singular_ids=(ids)
-
collection.clear
-
collection.empty?
-
collection.size
-
collection.find(…)
-
collection.where(…)
-
collection.exists?(…)
-
collection.build(attributes = {}, …)
-
collection.create(attributes = {})
-
collection.create!(attributes = {})
例如:
class Author < ApplicationRecord
has_many :books
end
?
?
books
books<<(object, ...)
books.delete(object, ...)
books.destroy(object, ...)
books=(objects)
book_ids
book_ids=(ids)
books.clear
books.empty?
books.size
books.find(...)
books.where(...)
books.exists?(...)
books.build(attributes = {}, ...)
books.create(attributes = {})
books.create!(attributes = {})has_many方法的选项
:source
指定has_many:through关联的源关联名称。只有无法从关联名中解出源关联的名称时才需要设置这个选项。
:source_type
:source_type选项指定通过多态关联处理 has_many :through 关联的源关联类型。
:through
:through选项指定一个联结模型,查询通过他执行。
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
endhas_many的作用域:
:extending
指定一个模块名,用于扩展关联代理。
:group
指定一个属性名,用在SQL GROUP BY子句中,分组查询结果。
:limit
限制通过关联获取的对象数量。
:offset
指定通过关联获取对象时的偏移量。如
-> { offset(11) }
会跳过前 11 个记录。:order
指定获取关联对象时使用的排序方式,用在SQL ORDER BY子句中。
:distinct
确保集合中没有重复的对象。与:through选项一起使用最有用。
什么时候保存对象
把对象赋值给
has_many
关联时,会自动保存对象(因为要更新外键)。如果一次赋值多个对象,所有对象都会自动保存。如果由于无法通过验证而导致保存失败,赋值语句返回
false
,赋值操作会取消。如果父对象(
has_many
关联声明所在的模型)没保存(new_record?
方法返回true
),那么子对象也不会保存。只有保存了父对象,才会保存子对象。如果赋值给
has_many
关联时不想保存对象,使用collection.build
方法
has_and_belongs_to_many关联详解
如果has_and_belongs_to_many关联使用的联结表中,除了两个外键之外还有其他列,通过关联获取的记录中会包含这些列,但是只读的,因为rails不知道如何保存对这些列的改动。
在
has_and_belongs_to_many
关联的联结表中使用其他字段的功能已经废弃。如果在多对多关联中需要使用这么复杂的数据表,应该用has_many :through
关联代替has_and_belongs_to_many
关联。has_and_belongs_to_many方法的选项
:join_table
如果默认按照字典顺序生成的联结表名不能满足要求,可以使用
:join_table
选项指定。
关联回调
关联回调和普通回调差不多,只不过由集合生命周期中的事件触发。关联回调有四种:
before_add
after_add
before_remove
after_remove
rails会把要添加或删除的对象传入回调。
同一个事件可以触发多个回调,多个回调使用数组指定。
关联扩展
rails基于关联代理对象自动创建的功能是死的,可以通过匿名模块、新的查找方法、创建对象的方法等进行扩展。
如:
class Author < ApplicationRecord
has_many :books do
def find_by_book_prefix(book_number)
find_by(category_id: book_number[0..2])
end
end
end如果扩展需要在多个关联中使用,可以将其写入具名扩展模块。
如:
module FindRecentExtension
def find_recent
where("created_at > ?", 5.days.ago)
end
end
class Author < ApplicationRecord
has_many :books, -> { extending FindRecentExtension }
end
class Supplier < ApplicationRecord
has_many :deliveries, -> { extending FindRecentExtension }
end在扩展中可以使用proxy_association方法的三个属性获取关联代理的内部信息:
proxy_association.owner:返回关联所属的对象。
proxy_association.reflection:返回描述关联的反射对象。
proxy_association.target:返回belongs_to或has_one关联的关联对象,或者 has_many 或 has_and_belongs_to_many` 关联的关联对象集合;
单表继承
表间的继承,只需要加一个type字段。
如:
$ rails generate model vehicle type:string color:string price:decimal{10.2}
?
?
$ rails generate model car --parent=Vehicle
?
?
class Car < Vehicle
end
?
?
Car.create(color: 'Red', price: 10000)
?
INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000)
?
Car.all
?
?
SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')