表格被拉得超长怎么办?

在项目中,表格被一些字段拉得超长,整个表格效果看起来很不协调。
怎么破?

一般字母会被浏览器默认是一个单词或字符串,所以不会自动换行,需要手工设置自动换行。

这样做:
  1. <table>里增加style="table-layout:fixed;"
  2. 在那个超长的字母单元格<td> 增加style="word-wrap:break-word;"

ruby下载文件保存到本地较好的方法

因为要汇出Excel档案,需要将图片下载回本地,才能写入excel档案。但总觉得下载的速度有点慢。

一开始的方法:

File.open( "#{Rails.root}/public/images/#{r.envelop}.png", "w") do |f|
    image_file = RestClient.get get_image_url
    f.write(image_file.force_encoding("utf-8"))
end

实测,导入25张图片用时15034ms。

思考:平时在网站上加载25张图片,超快!3秒内搞定。为什么同样的网络,用ruby代码来下载就如此费时!一定是哪个步骤比较耗时。猜测是转码RestClient

在Google找到的另一种方法:

data=open("http://example.com/public/2.jpg"){|f|f.read}
open("/public/1.jpg","wb"){|f|f.write(data)}

实测,时间降到5164ms。

如何根据链接保存uploader类文件到七牛云

我做了一个网络小爬虫,爬回来的照片链接,我想转存到我的七牛图床,怎么做?

几经周折,其实很简单。这样:

product.remote_[图片栏位名称]_url = "http://example.com/test.jpg"
product.save

搞定!

当保存时,Rails会自动把这个图片上传到七牛云!

《极速读书法》提取

《极速读书法》的要点:

  1. 不要从第一章节开始,从你最想看的章节开始。
  2. 最好是你有很强的目标去读这本书。
  3. 左右脑模式:
  • 晚上右脑会比较强
  • 早上左脑会比较强
  • 逻辑强的新书最好晚上看。一页一页地翻,大概看一下,第二天早上起来再细读。这时候会感觉很好读。下午再做一次复盘,写下来。

如何看待只字不差地阅读:
“只字不差”意思是说:要重复多几遍,搞清重点。

关于主题阅读

套路:找很多相关书籍,找出它们的共同点,这些共同点就是高频小套路。其中能把这些小套路讲清楚的书就是经典书籍。

听许岑《如何高效学习》提取记录

  1. #### 什么是高效的学习

什么是高效学习:

短时间内,

  1. #### 选择什么样的学习方式。

学习分两种:

  • 结构性学习

医学,经济学等等。

  • 自然学习法

英语,炒菜,吉他等等。

  1. #### 没兴趣学怎么办?

成年人学习要带着任务不驱动学习,如果没有任务自己给任务(教别人)。任务也可以是工作。做完事情需要交待,这样学习才会更好!

最好能出教程。

  1. #### 如何克服拖延症

大家都是有拖延症的人。但总会对某些事不拖延。

  • 给自己真正设定一个严格的任务
  • 再给自己一个小目标
  1. #### 改善注意力不集中的问题

我会把注意力一直放在这上面。

  • 学习工具要买贵的!好的工具是好的开始。(让自己有一点心疼点就好了)

许岑一开始学吉他时,花了4.6w买了一把吉他。用20元/张的纸来练书法。

许岑告诫:买其他东西也不要贪便宜。这会浪费更多的精力。分散注意力。

  • 吃饭:吃好的!少吃自助餐。

  • 工作学习的地方,最好可以使用射灯,能更加专注。

  • 睡眠充足。

运动会让人睡得好。先累坏自己,就很好睡。

  1. #### 如何找学习的切入点
  • 直奔大师级别 (快速),就是你的目标。
  • 要去到哪里,就从哪里开始。
  • 一个牛逼老师的重要性。
  1. #### 制造反馈

反馈的重要性:

  • 自己制造反馈:写点东西,录点音频,录像。总之要有输出。

作者举例:录像才发现自己丑态百出。录音后才发现自己差得好远。

  1. #### 遇到瓶颈,如何解决

通常遇到瓶颈,大多是心态问题,这时要跟自已说:你已经走得很远了。

  • 在方法上调整,可以提升。
  • 心态问题。

《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"

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

《ruby基础教程》 第三部分 提取

第三部分

数值类
  1. 一些方法:
  • 返回商的整数:x.quo(y)

  • 返回一个数组,[商,余数]:x.divmod(y)

  • Math模块 你想得到的数学函数都在里面

  • 返回整数部分:.floor

  • 返回100以内的随机数:Random.rand(100) (比我以前学的VB方便太多了好嘛!)

  • Integer的迭代方法:

    • upto遍历
    • downto遍历
  • 以分数的形式存在:Rational(1, 10)

  • step迭代的方法:

    • 2到10,每次加3:


    2.step(10,3) do |i|
    p i
    end #=> 2, 5, 8

    • 10到2,每次减3:


    10.step(2,-3) do |i|
    p i
    end #=> 10, 7, 4

数组:
  1. Array.new方法


Array.new #=> []
Array.new(5) #=> [nil,nil,nil,nil,nil]
Array.new(5,0) #=> [0,0,0,0,0]

  1. %w%i
  • 创建不包含空白的字符串数组时,可以使用%w:


    lang = %w(Ruyb Perl Python Scheme Pike)
    p lang #=>["Ruby","Perl","Python","Scheme","Pike"]

  • 创建不包含空白的符号数组时,可以使用%i:


    lang = %w(Ruyb Perl Python Scheme Pike)
    p lang #=>[:Ruby,:Perl,:Python,:Scheme,:Pike]

其中()可替换成其他符号:<> || !! @@ AA

  1. 将散列转换成数组:


a = {a: 1, b: 2}
a.to_a #=> [[:a, 1], [:b, 2]]

  1. 一次性拿到多个值:
  • a[n] or a[-n] 如果是负数,就倒着开始取值。超过总数值会报错。

    • a[n..m] or a[n…m]
    • a[n,len] 从某个元素起,取n个字符。
  1. 一次性赋多个值:

  • a = [1,2,3,4,5,6]
    a[2,3] = [c,d,e] #=> [1,2,c,d,e,6]
    a[2,0] = [a,b] #=> [1,2,a,b,3,4,5,6]


  • a = [1,2,3,4,5,6]
    a[2..3] = [a,b] #=> [1,2,a,b,5,6]

  • .values_at方法


    a = [1,2,3,4,5,6]
    a.values_at(1,3,5) #=> [2,4,6]

  1. 把数组当集合来运算:
  • 交集:ary = ary1 & ary2

  • 并集:ary = ary1 | ary2

  • 集合的差:ary = ary1 - ary2 (在1中找2没有的元素)


    ary1 = ["a","b","c"]
    ary2 = ["b","c","d"]
    p (ary1 - ary2) #=> ["a"]

  1. 把数组当

数组结构:

  • 队列:以排列的顺序,先进先出。(像排队过关一样)
  • 栈:以相反的顺序,先进后出。(像堆东西一样,最慢放入的最容易取出)
  • 追加,删除,引用:

| 操作 | 在头部开刀 | 在尾部开刀 |
| :--: | :-----: | :---: |
| 追加元素 | unshift | push |
| 删除元素 | shift | pop |
| 引用元素 | first | last |

例子:


a = [1,2,3,4,5]
a.push("E") #=> [1,2,3,4,5,E]
a.shift #=> 1
a #=> [2,3,4,5,E]

一些操作方法:
  1. 为数组增加元素:
  • a.unshift(item)

  • a.push(item) ~= a << item

  • a.concat(b) & a+b

    这个concat方法是破坏性方法,会改变被引用的对象。

什么是破坏性的方法

:会改变接收对象值的方法!要注意:被引用的对象也会被破坏!如:


a = [1,2,3,4]
b = a
b.pop #=> 4
p a #=> [1,2,3]

提示:这里b引用了a,并不是复制一个a,而是让a和b同时引用一个对象!这一点要纠正认识!

  1. 从数组中删除元素:
  • a.compact & a.compact! :会把a数组中的空元素nil去掉!区别:
    • 第一种会返回一个新的数组。
    • 第二种会直接替换掉原来的数组。
  • a.delete(x) :从数组中删除x元素。
  • a.delete_at(n) : 从数组中删除a[n]元素。
  • a.delete_if {|item| … } a.reject {|item| … } a.reject! {|item| … } : 这三个方法表示:遍历所有元素,如何右边的block块成立就删掉,其中带感叹号表示破坏性的方法!
  • a.slice!(n) a.slice!(n..m) a.slice!K(n,len) : 这三个方法表示:从数组a中删除指定的部分,并返回被删除的部分的值。slice!是具有破坏性的方法。
  • a.uniq & a.uniq! :表示:去掉重复的元素。
  • a.shift :删除开头的元素。返回删除的值。
  • a.pop :删除末尾的元素。返回删除的值。
  1. 替换数组元素:
  • a.collect{|item| … }
    a.collect!{|item| … }
    a.map{|item| … }
    a.map!{|item| … }
    : 遍历数组a和各元素传给block中的item,最后付出处理后的结果。

  • a.fill(value)
    a.fill(value, begin)
    a.fill(value, begin, len)
    a.fill(value, n..m)
    : 把数组指定元素全部替换成value,默认为全部。

  • a.flatten & a.flatten! :平坦化数组a,就是把里面嵌套的数组展开成一个大数组。(soga,原来是这样啊!)


    a = [1,[2,[3]],[4],5]
    a.flatten!
    p a #=> [1,2,3,4,5]

  • a.reverse & a.reverse! :反转数组a的元素顺序。

  • a.sort
    a.sort!
    a.sort {|i,j| … }
    a.sort {|i,j| … }
    :排序,其中block中的i & j 表示:从数组中两个两个拿出来的数据。

  • a.sort_by{|i| … } :根据块的运行结果时序排序。


    a = [2,4,3,5,1]
    p a.sort_by{|i| -i } #=> [5,4,3,2,1] (这里的block只是参与计算而已,不会破坏原来的数组。)

  1. 数组与迭代器:有些使用迭代器的对象不是数组,但处理后会返回一个数组:


a = 1..5
b = a.collect{|i| i += 2}
p b #=> [3,4,5,6,7]

  1. 数组的初始化问题:


a = Array.new(3, [0,0,0]) #=> [[0,0,0],[0,0,0],[0,0,0]]
a[0][1] = 2 #=> [[0,2,0],[0,2,0],[0,2,0]]
数组的初始化就是有这些问题,要注意,可以使用block带解决:
a = Array.new(3) {[0,0,0])} #=> [[0,0,0],[0,0,0],[0,0,0]]
a[0][1] = 2 #=> [[0,2,0],[0,0,0],[0,0,0]]

  1. zip方法 :引进多个数组,然后同步遍历!:

```
ary1 = [1,2,3,4,5]
ary2 = [10,20,30,40,50]
ary3 = [100,200,300,400,500]

result = []
ary1.zip(ary2,ary3) do |a, b, c|
result << a + b + c
end
p result #=> [111, 222, 333, 444, 555]
```

  1. 一个过滤数组的方法:.select


a = (1..100).to_a
a.select{|i| i % 3 == 0} 过滤3的倍数。

13章练习题:
  1. 创建一个1到100的整数按升序排列的数组:


(1..100).to_a

  1. 累加1中的数组:


a.reduce :+

  1. inject方法


a.inject(0){|memo, i| memo += i}
表示:遍历数组元素赋值给i,每次结果回传给memo.实现累加的效果。

字符串String
  1. 什么是内嵌表达式?


"String#{ruby}" #=> 这个#{}里可以执行ruby表达式的东西就是啦!

  1. 创建字符串
  • 使用%Q与%q

    ```
    desc = %Q{Ruby 的字符中也可以使用'' 和 "".}
    str = %q|Ruby said, 'Hello world!'|

    其中%Q相当于"",%q相当于''。
    ```

  • Here Document方法

    ```
    <<"结束标识"

    内容
    结束标识

    #=> (这里更多的时候要换成<<-"结束标识",这样能让结束的标识不一定在)

    这个方法应该很少用。
    ```

  • sprintf方法 & printf方法

    • printf


    n = 123
    printf("%d\n", n) #=> %d表示以整数形式输出
    printf("%4d\n", n) #=> %4d表示以4位数格式输出
    printf("%04d\n", n) #=> %04d表示不够4位时被零
    printf("%+d\n", n) #=> %+d表示输出结果带 + or -


    n = "Ruby"
    printf("Hello,%s!\n",n) #=> %s表示以字符串形式输出
    printf("Hello,%8s!\n",n) #=> %8s表示输出炎8位字符串
    printf("Hello,%-8s!\n",n) #=> %-8s表示输出左对齐的8位字符

    • sprintf

    printf一样的结果。但书中没有说明具体区别,差评!

  1. 获取字符串的长度
  • length or size两种方法可以,随意!
  • bytesize可以获取字节数。
  • 判断是否为0:empty?
  1. 字符串的索引
  • 当成数组一样用就可以了。
  1. 连接字符串
  • +


    a = "Hello,"
    b = "World!"
    a + b #=> "Hello,World!"

  • <<


    a = "Hello,"
    b = "World!"
    a << b #=> "Hello,World!" (a会被改变!)

  1. 字符串的比较
  • 判断两个字符串是否相同:== !=

  • 比较大小


    "aaaaa" < "b" #=> true 一般按照a~z的顺序排序

  • 如果查看码位 : .ord

  1. 字符串的分割:split(x) :以x为分割点进行分割!

  2. 换行符的操作:

  • 删掉

    | 属性 | 删掉最的一个字符 | 删掉挑选符 |
    | :---: | :------: | :----: |
    | 非破坏性的 | chop | chomp |
    | 破坏性的 | chop! | chomp! |


    a = "abcde"
    b = "abcde\n"
    a.chop #=> "abcd"
    b.chop #=> "abcde"
    b.chomp #=> "abcde"

  1. 字符串的检索与置换
  • 字符串的检索

    • index方法 从左到右检索,返回第一个字母的索引
    • rindex方法 从右到左检索,返回第一字母的索引


    a = "abbbbbb"
    a.index("bb") #=> 1
    a.rindex("bb") #=> 5

  • 判断是否包含某个索引值: include?


    a = "abbbbbb"
    a.include?("bb") #=> true

  1. 字符串与数组有很多共同的索引的方法:
  • s[n] s[n..m] s.slice!(n)
  • s.concat(s2) s+s2 s.delete(str) s.reverse
  1. 其他方法:
  • strip : 删除头尾的空白字符

  • upcase :小写转大写

  • downcase :大写转小写

  • swapcase :大的转小,小的转大

  • capitalize :首字母大写,其余转小写

  • tr :置换字符,与gsub相似,但这里可以一次转换多个字符。


    "ABCDE".tr("BD", "bd") #=> "AbCdE"

14章节练习题提取:
  1. 一个打散的字符串数组,如何快速连接成句子:


a = ["Ruby", "is", "an", "object", "oriented", "programming", "language"]
a.join(" ")
#=> "Ruby is an object oriented programming language"

  1. 统计下面各个字母出现次数,并用*的个数来表示次数:


a = "Ruby is an object oriented programming language"
b = Hash.new(0)
a.each_char do |c|
b[c] += 1
end
b.sort.each do |k, v|
printf("'%s': %s\n", c, "*" * v.to_i)
end

运行结果如图:

很酷!

  • 散列直接用a[c]就可以拿到这个值,不需要进行检索
  • printf格式输出,方便!先定义好整个字符串样式,再后面定义各个引用值。

散列类
  1. 新建hash:
  • a = {} a = {键: 值}
  • a = Hash.new(x) (这里可以设置一个默认值)。
  1. 值的获取与设定
  • fetch & store 一个用来取一个用来存,基本和a["s"]的作法一样,但:

    用下面两个方法可以

    • 设默认值
    • 添加block


    a.store("s1", "Ruby")
    a.fetch("s2", "undef") #=> "undef" (找不到,所以默认值)
    a.fetch("s2"){String.new} #=> "" (这里还可以用block)

  1. hash的迭代器。

| 数组形式 | 迭代器形式 |
| :----: | :-------------------: |
| keys | each_key{|键| …… } |
| Values | each_value{|值| …… } |
| to_a | each{|键,值| …… } |
| | each{|数组| …… } |

  1. hash的默认值。好处:取不存在的值时,不至于发生错误
  • 创建hash时指定默认值:


    h = Hash.new(1)
    h["a"] = 10
    h["a"] #=> 10
    h["b"] #=> 1

  • 创建hash时增加一个block:


    h = Hash.new do |hash,key|
    hash[key] = key.upcase
    end
    h ["a"] = "b"
    p h["a"] #=> "b"
    p h["b"] #=> "B"
    p h["c"] #=> "C" 当找不到这个值时,就会自动去执行这个block

  • 用fetch方法指定默认值:


    h = Hash.new do |hash.key|
    hash[key] = key.upcase
    end
    p h.fetch("x", "(undef)") #=> "(undef)"
    同时两个默认值的方法,fetch的优级级会高

  1. 判断是否为某个Hash的 or
    • h.key?(key)
    • h.has_key?(key)
    • h.include?(key)
    • h.member?(key)
    • h.value?(value)
    • h.has_value?(value)
  1. 查看Hash的大小
  • h.size & h.length


    h = {"a" => "b", "c" => "d"}
    h.size #=> 2

  • h.empty?

  1. 删除值:
  • h.delete(key)

  • h.delete_if{|key, val| … }

  • h.reject!{|key, val| … }

    当不符合时,delete_if会返回原来的Hash,reject!会返回一个nil

  1. 初始化散列
  • h.clear 清空使用过的散列

    那这个东西和h = Hash.new 有什么区别呢?在引用的时候要注意下,请看图:

  • 我忘记了一个超级方便的排序方法:sort
    PS:比起之学VB时的什么泡沫排序法之类的,这里简直不能太好!

正在表达式(Regexp类)
  1. 正则表达式的创建:


re = Regexp.new("Ruby") #=> /Ruby/

  • %r(模式)
  • %r<模式>
  • %r|模式|
  • %r!模式!
  1. 判断某个字符串是否匹配:正则表达式 =~ 字符串
  • 匹配:返回匹配的起始位置

  • 不匹配:返回nil

    还可以:用!~来颠倒结果。

  • 进一步应用:


    if 正则表达式 =~ 字符串
    匹配时的处理
    else
    不匹配时的处理
    end

  1. 匹配行首与行尾:

^, $分别表示匹配行首与行尾:

像这样的特殊字符称为:元字符。

\A,\z分别表示匹配字符串头与尾:

\z \Z 两者的区别


p "abc\n".gsub(/\z/, "!") => "abc\n!"
p "abc\n".gsub(/\Z/, "!") => "abc!\n!"

:大写的\Z,如果最后一个是换行符,则会同时匹配换行符与前一个字符。

  1. 匹配某一个字符:
  • [AB] :匹配A 或B
  • [ABC] :匹配A,B,C中的一个
  • [012ABC] :匹配0,1,2,A,B,C中的一个
  • [A-Z] :匹配A~Z中的一个
  • [ABC][BC] : 匹配一个两位数,第一个数为A,B,C中的一个,第二位数为B,C中的一个。
  • [A_-] :匹配A _ - 中的一个。
  • [^ABC] :匹配除A,B,C外的任意一位数。
  • . :万能通配符,相当于五笔中的z
  1. 反斜杠模式
  • \s : 匹配空格,制表符, 换行符,换页符

  • \d :匹配0到9的数字。

  • \w :匹配英文字与数字。

  • \A :匹配字符串的开头。

  • \z :匹配字符串的末尾。

  • \ :后面如果跟着:^ [ 等字符时,可以为这些字符转义再匹配。如:

    | 模式 | 字符串 | 匹配部分 |
    | ------- | --------- | ------------ |
    | /ABC[/ | "ABC[" | "▶ ABC[ ◀" |
    | /^ABC/ | "ABC" | (不匹配) |
    | /^ABC/ | "012^ABC" | "012▶^ABC ◀" |

  1. 重复模式:(单个字符)
  • * :重复0次以上。可以重复很多次也可以直接没有
  • + :重复1次以上。至少有一次以上
  • ? :重复0次或1次。可有可无不重复,前后的匹配还是要的!
  1. 最短匹配:
  • 上面6中和重复匹配会最可能匹配更多的字符。

  • 最短匹配:加上一个? ,在第一次匹配时就停止,匹配最短的字符。

    也叫贪婪匹配与懒惰匹配。

  1. 使用()来重复匹配多个字符。

| 模式 | 字符串 | 匹配部分 |
| ---------- | ---------- | ----------- |
| /^(ABC)$/ | "ABC" | "▶ABC ◀" |
| /^(ABC)
$/ | "" | "▶◀" |
| /^(ABC)$/ | "ABCABC" | "▶ABCABC ◀" |
| /^(ABC)
$/ | "ABCABCAB" | (不匹配) |

  1. 使用|来多一个匹配选项,可以多选一,也可以全中。

| 模式 | 字符串 | 匹配部分 |
| -------------- | ----- | -------- |
| /^(ABC|DEF)$/ | "ABC" | "▶ABC ◀" |
| /^(ABC|DEF)$/ | "DEF" | "▶DEF ◀" |
| /^(ABC|DEF)$/ | "AB" | (不匹配) |

  1. 使用quote方法来转义所有表达式所有字符:
 re1 = Regexp.new("abc*def")
 re2 = Regexp.new(Regexp.quote("abc*def")) p (re1 =~ "abc*def") #=> nil
 p (re2 =~ "abc*def") #=> 0
  1. 正则表达式的一些选项:

形式:/ … / im

  • i :忽略英文有大小写
  • x :忽略正则表达式中的空白字符以及 # 后面的字符的选项。指定这个选项后,我们就可以使用 # 在正则表达式中写注释了。
  • m :使用后可以:用.匹配换行符了。
  1. 捕获:
  • 查看匹配的部分是什么字符串:以()的形式


    /(.)(.)(.)/ =~ "abc" first = $1
    second = $2
    third = $3
    p first #=> "a"
    p second #=> "b"
    p third #=> "c"

  • 如果是有重复的匹配只会捕获最后一次的字符,如果此时我们想忽略它的捕获,可以这样:以(?:)开头就可以忽略捕获。


    /(.)(\d\d)+(.)/ =~ "123456"
    p$1 #=> "1"
    p$2 #=> "45"
    p$3 #=> "6"
    /(.)(?:\d\d)+(.)/ =~ "123456"
    p $1 #=> "1"
    p $2 #=> "6"

  • 另外一种形式:


    /C./ =~ "ABCDEF"
    p $` #=> "AB"
    p $& #=> "CD"
    p $' #=> "EF"
    这三种可以捕获整个匹配过程的字符

  1. sub方法与gsub方法:带block


str = "abracatabra"
nstr = str.sub(/.a/) do |matched|
'<'+matched.upcase+'>'
end
p nstr #=> "ab<RA>catabra"
nstr = str.gsub(/.a/) do |matched|
'<'+matched.upcase+'>'
end
p nstr #=> "ab<RA><CA><TA>b<RA>"

可以带block,然后把匹配的部分传入block,最后block处理后的结果置换匹配的字符串。

  1. 直接返回的字符串数组:scan方法


p "abracatabra".scan(/.a/) #=> ["ra", "ca", "ta", "ra"]

这是纠正一点:转义符号增加的顺序:


/http:\/\// =~ "http://baidu.com"
$& #=> "http://"

纠正:\+要转义的符号 这才是正确的格式。

章节:IO类

这一章节主要对文件内容的操作。暂时不会用到,理解起来也费劲。

章节:File类与Dir类

这一章节主要是对文件夹的操作。暂时不会用到,记起来也很费劲。

章节:Encoding类

这一章节主要是编码类的讲解。暂时不会用到,要用到时再来查看。

第20章 Time类与Date类
  1. 获取当前时间:
  • Time.new
  • Time.now

时间相关的方法:

| 方法名 | 意义 |
| ----- | ------------------------ |
| year | 年 |
| month | 月 |
| day | 日 |
| hour | 时 |
| min | 分 |
| sec | 秒 |
| usec | 秒以下的位数(以毫秒为单位) |
| to_i | 从 1970 年 1 月 1 日到当前时间的秒数 |
| wday | 一周中的第几天(0 表示星期天) |
| mday | 一个月中的第几天(与 day 方法一样) |
| yday | 一年中的第几天(1 表示 1 月 1 日) |
| zone | 时区(JST 等) |

  1. 指定特定时间:


t = Time.mktime(2017,7,25,16.59,40)
#=> 2017-07-25 16:40:00 +0800

  1. 时间的比较:
  • < > - 等,平常的计算比较方法都可以用啦。
  1. 时间的计算:默认直接计算秒数:


t = Time.now
p t #=> 2013-03-30 03:11:44 +0900 t2=t+60*60*24 #=>增加24小时的秒数
p t2 #=> 2013-03-31 03:11:44 +0900

  1. 时间输出的格式:
  • t.strftime(format)
  • t.to_s

| 格式 | 意义与范围 |
| ---- | -------------------------------- |
| %A | 星期的名称(Sunday 、 Monday ......) |
| %a | 星期的缩写名称(Sun 、 Mon ......) |
| %B | 月份的名称(January 、 February ......) |
| %b | 月份的缩写(Jan 、 Feb ......) |
| %c | 日期与时间 |
| %d | 日(01 ~ 31) |
| %H | 24 小时制(00 ~ 23) |
| %I | 12 小时制(01 ~ 12) |
| %j | 一年中的天(001 ~ 366) |
| %M | 分(00 ~ 59) |
| %m | 表示月的数字(01 ~ 12) |
| %p | 上午或下午(AM、PM) |
| %S | 秒(00 ~ 60) |
| %U | 表示周的数字。以星期天为一周的开始(00 ~ 53) |
| %W | 表示周的数字。以星期一为一周的开始(00 ~ 53) |
| %w | 表示星期的数字。0 表示星期天(0 ~ 6) |
| %X | 时间 |
| %x | 日期 |
| %Y | 表示西历的数字 |
| %y | 西历的后两位(00 ~ 99) |
| %Z | 时区( JST 等) |
| %z | 时区(+0900 等) |
| %% | 原封不动地输出 % |

示例:

Ruby
t = Time.now.strftime("%Y-%m-%d")
#=> "2017-7-25"

###### 另外两个冷门的格式:(需要引用time类)

  • t.rfc2822
  • t.iso8601f


require "time"
t = Time.now
p t.rfc2822 #=> "Tue, 25 Jul 2017 17:19:00 +0800"
p t.iso8601 #=> "2017-07-25T17:19:42+08:00"

  1. 本地时间与国际时间的切换:
  • t.utc
  • t.localtime


t = Time.now
p t #=> 2017-07-25 17:21:35 +0800
t.utc #=> 2017-07-25 09:21:35 UTC
t.localtime #=> 2017-07-25 17:21:35 +0800

  1. 从字符串中获取时间:
  • Time.parse(str)

    ```
    require "time"

    p Time.parse("2017-7-25") #=> 2017-07-25 00:00:00 +0800
    ```

  1. 日期的获取:Date类,适合只需日期不需时间的操作。
  • Date.today

  • 计算:直接加减默认计算天数

  • 同样也有各种方法:


    d = Date.today
    p d.year pd.month pd.day
    p d.wday p d.mday pd.yday
    # 年 => 2017
    #月 => 7
    #日 => 25
    # 一周中的第几天(0 表示星期天)
    # 一个月中的第几天(与 day 方法一样) => 30 #一年中的第几天(1表示 1月 1日) =>206

  • 用指定日期生成Date对象:


    d = Date.new(2017,7,25)
    puts d #=> 2017-7-25

    还可以用-1 -2 表示末尾的第几天:


    d = Date.new(2017,7,-1) #=> 2017-7-31 7月月尾
    d = Date.new(2017,7,-2) #=> 2017-7-30 7月月尾前一天

  1. Date类的运算:
  • 正常的加减,默认会以天数为单位计算
  • >> << 会以月份为单位进行运算。
  1. Date类的格式:
  • 与前面的Time类一致。
  1. 从字符串中获取Date类:
    • 和Time类一致: Date.parse(str)
Proc类
  1. 什么是proc类,

把block块变成一个对象,这个对象所属的类就是Proc类。这样可以方便我们在后面的调用。

  1. 创建方法:
  • Proc.new(…)
  • proc{}
  1. 调用方法:
  • 利用Proc#call方法

  • 利用Proc[]方法

    ```
    sayhello = Proc.new do |name|
    puts "Hello, #{name}."
    end

    sayhello.call("World") #=> Hello, World.
    sayhello["World"] #=> Hello, World.
    ```

  1. 另一种写法:lambda

lambda 还有另外一种写法:-> (块变量){处理}

```
prc1 = Proc.new do |a, b, c|
p [a, b, c]
end
prc1.call(1,2) #=> [1, 2, nil]

prc2 = lambda do |a, b, c|
p [a, b, c]
end
prc2.call(1,2) #=> 错误 (ArgumentError)
```

lambda proc 两者的区别:

  • lambda的参数数量要对应否则会出错,proc的参数数量则没有那么严格。
  • lambda可以使用return将值从块中返回。
  1. proc类可以接收块

  2. proc的特征:

具有闭包的特征。 闭包:将处理内容、变量等环境同时进行保存的对象,在编程语言中称为闭包(closure)。

  1. proc的实例方法:
  • prc.call(args,…)
  • prc[args, … ]
  • prc.yield(args, … )
  • prc.(args, … )
  • prc === arg


prc = Proc.new{|a, b| a + b}
p prc.call(1, 2) #=> 3
p prc[3, 4] #=> 7
p prc.yield(5, 6) #=> 11
p prc.(7, 8) #=> 15
p prc === [9, 10] #=> 19

Ruby
fizz = proc{|n| n % 3 == 0 }
buzz = proc{|n| n % 5 == 0 }
fizzbuzz = proc{|n| n % 3 == 0 && n % 5 == 0}
(1..100).each do |i|
case i
when fizzbuzz then puts "Fizz Buzz"
when fizz then puts "Fizz"
when buzz then puts "Buzz"
else
puts i
end
end

  • prc.arity : 返回块变量的个数。
  • prc.parameters
  • prc.lambda? :判断是否通过lambda定义的方法。
  • prc.source_location: 返回值为[代码文件名,行编号]

《ruby基础教程》第二部分 提取

第2部分 Ruby的基础:
  1. 什么是对象:在RUBY中, 靓丽数据的基本单位称为对象。
  • 数值对象
  • 字符串对象
  • 数组对象,散列对象
  • 正则表达式对象
  • 时间对象
  • 文件对象
  • 符号对象
  1. 什么是类:表示对象的种类。

  1. 什么是伪变量:代表某特定值的特殊量。
  • nil true false self
  1. 常量:
  • 常量以大写英文字母开头。
  • 给常量重复定义时,ruby会做出警告。
  1. 多重赋值里还可以这样用:


ary = [1,2]
a,b = ary #=> a=1, b=2

  1. 比较值是否相等时,通常用== ,但如果要严谨一点的,就用eql?

  2. 一些控制循环的语句:

  1. 语法糖(syntaxsugar),是指一种为了照顾一般人习惯而产生的特殊语法
方法:
  1. 什么是方法:把一系统参数传递给对象的过程。(对象会返回结果值)

  2. 方法的调用:


对象.方法名(参数1, 参数2, ..., 参数n)

  1. 方法的分类:
  • 实例方法:作用在实例上的方法。

  • 类方法:作用在类上的方法。调用:


    类名.方法名


    类名::方法名

  • 函数式方法:没有作用对象的方法,也没有回传值。

  1. 参数个数不确定的方法,使用带*号的参数:

这个参数组会被封装为数组供内部使用。

```
def foo(*args)
args
end

p foo(1,2,3) #=> [1,2,3]
p foo(1,2) #=> [1,2]
```

至少需要一个参数的方法:

```
def meth(arg, *args)
[arg, args]
end

p meth(1) #=> [1, []]
p meth(1,2,3) #=> [1, [2,3]]
```

首尾确定,中间不确定时:

```
def a(a, *b, c)
[a,b,c]
end

p a(1,2,3,4,5) #=> [1, [2,3,4], 5]
p a(1,2) #=> [1, [], 2]
```

  1. 关键字参数:以hash形式传递参数:


def a(a: 1, b: 2, c: 3)
[a,b,c]
end

如果想传递未定义的参数:

```
def a(a:1, **args)
[a, args]
end

p a(a: 2, k: 3, v: 4) #=> [2, {:k => 3, :v => 4}]
```

类与模块:
  1. 判断某个对象是否属于某个类时:instance_of?


ary = []
str = "Hello world."
p ary.instance_of?(Array) #=> true
p str.instance_of?(String) #=> true
p ary.instance_of?(String) #=> false
p ary.instance_of?(Array) #=> false

  1. 判断某个对象是否属于某个类时:is_a?


str = "This is a String."
p str.is_a?(String) #=> true
p str.is_a?(Object) #=> true

  1. 常量:


class HelloWorld
Version = "1.0"
end

调用时:


p HelloWorld::Version #=> "1.0"

  1. @@xxx:类变量,可以多次修改。

  2. 限制访问级别:

  • public 公开,外部可以访问。一般默认方法都是public的,但initialize方法除外。
  • private内部使用。外部无法访问。
  • protected同一类中可以使用。外部无法使用。
  1. 想知道一个类下有什么方法可以 被调用 ?


类名.instance_methods

  1. 为方法设置多个名字:alias


alias 别名 原名
alias :别名 :原名

  1. 删除方法: undef 方法名 or undef :方法名

  2. 什么是单例类:只为了个实体对象服务的类和方法。

模块:
  1. 使用方法:
  • 模块名.方法名
  • 在类中include 模块名, 然后就可以直接使用方法啦~
  1. 如果希望在外部可以用模块名.方法名,你可以这样做:


module_function :hello

  1. 如果想知道继承关系 ,可以使用:ancestors这个方法,如果想知道父类,可以使用方法:superclass

  2. 调用时的优先级:

  • 类本身 => 引入的模块(最后引入模块的优先) => 父类
  • 重复引入,第二个引入的会被忽略。
  1. extend方法:直接引入作用在对象上的模块。

```
module Edition
def edition(n)
"#{self}第#{n}版"
end
end

str = "Ruby 基础教程"
str.extend(Edition) #=> 将模块 Mix-in 进对象

p str.edtion(4) #=> "Ruby 基础教程第 4 版"
```

  • 也可以用extend来代替继承:


    class MyClass
    extend ClassMethods
    end

  1. .ceil:进位的意思,会把对象变成大一位的整数。


a=3.23
b=5.8
a.ceil #=> 4
b.ceil #=> 6

运算符:
  1. 一直以为||就是or的意思(visual basic中的or),今天才发现,我错了:

其实这两个符号不仅可以用在判断,还可以用在运算!!!

  • || :从左到右,返回第一个不为 faslenil 的值。


a = false || nil || 2 || 3 #=> a = 2

  • && :返回最后一个真值(期间不能出现假值)。


a = 1 && 2 && 3 && 4 #=> a = 4
a = nil && false && 1 #=> a = nil

  • ||= :当为 falsenil 时,才进行赋值。(这可给变量赋予默认值 )


    var ||= 1 #=> 当var为nil或false时,var = 1

  1. 范围运算符succ 可以返回一个:进位到下一位的值。


1.succ #=> 2
a.succ #=> b

  1. 原来ruby本身有优先级运算符:


1 + 2 * 3 #=> 1 + (2 * 3) #=> 7
2 +3 < 5 + 4 #=> (2 + 3) < (5 + 4) #=> true

还有一些没见过的运算符,刚开始还是老实用括号吧,哈哈!

异常处理与错误提示:
  1. 错误提示格式:


文件名:行号:in 方法名:错误信息(异常类名)
form 文件名:行号:in 方法名

example:

```

ruby test.rb
test.rb:2:in initialize':No such file or directory - /no/file(Errno::ENOENT)
form test.rb:2:in
open'
form test.rb:2:in foo'
form test.rb:9:in
main'
```

  1. 可以用这个格式来处理异常:


begin
可能会发生异常的处理
rescue => 引用异常对象的变量
发生异常时的处理
sleep(10) #=> 等待10秒
retry #=> 再执行一次begin下的程式
ensure
不管是否发生异常都希望执行的处理
end

这样不至于遇到错误时立即爆掉!!!

  1. 还可以:简化

```
begin
表达式 1
rescue
表达式 2
end

#=> 等同于下面这个:

表达式1 rescue 表达式2
```

  1. 还可以:再简化
  • 如果该方法是一整个begin ~ end 包含,可以省略begin end,可以直接用rescue ensure
  1. 还可以指定需要捕捉的异常:

  2. 主动抛出异常

raise

书中没有给出例子!差评!

  1. sort方法可以对数组进行排序:

  2. <=>排序运算符,可以进行排序。

  3. 数组里如果要按照长度来排序,可以:


array = ["rails", "ruby", "fullstack"]
array.sort{ |a, b| a.length <=> b.length }
#=> ["ruby", "rails", "fullstack"]

解说:先利用sort让数组遍历两两相比较,再增加代码块!人才啊!

  1. 要想把一段文字,直接切成一个个单词然后放入数组?可以这样:
 ary = %w(This is a example, I love rails)

 #=> ["This", "is", "a", "example,", "I", "love", "rails"]
  1. upto把值按从小到大的顺序取出,这个是Integer#upto方法!

  2. 判断使用方法时是否有带块?


if block_given?

  1. 在块中直接调用break next等方法控制流程,会直接返回nil,如果想返回带参数,可以使用:
  • break 0
  • next 0
  1. 利用Proc把代码块变成对象后,就可以直接用cell方法来直接使用啦~

```
hello = Proc.new do |new|
puts "Hello, #{name}."
end

hello.call("World") #=> Hello, World.
hello.call("Ruby") #=> Hello, Ruby.
```

  1. 传参数的时候 ,有一种参数叫做:Proc参数是这样的:&block (要放在最后一个参数)

  2. 判断是否有带block的另一个方法:


if block #直接进行判断

  1. 块变量的作用域:只在block中有效。如果跟局部变量同名,要小心赋值问题!

《ruby基础教程》第一部分 提取

写在前面:这里的提取是个人的提取(一些本人熟知的知识点不会在这里记录)。如果你和我一样的程度(刚参加完全栈两次大赛),那这个提取能提供不少帮助。

开始:

  1. 程序很多,各自目的不同:有的为了运行快速,有的为了一次编写,可以在从多平台使用,有的为了让小孩也能简单编程。而ruby:为了让编程更快乐!
  2. 什么是脚本语言:不需要经常翻译(编译),电脑能直接运行的语言。

第 1 部分 Ruby 初探

  1. 执行单个ruby文件xxx.rb,方法:
    • ruby helloruby.rb
  2. 直接打开irb:
    • $irb
  3. \n是换行符。
  4. \ :转义字符。
    • 程序会对这个\后的字体做特殊的处理。 print("hello,\"ruby\".) #=> hello, "ruby". print("hello \ ruby!") #=> hello \ ruby!
  5. ""和'',单引号和双引号的区别: ''里的东西,会原封不动地输出!(但,有两个符号除外:' & \)
  6. puts和print方法的区别:
    • puts后面默认会带有换行符。
    • print后面默认不带换行符。
  7. p方法可以做什么:(这个方法一般是程序员在用!)
    • 能从输出结果分辨字符串与数值。
    • 不会转义。
  8. 编码的规则称为 encoding
  9. 用这条指令开启irb可以开启简洁模式: $irb --simple-prompt
  10. 要用sin sqrt等方法时,可以这样:(使用Math这个类方法)

    => irb

    Math.sin(3.1415) #=> 9.26535896604902e-05

  11. 注释:

    • 单行注释:以#开头的注释。
    • 多行注释: =begin 这是一个例子 2017-7-5日 =end
    • 还有一个作用:暂时不执行
  12. while语句:
    i = 1
    while i <= 10
    puts i
    i = i + 1
    end

  13. times语句:(迭代器)
    5.times do
    p "example"
    end

  14. 像数组、散列这样保存对象的对象,我们称为容器(container)。

数组:

  1. 什么是数组: 按顺序保存多个对象的对象。
  2. 数组的大小 :size a = [1,2,3] a.size #=> 3

散列:

  1. 散列是键值对(key-value pair)的一种数据结构。

正则表达式:

  1. 什么正则表达式: /abc/ =~ "eacbcefg"
    • 左边:模式,用/ /包起来。
    • 右边:要匹配的字体串。 /abc/i =~ "aAbcdefg"
    • 在/后面加一个i表示忽略大小写。

从命令中拿到参数:

  1. 使用ruby内置的方法:ARGV[X] 就像数组一样用。
  2. 什么叫库:大部分的编程语言都提供了把多个不同程序组合为一个程序的功能。像这样,被其他程序引用的程序,我们称为库(library)。
  3. require "./grep"中的./表示:当前目录下。
  4. pp方法和p的区别:
    • pp可以适当地换行调整输出结果,让显示更漂亮。

读《为你自己学RUBY ON RAILS--高见龙》提取记录

写在前面:这里的提取是个人提取(只记录不知道的东西)。

电子书地址:http://railsbook.tw/

提示:在学RUBY基础时,有时不要把它和rails混在一起思考。先有ruby才有rails,ruby不了解rails,但rails很了解ruby....
开发工具和常用指令
  1. 如果出门在外,忘了带mac,怎么写代码???答案是可以用三线编辑器:

后两款:自带开发环境!!!!

  1. 常用指令:
  • sudo 暂时取得权限
第一个应用程式
  1. scaffold的作用:自动完成增删改查的功能,包括MVC。

  2. 在新增migration时,数据类型string可以省略(因为是默认)。

  3. N+1查询问题,就是会额外查询关联的资料库,对系统资源很浪费)。解法:

  • includes来减少不必要的资料库查询。


    def index
    @posts = Post.all
    end

    改成:(里是关联了user)


    def index
    @posts = Post.includes(:user)
    end

變數、常數、流程控制、迴圈
  1. 如果想交换x,y两个变数的值,怎么写最ruby???


x, y = y, x

  1. if可以放到程式的后面(这样读起来更像英文文章)


a=1 if b > a #如果b > a,那么a = 1

  1. 三元连算子:(if ….. else …..这个语法放到同一行)

```
if a > b
a = 1
else
a = 2
end

#上面的程式码,可以压缩成一行:
a > b ? a = 1 : a = 2

#帅到掉渣,有木有!
```

數字、字串、陣列、雜湊、符號
  1. 注意= ==的问题。一个是赋值,一个是比较!
  2. 整数的除法,其中一个数为小数,结果才为小数。
  3. 取余数%
    • 判断是否为单数:.odd?
    • 判断是否为复数: .even?
  4. 四舍五入.round(x)
  5. 字串中要插入变量,可以用#{}!!!
  6. 字串可以直接当成数组来用,取字。
  7. 如何获得字串的长度:
    • 长度: .size
    • 字节: .bytesize
  8. 计算几个字几个单词: .sqlite.count
  9. 大小写转换:
    • 转大写: upcase
    • 转小写: downcase
  10. 对比首尾字母:
    • start_with?
    • end_with?
  11. 替换掉其中的片段:
    • 只换第一个: sub
- 全部换掉:`gsub`
​```
  puts "PHP is good, and I love PHP".sub(/PHP/, "Ruby")
  # sub 只會換掉最先遇到的那個字串
  # => 印出「Ruby is good, and I love PHP」

  puts "PHP is good, and I love PHP".gsub(/PHP/, "Ruby")
  # gsub 會換掉全部符合的字串
  # => 印出「Ruby is good, and I love Ruby」
​```
  1. map方法示例:


list = [1, 2, 3, 4, 5]
p list.map { |i| i * 2 - 1 } # [1, 3, 5, 7, 9]

  1. reduce方法示例:(只作数值与index的运算)


puts [*1..100].reduce(:+)

  1. select方法示例:(相当于一个过滤器)


p [*1..100].select(&:odd?)

  1. 取随机数的方法:
  • shuffle洗牌:


    puts [*1..52].shuffle.first(5)

  • sample不重复的乱数:


    puts [*1..100].sample(5)

  1. object_id可以检测是否同一物件。

  2. 什么是symbol一个带有名字的物件(其他物件都没有名字,只有它有)

  3. stringsymbol的对比:

  • string可以被改变,symbol不行
  • symbol可以直接从内存中取得相同的东西,不会像string总是新建物件,所以一定程度上symbol是比较省内存的。
  • symbol的比较会比string快!
  • string和symbol是可以相互转换的!
方法與程式碼區塊(block)
  1. 在 Ruby 執行方法,經常省略小括號,目的是為了讓程式碼看起來更不像程式碼,反而像是一般的文章。

  2. 定义方法时,可以加个预设值:

```
def say_something(message = "something")
"message: #{message}"
end

 p say_something "hi"     # => message: hi
 p say_something          # => message: something

```

  1. ruby很爱省略小括号和大括号,这里日后可能会掉坑~

  2. 什么是程式码区块

{ ... } 以及 do ... end,在 Ruby 稱之一個程式碼區塊(Block)

  1. 程式码区块不是:
  • 不是物件:无法单独存在

  • 不是参数:无法当参数,放在方法后一般不会执行。要如何才能执行:

    ```
    def say_hello
    puts "開始"
    yield # 把控制權暫時讓給 Block
    puts "結束"
    end

    say_hello {
    puts "這裡是 Block"
    }
    ```

    6666,原来这两个地方都可以传参数……:

    ```
    def say_hello
    puts "開始"
    yield 123 # 把控制權暫時讓給 Block,並且傳數字 123 給 Block
    puts "結束"
    end

    say_hello { |x| # 這個 x 是來自 yield 方法
    puts "這裡是 Block,我收到了 #{x}"
    }
    ```

  1. block的物件化:

```
say_hello_to = Proc.new { |name| puts "你好,#{name}"}
say_hello_to.call("尼特羅會長")

 say_hello_to.call("尼特羅會長")    # 使用 call 方法
 say_hello_to.("尼特羅會長")        # 使用小括號(注意,有多一個小數點)
 say_hello_to["尼特羅會長"]         # 使用中括號
 say_hello_to === "尼特羅會長"      # 使用三個等號
 say_hello_to.yield "尼特羅會長"    # 使用 yield 方法

```

類別(Class)與模組(Module)
类别
  1. 什么类别:
物件(object) = 狀態(state) + 行為(behavior)

一个精妙的类比:

不知道大家有沒有在夜市有看過或吃過雞蛋糕,有小貓、小狗等可愛動物造型,只要把調配好的麵粉糊倒進模具,蓋上蓋子,幾分鐘後就會有香噴噴又造型可愛的雞蛋糕可以吃了。

photo by Bryan Liu

那個烤盤模具,就是類別(Class)的概念。如果沒意外,一樣形狀的模具,放一樣的原料進去,做出來雞蛋糕的型狀應該都會長得一樣。而這個做出來的雞蛋糕,以物件導向程式設計的概念來說便稱之「實體(Instance)」。

  1. 传参数初始化:

```
class Cat
def initialize(name, gender)
@name = name
@gender = gender
end

   def say_hello
     puts "hello, my name is #{@name}"
   end
 end

 kitty = Cat.new("kitty", "female")
 kitty.say_hello    # => hello, my name is kitty

```

  1. attr_accessor的作用:

帮助设定和取用实体变数的值!(ruby没有【属性】一说!)

所以,以下这个:

```
class Cat
def initialize(name, gender)
@name = name
@gender = gender
end

   def say_hello
     puts "hello, my name is #{@name}"
   end

   def name
     @name
   end

   def name=(new_name)
     @name = new_name
   end
 end

 kitty = Cat.new("kitty", "female")
 kitty.name = "nancy"
 puts kitty.name        # => nancy

```

可以直接变成:

```
class Cat
attr_accessor :name

   def initialize(name, gender)
     @name = name
     @gender = gender
   end

   def say_hello
     puts "hello, my name is #{@name}"
   end
 end

```

有了这个设定后,我们就可以直接用这个参数,像属性一样方便用!!!

  1. 什么实体方法,什么是类别方法:
  • 作用在实体上的方法,叫做:实体方法。

  • 作用在类别上的方法,叫做:类别方法。这个有几种表现:

    • class Cat def self.all # ... end end

    ```

    • class Cat class << self def all # ... end end end

    有很多类别方法时,可以用这种!

  1. 继承:可以让代码更简洁

  2. open class: 可以方便地为方法“加料”!

  3. 如果想要分开定义一些方法,可以用到模组,方法:


module Flyable
def fly
puts "I can fly!"
end
end


class Dog
include Flyable
end

  1. 模组和类别的不同之处:
  • 模组没办法new一个新的实体出来。
  • 模组没办法继承别的模组。
  1. 定义外部键时,使用references来自动增加外部键:


rails g model post user:references

这样,references会做多两件事:

  1. 自动加上索引,加快查询速度。
  2. 自动帮model加入关联。
Layout,Render 與 View Helper
  1. 在特定的controller 的action里,如何你想用不同的layout可以这样:

```
class CandidatesController < ApplicationController

   def index
     @candidates = Candidate.all
     render layout: "backend"
   end

   # ...[略]...
 end

```

  1. 如果想给每个页面定义网页title,这个好!可以这样做:
  • 方法1:使用 provide:

```
<!DOCTYPE html>


<%= yield :my_title %>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
   <body>
     <div class="container">
     <%= yield %>
     </div>
   </body>
 </html>

```

```
<% provide :my_title, "你好,我是 Title" %>

 <h1>候選人列表</h1>

 <%= link_to "新增候選人", new_candidate_path %>

 <table class="table">
   <thead>
     <tr>
       <td>投票</td>
       <td>候選人姓名</td>
 ...[略]...                                        

```

  • 使用 content_for:

```
<% content_for :my_title do %>
你好,我是 Title
<% end %>

 <h1>候選人列表</h1>

 <%= link_to "新增候選人", new_candidate_path %>

 <table class="table">
   <thead>
     <tr>
       <td>投票</td>
       <td>候選人姓名</td>
 ...[略]...

```

  1. 更好地用render:
  • partial里尽量用区域变量。
  • 使用时再传递这个变量。

例子:


<div class="advertisement">
<div>廣告</div>
<div> <%= content %> </div>
</div>


<%= render "banner", content: "我是廣告的內容" %>

  • partial里有一个collection的方法,使用时传递这个参数,就可以迭代输出partial。(回圈效果)


<%= render partial: "candidate", collection: @candidates %>

这里还可以简化成:


<%= render @candidates %>

但要满足以下条件:(利用了rails的惯例)

  • _xxx.html.erb的名字必须是这包资料的单数
  • 位置必须放在同一目录下。
  • _xxx.html.erb里不要写回圈,里面的区域变数必须是单数。
  1. 善用View Helper !
Model 基本操作
  1. 当我们需要引用外部数据库时,假如我们想改名字,可以这样做:


class User < ActiveRecord::Base
self.table_name = "example"
end

  1. 什么是主键(primary key):每個資料表的流水編號欄位(ID).

当然这个主键也可以自定义

  • class User < ActiveRecord::Base self.primary_key = "user_id" end

```

```

  1. 什么是ORM?

我們如果想要存取資料庫裡的內容,以前必需透過資料庫查詢語言(SQL)向資料庫進行查詢,但透過 ORM 的包裝之後,可以讓我們用操作「物件」的方式來操作資料庫。

  1. 平均数求和最大值最小值 直接让数据库来计算!

```
$ bin/rails console

Candidate.sum(:age)
(0.2ms) SELECT SUM("candidates"."age") FROM "candidates"
=> 44

Candidate.average(:age).to_f
(0.1ms) SELECT AVG("candidates"."age") FROM "candidates"
=> 14.6666666666667
```

```
$ bin/rails console

Candidate.maximum(:age)
(0.2ms) SELECT MAX("candidates"."age") FROM "candidates"
=> 22
Candidate.minimum(:age)
(0.2ms) SELECT MIN("candidates"."age") FROM "candidates"
=> 2
```

  1. 更新数据的方法有 saveupdateupdate_attributeupdate_attributes
  • ```
    # 使用 save 方法
    candidate.name = "剪彩倫"
    candidate.save

    # 使用 update_attribute 方法更新單一欄位的值(注意:方法名字是單數)
    candidate.update_attribute(:name, "剪彩倫")

    # 使用 update 更新資料,可一次更新多個欄位,且不需要再呼叫 save 方法
    candidate.update(name: "剪彩倫", age: 20)

    # 使用 update_attributes 方法
    candidate.update_attributes(name: "剪彩倫", age: 20)
    ```

``
單數的
update_attribute方法會跳過驗證(Validation),等於是save(validate: false)` 的效果,在使用的時候要稍微注意一下。

  • 如果想整个资料表一起修改:

```


Candidate.update_all(name: "剪彩倫", age: 18)

```

```

  1. scope可以嵌套,利用嵌套,可以方便我们:
  • 使代码整洁。
  • 方便我们修改。

还可以:预设scope,


class Product < ActiveRecord::Base
default_scope { order('id DESC') }
scope :available, -> { where(is_available: true) }
end

要取消預設的 scope,必須使用 unscope 方法:

```
$ bin/rails console

Product.unscope(:order)
Product Load (0.2ms) SELECT "products".* FROM "products"

Product.unscope(:order).order(:title)
Product Load (0.3ms) SELECT "products".* FROM "products" ORDER BY "products"."title" ASC

```

這樣才能把預設的 scope 的效果移除。

Model Migration
  1. 什么是migration: 描述数据表长什么样的档案。

  2. 为什么要这样设计:一切为了方便多人协作!

  3. 刚执行rake db:migrate发现要改一下栏位,怎么办?

  • 可以用rollback : rake db:rollback (这样一次可以退回一个migration文件。)
  • 新增migration文件来修改。(推荐!)
  1. 新增migration文件时,原来可以直接上index索引啊,酷!:


$ bin/rails g migration add_candidate_id_to_articles candidate_id:integer:index
Running via Spring preloader in process 7765
invoke active_record
create db/migrate/20170101081538_add_candidate_id_to_articles.rb


class AddCandidateIdToArticles < ActiveRecord::Migration[5.0]
def change
add_column :articles, :candidate_id, :integer
add_index :articles, :candidate_id
end
end

  1. rake db:setup可以一口氣把資料表建完,順便把預設資料寫入。
model的关联性
  1. 什么是外部键:用于对应其他model主键的栏位,例如:user_id product_id

  2. 可以用user.create_post(:title => "hello")来新建对应的资料,生成的资料自动对应好user_id。

  3. 之前商店大赛加油站,无法先新建address再对应user_id,现在终于找到原因和解决方法!:

  • 原因:rails5之后,必须先建立才能建立

  • 解法:加多一个optional: true


    class Store < ApplicationRecord
    belongs_to :user, optional: true
    end

  1. has_one 跟 belongs_to 方法需要同時設定嗎?

不一定,端看需求,一樣以我們上面 User 跟 Store 的例子來看,如果你不需要「從 Store 反查 User」的功能的話,那 belongs_to 是不需要設定的。

  1. 其实多对多没有我们rails101教材上的那么复杂,其实可以这么简单:


class WareHouse < ApplicationRecord
belongs_to :store
belongs_to :product
end

```
class Store < ApplicationRecord
belongs_to :user

   has_many :ware_houses
   has_many :products, through: :ware_houses
 end

```


class Product < ApplicationRecord
has_many :ware_houses
has_many :stores, through: :ware_houses
end

看到没有,第二个has_many只要写两个字段就可以啦!之前是这样的:

有点让新手摸不着头脑。。。

  1. 酷!还有一种叫HABTM(has_and_belongs_to_many)!

```
# Store Model
class Store < ActiveRecord::Base
has_and_belongs_to_many :products
end

 # Product Model
 class Product < ActiveRecord::Base
   has_and_belongs_to_many :stores
 end

```

就這樣,不需要另外新增第三方 Model 即可完成多對多關連。注意,我是說「不需要第三方 Model」,不是「不需要第三方資料表」,畢竟還是要有一個資料表存放雙方的資訊,只是這個資料表因為不重要也不會存取它,所以可以不需要 Model 對應。

這個第三方資料表的名字是有規定的,預設是「兩個資料表依照英文字母先後順序排序,中間以底線分格」,所以以我們這個例子來說,這個資料表的名字就是「products_stores」。

Model 驗證及回呼
  1. 资料验证:有三个方法:
  • 前端驗證:在 HTML 頁面使用 JavaScript 在使用者填寫資料的時候就先檢查。
  • 後端驗證:資料傳進來在寫入資料庫之前之後再檢查。
  • 資料庫驗證:直接由資料庫本身所提供的功能來做資料驗證。

推荐model层来做这件事!

  1. 验证是否为空:
  • class Article < ApplicationRecord validates :title, presence: true end

```


  • class Article < ActiveRecord::Base
    validates_presence_of :title
    end

    这两种效果是一样的。

  1. 想查看错误的提示信息?
  • 检查是否有错误提示:a1.errors.any?
  • 检查错误提示是什么:a1.errors.full_messages
  1. 只有这些方法会触发验证:
  • create
  • create!
  • save
  • save!
  • update
  • update!

其它方法不會經過驗證流程喔

有驚嘆號版本的,如果驗證未通過會產生錯誤訊息,而沒有驚嘆號版本則僅會回傳該 Model 的一個空物件。这大概就是惊叹号的好处了!

  1. 如何跳过验证?


user1 = User.new
user1.save(validate: false)

  1. 检查是否能通过验证?

```

user1.valid?
=> false
```

  1. 回呼(Callback):

其中,顏色比較深的那幾個流程是有機會可以掛上一些方法,又稱之回呼(Callback)

注意:before_savebefore_create 的差別,在於 before_save 是每次存檔的時候都會經過,但 before_create 只有在「新增」的時候才會觸發。

除了這樣的寫法,如果內容單純的話,也是可以使用 Block 的方式來寫:


require 'digest'
class User < ActiveRecord::Base
before_create do
self.email = Digest::MD5.hexdigest(email)
end
end

寄發信件
  1. 之前的奇怪,现在不奇怪了,寄送信件,rails已内置好这个功能了,使用很容易!
背景工作及工作排程(异步任务)

慌然大悟,原来这就是我之前一直想学的异步任务!!!!相见恨晚!

如果我要写一个异步任务,可以这样做:

  1. 执行rails g job user_confirm_email

  2. 设置 app/jobs :

```
class UserConfirmEmailJob < ApplicationJob
queue_as :default

   def perform(user)
     # 在這裡寄發確認信...
   end
 end

```

  1. 在controller里作用:


def create
@user = User.new(user_params)
if @user.save
UserConfirmEmailJob.perform_later(@user)
redirect_to @user, notice: 'User was successfully created.'
else
render :new
end
end

搞定!

一些小技巧:

  • 其中,queue_as 方法是指這件工作急不急,預設值是 :default,如果這件工作不急,可把 :default 改成 :low_priority,如果是急件則可設定成 :urgent

  • ```

    這樣是 5 秒之後做

    UserConfirmEmailJob.set(wait: 5.seconds).perform_later(@user)

    這樣是「明天下午有空再做」

    UserConfirmEmailJob.set(wait_until: Date.tomorrow.noon).perform_later(@user)
    ```

  • 通常异步任务会被放在缓存中,如果宕机,重开机这些资料会不见。安全起见,可以用第三方gem:(把任务放入资料库)常用:有 SidekiqDelayed Job

下面以Delayed Job为例:

  • Gemfile.rb 中增加:


    gem 'delayed_job_active_record'


  • $bundle

  • 修改:app/configs/application.rb

API 模式
  1. 直接用render json: @users会输出所有的json数据。如果想过滤一些数据的话,可以这样:
  • 新增app/views/users/index.json.jbuilder:


    json.array! @users, partial: 'users/user', as: :user

  • 新增app/views/users/_user.json.jbuilder:


    json.extract! user, :id, :name, :email, :created_at, :updated_at
    json.url user_url(user, format: :json)

  1. 有一个叫API-Only 的东西,在新增专案时,选择这种模式,可以帮rails减肥!


$ rails new my_blog --api

寫測試讓你更有信心 Part 1
  1. 为什么要写测试:

前期看起来有点花功能 ,但却是为后面多次开发的测试节省时间!

不写测试可能还有各种偷懒的理由,但,跟着大神学着写吧~

不要為了測試而測試,你寫的是「規格」(Spec)

  1. 开发前写一写“测试“,这里作者说是规格
下面以RSpec来实作:
  1. 安装RSpec:


$ gem install rspec

  1. 把要有哪些项目要测试,先写出来:

```
# 檔案:bank_account_spec.rb

 RSpec.describe BankAccount do
   describe "存錢功能" do
     it "原本帳戶有 10 元,存入 5 元之後,帳戶餘額變 15 元" do
     end

     it "原本帳戶有 10 元,存入 -5 元之後,帳戶餘額還是 10 元(不能存入小於等於零的金額)" do
     end
   end

   describe "領錢功能" do
     it "原本帳戶有 10 元,領出 5 元之後,帳戶餘額變 5 元" do
     end

     it "原本帳戶有 10 元,試圖領出 20 元,帳戶餘額還是 10 元,但無法領出(餘額不足)" do
     end

     it "原本帳戶有 10 元,領出 -5 元之後,帳戶餘額還是 10 元(不能領出小於或等於零的金額)" do
     end
   end
 end

```

然后开始写内容:

  1. 然后,开始$ rspec bank_account_spec.rb进行测试,自然会出错。

  2. 根据错误提示,把所有bug都解掉!

  3. 一些小技巧:

  • before(:each)
  • before(:all)
  • let(:account){BankAccount.new(10)}这个语句相当于动态地为整个方法提供一个区块变数。
  1. 寫測試是算是業界很常見的標準技能,所以:写吧!
代码重构:
  1. 页面上的逻辑可以这样处理:
    • 写进viewhelper
    • 在model里写义一个实体方法,然后在页面就只可以直接用啦!
  2. 善用继承,不能继承的就引入模组功能。
代码重构(进阶版):
  1. 設計 Service Object 類別,新增纯ruby的类别。
  2. 使用 Form Object

表示暂时看不太懂。。。先放过。。

后面的内容实操一遍,基本没有什么重要的知识了……。。

当然这个课程还有一些不错的内容在更新中……