一、心得体会
1、我完成了什么?
- 今天主要看了Rails guides 5的Active Record的3、4、5、6、7章。
2、我收获了什么?
- db/schema.rb在设计上有所取舍,不能表达数据库的特定项目,如触发器、存储过程或检查约束。
- accepts_nested_attributes_for是什么
- 数据验证的辅助方法:format、inclusion、length、numericality、presence、uniqueness、validates_with、validates_each
- 可以自定义验证的方法,比如要验证三个参数
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.erros[attribute] << (options[:message]|| "is not an email")
end
end
end
- 数据库查询的一些方法:find、take
- 条件查询:where
- 纯字符串条件:where("orders_count = '2'")
- 数组条件:where("orders_count = ?", params[:orders])
- 其他查询
3、今天的状态如何?
- 今天忽然接到剑爸的指示,把rails guides 5的中文版和英文版看完就可以接任务了,精神大振。
4、犯了哪些错误?
5、明天还有哪些工作需要完成?
- 明天把Rails guides 5中文版看完
二、读书笔记
Rails guides 5
直接跳到Active Record的迁移
3.5 修改现有的迁移
在编写的迁移来完成或部分撤销之前的迁移时,可以使用revert方法。
3.6 数据库模式转储
3.6.1 数据库模式文件有什么用?
迁移尽管很强大,但并非数据库模式的可信来源。
Active Record通过检查数据库生成的db/schema.rb文件或SQL文件才是数据库模式的可信来源。
这两个可信来源不应该被修改,它们仅用于表示数据库的当前状态。
当需要部署Rails的新实例时,不必把所有迁移重新运行一遍,直接加载当前数据库的模式文件要简单和快速的多。
例如,我们可以这样创建测试数据库,把当前的开发数据库转储为db/schema.rb或db/structure.sql文件,然后加载到测试数据库。
数据库模式文件还可以用于快速查看。
3.6.2 数据库模式转储的类型
config/application.rb文件的config.active_record.schema_format选项来设置想要采用的方式,即:sql或:ruby
尽管如此,db/schema.rb在设计上有所取舍,不能表达数据库的特定项目,如触发器、存储过程或检查约束。
:sql格式的数据库模式,只能加载到和原有数据库类型相同的数据库,而不能加载到其他类型的数据库。
4.1
accepts_nested_attributes_for是什么?
4.2 数据验证的辅助方法
4.2.5 format
这个辅助方法检查属性的值是否匹配:with选项指定的正则表达式
class Coffe
validates :le, format: { with: /\A[a-zA-Z]+\z/
message: "only allows letters"
}
end
4.2.6 inclusion
检查属性的值是否在指定的集合中,集合可以是任何一种可枚举的对象。
class Coffee
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size"}
end
4.2.7 length
这个辅助方法验证属性值的长度,有多个选项,可以使用不同的方法指定长度约束。
class Person < ApplicationRecord
validates :name, length: { minimum: 2 }
validates :bio, length: { maximum: 500 }
validates :password, length: { in: 6..20 }
validates :registration_number, length: { is: 6 }
end
可以自定义错误方法
class Person < ApplicationRecord
validates :name, length: { minimum: 2, too_short: "%{count} characters is the mininum allowed" }
validates :bio, length: { maximum: 500, too_long: "%{count} characters is the maximum allowed" }
end
4.2.8 numericality
这个辅助方法检查属性的值是否只包含数字,默认情况下,匹配的值是可选的正负符号后加整数或浮点数。
如果把:only_integer 的值设置为true,使用下面的正则表达式验证属性的值:
/\A[+-]?\d+\z/
否则,会尝试使用Float把值转换成数字。
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
除了:only_integer之外,这个方法还可指定一下选项,限制可接受的值。
4.2.9 presence
这个辅助方法检查指定的属性是否为非空值,它调用blank?方法检查值是否为nil或空字符串,即空字符串或只包含空白的字符串。
class Person < ApplicationRecord
validates :name, :login, :email, presence: true
end
如果确保关联对象是否存在,要在关联中指定:inverse_of选项。
class LineItem < ApplicationRecord
belongs_to :Order
validates :order, presence: true
end
为了能验证关联对象是否存在,要在关联中指定:inverse_of选项。
class Order
has_many :line_items, inverse_of: :order
end
4.2.11 uniqueness 验证属性值是否唯一,该方法不会在数据库中创建唯一性约束。
class Account < ApplicationRecord
validates :email, uniqueness: true
end
这个验证会在模型对应的表中执行一个SQL查询,检查现有的记录中该字段是否已经出现过相同的值。
:scope选项用于指定检查唯一性时使用的一个或多个属性。
class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year
message: "should happen once per year" }
end
如果想确保使用:scope选项的唯一性时使用的一个或多个属性。
4.2.12 validates_with 这个辅助方法把记录交给其他类做验证
4.2.13 validates_each 这个辅助方法使用代码块中的代码验证属性,它没有预先定义验证函数,你要在代码块中定义验证方式,要验证的每个属性都会传入块中,在下面的例子,我们确保名和姓都不能以小写字母开头。
class Person < ApplicationReocrd
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower]]/
end
end
4.3 常用的验证选项
4.3.1 :allow_nil
指定:allow_nil选项后,如果要验证的值为nil就跳过验证。
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size"}, allow_nil: true
end
4.3.2 :allow_blank
指定:allow_blank和:allow_nil选项类似,如果要验证的值为nil(调用blank?方法判断,例如nil或空字符串),就跳过验证。
validates :started_on, timeliness: true, allow_blank: true
4.3.3 :message
前面已经介绍过,如果验证失败,会把:message选项指定的字符串添加到errors集合中,如果没指定这个选项,Active Record使用各个验证辅助房的默认错误消息.
4.3.4 :on
:on 选项指定什么时候验证,所有内置的验证辅助方法默认都在保存时(新建记录或更新记录)验证,如果想修改,可以使用om: :create,指定只在创建记录时验证;或者使用on::update,指定只在更新记录时验证。
validates :number, format: /\A\d{10}\Z/, on: :create
4.4 严格验证
数据验证还可以使用严格模式,当对象无效时抛出ActiveModel::StrictValidationFailed异常
class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
Person.new.valid?
4.5 条件验证
有时,只有满足特定条件时做验证才说的通,条件可通过 :if和:unless选项指定,这两个选项的值可以是符号、字符串、Proc或数组,:if选项指定何时做验证,如果指定何时不做验证,使用:unless选项。
4.6 自定义验证
自定义的验证类继承自ActiveModel::Validator,必须实现validate方法,其参数是要验证的记录,然后验证这个记录是否有效,自定义的验证类通过validates_with方法调用。
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] << 'Need a name starting with X please'
end
end
在自定义的验证类中验证单个属性,最简单的方法世纪城ActiveModel::EachValidator类,此时,自定义的验证类必须实现validate_each方法,这个方法接收三个参数:记录、属性名和属性值。它们分别对应模型实例、要验证的属性及其值。
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.erros[attribute] << (options[:message]|| "is not an email")
end
end
end
4.6.2 自定义验证方法
你还可以自定义方法,验证模型的状态,如果验证失败,向errors集合添加错误消息,验证方法必须使用类方法validate(API)注册,传入自定义验证方法名的符号形式。
这个类方法可以接受多个符号,自定义的验证方法会按照注册的顺序执行。
valid?方法可以接受多个符号,自定义的验证方法名的符号形式顺序执行。
4.7 处理验证错误
除了前面介绍的valid和valid?方法之外,Rails还提供了很多方法用来处理errors集合,以及查询对象的有效性。
下面介绍一些最常用的方法,所有可用的方法请查阅ActiveModel::Errors的文档。
4.7.1 errors
ActiveModel::Errors的实例包含所有的错误,键是每个属性的名称,值是一个数组,包含错误消息字符串。
Active Person < ApplicationRecord
end
4.8 在视图中显示验证错误
在模型中加入数据验证后,如果在表单中创建模型,出错时,你或许想把错误消息显示出来。
因为每个应用显示错误消息的方式不同,所以Rails没有直接提供用于显示错误消息的视图辅助方法,不过,Rails提供了这么多方法用来处理验证,自己编写一个也不难,使用脚手架,Rails会在生成的_form.html.erb中加入一些ERB代码,显示模型错误消息的完整列表。
第五章 Active Record 回调
对象的生命周期的某些时刻被调用的方法,通过回调,我们可以编写在创建、保存、更新、删除、验证或从数据库中加载Active Record对象时执行的代码。
注册回调
class user < ApplicationRecord
validates :login, :email, presence: true
before_validation :ensure_login_has_a_value
protected
def ensure_login_has_a_value
if login.nil?
self.login = email unless email.blank?
end
end
end
5.3.4 after_initialize和after_find回调
当Active Record对象被实例化时,不管是通过直接使用new方法,还是从数据库加载记录,都会调用after_initialize回调。使用这个回调可以避免直接覆盖Active Record的initialize方法。
当Active Record从数据库中加载记录时,会调用after_find回调,如果同时定义了after_initialize和after_find回调。
5.4 调用回调
5.5 跳过回调
7.1 数据库查询
- find 可以输入数组,返回的也是数组
client = Client.find([1,10])
SELECT * FROM clients WHERE (clients.id in(1,10))
- take 检索一条记录而不考虑排序。
client = Client.take
7.2 条件查询
where方法用于指明限制返回记录所用的条件,相当于SQL语句的WHERE部分。条件可以使用字符串、数组或散列指定。
7.2.1 纯字符串条件
可以直接用纯字符串为查找添加条件,例如,Client.where("orders_count='2'")会查找所有orders_count字段的值为2的客户记录。
7.2.2 数组条件
如果Client.where("orders_count = '2'")这个例子中的数字是变化的,比如说是从别处传递过来的参数,那么可以像下面这样进行查找:
Client.where("orders_count = ?", params[:orders])
Active Record会把第一个参数作为条件字符串,并用之后的其他参数来替换条件字符串中的问号(?)
我们还可以指定多个条件:
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
在上面的例子中,第一个问号会被替换为params[:orders]的值,第二个问号会被替换为false在SQL中对应的值,这个值是什么取决于所使用的数据库适配器。
强烈推荐使用下面这种写法:
Client.where("orders_count = ?", params[:orders])
而不是
Client.where("orders_count = #{params[:orders]}", )
原因是,处于安全的考虑,把变量直接放入条件字符串会导致变量原封不动地传递给数据库,这意味着即使是恶意用户提交的变量也不会被转义,这样一来,整个数据库就处于风险之中。
7.2.2.1 条件中的占位符
和问号占位符(?)类似,我们还可以在条件字符串中使用符号占位符,并通过散列提供符号对应的值:
Client.where("Created_at >= :start_date AND created_at <= :end_date",
{start_date: params[:start_date], end_date: params[:end_date]})
如果条件中有很多变量,那么上面这种写法的可读性更高。
7.2.3 散列条件
Active Record还允许使用散列条件,以提高条件语句的可读性,使用散列条件时,散列的键指明需要限制的字段,键对应的值指明如何限制。
7.2.3.1 相等性条件
Client.where(locked: true)
上面的代码会生成下面的SQL语句:
SELECT * FROM clients WHERE (clients.locked = 1)
其中字段名也可以是字符串:
Client.where("locked" => true)
对于belongs_to关联来说,如果使用Active Record对象作为值,就可以使用关联键来指定模型。这种方法也适用多态关联。
7.2.3.1 相等性条件
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
7.3 排序
要想按特定顺序从数据库中检索记录,可以使用order方法。
例如,如果想按created_at字段的升序方式取回记录:
Client.order(:created_at)
或
Client.order("created_at")
7.4 选择特定字段
Client.select("viewable_by, locked")
# 生成
SELECT viewable_by, locked FROM clients
7.5 限量和偏移量
要想在Model.find生成的SQL语句中使用LIMIT子句,可以关联上使用limit和offset方法。
limit方法用于指明想要取回的记录数量,offset方法用于指明取回记录时再第一条记录之前要跳过多少条记录。
例如:
Client.limit(5)
上面的代码会返回5条记录,因为没有使用offset方法,所以返回的这5条记录就是前5条记录,生成的SQL语句如下:
SELECT * FROM clients LIMIT 5
如果使用offset方法:
Client.limit(5).offset(30)
这时会返回从第31条记录开始的第5条记录,生成的SQL语句如下:
SELECT * FROM clients LIMIT 5 OFFSET 30
这时会返回从第31条记录开始的第5条记录,生成的SQL语句如下:
SELECT * FROM clients LIMIT 5 OFFSET 30
7.6 分组
要想在查找方法生成的SQL语句中使用GROUP BY子句,可以使用group方法。
例如,如果我们想根据订单创建日期查找订单记录:
Article.select("created_at as ordered_date").group("created_at")
ps:怎么查询不出来
7.6.1 分组项目的总数
要想得到一次查询中分组项目的总和,可以在调用group方法后调用count方法。
Order.group(:status).count
=> { ‘awaiting_approval’ => 7, 'paid' => 12 }
下面的代码会生成SQL语句:
SELECT COUNT (*) AS count_all, status AS status FROM "orders" GROUP BY status
7.7 having方法
SQL语句用HAVING子句指明GROUP BY字段的约束条件,要想在Model.find生成的SQL语句中使用HAVING子句,可以使用having方法,例如:
Article.select("date(created_at) as ordered_date, sum(author3) as
author").group("date(created_at)").having("sum(price) > ?", 100)
7.8 条件覆盖
7.8.1 unscope 方法
可以使用unscope方法删除某些条件,例如:
Article.where('id > 10').limit(20).order('id asc').unscope(:order)
上面的代码会生成下面的SQL语句:
SELECT * FROM articles where id > 10 LIMIT 20
还可以使用unscope方法删除where方法中的某些条件。例如:
Article.where(id:10, trashed:false).unscope(where: :id)
在关联中使用unscope方法,会对整个关联造成影响。
Article.order('id asc').merge(Article.unscope(:order))
在关联中使用unscope方法,会对整个关联造成影响:
Article.order('id asc').merge(Article.unscope(:order))
7.8.2 only方法
可以使用only方法覆盖某些条件。例如:
Article.where('id > 10').limit(20).order('id desc').only(:order, :where)
7.8.3 reorder方法
可以使用reorder方法覆盖默认作用域中的排序方式。例如:
class Article < ApplicationRecord
has_many :comments, ->{ order('pasted_at DESC') }
end
Article.find(10).comments.reorder('name')
上面的代码会生成下面的SQL语句:
SELECT * FROM articles WHERE id = 10
SELECT * FROM articles WHERE article_id = 10 ORDER BY name
7.8.4 reverse_order方法
可以使用reverse_order方法反转排序条件。
Client.where("orders_count > 10").order(:name).reverse_order
上面的代码会生成下面的SQL语句:
7.8.5 rewhere方法
可以使用rewhere方法覆盖where方法中指定的条件。
7.9 空关系
none方法返回可以再链式调用中使用的、不包含任何记录的空关系,在这个空关系上应用后续条件链,会继续生成空关系,对于可能返回的零结果、但又需要在链式调用中,使用的方法或作用域,可以使用none方法提供返回值。
Article.none
7.10 只读对象
在关联中使用Active Record提供的readonly方法,可以显式禁止修改任何返回对象,如果尝试修改只读对象,不但不会成功,还会抛出ActiveRecord::Readonly异常。
client = Client.readonly.first
client.visits += 1
client.save
7.11在更新时锁定记录
在数据库中,锁定用于避免更新记录时的条件竞争,并确保原子更新.
- 乐观锁定
- 悲观锁定
为了使用乐观锁定,数据表中需要有一个整数类型的lock_version字段,每次更新记录时,Active Record都会增加lock_version字段的值,如果更新请求中lock_version字段的值比当前数据库中lock_version字段的值小,更新请求就会失败,并抛出ActiveRecord::StaleObjectError异常。例如:
c1 = Client.find(1)
c2 = Client.find(1)
c1.first_name = "Michael"
c1.save
c2.name = "should fail"
c2.save
抛出异常后,我们需要救援异常并处理冲突,或回滚,或合并,或应用其他业务逻辑来解决冲突。
通过设置异常后,我们需要救援异常并处理冲突,或回滚,或合并,或应用其他业务逻辑来解决冲突。