《ruby on rails guides》前半部分提取

[TOC]

新手入门
  1. Form_for表单助手:

```Html

New Article

<%= form_for :article, url: articles_path do |f| %>


<%= f.label :title %>

<%= f.text_field :title %>


<%= f.label :text %>

<%= f.text_area :text %>


<%= f.submit %>


<% end %>
```
  • url: articles_path 可以指定路由

  • 用下面这个语句可以直接返回字符形式得知params:

    ruby
    def create
    render plain: params[:article].inspect
    end

  1. CRUD的常规顺序:
  • index
  • show
  • new
  • edit
  • create
  • update
  • destroy
  1. Link_to方法还可以这样用:


<%= link_to 'New Blog', controller: 'articles' %>

  1. pluralize方法一个rails内建的方法,超过1会自动转换成复数形式:


<%= pluralize(@article.errors.count, "error") %>

  1. 原来rails内置了一个身份验证器,可以很方便地使用起来!:


http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

在controller里增加一条这样的语句就可以了,效果:

超级棒,在开发的时候,或者只需要一人的管理系统上这是很方便的!

Active Record基础
  1. 更新一个栏位:


user = User.find_by(name: 'David')
user.update(name: 'Dave')

就搞定了,比我之前的要简单一点。

Active Record迁移
  1. 如何让migration文件自动生成栏位:(按以下这种格式)


rails g migration add_title_to_products title:string
rails g migration remove_title_from_products title:string

  1. migration 中还有另外一个关于修改默认值的方法:
  • change_column_default
  • change_column_null

ruby
change_column_null :products, :name, false (设置这个字段不能空)
change_column_default :users, :is_admin, from: false, to: true

  1. 字体修饰符:
  • limit 修饰符:设置 string/text/binary/integer 字段的最大长度。
  • precision 修饰符:定义 decimal 字段的精度,表示数字的总位数。
  • scale 修饰符:定义 decimal 字段的标度,表示小数点后的位数。
  • polymorphic 修饰符:为 belongs_to 关联添加 type 字段。
  • null 修饰符:设置字段能否为 NULL 值。
  • default 修饰符:设置字段的默认值。请注意,如果使用动态值(如日期)作为默认值,那么默认值只会在第一次使时(如应用迁移的日期)计算一次。
  • index 修饰符:为字段添加索引。
  • comment 修饰符:为字段添加注释。
  1. 回滚
  • rails db:rollback
  • rails db:rollback STEP=3回滚三步
  • rails db:migration:redo重做最后一个步骤,
  • rails db:migration:redo STEP=3重做最后有一个步骤。
  1. 重置数据库
  • rake db:reset
Active Record数据验证
  1. .new_record? 判断对象是否已经存入数据库。

  2. 下面的方法会触发验证:

  • create
  • create!
  • save
  • save!
  • update
  • update!
  1. 下面的方法会跳过验证:
  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

另外,在需要验证的方法里如果传入validates: false也会跳过验证。


save(validates: false)

  1. 判断是否能通过验证:
  • valid?

invalid? 刚好相反,意思是要验证是否无法通过。

  1. 关于获取验证的错误信息:

所有发现的错误都可以通过实例方法errors.messages来获取。该方法会返回一个错误集合。

  1. 如果想精确的查看某个属性存在错误,可以这样:
  • .errors[attribute]
  1. 进一步想看是什么导致的错误,可以这样:
  • .errors.details[attribute]
  1. 如果相关联的Model也要验证的话,可以这样:


class Library < ApplicationRecord
has_many :books
validates_associated :books
end

  1. 如果想验证一个重要的栏位,如果确认密码,邮件时,必须要两个 栏位都正确时,才会通过验证:
  • 创建一个虚拟属性,命名规则是在后面加_confirmation

  • 在views中:

    html
    <%= text_field :person, :email %>
    <%= text_field :person, :email_confirmation %>

  • 在Model中:


    class Person < ApplicationRecord
    validates :email, confirmation: true
    validates :email_confirmation, presence: true
    end

验证时,是否区分大小写:


class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end

  1. 辅助方法:验证是否在指定的集合中?
 class Account < ApplicationRecord
   validates :subdomain, exclusion: { in: %w(www us ca jp),
     message: "%{value} is reserved." }
 end
  1. format用正则表达式来匹配:


class Product < ApplicationRecord
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "only allows letters" }
end

  1. 给验证限定指定的几个选项。


class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end

相反的结果:within

  1. 为栏位限定长度:


class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end

可用的长度约束选项有:

  • :minimum:属性的值不能比指定的长度短;
  • :maximum:属性的值不能比指定的长度长;
  • :in(或 :within):属性值的长度在指定的范围内。该选项的值必须是一个范围;
  • :is:属性值的长度必须等于指定值;
  1. 验证栏位必须是数字:

默认情况下,匹配的值是可选的正负符号后加整数或浮点数。如果只接受整数,把 :only_integer 选项设为 true


class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end

此外,还有以下这些选项:

  • :greater_than:属性值必须比指定的值大。该选项默认的错误消息是“must be greater than %{count}”;
  • :greater_than_or_equal_to:属性值必须大于或等于指定的值。该选项默认的错误消息是“must be greater than or equal to %{count}”;
  • :equal_to:属性值必须等于指定的值。该选项默认的错误消息是“must be equal to %{count}”;
  • :less_than:属性值必须比指定的值小。该选项默认的错误消息是“must be less than %{count}”;
  • :less_than_or_equal_to:属性值必须小于或等于指定的值。该选项默认的错误消息是“must be less than or equal to %{count}”;
  • :other_than:属性值必须与指定的值不同。该选项默认的错误消息是“must be other than %{count}”。
  • :odd:如果设为 true,属性值必须是奇数。该选项默认的错误消息是“must be odd”;
  • :even:如果设为 true,属性值必须是偶数。该选项默认的错误消息是“must be even”;

numericality 默认不接受 nil 值。可以使用 allow_nil: true 选项允许接受 nil

  1. 验证是否为空值:
  • presence


    class Person < ApplicationRecord
    validates :name, :login, :email, presence: true
    end

  • 验证关联的对象是否存在:


    class Order < ApplicationRecord
    has_many :line_items, inverse_of: :order
    end


    class LineItem < ApplicationRecord
    belongs_to :order
    validates :order, presence: true
    end

    如果验证 has_onehas_many 关联的对象是否存在,会在关联的对象上调用 blank?marked_for_destruction? 方法。

    因为 false.blank? 的返回值是 true,所以如果要验证布尔值字段是否存在,要使用下述验证中的一个:


    validates :boolean_field_name, inclusion: { in: [true, false] }
    validates :boolean_field_name, exclusion: { in: [nil] }

  1. 另外一个验证是否为空的方法:
  • absence

    这个方法和上面的presence 比较相似,这里暂时跳过。

  1. 验证是否唯一性:
  • uniqueness


    class Account < ApplicationRecord
    validates :email, uniqueness: true
    end

  • 选项一

可以加一个:scope以指定使用一个或多个属性


class Holiday < ApplicationRecord
validates :name, uniqueness: { scope: :year,
message: "should happen once per year" }
end

选项二 区分大小写用case_sensitive


class Person < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }
end

  1. 人才啊:这个方法可以用让其他类的方法来验证!也就是把借别人的方法来用!
  • validates_with

```
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end

class Person < ApplicationRecord
validates_with GoodnessValidator
end
```

record.errors[:base] 中的错误针对整个对象,而不是特定的属性。

  1. 给要验证的对象使用代码块block来运算:
  • validates_each

    每次把对象传入block运算


    class Person < ApplicationRecord
    validates_each :name, :surname do |record, attr, value|
    record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/
    end
    end

  1. 一些常用的验证选项:
- `allow_nil` :如果要验证值为Nil就跳过验证。
- `allow_blank` :和上面的类似。
- `message` :当发生验证失败时,把message选项里的值赋给errors集合。
- `:on` :指定什么时候验证。如`on: :create` `on: :update`
  1. 严格验证
- `strict`  :当对象无效时爆炸

  ```
  class Person < ApplicationRecord
    validates :name, presence: { strict: true }
  end

  Person.new.valid?  # => ActiveModel::StrictValidationFailed: Name can't be blank
  ```

  > 同时还可以用`strict`来指定爆炸的提示:
  >
  > ```
  > class Person < ApplicationRecord
  >   validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
  > end
  >  
  > Person.new.valid?  # => TokenGenerationException: Token can't be blank
  > ```
  1. 条件验证:只有当对象满足特定条件时,才触发验证,这个可以自己用if unless等条件语句实现。
- 使用符号:`:if` 和 `:unless` 选项的值为符号时,表示要在验证之前执行对应的方法。这是最常用的设置方法。

  ```
  class Order < ApplicationRecord
    validates :card_number, presence: true, if: :paid_with_card?

    def paid_with_card?
      payment_type == "card"
    end
  end
  ```

- 条件组合:有时需要很多验证:

  - `with_options`

  ```
  class User < ApplicationRecord
    with_options if: :is_admin? do |admin|
      admin.validates :password, length: { minimum: 10 }
      admin.validates :email, presence: true
    end
  end
  ```

- 联合条件

  ```
  class Computer < ApplicationRecord
    validates :mouse, presence: true,
                      if: ["market.retail?", :desktop?],
                      unless: Proc.new { |c| c.trackpad.present? }
  end
  ```

   这里只有当`:if` 选项返回true,且`:unless`选项返回false时都会做验证。
  1. 自定义验证

    跳过

  2. 处理错误信息

- 手动增加错误信息:

  - `errors.add`

    ```
    a.errors.add(:name, "this is a example.")
    a.errors.full_messages 
    #=> ["Title this is a example."]
    a.errors.to_a
    #=> ["Title this is a example."]
    ```

    > 这个的`.to_a`和`.full_messages`是一样的效果。

  - `<<`方法,追回到errors.messages数组中。

    ```
    a.errors.messages[:name] << "test"
    ```

- 不分属性栏位的错误提示:`errors[:base]` 

  > 这里的错误信息是针对整个对象的。只是简单都标记这对象无效。

- 清空错误信息:

  - `.clear`

- 查看错误错误信息的数量:

  - `.size`
Active Record回调

什么是回调:回调使我们可以在对象状态更改之前或之后触发逻辑。

通过回调,我们可以编写在创建、保存、更新、删除、验证或从数据库中加载 Active Record 对象时执行的代码。

通常需要封装为私有方法。

方法列表:
  1. 创建对象:
    • before_validation
    • after_validation
    • before_save
    • around_save
    • before_create
    • around_create
    • after_create
    • after_save
    • after_commit/after_rollback
  2. 更新对象:
    • before_validation
    • after_validation
    • before_save
    • around_save
    • before_update
    • around_update
    • after_update
    • after_save
    • after_commit/after_rollback
  3. 删除对象:
    • before_destroy
    • around_destroy
    • after_destroy
    • after_commit/after_rollback

其他回调:

  1. after_initialize after_find 在每次对数据库有操作时都会触发的回调。

  2. 下面这些方法会触发回调:

  • create
  • create!
  • decrement!
  • destroy
  • destroy!
  • destroy_all
  • increment!
  • save
  • save!
  • save(validate: false)
  • toggle!
  • update_attribute
  • update
  • update!
  • valid?

此外,下面这些查找方法会触发 after_find 回调:

  • all
  • first
  • find
  • find_by
  • find_by_*
  • find_by_*!
  • find_by_sql
  • last

每次初始化类的新对象时都会触发 after_initialize 回调。

  1. 一些会跳过回调的方法。

在不清楚有什么回调的情况下,要慎重使用。

  • decrement
  • decrement_counter
  • delete
  • delete_all
  • increment
  • increment_counter
  • toggle
  • touch
  • update_column
  • update_columns
  • update_all
  • update_counters
  1. 可以把回调的方法封装成一个类:


class PictureFileCallbacks
def self.after_destroy(picture_file)
if File.exist?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end


class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks
end

  1. 事务性回调:
  • after_commit after_rollback 这两个回调会在数据库事务完成时触发。
  • after_ commit包括在执行创建,更新,删除时会触发。所以,这些操作都拥有别名:
    • after_create_commit
    • after_update_commit
    • after_update_commit

在事务中使用回调要注意,如果其中一个发生异常,会使得其他回调不再执行。这点要注意。需要适当处理,以便让其他回调继续运行。

Active Record关联
  1. Rails支持六种关联:
  • belongs_to
  • has_one
  • has_many
  • has_many :through
  • has_one :through
  • has_and_belongs_to_many
  1. ##### 多态关联:超好用的模型关联,以后再也不用建立那么多图片数据表了!!!

```
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
```

belongs_to 中指定使用多态,可以理解成创建了一个接口,可供任何一个模型使用。在 Employee 模型实例上,可以使用 @employee.pictures 获取图片集合。

类似地,可使用 @product.pictures 获取产品的图片。

Picture 模型的实例上,可以使用 @picture.imageable 获取父对象。不过事先要在声明多态接口的模型中创建外键字段和类型字段:


class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true, index: true
t.timestamps
end
end
end

现在才发现以前写那么多的图片的数据表,好浪费资源,浪费代码量。

  1. 自联结
  • 有时,在同一表中需要息关联。如:同Users表中,有雇员,有经理,需要建立上级和下属的关系。怎么做:

    ```
    class Employee < ApplicationRecord
    has_many :subordinates, class_name: "Employee",
    foreign_key: "manager_id"

    belongs_to :manager, class_name: "Employee"
    end
    ```


    class CreateEmployees < ActiveRecord::Migration[5.0]
    def change
    create_table :employees do |t|
    t.references :manager, index: true
    t.timestamps
    end
    end
    end

    这样定义模型后,可以使用 @employee.subordinates@employee.manager 检索了。

  1. 小技巧和注意事项
  • 控制缓存

    关联添加的方法都会使用缓存,记录最近一次查询的结果,以备后用。缓存还会在方法之间共享。例如:


    author.books # 从数据库中检索图书
    author.books.size # 使用缓存的图书副本
    author.books.empty? # 使用缓存的图书副本

    那,如何重新加载呢?
    reload方法即可


    author.books.reload.empty? # 丢掉缓存的图书副本

  • 避免命名冲突

  • 控制关联的作用域:

    关联只会查找当前模块作用域中的对象。

    不同的命名空间中的模型要正常关联,就需要声明完整的类名:

    ```
    module MyApplication
    module Business
    class Supplier < ApplicationRecord
    has_one :account,
    class_name: "MyApplication::Billing::Account"
    end
    end

    module Billing
    class Account < ApplicationRecord
    belongs_to :supplier,
    class_name: "MyApplication::Business::Supplier"
    end
    end
    end
    ```


华丽丽的分割线,以下部分开始启动略过模式》》》》》》

脑子里的代码记忆不够匹配,看这书真心烧脑,笔记记得再清楚,也记不住,还低效!如何破解:

  • 开启略过模式:只记录有什么工具,工具有什么作用。不过多记录用法,需要用到时再来复查。

  1. :touch选项的意思是:保存或销毁对象时,关联的对象的Update_at && update_on 字段会自动设为当前时间。

  2. validate 选项,意思是,保存时,会同时验证关联的对象。

  3. optional选项,意思是,保存时,不会验证关联的对象是否存在。

  4. 如果经常透过第三方加载数据,如:@line_item.book.author,就可以在关联中及早把作者从商品引入图书中:这也是避免n+1query的作法。

```ruby
class LineItem < ApplicationRecord
belongs_to :book, -> { includes :author }
end

class Book < ApplicationRecord
belongs_to :author
has_many :line_items
end

class Author < ApplicationRecord
has_many :books
end
```

  1. dependent 控制对象销毁后怎么处理关联的对象:
  • destroy:也销毁关联的对象
  • delete:直接把关联的对象从数据库中删除。
  1. has_many后面,自动会多出这些方法,比如:


class Author < ApplicationRecord
has_many :books
end

Ruby
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 = {})

看吧,就会以有这么多方法可以用!

比如@author.books << @book1 这个方法可以用来存储多个照片,很方便,可以直接就追加进去了!

  • @author.books.clear 这个方法可以把关联的对象清空
  • @author.books.empty? 这个方法可以检查关联的对象是否为空?
  1. offset方法

与limit差不多,但这里是跳过一定量的记录。如:

  • -> { offset(11) }会跳过11个记录。
  1. readonly方法,读取出来的对象是只读的。

  2. 关联扩展,可以使用其他类里面的方法,做法:

```
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
```

  1. 表单继承,把共同的东西都抽取出来,存入同一个模型。我又想到了众多图片可以存入同一个模型。

做法:假如有 Car、Motorcycle 和 Bicycle 三个模型,我们想在它们中共用 colorprice 字段,但是各自的具体行为不同,而且使用不同的控制器。


  • $ rails generate model vehicle type:string color:string price:decimal{10.2}


  • $ rails generate model car --parent=Vehicle


  • class Car < Vehicle
    end

可以了,这样就节省了数据表了。

Active Record 查询接口
检索单个对象:
  1. .find方法 :检索指定主键对应的对象

会报错

  • Post.find(10)
  • Post.find([1,10]) 检查多个值。返回的结果也是数组形式
  1. .take方法:检索一条记录而不考虑排序

不会抛出异常

  • Post.take
  • Post.take(2) 返回不超过指定数量的查询结果。
  1. .first方法,返回第一条记录,如果没有找到不会抛出异常。
  • 可以指定数量:


    Post.first(3) 返回头三个记录

  • 可以指定一个属性:


    Post.order(:first_name).first

  1. .last 方法,基本和first一样,相反而已。

  2. .find_by还可以这样写:

  • Post.find_by first_name: 'activeliang'
  1. find_each ,分批量检索记录,每次都把记录传入block。

  • User.find_each do |user|
    NewsMailer.weekly(user).deliver_now
    end

  • batch_size规定一次检索的数量


    User.find_each(batch_size: 5000) do |user|
    NewsMailer.weekly(user).deliver_now
    end

  • start规定要从哪一个记录开发检索,小于这个值的不会被检索。


    User.find_each(start: 2000) do |user|
    NewsMailer.weekly(user).deliver_now
    end

  • finish规定到哪一个记录就停止,大于这个值的不会被检索。


    User.find_each(start: 2000, finish: 10000) do |user|
    NewsMailer.weekly(user).deliver_now
    end

  1. find_in_batches 方法与前面的find_each类似,区别在于:这个方法是把批量的数据以数组的形式一次性传入block里的。
条件查询
  1. 查询时的子集条件:


Post.where(orders_count: [1,3,5])

  1. not条件:


Post.where.not(locked: true)

排序
  1. 多个字段排序:


Client.order(orders_count: :asc, created_at: :desc)
# 或
Client.order(:orders_count, created_at: :desc)
# 或
Client.order("orders_count ASC, created_at DESC")
# 或
Client.order("orders_count ASC", "created_at DESC")


Client.order("orders_count ASC").order("created_at DESC")

每次调用order的时候,都会在前一次的基础上进行排序。

选择选定的字段
  1. 选择某个字段

  • Client.select("viewable_by, locked")


  • Client.select(:name).distinct 后面的选项表示无重复的。

条件覆盖
  1. unscope方法,删除某些条件


Article.where('id > 10').limit(20).order('id asc').unscope(:order)

  1. only方法,忽略某些条件。


Article.where('id > 10').limit(20).order('id desc').only(:order, :where)

  1. reorder方法,可以覆盖默认作用域中的排序方法。


class Article < ApplicationRecord
has_many :comments, -> { order('posted_at DESC') }
end


Article.find(10).comments.reorder('name')

  1. reverse_order方法 :反转排序结果。

  2. rewhere方法,覆盖where方法中指定的条件。

空关系
  1. none方法,可以返回一个空Relation对象。


Article.none #返回一个空的Relation对象,而且不执行查询

在更新时锁定记录
  1. 乐观锁定与悲观锁定。这里只讲理解,要用时再来深究。

悲观锁:需要的悲观锁的数据,一旦有用户打开后,此数据就进入锁定状态,其他用户暂时无法再读取。当事务结束后,再释放这个锁定。

乐观锁:加了乐观锁的数据,同时会在数据库里需要珍上字段存储锁的版本,每次提交事务都会通过这个版本判断这个数据是否被改过,如果被改过则拒绝提交事务,但可以重来。

  1. n+1查询问题

解决方法:通过指明includes方法,Active Record会使用尽可能少的查询来加载所有已指明的关联。

  • 多个关联的数组


Article.includes(:category, :comments)

  1. 删除作用域:
  • unscoped 方法
  1. find_or_create_by 意思是:检索,如果空的那就新建一个。

  2. 检查对象是否存在:


  • Post.exists?(1)
    POst.exists?(id: [1,2,3])


  • Post.any?
    Post.many?

  1. 数据库自带的计算功能:
  • count方法:计算计数

  • average方法,计算平均值


    Client.average("orders_count")

  • minimum方法,查找最小值

  • maximum方法,查找最大值


    Post.minimum("age")
    Post.maximum("age")

  • sum方法,求某个字段值的和。

Active Model 基础

这一章节讲的是有个叫Active Model的库,里面有很多模块。这里只是大概地说明了部分模块。

  1. 弄脏

如果修改了对象的一个或多个属性,但是没有保存,此时就把对象弄脏了。ActiveModel::Dirty模块提供检查对象是否被修改的功能。它还提供了基于属性的存取方法。假如有个 Person 类,它有两个属性,first_namelast_name

```
class Person
include ActiveModel::Dirty
define_attribute_methods :first_name, :last_name

 def first_name
   @first_name
 end

 def first_name=(value)
   first_name_will_change!
   @first_name = value
 end

 def last_name
   @last_name
 end

 def last_name=(value)
   last_name_will_change!
   @last_name = value
 end

 def save
   # 执行保存操作……
   changes_applied
 end

end
```

```
person = Person.new
person.changed? # => false

person.first_name = "First Name"
person.first_name # => "First Name"

# 如果修改属性后未保存,返回 true
person.changed? # => true

# 返回修改之后没有保存的属性列表
person.changed # => ["first_name"]

# 返回一个属性散列,指明原来的值
person.changed_attributes # => {"first_name"=>nil}

# 返回一个散列,键为修改的属性名,值是一个数组,包含旧值和新值
person.changes # => {"first_name"=>[nil, "First Name"]}

```

这里就可以查询到某个参数是否被更改过?如果有,那原来提什么?

Action View 概览
  1. ##### DateHelper模块
  • date_select方法,选择年,月,日的选择列表。

  • datetime_select方法,选择年,月,日,时,分的选择列表。

  • distance_of_time_in_words方法,返回两个 时间的间隔:


    distance_of_time_in_words(Time.now, Time.now + 15.seconds) # => less than a minute
    distance_of_time_in_words(Time.now, Time.now + 15.seconds, include_seconds: true) # => less than 20 seconds

    加入include_seconds选项设置为true可以得到更精确的时间间隔。

  • select_date方法,返回年月日的选择列表,并通过Datetime对象来设置默认值。

    ```
    # 生成一个日期时间选择列表,默认选中指定的日期时间(四天以后)
    select_datetime(Time.now + 4.days)

    # 生成一个日期时间选择列表,默认选中今天(未指定日期时间)
    select_datetime()
    ```

  • select_datetime 方法,和上面的方法如出一辙。

  • select_day方法,和上面的方法如出一辙。

  • select_hour方法,和上面的方法如出一辙。

  • select_minute方法。同上。

  • select_month方法,同上。

  • select_second方法,同上。

  • select_time

  • select_year

  • time_ago_in_words方法,计算指定时间到当前时间的间隔。

  • time_select

  1. #### FormHelper模块
  • form_for

    ```
    # 生成一个日期时间选择列表,默认选中指定的日期时间(四天以后)
    select_datetime(Time.now + 4.days)

    # 生成一个日期时间选择列表,默认选中今天(未指定日期时间)
    select_datetime()
    ```

  • check_box


    # 假设 @article.validated? 的值是 1
    check_box("article", "validated")
    # => <input type="checkbox" id="article_validated" name="article[validated]" value="1" />
    # <input name="article[validated]" type="hidden" value="0" />

  • label


    label(:article, :title)
    # => <label for="article_title">Title</label>

  • radio_button


    # 假设 @article.category 的值是“rails”
    radio_button("article", "category", "rails")
    radio_button("article", "category", "java")
    # => <input type="radio" id="article_category_rails" name="article[category]" value="rails" checked="checked" />
    # <input type="radio" id="article_category_java" name="article[category]" value="java" />

  • email_field


    email_field(:user, :email)
    # => <input type="email" id="user_email" name="user[email]" value="#{@user.email}" />

  • url_field

    看到这个方法的我,眼泪掉下来


    url_field(:user, :url)
    # => <input type="url" id="user_url" name="user[url]" value="#{@user.url}" />

  1. #### FormOptionsHelper模块
  • image_submit_tag :此方法会显示一张图像,点击图像会提交表单。
  1. #### JavaScriptHelper模块
  • javascript_tag方法,可以很方便在页面中塞Javascripts语句

    Ruby
    javascript_tag "alert('All is good')"

  1. #### NumberHelper模块
  • number_to_currency 方法,把数字转换成货币字符串:

    ```
    number_to_currency(1234567890.50) # => $1,234,567,890.50

    6.12.2
    ```

  • number_with_delimiter方法,把数字软的成带千们转换符的数字。


    number_with_delimiter(12345678) # => 12,345,678

  • number_with_precision方法,把数字转换成具有指定精度的数字。

Rails 布局和视图渲染
  1. render方法:
  • :plain选项,可以直接返回没有标记语言的线文本:```


    render plain: "OK"

  • :html选项,可以直接返回页面使用的html:


    render html : "<strong>Not Found</strong>".html_safe

  • :inline选项,直接返回erb代码。


    render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"

  • json xml

  • js


    render js: "alert('Hello Rails');"

  • :status 直接返回状态码

  • formats 返回数据格式

renderredirect_to的区别:render会直接去加载html页面而不会通过controller,而redirect_to 会重新从controller开发请求。

  1. redirect_to方法

  2. javascript_include_tag可以很方便引入js


<%= javascript_include_tag "main", "/photos/columns" %>


<%= javascript_include_tag "http://example.com/main.js" %>

  1. stylesheet_link_tag可以很方便引入css,大致方法同上。

  2. video_tag 方法可以很方便载入视频。


<%= video_tag "movie.ogg" %>

video_tag 方法还可使用散列指定 <video> 标签的所有属性,包括:

  • poster: "image_name.png":指定视频播放前在视频的位置显示的图片;
  • autoplay: true:页面加载后开始播放视频;
  • loop: true:视频播完后再次播放;
  • controls: true:为用户显示浏览器提供的控件,用于和视频交互;
  • autobuffer: true:页面加载时预先加载视频文件;
  1. audio_tag方法,可以很方便载入音频。

video_tag 类似,audio_tag 也有特殊的选项:

  • autoplay: true:页面加载后开始播放音频;
  • controls: true:为用户显示浏览器提供的控件,用于和音频交互;
  • autobuffer: true:页面加载时预先加载音频文件;
表单辅助方法
  1. 一些辅助方法:


<%= text_area_tag(:message, "Hi, nice site", size: "24x6") %>
<%= password_field_tag(:password) %>
<%= hidden_field_tag(:parent_id, "5") %>
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
<%= datetime_local_field(:user, :graduation_day) %>
<%= month_field(:user, :birthday_month) %>
<%= week_field(:user, :birthday_week) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
<%= range_field(:product, :discount, in: 1..100) %>

  1. 终于知道如何在一个表单内完成同时创建两个值的方法了!:
  • fields_for方法可以办到:


    <%= form_for @person, url: {action: "create"} do |person_form| %>
    <%= person_form.text_field :name %>
    <%= fields_for @person.contact_detail do |contact_detail_form| %>
    <%= contact_detail_form.text_field :phone_number %>
    <% end %>
    <% end %>

  1. Form_for处理命名空间:

    1. 快速动态生成选择列表
  - `select_tag`

    ```
    <%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %>
    ```

  - `options_for_select`

    ```
    <%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %>
    ```

  两者相结合,就可以动态方便都生成了:

  ```
  <%= select_tag(:city_id, options_for_select(...)) %>
  ```
  • 到这里,还有一个问题没有解决:option的选项还是需要手工,如果选项很多,那得输死。解决方法:见下面
  1. 从任意对象组成的集合中创建option标签。
  • 先把选项变成一个集合,再整合进option_for_select


    <% cities_array = City.all.map { |city| [city.name, city.id] } %>
    <%= options_for_select(cities_array) %>

  • 更简单粗暴的作法:

    利用rails已经帮我们内建的方法:options_from_collection_for_select


    <%= options_from_collection_for_select(City.all, :id, :name) %>

  1. 时间日期的表单辅助方法

  • <%= select_date Date.today, prefix: :start_date %>

  • 处理模型对象时,可以更简单粗暴:


    <%= date_select :person, :birth_date %>

通常rails的内置方法只是会包括前后5年而已,如果想更多,可以设置start_year end_year

  1. ##### 上传文件

在上传文件时特别需要注意的是,表单的编码必须设置为 multipart/form-data。使用 form_for 辅助方法时会自动完成这一设置。如果使用 form_tag 辅助方法,就必须手动完成这一设置,具体操作可以参考下面的例子。

```
<%= form_tag({action: :upload}, multipart: true) do %>
<%= file_field_tag 'picture' %>
<% end %>

<%= form_for @person do |f| %>
<%= f.file_field :picture %>
<% end %>
```

Action Controller概览

控制器负责解析请求,生成相应的输出。

  1. rails的参数要求是标量值,数组和Hash不是标量值,所以不能被使用。

允许使用的标量类型:有:StringSymbolNilClassNumericTrueClassFalseClassDateTimeDateTimeStringIOIOActionDispatch::Http::UploadedFileRack::Test::UploadedFile

  1. 会话(原来就是session)

  2. 事务transaction

主要的作用是,把一系列的数据库操作包裹在一起,当其中一条出错时,整个操作会回滚!

常见的错误用法:

  1. 单条记录操作时使用事务
  2. 不必要的使用嵌套式事务
  3. 事务中的代码不会导致回滚
  4. 在 controller 中使用事务

其中第3条占比比较多,常见:

  • 没有使用!方法,遇错不回滚
  • 容易出错的地方,主动判断,主动抛出异常
  • 嵌套的事务中,子事务的遇错回滚不会上传到父事务。
  1. 数据流和文件下载

这里提供了两个方法,可以直接传送数据给用户。像我们平时下载文件一样!

  • send_data 主要是把数据以流的形式发送。
  • send_file 主要是用来发送磁盘中现有的数据。

另外本书提示:下载最好是放在public里,这样能直接代下载,不需要经常rails栈,效率更高。

  1. REST式下载

优势:不使用数据流:

```
class ClientsController < ApplicationController
# 用户可以请求接收 HTML 或 PDF 格式的资源
def show
@client = Client.find(params[:id])

   respond_to do |format|
     format.html
     format.pdf { render pdf: generate_pdf(@client) }
   end
 end

end
```

为了让这段代码能顺利运行,要把 PDF 的 MIME 类型加入 Rails。在 config/initializers/mime_types.rb 文件中加入下面这行代码即可:


Mime::Type.register "application/pdf", :pdf

  1. 强大的数据流:

这个就好像和浏览器一直在通话一样,你可以随时发送数据。(平常的请求可以理解为微信发语音,而数据流可以想象成语音通话)

  1. 强制使用HTTPS协议
  • 在controller里增加:


    class DinnerController
    force_ssl only: :cheeseburger
    # 或者
    force_ssl except: :cheeseburger
    end

Rails 路由全解
  1. 同时定义多个资源:


resources :photos, :books, :videos

FUCK,原来可以这样!

  1. 使用单数资源时,form_for辅助方法无法自动处理单数资源,这里我们需要指定表单URL,例如:


form_for @geocoder, url: geocoder_path do |f|

  1. 嵌套资源最好不要超过1层

  2. 有时可以省略path


<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>
<%= link_to 'Ad details', [@magazine, @ad] %>
<%= link_to 'Magazine details', @magazine %>
<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>

  1. 动态片段


get 'photos/:id/:user_id', to: 'photos#show'

/photos/1/2 路径会被映射到 Photos 控制器的 show 动作上。此时,params[:id] 的值是 "1"params[:user_id] 的值是 "2"

  • 片段限制
  • 请求限制
  • 通配符和通配片段
前半部分结束》》》》》》》》》》》》》》

comments powered by Disqus