Day22 心得体会&读书笔记

一、心得体会

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

抛出异常后,我们需要救援异常并处理冲突,或回滚,或合并,或应用其他业务逻辑来解决冲突。

通过设置异常后,我们需要救援异常并处理冲突,或回滚,或合并,或应用其他业务逻辑来解决冲突。

你可能感兴趣的:(Day22 心得体会&读书笔记)