这会产生一个album_id列,当建立belongs_to关联时,需要用到。
references算时一种结构,会产生integer.
Database Configuration
config/database.yml中可以看到默认的数据库配置。
使用sqlite3+路径, 进入控制台。
在rails console建立一条记录,可以通过底层数据库sqlite3查看:
$ sqlite3 db/development.sqlite3
> select * from countries;
表示成功。
一次创建多条数据需要使用一个数组的hashes。
Country.create([{name: "China"}, {name:"France"}])
Country.all返回得到一个数组的Country。它是一个ActiveRecord::Relation scope object
因此,Country.all可以使用each方法。
rails db:drop 删除数据库。rails db:create, rails db:migrate三剑客。
rails db:seed建立种子文件。
find , where
使用查询方法的使用,如果是多条,必然返回一个Array。
Country.find(1)返回的是一个单独的object,他的类是Country
Country.find([1])返回的则是一个集合[],他的类是 Array
这里用到了between..and..
这里使用了and连接了2个between..and..
这里使用了in, 用于精确的指定查询条件
这里order by 和 ASC, limit
所有查询语法,条件都可以使用?, (?, ?)代替,在语法最后使用Array,按顺序列出具体条件。
not ,or
这里使用了!=
这里使用了数据库的or,另外rails语法,or()方法连接的应该是同一个数据库
⚠️和 Album.where (release_year:1960..1966, id: 1..5 ).count 的区别
自定义SQL,使用find_by_sql()方法。
模糊查找like, >= , <=
使用了AND
使用了local variable在语法内部用了#{}插入符号
链式查询:limit
这里用到limit方法限制查询记录数量。
Ablum.where(release_year: 1965..1968).order(:release_year).limit(3)
=>SELECT "albums".* FROM "albums" WHERE "albums"."release_year" BETWEEN ? AND ? ORDER BY "albums"."release_year" ASC LIMIT ? [["release_year", 1965], ["release_year", 1968], ["LIMIT", 3]]
自动的优化查询:
因为order对sum来说是无关紧要的查询条件。所以SQL没有使用order查询。
reverse_order和 order
reverse_order反转查询顺序。
Album.where(release_year: 1960..1969).order(:name).reverse_order
Pluck()方法
从检索的记录中挑出需要的字段。
这里返回一个Array,包含了所以符合查询条件的记录的名字。
可以挑出多个字段,返回嵌套数组。
⚠️Album.pluck(:id)等同于Album.ids
Select()方法 类似pluck()
返回的是一个ActiveRecord::Relation。
Calculations
average()方法。根据检索的条件,计算平均值,返回BigDecimal.
maximum(), minimum(), sum(),用法一样。
SQL EXPLAIN
大型的数据库,EXPLAIN是一个很好的debugging 方法。可以显示详细的信息。
批处理:batches
find_each,
find_in_batches: yields batches to 块,作为一个模型的数组。
见其他博客,或guide。
lock_version 乐观锁optimistic
备注:之前看rails的guide以及API, 因为是英文同时没有step to step的案例,一直没有弄明白。因此,如果某个特点在一篇教材上不懂,标记下来,可以通过查找不同的资料,如教材。一个step to step的案例是最方便理解知识点的地方了。
通过乐观锁可以锁定一个数据库的数据。
如果有多个用户同时修改同一条记录,就可能会产生冲突。 加上?,一旦冲突就会报告❌,同时更新会被忽视掉。
激活乐观?,一个model需要一个属性,名字是lock_version,类型是integer.
rails g model Car name "price:decimal{8,2}" lock_version:integer
生产:
如果建立了2个实例变量a, b都指向同一条记录,如果在a上进行了属性修改并save。那么b就不能再用于修改记录了,也不能删除记录, b会过期,会报告❌,然后忽略掉b的操作。
ActiveRecord::StaleObjectError: Attempted to update a stale object: Car.
之后,你需要处理这个冲突,进行营救并回滚,合并或者其他方法来解决这个冲突。
如果要关掉?:
Pessimistic lock 悲观?
和乐观锁锁定整个数据库不同,悲观锁用于锁定单个的记录。
为了防止死锁,一般在事物中使用,transaction do..end
joins()方法,连接两个有关联的表。1对n
=》一个数组集合。
反过来也可以,⚠️需要主要的是这里category是单数,因为是一对多的关系。
joins(),也叫内连接是返回所有关联的记录。如果一个目录和一个产品关联,那么就是一条记录。
left_joins()
Category.left_joins(:products)
左连接。返回所有内连接的记录,也返回没有建立连接的category。
includes()方法,已经理解。
Category.includes(:products)会使用2条查询语法,一个是查询所有的categories,另一个是根据category_id,查询products。
返回的是Category中的对象集合。同时,所有的products信息被储存到缓存中。
这样通过对象集合中的对象,比如对象fruit.products,就可以从缓存中拿出相关的记录。
这样就无需再调用数据库查询了,更快一点。但是会占用更多的缓存资源。
区别:joins和includes,left_joins
joins(),也叫内连接是返回所有关联的记录。如果一个目录和一个产品关联,那么就是一条记录。
SELECT COUNT(*) FROM "categories" INNER JOIN "products" ON "products"."category_id" = "categories"."id"
因为有一个Category的记录,name是"bad food",没有关联任何products,所以左连接会返回12条记录。
> Category.left_joins(:products).size
includes,返回的数组集合中包含4个对象记录,每个记录都可能关联着products的记录。 如果你知道之后会用到所有的product的数据。使用includes()就得到了。之后需要什么,直接会从缓存里取。无需再进行数据库的互动了。
destroy方法
ActiveRecord::Persistence
destroy(),在数据库中删除数据,并冻结缓存中的这个实例。 只能读,不能改。
可以使用frozen? 方法来判断是否是已经删除的 。
可以新建一个实例对象,把删除的对象的属性的值赋予新的对象,然后再保存。 变相回复数据。
如果Book和Author2个表建立了1对n关联,设置has_many :author, dependent: :destroy
book.destroy, 会从数据库删除关联的authors
但book.delete,不会从数据库删除关联的authors
book.authors.delete(2)可以从数据库删除对应的author
在has_many :author不加上dependent: :destroy的情况下:
book.authors.delete(2)只会把author的外键book_id:nil掉。book.authors.destroy(2)则可以从数据库删除author。
book.destroy和book.delete,都会从数据库删除book, 不会删除相关的authors ,也不会改变它的外键。
belongs_to中最重要的选项touch:true
用于俄罗斯套娃缓存cache。它会同步更新关联的记录的updated_at属性到当前时间。
has_many(name, scope = nil, **options, &extension)
scope作为第二个参数。可以取回记录,或者当你存取关联连接时客制化generated query
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
extension参数是一个block, 可以增加新的查询finders, creators,其他方法作为关联的一部分。
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
多对多关联
多态关联: Polymorphic Associations
一种更方便的关联结构。一种使用技巧。tricky.
用途:car和bike是两个不同类型,它们都有颜色标签这个属性。 此时可以增加2个表CarTag, BikeTag。但更方便的是只使用Tag,让Tag和Car, Bike都建立关联。
方法: 给Tag增加2个field,一个string field用于记录关联表的名字/类名, 一个integer field用于记录关联表的记录的id号。 并给这两个字段附上索引。
$ rails generate model Car name
$rails generate model Bike name
$ rails generate model Tag name taggable:references{polymorphic}
建立tags表格:
会生成taggable_index, taggable_index两个字段:
同时,这2个field会添加上index索引。
t.string "name"
t.string "taggable_type"
t.integer "taggable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["taggable_type", "taggable_id"], name: "index_tags_on_taggable_type_and_taggable_id"
end
建立关联:class Tag < ApplicationRecord
belongs_to :taggable, polymorphic: true
end
解释:
不同于传统的关联,这里taggable指向的是Tag内部的taggable_type字段。
因为这个字段储存了关联表的名字/类名。
本案例相当于belongs_to :car和belongs_to :bike
class Bike < ApplicationRecord
has_many :tags, as: :taggable
end
解释: as用来指定一个多态接口interfaceclass Car < ApplicationRecord
has_many :tags, as: :taggable
end
> beetle = Car.create(name: 'Beetle')
> beetle.tags.create(name: "blue")
INSERT INTO "tags" ("name", "taggable_type", "taggable_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "blue"], ["taggable_type", "Car"], ["taggable_id", 1],...
> beetle.tags.create(name: 'Automatic')INSERT INTO "tags" ("name", "taggable_type", "taggable_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "Automatic"], ["taggable_type", "Car"], ["taggable_id", 1],...
解释:
给beetle对象增加了2个tags,可以看到taggable_type和taggable_id能准确定位到beetle对象。
Scope
在model层写scope,就是便捷的查询语法, 本质是类方法。
⚠️在rails console, 增改一个类的类方法后,需要从新进入控制台才能生效。
scope可以带参数;使用有名字的scope可以便捷的建立数据。
scope :available, -> {where(in_stock: true)}
end
觉得比较诡异的使用方法,新建时会自动带上available的属性in_stock:true
model可以带一个defult_scope,设定了default_scope, 创建新的记录的时候会自动带上default_scope的属性。
unscoped方法会移除默认的scope.
Validates
validates有大量的选项设置,具体可以看API.
比如length选项,可以设置is, in, maximun, minimum, 提示信息too_long, too_short等
增加了验证后,Product.builde, 可以使用valid?方法测试是否通过验证。
使用errors生成❌对象
使用full_messages得到一个数组 => ["Name can't be blank", "Price can't be blank"]
可以使用save(validate: false)强制保存,⚠️除非真的有好的理由,否则别这么使用。
Numericality
validates :weight, numericality: {equal_to: 100}
⚠️存储的数据,某个字段的值如果不符合该字段的type, 同时又没有限制,该值不会存入数据库。
如果字段是integer/string 型,可以使用numericality: true来?。
numericality有很多?选项。如:
only_integer:true, greater_than: 100,甚至可以限制偶数even:true,奇数odd: true
Uniqueness值需唯一
也有大量选项,如大小写唯一等:
:scope
- One or more columns by which to limit the scope of the uniqueness constraint.
在account_id字段,不能有相同的user_name。
比如account_id: 001, 它有对应的user_name: "xiaoming", 那么account_id: 002就不能有xiaoming了。 也就是说xiaoming只在account_id的列集合数据中,只出现一次。
也可以多重范围scope的uniqueness:
这是一个教师表,唯一的设定是:每个学期semester_id的每个班class_id的teacher_id是唯一的,一个学期内,一个教师在这个班当班主任,别的班就不能当了。
inclusion和exclusion
包含:
validates_inclusion_of :gender, in: %w( m f )
选项是in, message。
validates :gender, inclusion: {in:["m", "f"], message: "this one is not allowed"}
exclusion是不包含。其他没区别。
format是用来配合正则表达式的。
通用的验证选项
on
限定使用的events create, update等,也可以限定自定义方法
allow_nil: true #值可以是nil
allow_blank: true #值可以是nil,或者空字符串""
if and unless
:if
- 指定一个方法,Proc对象,或字符串来调用,当返回true时,则该验证生效
(e.g.if: :allow_validation
, orif: Proc.new { |user| user.signup_step > 2 }
).
例子:
validates :name, presence: true, if: :today_is_moday?
def today_is_monday?
Date.today.monday?
end
自定义验证方法
Defining Validations with Your Own Methods
validate()方法。自定义验证条件。需要配合errors.add(attr,message)
例子:
这个方法不需要主动调用,在创建新的记录时会自动调用。
validates_each(*attr_names, &block) 可以接受一个block,可在块中自定义限制条件。
更高级的方法(很少用到):validates_with(*args, &block), 参数是一个个类。
这个方法是include 这个类,然后使用这个类的方法进行验证。
解释:
注意Person是继承自ApplicationRecord, 所以已经include了ActiveModel::Validator模块。