From 80884a1d44b2fc8d022854ef5cbafa7b14ca2488 Mon Sep 17 00:00:00 2001 From: "han.bing" Date: Mon, 28 Jul 2014 14:02:02 +0800 Subject: [PATCH 001/133] begin to translate "Active Support Core Extensions" --- .../zh-CN/active_support_core_extensions.md | 385 +++++++++--------- 1 file changed, 195 insertions(+), 190 deletions(-) diff --git a/source/zh-CN/active_support_core_extensions.md b/source/zh-CN/active_support_core_extensions.md index 4f37bf9..41b5129 100644 --- a/source/zh-CN/active_support_core_extensions.md +++ b/source/zh-CN/active_support_core_extensions.md @@ -1,105 +1,105 @@ -Active Support Core Extensions +Active Support 核心扩展 ============================== -Active Support is the Ruby on Rails component responsible for providing Ruby language extensions, utilities, and other transversal stuff. +Active Support 作为 Ruby on Rails 的一个组件,可以用来添加 Ruby 语言扩展、工具集以及其他这类事物。 -It offers a richer bottom-line at the language level, targeted both at the development of Rails applications, and at the development of Ruby on Rails itself. +它从语言的层面上进行了强化,既可起效于一般 Rails 程序开发,又能增强 Ruby on Rails 框架自身。 -After reading this guide, you will know: +读完本文,你将学到: -* What Core Extensions are. -* How to load all extensions. -* How to cherry-pick just the extensions you want. -* What extensions Active Support provides. +* 核心扩展是什么。 +* 如何加载全部扩展。 +* 如何恰如其分的选出你需要的扩展。 +* Active Support 都提供了哪些功能。 -------------------------------------------------------------------------------- -How to Load Core Extensions +如何加载核心扩展 --------------------------- -### Stand-Alone Active Support +### 单独的 Active Support -In order to have a near-zero default footprint, Active Support does not load anything by default. It is broken in small pieces so that you can load just what you need, and also has some convenience entry points to load related extensions in one shot, even everything. +为了使初始空间尽可能干净,默认情况下 Active Support 什么都不加载。它被拆分成许多小组建,这样一来你便可以只加载自己需要的那部分,同时它也提供了一系列便捷入口使你很容易加载相关的扩展,甚至把全部扩展都加载进来。 -Thus, after a simple require like: +因而,像下面这样只简单用一个 require: ```ruby require 'active_support' ``` -objects do not even respond to `blank?`. Let's see how to load its definition. +对象会连`blank?`都没法响应。让我们来看下该如何加载它的定义。 -#### Cherry-picking a Definition +#### 选出合适的定义 -The most lightweight way to get `blank?` is to cherry-pick the file that defines it. +找到`blank?`最轻便的方法就是直接找出定义它的那个文件。 -For every single method defined as a core extension this guide has a note that says where such a method is defined. In the case of `blank?` the note reads: +对于每一个定义在核心扩展里的方法,本指南都会注明此方法定义于何处。例如这里提到的`blank?`,会像这样注明: -NOTE: Defined in `active_support/core_ext/object/blank.rb`. +NOTE: 定义于 `active_support/core_ext/object/blank.rb`。 -That means that you can require it like this: +这意味着你可以像下面这样 require 它: ```ruby require 'active_support' require 'active_support/core_ext/object/blank' ``` -Active Support has been carefully revised so that cherry-picking a file loads only strictly needed dependencies, if any. +Active Support 经过了严格的修订,确保选定的文件只会加载必要的依赖,若没有则不加载。 -#### Loading Grouped Core Extensions +#### 加载一组核心扩展 -The next level is to simply load all extensions to `Object`. As a rule of thumb, extensions to `SomeClass` are available in one shot by loading `active_support/core_ext/some_class`. +接下来加载`Object`下的全部扩展。一般来说,想加载`SomeClass`下的全部可用扩展,只需加载`active_support/core_ext/some_class`即可。 -Thus, to load all extensions to `Object` (including `blank?`): +所以,若要加载`Object`下的全部扩展(包含`blank?`): ```ruby require 'active_support' require 'active_support/core_ext/object' ``` -#### Loading All Core Extensions +#### 加载全部核心扩展 -You may prefer just to load all core extensions, there is a file for that: +你可能更倾向于加载全部核心扩展,有一个文件能办到: ```ruby require 'active_support' require 'active_support/core_ext' ``` -#### Loading All Active Support +#### 加载全部 Active Support -And finally, if you want to have all Active Support available just issue: +最后,如果你想要 Active Support 的全部内容,只需: ```ruby require 'active_support/all' ``` -That does not even put the entire Active Support in memory upfront indeed, some stuff is configured via `autoload`, so it is only loaded if used. +这样做并不会把整个 Active Support 预加载到内存里,鉴于`autoload`的机制,其只有在真正用到时才会加载。 -### Active Support Within a Ruby on Rails Application +### Ruby on Rails 程序里的 Active Support -A Ruby on Rails application loads all Active Support unless `config.active_support.bare` is true. In that case, the application will only load what the framework itself cherry-picks for its own needs, and can still cherry-pick itself at any granularity level, as explained in the previous section. +除非把`config.active_support.bare`设置为 true, 否则 Ruby on Rails 的程序会加载全部的 Active Support。如此一来,程序只会加载框架为自身需要挑选出来的扩展,同时也可像上文所示,可以从任何级别加载特定扩展。 -Extensions to All Objects +所有对象都可用的扩展 ------------------------- ### `blank?` and `present?` -The following values are considered to be blank in a Rails application: +以下各值在 Rails 程序里都看作 blank。 -* `nil` and `false`, +* `nil` 和 `false`, -* strings composed only of whitespace (see note below), +* 只包含空白的字符串(参照下文注释), -* empty arrays and hashes, and +* 空的数组和散列表 -* any other object that responds to `empty?` and is empty. +* 任何其他能响应 `empty?` 方法且为空的对象。 -INFO: The predicate for strings uses the Unicode-aware character class `[:space:]`, so for example U+2029 (paragraph separator) is considered to be whitespace. +INFO: 判断字符串是否为空依据了 Unicode-aware 字符类 `[:space:]`,所以例如 U+2029(段落分隔符)这种会被当作空白。 -WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank. +WARNING: 注意这里没有提到数字。通常来说,0和0.0都**不是**blank。 -For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present: +例如,`ActionController::HttpAuthentication::Token::ControllerMethods`里的一个方法使用了`blank?`来检验 token 是否存在。 ```ruby def authenticate(controller, &login_procedure) @@ -110,7 +110,7 @@ def authenticate(controller, &login_procedure) end ``` -The method `present?` is equivalent to `!blank?`. This example is taken from `ActionDispatch::Http::Cache::Response`: +`present?` 方法等同于 `!blank?`, 下面的例子出自`ActionDispatch::Http::Cache::Response`: ```ruby def set_conditional_cache_control! @@ -119,41 +119,42 @@ def set_conditional_cache_control! end ``` -NOTE: Defined in `active_support/core_ext/object/blank.rb`. +NOTE: 定义于 `active_support/core_ext/object/blank.rb`. ### `presence` -The `presence` method returns its receiver if `present?`, and `nil` otherwise. It is useful for idioms like this: +`presence`方法如果满足`present?`则返回调用者,否则返回`nil`。它适用于下面这种情况: ```ruby host = config[:host].presence || 'localhost' ``` -NOTE: Defined in `active_support/core_ext/object/blank.rb`. +NOTE: 定义于 `active_support/core_ext/object/blank.rb`. ### `duplicable?` A few fundamental objects in Ruby are singletons. For example, in the whole life of a program the integer 1 refers always to the same instance: +Ruby 里有些基本对象是单例的。比如,在整个程序的生命周期里,数字1永远指向同一个实例。 ```ruby 1.object_id # => 3 Math.cos(0).to_i.object_id # => 3 ``` -Hence, there's no way these objects can be duplicated through `dup` or `clone`: +因而,这些对象永远没法用`dup`或`clone`复制。 ```ruby true.dup # => TypeError: can't dup TrueClass ``` -Some numbers which are not singletons are not duplicable either: +有些数字虽然不是单例的,但也同样无法复制: ```ruby 0.0.clone # => allocator undefined for Float (2**1024).clone # => allocator undefined for Bignum ``` -Active Support provides `duplicable?` to programmatically query an object about this property: +Active Support 提供了 `duplicable?` 方法来判断一个对象是否能够被复制: ```ruby "foo".duplicable? # => true @@ -162,15 +163,15 @@ Active Support provides `duplicable?` to programmatically query an object about false.duplicable? # => false ``` -By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, and module objects. +根据定义,所有的对象的`duplicated?`的,除了:`nil`、`false`、 `true`、 符号、 数字、 类和模块。 -WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. +WARNING: 任何的类都可以通过移除`dup`和`clone`方法,或者在其中抛出异常,来禁用其复制功能。虽然`duplicable?`方法是基于上面的硬编码列表,但是它比用`rescue`快的多。确保仅在你的情况合乎上面的硬编码列表时候再使用它。 -NOTE: Defined in `active_support/core_ext/object/duplicable.rb`. +NOTE: 定义于 `active_support/core_ext/object/duplicable.rb`. ### `deep_dup` -The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, Ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this: +`deep_dup`方法返回一个对象的深度拷贝。一般来说,当你`dup`一个包含其他对象的对象时,Ruby 并不会把被包含的对象一同`dup`,它只会创建一个对象的浅表拷贝。假如你有一个字符串数组,如下例所示: ```ruby array = ['string'] @@ -178,20 +179,20 @@ duplicate = array.dup duplicate.push 'another-string' -# the object was duplicated, so the element was added only to the duplicate +# 对象被复制了,所以只有 duplicate 的数组元素有所增加 array # => ['string'] duplicate # => ['string', 'another-string'] duplicate.first.gsub!('string', 'foo') -# first element was not duplicated, it will be changed in both arrays +# 第一个数组元素并未被复制,所以两个数组都发生了变化 array # => ['foo'] duplicate # => ['foo', 'another-string'] ``` -As you can see, after duplicating the `Array` instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since `dup` does not make deep copy, the string inside the array is still the same object. +如你所见,对`Array`实例进行复制后,我们得到了另一个对象,因而我们修改它时,原始对象并未跟着有所变化。不过对数组元素而言,情况却有所不同。因为`dup`不会创建深度拷贝,所以数组里的字符串依然是同一个对象。 -If you need a deep copy of an object, you should use `deep_dup`. Here is an example: +如果你需要一个对象的深度拷贝,就应该使用`deep_dup`。我们再来看下面这个例子: ```ruby array = ['string'] @@ -203,7 +204,7 @@ array # => ['string'] duplicate # => ['foo'] ``` -If the object is not duplicable, `deep_dup` will just return it: +如果一个对象是不可复制的,`deep_dup`会返回其自身: ```ruby number = 1 @@ -211,25 +212,25 @@ duplicate = number.deep_dup number.object_id == duplicate.object_id # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: 定义于 `active_support/core_ext/object/deep_dup.rb`. ### `try` -When you want to call a method on an object only if it is not `nil`, the simplest way to achieve it is with conditional statements, adding unnecessary clutter. The alternative is to use `try`. `try` is like `Object#send` except that it returns `nil` if sent to `nil`. +如果你想在一个对象不为`nil`时,对其调用一个方法,最简单的办法就是使用条件从句,但这么做也会使代码变得乱七八糟。另一个选择就是使用`try`。`try`就好比`Object#send`,只不过如果接收者为`nil`,那么返回值也会是`nil`。 -Here is an example: +看下这个例子: ```ruby -# without try +# 不使用 try unless @number.nil? @number.next end -# with try +# 使用 try @number.try(:next) ``` -Another example is this code from `ActiveRecord::ConnectionAdapters::AbstractAdapter` where `@logger` could be `nil`. You can see that the code uses `try` and avoids an unnecessary check. +接下来的这个例子,代码出自`ActiveRecord::ConnectionAdapters::AbstractAdapter`,这里的`@logger`有可能为`nil`。能够看到,代码里使用了`try`来避免不必要的检查。 ```ruby def log_info(sql, name, ms) @@ -240,17 +241,18 @@ def log_info(sql, name, ms) end ``` -`try` can also be called without arguments but a block, which will only be executed if the object is not nil: +调用`try`时也可以不传参数而是用代码快,其中的代码只有在对象不为`nil`时才会执行: ```ruby @person.try { |p| "#{p.first_name} #{p.last_name}" } ``` -NOTE: Defined in `active_support/core_ext/object/try.rb`. +NOTE: 定义于 `active_support/core_ext/object/try.rb`. ### `class_eval(*args, &block)` You can evaluate code in the context of any object's singleton class using `class_eval`: +使用`class_eval`,可以使代码在对象的单件类的上下文里执行: ```ruby class Proc @@ -267,52 +269,52 @@ class Proc end ``` -NOTE: Defined in `active_support/core_ext/kernel/singleton_class.rb`. +NOTE: 定义于 `active_support/core_ext/kernel/singleton_class.rb`. ### `acts_like?(duck)` -The method `acts_like?` provides a way to check whether some class acts like some other class based on a simple convention: a class that provides the same interface as `String` defines +`acts_like?`方法可以用来判断某个类与另一个类是否有相同的行为,它基于一个简单的惯例:这个类是否提供了与`String`相同的接口: ```ruby def acts_like_string? end ``` -which is only a marker, its body or return value are irrelevant. Then, client code can query for duck-type-safeness this way: +上述代码只是一个标识,它的方法体或返回值都是不相关的。之后,就可以像下述代码那样判断其代码是否为“鸭子类型安全”的代码了: ```ruby some_klass.acts_like?(:string) ``` -Rails has classes that act like `Date` or `Time` and follow this contract. +Rails 里的许多类,例如`Date`和`Time`,都遵循上述约定。 -NOTE: Defined in `active_support/core_ext/object/acts_like.rb`. +NOTE: 定义于 `active_support/core_ext/object/acts_like.rb`. ### `to_param` -All objects in Rails respond to the method `to_param`, which is meant to return something that represents them as values in a query string, or as URL fragments. +所有 Rails 对象都可以响应`to_param`方法,它会把对象的值转换为查询字符串,或者 URL 片段,并返回该值。 -By default `to_param` just calls `to_s`: +默认情况下,`to_param`仅仅调用了`to_s`: ```ruby 7.to_param # => "7" ``` -The return value of `to_param` should **not** be escaped: +**不要**对`to_param`方法的返回值进行转义: ```ruby "Tom & Jerry".to_param # => "Tom & Jerry" ``` -Several classes in Rails overwrite this method. +Rails 里的许多类重写了这个方法。 -For example `nil`, `true`, and `false` return themselves. `Array#to_param` calls `to_param` on the elements and joins the result with "/": +例如`nil`、`true`和`false`会返回其自身。`Array#to_param`会对数组元素调用`to_param`并把结果用"/"连接成字符串: ```ruby [0, true, String].to_param # => "0/true/String" ``` -Notably, the Rails routing system calls `to_param` on models to get a value for the `:id` placeholder. `ActiveRecord::Base#to_param` returns the `id` of a model, but you can redefine that method in your models. For example, given +需要注意的是, Rails 的路由系统会在模型上调用`to_param`并把结果作为`:id`占位符。`ActiveRecord::Base#to_param`会返回模型的`id`,但是你也可以在自己模型里重新定义它。例如: ```ruby class User @@ -322,19 +324,19 @@ class User end ``` -we get: +会得到: ```ruby user_path(@user) # => "/users/357-john-smith" ``` -WARNING. Controllers need to be aware of any redefinition of `to_param` because when a request like that comes in "357-john-smith" is the value of `params[:id]`. +WARNING. 控制器里需要注意被重定义过的`to_param`,因为一个类似上述的请求里,会把"357-john-smith"当作`params[:id]`的值。 -NOTE: Defined in `active_support/core_ext/object/to_param.rb`. +NOTE: 定义于 `active_support/core_ext/object/to_param.rb`. ### `to_query` -Except for hashes, given an unescaped `key` this method constructs the part of a query string that would map such key to what `to_param` returns. For example, given +除了哈希表之外,给定一个未转义的`key`,这个方法就会基于这个键和`to_param`的返回值,构造出一个新的查询字符串。例如: ```ruby class User @@ -344,48 +346,48 @@ class User end ``` -we get: +会得到: ```ruby -current_user.to_query('user') # => user=357-john-smith +current_user.to_query('user') # => "user=357-john-smith" ``` -This method escapes whatever is needed, both for the key and the value: +无论对于键还是值,本方法都会根据需要进行转义: ```ruby account.to_query('company[name]') # => "company%5Bname%5D=Johnson+%26+Johnson" ``` -so its output is ready to be used in a query string. +所以它的输出已经完全适合于用作查询字符串。 -Arrays return the result of applying `to_query` to each element with `_key_[]` as key, and join the result with "&": +对于数组,会对其中每个元素以`_key_[]`为键执行`to_query`方法,并把结果用"&"连接为字符串: ```ruby [3.4, -45.6].to_query('sample') # => "sample%5B%5D=3.4&sample%5B%5D=-45.6" ``` -Hashes also respond to `to_query` but with a different signature. If no argument is passed a call generates a sorted series of key/value assignments calling `to_query(key)` on its values. Then it joins the result with "&": +哈系表也可以响应`to_query`方法但是用法有所不同。如果调用时没传参数,会先生成一系列排过序的键值对并在值上调用`to_query(键)`。然后把所得结果用"&"连接为字符串: ```ruby {c: 3, b: 2, a: 1}.to_query # => "a=1&b=2&c=3" ``` -The method `Hash#to_query` accepts an optional namespace for the keys: +`Hash#to_query`方法也可接受一个可选的命名空间作为键: ```ruby {id: 89, name: "John Smith"}.to_query('user') # => "user%5Bid%5D=89&user%5Bname%5D=John+Smith" ``` -NOTE: Defined in `active_support/core_ext/object/to_query.rb`. +NOTE: 定义于 `active_support/core_ext/object/to_query.rb`. ### `with_options` -The method `with_options` provides a way to factor out common options in a series of method calls. +`with_options`方法可以为一组方法调用提取出共有的选项。 -Given a default options hash, `with_options` yields a proxy object to a block. Within the block, methods called on the proxy are forwarded to the receiver with their options merged. For example, you get rid of the duplication in: +假定有一个默认的哈希选项,`with_options`方法会引入一个代理对象到代码块。在代码块内部,代理对象上的方法调用,会连同被混入的选项一起,被转发至原方法接收者。例如,若要去除下述代码的重复内容: ```ruby class Account < ActiveRecord::Base @@ -396,7 +398,7 @@ class Account < ActiveRecord::Base end ``` -this way: +可按此法书写: ```ruby class Account < ActiveRecord::Base @@ -409,7 +411,10 @@ class Account < ActiveRecord::Base end ``` +#TODO: clear this after totally understanding what these statnances means... That idiom may convey _grouping_ to the reader as well. For example, say you want to send a newsletter whose language depends on the user. Somewhere in the mailer you could group locale-dependent bits like this: +上述写法也可用于对读取器进行分组。例如,假设你要发一份新闻通讯,通讯所用语言取决于用户。便可以利用如下例所示代码,对用户按照地区依赖进行分组: + ```ruby I18n.with_options locale: user.locale, scope: "newsletter" do |i18n| @@ -418,15 +423,15 @@ I18n.with_options locale: user.locale, scope: "newsletter" do |i18n| end ``` -TIP: Since `with_options` forwards calls to its receiver they can be nested. Each nesting level will merge inherited defaults in addition to their own. +TIP: 由于`with_options`会把方法调用转发给其自身的接收者,所以可以进行嵌套。每层嵌套都会把继承来的默认值混入到自身的默认值里。 -NOTE: Defined in `active_support/core_ext/object/with_options.rb`. +NOTE: 定义于 `active_support/core_ext/object/with_options.rb`. ### JSON support Active Support provides a better implementation of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash`, `OrderedHash` and `Process::Status` need special handling in order to provide a proper JSON representation. -NOTE: Defined in `active_support/core_ext/object/json.rb`. +NOTE: 定义于 `active_support/core_ext/object/json.rb`. ### Instance Variables @@ -447,7 +452,7 @@ end C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} ``` -NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. +NOTE: 定义于 `active_support/core_ext/object/instance_variables.rb`. #### `instance_variable_names` @@ -463,7 +468,7 @@ end C.new(0, 1).instance_variable_names # => ["@x", "@y"] ``` -NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. +NOTE: 定义于 `active_support/core_ext/object/instance_variables.rb`. ### Silencing Warnings, Streams, and Exceptions @@ -498,7 +503,7 @@ suppress(ActiveRecord::StaleObjectError) do end ``` -NOTE: Defined in `active_support/core_ext/kernel/reporting.rb`. +NOTE: 定义于 `active_support/core_ext/kernel/reporting.rb`. ### `in?` @@ -513,7 +518,7 @@ Examples of `in?`: 1.in?(1) # => ArgumentError ``` -NOTE: Defined in `active_support/core_ext/object/inclusion.rb`. +NOTE: 定义于 `active_support/core_ext/object/inclusion.rb`. Extensions to `Module` ---------------------- @@ -566,7 +571,7 @@ end Rails uses `alias_method_chain` all over the code base. For example validations are added to `ActiveRecord::Base#save` by wrapping the method that way in a separate module specialized in validations. -NOTE: Defined in `active_support/core_ext/module/aliasing.rb`. +NOTE: 定义于 `active_support/core_ext/module/aliasing.rb`. ### Attributes @@ -582,7 +587,7 @@ class User < ActiveRecord::Base end ``` -NOTE: Defined in `active_support/core_ext/module/aliasing.rb`. +NOTE: 定义于 `active_support/core_ext/module/aliasing.rb`. #### Internal Attributes @@ -620,7 +625,7 @@ module ActionView end ``` -NOTE: Defined in `active_support/core_ext/module/attr_internal.rb`. +NOTE: 定义于 `active_support/core_ext/module/attr_internal.rb`. #### Module Attributes @@ -647,7 +652,7 @@ module ActiveSupport end ``` -NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. +NOTE: 定义于 `active_support/core_ext/module/attribute_accessors.rb`. ### Parents @@ -672,7 +677,7 @@ If the module is anonymous or belongs to the top-level, `parent` returns `Object WARNING: Note that in that case `parent_name` returns `nil`. -NOTE: Defined in `active_support/core_ext/module/introspection.rb`. +NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. #### `parent_name` @@ -695,7 +700,7 @@ For top-level or anonymous modules `parent_name` returns `nil`. WARNING: Note that in that case `parent` returns `Object`. -NOTE: Defined in `active_support/core_ext/module/introspection.rb`. +NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. #### `parents` @@ -714,7 +719,7 @@ X::Y::Z.parents # => [X::Y, X, Object] M.parents # => [X::Y, X, Object] ``` -NOTE: Defined in `active_support/core_ext/module/introspection.rb`. +NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. ### Constants @@ -737,7 +742,7 @@ X::Y.local_constants # => [:Y1, :X1] The names are returned as symbols. -NOTE: Defined in `active_support/core_ext/module/introspection.rb`. +NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. #### Qualified Constant Names @@ -795,7 +800,7 @@ as in `const_defined?`. For coherence with the built-in methods only relative paths are accepted. Absolute qualified constant names like `::Math::PI` raise `NameError`. -NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`. +NOTE: 定义于 `active_support/core_ext/module/qualified_const.rb`. ### Reachable @@ -833,7 +838,7 @@ end orphan.reachable? # => false ``` -NOTE: Defined in `active_support/core_ext/module/reachable.rb`. +NOTE: 定义于 `active_support/core_ext/module/reachable.rb`. ### Anonymous @@ -874,7 +879,7 @@ m.anonymous? # => false though an anonymous module is unreachable by definition. -NOTE: Defined in `active_support/core_ext/module/anonymous.rb`. +NOTE: 定义于 `active_support/core_ext/module/anonymous.rb`. ### Method Delegation @@ -958,7 +963,7 @@ delegate :size, to: :attachment, prefix: :avatar In the previous example the macro generates `avatar_size` rather than `size`. -NOTE: Defined in `active_support/core_ext/module/delegation.rb` +NOTE: 定义于 `active_support/core_ext/module/delegation.rb` ### Redefining Methods @@ -966,7 +971,7 @@ There are cases where you need to define a method with `define_method`, but don' The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. -NOTE: Defined in `active_support/core_ext/module/remove_method.rb` +NOTE: 定义于 `active_support/core_ext/module/remove_method.rb` Extensions to `Class` --------------------- @@ -1053,7 +1058,7 @@ When `:instance_reader` is `false`, the instance predicate returns a `NoMethodEr If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. -NOTE: Defined in `active_support/core_ext/class/attribute.rb` +NOTE: 定义于 `active_support/core_ext/class/attribute.rb` #### `cattr_reader`, `cattr_writer`, and `cattr_accessor` @@ -1106,7 +1111,7 @@ end A model may find it useful to set `:instance_accessor` to `false` as a way to prevent mass-assignment from setting the attribute. -NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. +NOTE: 定义于 `active_support/core_ext/module/attribute_accessors.rb`. ### Subclasses & Descendants @@ -1130,7 +1135,7 @@ C.subclasses # => [B, D] The order in which these classes are returned is unspecified. -NOTE: Defined in `active_support/core_ext/class/subclasses.rb`. +NOTE: 定义于 `active_support/core_ext/class/subclasses.rb`. #### `descendants` @@ -1152,7 +1157,7 @@ C.descendants # => [B, A, D] The order in which these classes are returned is unspecified. -NOTE: Defined in `active_support/core_ext/class/subclasses.rb`. +NOTE: 定义于 `active_support/core_ext/class/subclasses.rb`. Extensions to `String` ---------------------- @@ -1165,9 +1170,9 @@ Inserting data into HTML templates needs extra care. For example, you can't just #### Safe Strings -Active Support has the concept of (html) safe strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. +Active Support has the concept of _(html) safe_ strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. -Strings are considered to be unsafe by default: +Strings are considered to be _unsafe_ by default: ```ruby "".html_safe? # => false @@ -1228,7 +1233,7 @@ def raw(stringish) end ``` -NOTE: Defined in `active_support/core_ext/string/output_safety.rb`. +NOTE: 定义于 `active_support/core_ext/string/output_safety.rb`. #### Transformation @@ -1256,7 +1261,7 @@ The method `remove` will remove all occurrences of the pattern: There's also the destructive version `String#remove!`. -NOTE: Defined in `active_support/core_ext/string/filters.rb`. +NOTE: 定义于 `active_support/core_ext/string/filters.rb`. ### `squish` @@ -1270,7 +1275,7 @@ There's also the destructive version `String#squish!`. Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). -NOTE: Defined in `active_support/core_ext/string/filters.rb`. +NOTE: 定义于 `active_support/core_ext/string/filters.rb`. ### `truncate` @@ -1308,7 +1313,7 @@ The option `:separator` can be a regexp: In above examples "dear" gets cut first, but then `:separator` prevents it. -NOTE: Defined in `active_support/core_ext/string/filters.rb`. +NOTE: 定义于 `active_support/core_ext/string/filters.rb`. ### `inquiry` @@ -1328,7 +1333,7 @@ Active Support defines 3rd person aliases of `String#start_with?` and `String#en "foo".ends_with?("o") # => true ``` -NOTE: Defined in `active_support/core_ext/string/starts_ends_with.rb`. +NOTE: 定义于 `active_support/core_ext/string/starts_ends_with.rb`. ### `strip_heredoc` @@ -1353,7 +1358,7 @@ the user would see the usage message aligned against the left margin. Technically, it looks for the least indented line in the whole string, and removes that amount of leading whitespace. -NOTE: Defined in `active_support/core_ext/string/strip.rb`. +NOTE: 定义于 `active_support/core_ext/string/strip.rb`. ### `indent` @@ -1390,7 +1395,7 @@ The third argument, `indent_empty_lines`, is a flag that says whether empty line The `indent!` method performs indentation in-place. -NOTE: Defined in `active_support/core_ext/string/indent.rb`. +NOTE: 定义于 `active_support/core_ext/string/indent.rb`. ### Access @@ -1405,7 +1410,7 @@ Returns the character of the string at position `position`: "hello".at(10) # => nil ``` -NOTE: Defined in `active_support/core_ext/string/access.rb`. +NOTE: 定义于 `active_support/core_ext/string/access.rb`. #### `from(position)` @@ -1418,7 +1423,7 @@ Returns the substring of the string starting at position `position`: "hello".from(10) # => "" if < 1.9, nil in 1.9 ``` -NOTE: Defined in `active_support/core_ext/string/access.rb`. +NOTE: 定义于 `active_support/core_ext/string/access.rb`. #### `to(position)` @@ -1431,19 +1436,19 @@ Returns the substring of the string up to position `position`: "hello".to(10) # => "hello" ``` -NOTE: Defined in `active_support/core_ext/string/access.rb`. +NOTE: 定义于 `active_support/core_ext/string/access.rb`. #### `first(limit = 1)` The call `str.first(n)` is equivalent to `str.to(n-1)` if `n` > 0, and returns an empty string for `n` == 0. -NOTE: Defined in `active_support/core_ext/string/access.rb`. +NOTE: 定义于 `active_support/core_ext/string/access.rb`. #### `last(limit = 1)` The call `str.last(n)` is equivalent to `str.from(-n)` if `n` > 0, and returns an empty string for `n` == 0. -NOTE: Defined in `active_support/core_ext/string/access.rb`. +NOTE: 定义于 `active_support/core_ext/string/access.rb`. ### Inflections @@ -1477,7 +1482,7 @@ def undecorated_table_name(class_name = base_class.name) end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `singularize` @@ -1500,7 +1505,7 @@ def derive_class_name end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `camelize` @@ -1548,7 +1553,7 @@ end `camelize` is aliased to `camelcase`. -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `underscore` @@ -1587,7 +1592,7 @@ end INFO: As a rule of thumb you can think of `underscore` as the inverse of `camelize`, though there are cases where that does not hold. For example, `"SSLError".underscore.camelize` gives back `"SslError"`. -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `titleize` @@ -1600,7 +1605,7 @@ The method `titleize` capitalizes the words in the receiver: `titleize` is aliased to `titlecase`. -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `dasherize` @@ -1621,7 +1626,7 @@ def reformat_name(name) end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `demodulize` @@ -1649,7 +1654,7 @@ def counter_cache_column end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `deconstantize` @@ -1674,7 +1679,7 @@ def qualified_const_set(path, value) end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `parameterize` @@ -1687,7 +1692,7 @@ The method `parameterize` normalizes its receiver in a way that can be used in p In fact, the result string is wrapped in an instance of `ActiveSupport::Multibyte::Chars`. -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `tableize` @@ -1701,7 +1706,7 @@ The method `tableize` is `underscore` followed by `pluralize`. As a rule of thumb, `tableize` returns the table name that corresponds to a given model for simple cases. The actual implementation in Active Record is not straight `tableize` indeed, because it also demodulizes the class name and checks a few options that may affect the returned string. -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `classify` @@ -1721,7 +1726,7 @@ The method understands qualified table names: Note that `classify` returns a class name as a string. You can get the actual class object invoking `constantize` on it, explained next. -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `constantize` @@ -1764,7 +1769,7 @@ rescue NameError => e end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `humanize` @@ -1814,7 +1819,7 @@ def full_messages end ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. #### `foreign_key` @@ -1839,7 +1844,7 @@ Associations use this method to infer foreign keys, for example `has_one` and `h foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key ``` -NOTE: Defined in `active_support/core_ext/string/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. ### Conversions @@ -1866,7 +1871,7 @@ Please refer to the documentation of `Date._parse` for further details. INFO: The three of them return `nil` for blank receivers. -NOTE: Defined in `active_support/core_ext/string/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/string/conversions.rb`. Extensions to `Numeric` ----------------------- @@ -1900,7 +1905,7 @@ Singular forms are aliased so you are able to say: 1.megabyte # => 1048576 ``` -NOTE: Defined in `active_support/core_ext/numeric/bytes.rb`. +NOTE: 定义于 `active_support/core_ext/numeric/bytes.rb`. ### Time @@ -1936,7 +1941,7 @@ In such cases, Ruby's core [Date](http://ruby-doc.org/stdlib/libdoc/date/rdoc/Da [Time](http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html) should be used for precision date and time arithmetic. -NOTE: Defined in `active_support/core_ext/numeric/time.rb`. +NOTE: 定义于 `active_support/core_ext/numeric/time.rb`. ### Formatting @@ -2023,7 +2028,7 @@ Produce a string representation of a number in human-readable words: 1234567890123456.to_s(:human) # => "1.23 Quadrillion" ``` -NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/numeric/conversions.rb`. Extensions to `Integer` ----------------------- @@ -2037,7 +2042,7 @@ The method `multiple_of?` tests whether an integer is multiple of the argument: 1.multiple_of?(2) # => false ``` -NOTE: Defined in `active_support/core_ext/integer/multiple.rb`. +NOTE: 定义于 `active_support/core_ext/integer/multiple.rb`. ### `ordinal` @@ -2052,7 +2057,7 @@ The method `ordinal` returns the ordinal suffix string corresponding to the rece -134.ordinal # => "th" ``` -NOTE: Defined in `active_support/core_ext/integer/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/integer/inflections.rb`. ### `ordinalize` @@ -2067,7 +2072,7 @@ The method `ordinalize` returns the ordinal string corresponding to the receiver -134.ordinalize # => "-134th" ``` -NOTE: Defined in `active_support/core_ext/integer/inflections.rb`. +NOTE: 定义于 `active_support/core_ext/integer/inflections.rb`. Extensions to `BigDecimal` -------------------------- @@ -2139,7 +2144,7 @@ The sum of an empty receiver can be customized in this form as well: [].sum(1) {|n| n**3} # => 1 ``` -NOTE: Defined in `active_support/core_ext/enumerable.rb`. +NOTE: 定义于 `active_support/core_ext/enumerable.rb`. ### `index_by` @@ -2154,7 +2159,7 @@ invoices.index_by(&:number) WARNING. Keys should normally be unique. If the block returns the same value for different elements no collection is built for that key. The last item will win. -NOTE: Defined in `active_support/core_ext/enumerable.rb`. +NOTE: 定义于 `active_support/core_ext/enumerable.rb`. ### `many?` @@ -2172,7 +2177,7 @@ If an optional block is given, `many?` only takes into account those elements th @see_more = videos.many? {|video| video.category == params[:category]} ``` -NOTE: Defined in `active_support/core_ext/enumerable.rb`. +NOTE: 定义于 `active_support/core_ext/enumerable.rb`. ### `exclude?` @@ -2182,7 +2187,7 @@ The predicate `exclude?` tests whether a given object does **not** belong to the to_visit << node if visited.exclude?(node) ``` -NOTE: Defined in `active_support/core_ext/enumerable.rb`. +NOTE: 定义于 `active_support/core_ext/enumerable.rb`. Extensions to `Array` --------------------- @@ -2211,7 +2216,7 @@ The methods `second`, `third`, `fourth`, and `fifth` return the corresponding el %w(a b c d).fifth # => nil ``` -NOTE: Defined in `active_support/core_ext/array/access.rb`. +NOTE: 定义于 `active_support/core_ext/array/access.rb`. ### Adding Elements @@ -2224,7 +2229,7 @@ This method is an alias of `Array#unshift`. [].prepend(10) # => [10] ``` -NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. +NOTE: 定义于 `active_support/core_ext/array/prepend_and_append.rb`. #### `append` @@ -2235,7 +2240,7 @@ This method is an alias of `Array#<<`. [].append([1,2]) # => [[1,2]] ``` -NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. +NOTE: 定义于 `active_support/core_ext/array/prepend_and_append.rb`. ### Options Extraction @@ -2263,7 +2268,7 @@ end This method receives an arbitrary number of action names, and an optional hash of options as last argument. With the call to `extract_options!` you obtain the options hash and remove it from `actions` in a simple and explicit way. -NOTE: Defined in `active_support/core_ext/array/extract_options.rb`. +NOTE: 定义于 `active_support/core_ext/array/extract_options.rb`. ### Conversions @@ -2292,7 +2297,7 @@ The defaults for these options can be localized, their keys are: | `:words_connector` | `support.array.words_connector` | | `:last_word_connector` | `support.array.last_word_connector` | -NOTE: Defined in `active_support/core_ext/array/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/array/conversions.rb`. #### `to_formatted_s` @@ -2310,7 +2315,7 @@ invoice.lines.to_formatted_s(:db) # => "23,567,556,12" Integers in the example above are supposed to come from the respective calls to `id`. -NOTE: Defined in `active_support/core_ext/array/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/array/conversions.rb`. #### `to_xml` @@ -2411,7 +2416,7 @@ Contributor.limit(2).order(:rank).to_xml(skip_types: true) # ``` -NOTE: Defined in `active_support/core_ext/array/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/array/conversions.rb`. ### Wrapping @@ -2452,7 +2457,7 @@ which in Ruby 1.8 returns `[nil]` for `nil`, and calls to `Array(object)` otherw Thus, in this case the behavior is different for `nil`, and the differences with `Kernel#Array` explained above apply to the rest of `object`s. -NOTE: Defined in `active_support/core_ext/array/wrap.rb`. +NOTE: 定义于 `active_support/core_ext/array/wrap.rb`. ### Duplicating @@ -2466,7 +2471,7 @@ dup[1][2] = 4 array[1][2] == nil # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: 定义于 `active_support/core_ext/object/deep_dup.rb`. ### Grouping @@ -2504,7 +2509,7 @@ And you can tell the method not to fill the last group passing `false`: As a consequence `false` can't be a used as a padding value. -NOTE: Defined in `active_support/core_ext/array/grouping.rb`. +NOTE: 定义于 `active_support/core_ext/array/grouping.rb`. #### `in_groups(number, fill_with = nil)` @@ -2542,7 +2547,7 @@ And you can tell the method not to fill the smaller groups passing `false`: As a consequence `false` can't be a used as a padding value. -NOTE: Defined in `active_support/core_ext/array/grouping.rb`. +NOTE: 定义于 `active_support/core_ext/array/grouping.rb`. #### `split(value = nil)` @@ -2564,7 +2569,7 @@ Otherwise, the value received as argument, which defaults to `nil`, is the separ TIP: Observe in the previous example that consecutive separators result in empty arrays. -NOTE: Defined in `active_support/core_ext/array/grouping.rb`. +NOTE: 定义于 `active_support/core_ext/array/grouping.rb`. Extensions to `Hash` -------------------- @@ -2616,7 +2621,7 @@ By default the root node is "hash", but that's configurable via the `:root` opti The default XML builder is a fresh instance of `Builder::XmlMarkup`. You can configure your own builder with the `:builder` option. The method also accepts options like `:dasherize` and friends, they are forwarded to the builder. -NOTE: Defined in `active_support/core_ext/hash/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/hash/conversions.rb`. ### Merging @@ -2651,7 +2656,7 @@ options.reverse_merge!(length: 30, omission: "...") WARNING. Take into account that `reverse_merge!` may change the hash in the caller, which may or may not be a good idea. -NOTE: Defined in `active_support/core_ext/hash/reverse_merge.rb`. +NOTE: 定义于 `active_support/core_ext/hash/reverse_merge.rb`. #### `reverse_update` @@ -2659,7 +2664,7 @@ The method `reverse_update` is an alias for `reverse_merge!`, explained above. WARNING. Note that `reverse_update` has no bang. -NOTE: Defined in `active_support/core_ext/hash/reverse_merge.rb`. +NOTE: 定义于 `active_support/core_ext/hash/reverse_merge.rb`. #### `deep_merge` and `deep_merge!` @@ -2674,7 +2679,7 @@ Active Support defines `Hash#deep_merge`. In a deep merge, if a key is found in The method `deep_merge!` performs a deep merge in place. -NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`. +NOTE: 定义于 `active_support/core_ext/hash/deep_merge.rb`. ### Deep duplicating @@ -2692,7 +2697,7 @@ hash[:b][:e] == nil # => true hash[:b][:d] == [3, 4] # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: 定义于 `active_support/core_ext/object/deep_dup.rb`. ### Working with Keys @@ -2713,7 +2718,7 @@ If the receiver responds to `convert_key`, the method is called on each of the a There's also the bang variant `except!` that removes keys in the very receiver. -NOTE: Defined in `active_support/core_ext/hash/except.rb`. +NOTE: 定义于 `active_support/core_ext/hash/except.rb`. #### `transform_keys` and `transform_keys!` @@ -2755,7 +2760,7 @@ Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to pe # => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} ``` -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. #### `stringify_keys` and `stringify_keys!` @@ -2797,7 +2802,7 @@ Besides that, one can use `deep_stringify_keys` and `deep_stringify_keys!` to st # => {""=>nil, "1"=>1, "nested"=>{"a"=>3, "5"=>5}} ``` -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. #### `symbolize_keys` and `symbolize_keys!` @@ -2841,13 +2846,13 @@ Besides that, one can use `deep_symbolize_keys` and `deep_symbolize_keys!` to sy # => {nil=>nil, 1=>1, nested:{a:3, 5=>5}} ``` -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. #### `to_options` and `to_options!` The methods `to_options` and `to_options!` are respectively aliases of `symbolize_keys` and `symbolize_keys!`. -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. #### `assert_valid_keys` @@ -2860,7 +2865,7 @@ The method `assert_valid_keys` receives an arbitrary number of arguments, and ch Active Record does not accept unknown options when building associations, for example. It implements that control via `assert_valid_keys`. -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. ### Slicing @@ -2891,7 +2896,7 @@ rest = hash.slice!(:a) # => {:b=>2} hash # => {:a=>1} ``` -NOTE: Defined in `active_support/core_ext/hash/slice.rb`. +NOTE: 定义于 `active_support/core_ext/hash/slice.rb`. ### Extracting @@ -2911,7 +2916,7 @@ rest = hash.extract!(:a).class # => ActiveSupport::HashWithIndifferentAccess ``` -NOTE: Defined in `active_support/core_ext/hash/slice.rb`. +NOTE: 定义于 `active_support/core_ext/hash/slice.rb`. ### Indifferent Access @@ -2921,7 +2926,7 @@ The method `with_indifferent_access` returns an `ActiveSupport::HashWithIndiffer {a: 1}.with_indifferent_access["a"] # => 1 ``` -NOTE: Defined in `active_support/core_ext/hash/indifferent_access.rb`. +NOTE: 定义于 `active_support/core_ext/hash/indifferent_access.rb`. ### Compacting @@ -2931,7 +2936,7 @@ The methods `compact` and `compact!` return a Hash without items with `nil` valu {a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2} ``` -NOTE: Defined in `active_support/core_ext/hash/compact.rb`. +NOTE: 定义于 `active_support/core_ext/hash/compact.rb`. Extensions to `Regexp` ---------------------- @@ -2960,7 +2965,7 @@ def assign_route_options(segments, defaults, requirements) end ``` -NOTE: Defined in `active_support/core_ext/regexp.rb`. +NOTE: 定义于 `active_support/core_ext/regexp.rb`. Extensions to `Range` --------------------- @@ -2979,7 +2984,7 @@ Active Support extends the method `Range#to_s` so that it understands an optiona As the example depicts, the `:db` format generates a `BETWEEN` SQL clause. That is used by Active Record in its support for range values in conditions. -NOTE: Defined in `active_support/core_ext/range/conversions.rb`. +NOTE: 定义于 `active_support/core_ext/range/conversions.rb`. ### `include?` @@ -3003,7 +3008,7 @@ Active Support extends these methods so that the argument may be another range i (1...9) === (3..9) # => false ``` -NOTE: Defined in `active_support/core_ext/range/include_range.rb`. +NOTE: 定义于 `active_support/core_ext/range/include_range.rb`. ### `overlaps?` @@ -3015,7 +3020,7 @@ The method `Range#overlaps?` says whether any two given ranges have non-void int (1..10).overlaps?(11..27) # => false ``` -NOTE: Defined in `active_support/core_ext/range/overlaps.rb`. +NOTE: 定义于 `active_support/core_ext/range/overlaps.rb`. Extensions to `Proc` -------------------- @@ -3062,7 +3067,7 @@ def handler_for_rescue(exception) end ``` -NOTE: Defined in `active_support/core_ext/proc.rb`. +NOTE: 定义于 `active_support/core_ext/proc.rb`. Extensions to `Date` -------------------- @@ -3672,9 +3677,9 @@ t.advance(seconds: 1) #### `Time.current` -Active Support defines `Time.current` to be today in the current time zone. That's like `Time.now`, except that it honors the user time zone, if defined. It also defines `Time.yesterday` and `Time.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Time.current`. +Active Support defines `Time.current` to be today in the current time zone. That's like `Time.now`, except that it honors the user time zone, if defined. It also defines the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Time.current`. -When making Time comparisons using methods which honor the user time zone, make sure to use `Time.current` and not `Time.now`. There are cases where the user time zone might be in the future compared to the system time zone, which `Time.today` uses by default. This means `Time.now` may equal `Time.yesterday`. +When making Time comparisons using methods which honor the user time zone, make sure to use `Time.current` instead of `Time.now`. There are cases where the user time zone might be in the future compared to the system time zone, which `Time.now` uses by default. This means `Time.now.to_date` may equal `Date.yesterday`. #### `all_day`, `all_week`, `all_month`, `all_quarter` and `all_year` @@ -3764,7 +3769,7 @@ WARNING. Note you can't append with `atomic_write`. The auxiliary file is written in a standard directory for temporary files, but you can pass a directory of your choice as second argument. -NOTE: Defined in `active_support/core_ext/file/atomic.rb`. +NOTE: 定义于 `active_support/core_ext/file/atomic.rb`. Extensions to `Marshal` ----------------------- @@ -3783,7 +3788,7 @@ If the cached data refers to a constant that is unknown at that point, the autol WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able to retry. Regular files respond to `rewind`. -NOTE: Defined in `active_support/core_ext/marshal.rb`. +NOTE: 定义于 `active_support/core_ext/marshal.rb`. Extensions to `Logger` ---------------------- @@ -3827,7 +3832,7 @@ logger.formatter = Logger::FormatWithTime logger.info("<- is the current time") ``` -NOTE: Defined in `active_support/core_ext/logger.rb`. +NOTE: 定义于 `active_support/core_ext/logger.rb`. Extensions to `NameError` ------------------------- @@ -3852,7 +3857,7 @@ rescue NameError => e end ``` -NOTE: Defined in `active_support/core_ext/name_error.rb`. +NOTE: 定义于 `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- @@ -3875,4 +3880,4 @@ rescue NameError => e end ``` -NOTE: Defined in `active_support/core_ext/load_error.rb`. +NOTE: 定义于 `active_support/core_ext/load_error.rb`. From f869ad139cbb2288c1a82e22a7094f0971a9fd0b Mon Sep 17 00:00:00 2001 From: "han.bing" Date: Mon, 4 Aug 2014 08:31:05 +0800 Subject: [PATCH 002/133] Active Support Core Extensions translation --- .../zh-CN/active_support_core_extensions.md | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/source/zh-CN/active_support_core_extensions.md b/source/zh-CN/active_support_core_extensions.md index 41b5129..eeb0455 100644 --- a/source/zh-CN/active_support_core_extensions.md +++ b/source/zh-CN/active_support_core_extensions.md @@ -336,7 +336,7 @@ NOTE: 定义于 `active_support/core_ext/object/to_param.rb`. ### `to_query` -除了哈希表之外,给定一个未转义的`key`,这个方法就会基于这个键和`to_param`的返回值,构造出一个新的查询字符串。例如: +除了散列表之外,给定一个未转义的`key`,这个方法就会基于这个键和`to_param`的返回值,构造出一个新的查询字符串。例如: ```ruby class User @@ -387,7 +387,7 @@ NOTE: 定义于 `active_support/core_ext/object/to_query.rb`. `with_options`方法可以为一组方法调用提取出共有的选项。 -假定有一个默认的哈希选项,`with_options`方法会引入一个代理对象到代码块。在代码块内部,代理对象上的方法调用,会连同被混入的选项一起,被转发至原方法接收者。例如,若要去除下述代码的重复内容: +假定有一个默认的散列表选项,`with_options`方法会引入一个代理对象到代码块。在代码块内部,代理对象上的方法调用,会连同被混入的选项一起,被转发至原方法接收者。例如,若要去除下述代码的重复内容: ```ruby class Account < ActiveRecord::Base @@ -427,20 +427,19 @@ TIP: 由于`with_options`会把方法调用转发给其自身的接收者,所 NOTE: 定义于 `active_support/core_ext/object/with_options.rb`. -### JSON support +### JSON 支持 -Active Support provides a better implementation of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash`, `OrderedHash` and `Process::Status` need special handling in order to provide a proper JSON representation. +相较于 `json` gem 为 Ruby 对象提供的`to_json`方法,Active Support 给出了一个更好的实现。因为有许多类,诸如`Hash`、`OrderedHash`和`Process::Status`,都需要做特殊处理才能到适合的 JSON 替换。 NOTE: 定义于 `active_support/core_ext/object/json.rb`. -### Instance Variables +### 实例变量 -Active Support provides several methods to ease access to instance variables. +Active Support 提供了若干方法以简化对实例变量的访问。 #### `instance_values` -The method `instance_values` returns a hash that maps instance variable names without "@" to their -corresponding values. Keys are strings: +`instance_values`方法返回一个散列表,其中会把实例变量名去掉"@"作为键,把相应的实例变量值作为值。键全部是字符串: ```ruby class C @@ -456,7 +455,7 @@ NOTE: 定义于 `active_support/core_ext/object/instance_variables.rb`. #### `instance_variable_names` -The method `instance_variable_names` returns an array. Each name includes the "@" sign. +`instance_variable_names`方法返回一个数组。数组中所有的实例变量名都带有"@"标志。 ```ruby class C @@ -470,31 +469,32 @@ C.new(0, 1).instance_variable_names # => ["@x", "@y"] NOTE: 定义于 `active_support/core_ext/object/instance_variables.rb`. -### Silencing Warnings, Streams, and Exceptions +### Silencing Warnings, Streams, 和 Exceptions -The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: +`silence_warnings`和`enable_warnings`方法都可以在其代码块里改变`$VERBOSE`的值,并在之后把值重置: ```ruby silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } ``` You can silence any stream while a block runs with `silence_stream`: +在通过`silence_stream`执行的代码块里,可以使任意流安静的运行: ```ruby silence_stream(STDOUT) do - # STDOUT is silent here + # 这里的代码不会输出到 STDOUT end ``` -The `quietly` method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses: +`quietly`方法可以使 STDOUT 和 STDERR 保持安静,即便在子进程里也如此: ```ruby quietly { system 'bundle install' } ``` -For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status. +例如,railties 测试组件会用到上述方法,来阻止普通消息与进度状态混到一起。 -Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is reraised: +也可以用`suppress`方法来使异常保持安静。方法接收任意数量的异常类。如果代码块的代码执行时报出异常,并且该异常`kind_of?`满足任一参数,`suppress`便会将异其捕获并安静的返回。否则会重新抛出该异常: ```ruby # If the user is locked the increment is lost, no big deal. @@ -507,9 +507,9 @@ NOTE: 定义于 `active_support/core_ext/kernel/reporting.rb`. ### `in?` -The predicate `in?` tests if an object is included in another object. An `ArgumentError` exception will be raised if the argument passed does not respond to `include?`. +判断式`in?`用于测试一个对象是否被包含在另一个对象里。当传入的参数无法响应`include?`时,会抛出`ArgumentError`异常。 -Examples of `in?`: +使用`in?`的例子: ```ruby 1.in?([1,2]) # => true @@ -520,14 +520,14 @@ Examples of `in?`: NOTE: 定义于 `active_support/core_ext/object/inclusion.rb`. -Extensions to `Module` +对`Module`的扩展 ---------------------- ### `alias_method_chain` -Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. +使用纯 Ruby 可以用方法环绕其他的方法,这种做法被称为环绕别名。 -For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionController::TestCase#process` this way in `test/test_helper.rb`: +例如,我们假设在功能测试里你希望参数都是字符串,就如同真实的请求中那样,但是同时你也希望对于数字和其他类型的值能够很方便的赋值。为了做到这点,你可以把`test/test_helper.rb`里的`ActionController::TestCase#process`方法像下面这样环绕: ```ruby ActionController::TestCase.class_eval do @@ -542,9 +542,9 @@ ActionController::TestCase.class_eval do end ``` -That's the method `get`, `post`, etc., delegate the work to. +`get`、`post`等最终会通过此方法执行。 -That technique has a risk, it could be the case that `:original_process` was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about: +这么做有一定风险,`:original_process`有可能已经被占用了。为了避免方法名发生碰撞,通常会添加标签来表明这是个关于什么的别名: ```ruby ActionController::TestCase.class_eval do @@ -557,7 +557,7 @@ ActionController::TestCase.class_eval do end ``` -The method `alias_method_chain` provides a shortcut for that pattern: +`alias_method_chain`为上述技巧提供了一个便捷之法: ```ruby ActionController::TestCase.class_eval do @@ -569,15 +569,15 @@ ActionController::TestCase.class_eval do end ``` -Rails uses `alias_method_chain` all over the code base. For example validations are added to `ActiveRecord::Base#save` by wrapping the method that way in a separate module specialized in validations. +Rails 源代码中随处可见`alias_method_chain`。例如`ActiveRecord::Base#save`里,就通过这种方式对方法进行环绕,从 validations 下一个专门的模块里为其增加了验证。 NOTE: 定义于 `active_support/core_ext/module/aliasing.rb`. -### Attributes +### 属性 #### `alias_attribute` -Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (one mnemonic is that they go in the same order as if you did an assignment): +模型属性包含读取器、写入器和判断式。只需添加一行代码,就可以为模型属性添加一个包含以上三个方法的别名。与其他别名方法一样,新名称充当第一个参数,原有名称是第二个参数(为了方便记忆,可以类比下赋值时的书写顺序)。 ```ruby class User < ActiveRecord::Base @@ -589,13 +589,13 @@ end NOTE: 定义于 `active_support/core_ext/module/aliasing.rb`. -#### Internal Attributes +#### 内部属性 -When you are defining an attribute in a class that is meant to be subclassed, name collisions are a risk. That's remarkably important for libraries. +当你在一个被继承的类里定义一条属性时,属性名称有可能会发生碰撞。这一点对许多库而言尤为重要。 -Active Support defines the macros `attr_internal_reader`, `attr_internal_writer`, and `attr_internal_accessor`. They behave like their Ruby built-in `attr_*` counterparts, except they name the underlying instance variable in a way that makes collisions less likely. +Active Support 定义了`attr_internal_reader`、`attr_internal_writer`和`attr_internal_accessor`这些类宏。它们的作用与 Ruby 内建的`attr_*`相当,只不过实例变量名多了下划线以避免碰撞。 -The macro `attr_internal` is a synonym for `attr_internal_accessor`: +类宏`attr_internal`与`attr_internal_accessor`是同义: ```ruby # library @@ -609,11 +609,11 @@ class MyCrawler < ThirdPartyLibrary::Crawler end ``` -In the previous example it could be the case that `:log_level` does not belong to the public interface of the library and it is only used for development. The client code, unaware of the potential conflict, subclasses and defines its own `:log_level`. Thanks to `attr_internal` there's no collision. +上述例子里的情况可能是,`:log_level`并不属于库的公共接口,而是只用于开发。而在客户代码里,由于不知道可能出现的冲突,便在子类里又定义了`:log_level`。多亏了`attr_internal`才没有出项碰撞。 -By default the internal instance variable is named with a leading underscore, `@_log_level` in the example above. That's configurable via `Module.attr_internal_naming_format` though, you can pass any `sprintf`-like format string with a leading `@` and a `%s` somewhere, which is where the name will be placed. The default is `"@_%s"`. +默认情况下,内部实例变量名以下划线开头,如上例中即为`@_log_level`。不过这点可以通过`Module.attr_internal_naming_format`进行配置,你可以传入任何`sprintf`这一类的格式化字符串,并在开头加上`@`,同时还要加上`%s`表示变量名称的位置。默认值为`"@_%s"`。 -Rails uses internal attributes in a few spots, for examples for views: +Rails 在若干地方使用了内部属性,比如在视图层: ```ruby module ActionView @@ -629,9 +629,9 @@ NOTE: 定义于 `active_support/core_ext/module/attr_internal.rb`. #### Module Attributes -The macros `mattr_reader`, `mattr_writer`, and `mattr_accessor` are the same as the `cattr_*` macros defined for class. In fact, the `cattr_*` macros are just aliases for the `mattr_*` macros. Check [Class Attributes](#class-attributes). +类宏`mattr_reader`、`mattr_writer`和`mattr_accessor`与为类定义的`cattr_*`是相同的。实际上,`cattr_*`系列的类宏只不过是`mattr_*`这些类宏的别名。详见[Class Attributes](#class-attributes)。 -For example, the dependencies mechanism uses them: +例如,依赖性机制就用到了它们: ```ruby module ActiveSupport @@ -658,7 +658,7 @@ NOTE: 定义于 `active_support/core_ext/module/attribute_accessors.rb`. #### `parent` -The `parent` method on a nested named module returns the module that contains its corresponding constant: +对一个嵌套的模块调用`parent`方法,会返回其相应的常量: ```ruby module X @@ -673,15 +673,15 @@ X::Y::Z.parent # => X::Y M.parent # => X::Y ``` -If the module is anonymous or belongs to the top-level, `parent` returns `Object`. +如果这个模块是匿名的或者属于顶级作用域, `parent`会返回`Object`。 -WARNING: Note that in that case `parent_name` returns `nil`. +WARNING: 若有上述情况,则`parent_name`会返回`nil`。 NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. #### `parent_name` -The `parent_name` method on a nested named module returns the fully-qualified name of the module that contains its corresponding constant: +对一个嵌套的模块调用`parent_name`方法,会返回其相应常量的完全限定名: ```ruby module X @@ -696,15 +696,15 @@ X::Y::Z.parent_name # => "X::Y" M.parent_name # => "X::Y" ``` -For top-level or anonymous modules `parent_name` returns `nil`. +定义在顶级作用域里的模块或匿名的模块,`parent_name`会返回`nil`。 -WARNING: Note that in that case `parent` returns `Object`. +WARNING: 若有上述情况,则`parent`返回`Object`。 NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. #### `parents` -The method `parents` calls `parent` on the receiver and upwards until `Object` is reached. The chain is returned in an array, from bottom to top: +`parents`方法会对接收者调用`parent`,并向上追溯直至`Object`。之后所得结果链按由低到高顺序组成一个数组被返回。 ```ruby module X @@ -721,10 +721,10 @@ M.parents # => [X::Y, X, Object] NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. -### Constants +### 常量 -The method `local_constants` returns the names of the constants that have been defined in the receiver module: +`local_constants`方法返回在接收者模块中定义的常量。 ```ruby module X @@ -740,11 +740,11 @@ X.local_constants # => [:X1, :X2, :Y] X::Y.local_constants # => [:Y1, :X1] ``` -The names are returned as symbols. +常量名会作为符号被返回。 NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. -#### Qualified Constant Names +#### 限定常量名 The standard methods `const_defined?`, `const_get` , and `const_set` accept bare constant names. Active Support extends this API to be able to pass From e53ec413035045729875fb1f5aa54014bf174182 Mon Sep 17 00:00:00 2001 From: n19270 Date: Tue, 2 Sep 2014 19:18:37 +0800 Subject: [PATCH 003/133] fix a typo --- source/zh-CN/active_record_validations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/active_record_validations.md b/source/zh-CN/active_record_validations.md index af92116..6b3285f 100644 --- a/source/zh-CN/active_record_validations.md +++ b/source/zh-CN/active_record_validations.md @@ -43,7 +43,7 @@ Person.create(name: nil).valid? # => false ### 什么时候做数据验证? -在 Active Record 中对象有两种状态:一种在数据库中有对应的记录,一种没有。新建的对象(例如,使用 `new` 方法)还不属于数据库。在对象上调用 `save` 方法后,才会把对象存入相应的数据表。Active Record 使用实例方法 `new_record?` 判断对象是否已经存入数据库。加入有下面这个简单的 Active Record 类: +在 Active Record 中对象有两种状态:一种在数据库中有对应的记录,一种没有。新建的对象(例如,使用 `new` 方法)还不属于数据库。在对象上调用 `save` 方法后,才会把对象存入相应的数据表。Active Record 使用实例方法 `new_record?` 判断对象是否已经存入数据库。假如有下面这个简单的 Active Record 类: ```ruby class Person < ActiveRecord::Base From 53f0238e736db987c410bdaee194519901cde365 Mon Sep 17 00:00:00 2001 From: Juanito Fatas Date: Wed, 3 Sep 2014 22:18:05 +0800 Subject: [PATCH 004/133] Regular maintenance. --- source/2_2_release_notes.md | 2 +- source/2_3_release_notes.md | 2 +- source/3_0_release_notes.md | 2 +- source/3_1_release_notes.md | 5 +- source/3_2_release_notes.md | 5 +- source/4_0_release_notes.md | 5 +- source/4_1_release_notes.md | 8 +- source/4_2_release_notes.md | 403 +++++++++++++++++++-- source/action_mailer_basics.md | 32 +- source/action_view_overview.md | 34 +- source/active_job_basics.md | 285 +++++++++++++++ source/active_record_postgresql.md | 3 +- source/active_record_querying.md | 12 +- source/association_basics.md | 16 +- source/caching_with_rails.md | 7 +- source/configuring.md | 6 +- source/contributing_to_ruby_on_rails.md | 2 +- source/debugging_rails_applications.md | 2 +- source/development_dependencies_install.md | 86 +++-- source/form_helpers.md | 32 +- source/generators.md | 17 + source/getting_started.md | 12 +- source/initialization.md | 2 - source/layouts_and_rendering.md | 7 +- source/nested_model_forms.md | 5 +- source/plugins.md | 42 +-- source/security.md | 8 +- source/testing.md | 12 +- source/upgrading_ruby_on_rails.md | 76 +++- 29 files changed, 929 insertions(+), 201 deletions(-) create mode 100644 source/active_job_basics.md diff --git a/source/2_2_release_notes.md b/source/2_2_release_notes.md index 522f628..b11aaa1 100644 --- a/source/2_2_release_notes.md +++ b/source/2_2_release_notes.md @@ -1,7 +1,7 @@ Ruby on Rails 2.2 Release Notes =============================== -Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub. Along with Rails, 2.2 marks the launch of the [Ruby on Rails Guides](http://guides.rubyonrails.org/), the first results of the ongoing [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide). This site will deliver high-quality documentation of the major features of Rails. diff --git a/source/2_3_release_notes.md b/source/2_3_release_notes.md index 52eeb4c..20566c9 100644 --- a/source/2_3_release_notes.md +++ b/source/2_3_release_notes.md @@ -1,7 +1,7 @@ Ruby on Rails 2.3 Release Notes =============================== -Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/master) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components. +Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/2-3-stable) in the main Rails repository on GitHub or review the `CHANGELOG` files for the individual Rails components. -------------------------------------------------------------------------------- diff --git a/source/3_0_release_notes.md b/source/3_0_release_notes.md index aec3a38..8122d6c 100644 --- a/source/3_0_release_notes.md +++ b/source/3_0_release_notes.md @@ -15,7 +15,7 @@ Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is g On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices. -These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the [list of commits](http://github.com/rails/rails/commits/3-0-stable) in the main Rails repository on GitHub. -------------------------------------------------------------------------------- diff --git a/source/3_1_release_notes.md b/source/3_1_release_notes.md index 7626296..b7ed285 100644 --- a/source/3_1_release_notes.md +++ b/source/3_1_release_notes.md @@ -8,7 +8,10 @@ Highlights in Rails 3.1: * Assets Pipeline * jQuery as the default JavaScript library -This release notes cover the major changes, but don't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +These release notes cover only the major changes. To learn about various bug +fixes and changes, please refer to the change logs or check out the [list of +commits](https://github.com/rails/rails/commits/3-1-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- diff --git a/source/3_2_release_notes.md b/source/3_2_release_notes.md index 2416e1a..c5db026 100644 --- a/source/3_2_release_notes.md +++ b/source/3_2_release_notes.md @@ -8,7 +8,10 @@ Highlights in Rails 3.2: * Automatic Query Explains * Tagged Logging -These release notes cover the major changes, but do not include each bug-fix and changes. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/3-2-stable) in the main Rails repository on GitHub. +These release notes cover only the major changes. To learn about various bug +fixes and changes, please refer to the change logs or check out the [list of +commits](https://github.com/rails/rails/commits/3-2-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- diff --git a/source/4_0_release_notes.md b/source/4_0_release_notes.md index 19c6902..1aaf5eb 100644 --- a/source/4_0_release_notes.md +++ b/source/4_0_release_notes.md @@ -8,7 +8,10 @@ Highlights in Rails 4.0: * Turbolinks * Russian Doll Caching -These release notes cover only the major changes. To know about various bug fixes and changes, please refer to the change logs or check out the [list of commits](https://github.com/rails/rails/commits/master) in the main Rails repository on GitHub. +These release notes cover only the major changes. To learn about various bug +fixes and changes, please refer to the change logs or check out the [list of +commits](https://github.com/rails/rails/commits/4-0-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- diff --git a/source/4_1_release_notes.md b/source/4_1_release_notes.md index 5f4bdaa..52a5acb 100644 --- a/source/4_1_release_notes.md +++ b/source/4_1_release_notes.md @@ -8,10 +8,10 @@ Highlights in Rails 4.1: * Action Pack variants * Action Mailer previews -These release notes cover only the major changes. To know about various bug -fixes and changes, please refer to the change logs or check out the -[list of commits](https://github.com/rails/rails/commits/master) in the main -Rails repository on GitHub. +These release notes cover only the major changes. To learn about various bug +fixes and changes, please refer to the change logs or check out the [list of +commits](https://github.com/rails/rails/commits/4-1-stable) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- diff --git a/source/4_2_release_notes.md b/source/4_2_release_notes.md index a39dd9a..ae8ef34 100644 --- a/source/4_2_release_notes.md +++ b/source/4_2_release_notes.md @@ -3,20 +3,28 @@ Ruby on Rails 4.2 Release Notes Highlights in Rails 4.2: -These release notes cover only the major changes. To know about various bug -fixes and changes, please refer to the change logs or check out the -[list of commits](https://github.com/rails/rails/commits/master) in the main -Rails repository on GitHub. +* Active Job, Action Mailer #deliver_later +* Adequate Record +* Web Console +* Foreign key support + +These release notes cover only the major changes. To learn about various bug +fixes and changes, please refer to the change logs or check out the [list of +commits](https://github.com/rails/rails/commits/master) in the main Rails +repository on GitHub. -------------------------------------------------------------------------------- +NOTE: This document is a work in progress, please help to improve this by sending +a [pull request](https://github.com/rails/rails/edit/master/guides/source/4_2_release_notes.md). + Upgrading to Rails 4.2 ---------------------- If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 4.1 in case you haven't and make sure your application still runs as expected before attempting -an update to Rails 4.2. A list of things to watch out for when upgrading is +to upgrade to Rails 4.2. A list of things to watch out for when upgrading is available in the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2) guide. @@ -25,6 +33,51 @@ guide. Major Features -------------- +### Active Job, Action Mailer #deliver_later + +Active Job is a new framework in Rails 4.2. It is an adapter layer on top of +queuing systems like [Resque](https://github.com/resque/resque), [Delayed Job](https://github.com/collectiveidea/delayed_job), [Sidekiq](https://github.com/mperham/sidekiq), and more. You can write your +jobs with the Active Job API, and it'll run on all these queues with no changes +(it comes pre-configured with an inline runner). + +Building on top of Active Job, Action Mailer now comes with a `#deliver_later` +method, which adds your email to be sent as a job to a queue, so it doesn't +bog down the controller or model. + +The new GlobalID library makes it easy to pass Active Record objects to jobs by +serializing them in a generic form. This means you no longer have to manually +pack and unpack your Active Records by passing ids. Just give the job the +straight Active Record object, and it'll serialize it using GlobalID, and +deserialize it at run time. + +### Adequate Record + +Rails 4.2 comes with a performance improvement feature called Adequate Record +for Active Record. A lot of common queries are now up to twice as fast in Rails +4.2! + +TODO: add some technical details + +### Web Console + +New applications generated from Rails 4.2 now come with the Web Console gem by +default. + +Web Console is a set of debugging tools for your Rails application. It will add +an interactive console on every error page, a `console` view helper and a VT100 +compatible terminal. + +The interactive console on the error pages lets you execute code where the +exception originated. It's quite handy to introspect the state that led to the +error. + +The `console` view helper launches an interactive console within the context of +the view where it is invoked. + +Finally, you can launch a VT100 terminal that runs `rails console`. If you need +to create or modify existing test data, you can do that straight from the +browser. + ### Foreign key support The migration DSL now supports adding and removing foreign keys. They are dumped @@ -52,6 +105,135 @@ and for a full description. +Incompatibilities +----------------- + +Previously deprecated functionality has been removed. Please refer to the +individual components for new deprecations in this release. + +The following changes may require immediate action upon upgrade. + +### `respond_with` / class-level `respond_to` + +`respond_with` and the corresponding class-level `respond_to` have been moved to +the `responders` gem. To use the following, add `gem 'responders', '~> 2.0'` to +your Gemfile: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + respond_to :html, :json + + def show + @user = User.find(params[:id]) + respond_with @user + end +end +``` + +Instance-level `respond_to` is unaffected: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + def show + @user = User.find(params[:id]) + respond_to do |format| + format.html + format.json { render json: @user } + end + end +end +``` + +### Production logging + +The default log level in the `production` environment is now `:debug`. This +makes it consistent with the other environments, and ensures plenty of +information is available to diagnose problems. + +It can be returned to the previous level, `:info`, in the environment +configuration: + +```ruby +# config/environments/production.rb + +# Decrease the log volume. +config.log_level = :info +``` + +### HTML Sanitizer + +The HTML sanitizer has been replaced with a new, more robust, implementation +built upon Loofah and Nokogiri. The new sanitizer is (TODO: betterer). + +With a new sanitization algorithm, the sanitized output will change for certain +pathological inputs. + +If you have particular need for the exact output of the old sanitizer, you can +add `rails-deprecated_sanitizer` to your Gemfile, and it will automatically +replace the new implementation. Because it is opt-in, the legacy gem will not +give deprecation warnings. + +`rails-deprecated_sanitizer` will be supported for Rails 4.2 only; it will not +be maintained for Rails 5.0. + +See [the blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/) +for more detail on the changes in the new sanitizer. + +### `assert_select` + +`assert_select` is now based on Nokogiri, making it (TODO: betterer). + +As a result, some previously-valid selectors are now unsupported. If your +application is using any of these spellings, you will need to update them: + +* Values in attribute selectors may need to be quoted if they contain + non-alphanumeric characters. + + ``` + a[href=/] => a[href="https://github.com/"] + a[href$=/] => a[href$="/"] + ``` + +* DOMs built from HTML source containing invalid HTML with improperly + nested elements may differ. + + For example: + + ``` ruby + # content:

+ + # before: + assert_select('div > i') # => true + assert_select('div > p') # => false + assert_select('i > p') # => true + + # now: + assert_select('div > i') # => true + assert_select('div > p') # => true + assert_select('i > p') # => false + ``` + +* If the data selected contains entities, the value selected for comparison + used to be raw (e.g. `AT&T`), and now is evaluated + (e.g. `AT&T`). + + ``` ruby + # content:

AT&T

+ + # before: + assert_select('p', 'AT&T') # => true + assert_select('p', 'AT&T') # => false + + # now: + assert_select('p', 'AT&T') # => true + assert_select('p', 'AT&T') # => false + ``` + + Railties -------- @@ -69,11 +251,60 @@ Please refer to the [Changelog][railties] for detailed changes. ### Notable changes -* Introduced `--skip-gems` option in the app generator to skip gems such as - `turbolinks` and `coffee-rails` that does not have their own specific flags. +* Introduced `web-console` in the default application Gemfile. + ([Pull Request](https://github.com/rails/rails/pull/11667)) + +* Added a `required` option to the model generator for associations. + ([Pull Request](https://github.com/rails/rails/pull/16062)) + +* Introduced an `after_bundle` callback for use in Rails templates. + ([Pull Request](https://github.com/rails/rails/pull/16359)) + +* Introduced the `x` namespace for defining custom configuration options: + + ```ruby + # config/environments/production.rb + config.x.payment_processing.schedule = :daily + config.x.payment_processing.retries = 3 + config.x.super_debugger = true + ``` + + These options are then available through the configuration object: + + ```ruby + Rails.configuration.x.payment_processing.schedule # => :daily + Rails.configuration.x.payment_processing.retries # => 3 + Rails.configuration.x.super_debugger # => true + ``` + + ([Commit](https://github.com/rails/rails/commit/611849772dd66c2e4d005dcfe153f7ce79a8a7db)) + +* Introduced `Rails::Application.config_for` to load a configuration for the + current environment. + + ```ruby + # config/exception_notification.yml: + production: + url: http://127.0.0.1:8080 + namespace: my_app_production + development: + url: http://localhost:3001 + namespace: my_app_development + + # config/production.rb + MyApp::Application.configure do + config.middleware.use ExceptionNotifier, config_for(:exception_notification) + end + ``` + + ([Pull Request](https://github.com/rails/rails/pull/16129)) + +* Introduced a `--skip-gems` option in the app generator to skip gems such as + `turbolinks` and `coffee-rails` that do not have their own specific flags. ([Commit](https://github.com/rails/rails/commit/10565895805887d4faf004a6f71219da177f78b7)) -* Introduced `bin/setup` script to bootstrap an application. +* Introduced a `bin/setup` script to enable automated setup code when + bootstrapping an application. ([Pull Request](https://github.com/rails/rails/pull/15189)) * Changed default value for `config.assets.digest` to `true` in development. @@ -82,20 +313,33 @@ Please refer to the [Changelog][railties] for detailed changes. * Introduced an API to register new extensions for `rake notes`. ([Pull Request](https://github.com/rails/rails/pull/14379)) -* Introduced `Rails.gem_version` as a convenience method to return `Gem::Version.new(Rails.version)`. +* Introduced `Rails.gem_version` as a convenience method to return + `Gem::Version.new(Rails.version)`. ([Pull Request](https://github.com/rails/rails/pull/14101)) -* Introduced an `after_bundle` callback in the Rails templates. - ([Pull Request](https://github.com/rails/rails/pull/16359)) - Action Pack ----------- Please refer to the [Changelog][action-pack] for detailed changes. +### Removals + +* `respond_with` and the class-level `respond_to` were removed from Rails and + moved to the `responders` gem (version 2.0). Add `gem 'responders', '~> 2.0'` + to your `Gemfile` to continue using these features. + ([Pull Request](https://github.com/rails/rails/pull/16526)) + +* Removed deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError` + in favor of `AbstractController::Helpers::MissingHelperError`. + ([Commit](https://github.com/rails/rails/commit/a1ddde15ae0d612ff2973de9cf768ed701b594e8)) + ### Deprecations +* Deprecated `assert_tag`, `assert_no_tag`, `find_tag` and `find_all_tag` in + favor of `assert_select`. + ([Commit](https://github.com/rails/rails-dom-testing/commit/b12850bc5ff23ba4b599bf2770874dd4f11bf750)) + * Deprecated support for setting the `:to` option of a router to a symbol or a string that does not contain a `#` character: @@ -110,6 +354,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. ### Notable changes +* Rails will now automatically include the template's digest in ETags. + ([Pull Request](https://github.com/rails/rails/pull/16527)) + * `render nothing: true` or rendering a `nil` body no longer add a single space padding to the response body. ([Pull Request](https://github.com/rails/rails/pull/14883)) @@ -119,8 +366,8 @@ Please refer to the [Changelog][action-pack] for detailed changes. is `['controller', 'action']`. ([Pull Request](https://github.com/rails/rails/pull/15933)) -* The `*_filter` family methods has been removed from the documentation. Their - usage are discouraged in favor of the `*_action` family methods: +* The `*_filter` family methods have been removed from the documentation. Their + usage is discouraged in favor of the `*_action` family methods: ``` after_filter => after_action @@ -138,9 +385,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. skip_filter => skip_action_callback ``` - If your application is depending on these methods, you should use the + If your application currently depends on these methods, you should use the replacement `*_action` methods instead. These methods will be deprecated in - the future and eventually removed from Rails. + the future and will eventually be removed from Rails. (Commit [1](https://github.com/rails/rails/commit/6c5f43bab8206747a8591435b2aa0ff7051ad3de), [2](https://github.com/rails/rails/commit/489a8f2a44dc9cea09154ee1ee2557d1f037c7d4)) @@ -148,19 +395,39 @@ Please refer to the [Changelog][action-pack] for detailed changes. * Added HTTP method `MKCALENDAR` from RFC-4791 ([Pull Request](https://github.com/rails/rails/pull/15121)) -* `*_fragment.action_controller` notifications now include the controller and action name - in the payload. +* `*_fragment.action_controller` notifications now include the controller + and action name in the payload. ([Pull Request](https://github.com/rails/rails/pull/14137)) * Segments that are passed into URL helpers are now automatically escaped. ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f)) -* Improved Routing Error page with fuzzy matching for route search. +* Improved the Routing Error page with fuzzy matching for route search. ([Pull Request](https://github.com/rails/rails/pull/14619)) -* Added option to disable logging of CSRF failures. +* Added an option to disable logging of CSRF failures. ([Pull Request](https://github.com/rails/rails/pull/14280)) +* When the Rails server is set to serve static assets, gzip assets will now be + served if the client supports it and a pre-generated gzip file (.gz) is on disk. + By default the asset pipeline generates `.gz` files for all compressible assets. + Serving gzip files minimizes data transfer and speeds up asset requests. Always + [use a CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) if you are + serving assets from your Rails server in production. + ([Pull Request](https://github.com/rails/rails/pull/16466)) + +* The way `assert_select` works has changed; specifically a different library + is used to interpret css selectors, build the transient DOM that the + selectors are applied against, and to extract the data from that DOM. These + changes should only affect edge cases. Examples: + * Values in attribute selectors may need to be quoted if they contain + non-alphanumeric characters. + * DOMs built from HTML source containing invalid HTML with improperly + nested elements may differ. + * If the data selected contains entities, the value selected for comparison + used to be raw (e.g. `AT&T`), and now is evaluated + (e.g. `AT&T`). + Action View ------------- @@ -174,24 +441,45 @@ Please refer to the [Changelog][action-view] for detailed changes. where to find views. ([Pull Request](https://github.com/rails/rails/pull/15026)) -* Deprecated `ActionView::Digestor#digest(name, format, finder, options = {})`, - arguments should be passed as a hash instead. +* Deprecated `ActionView::Digestor#digest(name, format, finder, options = {})`. + Arguments should be passed as a hash instead. ([Pull Request](https://github.com/rails/rails/pull/14243)) ### Notable changes +* Introduced a `#{partial_name}_iteration` special local variable for use with + partials that are rendered with a collection. It provides access to the + current state of the iteration via the `#index`, `#size`, `#first?` and + `#last?` methods. + ([Pull Request](https://github.com/rails/rails/pull/7698)) + * The form helpers no longer generate a `
` element with inline CSS around the hidden fields. ([Pull Request](https://github.com/rails/rails/pull/14738)) +* Placeholder I18n follows the same convention as `label` I18n. + ([Pull Request](https://github.com/rails/rails/pull/16438)) + Action Mailer ------------- Please refer to the [Changelog][action-mailer] for detailed changes. +### Deprecations + +* Deprecated `*_path` helpers in mailers. Always use `*_url` helpers instead. + ([Pull Request](https://github.com/rails/rails/pull/15840)) + +* Deprecated `deliver` / `deliver!` in favour of `deliver_now` / `deliver_now!`. + ([Pull Request](https://github.com/rails/rails/pull/16582)) + ### Notable changes +* Introduced `deliver_later` which enqueues a job on the application's queue + to deliver emails asynchronously. + ([Pull Request](https://github.com/rails/rails/pull/16485)) + * Added the `show_previews` configuration option for enabling mailer previews outside of the development environment. ([Pull Request](https://github.com/rails/rails/pull/15970)) @@ -200,9 +488,7 @@ Please refer to the [Changelog][action-mailer] for detailed changes. Active Record ------------- -Please refer to the -[Changelog](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) -for detailed changes. +Please refer to the [Changelog][active-record] for detailed changes. ### Removals @@ -218,11 +504,22 @@ for detailed changes. * Removed unused `:timestamp` type. Transparently alias it to `:datetime` in all cases. Fixes inconsistencies when column types are sent outside of - `ActiveRecord`, such as for XML Serialization. + `ActiveRecord`, such as for XML serialization. ([Pull Request](https://github.com/rails/rails/pull/15184)) ### Deprecations +* Deprecated swallowing of errors inside `after_commit` and `after_rollback`. + ([Pull Request](https://github.com/rails/rails/pull/16537)) + +* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use + `DatabaseTasks.load_schema_current` instead. + ([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608)) + +* Deprecated `Reflection#source_macro` without replacement as it is no longer + needed in Active Record. + ([Pull Request](https://github.com/rails/rails/pull/16373)) + * Deprecated broken support for automatic detection of counter caches on `has_many :through` associations. You should instead manually specify the counter cache on the `has_many` and `belongs_to` associations for the @@ -260,6 +557,13 @@ for detailed changes. ### Notable changes +* The PostgreSQL adapter now supports the `JSONB` datatype in PostgreSQL 9.4+. + ([Pull Request](https://github.com/rails/rails/pull/16220)) + +* The `#references` method in migrations now supports a `type` option for + specifying the type of the foreign key (e.g. `:uuid`). + ([Pull Request](https://github.com/rails/rails/pull/16231)) + * Added a `:required` option to singular associations, which defines a presence validation on the association. ([Pull Request](https://github.com/rails/rails/pull/16056)) @@ -295,7 +599,7 @@ for detailed changes. * `sqlite3:///some/path` now resolves to the absolute system path `/some/path`. For relative paths, use `sqlite3:some/path` instead. (Previously, `sqlite3:///some/path` resolved to the relative path - `some/path`. This behaviour was deprecated on Rails 4.1.) + `some/path`. This behaviour was deprecated on Rails 4.1). ([Pull Request](https://github.com/rails/rails/pull/14569)) * Introduced `#validate` as an alias for `#valid?`. @@ -323,13 +627,27 @@ Please refer to the [Changelog][active-model] for detailed changes. ### Removals * Removed deprecated `Validator#setup` without replacement. - ([Pull Request](https://github.com/rails/rails/pull/15617)) + ([Pull Request](https://github.com/rails/rails/pull/10716)) + +### Deprecations + +* Deprecated `reset_#{attribute}` in favor of `restore_#{attribute}`. + ([Pull Request](https://github.com/rails/rails/pull/16180)) + +* Deprecated `ActiveModel::Dirty#reset_changes` in favor of + `#clear_changes_information`. + ([Pull Request](https://github.com/rails/rails/pull/16180)) ### Notable changes -* Introduced `undo_changes` method in `ActiveModel::Dirty` to restore the - changed (dirty) attributes to their previous values. - ([Pull Request](https://github.com/rails/rails/pull/14861)) +* Introduced the `restore_attributes` method in `ActiveModel::Dirty` to restore + the changed (dirty) attributes to their previous values. + (Pull Request [1](https://github.com/rails/rails/pull/14861), + [2](https://github.com/rails/rails/pull/16180)) + +* `has_secure_password` no longer disallow blank passwords (i.e. passwords + that contains only spaces) by default. + ([Pull Request](https://github.com/rails/rails/pull/16412)) * `has_secure_password` now verifies that the given password is less than 72 characters if validations are enabled. @@ -355,6 +673,10 @@ Please refer to the [Changelog][active-support] for detailed changes. ### Deprecations +* Deprecated `Kernel#silence_stderr`, `Kernel#capture` and `Kernel#quietly` + without replacement. + ([Pull Request](https://github.com/rails/rails/pull/13392)) + * Deprecated `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead. ([Pull Request](https://github.com/rails/rails/pull/14271)) @@ -365,6 +687,23 @@ Please refer to the [Changelog][active-support] for detailed changes. ### Notable changes +* The `travel_to` test helper now truncates the `usec` component to 0. + ([Commit](https://github.com/rails/rails/commit/9f6e82ee4783e491c20f5244a613fdeb4024beb5)) + +* `ActiveSupport::TestCase` now randomizes the order that test cases are ran + by default. + ([Commit](https://github.com/rails/rails/commit/6ffb29d24e05abbd9ffe3ea974140d6c70221807)) + +* Introduced `Object#itself` as an identity function. + (Commit [1](https://github.com/rails/rails/commit/702ad710b57bef45b081ebf42e6fa70820fdd810), + [2](https://github.com/rails/rails/commit/64d91122222c11ad3918cc8e2e3ebc4b0a03448a)) + +* `Object#with_options` can now be used without an explicit receiver. + ([Pull Request](https://github.com/rails/rails/pull/16339)) + +* Introduced `String#truncate_words` to truncate a string by a number of words. + ([Pull Request](https://github.com/rails/rails/pull/16190)) + * Added `Hash#transform_values` and `Hash#transform_values!` to simplify a common pattern where the values of a hash must change, but the keys are left the same. @@ -373,7 +712,7 @@ Please refer to the [Changelog][active-support] for detailed changes. * The `humanize` inflector helper now strips any leading underscores. ([Commit](https://github.com/rails/rails/commit/daaa21bc7d20f2e4ff451637423a25ff2d5e75c7)) -* Introduce `Concern#class_methods` as an alternative to +* Introduced `Concern#class_methods` as an alternative to `module ClassMethods`, as well as `Kernel#concern` to avoid the `module Foo; extend ActiveSupport::Concern; end` boilerplate. ([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad)) diff --git a/source/action_mailer_basics.md b/source/action_mailer_basics.md index 9ad9319..f981d0d 100644 --- a/source/action_mailer_basics.md +++ b/source/action_mailer_basics.md @@ -159,7 +159,10 @@ $ bin/rake db:migrate Now that we have a user model to play with, we will just edit the `app/controllers/users_controller.rb` make it instruct the `UserMailer` to deliver an email to the newly created user by editing the create action and inserting a -call to `UserMailer.welcome_email` right after the user is successfully saved: +call to `UserMailer.welcome_email` right after the user is successfully saved. + +Action Mailer is nicely integrated with Active Job so you can send emails outside +of the request-response cycle, so the user doesn't have to wait on it: ```ruby class UsersController < ApplicationController @@ -171,7 +174,7 @@ class UsersController < ApplicationController respond_to do |format| if @user.save # Tell the UserMailer to send a welcome email after save - UserMailer.welcome_email(@user).deliver + UserMailer.welcome_email(@user).deliver_later format.html { redirect_to(@user, notice: 'User was successfully created.') } format.json { render json: @user, status: :created, location: @user } @@ -184,8 +187,29 @@ class UsersController < ApplicationController end ``` -The method `welcome_email` returns a `Mail::Message` object which can then just -be told `deliver` to send itself out. +NOTE: Active Job's default behavior is to execute jobs ':inline'. So, you can use +`deliver_later` now to send emails, and when you later decide to start sending +them from a background job, you'll only need to set up Active Job to use a queueing +backend (Sidekiq, Resque, etc). + +If you want to send emails right away (from a cronjob for example) just call +`deliver_now`: + +```ruby +class SendWeeklySummary + def run + User.find_each do |user| + UserMailer.weekly_summary(user).deliver_now + end + end +end +``` + +The method `welcome_email` returns a `ActionMailer::MessageDelivery` object which +can then just be told `deliver_now` or `deliver_later` to send itself out. The +`ActionMailer::MessageDelivery` object is just a wrapper around a `Mail::Message`. If +you want to inspect, alter or do anything else with the `Mail::Message` object you can +access it with the `message` method on the `ActionMailer::MessageDelivery` object. ### Auto encoding header values diff --git a/source/action_view_overview.md b/source/action_view_overview.md index ef7ef5a..683e633 100644 --- a/source/action_view_overview.md +++ b/source/action_view_overview.md @@ -44,18 +44,18 @@ $ bin/rails generate scaffold article There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above. For example, the index controller action of the `articles_controller.rb` will use the `index.html.erb` view file in the `app/views/articles` directory. -The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of these three components. +The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Within this guide you will find more detailed documentation about each of these three components. Templates, Partials and Layouts ------------------------------- -As mentioned before, the final HTML output is a composition of three Rails elements: `Templates`, `Partials` and `Layouts`. -Below is a brief overview of each one of them. +As mentioned, the final HTML output is a composition of three Rails elements: `Templates`, `Partials` and `Layouts`. +Below is a brief overview of each of them. ### Templates -Action View templates can be written in several ways. If the template file has a `.erb` extension then it uses a mixture of ERB (included in Ruby) and HTML. If the template file has a `.builder` extension then a fresh instance of `Builder::XmlMarkup` library is used. +Action View templates can be written in several ways. If the template file has a `.erb` extension then it uses a mixture of ERB (Embedded Ruby) and HTML. If the template file has a `.builder` extension then the `Builder::XmlMarkup` library is used. Rails supports multiple template systems and uses a file extension to distinguish amongst them. For example, an HTML file using the ERB template system will have `.html.erb` as a file extension. @@ -72,7 +72,7 @@ Consider the following loop for names: <% end %> ``` -The loop is set up in regular embedding tags (`<% %>`) and the name is written using the output embedding tags (`<%= %>`). Note that this is not just a usage suggestion, for regular output functions like `print` or `puts` won't work with ERB templates. So this would be wrong: +The loop is set up using regular embedding tags (`<% %>`) and the name is inserted using the output embedding tags (`<%= %>`). Note that this is not just a usage suggestion: regular output functions such as `print` and `puts` won't be rendered to the view with ERB templates. So this would be wrong: ```html+erb <%# WRONG %> @@ -231,7 +231,7 @@ The `object` and `as` options can also be used together: #### Rendering Collections -It is very common that a template needs to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. +It is very common that a template will need to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. So this example for rendering all the products: @@ -247,7 +247,7 @@ can be rewritten in a single line: <%= render partial: "product", collection: @products %> ``` -When a partial is called like this (eg. with a collection), the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is `_product`, and within it you can refer to `product` to get the instance that is being rendered. +When a partial is called with a collection, the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is `_product`, and within it you can refer to `product` to get the collection member that is being rendered. You can use a shorthand syntax for rendering collections. Assuming `@products` is a collection of `Product` instances, you can simply write the following to produce the same result: @@ -255,7 +255,7 @@ You can use a shorthand syntax for rendering collections. Assuming `@products` i <%= render @products %> ``` -Rails determines the name of the partial to use by looking at the model name in the collection, `Product` in this case. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection. +Rails determines the name of the partial to use by looking at the model name in the collection, `Product` in this case. In fact, you can even render a collection made up of instances of different models using this shorthand, and Rails will choose the proper partial for each member of the collection. #### Spacer Templates @@ -269,14 +269,14 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe ### Layouts -Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails application has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. +Layouts can be used to render a common view template around the results of Rails controller actions. Typically, a Rails application will have a couple of layouts that pages will be rendered within. For example, a site might have one layout for a logged in user and another for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us" pages. You would expect each layout to have a different look and feel. You can read about layouts in more detail in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. Partial Layouts --------------- -Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion. +Partials can have their own layouts applied to them. These layouts are different from those applied to a controller action, but they work in a similar fashion. -Let's say we're displaying an article on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Article`: +Let's say we're displaying an article on a page which should be wrapped in a `div` for display purposes. Firstly, we'll create a new `Article`: ```ruby Article.create(body: 'Partial Layouts are cool!') @@ -495,7 +495,7 @@ image_url("https://github.com/edit.png") # => http://www.example.com/assets/edit.png #### image_tag -Returns an html image tag for the source. The source can be a full path or a file that exists in your `app/assets/images` directory. +Returns an HTML image tag for the source. The source can be a full path or a file that exists in your `app/assets/images` directory. ```ruby image_tag("icon.png") # => Icon @@ -503,7 +503,7 @@ image_tag("icon.png") # => Icon #### javascript_include_tag -Returns an html script tag for each of the sources provided. You can pass in the filename (`.js` extension is optional) of JavaScript files that exist in your `app/assets/javascripts` directory for inclusion into the current page or you can pass the full path relative to your document root. +Returns an HTML script tag for each of the sources provided. You can pass in the filename (`.js` extension is optional) of JavaScript files that exist in your `app/assets/javascripts` directory for inclusion into the current page or you can pass the full path relative to your document root. ```ruby javascript_include_tag "common" # => @@ -736,7 +736,7 @@ distance_of_time_in_words(Time.now, Time.now + 15.seconds, include_seconds: true #### select_date -Returns a set of html select-tags (one for year, month, and day) pre-selected with the `date` provided. +Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the `date` provided. ```ruby # Generates a date select that defaults to the date provided (six days after today) @@ -748,7 +748,7 @@ select_date() #### select_datetime -Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the `datetime` provided. +Returns a set of HTML select-tags (one for year, month, day, hour, and minute) pre-selected with the `datetime` provided. ```ruby # Generates a datetime select that defaults to the datetime provided (four days after today) @@ -808,7 +808,7 @@ select_second(Time.now + 16.minutes) #### select_time -Returns a set of html select-tags (one for hour and minute). +Returns a set of HTML select-tags (one for hour and minute). ```ruby # Generates a time select that defaults to the time provided @@ -1526,7 +1526,7 @@ The SanitizeHelper module provides a set of methods for scrubbing text of undesi #### sanitize -This sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed. +This sanitize helper will HTML encode all tags and strip all attributes that aren't specifically allowed. ```ruby sanitize @article.body diff --git a/source/active_job_basics.md b/source/active_job_basics.md new file mode 100644 index 0000000..df51231 --- /dev/null +++ b/source/active_job_basics.md @@ -0,0 +1,285 @@ +Active Job Basics +================= + +This guide provides you with all you need to get started in creating, +enqueueing and executing background jobs. + +After reading this guide, you will know: + +* How to create jobs. +* How to enqueue jobs. +* How to run jobs in the background. +* How to send emails from your application async. + +-------------------------------------------------------------------------------- + + +Introduction +------------ + +Active Job is a framework for declaring jobs and making them run on a variety +of queueing backends. These jobs can be everything from regularly scheduled +clean-ups, to billing charges, to mailings. Anything that can be chopped up +into small units of work and run in parallel, really. + + +The Purpose of the Active Job +----------------------------- +The main point is to ensure that all Rails apps will have a job infrastructure +in place, even if it's in the form of an "immediate runner". We can then have +framework features and other gems build on top of that, without having to +worry about API differences between various job runners such as Delayed Job +and Resque. Picking your queuing backend becomes more of an operational concern, +then. And you'll be able to switch between them without having to rewrite your jobs. + + +Creating a Job +-------------- + +This section will provide a step-by-step guide to creating a job and enqueuing it. + +### Create the Job + +Active Job provides a Rails generator to create jobs. The following will create a +job in `app/jobs`: + +```bash +$ bin/rails generate job guests_cleanup +create app/jobs/guests_cleanup_job.rb +``` + +You can also create a job that will run on a specific queue: + +```bash +$ bin/rails generate job guests_cleanup --queue urgent +create app/jobs/guests_cleanup_job.rb +``` + +As you can see, you can generate jobs just like you use other generators with +Rails. + +If you don't want to use a generator, you could create your own file inside of +`app/jobs`, just make sure that it inherits from `ActiveJob::Base`. + +Here's what a job looks like: + +```ruby +class GuestsCleanupJob < ActiveJob::Base + queue_as :default + + def perform(*args) + # Do something later + end +end +``` + +### Enqueue the Job + +Enqueue a job like so: + +```ruby +MyJob.enqueue record # Enqueue a job to be performed as soon the queueing system is free. +``` + +```ruby +MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon. +``` + +```ruby +MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now. +``` + +That's it! + + +Job Execution +------------- + +If no adapter is set, the job is immediately executed. + +### Backends + +Active Job has adapters for the following queueing backends: + +* [Backburner](https://github.com/nesquena/backburner) +* [Delayed Job](https://github.com/collectiveidea/delayed_job) +* [Qu](https://github.com/bkeepers/qu) +* [Que](https://github.com/chanks/que) +* [QueueClassic 2.x](https://github.com/ryandotsmith/queue_classic/tree/v2.2.3) +* [Resque 1.x](https://github.com/resque/resque/tree/1-x-stable) +* [Sidekiq](https://github.com/mperham/sidekiq) +* [Sneakers](https://github.com/jondot/sneakers) +* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch) + +#### Backends Features + +| | Async | Queues | Delayed | Priorities | Timeout | Retries | +|-----------------------|-------|--------|-----------|------------|---------|---------| +| **Backburner** | Yes | Yes | Yes | Yes | Job | Global | +| **Delayed Job** | Yes | Yes | Yes | Job | Global | Global | +| **Que** | Yes | Yes | Yes | Job | No | Job | +| **Queue Classic** | Yes | Yes | No* | No | No | No | +| **Resque** | Yes | Yes | Yes (Gem) | Queue | Global | Yes | +| **Sidekiq** | Yes | Yes | Yes | Queue | No | Job | +| **Sneakers** | Yes | Yes | No | Queue | Queue | No | +| **Sucker Punch** | Yes | Yes | No | No | No | No | +| **Active Job Inline** | No | Yes | N/A | N/A | N/A | N/A | +| **Active Job** | Yes | Yes | Yes | No | No | No | + +NOTE: +* Queue Classic does not support Job scheduling. However you can implement this +yourself or you can use the queue_classic-later gem. See the documentation for +ActiveJob::QueueAdapters::QueueClassicAdapter. + +### Change Backends + +You can easily change your adapter: + +```ruby +# be sure to have the adapter gem in your Gemfile and follow the adapter specific +# installation and deployment instructions +YourApp::Application.config.active_job.queue_adapter = :sidekiq +``` + + +Queues +------ + +Most of the adapters support multiple queues. With Active Job you can schedule +the job to run on a specific queue: + +```ruby +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end +``` + +Also you can prefix the queue name for all your jobs using +`config.active_job.queue_name_prefix` in `application.rb`: + +```ruby +# config/application.rb +module YourApp + class Application < Rails::Application + config.active_job.queue_name_prefix = Rails.env + end +end + +# app/jobs/guests_cleanup.rb +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end + +# Now your job will run on queue production_low_priority on your production +# environment and on beta_low_priority on your beta environment +``` + +NOTE: Make sure your queueing backend "listens" on your queue name. For some +backends you need to specify the queues to listen to. + + +Callbacks +--------- + +Active Job provides hooks during the lifecycle of a job. Callbacks allow you to +trigger logic during the lifecycle of a job. + +### Available callbacks + +* `before_enqueue` +* `around_enqueue` +* `after_enqueue` +* `before_perform` +* `around_perform` +* `after_perform` + +### Usage + +```ruby +class GuestsCleanupJob < ActiveJob::Base + queue_as :default + + before_enqueue do |job| + # do somthing with the job instance + end + + around_perform do |job, block| + # do something before perform + block.call + # do something after perform + end + + def perform + # Do something later + end +end +``` + + +ActionMailer +------------ + +One of the most common jobs in a modern web application is sending emails outside +of the request-response cycle, so the user doesn't have to wait on it. Active Job +is integrated with Action Mailer so you can easily send emails asynchronously: + +```ruby +# If you want to send the email now use #deliver_now +UserMailer.welcome(@user).deliver_now + +# If you want to send the email through Active Job use #deliver_later +UserMailer.welcome(@user).deliver_later +``` + + +GlobalID +-------- +Active Job supports GlobalID for parameters. This makes it possible to pass live +Active Record objects to your job instead of class/id pairs, which you then have +to manually deserialize. Before, jobs would look like this: + +```ruby +class TrashableCleanupJob + def perform(trashable_class, trashable_id, depth) + trashable = trashable_class.constantize.find(trashable_id) + trashable.cleanup(depth) + end +end +``` + +Now you can simply do: + +```ruby +class TrashableCleanupJob + def perform(trashable, depth) + trashable.cleanup(depth) + end +end +``` + +This works with any class that mixes in `ActiveModel::GlobalIdentification`, which +by default has been mixed into Active Model classes. + + +Exceptions +---------- + +Active Job provides a way to catch exceptions raised during the execution of the +job: + +```ruby + +class GuestsCleanupJob < ActiveJob::Base + queue_as :default + + rescue_from(ActiveRecord::RecordNotFound) do |exception| + # do something with the exception + end + + def perform + # Do something later + end +end +``` diff --git a/source/active_record_postgresql.md b/source/active_record_postgresql.md index a5649e3..6c94218 100644 --- a/source/active_record_postgresql.md +++ b/source/active_record_postgresql.md @@ -132,7 +132,8 @@ event = Event.first event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]} ## Query based on JSON document -Event.where("payload->'kind' = ?", "user_renamed") +# The -> operator returns the original JSON type (which might be an object), whereas ->> returns text +Event.where("payload->>'kind' = ?", "user_renamed") ``` ### Range Types diff --git a/source/active_record_querying.md b/source/active_record_querying.md index 35467fe..cb243c9 100644 --- a/source/active_record_querying.md +++ b/source/active_record_querying.md @@ -276,7 +276,7 @@ This may appear straightforward: ```ruby # This is very inefficient when the users table has thousands of rows. User.all.each do |user| - NewsMailer.weekly(user).deliver + NewsMailer.weekly(user).deliver_now end ``` @@ -292,7 +292,7 @@ The `find_each` method retrieves a batch of records and then yields _each_ recor ```ruby User.find_each do |user| - NewsMailer.weekly(user).deliver + NewsMailer.weekly(user).deliver_now end ``` @@ -300,7 +300,7 @@ To add conditions to a `find_each` operation you can chain other Active Record m ```ruby User.where(weekly_subscriber: true).find_each do |user| - NewsMailer.weekly(user).deliver + NewsMailer.weekly(user).deliver_now end ``` @@ -316,7 +316,7 @@ The `:batch_size` option allows you to specify the number of records to be retri ```ruby User.find_each(batch_size: 5000) do |user| - NewsMailer.weekly(user).deliver + NewsMailer.weekly(user).deliver_now end ``` @@ -328,7 +328,7 @@ For example, to send newsletters only to users with the primary key starting fro ```ruby User.find_each(start: 2000, batch_size: 5000) do |user| - NewsMailer.weekly(user).deliver + NewsMailer.weekly(user).deliver_now end ``` @@ -1464,7 +1464,7 @@ Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE i ### `pluck` -`pluck` can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type. +`pluck` can be used to query single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type. ```ruby Client.where(active: true).pluck(:id) diff --git a/source/association_basics.md b/source/association_basics.md index daf4113..c9e0fcd 100644 --- a/source/association_basics.md +++ b/source/association_basics.md @@ -1321,9 +1321,9 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` -* `collection=objects` +* `collection=(objects)` * `collection_singular_ids` -* `collection_singular_ids=ids` +* `collection_singular_ids=(ids)` * `collection.clear` * `collection.empty?` * `collection.size` @@ -1399,7 +1399,7 @@ The `collection.destroy` method removes one or more objects from the collection WARNING: Objects will _always_ be removed from the database, ignoring the `:dependent` option. -##### `collection=objects` +##### `collection=(objects)` The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. @@ -1411,7 +1411,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects @order_ids = @customer.order_ids ``` -##### `collection_singular_ids=ids` +##### `collection_singular_ids=(ids)` The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. @@ -1810,9 +1810,9 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` -* `collection=objects` +* `collection=(objects)` * `collection_singular_ids` -* `collection_singular_ids=ids` +* `collection_singular_ids=(ids)` * `collection.clear` * `collection.empty?` * `collection.size` @@ -1895,7 +1895,7 @@ The `collection.destroy` method removes one or more objects from the collection @part.assemblies.destroy(@assembly1) ``` -##### `collection=objects` +##### `collection=(objects)` The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate. @@ -1907,7 +1907,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects @assembly_ids = @part.assembly_ids ``` -##### `collection_singular_ids=ids` +##### `collection_singular_ids=(ids)` The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate. diff --git a/source/caching_with_rails.md b/source/caching_with_rails.md index 0902e34..d0f3a59 100644 --- a/source/caching_with_rails.md +++ b/source/caching_with_rails.md @@ -353,7 +353,12 @@ Instead of an options hash, you can also simply pass in a model, Rails will use class ProductsController < ApplicationController def show @product = Product.find(params[:id]) - respond_with(@product) if stale?(@product) + + if stale?(@product) + respond_to do |wants| + # ... normal response processing + end + end end end ``` diff --git a/source/configuring.md b/source/configuring.md index 801cef5..dbbd0c1 100644 --- a/source/configuring.md +++ b/source/configuring.md @@ -112,7 +112,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. -* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode. +* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`. * `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below. @@ -137,7 +137,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is set to true by default. -* `config.assets.raise_runtime_errors`* Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`. +* `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`. * `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`. @@ -290,8 +290,6 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.partial_writes` is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty). Note that when using partial writes, you should also use optimistic locking `config.active_record.lock_optimistically` since concurrent updates may write attributes based on a possibly stale read state. The default value is `true`. -* `config.active_record.attribute_types_cached_by_default` sets the attribute types that `ActiveRecord::AttributeMethods` will cache by default on reads. The default is `[:datetime, :timestamp, :time, :date]`. - * `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true. * `config.active_record.dump_schema_after_migration` is a flag which diff --git a/source/contributing_to_ruby_on_rails.md b/source/contributing_to_ruby_on_rails.md index 0b05725..8bc4b10 100644 --- a/source/contributing_to_ruby_on_rails.md +++ b/source/contributing_to_ruby_on_rails.md @@ -397,7 +397,7 @@ inside, just indent it with 4 spaces: class ArticlesController def index - respond_with Article.limit(10) + render json: Article.limit(10) end end diff --git a/source/debugging_rails_applications.md b/source/debugging_rails_applications.md index 53b8566..88c6210 100644 --- a/source/debugging_rails_applications.md +++ b/source/debugging_rails_applications.md @@ -211,7 +211,7 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs " ### Impact of Logs on Performance Logging will always have a small impact on performance of your rails app, - particularly when logging to disk.However, there are a few subtleties: + particularly when logging to disk. However, there are a few subtleties: Using the `:debug` level will have a greater performance penalty than `:fatal`, as a far greater number of strings are being evaluated and written to the diff --git a/source/development_dependencies_install.md b/source/development_dependencies_install.md index b134c9d..3d9ec57 100644 --- a/source/development_dependencies_install.md +++ b/source/development_dependencies_install.md @@ -45,36 +45,14 @@ $ cd rails The test suite must pass with any submitted code. No matter whether you are writing a new patch, or evaluating someone else's, you need to be able to run the tests. -Install first libxml2 and libxslt together with their development files for Nokogiri. In Ubuntu that's +Install first SQLite3 and its development files for the `sqlite3` gem. Mac OS X +users are done with: ```bash -$ sudo apt-get install libxml2 libxml2-dev libxslt1-dev +$ brew install sqlite3 ``` -If you are on Fedora or CentOS, you can run - -```bash -$ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel -``` - -If you are running Arch Linux, you're done with: - -```bash -$ sudo pacman -S libxml2 libxslt -``` - -On FreeBSD, you just have to run: - -```bash -# pkg_add -r libxml2 libxslt -``` - -Alternatively, you can install the `textproc/libxml2` and `textproc/libxslt` -ports. - -If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . - -Also, SQLite3 and its development files for the `sqlite3-ruby` gem - in Ubuntu you're done with just +In Ubuntu you're done with just: ```bash $ sudo apt-get install sqlite3 libsqlite3-dev @@ -95,12 +73,12 @@ $ sudo pacman -S sqlite For FreeBSD users, you're done with: ```bash -# pkg_add -r sqlite3 +# pkg install sqlite3 ``` Or compile the `databases/sqlite3` port. -Get a recent version of [Bundler](http://gembundler.com/) +Get a recent version of [Bundler](http://bundler.io/) ```bash $ gem install bundler @@ -117,7 +95,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. -You can use [Homebrew](http://brew.sh/) to install memcached on OSX: +You can use [Homebrew](http://brew.sh/) to install memcached on OS X: ```bash $ brew install memcached @@ -135,6 +113,20 @@ Or use yum on Fedora or CentOS: $ sudo yum install memcached ``` +If you are running on Arch Linux: + +```bash +$ sudo pacman -S memcached +``` + +For FreeBSD users, you're done with: + +```bash +# pkg install memcached +``` + +Alternatively, you can compile the `databases/memcached` port. + With the dependencies now installed, you can run the test suite with: ```bash @@ -181,7 +173,19 @@ The Active Record test suite requires a custom config file: `activerecord/test/c #### MySQL and PostgreSQL -To be able to run the suite for MySQL and PostgreSQL we need their gems. Install first the servers, their client libraries, and their development files. In Ubuntu just run +To be able to run the suite for MySQL and PostgreSQL we need their gems. Install +first the servers, their client libraries, and their development files. + +On OS X, you can run: + +```bash +$ brew install mysql +$ brew install postgresql +``` + +Follow the instructions given by Homebrew to start these. + +In Ubuntu just run: ```bash $ sudo apt-get install mysql-server libmysqlclient15-dev @@ -206,17 +210,9 @@ $ sudo pacman -S postgresql postgresql-libs FreeBSD users will have to run the following: ```bash -# pkg_add -r mysql56-client mysql56-server -# pkg_add -r postgresql92-client postgresql92-server -``` - -You can use [Homebrew](http://brew.sh/) to install MySQL and PostgreSQL on OSX: - -```bash -$ brew install mysql -$ brew install postgresql +# pkg install mysql56-client mysql56-server +# pkg install postgresql93-client postgresql93-server ``` -Follow instructions given by [Homebrew](http://brew.sh/) to start these. Or install them through ports (they are located under the `databases` folder). If you run into troubles during the installation of MySQL, please see @@ -252,18 +248,20 @@ $ cd activerecord $ bundle exec rake db:mysql:build ``` -PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account -This is not needed when installed via [Homebrew](http://brew.sh). +PostgreSQL's authentication works differently. To setup the development environment +with your development account, on Linux or BSD, you just have to run: ```bash $ sudo -u postgres createuser --superuser $USER ``` -And for OS X (when installed via [Homebrew](http://brew.sh)) + +and for OS X: + ```bash $ createuser --superuser $USER ``` -and then create the test databases with +Then you need to create the test databases with ```bash $ cd activerecord diff --git a/source/form_helpers.md b/source/form_helpers.md index 048eb9a..2703e35 100644 --- a/source/form_helpers.md +++ b/source/form_helpers.md @@ -276,7 +276,7 @@ The name passed to `form_for` controls the key used in `params` to access the fo The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. -You can create a similar binding without actually creating `
` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a `Person` model with an associated `ContactDetail` model you could create a form for creating both like so: +You can create a similar binding without actually creating `` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example, if you had a `Person` model with an associated `ContactDetail` model, you could create a form for creating both like so: ```erb <%= form_for @person, url: {action: "create"} do |person_form| %> @@ -534,7 +534,7 @@ Both of these families of helpers will create a series of select boxes for the d ### Barebones Helpers -The `select_*` family of helpers take as their first argument an instance of `Date`, `Time` or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example +The `select_*` family of helpers take as their first argument an instance of `Date`, `Time` or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example: ```erb <%= select_date Date.today, prefix: :start_date %> @@ -548,7 +548,7 @@ outputs (with actual option values omitted for brevity) ``` -The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time` or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example +The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time` or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example: ```ruby Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) @@ -591,9 +591,9 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the ### Individual Components -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example, "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. -The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time` or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example +The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time` or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example: ```erb <%= select_year(2009) %> @@ -645,7 +645,7 @@ Unlike other forms making an asynchronous file upload form is not as simple as p Customizing Form Builders ------------------------- -As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of `FormBuilder` (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass `FormBuilder` and add the helpers there. For example +As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of `FormBuilder` (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass `FormBuilder` and add the helpers there. For example: ```erb <%= form_for @person do |f| %> @@ -684,12 +684,12 @@ If `f` is an instance of `FormBuilder` then this will render the `form` partial, Understanding Parameter Naming Conventions ------------------------------------------ -As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard `create` +As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example, in a standard `create` action for a Person model, `params[:person]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. -TIP: You may find you can try out examples in this section faster by using the console to directly invoke Racks' parameter parser. For example, +TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rack's parameter parser. For example, ```ruby Rack::Utils.parse_query "name=fred&phone=0123456789" @@ -698,7 +698,7 @@ Rack::Utils.parse_query "name=fred&phone=0123456789" ### Basic Structures -The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in `params`. For example if a form contains +The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in `params`. For example, if a form contains: ```html @@ -712,7 +712,7 @@ the `params` hash will contain and `params[:person][:name]` will retrieve the submitted value in the controller. -Hashes can be nested as many levels as required, for example +Hashes can be nested as many levels as required, for example: ```html @@ -724,7 +724,7 @@ will result in the `params` hash being {'person' => {'address' => {'city' => 'New York'}}} ``` -Normally Rails ignores duplicate parameter names. If the parameter name contains an empty set of square brackets [] then they will be accumulated in an array. If you wanted people to be able to input multiple phone numbers, you could place this in the form: +Normally Rails ignores duplicate parameter names. If the parameter name contains an empty set of square brackets `[]` then they will be accumulated in an array. If you wanted users to be able to input multiple phone numbers, you could place this in the form: ```html @@ -732,11 +732,11 @@ Normally Rails ignores duplicate parameter names. If the parameter name contains ``` -This would result in `params[:person][:phone_number]` being an array. +This would result in `params[:person][:phone_number]` being an array containing the inputted phone numbers. ### Combining Them -We can mix and match these two concepts. For example, one element of a hash might be an array as in the previous example, or you can have an array of hashes. For example a form might let you create any number of addresses by repeating the following form fragment +We can mix and match these two concepts. One element of a hash might be an array as in the previous example, or you can have an array of hashes. For example, a form might let you create any number of addresses by repeating the following form fragment ```html @@ -746,7 +746,7 @@ We can mix and match these two concepts. For example, one element of a hash migh This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash. -There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter. +There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can usually be replaced by hashes; for example, instead of having an array of model objects, one can have a hash of model objects keyed by their id, an array index or some other parameter. WARNING: Array parameters do not play well with the `check_box` helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The `check_box` helper fakes this by creating an auxiliary hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use `check_box_tag` or to use hashes instead of arrays. @@ -856,7 +856,7 @@ Or if you don't want to render an `authenticity_token` field: Building Complex Forms ---------------------- -Many apps grow beyond simple forms editing a single object. For example when creating a `Person` you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. +Many apps grow beyond simple forms editing a single object. For example, when creating a `Person` you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. ### Configuring the Model @@ -908,7 +908,7 @@ end ``` The `fields_for` yields a form builder. The parameters' name will be what -`accepts_nested_attributes_for` expects. For example when creating a user with +`accepts_nested_attributes_for` expects. For example, when creating a user with 2 addresses, the submitted parameters would look like: ```ruby diff --git a/source/generators.md b/source/generators.md index 5e88fa0..f5d2c67 100644 --- a/source/generators.md +++ b/source/generators.md @@ -8,6 +8,7 @@ After reading this guide, you will know: * How to see which generators are available in your application. * How to create a generator using templates. * How Rails searches for generators before invoking them. +* How Rails internally generates Rails code from the templates. * How to customize your scaffold by creating new generators. * How to customize your scaffold by changing generator templates. * How to use fallbacks to avoid overwriting a huge set of generators. @@ -340,6 +341,22 @@ end If you generate another resource, you can see that we get exactly the same result! This is useful if you want to customize your scaffold templates and/or layout by just creating `edit.html.erb`, `index.html.erb` and so on inside `lib/templates/erb/scaffold`. +Scaffold templates in Rails frequently use ERB tags; these tags need to be +escaped so that the generated output is valid ERB code. + +For example, the following escaped ERB tag would be needed in the template +(note the extra `%`)... + +```ruby +<%%= stylesheet_include_tag :application %> +``` + +...to generate the following output: + +```ruby +<%= stylesheet_include_tag :application %> +``` + Adding Generators Fallbacks --------------------------- diff --git a/source/getting_started.md b/source/getting_started.md index 1f91352..964bb30 100644 --- a/source/getting_started.md +++ b/source/getting_started.md @@ -21,10 +21,10 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. -* The [RubyGems](http://rubygems.org) packaging system, which is installed with Ruby +* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. +* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org). -* A working installation of the [SQLite3 Database](http://www.sqlite.org). +* A working installation of the [SQLite3 Database](https://www.sqlite.org). Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning @@ -101,7 +101,7 @@ If you don't have Ruby installed have a look at install Ruby on your platform. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows -users and others can find installation instructions at [the SQLite3 website](http://www.sqlite.org). +users and others can find installation instructions at [the SQLite3 website](https://www.sqlite.org). Verify that it is correctly installed and in your PATH: ```bash @@ -199,7 +199,7 @@ Rails adds the `therubyracer` gem to the generated `Gemfile` in a commented line for new apps and you can uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby users and is added by default to the `Gemfile` in apps generated under JRuby. You can investigate -about all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). +all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). This will fire up WEBrick, a web server distributed with Ruby by default. To see your application in action, open a browser window and navigate to @@ -748,7 +748,7 @@ to create an article. Try it! You should get an error that looks like this: (images/getting_started/forbidden_attributes_for_new_article.png) Rails has several security features that help you write secure applications, -and you're running into one of them now. This one is called [strong parameters](http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters), +and you're running into one of them now. This one is called [strong parameters](action_controller_overview.html#strong-parameters), which requires us to tell Rails exactly which parameters are allowed into our controller actions. diff --git a/source/initialization.md b/source/initialization.md index b81b048..53bf303 100644 --- a/source/initialization.md +++ b/source/initialization.md @@ -111,7 +111,6 @@ A standard Rails application depends on several gems, specifically: * i18n * mail * mime-types -* polyglot * rack * rack-cache * rack-mount @@ -121,7 +120,6 @@ A standard Rails application depends on several gems, specifically: * rake * sqlite3 * thor -* treetop * tzinfo ### `rails/commands.rb` diff --git a/source/layouts_and_rendering.md b/source/layouts_and_rendering.md index f00f7bc..ac254fc 100644 --- a/source/layouts_and_rendering.md +++ b/source/layouts_and_rendering.md @@ -248,7 +248,8 @@ service requests that are expecting something other than proper HTML. NOTE: By default, if you use the `:plain` option, the text is rendered without using the current layout. If you want Rails to put the text into the current -layout, you need to add the `layout: true` option. +layout, you need to add the `layout: true` option and use the `.txt.erb` +extension for the layout file. #### Rendering HTML @@ -263,7 +264,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: This option will escape HTML entities if the string is not html safe. +NOTE: This option will escape HTML entities if the string is not HTML safe. #### Rendering JSON @@ -903,7 +904,7 @@ You can also specify multiple videos to play by passing an array of videos to th This will produce: ```erb - + ``` #### Linking to Audio Files with the `audio_tag` diff --git a/source/nested_model_forms.md b/source/nested_model_forms.md index 4f0634d..f0ee34c 100644 --- a/source/nested_model_forms.md +++ b/source/nested_model_forms.md @@ -1,4 +1,4 @@ -Rails nested model forms +Rails Nested Model Forms ======================== Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations. @@ -54,6 +54,9 @@ class Person < ActiveRecord::Base end ``` +NOTE: For greater detail on associations see [Active Record Associations](association_basics.html). +For a complete reference on associations please visit the API documentation for [ActiveRecord::Associations::ClassMethods](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html). + ### Custom model As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: diff --git a/source/plugins.md b/source/plugins.md index f10699f..dbccfd4 100644 --- a/source/plugins.md +++ b/source/plugins.md @@ -57,7 +57,7 @@ You can navigate to the directory that contains the plugin, run the `bundle inst You should see: ```bash - 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips ``` This will tell you that everything got generated properly and you are ready to start adding functionality. @@ -85,9 +85,9 @@ Run `rake` to run the test. This test should fail because we haven't implemented ```bash 1) Error: - test_to_squawk_prepends_the_word_squawk(CoreExtTest): - NoMethodError: undefined method `to_squawk' for [Hello World](String) - test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' + CoreExtTest#test_to_squawk_prepends_the_word_squawk: + NoMethodError: undefined method `to_squawk' for "Hello World":String + /path/to/yaffle/test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' ``` Great - now you are ready to start development. @@ -118,7 +118,7 @@ end To test that your method does what it says it does, run the unit tests with `rake` from your plugin directory. ```bash - 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips + 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips ``` To see this in action, change to the test/dummy directory, fire up a console and start squawking: @@ -196,16 +196,16 @@ When you run `rake`, you should see the following: ``` 1) Error: - test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest): + ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: NameError: uninitialized constant ActsAsYaffleTest::Hickwall - test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' + /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' 2) Error: - test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest): + ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: NameError: uninitialized constant ActsAsYaffleTest::Wickwall - test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' + /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' - 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips + 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips ``` This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test. @@ -270,18 +270,18 @@ You can then return to the root directory (`cd ../..`) of your plugin and rerun ``` 1) Error: - test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest): - NoMethodError: undefined method `yaffle_text_field' for # - /Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing' - test/acts_as_yaffle_test.rb:5:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' + ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: + NoMethodError: undefined method `yaffle_text_field' for # + activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing' + /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' 2) Error: - test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest): - NoMethodError: undefined method `yaffle_text_field' for # - Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing' - test/acts_as_yaffle_test.rb:9:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' + ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: + NoMethodError: undefined method `yaffle_text_field' for # + activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing' + /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' - 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips + 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips ``` @@ -312,7 +312,7 @@ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle When you run `rake`, you should see the tests all pass: ```bash - 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips + 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips ``` ### Add an Instance Method @@ -386,7 +386,7 @@ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle Run `rake` one final time and you should see: ``` - 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips + 6 runs, 6 assertions, 0 failures, 0 errors, 0 skips ``` NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use: diff --git a/source/security.md b/source/security.md index ebfcc5b..125dd82 100644 --- a/source/security.md +++ b/source/security.md @@ -118,9 +118,9 @@ It works like this: * A user receives credits, the amount is stored in a session (which is a bad idea anyway, but we'll do this for demonstration purposes). * The user buys something. -* Their new, lower credit will be stored in the session. -* The dark side of the user forces them to take the cookie from the first step (which they copied) and replace the current cookie in the browser. -* The user has their credit back. +* The new adjusted credit value is stored in the session. +* The user takes the cookie from the first step (which they previously copied) and replaces the current cookie in the browser. +* The user has their original credit back. Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database). @@ -847,7 +847,7 @@ It is recommended to _use RedCloth in combination with a whitelist input filter_ NOTE: _The same security precautions have to be taken for Ajax actions as for "normal" ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._ -If you use the [in_place_editor plugin](http://dev.rubyonrails.org/browser/plugins/in_place_editing), or actions that return a string, rather than rendering a view, _you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method. +If you use the [in_place_editor plugin](https://rubygems.org/gems/in_place_editing), or actions that return a string, rather than rendering a view, _you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method. ### Command Line Injection diff --git a/source/testing.md b/source/testing.md index 3e12afc..29724ae 100644 --- a/source/testing.md +++ b/source/testing.md @@ -99,7 +99,7 @@ one: Note: For associations to reference one another by name, you cannot specify the `id:` attribute on the fixtures. Rails will auto assign a primary key to be consistent between runs. If you manually specify an `id:` attribute, this behavior will not work. For more - information on this assocation behavior please read the + information on this association behavior please read the [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### ERB'in It Up @@ -605,13 +605,13 @@ end Testing the response to your request by asserting the presence of key HTML elements and their content is a useful way to test the views of your application. The `assert_select` assertion allows you to do this by using a simple yet powerful syntax. -NOTE: You may find references to `assert_tag` in other documentation, but this is now deprecated in favor of `assert_select`. +NOTE: You may find references to `assert_tag` in other documentation. This has been removed in 4.2. Use `assert_select` instead. There are two forms of `assert_select`: -`assert_select(selector, [equality], [message])` ensures that the equality condition is met on the selected elements through the selector. The selector may be a CSS selector expression (String), an expression with substitution values, or an `HTML::Selector` object. +`assert_select(selector, [equality], [message])` ensures that the equality condition is met on the selected elements through the selector. The selector may be a CSS selector expression (String) or an expression with substitution values. -`assert_select(element, selector, [equality], [message])` ensures that the equality condition is met on all the selected elements through the selector starting from the _element_ (instance of `HTML::Node`) and its descendants. +`assert_select(element, selector, [equality], [message])` ensures that the equality condition is met on all the selected elements through the selector starting from the _element_ (instance of `Nokogiri::XML::Node` or `Nokogiri::XML::NodeSet`) and its descendants. For example, you could verify the contents on the title element in your response with: @@ -641,7 +641,7 @@ assert_select "ol" do end ``` -The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html). +The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb). #### Additional View-Based Assertions @@ -949,7 +949,7 @@ class UserMailerTest < ActionMailer::TestCase test "invite" do # Send the email, then test that it got queued email = UserMailer.create_invite('me@example.com', - 'friend@example.com', Time.now).deliver + 'friend@example.com', Time.now).deliver_now assert_not ActionMailer::Base.deliveries.empty? # Test the body of the sent email contains what we expect it to diff --git a/source/upgrading_ruby_on_rails.md b/source/upgrading_ruby_on_rails.md index cc20782..989d840 100644 --- a/source/upgrading_ruby_on_rails.md +++ b/source/upgrading_ruby_on_rails.md @@ -50,7 +50,20 @@ Don't forget to review the difference, to see if there were any unexpected chang Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- -NOTE: This section is a work in progress. +NOTE: This section is a work in progress, please help to improve this by sending +a [pull request](https://github.com/rails/rails/edit/master/guides/source/upgrading_ruby_on_rails.md). + +### Web Console + +TODO: setup instructions for web console on existing apps. + +### Responders + +TODO: mention https://github.com/rails/rails/pull/16526 + +### Error handling in transaction callbacks + +TODO: mention https://github.com/rails/rails/pull/16537 ### Serialized attributes @@ -91,6 +104,42 @@ after_bundle do end ``` +### Rails Html Sanitizer + +There's a new choice for sanitizing HTML fragments in your applications. The +venerable html-scanner approach is now officially being deprecated in favor of +[`Rails Html Sanitizer`](https://github.com/rails/rails-html-sanitizer). + +This means the methods `sanitize`, `sanitize_css`, `strip_tags` and +`strip_links` are backed by a new implementation. + +In the next major Rails version `Rails Html Sanitizer` will be the default +sanitizer. It already is for new applications. + +Include this in your Gemfile to try it out today: + +```ruby +gem 'rails-html-sanitizer' +``` + +This new sanitizer uses [Loofah](https://github.com/flavorjones/loofah) internally. Loofah in turn uses Nokogiri, which +wraps XML parsers written in both C and Java, so sanitization should be faster +no matter which Ruby version you run. + +The new version updates `sanitize`, so it can take a `Loofah::Scrubber` for +powerful scrubbing. +[See some examples of scrubbers here](https://github.com/flavorjones/loofah#loofahscrubber). + +Two new scrubbers have also been added: `PermitScrubber` and `TargetScrubber`. +Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more information. + +The documentation for `PermitScrubber` and `TargetScrubber` explains how you +can gain complete control over when and how elements should be stripped. + +### Rails DOM Testing + +TODO: Mention https://github.com/rails/rails/commit/4e97d7585a2f4788b9eed98c6cdaf4bb6f2cf5ce + Upgrading from Rails 4.0 to Rails 4.1 ------------------------------------- @@ -99,7 +148,7 @@ Upgrading from Rails 4.0 to Rails 4.1 Or, "whaaat my tests are failing!!!?" Cross-site request forgery (CSRF) protection now covers GET requests with -JavaScript responses, too. That prevents a third-party site from referencing +JavaScript responses, too. This prevents a third-party site from referencing your JavaScript URL and attempting to run it to extract sensitive data. This means that your functional and integration tests that use @@ -150,8 +199,8 @@ secrets, you need to: ``` 2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to - set the SECRET_KEY_BASE environment variable for whichever users run the Rails - app in production mode. Alternately, you can simply copy the existing + set the SECRET_KEY_BASE environment variable for whichever users running the + Rails application in production mode. Alternatively, you can simply copy the existing `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml` under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'. @@ -165,7 +214,7 @@ secrets, you need to: If your test helper contains a call to `ActiveRecord::Migration.check_pending!` this can be removed. The check -is now done automatically when you `require 'test_help'`, although +is now done automatically when you `require 'rails/test_help'`, although leaving this line in your helper is not harmful in any way. ### Cookies serializer @@ -344,7 +393,7 @@ included in the newly introduced `ActiveRecord::FixtureSet.context_class`, in `test_helper.rb`. ```ruby -class FixtureFileHelpers +module FixtureFileHelpers def file_sha(path) Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path))) end @@ -354,8 +403,8 @@ ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers ### I18n enforcing available locales -Rails 4.1 now defaults the I18n option `enforce_available_locales` to `true`, -meaning that it will make sure that all locales passed to it must be declared in +Rails 4.1 now defaults the I18n option `enforce_available_locales` to `true`. This +means that it will make sure that all locales passed to it must be declared in the `available_locales` list. To disable it (and allow I18n to accept *any* locale option) add the following @@ -365,9 +414,10 @@ configuration to your application: config.i18n.enforce_available_locales = false ``` -Note that this option was added as a security measure, to ensure user input could -not be used as locale information unless previously known, so it's recommended not -to disable this option unless you have a strong reason for doing so. +Note that this option was added as a security measure, to ensure user input +cannot be used as locale information unless it is previously known. Therefore, +it's recommended not to disable this option unless you have a strong reason for +doing so. ### Mutator methods called on Relation @@ -475,7 +525,7 @@ Using `render :text` may pose a security risk, as the content is sent as ### PostgreSQL json and hstore datatypes Rails 4.1 will map `json` and `hstore` columns to a string-keyed Ruby `Hash`. -In earlier versions a `HashWithIndifferentAccess` was used. This means that +In earlier versions, a `HashWithIndifferentAccess` was used. This means that symbol access is no longer supported. This is also the case for `store_accessors` based on top of `json` or `hstore` columns. Make sure to use string keys consistently. @@ -565,7 +615,7 @@ being used, you can update your form to use the `PUT` method instead: <%= form_for [ :update_name, @user ], method: :put do |f| %> ``` -For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/) +For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/) on the Rails blog. #### A note about media types From cee26d7cf317fa2d0502a45fade6a2963dbf5e11 Mon Sep 17 00:00:00 2001 From: "han.bing" Date: Thu, 11 Sep 2014 07:31:52 +0800 Subject: [PATCH 005/133] Active Support Core Extensions translation --- source/zh-CN/active_support_core_extensions.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/source/zh-CN/active_support_core_extensions.md b/source/zh-CN/active_support_core_extensions.md index eeb0455..e2248d7 100644 --- a/source/zh-CN/active_support_core_extensions.md +++ b/source/zh-CN/active_support_core_extensions.md @@ -746,13 +746,11 @@ NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. #### 限定常量名 -The standard methods `const_defined?`, `const_get` , and `const_set` accept -bare constant names. Active Support extends this API to be able to pass -relative qualified constant names. +标准方法`const_defined?`、`const_get`和`const_set`接受裸常量名。 +Active Support 扩展了这些API使其可以接受相对限定常量名。 -The new methods are `qualified_const_defined?`, `qualified_const_get`, and -`qualified_const_set`. Their arguments are assumed to be qualified constant -names relative to their receiver: +新的方法名是`qualified_const_defined?`,`qualified_const_get`和`qualified_const_set`。 +它们的参数被假定为相对于其接收者的限定常量名: ```ruby Object.qualified_const_defined?("Math::PI") # => true @@ -760,7 +758,7 @@ Object.qualified_const_get("Math::PI") # => 3.141592653589793 Object.qualified_const_set("Math::Phi", 1.618034) # => 1.618034 ``` -Arguments may be bare constant names: +参数可以使用裸常量名: ```ruby Math.qualified_const_get("E") # => 2.718281828459045 @@ -771,8 +769,9 @@ These methods are analogous to their built-in counterparts. In particular, able to say whether you want the predicate to look in the ancestors. This flag is taken into account for each constant in the expression while walking down the path. +这些方法与其内建的对应方法很类似。尤为值得一提的是,`qualified_constant_defined?`接收一个可选的第二参数,以此来标明你是否要在祖先链中进行查找。 -For example, given +例如,假定: ```ruby module M @@ -786,7 +785,7 @@ module N end ``` -`qualified_const_defined?` behaves this way: +`qualified_const_defined?`会这样执行: ```ruby N.qualified_const_defined?("C::X", false) # => false From ec14b98f70017fe89a67a05d08195cacbada4603 Mon Sep 17 00:00:00 2001 From: n19270 Date: Sun, 14 Sep 2014 15:27:10 +0800 Subject: [PATCH 006/133] fix typo --- source/zh-CN/active_record_querying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/active_record_querying.md b/source/zh-CN/active_record_querying.md index 1017af1..7791b12 100644 --- a/source/zh-CN/active_record_querying.md +++ b/source/zh-CN/active_record_querying.md @@ -340,7 +340,7 @@ TIP: `find_each` 和 `find_in_batches` 方法的目的是分批处理无法一 #### `find_each` -`find_each` 方法获取一批记录,然后分别把每个记录传入代码块。在下面的例子中,`find_each` 获取 1000 各记录,然后把每个记录传入代码块,知道所有记录都处理完为止: +`find_each` 方法获取一批记录,然后分别把每个记录传入代码块。在下面的例子中,`find_each` 获取 1000 个记录,然后把每个记录传入代码块,直到所有记录都处理完为止: ```ruby User.find_each do |user| From 6c3302e01349972d9c34f6bffa11525691362fdc Mon Sep 17 00:00:00 2001 From: wtbhk Date: Mon, 13 Oct 2014 16:33:51 +0800 Subject: [PATCH 007/133] erros => errors --- source/zh-CN/active_record_validations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/active_record_validations.md b/source/zh-CN/active_record_validations.md index 6b3285f..2c991b1 100644 --- a/source/zh-CN/active_record_validations.md +++ b/source/zh-CN/active_record_validations.md @@ -725,7 +725,7 @@ end ### 自定义验证使用的方法 -还可以自定义方法验证模型的状态,如果验证失败,向 `erros` 集合添加错误消息。然后还要使用类方法 `validate` 注册这些方法,传入自定义验证方法名的 Symbol 形式。 +还可以自定义方法验证模型的状态,如果验证失败,向 `errors` 集合添加错误消息。然后还要使用类方法 `validate` 注册这些方法,传入自定义验证方法名的 Symbol 形式。 类方法可以接受多个 Symbol,自定义的验证方法会按照注册的顺序执行。 From ef0037fd4dcd2e96438d6ac7aee6fa6618629d27 Mon Sep 17 00:00:00 2001 From: wtbhk Date: Mon, 13 Oct 2014 16:34:45 +0800 Subject: [PATCH 008/133] =?UTF-8?q?=E5=80=BC=3D>=E5=8F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/zh-CN/active_record_validations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/active_record_validations.md b/source/zh-CN/active_record_validations.md index 2c991b1..f82ec09 100644 --- a/source/zh-CN/active_record_validations.md +++ b/source/zh-CN/active_record_validations.md @@ -152,7 +152,7 @@ end 要检查对象的某个属性是否合法,可以使用 `errors[:attribute]`。`errors[:attribute]` 中包含 `:attribute` 的所有错误。如果某个属性没有错误,就会返回空数组。 -这个方法只在数据验证之后才能使用,因为它只是用来收集错误信息的,并不会触发验证。而且,和前面介绍的 `ActiveRecord::Base#invalid?` 方法不一样,因为 `errors[:attribute]` 不会验证整个对象,值检查对象的某个属性是否出错。 +这个方法只在数据验证之后才能使用,因为它只是用来收集错误信息的,并不会触发验证。而且,和前面介绍的 `ActiveRecord::Base#invalid?` 方法不一样,因为 `errors[:attribute]` 不会验证整个对象,只检查对象的某个属性是否出错。 ```ruby class Person < ActiveRecord::Base From f390126a4191fe2055fccab23830fe5a04cbf9b6 Mon Sep 17 00:00:00 2001 From: redraiment Date: Sun, 19 Oct 2014 10:31:58 +0800 Subject: [PATCH 009/133] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=20=E5=A3=B0?= =?UTF-8?q?=E6=98=8E=E5=91=A8=E6=9C=9F=20=3D>=20=E7=94=9F=E5=91=BD?= =?UTF-8?q?=E5=91=A8=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/zh-CN/active_record_callbacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/active_record_callbacks.md b/source/zh-CN/active_record_callbacks.md index 069de1b..dc906fa 100644 --- a/source/zh-CN/active_record_callbacks.md +++ b/source/zh-CN/active_record_callbacks.md @@ -11,7 +11,7 @@ Active Record 回调 -------------------------------------------------------------------------------- -对象的声明周期 +对象的生命周期 ------------ 在 Rails 程序运行过程中,对象可以被创建、更新和销毁。Active Record 为对象的生命周期提供了很多钩子,让你控制程序及其数据。 From 0b6ba2d22fba2aa0202bb5f8a24334c085182a9a Mon Sep 17 00:00:00 2001 From: redraiment Date: Sun, 19 Oct 2014 11:18:04 +0800 Subject: [PATCH 010/133] =?UTF-8?q?=E5=AE=8C=E5=96=84medical=20practice?= =?UTF-8?q?=E7=9A=84=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/zh-CN/association_basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/association_basics.md b/source/zh-CN/association_basics.md index 057f4be..2e300b3 100644 --- a/source/zh-CN/association_basics.md +++ b/source/zh-CN/association_basics.md @@ -179,7 +179,7 @@ end ### `has_many :through` 关联 -`has_many :through` 关联经常用来建立两个模型之间的多对多关联。这种关联表示一个模型的实例可以借由第三个模型,拥有零个和多个另一个模型的实例。例如,在医疗锻炼中,病人要和医生约定练习时间。这中间的关联声明如下: +`has_many :through` 关联经常用来建立两个模型之间的多对多关联。这种关联表示一个模型的实例可以借由第三个模型,拥有零个和多个另一个模型的实例。例如,在看病过程中,病人要和医生预约时间。这中间的关联声明如下: ```ruby class Physician < ActiveRecord::Base From 537bbeba223b886b49c24b0e5fb49f153f55d145 Mon Sep 17 00:00:00 2001 From: wusuopu Date: Sun, 26 Oct 2014 11:21:06 +0800 Subject: [PATCH 011/133] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/zh-CN/active_record_validations.md | 2 +- source/zh-CN/testing.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/zh-CN/active_record_validations.md b/source/zh-CN/active_record_validations.md index f82ec09..d5e5b08 100644 --- a/source/zh-CN/active_record_validations.md +++ b/source/zh-CN/active_record_validations.md @@ -81,7 +81,7 @@ WARNING: 修改数据库中对象的状态有很多方法。有些方法会做 ### 跳过验证 -下列方法会跳过验证,不过验证是否通过都会把对象存入数据库,使用时要特别留意。 +下列方法会跳过验证,不管验证是否通过都会把对象存入数据库,使用时要特别留意。 * `decrement!` * `decrement_counter` diff --git a/source/zh-CN/testing.md b/source/zh-CN/testing.md index dde2d03..b8edd60 100644 --- a/source/zh-CN/testing.md +++ b/source/zh-CN/testing.md @@ -624,7 +624,7 @@ end 集成测试 ------- -继承测试用来测试多个控制器之间的交互,一般用来测试程序中重要的工作流程。 +集成测试用来测试多个控制器之间的交互,一般用来测试程序中重要的工作流程。 与单元测试和功能测试不同,集成测试必须单独生成,保存在 `test/integration` 文件夹中。Rails 提供了一个生成器用来生成集成测试骨架。 @@ -754,7 +754,7 @@ end | 任务 | 说明 | |-------------------------|-----------| -| `rake test` | 运行所有单元测试,功能测试和继承测试。还可以直接运行 `rake`,因为默认的 Rake 任务就是运行所有测试。 | +| `rake test` | 运行所有单元测试,功能测试和集成测试。还可以直接运行 `rake`,因为默认的 Rake 任务就是运行所有测试。 | | `rake test:controllers` | 运行 `test/controllers` 文件夹中的所有控制器测试 | | `rake test:functionals` | 运行文件夹 `test/controllers`、`test/mailers` 和 `test/functional` 中的所有功能测试 | | `rake test:helpers` | 运行 `test/helpers` 文件夹中的所有帮助方法测试 | From 54ecf88de1e643b0bc19ef438676cde008338c07 Mon Sep 17 00:00:00 2001 From: n19270 Date: Wed, 12 Nov 2014 02:21:21 +0800 Subject: [PATCH 012/133] rectify link url --- source/zh-CN/active_record_basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/active_record_basics.md b/source/zh-CN/active_record_basics.md index 1318b34..4fcb90b 100644 --- a/source/zh-CN/active_record_basics.md +++ b/source/zh-CN/active_record_basics.md @@ -274,4 +274,4 @@ end Rails 会跟踪哪些迁移已经应用到数据库中,还提供了回滚功能。创建数据表要执行 `rake db:migrate` 命令;回滚操作要执行 `rake db:rollback` 命令。 -注意,上面的代码和具体的数据库种类无关,可用于 MySQL、PostgreSQL、Oracle 等数据库。关于迁移的详细介绍,参阅 [Active Record 迁移](migrations.html)一文。 +注意,上面的代码和具体的数据库种类无关,可用于 MySQL、PostgreSQL、Oracle 等数据库。关于迁移的详细介绍,参阅 [Active Record 迁移](active_record_migrations.html)一文。 From dd4b400965b75b3bed7b8fb4d072c31807362434 Mon Sep 17 00:00:00 2001 From: Juanito Fatas Date: Fri, 12 Dec 2014 12:16:08 +0800 Subject: [PATCH 013/133] Fix a typo, closes #25. --- source/zh-CN/getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/zh-CN/getting_started.md b/source/zh-CN/getting_started.md index c11c987..0fc1dde 100644 --- a/source/zh-CN/getting_started.md +++ b/source/zh-CN/getting_started.md @@ -382,7 +382,7 @@ edit_article GET /articles/:id/edit(.:format) articles#edit root GET / welcome#index ``` -`articles_path` 帮助方法告诉 Rails,对应的地址是 `/articels`,默认情况下,这个表单会向这个路由发起 `POST` 请求。这个路由对应于 `ArticlesController` 控制器的 `create` 动作。 +`articles_path` 帮助方法告诉 Rails,对应的地址是 `/articles`,默认情况下,这个表单会向这个路由发起 `POST` 请求。这个路由对应于 `ArticlesController` 控制器的 `create` 动作。 表单写好了,路由也定义了,现在可以填写表单,然后点击提交按钮新建文章了。请实际操作一下。提交表单后,会看到一个熟悉的错误: From 3fc9bee453e7b6e618c1d3e8ee0f672e386687d9 Mon Sep 17 00:00:00 2001 From: Juanito Fatas Date: Sun, 21 Dec 2014 20:01:27 +0800 Subject: [PATCH 014/133] update english guides. --- source/3_0_release_notes.md | 6 +- source/4_0_release_notes.md | 4 +- source/4_1_release_notes.md | 2 +- source/4_2_release_notes.md | 493 ++++--- source/action_controller_overview.md | 61 +- source/action_mailer_basics.md | 54 +- source/action_view_overview.md | 1 - source/active_job_basics.md | 143 +- source/active_model_basics.md | 369 ++++- source/active_record_basics.md | 6 +- source/active_record_migrations.md | 10 +- source/active_record_postgresql.md | 12 +- source/active_record_querying.md | 66 +- source/active_record_validations.md | 19 +- source/active_support_core_extensions.md | 88 +- source/active_support_instrumentation.md | 12 +- source/asset_pipeline.md | 229 ++- source/association_basics.md | 62 +- source/autoloading_and_reloading_constants.md | 1297 +++++++++++++++++ source/caching_with_rails.md | 2 +- source/command_line.md | 27 +- source/configuring.md | 42 +- source/contributing_to_ruby_on_rails.md | 23 +- source/debugging_rails_applications.md | 2 +- source/engines.md | 45 +- source/form_helpers.md | 18 +- source/getting_started.md | 85 +- source/i18n.md | 33 +- source/layouts_and_rendering.md | 7 +- source/maintenance_policy.md | 9 +- source/plugins.md | 4 +- source/rails_on_rack.md | 10 +- source/routing.md | 2 +- source/ruby_on_rails_guides_guidelines.md | 1 + source/security.md | 10 +- source/testing.md | 136 +- source/upgrading_ruby_on_rails.md | 162 +- 37 files changed, 2909 insertions(+), 643 deletions(-) create mode 100644 source/autoloading_and_reloading_constants.md diff --git a/source/3_0_release_notes.md b/source/3_0_release_notes.md index 8122d6c..e985f1a 100644 --- a/source/3_0_release_notes.md +++ b/source/3_0_release_notes.md @@ -138,7 +138,7 @@ More Information: - [Rails Edge Architecture](http://yehudakatz.com/2009/06/11/r [Arel](http://github.com/brynary/arel) (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. -More information: - [Why I wrote Arel](http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/.) +More information: - [Why I wrote Arel](https://web.archive.org/web/20120718093140/http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/) ### Mail Extraction @@ -298,7 +298,7 @@ Deprecations More Information: * [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/) -* [Revamped Routes in Rails 3](http://rizwanreza.com/2009/12/20/revamped-routes-in-rails-3) +* [Revamped Routes in Rails 3](https://medium.com/fusion-of-thoughts/revamped-routes-in-rails-3-b6d00654e5b0) * [Generic Actions in Rails 3](http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/) @@ -545,7 +545,7 @@ These are the main changes in Active Support: * `String#to_time` and `String#to_datetime` handle fractional seconds. * Added support to new callbacks for around filter object that respond to `:before` and `:after` used in before and after callbacks. * The `ActiveSupport::OrderedHash#to_a` method returns an ordered set of arrays. Matches Ruby 1.9's `Hash#to_a`. -* `MissingSourceFile` exists as a constant but it is now just equals to `LoadError`. +* `MissingSourceFile` exists as a constant but it is now just equal to `LoadError`. * Added `Class#class_attribute`, to be able to declare a class-level attribute whose value is inheritable and overwritable by subclasses. * Finally removed `DeprecatedCallbacks` in `ActiveRecord::Associations`. * `Object#metaclass` is now `Kernel#singleton_class` to match Ruby. diff --git a/source/4_0_release_notes.md b/source/4_0_release_notes.md index 1aaf5eb..84a65df 100644 --- a/source/4_0_release_notes.md +++ b/source/4_0_release_notes.md @@ -229,11 +229,11 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default` * New method `reversible` makes it possible to specify code to be run when migrating up or down. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method) + See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/active_record_migrations.md#using-reversible) * New method `revert` will revert a whole migration or the given block. If migrating down, the given migration / block is run normally. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) + See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/active_record_migrations.md#reverting-previous-migrations) * Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. diff --git a/source/4_1_release_notes.md b/source/4_1_release_notes.md index 52a5acb..c7877a9 100644 --- a/source/4_1_release_notes.md +++ b/source/4_1_release_notes.md @@ -136,7 +136,7 @@ end ### Action Mailer Previews -Action Mailer previews provide a way to visually see how emails look by visiting +Action Mailer previews provide a way to see how emails look by visiting a special URL that renders them. You implement a preview class whose methods return the mail object you'd like diff --git a/source/4_2_release_notes.md b/source/4_2_release_notes.md index ae8ef34..f5575ac 100644 --- a/source/4_2_release_notes.md +++ b/source/4_2_release_notes.md @@ -3,21 +3,19 @@ Ruby on Rails 4.2 Release Notes Highlights in Rails 4.2: -* Active Job, Action Mailer #deliver_later +* Active Job +* Asynchronous mails * Adequate Record * Web Console * Foreign key support -These release notes cover only the major changes. To learn about various bug -fixes and changes, please refer to the change logs or check out the [list of -commits](https://github.com/rails/rails/commits/master) in the main Rails -repository on GitHub. +These release notes cover only the major changes. To learn about other +features, bug fixes, and changes, please refer to the changelogs or check out +the [list of commits](https://github.com/rails/rails/commits/4-2-stable) in +the main Rails repository on GitHub. -------------------------------------------------------------------------------- -NOTE: This document is a work in progress, please help to improve this by sending -a [pull request](https://github.com/rails/rails/edit/master/guides/source/4_2_release_notes.md). - Upgrading to Rails 4.2 ---------------------- @@ -25,60 +23,112 @@ If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 4.1 in case you haven't and make sure your application still runs as expected before attempting to upgrade to Rails 4.2. A list of things to watch out for when upgrading is -available in the -[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2) -guide. +available in the guide [Upgrading Ruby on +Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2). Major Features -------------- -### Active Job, Action Mailer #deliver_later +### Active Job + +Active Job is a new framework in Rails 4.2. It is a common interface on top of +queuing systems like [Resque](https://github.com/resque/resque), [Delayed +Job](https://github.com/collectiveidea/delayed_job), +[Sidekiq](https://github.com/mperham/sidekiq), and more. + +Jobs written with the Active Job API run on any of the supported queues thanks +to their respective adapters. Active Job comes pre-configured with an inline +runner that executes jobs right away. + +Jobs often need to take Active Record objects as arguments. Active Job passes +object references as URIs (uniform resource identifiers) instead of marshaling +the object itself. The new [Global ID](https://github.com/rails/globalid) +library builds URIs and looks up the objects they reference. Passing Active +Record objects as job arguments just works by using Global ID internally. + +For example, if `trashable` is an Active Record object, then this job runs +just fine with no serialization involved: + +```ruby +class TrashableCleanupJob < ActiveJob::Base + def perform(trashable, depth) + trashable.cleanup(depth) + end +end +``` + +See the [Active Job Basics](active_job_basics.html) guide for more +information. -Active Job is a new framework in Rails 4.2. It is an adapter layer on top of -queuing systems like [Resque](https://github.com/resque/resque), [Delayed Job](https://github.com/collectiveidea/delayed_job), [Sidekiq](https://github.com/mperham/sidekiq), and more. You can write your -jobs with the Active Job API, and it'll run on all these queues with no changes -(it comes pre-configured with an inline runner). +### Asynchronous Mails -Building on top of Active Job, Action Mailer now comes with a `#deliver_later` -method, which adds your email to be sent as a job to a queue, so it doesn't -bog down the controller or model. +Building on top of Active Job, Action Mailer now comes with a `deliver_later` +method that sends emails via the queue, so it doesn't block the controller or +model if the queue is asynchronous (the default inline queue blocks). -The new GlobalID library makes it easy to pass Active Record objects to jobs by -serializing them in a generic form. This means you no longer have to manually -pack and unpack your Active Records by passing ids. Just give the job the -straight Active Record object, and it'll serialize it using GlobalID, and -deserialize it at run time. +Sending emails right away is still possible with `deliver_now`. ### Adequate Record -Rails 4.2 comes with a performance improvement feature called Adequate Record -for Active Record. A lot of common queries are now up to twice as fast in Rails -4.2! +Adequate Record is a set of performance improvements in Active Record that makes +common `find` and `find_by` calls and some association queries up to 2x faster. -TODO: add some technical details +It works by caching common SQL queries as prepared statements and reusing them +on similar calls, skipping most of the query-generation work on subsequent +calls. For more details, please refer to [Aaron Patterson's blog +post](http://tenderlovemaking.com/2014/02/19/adequaterecord-pro-like-activerecord.html). -### Web Console +Active Record will automatically take advantage of this feature on +supported operations without any user involvement or code changes. Here are +some examples of supported operations: + +```ruby +Post.find(1) # First call generates and cache the prepared statement +Post.find(2) # Subsequent calls reuse the cached prepared statement + +Post.find_by_title('first post') +Post.find_by_title('second post') + +post.comments +post.comments(true) +``` -New applications generated from Rails 4.2 now come with the Web Console gem by -default. +It's important to highlight that, as the examples above suggest, the prepared +statements do not cache the values passed in the method calls; rather, they +have placeholders for them. -Web Console is a set of debugging tools for your Rails application. It will add -an interactive console on every error page, a `console` view helper and a VT100 -compatible terminal. +Caching is not used in the following scenarios: -The interactive console on the error pages lets you execute code where the -exception originated. It's quite handy to introspect the state that led to the -error. +- The model has a default scope +- The model uses single table inheritance +- `find` with a list of ids, e.g.: + + ```ruby + # not cached + Post.find(1, 2, 3) + Post.find([1,2]) + ``` + +- `find_by` with SQL fragments: + + ```ruby + Post.find_by('published_at < ?', 2.weeks.ago) + ``` + +### Web Console -The `console` view helper launches an interactive console within the context of -the view where it is invoked. +New applications generated with Rails 4.2 now come with the [Web +Console](https://github.com/rails/web-console) gem by default. Web Console adds +an interactive Ruby console on every error page and provides a `console` view +and controller helpers. -Finally, you can launch a VT100 terminal that runs `rails console`. If you need -to create or modify existing test data, you can do that straight from the -browser. +The interactive console on error pages lets you execute code in the context of +the place where the exception originated. The `console` helper, if called +anywhere in a view or controller, launches an interactive console with the final +context, once rendering has completed. -### Foreign key support +### Foreign Key Support The migration DSL now supports adding and removing foreign keys. They are dumped to `schema.rb` as well. At this time, only the `mysql`, `mysql2` and `postgresql` @@ -113,11 +163,18 @@ individual components for new deprecations in this release. The following changes may require immediate action upon upgrade. -### `respond_with` / class-level `respond_to` +### `render` with a String Argument -`respond_with` and the corresponding class-level `respond_to` have been moved to -the `responders` gem. To use the following, add `gem 'responders', '~> 2.0'` to -your Gemfile: +Previously, calling `render "foo/bar"` in a controller action was equivalent to +`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean +`render template: "foo/bar"` instead. If you need to render a file, please +change your code to use the explicit form (`render file: "foo/bar"`) instead. + +### `respond_with` / Class-Level `respond_to` + +`respond_with` and the corresponding class-level `respond_to` have been moved +to the [responders](https://github.com/plataformatec/responders) gem. Add +`gem 'responders', '~> 2.0'` to your Gemfile to use it: ```ruby # app/controllers/users_controller.rb @@ -148,45 +205,47 @@ class UsersController < ApplicationController end ``` -### Production logging - -The default log level in the `production` environment is now `:debug`. This -makes it consistent with the other environments, and ensures plenty of -information is available to diagnose problems. +### Default Host for `rails server` -It can be returned to the previous level, `:info`, in the environment -configuration: +Due to a [change in Rack](https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc), +`rails server` now listens on `localhost` instead of `0.0.0.0` by default. This +should have minimal impact on the standard development workflow as both +http://127.0.0.1:3000 and http://localhost:3000 will continue to work as before +on your own machine. -```ruby -# config/environments/production.rb +However, with this change you will no longer be able to access the Rails +server from a different machine, for example if your development environment +is in a virtual machine and you would like to access it from the host machine. +In such cases, please start the server with `rails server -b 0.0.0.0` to +restore the old behavior. -# Decrease the log volume. -config.log_level = :info -``` +If you do this, be sure to configure your firewall properly such that only +trusted machines on your network can access your development server. ### HTML Sanitizer The HTML sanitizer has been replaced with a new, more robust, implementation -built upon Loofah and Nokogiri. The new sanitizer is (TODO: betterer). +built upon [Loofah](https://github.com/flavorjones/loofah) and +[Nokogiri](https://github.com/sparklemotion/nokogiri). The new sanitizer is +more secure and its sanitization is more powerful and flexible. -With a new sanitization algorithm, the sanitized output will change for certain +Due to the new algorithm, the sanitized output may be different for certain pathological inputs. -If you have particular need for the exact output of the old sanitizer, you can -add `rails-deprecated_sanitizer` to your Gemfile, and it will automatically -replace the new implementation. Because it is opt-in, the legacy gem will not -give deprecation warnings. +If you have a particular need for the exact output of the old sanitizer, you +can add the [rails-deprecated_sanitizer](https://github.com/kaspth/rails-deprecated_sanitizer) +gem to the `Gemfile`, to have the old behavior. The gem does not issue +deprecation warnings because it is opt-in. `rails-deprecated_sanitizer` will be supported for Rails 4.2 only; it will not be maintained for Rails 5.0. -See [the blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/) -for more detail on the changes in the new sanitizer. +See [this blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/) +for more details on the changes in the new sanitizer. ### `assert_select` -`assert_select` is now based on Nokogiri, making it (TODO: betterer). - +`assert_select` is now based on [Nokogiri](https://github.com/sparklemotion/nokogiri). As a result, some previously-valid selectors are now unsupported. If your application is using any of these spellings, you will need to update them: @@ -194,8 +253,13 @@ application is using any of these spellings, you will need to update them: non-alphanumeric characters. ``` - a[href=/] => a[href="https://github.com/"] - a[href$=/] => a[href$="/"] + # before + a[href=/] + a[href$=/] + + # now + a[href="https://github.com/"] + a[href$="/"] ``` * DOMs built from HTML source containing invalid HTML with improperly @@ -241,11 +305,24 @@ Please refer to the [Changelog][railties] for detailed changes. ### Removals +* The `--skip-action-view` option has been removed from the + app generator. ([Pull Request](https://github.com/rails/rails/pull/17042)) + * The `rails application` command has been removed without replacement. ([Pull Request](https://github.com/rails/rails/pull/11616)) ### Deprecations +* Deprecated missing `config.log_level` for production environments. + ([Pull Request](https://github.com/rails/rails/pull/16622)) + +* Deprecated `rake test:all` in favor of `rake test` as it now run all tests + in the `test` folder. + ([Pull Request](https://github.com/rails/rails/pull/17348)) + +* Deprecated `rake test:all:db` in favor of `rake test:db`. + ([Pull Request](https://github.com/rails/rails/pull/17348)) + * Deprecated `Rails::Rack::LogTailer` without replacement. ([Commit](https://github.com/rails/rails/commit/84a13e019e93efaa8994b3f8303d635a7702dbce)) @@ -257,9 +334,6 @@ Please refer to the [Changelog][railties] for detailed changes. * Added a `required` option to the model generator for associations. ([Pull Request](https://github.com/rails/rails/pull/16062)) -* Introduced an `after_bundle` callback for use in Rails templates. - ([Pull Request](https://github.com/rails/rails/pull/16359)) - * Introduced the `x` namespace for defining custom configuration options: ```ruby @@ -292,27 +366,30 @@ Please refer to the [Changelog][railties] for detailed changes. namespace: my_app_development # config/production.rb - MyApp::Application.configure do + Rails.application.configure do config.middleware.use ExceptionNotifier, config_for(:exception_notification) end ``` ([Pull Request](https://github.com/rails/rails/pull/16129)) -* Introduced a `--skip-gems` option in the app generator to skip gems such as - `turbolinks` and `coffee-rails` that do not have their own specific flags. - ([Commit](https://github.com/rails/rails/commit/10565895805887d4faf004a6f71219da177f78b7)) +* Introduced a `--skip-turbolinks` option in the app generator to not generate + turbolinks integration. + ([Commit](https://github.com/rails/rails/commit/bf17c8a531bc8059d50ad731398002a3e7162a7d)) -* Introduced a `bin/setup` script to enable automated setup code when +* Introduced a `bin/setup` script as a convention for automated setup code when bootstrapping an application. ([Pull Request](https://github.com/rails/rails/pull/15189)) -* Changed default value for `config.assets.digest` to `true` in development. +* Changed the default value for `config.assets.digest` to `true` in development. ([Pull Request](https://github.com/rails/rails/pull/15155)) * Introduced an API to register new extensions for `rake notes`. ([Pull Request](https://github.com/rails/rails/pull/14379)) +* Introduced an `after_bundle` callback for use in Rails templates. + ([Pull Request](https://github.com/rails/rails/pull/16359)) + * Introduced `Rails.gem_version` as a convenience method to return `Gem::Version.new(Rails.version)`. ([Pull Request](https://github.com/rails/rails/pull/14101)) @@ -325,10 +402,11 @@ Please refer to the [Changelog][action-pack] for detailed changes. ### Removals -* `respond_with` and the class-level `respond_to` were removed from Rails and +* `respond_with` and the class-level `respond_to` have been removed from Rails and moved to the `responders` gem (version 2.0). Add `gem 'responders', '~> 2.0'` to your `Gemfile` to continue using these features. - ([Pull Request](https://github.com/rails/rails/pull/16526)) + ([Pull Request](https://github.com/rails/rails/pull/16526), + [More Details](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#responders)) * Removed deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError` in favor of `AbstractController::Helpers::MissingHelperError`. @@ -336,12 +414,15 @@ Please refer to the [Changelog][action-pack] for detailed changes. ### Deprecations +* Deprecated the `only_path` option on `*_path` helpers. + ([Commit](https://github.com/rails/rails/commit/aa1fadd48fb40dd9396a383696134a259aa59db9)) + * Deprecated `assert_tag`, `assert_no_tag`, `find_tag` and `find_all_tag` in favor of `assert_select`. ([Commit](https://github.com/rails/rails-dom-testing/commit/b12850bc5ff23ba4b599bf2770874dd4f11bf750)) * Deprecated support for setting the `:to` option of a router to a symbol or a - string that does not contain a `#` character: + string that does not contain a "#" character: ```ruby get '/posts', to: MyRackApp => (No change necessary) @@ -352,22 +433,22 @@ Please refer to the [Changelog][action-pack] for detailed changes. ([Commit](https://github.com/rails/rails/commit/cc26b6b7bccf0eea2e2c1a9ebdcc9d30ca7390d9)) -### Notable changes +* Deprecated support for string keys in URL helpers: -* Rails will now automatically include the template's digest in ETags. - ([Pull Request](https://github.com/rails/rails/pull/16527)) + ```ruby + # bad + root_path('controller' => 'posts', 'action' => 'index') -* `render nothing: true` or rendering a `nil` body no longer add a single - space padding to the response body. - ([Pull Request](https://github.com/rails/rails/pull/14883)) + # good + root_path(controller: 'posts', action: 'index') + ``` -* Introduced the `always_permitted_parameters` option to configure which - parameters are permitted globally. The default value of this configuration - is `['controller', 'action']`. - ([Pull Request](https://github.com/rails/rails/pull/15933)) + ([Pull Request](https://github.com/rails/rails/pull/17743)) -* The `*_filter` family methods have been removed from the documentation. Their - usage is discouraged in favor of the `*_action` family methods: +### Notable changes + +* The `*_filter` family of methods have been removed from the documentation. Their + usage is discouraged in favor of the `*_action` family of methods: ``` after_filter => after_action @@ -392,16 +473,28 @@ Please refer to the [Changelog][action-pack] for detailed changes. (Commit [1](https://github.com/rails/rails/commit/6c5f43bab8206747a8591435b2aa0ff7051ad3de), [2](https://github.com/rails/rails/commit/489a8f2a44dc9cea09154ee1ee2557d1f037c7d4)) -* Added HTTP method `MKCALENDAR` from RFC-4791 +* `render nothing: true` or rendering a `nil` body no longer add a single + space padding to the response body. + ([Pull Request](https://github.com/rails/rails/pull/14883)) + +* Rails now automatically includes the template's digest in ETags. + ([Pull Request](https://github.com/rails/rails/pull/16527)) + +* Segments that are passed into URL helpers are now automatically escaped. + ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f)) + +* Introduced the `always_permitted_parameters` option to configure which + parameters are permitted globally. The default value of this configuration + is `['controller', 'action']`. + ([Pull Request](https://github.com/rails/rails/pull/15933)) + +* Added the HTTP method `MKCALENDAR` from [RFC 4791](https://tools.ietf.org/html/rfc4791). ([Pull Request](https://github.com/rails/rails/pull/15121)) * `*_fragment.action_controller` notifications now include the controller and action name in the payload. ([Pull Request](https://github.com/rails/rails/pull/14137)) -* Segments that are passed into URL helpers are now automatically escaped. - ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f)) - * Improved the Routing Error page with fuzzy matching for route search. ([Pull Request](https://github.com/rails/rails/pull/14619)) @@ -409,28 +502,26 @@ Please refer to the [Changelog][action-pack] for detailed changes. ([Pull Request](https://github.com/rails/rails/pull/14280)) * When the Rails server is set to serve static assets, gzip assets will now be - served if the client supports it and a pre-generated gzip file (.gz) is on disk. + served if the client supports it and a pre-generated gzip file (`.gz`) is on disk. By default the asset pipeline generates `.gz` files for all compressible assets. Serving gzip files minimizes data transfer and speeds up asset requests. Always [use a CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) if you are serving assets from your Rails server in production. ([Pull Request](https://github.com/rails/rails/pull/16466)) -* The way `assert_select` works has changed; specifically a different library - is used to interpret css selectors, build the transient DOM that the - selectors are applied against, and to extract the data from that DOM. These - changes should only affect edge cases. Examples: - * Values in attribute selectors may need to be quoted if they contain - non-alphanumeric characters. - * DOMs built from HTML source containing invalid HTML with improperly - nested elements may differ. - * If the data selected contains entities, the value selected for comparison - used to be raw (e.g. `AT&T`), and now is evaluated - (e.g. `AT&T`). +* When calling the `process` helpers in an integration test the path needs to have + a leading slash. Previously you could omit it but that was a byproduct of the + implementation and not an intentional feature, e.g.: + ```ruby + test "list all posts" do + get "/posts" + assert_response :success + end + ``` Action View -------------- +----------- Please refer to the [Changelog][action-view] for detailed changes. @@ -447,16 +538,20 @@ Please refer to the [Changelog][action-view] for detailed changes. ### Notable changes -* Introduced a `#{partial_name}_iteration` special local variable for use with - partials that are rendered with a collection. It provides access to the - current state of the iteration via the `#index`, `#size`, `#first?` and - `#last?` methods. - ([Pull Request](https://github.com/rails/rails/pull/7698)) +* `render "foo/bar"` now expands to `render template: "foo/bar"` instead of + `render file: "foo/bar"`. + ([Pull Request](https://github.com/rails/rails/pull/16888)) * The form helpers no longer generate a `
` element with inline CSS around the hidden fields. ([Pull Request](https://github.com/rails/rails/pull/14738)) +* Introduced a `#{partial_name}_iteration` special local variable for use with + partials that are rendered with a collection. It provides access to the + current state of the iteration via the `index`, `size`, `first?` and + `last?` methods. + ([Pull Request](https://github.com/rails/rails/pull/7698)) + * Placeholder I18n follows the same convention as `label` I18n. ([Pull Request](https://github.com/rails/rails/pull/16438)) @@ -471,11 +566,15 @@ Please refer to the [Changelog][action-mailer] for detailed changes. * Deprecated `*_path` helpers in mailers. Always use `*_url` helpers instead. ([Pull Request](https://github.com/rails/rails/pull/15840)) -* Deprecated `deliver` / `deliver!` in favour of `deliver_now` / `deliver_now!`. +* Deprecated `deliver` / `deliver!` in favor of `deliver_now` / `deliver_now!`. ([Pull Request](https://github.com/rails/rails/pull/16582)) ### Notable changes +* `link_to` and `url_for` generate absolute URLs by default in templates, + it is no longer needed to pass `only_path: false`. + ([Commit](https://github.com/rails/rails/commit/9685080a7677abfa5d288a81c3e078368c6bb67c)) + * Introduced `deliver_later` which enqueues a job on the application's queue to deliver emails asynchronously. ([Pull Request](https://github.com/rails/rails/pull/16485)) @@ -504,7 +603,7 @@ Please refer to the [Changelog][active-record] for detailed changes. * Removed unused `:timestamp` type. Transparently alias it to `:datetime` in all cases. Fixes inconsistencies when column types are sent outside of - `ActiveRecord`, such as for XML serialization. + Active Record, such as for XML serialization. ([Pull Request](https://github.com/rails/rails/pull/15184)) ### Deprecations @@ -512,112 +611,123 @@ Please refer to the [Changelog][active-record] for detailed changes. * Deprecated swallowing of errors inside `after_commit` and `after_rollback`. ([Pull Request](https://github.com/rails/rails/pull/16537)) -* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use - `DatabaseTasks.load_schema_current` instead. - ([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608)) - -* Deprecated `Reflection#source_macro` without replacement as it is no longer - needed in Active Record. - ([Pull Request](https://github.com/rails/rails/pull/16373)) - * Deprecated broken support for automatic detection of counter caches on `has_many :through` associations. You should instead manually specify the counter cache on the `has_many` and `belongs_to` associations for the through records. ([Pull Request](https://github.com/rails/rails/pull/15754)) -* Deprecated `serialized_attributes` without replacement. - ([Pull Request](https://github.com/rails/rails/pull/15704)) - -* Deprecated returning `nil` from `column_for_attribute` when no column - exists. It will return a null object in Rails 5.0 - ([Pull Request](https://github.com/rails/rails/pull/15878)) - -* Deprecated using `.joins`, `.preload` and `.eager_load` with associations - that depends on the instance state (i.e. those defined with a scope that - takes an argument) without replacement. - ([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1)) - * Deprecated passing Active Record objects to `.find` or `.exists?`. Call - `#id` on the objects first. + `id` on the objects first. (Commit [1](https://github.com/rails/rails/commit/d92ae6ccca3bcfd73546d612efaea011270bd270), [2](https://github.com/rails/rails/commit/d35f0033c7dec2b8d8b52058fb8db495d49596f7)) * Deprecated half-baked support for PostgreSQL range values with excluding beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion - is not fully possible because the Ruby range does not support excluded - beginnings. + is not fully possible because Ruby ranges do not support excluded beginnings. The current solution of incrementing the beginning is not correct and is now deprecated. For subtypes where we don't know how to increment - (e.g. `#succ` is not defined) it will raise an `ArgumentError` for ranges + (e.g. `succ` is not defined) it will raise an `ArgumentError` for ranges with excluding beginnings. - ([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3)) -### Notable changes +* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use + `DatabaseTasks.load_schema_current` instead. + ([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608)) -* The PostgreSQL adapter now supports the `JSONB` datatype in PostgreSQL 9.4+. - ([Pull Request](https://github.com/rails/rails/pull/16220)) +* Deprecated `sanitize_sql_hash_for_conditions` without replacement. Using a + `Relation` for performing queries and updates is the preferred API. + ([Commit](https://github.com/rails/rails/commit/d5902c9e)) -* The `#references` method in migrations now supports a `type` option for - specifying the type of the foreign key (e.g. `:uuid`). - ([Pull Request](https://github.com/rails/rails/pull/16231)) +* Deprecated `add_timestamps` and `t.timestamps` without passing the `:null` + option. The default of `null: true` will change in Rails 5 to `null: false`. + ([Pull Request](https://github.com/rails/rails/pull/16481)) -* Added a `:required` option to singular associations, which defines a - presence validation on the association. - ([Pull Request](https://github.com/rails/rails/pull/16056)) +* Deprecated `Reflection#source_macro` without replacement as it is no longer + needed in Active Record. + ([Pull Request](https://github.com/rails/rails/pull/16373)) -* Introduced `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the - record is invalid. - ([Pull Request](https://github.com/rails/rails/pull/8639)) +* Deprecated `serialized_attributes` without replacement. + ([Pull Request](https://github.com/rails/rails/pull/15704)) -* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`, - meaning that it no longer retains the extra attributes from custom - `select`s. - ([Pull Request](https://github.com/rails/rails/pull/15866)) +* Deprecated returning `nil` from `column_for_attribute` when no column + exists. It will return a null object in Rails 5.0. + ([Pull Request](https://github.com/rails/rails/pull/15878)) -* Introduced the `bin/rake db:purge` task to empty the database for the - current environment. - ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d)) +* Deprecated using `.joins`, `.preload` and `.eager_load` with associations + that depend on the instance state (i.e. those defined with a scope that + takes an argument) without replacement. + ([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1)) + +### Notable changes + +* `SchemaDumper` uses `force: :cascade` on `create_table`. This makes it + possible to reload a schema when foreign keys are in place. + +* Added a `:required` option to singular associations, which defines a + presence validation on the association. + ([Pull Request](https://github.com/rails/rails/pull/16056)) * `ActiveRecord::Dirty` now detects in-place changes to mutable values. - Serialized attributes on ActiveRecord models will no longer save when + Serialized attributes on Active Record models are no longer saved when unchanged. This also works with other types such as string columns and json columns on PostgreSQL. (Pull Requests [1](https://github.com/rails/rails/pull/15674), [2](https://github.com/rails/rails/pull/15786), [3](https://github.com/rails/rails/pull/15788)) -* Added support for `#pretty_print` in `ActiveRecord::Base` objects. - ([Pull Request](https://github.com/rails/rails/pull/15172)) +* Introduced the `db:purge` Rake task to empty the database for the + current environment. + ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d)) + +* Introduced `ActiveRecord::Base#validate!` that raises + `ActiveRecord::RecordInvalid` if the record is invalid. + ([Pull Request](https://github.com/rails/rails/pull/8639)) -* PostgreSQL and SQLite adapters no longer add a default limit of 255 +* Introduced `validate` as an alias for `valid?`. + ([Pull Request](https://github.com/rails/rails/pull/14456)) + +* `touch` now accepts multiple attributes to be touched at once. + ([Pull Request](https://github.com/rails/rails/pull/14423)) + +* The PostgreSQL adapter now supports the `jsonb` datatype in PostgreSQL 9.4+. + ([Pull Request](https://github.com/rails/rails/pull/16220)) + +* The PostgreSQL and SQLite adapters no longer add a default limit of 255 characters on string columns. ([Pull Request](https://github.com/rails/rails/pull/14579)) +* Added support for the `citext` column type in the PostgreSQL adapter. + ([Pull Request](https://github.com/rails/rails/pull/12523)) + +* Added support for user-created range types in the PostgreSQL adapter. + ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032)) + * `sqlite3:///some/path` now resolves to the absolute system path `/some/path`. For relative paths, use `sqlite3:some/path` instead. (Previously, `sqlite3:///some/path` resolved to the relative path - `some/path`. This behaviour was deprecated on Rails 4.1). + `some/path`. This behavior was deprecated on Rails 4.1). ([Pull Request](https://github.com/rails/rails/pull/14569)) -* Introduced `#validate` as an alias for `#valid?`. - ([Pull Request](https://github.com/rails/rails/pull/14456)) - -* `#touch` now accepts multiple attributes to be touched at once. - ([Pull Request](https://github.com/rails/rails/pull/14423)) - * Added support for fractional seconds for MySQL 5.6 and above. (Pull Request [1](https://github.com/rails/rails/pull/8240), [2](https://github.com/rails/rails/pull/14359)) -* Added support for the `citext` column type in PostgreSQL adapter. - ([Pull Request](https://github.com/rails/rails/pull/12523)) +* Added `ActiveRecord::Base#pretty_print` to pretty print models. + ([Pull Request](https://github.com/rails/rails/pull/15172)) -* Added support for user-created range types in PostgreSQL adapter. - ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032)) +* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`, + meaning that it no longer retains the extra attributes from custom + `SELECT`s. + ([Pull Request](https://github.com/rails/rails/pull/15866)) +* `ActiveRecord::Base#reflections` now returns a hash with string keys instead + of symbol keys. ([Pull Request](https://github.com/rails/rails/pull/17718)) + +* The `references` method in migrations now supports a `type` option for + specifying the type of the foreign key (e.g. `:uuid`). + ([Pull Request](https://github.com/rails/rails/pull/16231)) Active Model ------------ @@ -635,17 +745,20 @@ Please refer to the [Changelog][active-model] for detailed changes. ([Pull Request](https://github.com/rails/rails/pull/16180)) * Deprecated `ActiveModel::Dirty#reset_changes` in favor of - `#clear_changes_information`. + `clear_changes_information`. ([Pull Request](https://github.com/rails/rails/pull/16180)) ### Notable changes +* Introduced `validate` as an alias for `valid?`. + ([Pull Request](https://github.com/rails/rails/pull/14456)) + * Introduced the `restore_attributes` method in `ActiveModel::Dirty` to restore the changed (dirty) attributes to their previous values. (Pull Request [1](https://github.com/rails/rails/pull/14861), [2](https://github.com/rails/rails/pull/16180)) -* `has_secure_password` no longer disallow blank passwords (i.e. passwords +* `has_secure_password` no longer disallows blank passwords (i.e. passwords that contains only spaces) by default. ([Pull Request](https://github.com/rails/rails/pull/16412)) @@ -653,10 +766,6 @@ Please refer to the [Changelog][active-model] for detailed changes. characters if validations are enabled. ([Pull Request](https://github.com/rails/rails/pull/15708)) -* Introduced `#validate` as an alias for `#valid?`. - ([Pull Request](https://github.com/rails/rails/pull/14456)) - - Active Support -------------- @@ -687,18 +796,23 @@ Please refer to the [Changelog][active-support] for detailed changes. ### Notable changes +* Introduced a new configuration option `active_support.test_order` for + specifying the order test cases are executed. This option currently defaults + to `:sorted` but will be changed to `:random` in Rails 5.0. + ([Commit](https://github.com/rails/rails/commit/53e877f7d9291b2bf0b8c425f9e32ef35829f35b)) + +* `Object#try` and `Object#try!` can now be used without an explicit receiver in the block. + ([Commit](https://github.com/rails/rails/commit/5e51bdda59c9ba8e5faf86294e3e431bd45f1830), + [Pull Request](https://github.com/rails/rails/pull/17361)) + * The `travel_to` test helper now truncates the `usec` component to 0. ([Commit](https://github.com/rails/rails/commit/9f6e82ee4783e491c20f5244a613fdeb4024beb5)) -* `ActiveSupport::TestCase` now randomizes the order that test cases are ran - by default. - ([Commit](https://github.com/rails/rails/commit/6ffb29d24e05abbd9ffe3ea974140d6c70221807)) - * Introduced `Object#itself` as an identity function. (Commit [1](https://github.com/rails/rails/commit/702ad710b57bef45b081ebf42e6fa70820fdd810), [2](https://github.com/rails/rails/commit/64d91122222c11ad3918cc8e2e3ebc4b0a03448a)) -* `Object#with_options` can now be used without an explicit receiver. +* `Object#with_options` can now be used without an explicit receiver in the block. ([Pull Request](https://github.com/rails/rails/pull/16339)) * Introduced `String#truncate_words` to truncate a string by a number of words. @@ -717,6 +831,7 @@ Please refer to the [Changelog][active-support] for detailed changes. `module Foo; extend ActiveSupport::Concern; end` boilerplate. ([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad)) +* New [guide](constant_autoloading_and_reloading.html) about constant autoloading and reloading. Credits ------- diff --git a/source/action_controller_overview.md b/source/action_controller_overview.md index 4c04a06..57546da 100644 --- a/source/action_controller_overview.md +++ b/source/action_controller_overview.md @@ -112,8 +112,8 @@ NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&id The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type. -NOTE: Values such as `[]`, `[nil]` or `[nil, nil, ...]` in `params` are replaced -with `nil` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation) +NOTE: Values such as `[nil]` or `[nil, nil, ...]` in `params` are replaced +with `[]` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation) for more information. To send a hash you include the key name inside the brackets: @@ -1164,65 +1164,10 @@ class ClientsController < ApplicationController end ``` -WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). If you would like to dynamically generate error pages, see [Custom errors page](#custom-errors-page). +WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information. - -### Custom errors page - -You can customize the layout of your error handling using controllers and views. -First define your app own routes to display the errors page. - -* `config/application.rb` - - ```ruby - config.exceptions_app = self.routes - ``` - -* `config/routes.rb` - - ```ruby - get '/404', to: 'errors#not_found' - get '/422', to: 'errors#unprocessable_entity' - get '/500', to: 'errors#server_error' - ``` - -Create the controller and views. - -* `app/controllers/errors_controller.rb` - - ```ruby - class ErrorsController < ActionController::Base - layout 'error' - - def not_found - render status: :not_found - end - - def unprocessable_entity - render status: :unprocessable_entity - end - - def server_error - render status: :server_error - end - end - ``` - -* `app/views` - - ``` - errors/ - not_found.html.erb - unprocessable_entity.html.erb - server_error.html.erb - layouts/ - error.html.erb - ``` - -Do not forget to set the correct status code on the controller as shown before. You should avoid using the database or any complex operations because the user is already on the error page. Generating another error while on an error page could cause issues. - Force HTTPS protocol -------------------- diff --git a/source/action_mailer_basics.md b/source/action_mailer_basics.md index f981d0d..ba7c16a 100644 --- a/source/action_mailer_basics.md +++ b/source/action_mailer_basics.md @@ -35,10 +35,26 @@ views. ```bash $ bin/rails generate mailer UserMailer create app/mailers/user_mailer.rb +create app/mailers/application_mailer.rb invoke erb create app/views/user_mailer +create app/views/layouts/mailer.text.erb +create app/views/layouts/mailer.html.erb invoke test_unit create test/mailers/user_mailer_test.rb +create test/mailers/previews/user_mailer_preview.rb +``` + +```ruby +# app/mailers/application_mailer.rb +class ApplicationMailer < ActionMailer::Base + default "from@example.com" + layout 'mailer' +end + +# app/mailers/user_mailer.rb +class UserMailer < ApplicationMailer +end ``` As you can see, you can generate mailers just like you use other generators with @@ -63,8 +79,7 @@ delivered via email. `app/mailers/user_mailer.rb` contains an empty mailer: ```ruby -class UserMailer < ActionMailer::Base - default from: 'from@example.com' +class UserMailer < ApplicationMailer end ``` @@ -72,7 +87,7 @@ Let's add a method called `welcome_email`, that will send an email to the user's registered email address: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -298,8 +313,7 @@ Action Mailer 3.0 makes inline attachments, which involved a lot of hacking in p ```html+erb

Hello there, this is our image

- <%= image_tag attachments['image.jpg'].url, alt: 'My Photo', - class: 'photos' %> + <%= image_tag attachments['image.jpg'].url, alt: 'My Photo', class: 'photos' %> ``` #### Sending Email To Multiple Recipients @@ -349,7 +363,7 @@ for the HTML version and `welcome_email.text.erb` for the plain text version. To change the default mailer view for your action you do something like: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -371,7 +385,7 @@ If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer default from: 'notifications@example.com' def welcome_email(user) @@ -401,7 +415,7 @@ layout. In order to use a different file, call `layout` in your mailer: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer layout 'awesome' # use awesome.(html|text).erb as the layout end ``` @@ -413,7 +427,7 @@ You can also pass in a `layout: 'layout_name'` option to the render call inside the format block to specify different layouts for different formats: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user) mail(to: user.email) do |format| format.html { render layout: 'my_layout' } @@ -456,16 +470,7 @@ By using the full URL, your links will now work in your emails. #### generating URLs with `url_for` -You need to pass the `only_path: false` option when using `url_for`. This will -ensure that absolute URLs are generated because the `url_for` view helper will, -by default, generate relative URLs when a `:host` option isn't explicitly -provided. - -```erb -<%= url_for(controller: 'welcome', - action: 'greeting', - only_path: false) %> -``` +`url_for` generate full URL by default in templates. If you did not configure the `:host` option globally make sure to pass it to `url_for`. @@ -477,9 +482,6 @@ If you did not configure the `:host` option globally make sure to pass it to action: 'greeting') %> ``` -NOTE: When you explicitly pass the `:host` Rails will always generate absolute -URLs, so there is no need to pass `only_path: false`. - #### generating URLs with named routes Email clients have no web context and so paths have no base URL to form complete @@ -511,7 +513,7 @@ while delivering emails, you can do this using `delivery_method_options` in the mailer action. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user, company) @user = user @url = user_url(https://github.com/@user) @@ -533,7 +535,7 @@ option. In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def welcome_email(user, email_body) mail(to: user.email, body: email_body, @@ -563,7 +565,7 @@ mailer, and pass the email object to the mailer `receive` instance method. Here's an example: ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer def receive(email) page = Page.find_by(address: email.to.first) page.emails.create( @@ -599,7 +601,7 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and using instance variables set in your mailer action. ```ruby -class UserMailer < ActionMailer::Base +class UserMailer < ApplicationMailer after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers diff --git a/source/action_view_overview.md b/source/action_view_overview.md index 683e633..f4d5bb8 100644 --- a/source/action_view_overview.md +++ b/source/action_view_overview.md @@ -7,7 +7,6 @@ After reading this guide, you will know: * How best to use templates, partials, and layouts. * What helpers are provided by Action View and how to make your own. * How to use localized views. -* How to use Action View outside of Rails. -------------------------------------------------------------------------------- diff --git a/source/active_job_basics.md b/source/active_job_basics.md index df51231..3657d7c 100644 --- a/source/active_job_basics.md +++ b/source/active_job_basics.md @@ -23,7 +23,7 @@ clean-ups, to billing charges, to mailings. Anything that can be chopped up into small units of work and run in parallel, really. -The Purpose of the Active Job +The Purpose of Active Job ----------------------------- The main point is to ensure that all Rails apps will have a job infrastructure in place, even if it's in the form of an "immediate runner". We can then have @@ -41,10 +41,12 @@ This section will provide a step-by-step guide to creating a job and enqueuing i ### Create the Job Active Job provides a Rails generator to create jobs. The following will create a -job in `app/jobs`: +job in `app/jobs` (with an attached test case under `test/jobs`): ```bash $ bin/rails generate job guests_cleanup +invoke test_unit +create test/jobs/guests_cleanup_job_test.rb create app/jobs/guests_cleanup_job.rb ``` @@ -52,12 +54,8 @@ You can also create a job that will run on a specific queue: ```bash $ bin/rails generate job guests_cleanup --queue urgent -create app/jobs/guests_cleanup_job.rb ``` -As you can see, you can generate jobs just like you use other generators with -Rails. - If you don't want to use a generator, you could create your own file inside of `app/jobs`, just make sure that it inherits from `ActiveJob::Base`. @@ -78,15 +76,18 @@ end Enqueue a job like so: ```ruby -MyJob.enqueue record # Enqueue a job to be performed as soon the queueing system is free. +# Enqueue a job to be performed as soon the queueing system is free. +MyJob.perform_later record ``` ```ruby -MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon. +# Enqueue a job to be performed tomorrow at noon. +MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) ``` ```ruby -MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now. +# Enqueue a job to be performed 1 week from now. +MyJob.set(wait: 1.week).perform_later(record) ``` That's it! @@ -99,46 +100,23 @@ If no adapter is set, the job is immediately executed. ### Backends -Active Job has adapters for the following queueing backends: - -* [Backburner](https://github.com/nesquena/backburner) -* [Delayed Job](https://github.com/collectiveidea/delayed_job) -* [Qu](https://github.com/bkeepers/qu) -* [Que](https://github.com/chanks/que) -* [QueueClassic 2.x](https://github.com/ryandotsmith/queue_classic/tree/v2.2.3) -* [Resque 1.x](https://github.com/resque/resque/tree/1-x-stable) -* [Sidekiq](https://github.com/mperham/sidekiq) -* [Sneakers](https://github.com/jondot/sneakers) -* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch) - -#### Backends Features - -| | Async | Queues | Delayed | Priorities | Timeout | Retries | -|-----------------------|-------|--------|-----------|------------|---------|---------| -| **Backburner** | Yes | Yes | Yes | Yes | Job | Global | -| **Delayed Job** | Yes | Yes | Yes | Job | Global | Global | -| **Que** | Yes | Yes | Yes | Job | No | Job | -| **Queue Classic** | Yes | Yes | No* | No | No | No | -| **Resque** | Yes | Yes | Yes (Gem) | Queue | Global | Yes | -| **Sidekiq** | Yes | Yes | Yes | Queue | No | Job | -| **Sneakers** | Yes | Yes | No | Queue | Queue | No | -| **Sucker Punch** | Yes | Yes | No | No | No | No | -| **Active Job Inline** | No | Yes | N/A | N/A | N/A | N/A | -| **Active Job** | Yes | Yes | Yes | No | No | No | - -NOTE: -* Queue Classic does not support Job scheduling. However you can implement this -yourself or you can use the queue_classic-later gem. See the documentation for -ActiveJob::QueueAdapters::QueueClassicAdapter. - -### Change Backends - -You can easily change your adapter: +Active Job has built-in adapters for multiple queueing backends (Sidekiq, +Resque, Delayed Job and others). To get an up-to-date list of the adapters +see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). + +### Setting the Backend + +You can easily set your queueing backend: ```ruby -# be sure to have the adapter gem in your Gemfile and follow the adapter specific -# installation and deployment instructions -YourApp::Application.config.active_job.queue_adapter = :sidekiq +# config/application.rb +module YourApp + class Application < Rails::Application + # Be sure to have the adapter's gem in your Gemfile and follow + # the adapter's specific installation and deployment instructions. + config.active_job.queue_adapter = :sidekiq + end +end ``` @@ -155,7 +133,7 @@ class GuestsCleanupJob < ActiveJob::Base end ``` -Also you can prefix the queue name for all your jobs using +You can prefix the queue name for all your jobs using `config.active_job.queue_name_prefix` in `application.rb`: ```ruby @@ -172,8 +150,62 @@ class GuestsCleanupJob < ActiveJob::Base #.... end -# Now your job will run on queue production_low_priority on your production -# environment and on beta_low_priority on your beta environment +# Now your job will run on queue production_low_priority on your +# production environment and on staging_low_priority on your staging +# environment +``` + +The default queue name prefix delimiter is '\_'. This can be changed by setting +`config.active_job.queue_name_delimiter` in `application.rb`: + +```ruby +# config/application.rb +module YourApp + class Application < Rails::Application + config.active_job.queue_name_prefix = Rails.env + config.active_job.queue_name_delimiter = '.' + end +end + +# app/jobs/guests_cleanup.rb +class GuestsCleanupJob < ActiveJob::Base + queue_as :low_priority + #.... +end + +# Now your job will run on queue production.low_priority on your +# production environment and on staging.low_priority on your staging +# environment +``` + +If you want more control on what queue a job will be run you can pass a `:queue` +option to `#set`: + +```ruby +MyJob.set(queue: :another_queue).perform_later(record) +``` + +To control the queue from the job level you can pass a block to `#queue_as`. The +block will be executed in the job context (so you can access `self.arguments`) +and you must return the queue name: + +```ruby +class ProcessVideoJob < ActiveJob::Base + queue_as do + video = self.arguments.first + if video.owner.premium? + :premium_videojobs + else + :videojobs + end + end + + def perform(video) + # do process video + end +end + +ProcessVideoJob.perform_later(Video.last) ``` NOTE: Make sure your queueing backend "listens" on your queue name. For some @@ -202,7 +234,7 @@ class GuestsCleanupJob < ActiveJob::Base queue_as :default before_enqueue do |job| - # do somthing with the job instance + # do something with the job instance end around_perform do |job, block| @@ -218,7 +250,7 @@ end ``` -ActionMailer +Action Mailer ------------ One of the most common jobs in a modern web application is sending emails outside @@ -236,12 +268,13 @@ UserMailer.welcome(@user).deliver_later GlobalID -------- + Active Job supports GlobalID for parameters. This makes it possible to pass live Active Record objects to your job instead of class/id pairs, which you then have to manually deserialize. Before, jobs would look like this: ```ruby -class TrashableCleanupJob +class TrashableCleanupJob < ActiveJob::Base def perform(trashable_class, trashable_id, depth) trashable = trashable_class.constantize.find(trashable_id) trashable.cleanup(depth) @@ -252,14 +285,14 @@ end Now you can simply do: ```ruby -class TrashableCleanupJob +class TrashableCleanupJob < ActiveJob::Base def perform(trashable, depth) trashable.cleanup(depth) end end ``` -This works with any class that mixes in `ActiveModel::GlobalIdentification`, which +This works with any class that mixes in `GlobalID::Identification`, which by default has been mixed into Active Model classes. diff --git a/source/active_model_basics.md b/source/active_model_basics.md index 3eaeeff..a520b91 100644 --- a/source/active_model_basics.md +++ b/source/active_model_basics.md @@ -1,20 +1,32 @@ Active Model Basics =================== -This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-Active Record models. Active Model also helps building custom ORMs for use outside of the Rails framework. +This guide should provide you with all you need to get started using model +classes. Active Model allows for Action Pack helpers to interact with +plain Ruby objects. Active Model also helps build custom ORMs for use +outside of the Rails framework. -After reading this guide, you will know: +After reading this guide, you will be able to add to plain Ruby objects: + +* The ability to behave like an Active Record model. +* Callbacks and validations like Active Record. +* Serializers. +* Integration with the Rails internationalization (i18n) framework. -------------------------------------------------------------------------------- Introduction ------------ -Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below. +Active Model is a library containing various modules used in developing +classes that need some features present on Active Record. +Some of these modules are explained below. -### AttributeMethods +### Attribute Methods -The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes and which methods on the object will use them. +The `ActiveModel::AttributeMethods` module can add custom prefixes and suffixes +on methods of a class. It is used by defining the prefixes and suffixes and +which methods on the object will use them. ```ruby class Person @@ -38,14 +50,17 @@ end person = Person.new person.age = 110 -person.age_highest? # true -person.reset_age # 0 -person.age_highest? # false +person.age_highest? # => true +person.reset_age # => 0 +person.age_highest? # => false ``` ### Callbacks -Callbacks gives Active Record style callbacks. This provides an ability to define callbacks which run at appropriate times. After defining callbacks, you can wrap them with before, after and around custom methods. +`ActiveModel::Callbacks` gives Active Record style callbacks. This provides an +ability to define callbacks which run at appropriate times. +After defining callbacks, you can wrap them with before, after and around +custom methods. ```ruby class Person @@ -69,7 +84,9 @@ end ### Conversion -If a class defines `persisted?` and `id` methods, then you can include the `Conversion` module in that class and call the Rails conversion methods on objects of that class. +If a class defines `persisted?` and `id` methods, then you can include the +`ActiveModel::Conversion` module in that class and call the Rails conversion +methods on objects of that class. ```ruby class Person @@ -92,11 +109,13 @@ person.to_param # => nil ### Dirty -An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes `first_name` and `last_name`: +An object becomes dirty when it has gone through one or more changes to its +attributes and has not been saved. `ActiveModel::Dirty` gives the ability to +check whether an object has been changed or not. It also has attribute based +accessor methods. Let's consider a Person class with attributes `first_name` +and `last_name`: ```ruby -require 'active_model' - class Person include ActiveModel::Dirty define_attribute_methods :first_name, :last_name @@ -162,10 +181,11 @@ Track what was the previous value of the attribute. ```ruby # attr_name_was accessor -person.first_name_was # => "First Name" +person.first_name_was # => nil ``` -Track both previous and current value of the changed attribute. Returns an array if changed, else returns nil. +Track both previous and current value of the changed attribute. Returns an array +if changed, else returns nil. ```ruby # attr_name_change @@ -175,7 +195,8 @@ person.last_name_change # => nil ### Validations -Validations module adds the ability to class objects to validate them in Active Record style. +`ActiveModel::Validations` module adds the ability to validate class objects +like in Active Record. ```ruby class Person @@ -188,7 +209,8 @@ class Person validates! :token, presence: true end -person = Person.new(token: "2b1f325") +person = Person.new +person.token = "2b1f325" person.valid? # => false person.name = 'vishnu' person.email = 'me' @@ -199,9 +221,9 @@ person.token = nil person.valid? # => raises ActiveModel::StrictValidationFailed ``` -### ActiveModel::Naming +### Naming -Naming adds a number of class methods which make the naming and routing +`ActiveModel::Naming` adds a number of class methods which make the naming and routing easier to manage. The module defines the `model_name` class method which will define a number of accessors using some `ActiveSupport::Inflector` methods. @@ -221,3 +243,312 @@ Person.model_name.i18n_key # => :person Person.model_name.route_key # => "people" Person.model_name.singular_route_key # => "person" ``` + +### Model + +`ActiveModel::Model` adds the ability to a class to work with Action Pack and +Action View right out of the box. + +```ruby +class EmailContact + include ActiveModel::Model + + attr_accessor :name, :email, :message + validates :name, :email, :message, presence: true + + def deliver + if valid? + # deliver email + end + end +end +``` + +When including `ActiveModel::Model` you get some features like: + +- model name introspection +- conversions +- translations +- validations + +It also gives you the ability to initialize an object with a hash of attributes, +much like any Active Record object. + +```ruby +email_contact = EmailContact.new(name: 'David', + email: 'david@example.com', + message: 'Hello World') +email_contact.name # => 'David' +email_contact.email # => 'david@example.com' +email_contact.valid? # => true +email_contact.persisted? # => false +``` + +Any class that includes `ActiveModel::Model` can be used with `form_for`, +`render` and any other Action View helper methods, just like Active Record +objects. + +### Serialization + +`ActiveModel::Serialization` provides a basic serialization for your object. +You need to declare an attributes hash which contains the attributes you want to +serialize. Attributes must be strings, not symbols. + +```ruby +class Person + include ActiveModel::Serialization + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +Now you can access a serialized hash of your object using the `serializable_hash`. + +```ruby +person = Person.new +person.serializable_hash # => {"name"=>nil} +person.name = "Bob" +person.serializable_hash # => {"name"=>"Bob"} +``` + +#### ActiveModel::Serializers + +Rails provides two serializers `ActiveModel::Serializers::JSON` and +`ActiveModel::Serializers::Xml`. Both of these modules automatically include +the `ActiveModel::Serialization`. + +##### ActiveModel::Serializers::JSON + +To use the `ActiveModel::Serializers::JSON` you only need to change from +`ActiveModel::Serialization` to `ActiveModel::Serializers::JSON`. + +```ruby +class Person + include ActiveModel::Serializers::JSON + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +With the `as_json` you have a hash representing the model. + +```ruby +person = Person.new +person.as_json # => {"name"=>nil} +person.name = "Bob" +person.as_json # => {"name"=>"Bob"} +``` + +From a JSON string you define the attributes of the model. +You need to have the `attributes=` method defined on your class: + +```ruby +class Person + include ActiveModel::Serializers::JSON + + attr_accessor :name + + def attributes=(hash) + hash.each do |key, value| + send("#{key}=", value) + end + end + + def attributes + {'name' => nil} + end +end +``` + +Now it is possible to create an instance of person and set the attributes using `from_json`. + +```ruby +json = { name: 'Bob' }.to_json +person = Person.new +person.from_json(json) # => # +person.name # => "Bob" +``` + +##### ActiveModel::Serializers::Xml + +To use the `ActiveModel::Serializers::Xml` you only need to change from +`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`. + +```ruby +class Person + include ActiveModel::Serializers::Xml + + attr_accessor :name + + def attributes + {'name' => nil} + end +end +``` + +With the `to_xml` you have a XML representing the model. + +```ruby +person = Person.new +person.to_xml # => "\n\n \n\n" +person.name = "Bob" +person.to_xml # => "\n\n Bob\n\n" +``` + +From a XML string you define the attributes of the model. +You need to have the `attributes=` method defined on your class: + +```ruby +class Person + include ActiveModel::Serializers::Xml + + attr_accessor :name + + def attributes=(hash) + hash.each do |key, value| + send("#{key}=", value) + end + end + + def attributes + {'name' => nil} + end +end +``` + +Now it is possible to create an instance of person and set the attributes using `from_xml`. + +```ruby +xml = { name: 'Bob' }.to_xml +person = Person.new +person.from_xml(xml) # => # +person.name # => "Bob" +``` + +### Translation + +`ActiveModel::Translation` provides integration between your object and the Rails +internationalization (i18n) framework. + +```ruby +class Person + extend ActiveModel::Translation +end +``` + +With the `human_attribute_name` you can transform attribute names into a more +human format. The human format is defined in your locale file. + +* config/locales/app.pt-BR.yml + + ```yml + pt-BR: + activemodel: + attributes: + person: + name: 'Nome' + ``` + +```ruby +Person.human_attribute_name('name') # => "Nome" +``` + +### Lint Tests + +`ActiveModel::Lint::Tests` allow you to test whether an object is compliant with +the Active Model API. + +* app/models/person.rb + + ```ruby + class Person + include ActiveModel::Model + + end + ``` + +* test/models/person_test.rb + + ```ruby + require 'test_helper' + + class PersonTest < ActiveSupport::TestCase + include ActiveModel::Lint::Tests + + def setup + @model = Person.new + end + end + ``` + +```bash +$ rake test + +Run options: --seed 14596 + +# Running: + +...... + +Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s. + +6 runs, 30 assertions, 0 failures, 0 errors, 0 skips +``` + +An object is not required to implement all APIs in order to work with +Action Pack. This module only intends to provide guidance in case you want all +features out of the box. + +### SecurePassword + +`ActiveModel::SecurePassword` provides a way to securely store any +password in an encrypted form. On including this module, a +`has_secure_password` class method is provided which defines +an accessor named `password` with certain validations on it. + +#### Requirements + +`ActiveModel::SecurePassword` depends on the [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), +so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly. +In order to make this work, the model must have an accessor named `password_digest`. +The `has_secure_password` will add the following validations on the `password` accessor: + +1. Password should be present. +2. Password should be equal to its confirmation. +3. This maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) + +#### Examples + +```ruby +class Person + include ActiveModel::SecurePassword + has_secure_password + attr_accessor :password_digest +end + +person = Person.new + +# When password is blank. +person.valid? # => false + +# When the confirmation doesn't match the password. +person.password = 'aditya' +person.password_confirmation = 'nomatch' +person.valid? # => false + +# When the length of password, exceeds 72. +person.password = person.password_confirmation = 'a' * 100 +person.valid? # => false + +# When all validations are passed. +person.password = person.password_confirmation = 'aditya' +person.valid? # => true +``` diff --git a/source/active_record_basics.md b/source/active_record_basics.md index eff93ce..ef86531 100644 --- a/source/active_record_basics.md +++ b/source/active_record_basics.md @@ -31,7 +31,7 @@ Object Relational Mapping system. in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring -data access logic is part of the object will educate users of that +data access logic as part of the object will educate users of that object on how to write to and read from the database. ### Object Relational Mapping @@ -116,7 +116,7 @@ to Active Record instances: locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model. * `type` - Specifies that the model uses [Single Table - Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance). + Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance). * `(association_name)_type` - Stores the type for [polymorphic associations](association_basics.html#polymorphic-associations). * `(table_name)_count` - Used to cache the number of belonging objects on @@ -358,7 +358,7 @@ class CreatePublications < ActiveRecord::Migration t.string :publisher_type t.boolean :single_issue - t.timestamps + t.timestamps null: false end add_index :publications, :publication_type_id end diff --git a/source/active_record_migrations.md b/source/active_record_migrations.md index 229c6ee..e76a57e 100644 --- a/source/active_record_migrations.md +++ b/source/active_record_migrations.md @@ -39,7 +39,7 @@ class CreateProducts < ActiveRecord::Migration t.string :name t.text :description - t.timestamps + t.timestamps null: false end end end @@ -285,7 +285,7 @@ class CreateProducts < ActiveRecord::Migration t.string :name t.text :description - t.timestamps + t.timestamps null: false end end end @@ -452,6 +452,8 @@ number of digits after the decimal point. are using a dynamic value (such as a date), the default will only be calculated the first time (i.e. on the date the migration is applied). * `index` Adds an index for the column. +* `required` Adds `required: true` for `belongs_to` associations and +`null: false` to the column in the migration. Some adapters may support additional options; see the adapter specific API docs for further information. @@ -466,7 +468,7 @@ add_foreign_key :articles, :authors ``` This adds a new foreign key to the `author_id` column of the `articles` -table. The key references the `id` column of the `articles` table. If the +table. The key references the `id` column of the `authors` table. If the column names can not be derived from the table names, you can use the `:column` and `:primary_key` options. @@ -824,7 +826,7 @@ class CreateProducts < ActiveRecord::Migration create_table :products do |t| t.string :name t.text :description - t.timestamps + t.timestamps null: false end end diff --git a/source/active_record_postgresql.md b/source/active_record_postgresql.md index 6c94218..446be1c 100644 --- a/source/active_record_postgresql.md +++ b/source/active_record_postgresql.md @@ -83,9 +83,12 @@ Book.where("array_length(ratings, 1) >= 3") * [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html) +NOTE: you need to enable the `hstore` extension to use hstore. + ```ruby # db/migrate/20131009135255_create_profiles.rb ActiveRecord::Schema.define do + enable_extension 'hstore' unless extension_enabled?('hstore') create_table :profiles do |t| t.hstore 'settings' end @@ -103,11 +106,6 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"} profile.settings = {"color" => "yellow", "resolution" => "1280x1024"} profile.save! - -## you need to call _will_change! if you are editing the store in place -profile.settings["color"] = "green" -profile.settings_will_change! -profile.save! ``` ### JSON @@ -219,7 +217,7 @@ Currently there is no special support for enumerated types. They are mapped as normal text columns: ```ruby -# db/migrate/20131220144913_create_events.rb +# db/migrate/20131220144913_create_articles.rb execute <<-SQL CREATE TYPE article_status AS ENUM ('draft', 'published'); SQL @@ -281,7 +279,7 @@ end # Usage User.create settings: "01010011" user = User.first -user.settings # => "(Paris,Champs-Élysées)" +user.settings # => "01010011" user.settings = "0xAF" user.settings # => 10101111 user.save! diff --git a/source/active_record_querying.md b/source/active_record_querying.md index cb243c9..2e7bb74 100644 --- a/source/active_record_querying.md +++ b/source/active_record_querying.md @@ -9,6 +9,7 @@ After reading this guide, you will know: * How to specify the order, retrieved attributes, grouping, and other properties of the found records. * How to use eager loading to reduce the number of database queries needed for data retrieval. * How to use dynamic finders methods. +* How to use method chaining to use multiple ActiveRecord methods together. * How to check for the existence of particular records. * How to perform various calculations on Active Record models. * How to run EXPLAIN on relations. @@ -340,16 +341,14 @@ The `find_in_batches` method is similar to `find_each`, since both retrieve batc ```ruby # Give add_invoices an array of 1000 invoices at a time -Invoice.find_in_batches(include: :invoice_lines) do |invoices| +Invoice.find_in_batches do |invoices| export.add_invoices(invoices) end ``` -NOTE: The `:include` option allows you to name associations that should be loaded alongside with the models. - ##### Options for `find_in_batches` -The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`, as well as most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_in_batches`. +The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`. Conditions ---------- @@ -1329,10 +1328,67 @@ You can specify an exclamation point (`!`) on the end of the dynamic finders to If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`. +Understanding The Method Chaining +--------------------------------- + +The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining), +which allow us to use multiple Active Record methods together in a simple and straightforward way. + +You can chain a method in a sentence when the previous method called returns `ActiveRecord::Relation`, +like `all`, `where`, and `joins`. Methods that returns a instance of a single object +(see [Retrieving a Single Object Section](#retrieving-a-single-object)) have to be be the last +in the sentence. + +There are some examples below. This guide won't cover all the possibilities, just a few as example. +When a Active Record method is called, the query is not immediately generated and sent to the database, +this just happen when the data is actually needed. So each example below generate a single query. + +### Retrieving filtered data from multiple tables + +```ruby +Person + .select('people.id, people.name, comments.text') + .joins(:comments) + .where('comments.created_at > ?', 1.week.ago) +``` + +The result should be something like this: + +```sql +SELECT people.id, people.name, comments.text +FROM people +INNER JOIN comments + ON comments.person_id = people.id +WHERE comments.created_at = '2015-01-01' +``` + +### Retrieving specific data from multiple tables + +```ruby +Person + .select('people.id, people.name, companies.name') + .joins(:company) + .find_by('people.name' => 'John') # this should be the last +``` + +The above should generate: + +```sql +SELECT people.id, people.name, companies.name +FROM people +INNER JOIN companies + ON companies.person_id = people.id +WHERE people.name = 'John' +LIMIT 1 +``` + +NOTE: Remember that, if `find_by` return more than one registry, it will take just the first +and ignore the others. Note the `LIMIT 1` statement above. + Find or Build a New Object -------------------------- -NOTE: Some dynamic finders have been deprecated in Rails 4.0 and will be +NOTE: Some dynamic finders were deprecated in Rails 4.0 and removed in Rails 4.1. The best practice is to use Active Record scopes instead. You can find the deprecation gem at https://github.com/rails/activerecord-deprecated_finders diff --git a/source/active_record_validations.md b/source/active_record_validations.md index 582bb24..7628b82 100644 --- a/source/active_record_validations.md +++ b/source/active_record_validations.md @@ -361,6 +361,8 @@ class Product < ActiveRecord::Base end ``` +Alternatively, you can require that the specified attribute does _not_ match the regular expression by using the `:without` option. + The default error message is _"is invalid"_. ### `inclusion` @@ -425,7 +427,7 @@ class Essay < ActiveRecord::Base validates :content, length: { minimum: 300, maximum: 400, - tokenizer: lambda { |str| str.scan(/\w+/) }, + tokenizer: lambda { |str| str.split(/\s+/) }, too_short: "must have at least %{count} words", too_long: "must have at most %{count} words" } @@ -485,6 +487,8 @@ constraints to acceptable values: * `:even` - Specifies the value must be an even number if set to true. The default error message for this option is _"must be even"_. +NOTE: By default, `numericality` doesn't allow `nil` values. You can use `allow_nil: true` option to permit it. + The default error message is _"is not a number"_. ### `presence` @@ -524,9 +528,16 @@ If you validate the presence of an object associated via a `has_one` or `marked_for_destruction?`. Since `false.blank?` is true, if you want to validate the presence of a boolean -field you should use `validates :field_name, inclusion: { in: [true, false] }`. +field you should use one of the following validations: + +```ruby +validates :boolean_field_name, presence: true +validates :boolean_field_name, inclusion: { in: [true, false] } +validates :boolean_field_name, exclusion: { in: [nil] } +``` -The default error message is _"can't be blank"_. +By using one of these validations, you will ensure the value will NOT be `nil` +which would result in a `NULL` value in most cases. ### `absence` @@ -698,7 +709,7 @@ we don't want names and surnames to begin with lower case. ```ruby class Person < ActiveRecord::Base validates_each :name, :surname do |record, attr, value| - record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ + record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/ end end ``` diff --git a/source/active_support_core_extensions.md b/source/active_support_core_extensions.md index 5ed392d..faad34d 100644 --- a/source/active_support_core_extensions.md +++ b/source/active_support_core_extensions.md @@ -162,7 +162,7 @@ Active Support provides `duplicable?` to programmatically query an object about false.duplicable? # => false ``` -By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, and module objects. +By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, module, and method objects. WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. @@ -1011,7 +1011,7 @@ self.default_params = { }.freeze ``` -They can be also accessed and overridden at the instance level. +They can also be accessed and overridden at the instance level. ```ruby A.x = 1 @@ -1268,7 +1268,7 @@ The method `squish` strips leading and trailing whitespace, and substitutes runs There's also the destructive version `String#squish!`. -Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). +Note that it handles both ASCII and Unicode whitespace. NOTE: Defined in `active_support/core_ext/string/filters.rb`. @@ -1310,6 +1310,38 @@ In above examples "dear" gets cut first, but then `:separator` prevents it. NOTE: Defined in `active_support/core_ext/string/filters.rb`. +### `truncate_words` + +The method `truncate_words` returns a copy of its receiver truncated after a given number of words: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(4) +# => "Oh dear! Oh dear!..." +``` + +Ellipsis can be customized with the `:omission` option: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: '…') +# => "Oh dear! Oh dear!…" +``` + +Pass a `:separator` to truncate the string at a natural break: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!') +# => "Oh dear! Oh dear! I shall be late..." +``` + +The option `:separator` can be a regexp: + +```ruby +"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/) +# => "Oh dear! Oh dear!..." +``` + +NOTE: Defined in `active_support/core_ext/string/filters.rb`. + ### `inquiry` The `inquiry` method converts a string into a `StringInquirer` object making equality checks prettier. @@ -1415,7 +1447,7 @@ Returns the substring of the string starting at position `position`: "hello".from(0) # => "hello" "hello".from(2) # => "llo" "hello".from(-2) # => "lo" -"hello".from(10) # => "" if < 1.9, nil in 1.9 +"hello".from(10) # => nil ``` NOTE: Defined in `active_support/core_ext/string/access.rb`. @@ -1801,16 +1833,14 @@ attribute names: ```ruby def full_messages - full_messages = [] - - each do |attribute, messages| - ... - attr_name = attribute.to_s.gsub('.', '_').humanize - attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - ... - end + map { |attribute, message| full_message(attribute, message) } +end - full_messages +def full_message + ... + attr_name = attribute.to_s.tr('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, default: attr_name) + ... end ``` @@ -1920,24 +1950,6 @@ as well as adding or subtracting their results from a Time object. For example: (4.months + 5.years).from_now ``` -While these methods provide precise calculation when used as in the examples above, care -should be taken to note that this is not true if the result of `months', `years', etc is -converted before use: - -```ruby -# equivalent to 30.days.to_i.from_now -1.month.to_i.from_now - -# equivalent to 365.25.days.to_f.from_now -1.year.to_f.from_now -``` - -In such cases, Ruby's core [Date](http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html) and -[Time](http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html) should be used for precision -date and time arithmetic. - -NOTE: Defined in `active_support/core_ext/numeric/time.rb`. - ### Formatting Enables the formatting of numbers in a variety of ways. @@ -2862,6 +2874,20 @@ Active Record does not accept unknown options when building associations, for ex NOTE: Defined in `active_support/core_ext/hash/keys.rb`. +### Working with Values + +#### `transform_values` && `transform_values!` + +The method `transform_values` accepts a block and returns a hash that has applied the block operations to each of the values in the receiver. + +```ruby +{ nil => nil, 1 => 1, :x => :a }.transform_values { |value| value.to_s.upcase } +# => {nil=>"", 1=>"1", :x=>"A"} +``` +There's also the bang variant `transform_values!` that applies the block operations to values in the very receiver. + +NOTE: Defined in `active_support/core_text/hash/transform_values.rb`. + ### Slicing Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes: diff --git a/source/active_support_instrumentation.md b/source/active_support_instrumentation.md index 7033947..9dfacce 100644 --- a/source/active_support_instrumentation.md +++ b/source/active_support_instrumentation.md @@ -135,7 +135,9 @@ Action Controller | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | +| `:status` | HTTP status code | | `:view_runtime` | Amount spent in view in ms | +| `:db_runtime` | Amount spent executing database queries in ms | ```ruby { @@ -223,11 +225,11 @@ Active Record ### sql.active_record -| Key | Value | -| ------------ | --------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:object_id` | `self.object_id` | +| Key | Value | +| ---------------- | --------------------- | +| `:sql` | SQL statement | +| `:name` | Name of the operation | +| `:connection_id` | `self.object_id` | INFO. The adapters will add their own data as well. diff --git a/source/asset_pipeline.md b/source/asset_pipeline.md index e31cefa..156daf1 100644 --- a/source/asset_pipeline.md +++ b/source/asset_pipeline.md @@ -166,9 +166,9 @@ pipeline, the preferred location for these assets is now the `app/assets` directory. Files in this directory are served by the Sprockets middleware. Assets can still be placed in the `public` hierarchy. Any assets under `public` -will be served as static files by the application or web server. You should use -`app/assets` for files that must undergo some pre-processing before they are -served. +will be served as static files by the application or web server when +`config.serve_static_files` is set to true. You should use `app/assets` for +files that must undergo some pre-processing before they are served. In production, Rails precompiles these files to `public/assets` by default. The precompiled copies are then served as static assets by the web server. The files @@ -207,9 +207,7 @@ precompiling works. NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows, you have a JavaScript runtime installed in -your operating system. Check -[ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all -supported JavaScript runtimes. +your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes. You can also disable generation of controller specific asset files by adding the following to your `config/application.rb` configuration: @@ -232,7 +230,9 @@ images, JavaScript files or stylesheets. scope of the application or those libraries which are shared across applications. * `vendor/assets` is for assets that are owned by outside entities, such as -code for JavaScript plugins and CSS frameworks. +code for JavaScript plugins and CSS frameworks. Keep in mind that third party +code with references to other files also processed by the asset Pipeline (images, +stylesheets, etc.), will need to be rewritten to use helpers like `asset_path`. WARNING: If you are upgrading from Rails 3, please take into account that assets under `lib/assets` or `vendor/assets` are available for inclusion via the @@ -736,10 +736,10 @@ Rails.application.config.assets.precompile << Proc.new do |path| full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path if full_path.starts_with? app_assets_path - puts "including asset: " + full_path + logger.info "including asset: " + full_path true else - puts "excluding asset: " + full_path + logger.info "excluding asset: " + full_path false end else @@ -916,24 +916,207 @@ end ### CDNs -If your assets are being served by a CDN, ensure they don't stick around in your -cache forever. This can cause problems. If you use -`config.action_controller.perform_caching = true`, Rack::Cache will use -`Rails.cache` to store assets. This can cause your cache to fill up quickly. +CDN stands for [Content Delivery +Network](http://en.wikipedia.org/wiki/Content_delivery_network), they are +primarily designed to cache assets all over the world so that when a browser +requests the asset, a cached copy will be geographically close to that browser. +If you are serving assets directly from your Rails server in production, the +best practice is to use a CDN in front of your application. + +A common pattern for using a CDN is to set your production application as the +"origin" server. This means when a browser requests an asset from the CDN and +there is a cache miss, it will grab the file from your server on the fly and +then cache it. For example if you are running a Rails application on +`example.com` and have a CDN configured at `mycdnsubdomain.fictional-cdn.com`, +then when a request is made to `mycdnsubdomain.fictional- +cdn.com/assets/smile.png`, the CDN will query your server once at +`example.com/assets/smile.png` and cache the request. The next request to the +CDN that comes in to the same URL will hit the cached copy. When the CDN can +serve an asset directly the request never touches your Rails server. Since the +assets from a CDN are geographically closer to the browser, the request is +faster, and since your server doesn't need to spend time serving assets, it can +focus on serving application code as fast as possible. + +#### Set up a CDN to Serve Static Assets + +To set up your CDN you have to have your application running in production on +the internet at a publically available URL, for example `example.com`. Next +you'll need to sign up for a CDN service from a cloud hosting provider. When you +do this you need to configure the "origin" of the CDN to point back at your +website `example.com`, check your provider for documentation on configuring the +origin server. + +The CDN you provisioned should give you a custom subdomain for your application +such as `mycdnsubdomain.fictional-cdn.com` (note fictional-cdn.com is not a +valid CDN provider at the time of this writing). Now that you have configured +your CDN server, you need to tell browsers to use your CDN to grab assets +instead of your Rails server directly. You can do this by configuring Rails to +set your CDN as the asset host instead of using a relative path. To set your +asset host in Rails, you need to set `config.action_controller.asset_host` in +`config/production.rb`: -Every cache is different, so evaluate how your CDN handles caching and make sure -that it plays nicely with the pipeline. You may find quirks related to your -specific set up, you may not. The defaults NGINX uses, for example, should give -you no problems when used as an HTTP cache. +```ruby +config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com' +``` + +NOTE: You only need to provide the "host", this is the subdomain and root +domain, you do not need to specify a protocol or "scheme" such as `http://` or +`https://`. When a web page is requested, the protocol in the link to your asset +that is generated will match how the webpage is accessed by default. + +You can also set this value through an [environment +variable](http://en.wikipedia.org/wiki/Environment_variable) to make running a +staging copy of your site easier: + +``` +config.action_controller.asset_host = ENV['CDN_HOST'] +``` + + + +Note: You would need to set `CDN_HOST` on your server to `mycdnsubdomain +.fictional-cdn.com` for this to work. + +Once you have configured your server and your CDN when you serve a webpage that +has an asset: + +```erb +<%= asset_path('smile.png') %> +``` + +Instead of returning a path such as `/assets/smile.png` (digests are left out +for readability). The URL generated will have the full path to your CDN. -If you want to serve only some assets from your CDN, you can use custom -`:host` option of `asset_url` helper, which overwrites value set in +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile.png +``` + +If the CDN has a copy of `smile.png` it will serve it to the browser and your +server doesn't even know it was requested. If the CDN does not have a copy it +will try to find it a the "origin" `example.com/assets/smile.png` and then store +it for future use. + +If you want to serve only some assets from your CDN, you can use custom `:host` +option your asset helper, which overwrites value set in `config.action_controller.asset_host`. -```ruby -asset_url 'image.png', :host => 'http://cdn.example.com/' +```erb +<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %> ``` +#### Customize CDN Caching Behavior + +A CDN works by caching content. If the CDN has stale or bad content, then it is +hurting rather than helping your application. The purpose of this section is to +describe general caching behavior of most CDNs, your specific provider may +behave slightly differently. + +##### CDN Request Caching + +While a CDN is described as being good for caching assets, in reality caches the +entire request. This includes the body of the asset as well as any headers. The +most important one being `Cache-Control` which tells the CDN (and web browsers) +how to cache contents. This means that if someone requests an asset that does +not exist `/assets/i-dont-exist.png` and your Rails application returns a 404, +then your CDN will likely cache the 404 page if a valid `Cache-Control` header +is present. + +##### CDN Header Debugging + +One way to check the headers are cached properly in your CDN is by using [curl]( +http://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com). You +can request the headers from both your server and your CDN to verify they are +the same: + +``` +$ curl -I http://www.example/assets/application- +d0e099e021c95eb0de3615fd1d8c4d83.css +HTTP/1.1 200 OK +Server: Cowboy +Date: Sun, 24 Aug 2014 20:27:50 GMT +Connection: keep-alive +Last-Modified: Thu, 08 May 2014 01:24:14 GMT +Content-Type: text/css +Cache-Control: public, max-age=2592000 +Content-Length: 126560 +Via: 1.1 vegur +``` + +Versus the CDN copy. + +``` +$ curl -I http://mycdnsubdomain.fictional-cdn.com/application- +d0e099e021c95eb0de3615fd1d8c4d83.css +HTTP/1.1 200 OK Server: Cowboy Last- +Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css +Cache-Control: +public, max-age=2592000 +Via: 1.1 vegur +Content-Length: 126560 +Accept-Ranges: +bytes +Date: Sun, 24 Aug 2014 20:28:45 GMT +Via: 1.1 varnish +Age: 885814 +Connection: keep-alive +X-Served-By: cache-dfw1828-DFW +X-Cache: HIT +X-Cache-Hits: +68 +X-Timer: S1408912125.211638212,VS0,VE0 +``` + +Check your CDN documentation for any additional information they may provide +such as `X-Cache` or for any additional headers they may add. + +##### CDNs and the Cache-Control Header + +The [cache control +header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C +specification that describes how a request can be cached. When no CDN is used, a +browser will use this information to cache contents. This is very helpful for +assets that are not modified so that a browser does not need to re-download a +website's CSS or javascript on every request. Generally we want our Rails server +to tell our CDN (and browser) that the asset is "public", that means any cache +can store the request. Also we commonly want to set `max-age` which is how long +the cache will store the object before invalidating the cache. The `max-age` +value is set to seconds with a maximum possible value of `31536000` which is one +year. You can do this in your rails application by setting + +``` +config.static_cache_control = "public, max-age=31536000" +``` + +Now when your application serves an asset in production, the CDN will store the +asset for up to a year. Since most CDNs also cache headers of the request, this +`Cache-Control` will be passed along to all future browsers seeking this asset, +the browser then knows that it can store this asset for a very long time before +needing to re-request it. + +##### CDNs and URL based Cache Invalidation + +Most CDNs will cache contents of an asset based on the complete URL. This means +that a request to + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png +``` + +Will be a completely different cache from + +``` +http://mycdnsubdomain.fictional-cdn.com/assets/smile.png +``` + +If you want to set far future `max-age` in your `Cache-Control` (and you do), +then make sure when you change your assets that your cache is invalidated. For +example when changing the smiley face in an image from yellow to blue, you want +all visitors of your site to get the new blue face. When using a CDN with the +Rails asset pipeline `config.assets.digest` is set to true by default so that +each asset will have a different file name when it is changed. This way you +don't have to ever manually invalidate any items in your cache. By using a +different unique asset name instead, your users get the latest asset. + Customizing the Pipeline ------------------------ @@ -1165,8 +1348,8 @@ config.assets.digest = true Rails 4 no longer sets default config values for Sprockets in `test.rb`, so `test.rb` now requires Sprockets configuration. The old defaults in the test -environment are: `config.assets.compile = true`, `config.assets.compress = -false`, `config.assets.debug = false` and `config.assets.digest = false`. +environment are: `config.assets.compile = true`, `config.assets.compress = false`, +`config.assets.debug = false` and `config.assets.digest = false`. The following should also be added to `Gemfile`: diff --git a/source/association_basics.md b/source/association_basics.md index c9e0fcd..5c05f0c 100644 --- a/source/association_basics.md +++ b/source/association_basics.md @@ -101,13 +101,13 @@ class CreateOrders < ActiveRecord::Migration def change create_table :customers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :orders do |t| t.belongs_to :customer, index: true t.datetime :order_date - t.timestamps + t.timestamps null: false end end end @@ -132,13 +132,13 @@ class CreateSuppliers < ActiveRecord::Migration def change create_table :suppliers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number - t.timestamps + t.timestamps null: false end end end @@ -165,13 +165,13 @@ class CreateCustomers < ActiveRecord::Migration def change create_table :customers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :orders do |t| t.belongs_to :customer, index:true t.datetime :order_date - t.timestamps + t.timestamps null: false end end end @@ -207,19 +207,19 @@ class CreateAppointments < ActiveRecord::Migration def change create_table :physicians do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :patients do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :appointments do |t| t.belongs_to :physician, index: true t.belongs_to :patient, index: true t.datetime :appointment_date - t.timestamps + t.timestamps null: false end end end @@ -291,19 +291,19 @@ class CreateAccountHistories < ActiveRecord::Migration def change create_table :suppliers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number - t.timestamps + t.timestamps null: false end create_table :account_histories do |t| t.belongs_to :account, index: true t.integer :credit_rating - t.timestamps + t.timestamps null: false end end end @@ -332,12 +332,12 @@ class CreateAssembliesAndParts < ActiveRecord::Migration def change create_table :assemblies do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :parts do |t| t.string :part_number - t.timestamps + t.timestamps null: false end create_table :assemblies_parts, id: false do |t| @@ -371,13 +371,13 @@ class CreateSuppliers < ActiveRecord::Migration def change create_table :suppliers do |t| t.string :name - t.timestamps + t.timestamps null: false end create_table :accounts do |t| t.integer :supplier_id t.string :account_number - t.timestamps + t.timestamps null: false end add_index :accounts, :supplier_id @@ -455,7 +455,7 @@ class CreatePictures < ActiveRecord::Migration t.string :name t.integer :imageable_id t.string :imageable_type - t.timestamps + t.timestamps null: false end add_index :pictures, :imageable_id @@ -471,7 +471,7 @@ class CreatePictures < ActiveRecord::Migration create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true, index: true - t.timestamps + t.timestamps null: false end end end @@ -501,7 +501,7 @@ class CreateEmployees < ActiveRecord::Migration def change create_table :employees do |t| t.references :manager, index: true - t.timestamps + t.timestamps null: false end end end @@ -756,7 +756,7 @@ class Order < ActiveRecord::Base end ``` -Each instance of the order model will have these methods: +Each instance of the `Order` model will have these methods: ```ruby customer @@ -879,10 +879,12 @@ class Order < ActiveRecord::Base belongs_to :customer, counter_cache: :count_of_orders end class Customer < ActiveRecord::Base - has_many :orders + has_many :orders, counter_cache: :count_of_orders end ``` +NOTE: You only need to specify the :counter_cache option on the "has_many side" of the association when using a custom name for the counter cache. + Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`. ##### `:dependent` @@ -1342,16 +1344,16 @@ class Customer < ActiveRecord::Base end ``` -Each instance of the customer model will have these methods: +Each instance of the `Customer` model will have these methods: ```ruby orders(force_reload = false) orders<<(object, ...) orders.delete(object, ...) orders.destroy(object, ...) -orders=objects +orders=(objects) order_ids -order_ids=ids +order_ids=(ids) orders.clear orders.empty? orders.size @@ -1495,6 +1497,7 @@ The `has_many` association supports these options: * `:as` * `:autosave` * `:class_name` +* `:counter_cache` * `:dependent` * `:foreign_key` * `:inverse_of` @@ -1522,6 +1525,9 @@ class Customer < ActiveRecord::Base end ``` +##### `:counter_cache` +This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to). + ##### `:dependent` Controls what happens to the associated objects when their owner is destroyed: @@ -1532,8 +1538,6 @@ Controls what happens to the associated objects when their owner is destroyed: * `:restrict_with_exception` causes an exception to be raised if there are any associated records * `:restrict_with_error` causes an error to be added to the owner if there are any associated objects -NOTE: This option is ignored when you use the `:through` option on the association. - ##### `:foreign_key` By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: @@ -1831,16 +1835,16 @@ class Part < ActiveRecord::Base end ``` -Each instance of the part model will have these methods: +Each instance of the `Part` model will have these methods: ```ruby assemblies(force_reload = false) assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) -assemblies=objects +assemblies=(objects) assembly_ids -assembly_ids=ids +assembly_ids=(ids) assemblies.clear assemblies.empty? assemblies.size diff --git a/source/autoloading_and_reloading_constants.md b/source/autoloading_and_reloading_constants.md new file mode 100644 index 0000000..c392103 --- /dev/null +++ b/source/autoloading_and_reloading_constants.md @@ -0,0 +1,1297 @@ +Autoloading and Reloading Constants +=================================== + +This guide documents how constant autoloading and reloading works. + +After reading this guide, you will know: + +* Key aspects of Ruby constants +* What is `autoload_paths` +* How constant autoloading works +* What is `require_dependency` +* How constant reloading works +* Solutions to common autoloading gotchas + +-------------------------------------------------------------------------------- + + +Introduction +------------ + +Ruby on Rails allows applications to be written as if their code was preloaded. + +In a normal Ruby program classes need to load their dependencies: + +```ruby +require 'application_controller' +require 'post' + +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +Our Rubyist instinct quickly sees some redundancy in there: If classes were +defined in files matching their name, couldn't their loading be automated +somehow? We could save scanning the file for dependencies, which is brittle. + +Moreover, `Kernel#require` loads files once, but development is much more smooth +if code gets refreshed when it changes without restarting the server. It would +be nice to be able to use `Kernel#load` in development, and `Kernel#require` in +production. + +Indeed, those features are provided by Ruby on Rails, where we just write + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +This guide documents how that works. + + +Constants Refresher +------------------- + +While constants are trivial in most programming languages, they are a rich +topic in Ruby. + +It is beyond the scope of this guide to document Ruby constants, but we are +nevertheless going to highlight a few key topics. Truly grasping the following +sections is instrumental to understanding constant autoloading and reloading. + +### Nesting + +Class and module definitions can be nested to create namespaces: + +```ruby +module XML + class SAXParser + # (1) + end +end +``` + +The *nesting* at any given place is the collection of enclosing nested class and +module objects outwards. For example, in the previous example, the nesting at +(1) is + +```ruby +[XML::SAXParser, XML] +``` + +It is important to understand that the nesting is composed of class and module +*objects*, it has nothing to do with the constants used to access them, and is +also unrelated to their names. + +For instance, while this definition is similar to the previous one: + +```ruby +class XML::SAXParser + # (2) +end +``` + +the nesting in (2) is different: + +```ruby +[XML::SAXParser] +``` + +`XML` does not belong to it. + +We can see in this example that the name of a class or module that belongs to a +certain nesting does not necessarily correlate with the namespaces at the spot. + +Even more, they are totally independent, take for instance + +```ruby +module X::Y + module A::B + # (3) + end +end +``` + +The nesting in (3) consists of two module objects: + +```ruby +[A::B, X::Y] +``` + +So, it not only doesn't end in `A`, which does not even belong to the nesting, +but it also contains `X::Y`, which is independent from `A::B`. + +The nesting is an internal stack maintained by the interpreter, and it gets +modified according to these rules: + +* The class object following a `class` keyword gets pushed when its body is +executed, and popped after it. + +* The module object following a `module` keyword gets pushed when its body is +executed, and popped after it. + +* A singleton class opened with `class << object` gets pushed, and popped later. + +* When any of the `*_eval` family of methods is called using a string argument, +the singleton class of the receiver is pushed to the nesting of the eval'ed +code. + +* The nesting at the top-level of code interpreted by `Kernel#load` is empty +unless the `load` call receives a true value as second argument, in which case +a newly created anonymous module is pushed by Ruby. + +It is interesting to observe that blocks do not modify the stack. In particular +the blocks that may be passed to `Class.new` and `Module.new` do not get the +class or module being defined pushed to their nesting. That's one of the +differences between defining classes and modules in one way or another. + +The nesting at any given place can be inspected with `Module.nesting`. + +### Class and Module Definitions are Constant Assignments + +Let's suppose the following snippet creates a class (rather than reopening it): + +```ruby +class C +end +``` + +Ruby creates a constant `C` in `Object` and stores in that constant a class +object. The name of the class instance is "C", a string, named after the +constant. + +That is, + +```ruby +class Project < ActiveRecord::Base +end +``` + +performs a constant assignment equivalent to + +```ruby +Project = Class.new(ActiveRecord::Base) +``` + +including setting the name of the class as a side-effect: + +```ruby +Project.name # => "Project" +``` + +Constant assignment has a special rule to make that happen: if the object +being assigned is an anonymous class or module, Ruby sets the object's name to +the name of the constant. + +INFO. From then on, what happens to the constant and the instance does not +matter. For example, the constant could be deleted, the class object could be +assigned to a different constant, be stored in no constant anymore, etc. Once +the name is set, it doesn't change. + +Similarly, module creation using the `module` keyword as in + +```ruby +module Admin +end +``` + +performs a constant assignment equivalent to + +```ruby +Admin = Module.new +``` + +including setting the name as a side-effect: + +```ruby +Admin.name # => "Admin" +``` + +WARNING. The execution context of a block passed to `Class.new` or `Module.new` +is not entirely equivalent to the one of the body of the definitions using the +`class` and `module` keywords. But both idioms result in the same constant +assignment. + +Thus, when one informally says "the `String` class", that really means: the +class object stored in the constant called "String" in the class object stored +in the `Object` constant. `String` is otherwise an ordinary Ruby constant and +everything related to constants such as resolution algorithms applies to it. + +Likewise, in the controller + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If +all is good, the constant evaluates to an object that responds to `all`. + +That is why we talk about *constant* autoloading, Rails has the ability to +load constants on the fly. + +### Constants are Stored in Modules + +Constants belong to modules in a very literal sense. Classes and modules have +a constant table; think of it as a hash table. + +Let's analyze an example to really understand what that means. While common +abuses of language like "the `String` class" are convenient, the exposition is +going to be precise here for didactic purposes. + +Let's consider the following module definition: + +```ruby +module Colors + RED = '0xff0000' +end +``` + +First, when the `module` keyword is processed the interpreter creates a new +entry in the constant table of the class object stored in the `Object` constant. +Said entry associates the name "Colors" to a newly created module object. +Furthermore, the interpreter sets the name of the new module object to be the +string "Colors". + +Later, when the body of the module definition is interpreted, a new entry is +created in the constant table of the module object stored in the `Colors` +constant. That entry maps the name "RED" to the string "0xff0000". + +In particular, `Colors::RED` is totally unrelated to any other `RED` constant +that may live in any other class or module object. If there were any, they +would have separate entries in their respective constant tables. + +Pay special attention in the previous paragraphs to the distinction between +class and module objects, constant names, and value objects associated to them +in constant tables. + +### Resolution Algorithms + +#### Resolution Algorithm for Relative Constants + +At any given place in the code, let's define *cref* to be the first element of +the nesting if it is not empty, or `Object` otherwise. + +Without getting too much into the details, the resolution algorithm for relative +constant references goes like this: + +1. If the nesting is not empty the constant is looked up in its elements and in +order. The ancestors of those elements are ignored. + +2. If not found, then the algorithm walks up the ancestor chain of the cref. + +3. If not found, `const_missing` is invoked on the cref. The default +implementation of `const_missing` raises `NameError`, but it can be overridden. + +Rails autoloading **does not emulate this algorithm**, but its starting point is +the name of the constant to be autoloaded, and the cref. See more in [Relative +References](#autoloading-algorithms-relative-references). + +#### Resolution Algorithm for Qualified Constants + +Qualified constants look like this: + +```ruby +Billing::Invoice +``` + +`Billing::Invoice` is composed of two constants: `Billing` is relative and is +resolved using the algorithm of the previous section. + +INFO. Leading colons would make the first segment absolute rather than +relative: `::Billing::Invoice`. That would force `Billing` to be looked up +only as a top-level constant. + +`Invoice` on the other hand is qualified by `Billing` and we are going to see +its resolution next. Let's call *parent* to that qualifying class or module +object, that is, `Billing` in the example above. The algorithm for qualified +constants goes like this: + +1. The constant is looked up in the parent and its ancestors. + +2. If the lookup fails, `const_missing` is invoked in the parent. The default +implementation of `const_missing` raises `NameError`, but it can be overridden. + +As you see, this algorithm is simpler than the one for relative constants. In +particular, the nesting plays no role here, and modules are not special-cased, +if neither they nor their ancestors have the constants, `Object` is **not** +checked. + +Rails autoloading **does not emulate this algorithm**, but its starting point is +the name of the constant to be autoloaded, and the parent. See more in +[Qualified References](#qualified-references). + + +Vocabulary +---------- + +### Parent Namespaces + +Given a string with a constant path we define its *parent namespace* to be the +string that results from removing its rightmost segment. + +For example, the parent namespace of the string "A::B::C" is the string "A::B", +the parent namespace of "A::B" is "A", and the parent namespace of "A" is "". + +The interpretation of a parent namespace when thinking about classes and modules +is tricky though. Let's consider a module M named "A::B": + +* The parent namespace, "A", may not reflect nesting at a given spot. + +* The constant `A` may no longer exist, some code could have removed it from +`Object`. + +* If `A` exists, the class or module that was originally in `A` may not be there +anymore. For example, if after a constant removal there was another constant +assignment there would generally be a different object in there. + +* In such case, it could even happen that the reassigned `A` held a new class or +module called also "A"! + +* In the previous scenarios M would no longer be reachable through `A::B` but +the module object itself could still be alive somewhere and its name would +still be "A::B". + +The idea of a parent namespace is at the core of the autoloading algorithms +and helps explain and understand their motivation intuitively, but as you see +that metaphor leaks easily. Given an edge case to reason about, take always into +account that by "parent namespace" the guide means exactly that specific string +derivation. + +### Loading Mechanism + +Rails autoloads files with `Kernel#load` when `config.cache_classes` is false, +the default in development mode, and with `Kernel#require` otherwise, the +default in production mode. + +`Kernel#load` allows Rails to execute files more than once if [constant +reloading](#constant-reloading) is enabled. + +This guide uses the word "load" freely to mean a given file is interpreted, but +the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that +flag. + + +Autoloading Availability +------------------------ + +Rails is always able to autoload provided its environment is in place. For +example the `runner` command autoloads: + +``` +$ bin/rails runner 'p User.column_names' +["id", "email", "created_at", "updated_at"] +``` + +The console autoloads, the test suite autoloads, and of course the application +autoloads. + +By default, Rails eager loads the application files when it boots in production +mode, so most of the autoloading going on in development does not happen. But +autoloading may still be triggered during eager loading. + +For example, given + +```ruby +class BeachHouse < House +end +``` + +if `House` is still unknown when `app/models/beach_house.rb` is being eager +loaded, Rails autoloads it. + + +autoload_paths +-------------- + +As you probably know, when `require` gets a relative file name: + +```ruby +require 'erb' +``` + +Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby +iterates over all its directories and for each one of them checks whether they +have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds +any of them, the interpreter loads it and ends the search. Otherwise, it tries +again in the next directory of the list. If the list gets exhausted, `LoadError` +is raised. + +We are going to cover how constant autoloading works in more detail later, but +the idea is that when a constant like `Post` is hit and missing, if there's a +`post.rb` file for example in `app/models` Rails is going to find it, evaluate +it, and have `Post` defined as a side-effect. + +Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which +to look up `post.rb`. That collection is called `autoload_paths` and by +default it contains: + +* All subdirectories of `app` in the application and engines. For example, + `app/controllers`. They do not need to be the default ones, any custom + directories like `app/workers` belong automatically to `autoload_paths`. + +* Any existing second level directories called `app/*/concerns` in the + application and engines. + +* The directory `test/mailers/previews`. + +Also, this collection is configurable via `config.autoload_paths`. For example, +`lib` was in the list years ago, but no longer is. An application can opt-in +by adding this to `config/application.rb`: + +```ruby +config.autoload_paths += "#{Rails.root}/lib" +``` + +The value of `autoload_paths` can be inspected. In a just generated application +it is (edited): + +``` +$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' +.../app/assets +.../app/controllers +.../app/helpers +.../app/mailers +.../app/models +.../app/controllers/concerns +.../app/models/concerns +.../test/mailers/previews +``` + +INFO. `autoload_paths` is computed and cached during the initialization process. +The application needs to be restarted to reflect any changes in the directory +structure. + + +Autoloading Algorithms +---------------------- + +### Relative References + +A relative constant reference may appear in several places, for example, in + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +all three constant references are relative. + +#### Constants after the `class` and `module` Keywords + +Ruby performs a lookup for the constant that follows a `class` or `module` +keyword because it needs to know if the class or module is going to be created +or reopened. + +If the constant is not defined at that point it is not considered to be a +missing constant, autoloading is **not** triggered. + +So, in the previous example, if `PostsController` is not defined when the file +is interpreted Rails autoloading is not going to be triggered, Ruby will just +define the controller. + +#### Top-Level Constants + +On the contrary, if `ApplicationController` is unknown, the constant is +considered missing and an autoload is going to be attempted by Rails. + +In order to load `ApplicationController`, Rails iterates over `autoload_paths`. +First checks if `app/assets/application_controller.rb` exists. If it does not, +which is normally the case, it continues and finds +`app/controllers/application_controller.rb`. + +If the file defines the constant `ApplicationController` all is fine, otherwise +`LoadError` is raised: + +``` +unable to autoload constant ApplicationController, expected + to define it (LoadError) +``` + +INFO. Rails does not require the value of autoloaded constants to be a class or +module object. For example, if the file `app/models/max_clients.rb` defines +`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine. + +#### Namespaces + +Autoloading `ApplicationController` looks directly under the directories of +`autoload_paths` because the nesting in that spot is empty. The situation of +`Post` is different, the nesting in that line is `[PostsController]` and support +for namespaces comes into play. + +The basic idea is that given + +```ruby +module Admin + class BaseController < ApplicationController + @@all_roles = Role.all + end +end +``` + +to autoload `Role` we are going to check if it is defined in the current or +parent namespaces, one at a time. So, conceptually we want to try to autoload +any of + +``` +Admin::BaseController::Role +Admin::Role +Role +``` + +in that order. That's the idea. To do so, Rails looks in `autoload_paths` +respectively for file names like these: + +``` +admin/base_controller/role.rb +admin/role.rb +role.rb +``` + +modulus some additional directory lookups we are going to cover soon. + +INFO. `'Constant::Name'.underscore` gives the relative path without extension of +the file name where `Constant::Name` is expected to be defined. + +Let's see how Rails autoloads the `Post` constant in the `PostsController` +above assuming the application has a `Post` model defined in +`app/models/post.rb`. + +First it checks for `posts_controller/post.rb` in `autoload_paths`: + +``` +app/assets/posts_controller/post.rb +app/controllers/posts_controller/post.rb +app/helpers/posts_controller/post.rb +... +test/mailers/previews/posts_controller/post.rb +``` + +Since the lookup is exhausted without success, a similar search for a directory +is performed, we are going to see why in the [next section](#automatic-modules): + +``` +app/assets/posts_controller/post +app/controllers/posts_controller/post +app/helpers/posts_controller/post +... +test/mailers/previews/posts_controller/post +``` + +If all those attempts fail, then Rails starts the lookup again in the parent +namespace. In this case only the top-level remains: + +``` +app/assets/post.rb +app/controllers/post.rb +app/helpers/post.rb +app/mailers/post.rb +app/models/post.rb +``` + +A matching file is found in `app/models/post.rb`. The lookup stops there and the +file is loaded. If the file actually defines `Post` all is fine, otherwise +`LoadError` is raised. + +### Qualified References + +When a qualified constant is missing Rails does not look for it in the parent +namespaces. But there is a caveat: When a constant is missing, Rails is +unable to tell if the trigger was a relative reference or a qualified one. + +For example, consider + +```ruby +module Admin + User +end +``` + +and + +```ruby +Admin::User +``` + +If `User` is missing, in either case all Rails knows is that a constant called +"User" was missing in a module called "Admin". + +If there is a top-level `User` Ruby would resolve it in the former example, but +wouldn't in the latter. In general, Rails does not emulate the Ruby constant +resolution algorithms, but in this case it tries using the following heuristic: + +> If none of the parent namespaces of the class or module has the missing +> constant then Rails assumes the reference is relative. Otherwise qualified. + +For example, if this code triggers autoloading + +```ruby +Admin::User +``` + +and the `User` constant is already present in `Object`, it is not possible that +the situation is + +```ruby +module Admin + User +end +``` + +because otherwise Ruby would have resolved `User` and no autoloading would have +been triggered in the first place. Thus, Rails assumes a qualified reference and +considers the file `admin/user.rb` and directory `admin/user` to be the only +valid options. + +In practice, this works quite well as long as the nesting matches all parent +namespaces respectively and the constants that make the rule apply are known at +that time. + +However, autoloading happens on demand. If by chance the top-level `User` was +not yet loaded, then Rails assumes a relative reference by contract. + +Naming conflicts of this kind are rare in practice, but if one occurs, +`require_dependency` provides a solution by ensuring that the constant needed +to trigger the heuristic is defined in the conflicting place. + +### Automatic Modules + +When a module acts as a namespace, Rails does not require the application to +defines a file for it, a directory matching the namespace is enough. + +Suppose an application has a back office whose controllers are stored in +`app/controllers/admin`. If the `Admin` module is not yet loaded when +`Admin::UsersController` is hit, Rails needs first to autoload the constant +`Admin`. + +If `autoload_paths` has a file called `admin.rb` Rails is going to load that +one, but if there's no such file and a directory called `admin` is found, Rails +creates an empty module and assigns it to the `Admin` constant on the fly. + +### Generic Procedure + +Relative references are reported to be missing in the cref where they were hit, +and qualified references are reported to be missing in their parent. (See +[Resolution Algorithm for Relative +Constants](#resolution-algorithm-for-relative-constants) at the beginning of +this guide for the definition of *cref*, and [Resolution Algorithm for Qualified +Constants](#resolution-algorithm-for-qualified-constants) for the definition of +*parent*.) + +The procedure to autoload constant `C` in an arbitrary situation is as follows: + +``` +if the class or module in which C is missing is Object + let ns = '' +else + let M = the class or module in which C is missing + + if M is anonymous + let ns = '' + else + let ns = M.name + end +end + +loop do + # Look for a regular file. + for dir in autoload_paths + if the file "#{dir}/#{ns.underscore}/c.rb" exists + load/require "#{dir}/#{ns.underscore}/c.rb" + + if C is now defined + return + else + raise LoadError + end + end + end + + # Look for an automatic module. + for dir in autoload_paths + if the directory "#{dir}/#{ns.underscore}/c" exists + if ns is an empty string + let C = Module.new in Object and return + else + let C = Module.new in ns.constantize and return + end + end + end + + if ns is empty + # We reached the top-level without finding the constant. + raise NameError + else + if C exists in any of the parent namespaces + # Qualified constants heuristic. + raise NameError + else + # Try again in the parent namespace. + let ns = the parent namespace of ns and retry + end + end +end +``` + + +require_dependency +------------------ + +Constant autoloading is triggered on demand and therefore code that uses a +certain constant may have it already defined or may trigger an autoload. That +depends on the execution path and it may vary between runs. + +There are times, however, in which you want to make sure a certain constant is +known when the execution reaches some code. `require_dependency` provides a way +to load a file using the current [loading mechanism](#loading-mechanism), and +keeping track of constants defined in that file as if they were autoloaded to +have them reloaded as needed. + +`require_dependency` is rarely needed, but see a couple of use-cases in +[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't +Triggered](#when-constants-aren-t-missed). + +WARNING. Unlike autoloading, `require_dependency` does not expect the file to +define any particular constant. Exploiting this behavior would be a bad practice +though, file and constant paths should match. + + +Constant Reloading +------------------ + +When `config.cache_classes` is false Rails is able to reload autoloaded +constants. + +For example, in you're in a console session and edit some file behind the +scenes, the code can be reloaded with the `reload!` command: + +``` +> reload! +``` + +When the application runs, code is reloaded when something relevant to this +logic changes. In order to do that, Rails monitors a number of things: + +* `config/routes.rb`. + +* Locales. + +* Ruby files under `autoload_paths`. + +* `db/schema.rb` and `db/structure.sql`. + +If anything in there changes, there is a middleware that detects it and reloads +the code. + +Autoloading keeps track of autoloaded constants. Reloading is implemented by +removing them all from their respective classes and modules using +`Module#remove_const`. That way, when the code goes on, those constants are +going to be unknown again, and files reloaded on demand. + +INFO. This is an all-or-nothing operation, Rails does not attempt to reload only +what changed since dependencies between classes makes that really tricky. +Instead, everything is wiped. + + +Module#autoload isn't Involved +------------------------------ + +`Module#autoload` provides a lazy way to load constants that is fully integrated +with the Ruby constant lookup algorithms, dynamic constant API, etc. It is quite +transparent. + +Rails internals make extensive use of it to defer as much work as possible from +the boot process. But constant autoloading in Rails is **not** implemented with +`Module#autoload`. + +One possible implementation based on `Module#autoload` would be to walk the +application tree and issue `autoload` calls that map existing file names to +their conventional constant name. + +There are a number of reasons that prevent Rails from using that implementation. + +For example, `Module#autoload` is only capable of loading files using `require`, +so reloading would not be possible. Not only that, it uses an internal `require` +which is not `Kernel#require`. + +Then, it provides no way to remove declarations in case a file is deleted. If a +constant gets removed with `Module#remove_const` its `autoload` is not triggered +again. Also, it doesn't support qualified names, so files with namespaces should +be interpreted during the walk tree to install their own `autoload` calls, but +those files could have constant references not yet configured. + +An implementation based on `Module#autoload` would be awesome but, as you see, +at least as of today it is not possible. Constant autoloading in Rails is +implemented with `Module#const_missing`, and that's why it has its own contract, +documented in this guide. + + +Common Gotchas +-------------- + +### Nesting and Qualified Constants + +Let's consider + +```ruby +module Admin + class UsersController < ApplicationController + def index + @users = User.all + end + end +end +``` + +and + +```ruby +class Admin::UsersController < ApplicationController + def index + @users = User.all + end +end +``` + +To resolve `User` Ruby checks `Admin` in the former case, but it does not in +the latter because it does not belong to the nesting. (See [Nesting](#nesting) +and [Resolution Algorithms](#resolution-algorithms).) + +Unfortunately Rails autoloading does not know the nesting in the spot where the +constant was missing and so it is not able to act as Ruby would. In particular, +`Admin::User` will get autoloaded in either case. + +Albeit qualified constants with `class` and `module` keywords may technically +work with autoloading in some cases, it is preferable to use relative constants +instead: + +```ruby +module Admin + class UsersController < ApplicationController + def index + @users = User.all + end + end +end +``` + +### Autoloading and STI + +Single Table Inheritance (STI) is a feature of Active Record that enables +storing a hierarchy of models in one single table. The API of such models is +aware of the hierarchy and encapsulates some common needs. For example, given +these classes: + +```ruby +# app/models/polygon.rb +class Polygon < ActiveRecord::Base +end + +# app/models/triangle.rb +class Triangle < Polygon +end + +# app/models/rectangle.rb +class Rectangle < Polygon +end +``` + +`Triangle.create` creates a row that represents a triangle, and +`Rectangle.create` creates a row that represents a rectangle. If `id` is the +ID of an existing record, `Polygon.find(id)` returns an object of the correct +type. + +Methods that operate on collections are also aware of the hierarchy. For +example, `Polygon.all` returns all the records of the table, because all +rectangles and triangles are polygons. Active Record takes care of returning +instances of their corresponding class in the result set. + +Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle +and `Rectangle` has not yet been loaded, Active Record autoloads it and the +record is correctly instantiated. + +All good, but if instead of performing queries based on the root class we need +to work on some subclass, things get interesting. + +While working with `Polygon` you do not need to be aware of all its descendants, +because anything in the table is by definition a polygon, but when working with +subclasses Active Record needs to be able to enumerate the types it is looking +for. Let’s see an example. + +`Rectangle.all` only loads rectangles by adding a type constraint to the query: + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle") +``` + +Let’s introduce now a subclass of `Rectangle`: + +```ruby +# app/models/square.rb +class Square < Rectangle +end +``` + +`Rectangle.all` should now return rectangles **and** squares: + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle", "Square") +``` + +But there’s a caveat here: How does Active Record know that the class `Square` +exists at all? + +Even if the file `app/models/square.rb` exists and defines the `Square` class, +if no code yet used that class, `Rectangle.all` issues the query + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle") +``` + +That is not a bug, the query includes all *known* descendants of `Rectangle`. + +A way to ensure this works correctly regardless of the order of execution is to +load the leaves of the tree by hand at the bottom of the file that defines the +root class: + +```ruby +# app/models/polygon.rb +class Polygon < ActiveRecord::Base +end +require_dependency ‘square’ +``` + +Only the leaves that are **at least grandchildren** need to be loaded this +way. Direct subclasses do not need to be preloaded. If the hierarchy is +deeper, intermediate classes will be autoloaded recursively from the bottom +because their constant will appear in the class definitions as superclass. + +### Autoloading and `require` + +Files defining constants to be autoloaded should never be `require`d: + +```ruby +require 'user' # DO NOT DO THIS + +class UsersController < ApplicationController + ... +end +``` + +There are two possible gotchas here in development mode: + +1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb` +runs again because `load` does not update `$LOADED_FEATURES`. + +2. If the `require` runs first Rails does not mark `User` as an autoloaded +constant and changes to `app/models/user.rb` aren't reloaded. + +Just follow the flow and use constant autoloading always, never mix +autoloading and `require`. As a last resort, if some file absolutely needs to +load a certain file use `require_dependency` to play nice with constant +autoloading. This option is rarely needed in practice, though. + +Of course, using `require` in autoloaded files to load ordinary 3rd party +libraries is fine, and Rails is able to distinguish their constants, they are +not marked as autoloaded. + +### Autoloading and Initializers + +Consider this assignment in `config/initializers/set_auth_service.rb`: + +```ruby +AUTH_SERVICE = if Rails.env.production? + RealAuthService +else + MockedAuthService +end +``` + +The purpose of this setup would be that the application uses the class that +corresponds to the environment via `AUTH_SERVICE`. In development mode +`MockedAuthService` gets autoloaded when the initializer runs. Let’s suppose +we do some requests, change its implementation, and hit the application again. +To our surprise the changes are not reflected. Why? + +As [we saw earlier](#constant-reloading), Rails removes autoloaded constants, +but `AUTH_SERVICE` stores the original class object. Stale, non-reachable +using the original constant, but perfectly functional. + +The following code summarizes the situation: + +```ruby +class C + def quack + 'quack!' + end +end + +X = C +Object.instance_eval { remove_const(:C) } +X.new.quack # => quack! +X.name # => C +C # => uninitialized constant C (NameError) +``` + +Because of that, it is not a good idea to autoload constants on application +initialization. + +In the case above we could implement a dynamic access point: + +```ruby +# app/models/auth_service.rb +class AuthService + if Rails.env.production? + def self.instance + RealAuthService + end + else + def self.instance + MockedAuthService + end + end +end +``` + +and have the application use `AuthService.instance` instead. `AuthService` +would be loaded on demand and be autoload-friendly. + +### `require_dependency` and Initializers + +As we saw before, `require_dependency` loads files in an autoloading-friendly +way. Normally, though, such a call does not make sense in an initializer. + +One could think about doing some [`require_dependency`](#require-dependency) +calls in an initializer to make sure certain constants are loaded upfront, for +example as an attempt to address the [gotcha with STIs](#autoloading-and-sti). + +Problem is, in development mode [autoloaded constants are wiped](#constant-reloading) +if there is any relevant change in the file system. If that happens then +we are in the very same situation the initializer wanted to avoid! + +Calls to `require_dependency` have to be strategically written in autoloaded +spots. + +### When Constants aren't Missed + +#### Relative References + +Let's consider a flight simulator. The application has a default flight model + +```ruby +# app/models/flight_model.rb +class FlightModel +end +``` + +that can be overridden by each airplane, for instance + +```ruby +# app/models/bell_x1/flight_model.rb +module BellX1 + class FlightModel < FlightModel + end +end + +# app/models/bell_x1/aircraft.rb +module BellX1 + class Aircraft + def initialize + @flight_model = FlightModel.new + end + end +end +``` + +The initializer wants to create a `BellX1::FlightModel` and nesting has +`BellX1`, that looks good. But if the default flight model is loaded and the +one for the Bell-X1 is not, the interpreter is able to resolve the top-level +`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`. + +That code depends on the execution path. + +These kind of ambiguities can often be resolved using qualified constants: + +```ruby +module BellX1 + class Plane + def flight_model + @flight_model ||= BellX1::FlightModel.new + end + end +end +``` + +Also, `require_dependency` is a solution: + +```ruby +require_dependency 'bell_x1/flight_model' + +module BellX1 + class Plane + def flight_model + @flight_model ||= FlightModel.new + end + end +end +``` + +#### Qualified References + +Given + +```ruby +# app/models/hotel.rb +class Hotel +end + +# app/models/image.rb +class Image +end + +# app/models/hotel/image.rb +class Hotel + class Image < Image + end +end +``` + +the expression `Hotel::Image` is ambiguous, depends on the execution path. + +As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks +up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has +been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image` +in `Hotel`, but it does in `Object`: + +``` +$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null +Image # NOT Hotel::Image! +``` + +The code evaluating `Hotel::Image` needs to make sure +`app/models/hotel/image.rb` has been loaded, possibly with +`require_dependency`. + +In these cases the interpreter issues a warning though: + +``` +warning: toplevel constant Image referenced by Hotel::Image +``` + +This surprising constant resolution can be observed with any qualifying class: + +``` +2.1.5 :001 > String::Array +(irb):1: warning: toplevel constant Array referenced by String::Array + => Array +``` + +WARNING. To find this gotcha the qualifying namespace has to be a class, +`Object` is not an ancestor of modules. + +### Autoloading within Singleton Classes + +Let's suppose we have these class definitions: + +```ruby +# app/models/hotel/services.rb +module Hotel + class Services + end +end + +# app/models/hotel/geo_location.rb +module Hotel + class GeoLocation + class << self + Services + end + end +end +``` + +If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb` +is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the +nesting when the singleton class of `Hotel::GeoLocation` is opened. + +But if `Hotel::Services` is not known, Rails is not able to autoload it, the +application raises `NameError`. + +The reason is that autoloading is triggered for the singleton class, which is +anonymous, and as [we saw before](#generic-procedure), Rails only checks the +top-level namespace in that edge case. + +An easy solution to this caveat is to qualify the constant: + +```ruby +module Hotel + class GeoLocation + class << self + Hotel::Services + end + end +end +``` + +### Autoloading in `BasicObject` + +Direct descendants of `BasicObject` do not have `Object` among their ancestors +and cannot resolve top-level constants: + +```ruby +class C < BasicObject + String # NameError: uninitialized constant C::String +end +``` + +When autoloading is involved that plot has a twist. Let's consider: + +```ruby +class C < BasicObject + def user + User # WRONG + end +end +``` + +Since Rails checks the top-level namespace `User` gets autoloaded just fine the +first time the `user` method is invoked. You only get the exception if the +`User` constant is known at that point, in particular in a *second* call to +`user`: + +```ruby +c = C.new +c.user # surprisingly fine, User +c.user # NameError: uninitialized constant C::User +``` + +because it detects a parent namespace already has the constant (see [Qualified +References](#qualified-references).) + +As with pure Ruby, within the body of a direct descendant of `BasicObject` use +always absolute constant paths: + +```ruby +class C < BasicObject + ::String # RIGHT + + def user + ::User # RIGHT + end +end +``` diff --git a/source/caching_with_rails.md b/source/caching_with_rails.md index d0f3a59..cbcd053 100644 --- a/source/caching_with_rails.md +++ b/source/caching_with_rails.md @@ -363,7 +363,7 @@ class ProductsController < ApplicationController end ``` -If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using respond_to or calling render yourself) then you've got an easy helper in fresh_when: +If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using `respond_to` or calling render yourself) then you've got an easy helper in `fresh_when`: ```ruby class ProductsController < ApplicationController diff --git a/source/command_line.md b/source/command_line.md index a074b84..713c91d 100644 --- a/source/command_line.md +++ b/source/command_line.md @@ -24,7 +24,7 @@ There are a few commands that are absolutely critical to your everyday usage of * `rails dbconsole` * `rails new app_name` -All commands can run with ```-h or --help``` to list more information. +All commands can run with `-h` or `--help` to list more information. Let's create a simple Rails application to step through each of these commands in context. @@ -61,7 +61,7 @@ With no further work, `rails server` will run our new shiny Rails app: $ cd commandsapp $ bin/rails server => Booting WEBrick -=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000 +=> Rails 4.2.0 application starting in development on http://localhost:3000 => Call with -d to detach => Ctrl-C to shutdown server [2013-08-07 02:00:01] INFO WEBrick 1.3.1 @@ -79,7 +79,7 @@ The server can be run on a different port using the `-p` option. The default dev $ bin/rails server -e production -p 4000 ``` -The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option. +The `-b` option binds Rails to the specified IP, by default it is localhost. You can run a server as a daemon by passing a `-d` option. ### `rails generate` @@ -130,7 +130,7 @@ Example: `rails generate controller CreditCards open debit credit close` Credit card controller with URLs like /credit_cards/debit. - Controller: app/controllers/credit_card_controller.rb + Controller: app/controllers/credit_cards_controller.rb Test: test/controllers/credit_cards_controller_test.rb Views: app/views/credit_cards/debit.html.erb [...] Helper: app/helpers/credit_cards_helper.rb @@ -368,8 +368,7 @@ Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'mak You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need. -To get the full backtrace for running rake task you can pass the option -```--trace``` to command line, for example ```rake db:create --trace```. +To get the full backtrace for running rake task you can pass the option `--trace` to command line, for example `rake db:create --trace`. ```bash $ bin/rake --tasks @@ -385,7 +384,7 @@ rake middleware # Prints out your Rack middleware stack rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids ``` -INFO: You can also use ```rake -T``` to get the list of tasks. +INFO: You can also use `rake -T` to get the list of tasks. ### `about` @@ -394,16 +393,11 @@ INFO: You can also use ```rake -T``` to get the list of tasks. ```bash $ bin/rake about About your application's environment +Rails version 4.2.0 Ruby version 1.9.3 (x86_64-linux) RubyGems version 1.3.6 Rack version 1.3 -Rails version 4.2.0 JavaScript Runtime Node.js (V8) -Active Record version 4.2.0 -Action Pack version 4.2.0 -Action View version 4.2.0 -Action Mailer version 4.2.0 -Active Support version 4.2.0 Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development @@ -413,10 +407,7 @@ Database schema version 20110805173523 ### `assets` -You can precompile the assets in `app/assets` using `rake assets:precompile`, -and remove older compiled assets using `rake assets:clean`. The `assets:clean` -task allows for rolling deploys that may still be linking to an old asset while -the new assets are being built. +You can precompile the assets in `app/assets` using `rake assets:precompile`, and remove older compiled assets using `rake assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built. If you want to clear `public/assets` completely, you can use `rake assets:clobber`. @@ -503,7 +494,7 @@ Rails comes with a test suite called Minitest. Rails owes its stability to the u ### `tmp` -The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. +The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for sessions), process id files, and cached actions. The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory: diff --git a/source/configuring.md b/source/configuring.md index dbbd0c1..0c730ac 100644 --- a/source/configuring.md +++ b/source/configuring.md @@ -62,7 +62,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`. -* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. Can also be enabled with `threadsafe!`. +* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. * `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`. @@ -86,7 +86,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`). end ``` -* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. This flag is set to false by `config.threadsafe!`. +* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. * `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. @@ -108,7 +108,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. -* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`. +* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments. * `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. @@ -120,7 +120,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`. -* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. NGINX or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. +* `config.serve_static_files` configures Rails to serve static files. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able use page caching and requests for files that exist under the public directory. * `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: @@ -153,7 +153,7 @@ pipeline is enabled. It is set to true by default. * `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-.json` in the `config.assets.prefix` directory within the public folder. -* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb`. +* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb` and `development.rb`. * `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`. @@ -214,7 +214,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value. * `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. -* `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so. +* `Rack::Head` converts HEAD requests to GET requests and serves them as so. Besides these usual middleware, you can add your own by using the `config.middleware.use` method: @@ -225,13 +225,13 @@ config.middleware.use Magical::Unicorns This will put the `Magical::Unicorns` middleware on the end of the stack. You can use `insert_before` if you wish to add a middleware before another. ```ruby -config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_before Rack::Head, Magical::Unicorns ``` There's also `insert_after` which will insert a middleware after another: ```ruby -config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_after Rack::Head, Magical::Unicorns ``` Middlewares can also be completely swapped out and replaced with others: @@ -364,6 +364,30 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation) for more information. It defaults to true. +* `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as: + + ```ruby + config.action_dispatch.rescue_responses = { + 'ActionController::RoutingError' => :not_found, + 'AbstractController::ActionNotFound' => :not_found, + 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, + 'ActionController::NotImplemented' => :not_implemented, + 'ActionController::UnknownFormat' => :not_acceptable, + 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, + 'ActionController::BadRequest' => :bad_request, + 'ActionController::ParameterMissing' => :bad_request, + 'ActiveRecord::RecordNotFound' => :not_found, + 'ActiveRecord::StaleObjectError' => :conflict, + 'ActiveRecord::RecordInvalid' => :unprocessable_entity, + 'ActiveRecord::RecordNotSaved' => :unprocessable_entity + } + ``` + + Any exceptions that are not configured will be mapped to 500 Internal Server Error. + * `ActionDispatch::Callbacks.before` takes a block of code to run before the request. * `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`. @@ -471,6 +495,8 @@ There are a few configuration options available in Active Support: * `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded. +* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:sorted` and `:random`. Currently defaults to `:sorted`. In Rails 5.0, the default will be changed to `:random` instead. + * `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `false`. * `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`. diff --git a/source/contributing_to_ruby_on_rails.md b/source/contributing_to_ruby_on_rails.md index 8bc4b10..17afd07 100644 --- a/source/contributing_to_ruby_on_rails.md +++ b/source/contributing_to_ruby_on_rails.md @@ -24,7 +24,7 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge ### Creating a Bug Report -If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you do not find any issue addressing it you may proceed to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) +If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. @@ -193,7 +193,7 @@ Now get busy and add/edit code. You're on your branch now, so you can write what * Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution. -TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. +TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted (read more about [our rationales behind this decision](https://github.com/rails/rails/pull/13771#issuecomment-32746700)). #### Follow the Coding Conventions @@ -205,7 +205,7 @@ Rails follows a simple set of coding style conventions: * Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`. * Prefer `&&`/`||` over `and`/`or`. * Prefer class << self over self.method for class methods. -* Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`. +* Use `my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`. * Use `a = b` and not `a=b`. * Use assert_not methods instead of refute. * Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks. @@ -559,6 +559,23 @@ $ git push origin my_pull_request -f You should be able to refresh the pull request on GitHub and see that it has been updated. +#### Updating pull request + +Sometimes you will be asked to make some changes to the code you have +already committed. This can include amending existing commits. In this +case Git will not allow you to push the changes as the pushed branch +and local branch do not match. Instead of opening a new pull request, +you can force push to your branch on GitHub as described earlier in +squashing commits section: + +```bash +$ git push origin my_pull_request -f +``` + +This will update the branch and pull request on GitHub with your new code. Do +note that using force push may result in commits being lost on the remote branch; use it with care. + + ### Older Versions of Ruby on Rails If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 4-0-stable branch: diff --git a/source/debugging_rails_applications.md b/source/debugging_rails_applications.md index 88c6210..1a647f8 100644 --- a/source/debugging_rails_applications.md +++ b/source/debugging_rails_applications.md @@ -138,7 +138,7 @@ Rails.logger.level = 0 # at any time This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information. -TIP: The default Rails log level is `info` in production mode and `debug` in development and test mode. +TIP: The default Rails log level is `debug` in all environments. ### Sending Messages diff --git a/source/engines.md b/source/engines.md index 24548a5..a1f2da1 100644 --- a/source/engines.md +++ b/source/engines.md @@ -32,7 +32,7 @@ directory structure, and are both generated using the `rails plugin new` generator. The difference is that an engine is considered a "full plugin" by Rails (as indicated by the `--full` option that's passed to the generator command). We'll actually be using the `--mountable` option here, which includes -all the features of `--full`, and then some. This guide will refer to these +all the features of `--full`, and then some. This guide will refer to these "full plugins" simply as "engines" throughout. An engine **can** be a plugin, and a plugin **can** be an engine. @@ -74,13 +74,13 @@ options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal: ```bash -$ bin/rails plugin new blorgh --mountable +$ rails plugin new blorgh --mountable ``` The full list of options for the plugin generator may be seen by typing: ```bash -$ bin/rails plugin --help +$ rails plugin --help ``` The `--mountable` option tells the generator that you want to create a @@ -1036,31 +1036,42 @@ functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this: ```ruby -get :index +module Blorgh + class FooControllerTest < ActionController::TestCase + def test_index + get :index + ... + end + end +end ``` It may not function correctly. This is because the application doesn't know how to route these requests to the engine unless you explicitly tell it **how**. To -do this, you must also pass the `:use_route` option as a parameter on these -requests: +do this, you must set the `@routes` instance variable to the engine's route set +in your setup code: ```ruby -get :index, use_route: :blorgh +module Blorgh + class FooControllerTest < ActionController::TestCase + setup do + @routes = Engine.routes + end + + def test_index + get :index + ... + end + end +end ``` This tells the application that you still want to perform a `GET` request to the `index` action of this controller, but you want to use the engine's route to get there, rather than the application's one. -Another way to do this is to assign the `@routes` instance variable to `Engine.routes` in your test setup: - -```ruby -setup do - @routes = Engine.routes -end -``` - -This will also ensure url helpers for the engine will work as expected in your tests. +This also ensures that the engine's URL helpers will work as expected in your +tests. Improving engine functionality ------------------------------ @@ -1155,7 +1166,7 @@ end Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`] -(http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html). +(http://api.rubyonrails.org/classes/ActiveSupport/Concern.html). ActiveSupport::Concern manages load order of interlinked dependent modules and classes at run time allowing you to significantly modularize your code. diff --git a/source/form_helpers.md b/source/form_helpers.md index 2703e35..f3f7415 100644 --- a/source/form_helpers.md +++ b/source/form_helpers.md @@ -96,7 +96,15 @@ form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_ ### Helpers for Generating Form Elements -Rails provides a series of helpers for generating form elements such as checkboxes, text fields, and radio buttons. These basic helpers, with names ending in "_tag" (such as `text_field_tag` and `check_box_tag`), generate just a single `` element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form data, and will make its way to the `params` hash in the controller with the value entered by the user for that field. For example, if the form contains `<%= text_field_tag(:query) %>`, then you would be able to get the value of this field in the controller with `params[:query]`. +Rails provides a series of helpers for generating form elements such as +checkboxes, text fields, and radio buttons. These basic helpers, with names +ending in `_tag` (such as `text_field_tag` and `check_box_tag`), generate just a +single `` element. The first parameter to these is always the name of the +input. When the form is submitted, the name will be passed along with the form +data, and will make its way to the `params` hash in the controller with the +value entered by the user for that field. For example, if the form contains `<%= +text_field_tag(:query) %>`, then you would be able to get the value of this +field in the controller with `params[:query]`. When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in [chapter 7 of this guide](#understanding-parameter-naming-conventions). For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html). @@ -506,6 +514,12 @@ As the name implies, this only generates option tags. To generate a working sele <%= collection_select(:person, :city_id, City.all, :id, :name) %> ``` +As with other helpers, if you were to use the `collection_select` helper on a form builder scoped to the `@person` object, the syntax would be: + +```erb +<%= f.collection_select(:city_id, City.all, :id, :name) %> +``` + To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`. NOTE: Pairs passed to `options_for_select` should have the name first and the id second, however with `options_from_collection_for_select` the first argument is the value method and the second the text method. @@ -623,7 +637,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the ### What Gets Uploaded -The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). +The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a `StringIO` or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). ```ruby def upload diff --git a/source/getting_started.md b/source/getting_started.md index 964bb30..e68e07a 100644 --- a/source/getting_started.md +++ b/source/getting_started.md @@ -90,18 +90,18 @@ current version of Ruby installed: TIP: A number of tools exist to help you quickly install Ruby and Ruby on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp). +For more installation methods for most Operating Systems take a look at +[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). ```bash $ ruby -v ruby 2.0.0p353 ``` -If you don't have Ruby installed have a look at -[ruby-lang.org](https://www.ruby-lang.org/en/installation/) for possible ways to -install Ruby on your platform. - -Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows -users and others can find installation instructions at [the SQLite3 website](https://www.sqlite.org). +Many popular UNIX-like OSes ship with an acceptable version of SQLite3. +On Windows, if you installed Rails through Rails Installer, you +already have SQLite installed. Others can find installation instructions +at the [SQLite3 website](https://www.sqlite.org). Verify that it is correctly installed and in your PATH: ```bash @@ -165,7 +165,7 @@ of the files and folders that Rails created by default: |config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| |config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Contains your current database schema, as well as the database migrations.| -|Gemfile
Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://bundler.io).| +|Gemfile
Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](http://bundler.io).| |lib/|Extended modules for your application.| |log/|Application log files.| |public/|The only folder seen by the world as-is. Contains static files and compiled assets.| @@ -195,8 +195,8 @@ TIP: Compiling CoffeeScript and JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence of a runtime you will see an `execjs` error during asset compilation. Usually Mac OS X and Windows come with a JavaScript runtime installed. -Rails adds the `therubyracer` gem to the generated `Gemfile` in a -commented line for new apps and you can uncomment if you need it. +Rails adds the `therubyracer` gem to the generated `Gemfile` in a +commented line for new apps and you can uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby users and is added by default to the `Gemfile` in apps generated under JRuby. You can investigate all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). @@ -259,9 +259,9 @@ invoke helper create app/helpers/welcome_helper.rb invoke assets invoke coffee -create app/assets/javascripts/welcome.js.coffee +create app/assets/javascripts/welcome.coffee invoke scss -create app/assets/stylesheets/welcome.css.scss +create app/assets/stylesheets/welcome.scss ``` Most important of these are of course the controller, located at @@ -300,8 +300,9 @@ Rails.application.routes.draw do # ... ``` -This is your application's _routing file_ which holds entries in a special DSL -(domain-specific language) that tells Rails how to connect incoming requests to +This is your application's _routing file_ which holds entries in a special +[DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language) +that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root` and @@ -338,8 +339,8 @@ You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. Rails provides a `resources` method which can be used to declare a standard REST -resource. Here's what `config/routes.rb` should look like after the -_article resource_ is declared. +resource. You need to add the _article resource_ to the +`config/routes.rb` as follows: ```ruby Rails.application.routes.draw do @@ -422,12 +423,12 @@ If you refresh now, you'll get a new error: This error indicates that Rails cannot find the `new` action inside the `ArticlesController` that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it -your wanted actions during the generation process. +your desired actions during the generation process. To manually define an action inside a controller, all you need to do is to define a new method inside the controller. Open `app/controllers/articles_controller.rb` and inside the `ArticlesController` -class, define a `new` method so that the controller now looks like this: +class, define the `new` method so that your controller now looks like this: ```ruby class ArticlesController < ApplicationController @@ -444,23 +445,23 @@ With the `new` method defined in `ArticlesController`, if you refresh You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view -available, Rails errors out. +available, Rails will raise an exception. In the above image, the bottom line has been truncated. Let's see what the full -thing looks like: +error message looks like: >Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" That's quite a lot of text! Let's quickly go through and understand what each -part of it does. +part of it means. -The first part identifies what template is missing. In this case, it's the +The first part identifies which template is missing. In this case, it's the `articles/new` template. Rails will first look for this template. If not found, then it will attempt to load a template called `application/new`. It looks for one here because the `ArticlesController` inherits from `ApplicationController`. The next part of the message contains a hash. The `:locale` key in this hash -simply indicates what spoken language template should be retrieved. By default, +simply indicates which spoken language template should be retrieved. By default, this is the English - or "en" - template. The next key, `:formats` specifies the format of template to be served in response. The default format is `:html`, and so Rails is looking for an HTML template. The final key, `:handlers`, is telling @@ -473,14 +474,16 @@ Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths. The simplest template that would work in this case would be one located at -`app/views/articles/new.html.erb`. The extension of this file name is key: the -first extension is the _format_ of the template, and the second extension is the -_handler_ that will be used. Rails is attempting to find a template called -`articles/new` within `app/views` for the application. The format for this -template can only be `html` and the handler must be one of `erb`, `builder` or -`coffee`. Because you want to create a new HTML form, you will be using the `ERB` -language. Therefore the file should be called `articles/new.html.erb` and needs -to be located inside the `app/views` directory of the application. +`app/views/articles/new.html.erb`. The extension of this file name is important: +the first extension is the _format_ of the template, and the second extension +is the _handler_ that will be used. Rails is attempting to find a template +called `articles/new` within `app/views` for the application. The format for +this template can only be `html` and the handler must be one of `erb`, +`builder` or `coffee`. Because you want to create a new HTML form, you will be +using the `ERB` language which is designed to embed Ruby in HTML. + +Therefore the file should be called `articles/new.html.erb` and needs to be +located inside the `app/views` directory of the application. Go ahead now and create a new file at `app/views/articles/new.html.erb` and write this content in it: @@ -665,8 +668,8 @@ rake commands to run migrations, and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created. -If you look in the `db/migrate/20140120191729_create_articles.rb` file (remember, -yours will have a slightly different name), here's what you'll find: +If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file +(remember, yours will have a slightly different name), here's what you'll find: ```ruby class CreateArticles < ActiveRecord::Migration @@ -675,7 +678,7 @@ class CreateArticles < ActiveRecord::Migration t.string :title t.text :text - t.timestamps + t.timestamps null: false end end end @@ -736,7 +739,7 @@ database columns. In the first line we do just that (remember that `@article.save` is responsible for saving the model in the database. Finally, we redirect the user to the `show` action, which we'll define later. -TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `\models\article.rb`. Class names in Ruby must begin with a capital letter. +TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `app/models/article.rb`. Class names in Ruby must begin with a capital letter. TIP: As we'll see later, `@article.save` returns a boolean indicating whether the article was saved or not. @@ -833,7 +836,7 @@ class ArticlesController < ApplicationController A couple of things to note. We use `Article.find` to find the article we're interested in, passing in `params[:id]` to get the `:id` parameter from the -request. We also use an instance variable (prefixed by `@`) to hold a +request. We also use an instance variable (prefixed with `@`) to hold a reference to the article object. We do this because Rails will pass all instance variables to the view. @@ -1279,7 +1282,7 @@ And here's how our app looks so far: Our `edit` page looks very similar to the `new` page; in fact, they both share the same code for displaying the form. Let's remove this duplication by using a view partial. By convention, partial files are -prefixed by an underscore. +prefixed with an underscore. TIP: You can read more about partials in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. @@ -1537,7 +1540,7 @@ class CreateComments < ActiveRecord::Migration # this line adds an integer column called `article_id`. t.references :article, index: true - t.timestamps + t.timestamps null: false end end end @@ -1627,7 +1630,7 @@ controller. Again, we'll use the same generator we used before: $ bin/rails generate controller Comments ``` -This creates six files and one empty directory: +This creates five files and one empty directory: | File/Directory | Purpose | | -------------------------------------------- | ---------------------------------------- | @@ -1635,8 +1638,8 @@ This creates six files and one empty directory: | app/views/comments/ | Views of the controller are stored here | | test/controllers/comments_controller_test.rb | The test for the controller | | app/helpers/comments_helper.rb | A view helper file | -| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller | -| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller | +| app/assets/javascripts/comment.coffee | CoffeeScript for the controller | +| app/assets/stylesheets/comment.scss | Cascading style sheet for the controller | Like with any blog, our readers will create their comments directly after reading the article, and once they have added their comment, will be sent back @@ -2049,7 +2052,7 @@ command-line utility: in your web browser to explore the API documentation. TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake -task you need to install the RedCloth gem. Add it to your `Gemfile` and run +task you need to install the RedCloth and Nokogiri gems. Add it to your `Gemfile` and run `bundle install` and you're ready to go. Configuration Gotchas diff --git a/source/i18n.md b/source/i18n.md index 1023598..75b5275 100644 --- a/source/i18n.md +++ b/source/i18n.md @@ -28,7 +28,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Ruby [I18n Wiki](http://ruby-i18n.org/wiki) for more information. +NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, also use various gems available to add additional functionality or features. See the [rails-i18n gem](https://github.com/svenfuchs/rails-i18n) for more information. How I18n in Ruby on Rails Works ------------------------------- @@ -101,7 +101,7 @@ This means, that in the `:en` locale, the key _hello_ will map to the _Hello wor The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations. -NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. +NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. @@ -262,7 +262,7 @@ get '/:locale' => 'dashboard#index' Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.) -NOTE: Have a look at two plugins which simplify working with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master) and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master). +NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator). ### Setting the Locale from the Client Supplied Information @@ -288,7 +288,7 @@ private end ``` -Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). +Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). #### Using GeoIP (or Similar) Database @@ -484,8 +484,6 @@ NOTE: The default locale loading mechanism in Rails does not load locale files i ``` -Do check the [Rails i18n Wiki](http://rails-i18n.org/wiki) for list of tools available for managing translations. - Overview of the I18n API Features --------------------------------- @@ -628,7 +626,7 @@ entry[count == 1 ? 0 : 1] I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero). -If the lookup for the key does not return a Hash suitable for pluralization, an `18n::InvalidPluralizationData` exception is raised. +If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised. ### Setting and Passing a Locale @@ -676,6 +674,22 @@ en:
<%= t('title.html') %>
``` +Interpolation escapes as needed though. For example, given: + +```yaml +en: + welcome_html: "Welcome %{username}!" +``` + +you can safely pass the username as set by the user: + +```erb +<%# This is safe, it is going to be escaped if needed. %> +<%= t('welcome_html', username: @current_user.username %> +``` + +Safe strings on the other hand are interpolated verbatim. + NOTE: Automatic conversion to HTML safe translate text is only available from the `translate` view helper method. ![i18n demo html safe](images/i18n/demo_html_safe.png) @@ -1036,9 +1050,9 @@ If you find anything missing or wrong in this guide, please file a ticket on our Contributing to Rails I18n -------------------------- -I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. +I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in gems and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. -Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) +Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://github.com/guides/pull-requests). @@ -1046,7 +1060,6 @@ If you find your own locale (language) missing from our [example translations da Resources --------- -* [rails-i18n.org](http://rails-i18n.org) - Homepage of the rails-i18n project. You can find lots of useful resources on the [wiki](http://rails-i18n.org/wiki). * [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. * [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. * [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. diff --git a/source/layouts_and_rendering.md b/source/layouts_and_rendering.md index ac254fc..28fa61a 100644 --- a/source/layouts_and_rendering.md +++ b/source/layouts_and_rendering.md @@ -189,7 +189,7 @@ render file: "/u/apps/warehouse_app/current/app/views/products/show" The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. -NOTE: By default, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the `layout: true` option. +NOTE: By default, the file is rendered using the current layout. TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames. @@ -904,7 +904,10 @@ You can also specify multiple videos to play by passing an array of videos to th This will produce: ```erb - + ``` #### Linking to Audio Files with the `audio_tag` diff --git a/source/maintenance_policy.md b/source/maintenance_policy.md index 6f8584b..050a64d 100644 --- a/source/maintenance_policy.md +++ b/source/maintenance_policy.md @@ -39,7 +39,10 @@ Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. -**Currently included series:** `4.1.Z`, `4.0.Z`. +In special situations, where someone from the Core Team agrees to support more series, +they are included in the list of supported series. + +**Currently included series:** `4.2.Z`, `4.1.Z` (Supported by Rafael França). Security Issues --------------- @@ -54,7 +57,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that security releases are easy to upgrade to if you're running the latest version of Rails. -**Currently included series:** `4.1.Z`, `4.0.Z`. +**Currently included series:** `4.2.Z`, `4.1.Z`. Severe Security Issues ---------------------- @@ -63,7 +66,7 @@ For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. -**Currently included series:** `4.1.Z`, `4.0.Z`, `3.2.Z`. +**Currently included series:** `4.2.Z`, `4.1.Z`, `3.2.Z`. Unsupported Release Series -------------------------- diff --git a/source/plugins.md b/source/plugins.md index dbccfd4..7b7eb80 100644 --- a/source/plugins.md +++ b/source/plugins.md @@ -39,13 +39,13 @@ to run integration tests using a dummy Rails application. Create your plugin with the command: ```bash -$ bin/rails plugin new yaffle +$ rails plugin new yaffle ``` See usage and options by asking for help: ```bash -$ bin/rails plugin new --help +$ rails plugin new --help ``` Testing Your Newly Generated Plugin diff --git a/source/rails_on_rack.md b/source/rails_on_rack.md index 01941fa..042ebde 100644 --- a/source/rails_on_rack.md +++ b/source/rails_on_rack.md @@ -99,6 +99,10 @@ To find out more about different `rackup` options: $ rackup --help ``` +### Development and auto-reloading + +Middlewares are loaded once and are not monitored for changes. You will have to restart the server for changes to be reflected in the running application. + Action Dispatcher Middleware Stack ---------------------------------- @@ -229,7 +233,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::Static`** -* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`. +* Used to serve static files. Disabled if `config.serve_static_files` is `false`. **`Rack::Lock`** @@ -273,7 +277,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::Callbacks`** -* Runs the prepare callbacks before serving the request. +* Provides callbacks to be executed before and after dispatching the request. **`ActiveRecord::Migration::CheckPending`** @@ -303,7 +307,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Parses out parameters from the request into `params`. -**`ActionDispatch::Head`** +**`Rack::Head`** * Converts HEAD requests to `GET` requests and serves them as so. diff --git a/source/routing.md b/source/routing.md index af8c1bb..b1a287f 100644 --- a/source/routing.md +++ b/source/routing.md @@ -756,7 +756,7 @@ get '*a/foo/*b', to: 'test#index' would match `zoo/woo/foo/bar/baz` with `params[:a]` equals `'zoo/woo'`, and `params[:b]` equals `'bar/baz'`. -NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equals to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this: +NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equal to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this: ```ruby get '*pages', to: 'pages#show', format: false diff --git a/source/ruby_on_rails_guides_guidelines.md b/source/ruby_on_rails_guides_guidelines.md index 6206b3c..c0438f6 100644 --- a/source/ruby_on_rails_guides_guidelines.md +++ b/source/ruby_on_rails_guides_guidelines.md @@ -54,6 +54,7 @@ API Documentation Guidelines The guides and the API should be coherent and consistent where appropriate. In particular, these sections of the [API Documentation Guidelines](api_documentation_guidelines.html) also apply to the guides: * [Wording](api_documentation_guidelines.html#wording) +* [English](api_documentation_guidelines.html#english) * [Example Code](api_documentation_guidelines.html#example-code) * [Filenames](api_documentation_guidelines.html#file-names) * [Fonts](api_documentation_guidelines.html#fonts) diff --git a/source/security.md b/source/security.md index 125dd82..b3869b1 100644 --- a/source/security.md +++ b/source/security.md @@ -362,7 +362,7 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend **CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. -A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen. +A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen. Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.
 @@ -942,7 +942,7 @@ unless params[:token].nil? end ``` -When `params[:token]` is one of: `[]`, `[nil]`, `[nil, nil, ...]` or +When `params[:token]` is one of: `[nil]`, `[nil, nil, ...]` or `['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or `IN ('foo', NULL)` where clauses still will be added to the SQL query. @@ -953,9 +953,9 @@ request: | JSON | Parameters | |-----------------------------------|--------------------------| | `{ "person": null }` | `{ :person => nil }` | -| `{ "person": [] }` | `{ :person => nil }` | -| `{ "person": [null] }` | `{ :person => nil }` | -| `{ "person": [null, null, ...] }` | `{ :person => nil }` | +| `{ "person": [] }` | `{ :person => [] }` | +| `{ "person": [null] }` | `{ :person => [] }` | +| `{ "person": [null, null, ...] }` | `{ :person => [] }` | | `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` | It is possible to return to old behaviour and disable `deep_munge` configuring diff --git a/source/testing.md b/source/testing.md index 29724ae..8a8befc 100644 --- a/source/testing.md +++ b/source/testing.md @@ -29,11 +29,13 @@ Testing support was woven into the Rails fabric from the beginning. It wasn't an By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`. -A dedicated test database allows you to set up and interact with test data in isolation. Tests can mangle test data with confidence, that won't touch the data in the development or production databases. +A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases. + +Also, each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`. ### Rails Sets up for Testing from the Word Go -Rails creates a `test` folder for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this folder then you shall see: +Rails creates a `test` directory for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this directory then you shall see: ```bash $ ls -F test @@ -41,9 +43,9 @@ controllers/ helpers/ mailers/ test_helper.rb fixtures/ integration/ models/ ``` -The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. +The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers. -Fixtures are a way of organizing test data; they reside in the `fixtures` folder. +Fixtures are a way of organizing test data; they reside in the `fixtures` directory. The `test_helper.rb` file holds the default configuration for your tests. @@ -51,7 +53,7 @@ The `test_helper.rb` file holds the default configuration for your tests. For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures. -You can find comprehensive documentation in the [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). +You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### What Are Fixtures? @@ -61,7 +63,7 @@ You'll find fixtures under your `test/fixtures` directory. When you run `rails g #### YAML -YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`). +YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`). Here's a sample YAML fixture file: @@ -78,11 +80,11 @@ steve: profession: guy with keyboard ``` -Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them. +Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. If you are working with [associations](/association_basics.html), you can simply define a reference node between two different fixtures. Here's an example with -a belongs_to/has_many association: +a `belongs_to`/`has_many` association: ```yaml # In fixtures/categories.yml @@ -96,11 +98,9 @@ one: category: about ``` -Note: For associations to reference one another by name, you cannot specify the `id:` - attribute on the fixtures. Rails will auto assign a primary key to be consistent between - runs. If you manually specify an `id:` attribute, this behavior will not work. For more - information on this association behavior please read the - [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). +Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`. + +NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### ERB'in It Up @@ -116,15 +116,17 @@ user_<%= n %>: #### Fixtures in Action -Rails by default automatically loads all fixtures from the `test/fixtures` folder for your models and controllers test. Loading involves three steps: +Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps: * Remove any existing data from the table corresponding to the fixture * Load the fixture data into the table -* Dump the fixture data into a variable in case you want to access it directly +* Dump the fixture data into a method in case you want to access it directly + +TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)) #### Fixtures are Active Record objects -Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example: +Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method who's scope is local of the test case. For example: ```ruby # this will return the User object for the fixture named david @@ -140,13 +142,11 @@ email(david.girlfriend.email, david.location_tonight) Unit Testing your Models ------------------------ -In Rails, models tests are what you write to test your models. - -For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. We will be using examples from this generated code and will be supplementing it with additional examples where necessary. +In Rails, unit tests are what you write to test your models. -NOTE: For more information on Rails _scaffolding_, refer to [Getting Started with Rails](getting_started.html) +For this guide we will be using the application we built in the [Getting Started with Rails](getting_started.html) guide. -When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder: +If you remember when you used the `rails generate scaffold` command from earlier. We created our first resource among other things it created a test stub in the `test/models` directory: ```bash $ bin/rails generate scaffold article title:string body:text @@ -666,7 +666,7 @@ Integration Testing Integration tests are used to test the interaction among any number of controllers. They are generally used to test important work flows within your application. -Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you. +Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' directory within your application. Rails provides a generator to create an integration test skeleton for you. ```bash $ bin/rails generate integration_test user_flows @@ -728,7 +728,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest https!(false) get "/articles/all" assert_response :success - assert assigns(:products) + assert assigns(:articles) end end ``` @@ -785,26 +785,25 @@ end Rake Tasks for Running your Tests --------------------------------- -You don't need to set up and run your tests by hand on a test-by-test basis. -Rails comes with a number of commands to help in testing. -The table below lists all commands that come along in the default Rakefile -when you initiate a Rails project. +Rails comes with a number of built-in rake tasks to help with testing. The +table below lists the commands included in the default Rakefile when a Rails +project is created. | Tasks | Description | | ----------------------- | ----------- | -| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the tests by default | +| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default | | `rake test:controllers` | Runs all the controller tests from `test/controllers` | | `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` | | `rake test:helpers` | Runs all the helper tests from `test/helpers` | | `rake test:integration` | Runs all the integration tests from `test/integration` | +| `rake test:jobs` | Runs all the job tests from `test/jobs` | | `rake test:mailers` | Runs all the mailer tests from `test/mailers` | | `rake test:models` | Runs all the model tests from `test/models` | | `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` | -| `rake test:all` | Runs all tests quickly by merging all types and not resetting db | -| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db | +| `rake test:db` | Runs all tests in the `test` directory and resets the db | -Brief Note About `Minitest` +A Brief Note About Minitest ----------------------------- Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. @@ -901,11 +900,17 @@ end Testing Routes -------------- -Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Articles` controller above should look like: +Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like: ```ruby -test "should route to article" do - assert_routing '/articles/1', {controller: "articles", action: "show", id: "1"} +class ArticleRoutesTest < ActionController::TestCase + test "should route to article" do + assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" } + end + + test "should route to create article" do + assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" }) + end end ``` @@ -1042,6 +1047,68 @@ end Moreover, since the test class extends from `ActionView::TestCase`, you have access to Rails' helper methods such as `link_to` or `pluralize`. +Testing Jobs +------------ + +Since your custom jobs can be queued at different levels inside your application, +you'll need to test both jobs themselves (their behavior when they get enqueued) +and that other entities correctly enqueue them. + +### A Basic Test Case + +By default, when you generate a job, an associated test will be generated as well +under the `test/jobs` directory. Here's an example test with a billing job: + +```ruby +require 'test_helper' + +class BillingJobTest < ActiveJob::TestCase + test 'that account is charged' do + BillingJob.perform_now(account, product) + assert account.reload.charged_for?(product) + end +end +``` + +This test is pretty simple and only asserts that the job get the work done +as expected. + +By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that +your jobs are performed inline. It will also ensure that all previously performed +and enqueued jobs are cleared before any test run so you can safely assume that +no jobs have already been executed in the scope of each test. + +### Custom Assertions And Testing Jobs Inside Other Components + +Active Job ships with a bunch of custom assertions that can be used to lessen +the verbosity of tests: + +| Assertion | Purpose | +| -------------------------------------- | ------- | +| `assert_enqueued_jobs(number)` | Asserts that the number of enqueued jobs matches the given number. | +| `assert_performed_jobs(number)` | Asserts that the number of performed jobs matches the given number. | +| `assert_no_enqueued_jobs { ... }` | Asserts that no jobs have been enqueued. | +| `assert_no_performed_jobs { ... }` | Asserts that no jobs have been performed. | +| `assert_enqueued_with([args]) { ... }` | Asserts that the job passed in the block has been enqueued with the given arguments. | +| `assert_performed_with([args]) { ... }`| Asserts that the job passed in the block has been performed with the given arguments. | + +It's a good practice to ensure that your jobs correctly get enqueued or performed +wherever you invoke them (e.g. inside your controllers). This is precisely where +the custom assertions provided by Active Job are pretty useful. For instance, +within a model: + +```ruby +require 'test_helper' + +class ProductTest < ActiveSupport::TestCase + test 'billing job scheduling' do + assert_enqueued_with(job: BillingJob) do + product.charge(account) + end + end +end +``` + Other Testing Approaches ------------------------ @@ -1053,3 +1120,4 @@ The built-in `minitest` based testing is not the only way to test Rails applicat * [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests. * [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions. * [RSpec](http://relishapp.com/rspec), a behavior-driven development framework +* [Capybara](http://jnicklas.github.com/capybara/), Acceptance test framework for web applications diff --git a/source/upgrading_ruby_on_rails.md b/source/upgrading_ruby_on_rails.md index 989d840..51c1449 100644 --- a/source/upgrading_ruby_on_rails.md +++ b/source/upgrading_ruby_on_rails.md @@ -8,7 +8,7 @@ This guide provides steps to be followed when you upgrade your applications to a General Advice -------------- -Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance out several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few. +Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few. ### Test Coverage @@ -28,7 +28,7 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp Rails provides the `rails:update` rake task. After updating the Rails version in the Gemfile, run this rake task. -This will help you with the creation of new files and changes of old files in a +This will help you with the creation of new files and changes of old files in an interactive session. ```bash @@ -50,20 +50,81 @@ Don't forget to review the difference, to see if there were any unexpected chang Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- -NOTE: This section is a work in progress, please help to improve this by sending -a [pull request](https://github.com/rails/rails/edit/master/guides/source/upgrading_ruby_on_rails.md). - ### Web Console -TODO: setup instructions for web console on existing apps. +First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment. ### Responders -TODO: mention https://github.com/rails/rails/pull/16526 +`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your Gemfile. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + respond_to :html, :json + + def show + @user = User.find(params[:id]) + respond_with @user + end +end +``` + +Instance-level `respond_to` is unaffected and does not require the additional gem: + +```ruby +# app/controllers/users_controller.rb + +class UsersController < ApplicationController + def show + @user = User.find(params[:id]) + respond_to do |format| + format.html + format.json { render json: @user } + end + end +end +``` + +See [#16526](https://github.com/rails/rails/pull/16526) for more details. ### Error handling in transaction callbacks -TODO: mention https://github.com/rails/rails/pull/16537 +Currently, Active Record suppresses errors raised +within `after_rollback` or `after_commit` callbacks and only prints them to +the logs. In the next version, these errors will no longer be suppressed. +Instead, the errors will propagate normally just like in other Active +Record callbacks. + +When you define a `after_rollback` or `after_commit` callback, you +will receive a deprecation warning about this upcoming change. When +you are ready, you can opt into the new behavior and remove the +deprecation warning by adding following configuration to your +`config/application.rb`: + + config.active_record.raise_in_transactional_callbacks = true + +See [#14488](https://github.com/rails/rails/pull/14488) and +[#16537](https://github.com/rails/rails/pull/16537) for more details. + +### Ordering of test cases + +In Rails 5.0, test cases will be executed in random order by default. In +anticipation of this change, Rails 4.2 introduced a new configuration option +`active_support.test_order` for explicitly specifying the test ordering. This +allows you to either lock down the current behavior by setting the option to +`:sorted`, or opt into the future behavior by setting the option to `:random`. + +If you do not specify a value for this option, a deprecation warning will be +emitted. To avoid this, add the following line to your test environment: + +```ruby +# config/environments/test.rb +Rails.application.configure do + config.active_support.test_order = :sorted # or `:random` if you prefer +end +``` ### Serialized attributes @@ -72,6 +133,18 @@ assigning `nil` to a serialized attribute will save it to the database as `NULL` instead of passing the `nil` value through the coder (e.g. `"null"` when using the `JSON` coder). +### Production log level + +In Rails 5, the default log level for the production environment will be changed +to `:debug` (from `:info`). To preserve the current default, add the following +line to your `production.rb`: + +```ruby +# Set to `:info` to match the current default, or set to `:debug` to opt-into +# the future default. +config.log_level = :info +``` + ### `after_bundle` in Rails templates If you have a Rails template that adds all the files in version control, it @@ -104,24 +177,15 @@ after_bundle do end ``` -### Rails Html Sanitizer +### Rails HTML Sanitizer There's a new choice for sanitizing HTML fragments in your applications. The venerable html-scanner approach is now officially being deprecated in favor of -[`Rails Html Sanitizer`](https://github.com/rails/rails-html-sanitizer). +[`Rails HTML Sanitizer`](https://github.com/rails/rails-html-sanitizer). This means the methods `sanitize`, `sanitize_css`, `strip_tags` and `strip_links` are backed by a new implementation. -In the next major Rails version `Rails Html Sanitizer` will be the default -sanitizer. It already is for new applications. - -Include this in your Gemfile to try it out today: - -```ruby -gem 'rails-html-sanitizer' -``` - This new sanitizer uses [Loofah](https://github.com/flavorjones/loofah) internally. Loofah in turn uses Nokogiri, which wraps XML parsers written in both C and Java, so sanitization should be faster no matter which Ruby version you run. @@ -136,9 +200,53 @@ Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more The documentation for `PermitScrubber` and `TargetScrubber` explains how you can gain complete control over when and how elements should be stripped. +If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your Gemfile: + +```ruby +gem 'rails-deprecated_sanitizer' +``` + ### Rails DOM Testing -TODO: Mention https://github.com/rails/rails/commit/4e97d7585a2f4788b9eed98c6cdaf4bb6f2cf5ce +The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing). + + +### Masked Authenticity Tokens + +In order to mitigate SSL attacks, `form_authenticity_token` is now masked so that it varies with each request. Thus, tokens are validated by unmasking and then decrypting. As a result, any strategies for verifying requests from non-rails forms that relied on a static session CSRF token have to take this into account. + +### Action Mailer + +Previously, calling a mailer method on a mailer class will result in the +corresponding instance method being executed directly. With the introduction of +Active Job and `#deliver_later`, this is no longer true. In Rails 4.2, the +invocation of the instance methods are deferred until either `deliver_now` or +`deliver_later` is called. For example: + +```ruby +class Notifier < ActionMailer::Base + def notify(user, ...) + puts "Called" + mail(to: user.email, ...) + end +end + +mail = Notifier.notify(user, ...) # Notifier#notify is not yet called at this point +mail = mail.deliver_now # Prints "Called" +``` + +This should not result in any noticeable differences for most applications. +However, if you need some non-mailer methods to be executed synchronously, and +you were previously relying on the synchronous proxying behavior, you should +define them as class methods on the mailer class directly: + +```ruby +class Notifier < ActionMailer::Base + def self.broadcast_notifications(users, ...) + users.each { |user| Notifier.notify(user, ...) } + end +end +``` Upgrading from Rails 4.0 to Rails 4.1 ------------------------------------- @@ -163,7 +271,7 @@ will now trigger CSRF protection. Switch to xhr :get, :index, format: :js ``` -to explicitly test an XmlHttpRequest. +to explicitly test an `XmlHttpRequest`. If you really mean to load JavaScript from remote ` - - -``` - -#### register_stylesheet_expansion - -Register one or more stylesheet files to be included when symbol is passed to `stylesheet_link_tag`. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in `vendor/assets/stylesheets`. - -```ruby -ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion monkey: ["head", "body", "tail"] - -stylesheet_link_tag :monkey # => - - - -``` - #### auto_discovery_link_tag Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed. ```ruby -auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # => - +auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", { title: "RSS Feed" }) # => + ``` #### image_path @@ -486,7 +442,7 @@ image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png #### image_url -Computes the url to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host. +Computes the URL to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host. ```ruby image_url("https://github.com/edit.png") # => http://www.example.com/assets/edit.png @@ -537,7 +493,7 @@ javascript_path "common" # => /assets/common.js #### javascript_url -Computes the url to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host. +Computes the URL to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host. ```ruby javascript_url "common" # => http://www.example.com/assets/common.js @@ -551,7 +507,7 @@ Returns a stylesheet link tag for the sources specified as arguments. If you don stylesheet_link_tag "application" # => ``` -You can also include all styles in the stylesheet directory using :all as the source: +You can also include all styles in the stylesheet directory using `:all` as the source: ```ruby stylesheet_link_tag :all @@ -566,7 +522,7 @@ stylesheet_link_tag :all, cache: true #### stylesheet_path -Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. +Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. ```ruby stylesheet_path "application" # => /assets/application.css @@ -574,7 +530,7 @@ stylesheet_path "application" # => /assets/application.css #### stylesheet_url -Computes the url to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host. +Computes the URL to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host. ```ruby stylesheet_url "application" # => http://www.example.com/assets/application.css @@ -610,7 +566,7 @@ end ```ruby atom_feed do |feed| feed.title("Articles Index") - feed.updated((@articles.first.created_at)) + feed.updated(@articles.first.created_at) @articles.each do |article| feed.entry(article) do |entry| @@ -643,7 +599,7 @@ This would add something like "Process data files (0.34523)" to the log, which y #### cache -A method for caching fragments of a view rather than an entire action or page. This technique is useful caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `ActionController::Caching::Fragments` for more information. +A method for caching fragments of a view rather than an entire action or page. This technique is useful for caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See `AbstractController::Caching::Fragments` for more information. ```erb <% cache do %> @@ -784,7 +740,7 @@ Returns a select tag with options for each of the minutes 0 through 59 with the ```ruby # Generates a select field for minutes that defaults to the minutes for the time provided. -select_minute(Time.now + 6.hours) +select_minute(Time.now + 10.minutes) ``` #### select_month @@ -802,7 +758,7 @@ Returns a select tag with options for each of the seconds 0 through 59 with the ```ruby # Generates a select field for seconds that defaults to the seconds for the time provided -select_second(Time.now + 16.minutes) +select_second(Time.now + 16.seconds) ``` #### select_time @@ -848,7 +804,7 @@ time_select("order", "submitted") Returns a `pre` tag that has object dumped by YAML. This creates a very readable way to inspect an object. ```ruby -my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]} +my_hash = { 'first' => 1, 'second' => 'two', 'third' => [1,2,3] } debug(my_hash) ``` @@ -867,13 +823,13 @@ third: Form helpers are designed to make working with models much easier compared to using just standard HTML elements by providing a set of methods for creating forms based on your models. This helper generates the HTML for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form is submitted (i.e., when the user hits the submit button or form.submit is called via JavaScript), the form inputs will be bundled into the params object and passed back to the controller. -There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the ActionView::Helpers::FormTagHelper documentation. +There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the `ActionView::Helpers::FormTagHelper` documentation. -The core method of this helper, form_for, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it: +The core method of this helper, `form_for`, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it: ```html+erb # Note: a @person variable will have been created in the controller (e.g. @person = Person.new) -<%= form_for @person, url: {action: "create"} do |f| %> +<%= form_for @person, url: { action: "create" } do |f| %> <%= f.text_field :first_name %> <%= f.text_field :last_name %> <%= submit_tag 'Create' %> @@ -893,7 +849,7 @@ The HTML generated for this would be: The params object created when this form is submitted would look like: ```ruby -{"action" => "create", "controller" => "people", "person" => {"first_name" => "William", "last_name" => "Smith"}} +{ "action" => "create", "controller" => "people", "person" => { "first_name" => "William", "last_name" => "Smith" } } ``` The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller. @@ -911,10 +867,10 @@ check_box("article", "validated") #### fields_for -Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form: +Creates a scope around a specific model object like `form_for`, but doesn't create the form tags themselves. This makes `fields_for` suitable for specifying additional model objects in the same form: ```html+erb -<%= form_for @person, url: {action: "update"} do |person_form| %> +<%= form_for @person, url: { action: "update" } do |person_form| %> First name: <%= person_form.text_field :first_name %> Last name : <%= person_form.text_field :last_name %> @@ -1034,11 +990,11 @@ Returns `select` and `option` tags for the collection of existing return values Example object structure for use with this method: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord belongs_to :author end -class Author < ActiveRecord::Base +class Author < ApplicationRecord has_many :articles def name_with_initial "#{first_name.first}. #{last_name}" @@ -1049,7 +1005,7 @@ end Sample usage (selecting the associated Author for an instance of Article, `@article`): ```ruby -collection_select(:article, :author_id, Author.all, :id, :name_with_initial, {prompt: true}) +collection_select(:article, :author_id, Author.all, :id, :name_with_initial, { prompt: true }) ``` If `@article.author_id` is 1, this would return: @@ -1070,11 +1026,11 @@ Returns `radio_button` tags for the collection of existing return values of `met Example object structure for use with this method: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord belongs_to :author end -class Author < ActiveRecord::Base +class Author < ApplicationRecord has_many :articles def name_with_initial "#{first_name.first}. #{last_name}" @@ -1106,11 +1062,11 @@ Returns `check_box` tags for the collection of existing return values of `method Example object structure for use with this method: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord has_and_belongs_to_many :authors end -class Author < ActiveRecord::Base +class Author < ApplicationRecord has_and_belongs_to_many :articles def name_with_initial "#{first_name.first}. #{last_name}" @@ -1136,14 +1092,6 @@ If `@article.author_ids` is [1], this would return: ``` -#### country_options_for_select - -Returns a string of option tags for pretty much any country in the world. - -#### country_select - -Returns select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. - #### option_groups_from_collection_for_select Returns a string of `option` tags, like `options_from_collection_for_select`, but groups them by `optgroup` tags based on the object relationships of the arguments. @@ -1151,12 +1099,12 @@ Returns a string of `option` tags, like `options_from_collection_for_select`, bu Example object structure for use with this method: ```ruby -class Continent < ActiveRecord::Base +class Continent < ApplicationRecord has_many :countries # attribs: id, name end -class Country < ActiveRecord::Base +class Country < ApplicationRecord belongs_to :continent # attribs: id, name, continent_id end @@ -1205,7 +1153,7 @@ Returns a string of option tags that have been compiled by iterating over the `c # options_from_collection_for_select(collection, value_method, text_method, selected = nil) ``` -For example, imagine a loop iterating over each person in @project.people to generate an input tag: +For example, imagine a loop iterating over each person in `@project.people` to generate an input tag: ```ruby options_from_collection_for_select(@project.people, "id", "name") @@ -1221,7 +1169,7 @@ Create a select tag and a series of contained option tags for the provided objec Example: ```ruby -select("article", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true}) +select("article", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true }) ``` If `@article.person_id` is 1, this would become: @@ -1230,8 +1178,8 @@ If `@article.person_id` is 1, this would become: ``` @@ -1284,7 +1232,7 @@ Creates a field set for grouping HTML form elements. Creates a file upload field. ```html+erb -<%= form_tag({action:"post"}, multipart: true) do %> +<%= form_tag({ action: "post" }, multipart: true) do %> <%= file_field_tag "file" %> <%= submit_tag %> <% end %> @@ -1299,7 +1247,7 @@ file_field_tag 'attachment' #### form_tag -Starts a form tag that points the action to an url configured with `url_for_options` just like `ActionController::Base#url_for`. +Starts a form tag that points the action to a URL configured with `url_for_options` just like `ActionController::Base#url_for`. ```html+erb <%= form_tag '/articles' do %> @@ -1420,22 +1368,6 @@ date_field_tag "dob" Provides functionality for working with JavaScript in your views. -#### button_to_function - -Returns a button that'll trigger a JavaScript function using the onclick handler. Examples: - -```ruby -button_to_function "Greeting", "alert('Hello world!')" -button_to_function "Delete", "if (confirm('Really?')) do_delete()" -button_to_function "Details" do |page| - page[:details].visual_effect :toggle_slide -end -``` - -#### define_javascript_functions - -Includes the Action Pack JavaScript libraries inside a single `script` tag. - #### escape_javascript Escape carrier returns and single and double quotes for JavaScript segments. @@ -1456,15 +1388,6 @@ alert('All is good') ``` -#### link_to_function - -Returns a link that will trigger a JavaScript function using the onclick handler and return false after the fact. - -```ruby -link_to_function "Greeting", "alert('Hello world!')" -# => Greeting -``` - ### NumberHelper Provides methods for converting numbers into formatted strings. Methods are provided for phone numbers, currency, percentage, precision, positional notation, and file size. @@ -1496,7 +1419,7 @@ number_to_percentage(100, precision: 0) # => 100% #### number_to_phone -Formats a number into a US phone number. +Formats a number into a phone number (US by default). ```ruby number_to_phone(1235551234) # => 123-555-1234 @@ -1531,7 +1454,7 @@ This sanitize helper will HTML encode all tags and strip all attributes that are sanitize @article.body ``` -If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else. +If either the `:attributes` or `:tags` options are passed, only the mentioned attributes and tags are allowed and nothing else. ```ruby sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) @@ -1553,12 +1476,12 @@ Sanitizes a block of CSS code. Strips all link tags from text leaving just the link text. ```ruby -strip_links("Ruby on Rails") +strip_links('Ruby on Rails') # => Ruby on Rails ``` ```ruby -strip_links("emails to me@email.com.") +strip_links('emails to me@email.com.') # => emails to me@email.com. ``` @@ -1599,9 +1522,9 @@ details can be found in the [Rails Security Guide](security.html#cross-site-requ Localized Views --------------- -Action View has the ability render different templates depending on the current locale. +Action View has the ability to render different templates depending on the current locale. -For example, suppose you have a `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. +For example, suppose you have an `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. You can use the same technique to localize the rescue files in your public directory. For example, setting `I18n.locale = :de` and creating `public/500.de.html` and `public/404.de.html` would allow you to have localized rescue pages. diff --git a/source/active_job_basics.md b/source/active_job_basics.md index 3657d7c..c9f70dc 100644 --- a/source/active_job_basics.md +++ b/source/active_job_basics.md @@ -1,15 +1,17 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Job Basics ================= This guide provides you with all you need to get started in creating, -enqueueing and executing background jobs. +enqueuing and executing background jobs. After reading this guide, you will know: * How to create jobs. * How to enqueue jobs. * How to run jobs in the background. -* How to send emails from your application async. +* How to send emails from your application asynchronously. -------------------------------------------------------------------------------- @@ -18,7 +20,7 @@ Introduction ------------ Active Job is a framework for declaring jobs and making them run on a variety -of queueing backends. These jobs can be everything from regularly scheduled +of queuing backends. These jobs can be everything from regularly scheduled clean-ups, to billing charges, to mailings. Anything that can be chopped up into small units of work and run in parallel, really. @@ -26,11 +28,14 @@ into small units of work and run in parallel, really. The Purpose of Active Job ----------------------------- The main point is to ensure that all Rails apps will have a job infrastructure -in place, even if it's in the form of an "immediate runner". We can then have -framework features and other gems build on top of that, without having to -worry about API differences between various job runners such as Delayed Job -and Resque. Picking your queuing backend becomes more of an operational concern, -then. And you'll be able to switch between them without having to rewrite your jobs. +in place. We can then have framework features and other gems build on top of that, +without having to worry about API differences between various job runners such as +Delayed Job and Resque. Picking your queuing backend becomes more of an operational +concern, then. And you'll be able to switch between them without having to rewrite +your jobs. + +NOTE: Rails by default comes with an "immediate runner" queuing implementation. +That means that each job that has been enqueued will run immediately. Creating a Job @@ -57,68 +62,107 @@ $ bin/rails generate job guests_cleanup --queue urgent ``` If you don't want to use a generator, you could create your own file inside of -`app/jobs`, just make sure that it inherits from `ActiveJob::Base`. +`app/jobs`, just make sure that it inherits from `ApplicationJob`. Here's what a job looks like: ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :default - def perform(*args) + def perform(*guests) # Do something later end end ``` +Note that you can define `perform` with as many arguments as you want. + ### Enqueue the Job Enqueue a job like so: ```ruby -# Enqueue a job to be performed as soon the queueing system is free. -MyJob.perform_later record +# Enqueue a job to be performed as soon as the queuing system is +# free. +GuestsCleanupJob.perform_later guest ``` ```ruby # Enqueue a job to be performed tomorrow at noon. -MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) +GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest) ``` ```ruby # Enqueue a job to be performed 1 week from now. -MyJob.set(wait: 1.week).perform_later(record) +GuestsCleanupJob.set(wait: 1.week).perform_later(guest) ``` -That's it! +```ruby +# `perform_now` and `perform_later` will call `perform` under the hood so +# you can pass as many arguments as defined in the latter. +GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter') +``` +That's it! Job Execution ------------- -If no adapter is set, the job is immediately executed. +For enqueuing and executing jobs in production you need to set up a queuing backend, +that is to say you need to decide for a 3rd-party queuing library that Rails should use. +Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM. +If the process crashes or the machine is reset, then all outstanding jobs are lost with the +default async back-end. This may be fine for smaller apps or non-critical jobs, but most +production apps will need to pick a persistent backend. ### Backends -Active Job has built-in adapters for multiple queueing backends (Sidekiq, +Active Job has built-in adapters for multiple queuing backends (Sidekiq, Resque, Delayed Job and others). To get an up-to-date list of the adapters see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). ### Setting the Backend -You can easily set your queueing backend: +You can easily set your queuing backend: ```ruby # config/application.rb module YourApp class Application < Rails::Application - # Be sure to have the adapter's gem in your Gemfile and follow - # the adapter's specific installation and deployment instructions. + # Be sure to have the adapter's gem in your Gemfile + # and follow the adapter's specific installation + # and deployment instructions. config.active_job.queue_adapter = :sidekiq end end ``` +You can also configure your backend on a per job basis. + +```ruby +class GuestsCleanupJob < ApplicationJob + self.queue_adapter = :resque + #.... +end + +# Now your job will use `resque` as it's backend queue adapter overriding what +# was configured in `config.active_job.queue_adapter`. +``` + +### Starting the Backend + +Since jobs run in parallel to your Rails application, most queuing libraries +require that you start a library-specific queuing service (in addition to +starting your Rails app) for the job processing to work. Refer to library +documentation for instructions on starting your queue backend. + +Here is a noncomprehensive list of documentation: + +- [Sidekiq](https://github.com/mperham/sidekiq/wiki/Active-Job) +- [Resque](https://github.com/resque/resque/wiki/ActiveJob) +- [Sucker Punch](https://github.com/brandonhilkert/sucker_punch#active-job) +- [Queue Classic](https://github.com/QueueClassic/queue_classic#active-job) Queues ------ @@ -127,7 +171,7 @@ Most of the adapters support multiple queues. With Active Job you can schedule the job to run on a specific queue: ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :low_priority #.... end @@ -144,15 +188,15 @@ module YourApp end end -# app/jobs/guests_cleanup.rb -class GuestsCleanupJob < ActiveJob::Base +# app/jobs/guests_cleanup_job.rb +class GuestsCleanupJob < ApplicationJob queue_as :low_priority #.... end # Now your job will run on queue production_low_priority on your -# production environment and on staging_low_priority on your staging -# environment +# production environment and on staging_low_priority +# on your staging environment ``` The default queue name prefix delimiter is '\_'. This can be changed by setting @@ -167,15 +211,15 @@ module YourApp end end -# app/jobs/guests_cleanup.rb -class GuestsCleanupJob < ActiveJob::Base +# app/jobs/guests_cleanup_job.rb +class GuestsCleanupJob < ApplicationJob queue_as :low_priority #.... end # Now your job will run on queue production.low_priority on your -# production environment and on staging.low_priority on your staging -# environment +# production environment and on staging.low_priority +# on your staging environment ``` If you want more control on what queue a job will be run you can pass a `:queue` @@ -190,7 +234,7 @@ block will be executed in the job context (so you can access `self.arguments`) and you must return the queue name: ```ruby -class ProcessVideoJob < ActiveJob::Base +class ProcessVideoJob < ApplicationJob queue_as do video = self.arguments.first if video.owner.premium? @@ -201,22 +245,22 @@ class ProcessVideoJob < ActiveJob::Base end def perform(video) - # do process video + # Do process video end end ProcessVideoJob.perform_later(Video.last) ``` -NOTE: Make sure your queueing backend "listens" on your queue name. For some +NOTE: Make sure your queuing backend "listens" on your queue name. For some backends you need to specify the queues to listen to. Callbacks --------- -Active Job provides hooks during the lifecycle of a job. Callbacks allow you to -trigger logic during the lifecycle of a job. +Active Job provides hooks during the life cycle of a job. Callbacks allow you to +trigger logic during the life cycle of a job. ### Available callbacks @@ -230,17 +274,17 @@ trigger logic during the lifecycle of a job. ### Usage ```ruby -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :default before_enqueue do |job| - # do something with the job instance + # Do something with the job instance end around_perform do |job, block| - # do something before perform + # Do something before perform block.call - # do something after perform + # Do something after perform end def perform @@ -266,6 +310,19 @@ UserMailer.welcome(@user).deliver_later ``` +Internationalization +-------------------- + +Each job uses the `I18n.locale` set when the job was created. Useful if you send +emails asynchronously: + +```ruby +I18n.locale = :eo + +UserMailer.welcome(@user).deliver_later # Email will be localized to Esperanto. +``` + + GlobalID -------- @@ -274,7 +331,7 @@ Active Record objects to your job instead of class/id pairs, which you then have to manually deserialize. Before, jobs would look like this: ```ruby -class TrashableCleanupJob < ActiveJob::Base +class TrashableCleanupJob < ApplicationJob def perform(trashable_class, trashable_id, depth) trashable = trashable_class.constantize.find(trashable_id) trashable.cleanup(depth) @@ -285,7 +342,7 @@ end Now you can simply do: ```ruby -class TrashableCleanupJob < ActiveJob::Base +class TrashableCleanupJob < ApplicationJob def perform(trashable, depth) trashable.cleanup(depth) end @@ -293,7 +350,7 @@ end ``` This works with any class that mixes in `GlobalID::Identification`, which -by default has been mixed into Active Model classes. +by default has been mixed into Active Record classes. Exceptions @@ -303,12 +360,11 @@ Active Job provides a way to catch exceptions raised during the execution of the job: ```ruby - -class GuestsCleanupJob < ActiveJob::Base +class GuestsCleanupJob < ApplicationJob queue_as :default rescue_from(ActiveRecord::RecordNotFound) do |exception| - # do something with the exception + # Do something with the exception end def perform @@ -316,3 +372,17 @@ class GuestsCleanupJob < ActiveJob::Base end end ``` + +### Deserialization + +GlobalID allows serializing full Active Record objects passed to `#perform`. + +If a passed record is deleted after the job is enqueued but before the `#perform` +method is called Active Job will raise an `ActiveJob::DeserializationError` +exception. + +Job Testing +-------------- + +You can find detailed instructions on how to test your jobs in the +[testing guide](testing.html#testing-jobs). diff --git a/source/active_model_basics.md b/source/active_model_basics.md index a520b91..e834aea 100644 --- a/source/active_model_basics.md +++ b/source/active_model_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Model Basics =================== @@ -6,12 +8,12 @@ classes. Active Model allows for Action Pack helpers to interact with plain Ruby objects. Active Model also helps build custom ORMs for use outside of the Rails framework. -After reading this guide, you will be able to add to plain Ruby objects: +After reading this guide, you will know: -* The ability to behave like an Active Record model. -* Callbacks and validations like Active Record. -* Serializers. -* Integration with the Rails internationalization (i18n) framework. +* How an Active Record model behaves. +* How Callbacks and validations work. +* How serializers work. +* How Active Model integrates with the Rails internationalization (i18n) framework. -------------------------------------------------------------------------------- @@ -154,7 +156,7 @@ person.changed? # => false person.first_name = "First Name" person.first_name # => "First Name" -# returns if any attribute has changed. +# returns true if any of the attributes have unsaved changes, false otherwise. person.changed? # => true # returns a list of attributes that have changed before saving. @@ -195,7 +197,7 @@ person.last_name_change # => nil ### Validations -`ActiveModel::Validations` module adds the ability to validate class objects +The `ActiveModel::Validations` module adds the ability to validate class objects like in Active Record. ```ruby @@ -290,7 +292,7 @@ objects. ### Serialization -`ActiveModel::Serialization` provides a basic serialization for your object. +`ActiveModel::Serialization` provides basic serialization for your object. You need to declare an attributes hash which contains the attributes you want to serialize. Attributes must be strings, not symbols. @@ -317,9 +319,8 @@ person.serializable_hash # => {"name"=>"Bob"} #### ActiveModel::Serializers -Rails provides two serializers `ActiveModel::Serializers::JSON` and -`ActiveModel::Serializers::Xml`. Both of these modules automatically include -the `ActiveModel::Serialization`. +Rails provides an `ActiveModel::Serializers::JSON` serializer. +This module automatically include the `ActiveModel::Serialization`. ##### ActiveModel::Serializers::JSON @@ -338,7 +339,7 @@ class Person end ``` -With the `as_json` you have a hash representing the model. +With the `as_json` method you have a hash representing the model. ```ruby person = Person.new @@ -377,62 +378,6 @@ person.from_json(json) # => # person.name # => "Bob" ``` -##### ActiveModel::Serializers::Xml - -To use the `ActiveModel::Serializers::Xml` you only need to change from -`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`. - -```ruby -class Person - include ActiveModel::Serializers::Xml - - attr_accessor :name - - def attributes - {'name' => nil} - end -end -``` - -With the `to_xml` you have a XML representing the model. - -```ruby -person = Person.new -person.to_xml # => "\n\n \n\n" -person.name = "Bob" -person.to_xml # => "\n\n Bob\n\n" -``` - -From a XML string you define the attributes of the model. -You need to have the `attributes=` method defined on your class: - -```ruby -class Person - include ActiveModel::Serializers::Xml - - attr_accessor :name - - def attributes=(hash) - hash.each do |key, value| - send("#{key}=", value) - end - end - - def attributes - {'name' => nil} - end -end -``` - -Now it is possible to create an instance of person and set the attributes using `from_xml`. - -```ruby -xml = { name: 'Bob' }.to_xml -person = Person.new -person.from_xml(xml) # => # -person.name # => "Bob" -``` - ### Translation `ActiveModel::Translation` provides integration between your object and the Rails @@ -463,7 +408,7 @@ Person.human_attribute_name('name') # => "Nome" ### Lint Tests -`ActiveModel::Lint::Tests` allow you to test whether an object is compliant with +`ActiveModel::Lint::Tests` allows you to test whether an object is compliant with the Active Model API. * app/models/person.rb @@ -483,14 +428,14 @@ the Active Model API. class PersonTest < ActiveSupport::TestCase include ActiveModel::Lint::Tests - def setup + setup do @model = Person.new end end ``` ```bash -$ rake test +$ rails test Run options: --seed 14596 @@ -516,14 +461,14 @@ an accessor named `password` with certain validations on it. #### Requirements -`ActiveModel::SecurePassword` depends on the [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), +`ActiveModel::SecurePassword` depends on [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'), so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly. In order to make this work, the model must have an accessor named `password_digest`. The `has_secure_password` will add the following validations on the `password` accessor: 1. Password should be present. 2. Password should be equal to its confirmation. -3. This maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) +3. The maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) #### Examples @@ -544,7 +489,7 @@ person.password = 'aditya' person.password_confirmation = 'nomatch' person.valid? # => false -# When the length of password, exceeds 72. +# When the length of password exceeds 72. person.password = person.password_confirmation = 'a' * 100 person.valid? # => false diff --git a/source/active_record_basics.md b/source/active_record_basics.md index ef86531..d9e9466 100644 --- a/source/active_record_basics.md +++ b/source/active_record_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Basics ==================== @@ -18,7 +20,7 @@ After reading this guide, you will know: What is Active Record? ---------------------- -Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the +Active Record is the M in [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an @@ -36,7 +38,7 @@ object on how to write to and read from the database. ### Object Relational Mapping -Object-Relational Mapping, commonly referred to as its abbreviation ORM, is +Object Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and @@ -60,7 +62,7 @@ Convention over Configuration in Active Record When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by -Rails, you'll need to write very little configuration (in some case no +Rails, you'll need to write very little configuration (in some cases no configuration at all) when creating Active Record models. The idea is that if you configure your applications in the very same way most of the time then this should be the default way. Thus, explicit configuration would be needed @@ -72,8 +74,8 @@ By default, Active Record uses some naming conventions to find out how the mapping between models and database tables should be created. Rails will pluralize your class names to find the respective database table. So, for a class `Book`, you should have a database table called **books**. The Rails -pluralization mechanisms are very powerful, being capable to pluralize (and -singularize) both regular and irregular words. When using class names composed +pluralization mechanisms are very powerful, being capable of pluralizing (and +singularizing) both regular and irregular words. When using class names composed of two or more words, the model class name should follow the Ruby conventions, using the CamelCase form, while the table name must contain the words separated by underscores. Examples: @@ -120,7 +122,7 @@ to Active Record instances: * `(association_name)_type` - Stores the type for [polymorphic associations](association_basics.html#polymorphic-associations). * `(table_name)_count` - Used to cache the number of belonging objects on - associations. For example, a `comments_count` column in a `Articles` class that + associations. For example, a `comments_count` column in an `Article` class that has many instances of `Comment` will cache the number of existent comments for each article. @@ -130,17 +132,17 @@ Creating Active Record Models ----------------------------- It is very easy to create Active Record models. All you have to do is to -subclass the `ActiveRecord::Base` class and you're good to go: +subclass the `ApplicationRecord` class and you're good to go: ```ruby -class Product < ActiveRecord::Base +class Product < ApplicationRecord end ``` This will create a `Product` model, mapped to a `products` table at the database. By doing this you'll also have the ability to map the columns of each row in that table with the attributes of the instances of your model. Suppose -that the `products` table was created using an SQL sentence like: +that the `products` table was created using an SQL statement like: ```sql CREATE TABLE products ( @@ -166,23 +168,24 @@ What if you need to follow a different naming convention or need to use your Rails application with a legacy database? No problem, you can easily override the default conventions. -You can use the `ActiveRecord::Base.table_name=` method to specify the table -name that should be used: +`ApplicationRecord` inherits from `ActiveRecord::Base`, which defines a +number of helpful methods. You can use the `ActiveRecord::Base.table_name=` +method to specify the table name that should be used: ```ruby -class Product < ActiveRecord::Base - self.table_name = "PRODUCT" +class Product < ApplicationRecord + self.table_name = "my_products" end ``` If you do so, you will have to define manually the class name that is hosting -the fixtures (class_name.yml) using the `set_fixture_class` method in your test +the fixtures (my_products.yml) using the `set_fixture_class` method in your test definition: ```ruby -class FunnyJoke < ActiveSupport::TestCase - set_fixture_class funny_jokes: Joke - fixtures :funny_jokes +class ProductTest < ActiveSupport::TestCase + set_fixture_class my_products: Product + fixtures :my_products ... end ``` @@ -191,7 +194,7 @@ It's also possible to override the column that should be used as the table's primary key using the `ActiveRecord::Base.primary_key=` method: ```ruby -class Product < ActiveRecord::Base +class Product < ApplicationRecord self.primary_key = "product_id" end ``` @@ -258,7 +261,7 @@ david = User.find_by(name: 'David') ```ruby # find all users named David who are Code Artists and sort by created_at in reverse chronological order -users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC') +users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc) ``` You can learn more about querying an Active Record model in the [Active Record @@ -318,7 +321,7 @@ they raise the exception `ActiveRecord::RecordInvalid` if validation fails. A quick example to illustrate: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord validates :name, presence: true end @@ -348,7 +351,7 @@ database that Active Record supports using `rake`. Here's a migration that creates a table: ```ruby -class CreatePublications < ActiveRecord::Migration +class CreatePublications < ActiveRecord::Migration[5.0] def change create_table :publications do |t| t.string :title @@ -358,7 +361,7 @@ class CreatePublications < ActiveRecord::Migration t.string :publisher_type t.boolean :single_issue - t.timestamps null: false + t.timestamps end add_index :publications, :publication_type_id end @@ -366,8 +369,8 @@ end ``` Rails keeps track of which files have been committed to the database and -provides rollback features. To actually create the table, you'd run `rake db:migrate` -and to roll it back, `rake db:rollback`. +provides rollback features. To actually create the table, you'd run `rails db:migrate` +and to roll it back, `rails db:rollback`. Note that the above code is database-agnostic: it will run in MySQL, PostgreSQL, Oracle and others. You can learn more about migrations in the diff --git a/source/active_record_callbacks.md b/source/active_record_callbacks.md index 9c7e60c..fb5d206 100644 --- a/source/active_record_callbacks.md +++ b/source/active_record_callbacks.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Callbacks ======================= @@ -29,7 +31,7 @@ Callbacks are methods that get called at certain moments of an object's life cyc In order to use the available callbacks, you need to register them. You can implement the callbacks as ordinary methods and use a macro-style class method to register them as callbacks: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord validates :login, :email, presence: true before_validation :ensure_login_has_a_value @@ -46,7 +48,7 @@ end The macro-style class methods can also receive a block. Consider using this style if the code inside your block is so short that it fits in a single line: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord validates :login, :email, presence: true before_create do @@ -58,7 +60,7 @@ end Callbacks can also be registered to only fire on certain life cycle events: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord before_validation :normalize_name, on: :create # :on takes an array as well @@ -66,7 +68,7 @@ class User < ActiveRecord::Base protected def normalize_name - self.name = self.name.downcase.titleize + self.name = name.downcase.titleize end def set_location @@ -124,7 +126,7 @@ The `after_find` callback will be called whenever Active Record loads a record f The `after_initialize` and `after_find` callbacks have no `before_*` counterparts, but they can be registered just like the other Active Record callbacks. ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord after_initialize do |user| puts "You have initialized an object!" end @@ -149,7 +151,7 @@ You have initialized an object! The `after_touch` callback will be called whenever an Active Record object is touched. ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord after_touch do |user| puts "You have touched an object" end @@ -166,14 +168,14 @@ You have touched an object It can be used along with `belongs_to`: ```ruby -class Employee < ActiveRecord::Base +class Employee < ApplicationRecord belongs_to :company, touch: true after_touch do puts 'An Employee was touched' end end -class Company < ActiveRecord::Base +class Company < ApplicationRecord has_many :employees after_touch :log_when_employees_or_company_touched @@ -256,7 +258,7 @@ As you start registering new callbacks for your models, they will be queued for The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception. -WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception. +WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception. Relational Callbacks -------------------- @@ -264,11 +266,11 @@ Relational Callbacks Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many articles. A user's articles should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Article` model: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_many :articles, dependent: :destroy end -class Article < ActiveRecord::Base +class Article < ApplicationRecord after_destroy :log_destroy_action def log_destroy_action @@ -295,7 +297,7 @@ As with validations, we can also make the calling of a callback method condition You can associate the `:if` and `:unless` options with a symbol corresponding to the name of a predicate method that will get called right before the callback. When using the `:if` option, the callback won't be executed if the predicate method returns false; when using the `:unless` option, the callback won't be executed if the predicate method returns true. This is the most common option. Using this form of registration it is also possible to register several different predicates that should be called to check if the callback should be executed. ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord before_save :normalize_card_number, if: :paid_with_card? end ``` @@ -305,7 +307,7 @@ end You can also use a string that will be evaluated using `eval` and hence needs to contain valid Ruby code. You should use this option only when the string represents a really short condition: ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord before_save :normalize_card_number, if: "paid_with_card?" end ``` @@ -315,7 +317,7 @@ end Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners: ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord before_save :normalize_card_number, if: Proc.new { |order| order.paid_with_card? } end @@ -326,7 +328,7 @@ end When writing conditional callbacks, it is possible to mix both `:if` and `:unless` in the same callback declaration: ```ruby -class Comment < ActiveRecord::Base +class Comment < ApplicationRecord after_create :send_email_to_author, if: :author_wants_emails?, unless: Proc.new { |comment| comment.article.ignore_comments? } end @@ -352,7 +354,7 @@ end When declared inside a class, as above, the callback methods will receive the model object as a parameter. We can now use the callback class in the model: ```ruby -class PictureFile < ActiveRecord::Base +class PictureFile < ApplicationRecord after_destroy PictureFileCallbacks.new end ``` @@ -372,7 +374,7 @@ end If the callback method is declared this way, it won't be necessary to instantiate a `PictureFileCallbacks` object. ```ruby -class PictureFile < ActiveRecord::Base +class PictureFile < ApplicationRecord after_destroy PictureFileCallbacks end ``` @@ -396,7 +398,7 @@ end By using the `after_commit` callback we can account for this case. ```ruby -class PictureFile < ActiveRecord::Base +class PictureFile < ApplicationRecord after_commit :delete_picture_file_from_disk, on: [:destroy] def delete_picture_file_from_disk @@ -410,4 +412,23 @@ end NOTE: the `:on` option specifies when a callback will be fired. If you don't supply the `:on` option the callback will fire for every action. +Since using `after_commit` callback only on create, update or delete is +common, there are aliases for those operations: + +* `after_create_commit` +* `after_update_commit` +* `after_destroy_commit` + +```ruby +class PictureFile < ApplicationRecord + after_destroy_commit :delete_picture_file_from_disk + + def delete_picture_file_from_disk + if File.exist?(filepath) + File.delete(filepath) + end + end +end +``` + WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. diff --git a/source/active_record_migrations.md b/source/active_record_migrations.md index e76a57e..37db576 100644 --- a/source/active_record_migrations.md +++ b/source/active_record_migrations.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Migrations ======================== @@ -10,7 +12,7 @@ After reading this guide, you will know: * The generators you can use to create them. * The methods Active Record provides to manipulate your database. -* The Rake tasks that manipulate migrations and your schema. +* The bin/rails tasks that manipulate migrations and your schema. * How migrations relate to `schema.rb`. -------------------------------------------------------------------------------- @@ -33,13 +35,13 @@ history to the latest version. Active Record will also update your Here's an example of a migration: ```ruby -class CreateProducts < ActiveRecord::Migration +class CreateProducts < ActiveRecord::Migration[5.0] def change create_table :products do |t| t.string :name t.text :description - t.timestamps null: false + t.timestamps end end end @@ -70,7 +72,7 @@ If you wish for a migration to do something that Active Record doesn't know how to reverse, you can use `reversible`: ```ruby -class ChangeProductsPrice < ActiveRecord::Migration +class ChangeProductsPrice < ActiveRecord::Migration[5.0] def change reversible do |dir| change_table :products do |t| @@ -85,7 +87,7 @@ end Alternatively, you can use `up` and `down` instead of `change`: ```ruby -class ChangeProductsPrice < ActiveRecord::Migration +class ChangeProductsPrice < ActiveRecord::Migration[5.0] def up change_table :products do |t| t.change :price, :string @@ -127,7 +129,7 @@ $ bin/rails generate migration AddPartNumberToProducts This will create an empty but appropriately named migration: ```ruby -class AddPartNumberToProducts < ActiveRecord::Migration +class AddPartNumberToProducts < ActiveRecord::Migration[5.0] def change end end @@ -144,7 +146,7 @@ $ bin/rails generate migration AddPartNumberToProducts part_number:string will generate ```ruby -class AddPartNumberToProducts < ActiveRecord::Migration +class AddPartNumberToProducts < ActiveRecord::Migration[5.0] def change add_column :products, :part_number, :string end @@ -160,7 +162,7 @@ $ bin/rails generate migration AddPartNumberToProducts part_number:string:index will generate ```ruby -class AddPartNumberToProducts < ActiveRecord::Migration +class AddPartNumberToProducts < ActiveRecord::Migration[5.0] def change add_column :products, :part_number, :string add_index :products, :part_number @@ -178,7 +180,7 @@ $ bin/rails generate migration RemovePartNumberFromProducts part_number:string generates ```ruby -class RemovePartNumberFromProducts < ActiveRecord::Migration +class RemovePartNumberFromProducts < ActiveRecord::Migration[5.0] def change remove_column :products, :part_number, :string end @@ -194,7 +196,7 @@ $ bin/rails generate migration AddDetailsToProducts part_number:string price:dec generates ```ruby -class AddDetailsToProducts < ActiveRecord::Migration +class AddDetailsToProducts < ActiveRecord::Migration[5.0] def change add_column :products, :part_number, :string add_column :products, :price, :decimal @@ -213,7 +215,7 @@ $ bin/rails generate migration CreateProducts name:string part_number:string generates ```ruby -class CreateProducts < ActiveRecord::Migration +class CreateProducts < ActiveRecord::Migration[5.0] def change create_table :products do |t| t.string :name @@ -237,14 +239,15 @@ $ bin/rails generate migration AddUserRefToProducts user:references generates ```ruby -class AddUserRefToProducts < ActiveRecord::Migration +class AddUserRefToProducts < ActiveRecord::Migration[5.0] def change - add_reference :products, :user, index: true + add_reference :products, :user, index: true, foreign_key: true end end ``` This migration will create a `user_id` column and appropriate index. +For more `add_reference` options, visit the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference). There is also a generator which will produce join tables if `JoinTable` is part of the name: @@ -255,7 +258,7 @@ $ bin/rails g migration CreateJoinTableCustomerProduct customer product will produce the following migration: ```ruby -class CreateJoinTableCustomerProduct < ActiveRecord::Migration +class CreateJoinTableCustomerProduct < ActiveRecord::Migration[5.0] def change create_join_table :customers, :products do |t| # t.index [:customer_id, :product_id] @@ -279,13 +282,13 @@ $ bin/rails generate model Product name:string description:text will create a migration that looks like this ```ruby -class CreateProducts < ActiveRecord::Migration +class CreateProducts < ActiveRecord::Migration[5.0] def change create_table :products do |t| t.string :name t.text :description - t.timestamps null: false + t.timestamps end end end @@ -307,7 +310,7 @@ $ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplie will produce a migration that looks like this ```ruby -class AddDetailsToProducts < ActiveRecord::Migration +class AddDetailsToProducts < ActiveRecord::Migration[5.0] def change add_column :products, :price, :decimal, precision: 5, scale: 2 add_reference :products, :supplier, polymorphic: true, index: true @@ -351,12 +354,19 @@ end ``` will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table -(when using MySQL, the default is `ENGINE=InnoDB`). +(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`). + +Also you can pass the `:comment` option with any description for the table +that will be stored in database itself and can be viewed with database administration +tools, such as MySQL Workbench or PgAdmin III. It's highly recommended to specify +comments in migrations for applications with large databases as it helps people +to understand data model and generate documentation. +Currently only the MySQL and PostgreSQL adapters support comments. ### Creating a Join Table -Migration method `create_join_table` creates a HABTM join table. A typical use -would be: +The migration method `create_join_table` creates an HABTM (has and belongs to +many) join table. A typical use would be: ```ruby create_join_table :products, :categories @@ -365,23 +375,21 @@ create_join_table :products, :categories which creates a `categories_products` table with two columns called `category_id` and `product_id`. These columns have the option `:null` set to `false` by default. This can be overridden by specifying the `:column_options` -option. +option: ```ruby -create_join_table :products, :categories, column_options: {null: true} +create_join_table :products, :categories, column_options: { null: true } ``` -will create the `product_id` and `category_id` with the `:null` option as -`true`. - -You can pass the option `:table_name` when you want to customize the table -name. For example: +By default, the name of the join table comes from the union of the first two +arguments provided to create_join_table, in alphabetical order. +To customize the name of the table, provide a `:table_name` option: ```ruby create_join_table :products, :categories, table_name: :categorization ``` -will create a `categorization` table. +creates a `categorization` table. `create_join_table` also accepts a block, which you can use to add indices (which are not created by default) or additional columns: @@ -421,21 +429,23 @@ change_column :products, :part_number, :text ``` This changes the column `part_number` on products table to be a `:text` field. +Note that `change_column` command is irreversible. Besides `change_column`, the `change_column_null` and `change_column_default` -methods are used specifically to change the null and default values of a -column. +methods are used specifically to change a not null constraint and default +values of a column. ```ruby change_column_null :products, :name, false -change_column_default :products, :approved, false +change_column_default :products, :approved, from: true, to: false ``` This sets `:name` field on products to a `NOT NULL` column and the default -value of the `:approved` field to false. +value of the `:approved` field from true to false. -TIP: Unlike `change_column` (and `change_column_default`), `change_column_null` -is reversible. +Note: You could also write the above `change_column_default` migration as +`change_column_default :products, :approved, false`, but unlike the previous +example, this would make your migration irreversible. ### Column Modifiers @@ -452,12 +462,13 @@ number of digits after the decimal point. are using a dynamic value (such as a date), the default will only be calculated the first time (i.e. on the date the migration is applied). * `index` Adds an index for the column. -* `required` Adds `required: true` for `belongs_to` associations and -`null: false` to the column in the migration. +* `comment` Adds a comment for the column. Some adapters may support additional options; see the adapter specific API docs for further information. +NOTE: `null` and `default` cannot be specified via command line. + ### Foreign Keys While it's not required you might want to add foreign key constraints to @@ -473,11 +484,13 @@ column names can not be derived from the table names, you can use the `:column` and `:primary_key` options. Rails will generate a name for every foreign key starting with -`fk_rails_` followed by 10 random characters. +`fk_rails_` followed by 10 characters which are deterministically +generated from the `from_table` and `column`. There is a `:name` option to specify a different name if needed. NOTE: Active Record only supports single column foreign keys. `execute` and -`structure.sql` are required to use composite foreign keys. +`structure.sql` are required to use composite foreign keys. See +[Schema Dumping and You](#schema-dumping-and-you). Removing a foreign key is easy as well: @@ -498,7 +511,7 @@ If the helpers provided by Active Record aren't enough you can use the `execute` method to execute arbitrary SQL: ```ruby -Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1') +Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1") ``` For more details and examples of individual methods, check the API documentation. @@ -518,24 +531,39 @@ majority of cases, where Active Record knows how to reverse the migration automatically. Currently, the `change` method supports only these migration definitions: -* `add_column` -* `add_index` -* `add_reference` -* `add_timestamps` -* `add_foreign_key` -* `create_table` -* `create_join_table` -* `drop_table` (must supply a block) -* `drop_join_table` (must supply a block) -* `remove_timestamps` -* `rename_column` -* `rename_index` -* `remove_reference` -* `rename_table` +* add_column +* add_foreign_key +* add_index +* add_reference +* add_timestamps +* change_column_default (must supply a :from and :to option) +* change_column_null +* create_join_table +* create_table +* disable_extension +* drop_join_table +* drop_table (must supply a block) +* enable_extension +* remove_column (must supply a type) +* remove_foreign_key (must supply a second table) +* remove_index +* remove_reference +* remove_timestamps +* rename_column +* rename_index +* rename_table `change_table` is also reversible, as long as the block does not call `change`, `change_default` or `remove`. +`remove_column` is reversible if you supply the column type as the third +argument. Provide the original column options too, otherwise Rails can't +recreate the column exactly when rolling back: + +```ruby +remove_column :posts, :slug, :string, null: false, default: '', index: true +``` + If you're going to need to use any other methods, you should use `reversible` or write the `up` and `down` methods instead of using the `change` method. @@ -543,10 +571,10 @@ or write the `up` and `down` methods instead of using the `change` method. Complex migrations may require processing that Active Record doesn't know how to reverse. You can use `reversible` to specify what to do when running a -migration what else to do when reverting it. For example: +migration and what else to do when reverting it. For example: ```ruby -class ExampleMigration < ActiveRecord::Migration +class ExampleMigration < ActiveRecord::Migration[5.0] def change create_table :distributors do |t| t.string :zipcode @@ -595,11 +623,11 @@ schema, and the `down` method of your migration should revert the transformations done by the `up` method. In other words, the database schema should be unchanged if you do an `up` followed by a `down`. For example, if you create a table in the `up` method, you should drop it in the `down` method. It -is wise to reverse the transformations in precisely the reverse order they were +is wise to perform the transformations in precisely the reverse order they were made in the `up` method. The example in the `reversible` section is equivalent to: ```ruby -class ExampleMigration < ActiveRecord::Migration +class ExampleMigration < ActiveRecord::Migration[5.0] def up create_table :distributors do |t| t.string :zipcode @@ -640,9 +668,9 @@ can't be done. You can use Active Record's ability to rollback migrations using the `revert` method: ```ruby -require_relative '2012121212_example_migration' +require_relative '20121212123456_example_migration' -class FixupExampleMigration < ActiveRecord::Migration +class FixupExampleMigration < ActiveRecord::Migration[5.0] def change revert ExampleMigration @@ -660,7 +688,7 @@ is later decided it would be best to use Active Record validations, in place of the `CHECK` constraint, to verify the zipcode. ```ruby -class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration +class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration[5.0] def change revert do # copy-pasted code from ExampleMigration @@ -693,13 +721,17 @@ of `create_table` and `reversible`, replacing `create_table` by `drop_table`, and finally replacing `up` by `down` and vice-versa. This is all taken care of by `revert`. +NOTE: If you want to add check constraints like in the examples above, +you will have to use `structure.sql` as dump method. See +[Schema Dumping and You](#schema-dumping-and-you). + Running Migrations ------------------ -Rails provides a set of Rake tasks to run certain sets of migrations. +Rails provides a set of bin/rails tasks to run certain sets of migrations. -The very first migration related Rake task you will use will probably be -`rake db:migrate`. In its most basic form it just runs the `change` or `up` +The very first migration related bin/rails task you will use will probably be +`rails db:migrate`. In its most basic form it just runs the `change` or `up` method for all the migrations that have not yet been run. If there are no such migrations, it exits. It will run these migrations in order based on the date of the migration. @@ -713,7 +745,7 @@ is the numerical prefix on the migration's filename. For example, to migrate to version 20080906120000 run: ```bash -$ bin/rake db:migrate VERSION=20080906120000 +$ bin/rails db:migrate VERSION=20080906120000 ``` If version 20080906120000 is greater than the current version (i.e., it is @@ -730,7 +762,7 @@ mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run: ```bash -$ bin/rake db:rollback +$ bin/rails db:rollback ``` This will rollback the latest migration, either by reverting the `change` @@ -738,7 +770,7 @@ method or by running the `down` method. If you need to undo several migrations you can provide a `STEP` parameter: ```bash -$ bin/rake db:rollback STEP=3 +$ bin/rails db:rollback STEP=3 ``` will revert the last 3 migrations. @@ -748,26 +780,26 @@ back up again. As with the `db:rollback` task, you can use the `STEP` parameter if you need to go more than one version back, for example: ```bash -$ bin/rake db:migrate:redo STEP=3 +$ bin/rails db:migrate:redo STEP=3 ``` -Neither of these Rake tasks do anything you could not do with `db:migrate`. They +Neither of these bin/rails tasks do anything you could not do with `db:migrate`. They are simply more convenient, since you do not need to explicitly specify the version to migrate to. ### Setup the Database -The `rake db:setup` task will create the database, load the schema and initialize +The `rails db:setup` task will create the database, load the schema and initialize it with the seed data. ### Resetting the Database -The `rake db:reset` task will drop the database and set it up again. This is -functionally equivalent to `rake db:drop db:setup`. +The `rails db:reset` task will drop the database and set it up again. This is +functionally equivalent to `rails db:drop db:setup`. NOTE: This is not the same as running all the migrations. It will only use the -contents of the current `schema.rb` file. If a migration can't be rolled back, -`rake db:reset` may not help you. To find out more about dumping the schema see +contents of the current `db/schema.rb` or `db/structure.sql` file. If a migration can't be rolled back, +`rails db:reset` may not help you. To find out more about dumping the schema see [Schema Dumping and You](#schema-dumping-and-you) section. ### Running Specific Migrations @@ -778,7 +810,7 @@ the corresponding migration will have its `change`, `up` or `down` method invoked, for example: ```bash -$ bin/rake db:migrate:up VERSION=20080906120000 +$ bin/rails db:migrate:up VERSION=20080906120000 ``` will run the 20080906120000 migration by running the `change` method (or the @@ -788,13 +820,13 @@ Active Record believes that it has already been run. ### Running Migrations in Different Environments -By default running `rake db:migrate` will run in the `development` environment. +By default running `bin/rails db:migrate` will run in the `development` environment. To run migrations against another environment you can specify it using the `RAILS_ENV` environment variable while running the command. For example to run migrations against the `test` environment you could run: ```bash -$ bin/rake db:migrate RAILS_ENV=test +$ bin/rails db:migrate RAILS_ENV=test ``` ### Changing the Output of Running Migrations @@ -820,13 +852,13 @@ Several methods are provided in migrations that allow you to control all this: For example, this migration: ```ruby -class CreateProducts < ActiveRecord::Migration +class CreateProducts < ActiveRecord::Migration[5.0] def change suppress_messages do create_table :products do |t| t.string :name t.text :description - t.timestamps null: false + t.timestamps end end @@ -855,18 +887,18 @@ generates the following output == CreateProducts: migrated (10.0054s) ======================================= ``` -If you want Active Record to not output anything, then running `rake db:migrate +If you want Active Record to not output anything, then running `rails db:migrate VERBOSE=false` will suppress all output. Changing Existing Migrations ---------------------------- Occasionally you will make a mistake when writing a migration. If you have -already run the migration then you cannot just edit the migration and run the +already run the migration, then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do -nothing when you run `rake db:migrate`. You must rollback the migration (for -example with `rake db:rollback`), edit your migration and then run -`rake db:migrate` to run the corrected version. +nothing when you run `rails db:migrate`. You must rollback the migration (for +example with `bin/rails db:rollback`), edit your migration and then run +`rails db:migrate` to run the corrected version. In general, editing existing migrations is not a good idea. You will be creating extra work for yourself and your co-workers and cause major headaches @@ -912,7 +944,7 @@ There are two ways to dump the schema. This is set in `config/application.rb` by the `config.active_record.schema_format` setting, which may be either `:sql` or `:ruby`. -If `:ruby` is selected then the schema is stored in `db/schema.rb`. If you look +If `:ruby` is selected, then the schema is stored in `db/schema.rb`. If you look at this file you'll find that it looks an awful lot like one very big migration: @@ -941,15 +973,15 @@ that Active Record supports. This could be very useful if you were to distribute an application that is able to run against multiple databases. There is however a trade-off: `db/schema.rb` cannot express database specific -items such as triggers, or stored procedures. While in a migration you can -execute custom SQL statements, the schema dumper cannot reconstitute those -statements from the database. If you are using features like this, then you -should set the schema format to `:sql`. +items such as triggers, stored procedures or check constraints. While in a +migration you can execute custom SQL statements, the schema dumper cannot +reconstitute those statements from the database. If you are using features like +this, then you should set the schema format to `:sql`. Instead of using Active Record's schema dumper, the database's structure will be dumped using a tool specific to the database (via the `db:structure:dump` -Rake task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump` -utility is used. For MySQL, this file will contain the output of +rails task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump` +utility is used. For MySQL and MariaDB, this file will contain the output of `SHOW CREATE TABLE` for the various tables. Loading these schemas is simply a question of executing the SQL statements they @@ -988,10 +1020,13 @@ such features, the `execute` method can be used to execute arbitrary SQL. Migrations and Seed Data ------------------------ -Some people use migrations to add data to the database: +The main purpose of Rails' migration feature is to issue commands that modify the +schema using a consistent process. Migrations can also be used +to add or modify data. This is useful in an existing database that can't be destroyed +and recreated, such as a production database. ```ruby -class AddInitialProducts < ActiveRecord::Migration +class AddInitialProducts < ActiveRecord::Migration[5.0] def up 5.times do |i| Product.create(name: "Product ##{i}", description: "A product.") @@ -1004,9 +1039,11 @@ class AddInitialProducts < ActiveRecord::Migration end ``` -However, Rails has a 'seeds' feature that should be used for seeding a database -with initial data. It's a really simple feature: just fill up `db/seeds.rb` -with some Ruby code, and run `rake db:seed`: +To add initial data after a database is created, Rails has a built-in +'seeds' feature that makes the process quick and easy. This is especially +useful when reloading the database frequently in development and test environments. +It's easy to get started with this feature: just fill up `db/seeds.rb` with some +Ruby code, and run `rails db:seed`: ```ruby 5.times do |i| diff --git a/source/active_record_postgresql.md b/source/active_record_postgresql.md index 446be1c..5eb19f5 100644 --- a/source/active_record_postgresql.md +++ b/source/active_record_postgresql.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record and PostgreSQL ============================ @@ -12,7 +14,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -In order to use the PostgreSQL adapter you need to have at least version 8.2 +In order to use the PostgreSQL adapter you need to have at least version 9.1 installed. Older versions are not supported. To get started with PostgreSQL have a look at the @@ -27,8 +29,8 @@ that are supported by the PostgreSQL adapter. ### Bytea -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-binary.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-binarystring.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-binary.html) +* [functions and operators](http://www.postgresql.org/docs/current/static/functions-binarystring.html) ```ruby # db/migrate/20140207133952_create_documents.rb @@ -37,7 +39,7 @@ create_table :documents do |t| end # app/models/document.rb -class Document < ActiveRecord::Base +class Document < ApplicationRecord end # Usage @@ -47,8 +49,8 @@ Document.create payload: data ### Array -* [type definition](http://www.postgresql.org/docs/9.3/static/arrays.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-array.html) +* [type definition](http://www.postgresql.org/docs/current/static/arrays.html) +* [functions and operators](http://www.postgresql.org/docs/current/static/functions-array.html) ```ruby # db/migrate/20140207133952_create_books.rb @@ -61,7 +63,7 @@ add_index :books, :tags, using: 'gin' add_index :books, :ratings, using: 'gin' # app/models/book.rb -class Book < ActiveRecord::Base +class Book < ApplicationRecord end # Usage @@ -81,9 +83,10 @@ Book.where("array_length(ratings, 1) >= 3") ### Hstore -* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html) +* [type definition](http://www.postgresql.org/docs/current/static/hstore.html) +* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN167712) -NOTE: you need to enable the `hstore` extension to use hstore. +NOTE: You need to enable the `hstore` extension to use hstore. ```ruby # db/migrate/20131009135255_create_profiles.rb @@ -95,7 +98,7 @@ ActiveRecord::Schema.define do end # app/models/profile.rb -class Profile < ActiveRecord::Base +class Profile < ApplicationRecord end # Usage @@ -106,12 +109,15 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"} profile.settings = {"color" => "yellow", "resolution" => "1280x1024"} profile.save! + +Profile.where("settings->'color' = ?", "yellow") +#=> #"yellow", "resolution"=>"1280x1024"}>]> ``` ### JSON -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-json.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-json.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-json.html) +* [functions and operators](http://www.postgresql.org/docs/current/static/functions-json.html) ```ruby # db/migrate/20131220144913_create_events.rb @@ -120,7 +126,7 @@ create_table :events do |t| end # app/models/event.rb -class Event < ActiveRecord::Base +class Event < ApplicationRecord end # Usage @@ -136,10 +142,10 @@ Event.where("payload->>'kind' = ?", "user_renamed") ### Range Types -* [type definition](http://www.postgresql.org/docs/9.3/static/rangetypes.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-range.html) +* [type definition](http://www.postgresql.org/docs/current/static/rangetypes.html) +* [functions and operators](http://www.postgresql.org/docs/current/static/functions-range.html) -This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.1.1/Range.html) objects. +This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.2.2/Range.html) objects. ```ruby # db/migrate/20130923065404_create_events.rb @@ -148,7 +154,7 @@ create_table :events do |t| end # app/models/event.rb -class Event < ActiveRecord::Base +class Event < ApplicationRecord end # Usage @@ -171,7 +177,7 @@ event.ends_at # => Thu, 13 Feb 2014 ### Composite Types -* [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html) +* [type definition](http://www.postgresql.org/docs/current/static/rowtypes.html) Currently there is no special support for composite types. They are mapped to normal text columns: @@ -198,7 +204,7 @@ create_table :contacts do |t| end # app/models/contact.rb -class Contact < ActiveRecord::Base +class Contact < ApplicationRecord end # Usage @@ -211,22 +217,33 @@ contact.save! ### Enumerated Types -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-enum.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-enum.html) Currently there is no special support for enumerated types. They are mapped as normal text columns: ```ruby # db/migrate/20131220144913_create_articles.rb -execute <<-SQL - CREATE TYPE article_status AS ENUM ('draft', 'published'); -SQL -create_table :articles do |t| - t.column :status, :article_status +def up + execute <<-SQL + CREATE TYPE article_status AS ENUM ('draft', 'published'); + SQL + create_table :articles do |t| + t.column :status, :article_status + end +end + +# NOTE: It's important to drop table before dropping enum. +def down + drop_table :articles + + execute <<-SQL + DROP TYPE article_status; + SQL end # app/models/article.rb -class Article < ActiveRecord::Base +class Article < ApplicationRecord end # Usage @@ -238,20 +255,50 @@ article.status = "published" article.save! ``` +To add a new value before/after existing one you should use [ALTER TYPE](http://www.postgresql.org/docs/current/static/sql-altertype.html): + +```ruby +# db/migrate/20150720144913_add_new_state_to_articles.rb +# NOTE: ALTER TYPE ... ADD VALUE cannot be executed inside of a transaction block so here we are using disable_ddl_transaction! +disable_ddl_transaction! + +def up + execute <<-SQL + ALTER TYPE article_status ADD VALUE IF NOT EXISTS 'archived' AFTER 'published'; + SQL +end +``` + +NOTE: ENUM values can't be dropped currently. You can read why [here](http://www.postgresql.org/message-id/29F36C7C98AB09499B1A209D48EAA615B7653DBC8A@mail2a.alliedtesting.com). + +Hint: to show all the values of the all enums you have, you should call this query in `bin/rails db` or `psql` console: + +```sql +SELECT n.nspname AS enum_schema, + t.typname AS enum_name, + e.enumlabel AS enum_value + FROM pg_type t + JOIN pg_enum e ON t.oid = e.enumtypid + JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace +``` + ### UUID -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html) -* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-uuid.html) +* [pgcrypto generator function](http://www.postgresql.org/docs/current/static/pgcrypto.html#AEN159361) +* [uuid-ossp generator functions](http://www.postgresql.org/docs/current/static/uuid-ossp.html) +NOTE: You need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp` +extension to use uuid. ```ruby # db/migrate/20131220144913_create_revisions.rb create_table :revisions do |t| - t.column :identifier, :uuid + t.uuid :identifier end # app/models/revision.rb -class Revision < ActiveRecord::Base +class Revision < ApplicationRecord end # Usage @@ -261,10 +308,35 @@ revision = Revision.first revision.identifier # => "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" ``` +You can use `uuid` type to define references in migrations: + +```ruby +# db/migrate/20150418012400_create_blog.rb +enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') +create_table :posts, id: :uuid, default: 'gen_random_uuid()' + +create_table :comments, id: :uuid, default: 'gen_random_uuid()' do |t| + # t.belongs_to :post, type: :uuid + t.references :post, type: :uuid +end + +# app/models/post.rb +class Post < ApplicationRecord + has_many :comments +end + +# app/models/comment.rb +class Comment < ApplicationRecord + belongs_to :post +end +``` + +See [this section](#uuid-primary-keys) for more details on using UUIDs as primary key. + ### Bit String Types -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-bit.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-bitstring.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-bit.html) +* [functions and operators](http://www.postgresql.org/docs/current/static/functions-bitstring.html) ```ruby # db/migrate/20131220144913_create_users.rb @@ -273,7 +345,7 @@ create_table :users, force: true do |t| end # app/models/device.rb -class User < ActiveRecord::Base +class User < ApplicationRecord end # Usage @@ -287,10 +359,10 @@ user.save! ### Network Address Types -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-net-types.html) The types `inet` and `cidr` are mapped to Ruby -[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html) +[`IPAddr`](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/ipaddr/rdoc/IPAddr.html) objects. The `macaddr` type is mapped to normal text. ```ruby @@ -302,7 +374,7 @@ create_table(:devices, force: true) do |t| end # app/models/device.rb -class Device < ActiveRecord::Base +class Device < ApplicationRecord end # Usage @@ -322,7 +394,7 @@ macbook.address ### Geometric Types -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-geometric.html) +* [type definition](http://www.postgresql.org/docs/current/static/datatype-geometric.html) All geometric types, with the exception of `points` are mapped to normal text. A point is casted to an array containing `x` and `y` coordinates. @@ -331,17 +403,18 @@ A point is casted to an array containing `x` and `y` coordinates. UUID Primary Keys ----------------- -NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs. +NOTE: You need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp` +extension to generate random UUIDs. ```ruby # db/migrate/20131220144913_create_devices.rb -enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp') -create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t| +enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') +create_table :devices, id: :uuid, default: 'gen_random_uuid()' do |t| t.string :kind end # app/models/device.rb -class Device < ActiveRecord::Base +class Device < ApplicationRecord end # Usage @@ -349,6 +422,9 @@ device = Device.create device.id # => "814865cd-5a1d-4771-9306-4268f188fe9e" ``` +NOTE: `uuid_generate_v4()` (from `uuid-ossp`) is assumed if no `:default` option was +passed to `create_table`. + Full Text Search ---------------- @@ -362,7 +438,7 @@ end execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));" # app/models/document.rb -class Document < ActiveRecord::Base +class Document < ApplicationRecord end # Usage @@ -376,7 +452,7 @@ Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)", Database Views -------------- -* [view creation](http://www.postgresql.org/docs/9.3/static/sql-createview.html) +* [view creation](http://www.postgresql.org/docs/current/static/sql-createview.html) Imagine you need to work with a legacy database containing the following table: @@ -412,7 +488,7 @@ CREATE VIEW articles AS SQL # app/models/article.rb -class Article < ActiveRecord::Base +class Article < ApplicationRecord self.primary_key = "id" def archive! update_attribute :archived, true diff --git a/source/active_record_querying.md b/source/active_record_querying.md index 2e7bb74..928ab43 100644 --- a/source/active_record_querying.md +++ b/source/active_record_querying.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Query Interface ============================= @@ -8,8 +10,8 @@ After reading this guide, you will know: * How to find records using a variety of methods and conditions. * How to specify the order, retrieved attributes, grouping, and other properties of the found records. * How to use eager loading to reduce the number of database queries needed for data retrieval. -* How to use dynamic finders methods. -* How to use method chaining to use multiple ActiveRecord methods together. +* How to use dynamic finder methods. +* How to use method chaining to use multiple Active Record methods together. * How to check for the existence of particular records. * How to perform various calculations on Active Record models. * How to run EXPLAIN on relations. @@ -23,7 +25,7 @@ Code examples throughout this guide will refer to one or more of the following m TIP: All of the following models use `id` as the primary key, unless specified otherwise. ```ruby -class Client < ActiveRecord::Base +class Client < ApplicationRecord has_one :address has_many :orders has_and_belongs_to_many :roles @@ -31,24 +33,24 @@ end ``` ```ruby -class Address < ActiveRecord::Base +class Address < ApplicationRecord belongs_to :client end ``` ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord belongs_to :client, counter_cache: true end ``` ```ruby -class Role < ActiveRecord::Base +class Role < ApplicationRecord has_and_belongs_to_many :clients end ``` -Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. +Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same. Retrieving Objects from the Database ------------------------------------ @@ -57,7 +59,7 @@ To retrieve objects from the database, Active Record provides several finder met The methods are: -* `bind` +* `find` * `create_with` * `distinct` * `eager_load` @@ -67,6 +69,7 @@ The methods are: * `having` * `includes` * `joins` +* `left_outer_joins` * `limit` * `lock` * `none` @@ -78,7 +81,7 @@ The methods are: * `reorder` * `reverse_order` * `select` -* `uniq` +* `distinct` * `where` All of the above methods return an instance of `ActiveRecord::Relation`. @@ -88,7 +91,7 @@ The primary operation of `Model.find(options)` can be summarized as: * Convert the supplied options to an equivalent SQL query. * Fire the SQL query and retrieve the corresponding results from the database. * Instantiate the equivalent Ruby object of the appropriate model for every resulting row. -* Run `after_find` callbacks, if any. +* Run `after_find` and then `after_initialize` callbacks, if any. ### Retrieving a Single Object @@ -150,9 +153,9 @@ You can pass in a numerical argument to the `take` method to return up to that n ```ruby client = Client.take(2) # => [ - #, - # -] +# #, +# # +# ] ``` The SQL equivalent of the above is: @@ -167,7 +170,7 @@ TIP: The retrieved record may vary depending on the database engine. #### `first` -The `first` method finds the first record ordered by the primary key. For example: +The `first` method finds the first record ordered by primary key (default). For example: ```ruby client = Client.first @@ -182,15 +185,17 @@ SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 The `first` method returns `nil` if no matching record is found and no exception will be raised. +If your [default scope](active_record_querying.html#applying-a-default-scope) contains an order method, `first` will return the first record according to this ordering. + You can pass in a numerical argument to the `first` method to return up to that number of results. For example ```ruby client = Client.first(3) # => [ - #, - #, - # -] +# #, +# #, +# # +# ] ``` The SQL equivalent of the above is: @@ -199,11 +204,24 @@ The SQL equivalent of the above is: SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3 ``` +On a collection that is ordered using `order`, `first` will return the first record ordered by the specified attribute for `order`. + +```ruby +client = Client.order(:first_name).first +# => # +``` + +The SQL equivalent of the above is: + +```sql +SELECT * FROM clients ORDER BY clients.first_name ASC LIMIT 1 +``` + The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. #### `last` -The `last` method finds the last record ordered by the primary key. For example: +The `last` method finds the last record ordered by primary key (default). For example: ```ruby client = Client.last @@ -218,15 +236,17 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 The `last` method returns `nil` if no matching record is found and no exception will be raised. +If your [default scope](active_record_querying.html#applying-a-default-scope) contains an order method, `last` will return the last record according to this ordering. + You can pass in a numerical argument to the `last` method to return up to that number of results. For example ```ruby client = Client.last(3) # => [ - #, - #, - # -] +# #, +# #, +# # +# ] ``` The SQL equivalent of the above is: @@ -235,6 +255,19 @@ The SQL equivalent of the above is: SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3 ``` +On a collection that is ordered using `order`, `last` will return the last record ordered by the specified attribute for `order`. + +```ruby +client = Client.order(:first_name).last +# => # +``` + +The SQL equivalent of the above is: + +```sql +SELECT * FROM clients ORDER BY clients.first_name DESC LIMIT 1 +``` + The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. #### `find_by` @@ -255,6 +288,12 @@ It is equivalent to writing: Client.where(first_name: 'Lifo').take ``` +The SQL equivalent of the above is: + +```sql +SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1 +``` + The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example: ```ruby @@ -309,7 +348,7 @@ end The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`. -Two additional options, `:batch_size` and `:start`, are available as well. +Three additional options, `:batch_size`, `:start` and `:finish`, are available as well. **`:batch_size`** @@ -333,7 +372,22 @@ User.find_each(start: 2000, batch_size: 5000) do |user| end ``` -Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:start` option on each worker. +**`:finish`** + +Similar to the `:start` option, `:finish` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need. +This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:start` and `:finish` + +For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000: + +```ruby +User.find_each(start: 2000, finish: 10000, batch_size: 5000) do |user| + NewsMailer.weekly(user).deliver_now +end +``` + +Another example would be if you wanted multiple workers handling the same +processing queue. You could have each worker handle 10000 records by setting the +appropriate `:start` and `:finish` options on each worker. #### `find_in_batches` @@ -348,7 +402,7 @@ end ##### Options for `find_in_batches` -The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`. +The `find_in_batches` method accepts the same `:batch_size`, `:start` and `:finish` options as `find_each`. Conditions ---------- @@ -369,7 +423,7 @@ Now what if that number could vary, say as an argument from somewhere? The find Client.where("orders_count = ?", params[:orders]) ``` -Active Record will go through the first element in the conditions value and any additional elements will replace the question marks `(?)` in the first element. +Active Record will take the first argument as the conditions string and any additional arguments will replace the question marks `(?)` in it. If you want to specify multiple conditions: @@ -397,7 +451,7 @@ TIP: For more information on the dangers of SQL injection, see the [Ruby on Rail #### Placeholder Conditions -Similar to the `(?)` replacement style of params, you can also specify keys/values hash in your array conditions: +Similar to the `(?)` replacement style of params, you can also specify keys in your conditions string along with a corresponding keys/values hash: ```ruby Client.where("created_at >= :start_date AND created_at <= :end_date", @@ -408,7 +462,7 @@ This makes for clearer readability if you have a large number of variable condit ### Hash Conditions -Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: +Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want qualified and the values of how you want to qualify them: NOTE: Only equality, range and subset checking are possible with Hash conditions. @@ -418,6 +472,12 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions Client.where(locked: true) ``` +This will generate SQL like this: + +```sql +SELECT * FROM clients WHERE (clients.locked = 1) +``` + The field name can also be a string: ```ruby @@ -463,13 +523,17 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) ### NOT Conditions -`NOT` SQL queries can be built by `where.not`. +`NOT` SQL queries can be built by `where.not`: ```ruby -Article.where.not(author: author) +Client.where.not(locked: true) ``` -In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. +In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. This will generate SQL like this: + +```sql +SELECT * FROM clients WHERE (clients.locked != 1) +``` Ordering -------- @@ -508,7 +572,7 @@ Client.order("orders_count ASC, created_at DESC") Client.order("orders_count ASC", "created_at DESC") ``` -If you want to call `order` multiple times e.g. in different context, new order will append previous one +If you want to call `order` multiple times, subsequent orders will be appended to the first: ```ruby Client.order("orders_count ASC").order("created_at DESC") @@ -596,9 +660,9 @@ SELECT * FROM clients LIMIT 5 OFFSET 30 Group ----- -To apply a `GROUP BY` clause to the SQL fired by the finder, you can specify the `group` method on the find. +To apply a `GROUP BY` clause to the SQL fired by the finder, you can use the `group` method. -For example, if you want to find a collection of the dates orders were created on: +For example, if you want to find a collection of the dates on which orders were created: ```ruby Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)") @@ -616,7 +680,7 @@ GROUP BY date(created_at) ### Total of grouped items -To get the total of grouped items on a single query call `count` after the `group`. +To get the total of grouped items on a single query, call `count` after the `group`. ```ruby Order.group(:status).count @@ -634,7 +698,7 @@ GROUP BY status Having ------ -SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You can add the `HAVING` clause to the SQL fired by the `Model.find` by adding the `:having` option to the find. +SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You can add the `HAVING` clause to the SQL fired by the `Model.find` by adding the `having` method to the find. For example: @@ -652,7 +716,7 @@ GROUP BY date(created_at) HAVING sum(price) > 100 ``` -This will return single order objects for each day, but only those that are ordered more than $100 in a day. +This returns the date and total price for each order object, grouped by the day they were ordered and where the price is more than $100. Overriding Conditions --------------------- @@ -682,8 +746,7 @@ Article.where(id: 10, trashed: false).unscope(where: :id) # SELECT "articles".* FROM "articles" WHERE trashed = 0 ``` -A relation which has used `unscope` will affect any relation it is -merged in to: +A relation which has used `unscope` will affect any relation into which it is merged: ```ruby Article.order('id asc').merge(Article.unscope(:order)) @@ -713,7 +776,7 @@ SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20 The `reorder` method overrides the default scope order. For example: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord has_many :comments, -> { order('posted_at DESC') } end @@ -727,7 +790,7 @@ SELECT * FROM articles WHERE id = 10 SELECT * FROM comments WHERE article_id = 10 ORDER BY name ``` -In case the `reorder` clause is not used, the SQL executed would be: +In the case where the `reorder` clause is not used, the SQL executed would be: ```sql SELECT * FROM articles WHERE id = 10 @@ -816,7 +879,7 @@ end Readonly Objects ---------------- -Active Record provides `readonly` method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an `ActiveRecord::ReadOnlyRecord` exception. +Active Record provides the `readonly` method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an `ActiveRecord::ReadOnlyRecord` exception. ```ruby client = Client.readonly.first @@ -862,7 +925,7 @@ This behavior can be turned off by setting `ActiveRecord::Base.lock_optimistical To override the name of the `lock_version` column, `ActiveRecord::Base` provides a class attribute called `locking_column`: ```ruby -class Client < ActiveRecord::Base +class Client < ApplicationRecord self.locking_column = :lock_client_column end ``` @@ -877,7 +940,7 @@ For example: Item.transaction do i = Item.lock.first i.name = 'Jones' - i.save + i.save! end ``` @@ -913,58 +976,63 @@ end Joining Tables -------------- -Active Record provides a finder method called `joins` for specifying `JOIN` clauses on the resulting SQL. There are multiple ways to use the `joins` method. +Active Record provides two finder methods for specifying `JOIN` clauses on the +resulting SQL: `joins` and `left_outer_joins`. +While `joins` should be used for `INNER JOIN` or custom queries, +`left_outer_joins` is used for queries using `LEFT OUTER JOIN`. + +### `joins` -### Using a String SQL Fragment +There are multiple ways to use the `joins` method. + +#### Using a String SQL Fragment You can just supply the raw SQL specifying the `JOIN` clause to `joins`: ```ruby -Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') +Author.joins("INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'") ``` This will result in the following SQL: ```sql -SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id +SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't' ``` -### Using Array/Hash of Named Associations - -WARNING: This method only works with `INNER JOIN`. +#### Using Array/Hash of Named Associations Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method. For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models: ```ruby -class Category < ActiveRecord::Base +class Category < ApplicationRecord has_many :articles end -class Article < ActiveRecord::Base +class Article < ApplicationRecord belongs_to :category has_many :comments has_many :tags end -class Comment < ActiveRecord::Base +class Comment < ApplicationRecord belongs_to :article has_one :guest end -class Guest < ActiveRecord::Base +class Guest < ApplicationRecord belongs_to :comment end -class Tag < ActiveRecord::Base +class Tag < ApplicationRecord belongs_to :article end ``` Now all of the following will produce the expected join queries using `INNER JOIN`: -#### Joining a Single Association +##### Joining a Single Association ```ruby Category.joins(:articles) @@ -977,7 +1045,7 @@ SELECT categories.* FROM categories INNER JOIN articles ON articles.category_id = categories.id ``` -Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`. +Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).distinct`. #### Joining Multiple Associations @@ -995,7 +1063,7 @@ SELECT articles.* FROM articles Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times. -#### Joining Nested Associations (Single Level) +##### Joining Nested Associations (Single Level) ```ruby Article.joins(comments: :guest) @@ -1011,7 +1079,7 @@ SELECT articles.* FROM articles Or, in English: "return all articles that have a comment made by a guest." -#### Joining Nested Associations (Multiple Level) +##### Joining Nested Associations (Multiple Level) ```ruby Category.joins(articles: [{ comments: :guest }, :tags]) @@ -1027,9 +1095,11 @@ SELECT categories.* FROM categories INNER JOIN tags ON tags.article_id = articles.id ``` -### Specifying Conditions on the Joined Tables +Or, in English: "return all categories that have articles, where those articles have a comment made by a guest, and where those articles also have a tag." + +#### Specifying Conditions on the Joined Tables -You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provides a special syntax for specifying conditions for the joined tables: +You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provide a special syntax for specifying conditions for the joined tables: ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight @@ -1045,6 +1115,26 @@ Client.joins(:orders).where(orders: { created_at: time_range }) This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression. +### `left_outer_joins` + +If you want to select a set of records whether or not they have associated +records you can use the `left_outer_joins` method. + +```ruby +Author.left_outer_joins(:posts).distinct.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id') +``` + +Which produces: + +```sql +SELECT DISTINCT authors.*, COUNT(posts.*) AS posts_count FROM "authors" +LEFT OUTER JOIN posts ON posts.author_id = authors.id GROUP BY authors.id +``` + +Which means: "return all authors with their count of posts, whether or not they +have any posts at all" + + Eager Loading Associations -------------------------- @@ -1068,7 +1158,7 @@ This code looks fine at the first sight. But the problem lies within the total n Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the `includes` method of the `Model.find` call. With `includes`, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries. -Revisiting the above case, we could rewrite `Client.limit(10)` to use eager load addresses: +Revisiting the above case, we could rewrite `Client.limit(10)` to eager load addresses: ```ruby clients = Client.includes(:address).limit(10) @@ -1126,7 +1216,7 @@ This would generate a query which contains a `LEFT OUTER JOIN` whereas the If there was no `where` condition, this would generate the normal set of two queries. NOTE: Using `where` like this will only work when you pass it a Hash. For -SQL-fragments you need use `references` to force joined tables: +SQL-fragments you need to use `references` to force joined tables: ```ruby Article.includes(:comments).where("comments.visible = true").references(:comments) @@ -1147,7 +1237,7 @@ Scoping allows you to specify commonly-used queries which can be referenced as m To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord scope :published, -> { where(published: true) } end ``` @@ -1155,7 +1245,7 @@ end This is exactly the same as defining a class method, and which you use is a matter of personal preference: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord def self.published where(published: true) end @@ -1165,7 +1255,7 @@ end Scopes are also chainable within scopes: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord scope :published, -> { where(published: true) } scope :published_and_commented, -> { published.where("comments_count > 0") } end @@ -1189,7 +1279,7 @@ category.articles.published # => [published articles belonging to this category] Your scope can take arguments: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord scope :created_before, ->(time) { where("created_at < ?", time) } end ``` @@ -1203,7 +1293,7 @@ Article.created_before(Time.zone.now) However, this is just duplicating the functionality that would be provided to you by a class method. ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord def self.created_before(time) where("created_at < ?", time) end @@ -1216,13 +1306,35 @@ Using a class method is the preferred way to accept arguments for scopes. These category.articles.created_before(time) ``` +### Using conditionals + +Your scope can utilize conditionals: + +```ruby +class Article < ApplicationRecord + scope :created_before, ->(time) { where("created_at < ?", time) if time.present? } +end +``` + +Like the other examples, this will behave similarly to a class method. + +```ruby +class Article < ApplicationRecord + def self.created_before(time) + where("created_at < ?", time) if time.present? + end +end +``` + +However, there is one important caveat: A scope will always return an `ActiveRecord::Relation` object, even if the conditional evaluates to `false`, whereas a class method, will return `nil`. This can cause `NoMethodError` when chaining class methods with conditionals, if any of the conditionals return `false`. + ### Applying a default scope If we wish for a scope to be applied across all queries to the model we can use the `default_scope` method within the model itself. ```ruby -class Client < ActiveRecord::Base +class Client < ApplicationRecord default_scope { where("removed_at IS NULL") } end ``` @@ -1238,19 +1350,31 @@ If you need to do more complex things with a default scope, you can alternativel define it as a class method: ```ruby -class Client < ActiveRecord::Base +class Client < ApplicationRecord def self.default_scope # Should return an ActiveRecord::Relation. end end ``` +NOTE: The `default_scope` is also applied while creating/building a record. +It is not applied while updating a record. E.g.: + +```ruby +class Client < ApplicationRecord + default_scope { where(active: true) } +end + +Client.new # => # +Client.unscoped.new # => # +``` + ### Merging of scopes Just like `where` clauses scopes are merged using `AND` conditions. ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end @@ -1267,7 +1391,7 @@ User.active.where(state: 'finished') # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' ``` -If we do want the `last where clause` to win then `Relation#merge` can +If we do want the last `where` clause to win then `Relation#merge` can be used. ```ruby @@ -1279,7 +1403,7 @@ One important caveat is that `default_scope` will be prepended in `scope` and `where` conditions. ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } @@ -1310,8 +1434,15 @@ Client.unscoped.load This method removes all scoping and will do a normal query on the table. -Note that chaining `unscoped` with a `scope` does not work. In these cases, it is -recommended that you use the block form of `unscoped`: +```ruby +Client.unscoped.all +# SELECT "clients".* FROM "clients" + +Client.where(published: false).unscoped.all +# SELECT "clients".* FROM "clients" +``` + +`unscoped` can also accept a block. ```ruby Client.unscoped { @@ -1322,26 +1453,56 @@ Client.unscoped { Dynamic Finders --------------- -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods. +For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` method. You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")` If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`. +Enums +----- + +The `enum` macro maps an integer column to a set of possible values. + +```ruby +class Book < ApplicationRecord + enum availability: [:available, :unavailable] +end +``` + +This will automatically create the corresponding [scopes](#scopes) to query the +model. Methods to transition between states and query the current state are also +added. + +```ruby +# Both examples below query just available books. +Book.available +# or +Book.where(availability: :available) + +book = Book.new(availability: :available) +book.available? # => true +book.unavailable! # => true +book.available? # => false +``` + +Read the full documentation about enums +[in the Rails API docs](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html). + Understanding The Method Chaining --------------------------------- The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining), which allow us to use multiple Active Record methods together in a simple and straightforward way. -You can chain a method in a sentence when the previous method called returns `ActiveRecord::Relation`, -like `all`, `where`, and `joins`. Methods that returns a instance of a single object -(see [Retrieving a Single Object Section](#retrieving-a-single-object)) have to be be the last -in the sentence. +You can chain methods in a statement when the previous method called returns an +`ActiveRecord::Relation`, like `all`, `where`, and `joins`. Methods that return +a single object (see [Retrieving a Single Object Section](#retrieving-a-single-object)) +have to be at the end of the statement. -There are some examples below. This guide won't cover all the possibilities, just a few as example. -When a Active Record method is called, the query is not immediately generated and sent to the database, -this just happen when the data is actually needed. So each example below generate a single query. +There are some examples below. This guide won't cover all the possibilities, just a few as examples. +When an Active Record method is called, the query is not immediately generated and sent to the database, +this just happens when the data is actually needed. So each example below generates a single query. ### Retrieving filtered data from multiple tables @@ -1382,22 +1543,18 @@ WHERE people.name = 'John' LIMIT 1 ``` -NOTE: Remember that, if `find_by` return more than one registry, it will take just the first -and ignore the others. Note the `LIMIT 1` statement above. +NOTE: Note that if a query matches multiple records, `find_by` will +fetch only the first one and ignore the others (see the `LIMIT 1` +statement above). Find or Build a New Object -------------------------- -NOTE: Some dynamic finders were deprecated in Rails 4.0 and -removed in Rails 4.1. The best practice is to use Active Record scopes -instead. You can find the deprecation gem at -https://github.com/rails/activerecord-deprecated_finders - It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods. ### `find_or_create_by` -The `find_or_create_by` method checks whether a record with the attributes exists. If it doesn't, then `create` is called. Let's see an example. +The `find_or_create_by` method checks whether a record with the specified attributes exists. If it doesn't, then `create` is called. Let's see an example. Suppose you want to find a client named 'Andy', and if there's none, create one. You can do so by running: @@ -1466,7 +1623,7 @@ now want the client named 'Nick': ```ruby nick = Client.find_or_initialize_by(first_name: 'Nick') -# => +# => # nick.persisted? # => false @@ -1498,10 +1655,10 @@ Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER BY clients.created_at desc") # => [ - #, - #, - # ... -] +# #, +# #, +# ... +# ] ``` `find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects. @@ -1513,9 +1670,9 @@ Client.find_by_sql("SELECT * FROM clients ```ruby Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") # => [ - {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, - {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} -] +# {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, +# {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} +# ] ``` ### `pluck` @@ -1560,7 +1717,7 @@ a large or often-running query. However, any model method overrides will not be available. For example: ```ruby -class Client < ActiveRecord::Base +class Client < ApplicationRecord def name "I am #{super}" end @@ -1595,7 +1752,7 @@ Person.ids ``` ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord self.primary_key = "person_id" end @@ -1768,10 +1925,11 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.` 2 rows in set (0.00 sec) ``` -under MySQL. +under MySQL and MariaDB. -Active Record performs a pretty printing that emulates the one of the database -shells. So, the same query running with the PostgreSQL adapter would yield instead +Active Record performs a pretty printing that emulates that of the +corresponding database shell. So, the same query running with the +PostgreSQL adapter would yield instead ``` EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1 @@ -1827,7 +1985,7 @@ EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN 1 row in set (0.00 sec) ``` -under MySQL. +under MySQL and MariaDB. ### Interpreting EXPLAIN @@ -1836,6 +1994,8 @@ following pointers may be helpful: * SQLite3: [EXPLAIN QUERY PLAN](http://www.sqlite.org/eqp.html) -* MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.6/en/explain-output.html) +* MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.7/en/explain-output.html) + +* MariaDB: [EXPLAIN](https://mariadb.com/kb/en/mariadb/explain/) * PostgreSQL: [Using EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html) diff --git a/source/active_record_validations.md b/source/active_record_validations.md index 7628b82..936d6a3 100644 --- a/source/active_record_validations.md +++ b/source/active_record_validations.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Validations ========================= @@ -18,7 +20,7 @@ Validations Overview Here's an example of a very simple validation: ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true end @@ -45,7 +47,7 @@ built-in helpers for common needs, and allows you to create your own validation methods as well. There are several other ways to validate data before it is saved into your -database, including native database constraints, client-side validations, +database, including native database constraints, client-side validations and controller-level validations. Here's a summary of the pros and cons: * Database constraints and/or stored procedures make the validation mechanisms @@ -78,7 +80,7 @@ method to determine whether an object is already in the database or not. Consider the following simple Active Record class: ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord end ``` @@ -120,7 +122,7 @@ database only if the object is valid: * `update!` The bang versions (e.g. `save!`) raise an exception if the record is invalid. -The non-bang versions don't, `save` and `update` return `false`, +The non-bang versions don't: `save` and `update` return `false`, and `create` just returns the object. ### Skipping Validations @@ -141,19 +143,21 @@ database regardless of its validity. They should be used with caution. * `update_counters` Note that `save` also has the ability to skip validations if passed `validate: -false` as argument. This technique should be used with caution. +false` as an argument. This technique should be used with caution. * `save(validate: false)` ### `valid?` and `invalid?` -To verify whether or not an object is valid, Rails uses the `valid?` method. -You can also use this method on your own. `valid?` triggers your validations +Before saving an Active Record object, Rails runs your validations. +If these validations produce any errors, Rails does not save the object. + +You can also run these validations on your own. `valid?` triggers your validations and returns true if no errors were found in the object, and false otherwise. As you saw above: ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true end @@ -166,11 +170,12 @@ through the `errors.messages` instance method, which returns a collection of err By definition, an object is valid if this collection is empty after running validations. -Note that an object instantiated with `new` will not report errors even if it's -technically invalid, because validations are not run when using `new`. +Note that an object instantiated with `new` will not report errors +even if it's technically invalid, because validations are automatically run +only when the object is saved, such as with the `create` or `save` methods. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true end @@ -216,7 +221,7 @@ it doesn't verify the validity of the object as a whole. It only checks to see whether there are errors found on an individual attribute of the object. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true end @@ -225,8 +230,26 @@ end ``` We'll cover validation errors in greater depth in the [Working with Validation -Errors](#working-with-validation-errors) section. For now, let's turn to the -built-in validation helpers that Rails provides by default. +Errors](#working-with-validation-errors) section. + +### `errors.details` + +To check which validations failed on an invalid attribute, you can use +`errors.details[:attribute]`. It returns an array of hashes with an `:error` +key to get the symbol of the validator: + +```ruby +class Person < ApplicationRecord + validates :name, presence: true +end + +>> person = Person.new +>> person.valid? +>> person.errors.details[:name] # => [{error: :blank}] +``` + +Using `details` with custom validators is covered in the [Working with +Validation Errors](#working-with-validation-errors) section. Validation Helpers ------------------ @@ -252,28 +275,42 @@ available helpers. This method validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your -application's terms of service, confirm reading some text, or any similar -concept. This validation is very specific to web applications and this -'acceptance' does not need to be recorded anywhere in your database (if you -don't have a field for it, the helper will just create a virtual attribute). +application's terms of service, confirm that some text is read, or any similar +concept. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :terms_of_service, acceptance: true end ``` +This check is performed only if `terms_of_service` is not `nil`. The default error message for this helper is _"must be accepted"_. +You can also pass custom message via the `message` option. + +```ruby +class Person < ApplicationRecord + validates :terms_of_service, acceptance: true, message: 'must be abided' +end +``` -It can receive an `:accept` option, which determines the value that will be -considered acceptance. It defaults to "1" and can be easily changed. +It can also receive an `:accept` option, which determines the allowed values +that will be considered as accepted. It defaults to `['1', true]` and can be +easily changed. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :terms_of_service, acceptance: { accept: 'yes' } + validates :eula, acceptance: { accept: ['TRUE', 'accepted'] } end ``` +This validation is very specific to web applications and this +'acceptance' does not need to be recorded anywhere in your database. If you +don't have a field for it, the helper will just create a virtual attribute. If +the field does exist in your database, the `accept` option must be set to +or include `true` or else the validation will not run. + ### `validates_associated` You should use this helper when your model has associations with other models @@ -281,7 +318,7 @@ and they also need to be validated. When you try to save your object, `valid?` will be called upon each one of the associated objects. ```ruby -class Library < ActiveRecord::Base +class Library < ApplicationRecord has_many :books validates_associated :books end @@ -304,7 +341,7 @@ or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with "_confirmation" appended. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :email, confirmation: true end ``` @@ -318,15 +355,25 @@ In your view template you could use something like This check is performed only if `email_confirmation` is not `nil`. To require confirmation, make sure to add a presence check for the confirmation attribute -(we'll take a look at `presence` later on this guide): +(we'll take a look at `presence` later on in this guide): ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :email, confirmation: true validates :email_confirmation, presence: true end ``` +There is also a `:case_sensitive` option that you can use to define whether the +confirmation constraint will be case sensitive or not. This option defaults to +true. + +```ruby +class Person < ApplicationRecord + validates :email, confirmation: { case_sensitive: false } +end +``` + The default error message for this helper is _"doesn't match confirmation"_. ### `exclusion` @@ -335,7 +382,7 @@ This helper validates that the attributes' values are not included in a given set. In fact, this set can be any enumerable object. ```ruby -class Account < ActiveRecord::Base +class Account < ApplicationRecord validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value} is reserved." } end @@ -355,7 +402,7 @@ This helper validates the attributes' values by testing whether they match a given regular expression, which is specified using the `:with` option. ```ruby -class Product < ActiveRecord::Base +class Product < ApplicationRecord validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" } end @@ -371,7 +418,7 @@ This helper validates that the attributes' values are included in a given set. In fact, this set can be any enumerable object. ```ruby -class Coffee < ActiveRecord::Base +class Coffee < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value} is not a valid size" } end @@ -390,7 +437,7 @@ This helper validates the length of the attributes' values. It provides a variety of options, so you can specify length constraints in different ways: ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, length: { minimum: 2 } validates :bio, length: { maximum: 500 } validates :password, length: { in: 6..20 } @@ -413,27 +460,12 @@ number corresponding to the length constraint being used. You can still use the `:message` option to specify an error message. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :bio, length: { maximum: 1000, too_long: "%{count} characters is the maximum allowed" } end ``` -This helper counts characters by default, but you can split the value in a -different way using the `:tokenizer` option: - -```ruby -class Essay < ActiveRecord::Base - validates :content, length: { - minimum: 300, - maximum: 400, - tokenizer: lambda { |str| str.split(/\s+/) }, - too_short: "must have at least %{count} words", - too_long: "must have at most %{count} words" - } -end -``` - Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when `:minimum` is 1 you should provide a personalized message or use `presence: true` instead. When @@ -450,7 +482,7 @@ point number. To specify that only integral numbers are allowed set If you set `:only_integer` to `true`, then it will use the ```ruby -/\A[+-]?\d+\Z/ +/\A[+-]?\d+\z/ ``` regular expression to validate the attribute's value. Otherwise, it will try to @@ -460,7 +492,7 @@ WARNING. Note that the regular expression above allows a trailing newline character. ```ruby -class Player < ActiveRecord::Base +class Player < ApplicationRecord validates :points, numericality: true validates :games_played, numericality: { only_integer: true } end @@ -479,9 +511,11 @@ constraints to acceptable values: default error message for this option is _"must be equal to %{count}"_. * `:less_than` - Specifies the value must be less than the supplied value. The default error message for this option is _"must be less than %{count}"_. -* `:less_than_or_equal_to` - Specifies the value must be less than or equal the - supplied value. The default error message for this option is _"must be less - than or equal to %{count}"_. +* `:less_than_or_equal_to` - Specifies the value must be less than or equal to + the supplied value. The default error message for this option is _"must be + less than or equal to %{count}"_. +* `:other_than` - Specifies the value must be other than the supplied value. + The default error message for this option is _"must be other than %{count}"_. * `:odd` - Specifies the value must be an odd number if set to true. The default error message for this option is _"must be odd"_. * `:even` - Specifies the value must be an even number if set to true. The @@ -498,7 +532,7 @@ This helper validates that the specified attributes are not empty. It uses the is, a string that is either empty or consists of whitespace. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, :login, :email, presence: true end ``` @@ -508,7 +542,7 @@ whether the associated object itself is present, and not the foreign key used to map the association. ```ruby -class LineItem < ActiveRecord::Base +class LineItem < ApplicationRecord belongs_to :order validates :order, presence: true end @@ -518,7 +552,7 @@ In order to validate associated records whose presence is required, you must specify the `:inverse_of` option for the association: ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord has_many :line_items, inverse_of: :order end ``` @@ -531,7 +565,6 @@ Since `false.blank?` is true, if you want to validate the presence of a boolean field you should use one of the following validations: ```ruby -validates :boolean_field_name, presence: true validates :boolean_field_name, inclusion: { in: [true, false] } validates :boolean_field_name, exclusion: { in: [nil] } ``` @@ -546,7 +579,7 @@ This helper validates that the specified attributes are absent. It uses the is, a string that is either empty or consists of whitespace. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, :login, :email, absence: true end ``` @@ -556,7 +589,7 @@ whether the associated object itself is absent, and not the foreign key used to map the association. ```ruby -class LineItem < ActiveRecord::Base +class LineItem < ApplicationRecord belongs_to :order validates :order, absence: true end @@ -566,7 +599,7 @@ In order to validate associated records whose absence is required, you must specify the `:inverse_of` option for the association: ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord has_many :line_items, inverse_of: :order end ``` @@ -586,12 +619,10 @@ This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, -you must create a unique index on both columns in your database. See -[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) -for more details about multiple column indexes. +you must create a unique index on that column in your database. ```ruby -class Account < ActiveRecord::Base +class Account < ApplicationRecord validates :email, uniqueness: true end ``` @@ -599,22 +630,23 @@ end The validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute. -There is a `:scope` option that you can use to specify other attributes that +There is a `:scope` option that you can use to specify one or more attributes that are used to limit the uniqueness check: ```ruby -class Holiday < ActiveRecord::Base +class Holiday < ApplicationRecord validates :name, uniqueness: { scope: :year, message: "should happen once per year" } end ``` +Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. There is also a `:case_sensitive` option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, uniqueness: { case_sensitive: false } end ``` @@ -637,7 +669,7 @@ class GoodnessValidator < ActiveModel::Validator end end -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates_with GoodnessValidator end ``` @@ -665,7 +697,7 @@ class GoodnessValidator < ActiveModel::Validator end end -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates_with GoodnessValidator, fields: [:first_name, :last_name] end ``` @@ -678,7 +710,7 @@ If your validator is complex enough that you want instance variables, you can easily use a plain old Ruby object instead: ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validate do |person| GoodnessValidator.new(person).validate end @@ -707,7 +739,7 @@ passed to `validates_each` will be tested against it. In the following example, we don't want names and surnames to begin with lower case. ```ruby -class Person < ActiveRecord::Base +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 @@ -730,7 +762,7 @@ The `:allow_nil` option skips the validation when the value being validated is `nil`. ```ruby -class Coffee < ActiveRecord::Base +class Coffee < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value} is not a valid size" }, allow_nil: true end @@ -743,7 +775,7 @@ will let validation pass if the attribute's value is `blank?`, like `nil` or an empty string for example. ```ruby -class Topic < ActiveRecord::Base +class Topic < ApplicationRecord validates :title, length: { is: 5 }, allow_blank: true end @@ -756,7 +788,36 @@ Topic.create(title: nil).valid? # => true As you've already seen, the `:message` option lets you specify the message that will be added to the `errors` collection when validation fails. When this option is not used, Active Record will use the respective default error message -for each validation helper. +for each validation helper. The `:message` option accepts a `String` or `Proc`. + +A `String` `:message` value can optionally contain any/all of `%{value}`, +`%{attribute}`, and `%{model}` which will be dynamically replaced when +validation fails. + +A `Proc` `:message` value is given two arguments: the object being validated, and +a hash with `:model`, `:attribute`, and `:value` key-value pairs. + +```ruby +class Person < ApplicationRecord + # Hard-coded message + validates :name, presence: { message: "must be given please" } + + # Message with dynamic attribute value. %{value} will be replaced with + # the actual value of the attribute. %{attribute} and %{model} also + # available. + validates :age, numericality: { message: "%{value} seems wrong" } + + # Proc + validates :username, + uniqueness: { + # object = person object being validated + # data = { model: "Person", attribute: "Username", value: } + message: ->(object, data) do + "Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}" + end + } +end +``` ### `:on` @@ -768,7 +829,7 @@ new record is created or `on: :update` to run the validation only when a record is updated. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord # it will be possible to update email with a duplicated value validates :email, uniqueness: true, on: :create @@ -780,6 +841,25 @@ class Person < ActiveRecord::Base end ``` +You can also use `on:` to define custom context. +Custom contexts need to be triggered explicitly +by passing name of the context to `valid?`, `invalid?` or `save`. + +```ruby +class Person < ApplicationRecord + validates :email, uniqueness: true, on: :account_setup + validates :age, numericality: true, on: :account_setup +end + +person = Person.new +``` + +`person.valid?(:account_setup)` executes both the validations +without saving the model. And `person.save(context: :account_setup)` +validates `person` in `account_setup` context before saving. +On explicit triggers, model is validated by +validations of only that context and validations without context. + Strict Validations ------------------ @@ -787,17 +867,17 @@ You can also specify validations to be strict and raise `ActiveModel::StrictValidationFailed` when the object is invalid. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: { strict: true } end Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank ``` -There is also an ability to pass custom exception to `:strict` option. +There is also the ability to pass a custom exception to the `:strict` option. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :token, presence: true, uniqueness: true, strict: TokenGenerationException end @@ -821,7 +901,7 @@ to the name of a method that will get called right before validation happens. This is the most commonly used option. ```ruby -class Order < ActiveRecord::Base +class Order < ApplicationRecord validates :card_number, presence: true, if: :paid_with_card? def paid_with_card? @@ -837,7 +917,7 @@ contain valid Ruby code. You should use this option only when the string represents a really short condition. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :surname, presence: true, if: "name.nil?" end ``` @@ -850,7 +930,7 @@ inline condition instead of a separate method. This option is best suited for one-liners. ```ruby -class Account < ActiveRecord::Base +class Account < ApplicationRecord validates :password, confirmation: true, unless: Proc.new { |a| a.password.blank? } end @@ -858,11 +938,11 @@ end ### Grouping Conditional validations -Sometimes it is useful to have multiple validations use one condition, it can +Sometimes it is useful to have multiple validations use one condition. It can be easily achieved using `with_options`. ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord with_options if: :is_admin? do |admin| admin.validates :password, length: { minimum: 10 } admin.validates :email, presence: true @@ -870,8 +950,8 @@ class User < ActiveRecord::Base end ``` -All validations inside of `with_options` block will have automatically passed -the condition `if: :is_admin?` +All validations inside of the `with_options` block will have automatically +passed the condition `if: :is_admin?` ### Combining Validation Conditions @@ -880,7 +960,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and `:unless` to the same validation. ```ruby -class Computer < ActiveRecord::Base +class Computer < ApplicationRecord validates :mouse, presence: true, if: ["market.retail?", :desktop?], unless: Proc.new { |c| c.trackpad.present? } @@ -898,8 +978,8 @@ write your own validators or validation methods as you prefer. ### Custom Validators -Custom validators are classes that extend `ActiveModel::Validator`. These -classes must implement a `validate` method which takes a record as an argument +Custom validators are classes that inherit from `ActiveModel::Validator`. These +classes must implement the `validate` method which takes a record as an argument and performs the validation on it. The custom validator is called using the `validates_with` method. @@ -934,7 +1014,7 @@ class EmailValidator < ActiveModel::EachValidator end end -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :email, presence: true, email: true end ``` @@ -946,14 +1026,19 @@ own custom validators. You can also create methods that verify the state of your models and add messages to the `errors` collection when they are invalid. You must then -register these methods by using the `validate` class method, passing in the -symbols for the validation methods' names. +register these methods by using the `validate` +([API](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate)) +class method, passing in the symbols for the validation methods' names. You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered. +The `valid?` method will verify that the errors collection is empty, +so your custom validation methods should add errors to it when you +wish validation to fail: + ```ruby -class Invoice < ActiveRecord::Base +class Invoice < ApplicationRecord validate :expiration_date_cannot_be_in_the_past, :discount_cannot_be_greater_than_total_value @@ -971,12 +1056,13 @@ class Invoice < ActiveRecord::Base end ``` -By default such validations will run every time you call `valid?`. It is also -possible to control when to run these custom validations by giving an `:on` -option to the `validate` method, with either: `:create` or `:update`. +By default, such validations will run every time you call `valid?` +or save the object. But it is also possible to control when to run these +custom validations by giving an `:on` option to the `validate` method, +with either: `:create` or `:update`. ```ruby -class Invoice < ActiveRecord::Base +class Invoice < ApplicationRecord validate :active_customer, on: :create def active_customer @@ -997,7 +1083,7 @@ The following is a list of the most commonly used methods. Please refer to the ` Returns an instance of the class `ActiveModel::Errors` containing all errors. Each key is the attribute name and the value is an array of strings with all errors. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end @@ -1016,7 +1102,7 @@ person.errors.messages # => {} `errors[]` is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end @@ -1036,10 +1122,12 @@ person.errors[:name] ### `errors.add` -The `add` method lets you manually add messages that are related to particular attributes. You can use the `errors.full_messages` or `errors.to_a` methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). `add` receives the name of the attribute you want to add the message to, and the message itself. +The `add` method lets you add an error message related to a particular attribute. It takes as arguments the attribute and the error message. + +The `errors.full_messages` method (or its equivalent, `errors.to_a`) returns the error messages in a user-friendly format, with the capitalized attribute name prepended to each message, as shown in the examples below. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord def a_method_used_for_validation_purposes errors.add(:name, "cannot contain the characters !@#%*()_-+=") end @@ -1054,12 +1142,12 @@ person.errors.full_messages # => ["Name cannot contain the characters !@#%*()_-+="] ``` -Another way to do this is using `[]=` setter +An equivalent to `errors#add` is to use `<<` to append a message to the `errors.messages` array for an attribute: ```ruby - class Person < ActiveRecord::Base + class Person < ApplicationRecord def a_method_used_for_validation_purposes - errors[:name] = "cannot contain the characters !@#%*()_-+=" + errors.messages[:name] << "cannot contain the characters !@#%*()_-+=" end end @@ -1072,12 +1160,49 @@ Another way to do this is using `[]=` setter # => ["Name cannot contain the characters !@#%*()_-+="] ``` +### `errors.details` + +You can specify a validator type to the returned error details hash using the +`errors.add` method. + +```ruby +class Person < ApplicationRecord + def a_method_used_for_validation_purposes + errors.add(:name, :invalid_characters) + end +end + +person = Person.create(name: "!@#") + +person.errors.details[:name] +# => [{error: :invalid_characters}] +``` + +To improve the error details to contain the unallowed characters set for instance, +you can pass additional keys to `errors.add`. + +```ruby +class Person < ApplicationRecord + def a_method_used_for_validation_purposes + errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=") + end +end + +person = Person.create(name: "!@#") + +person.errors.details[:name] +# => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}] +``` + +All built in Rails validators populate the details hash with the corresponding +validator type. + ### `errors[:base]` You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord def a_method_used_for_validation_purposes errors[:base] << "This person is invalid because ..." end @@ -1089,7 +1214,7 @@ end The `clear` method is used when you intentionally want to clear all the messages in the `errors` collection. Of course, calling `errors.clear` upon an invalid object won't actually make it valid: the `errors` collection will now be empty, but the next time you call `valid?` or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the `errors` collection will be filled again. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end @@ -1101,9 +1226,9 @@ person.errors[:name] person.errors.clear person.errors.empty? # => true -p.save # => false +person.save # => false -p.errors[:name] +person.errors[:name] # => ["can't be blank", "is too short (minimum is 3 characters)"] ``` @@ -1112,7 +1237,7 @@ p.errors[:name] The `size` method returns the total number of error messages for the object. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end diff --git a/source/active_support_core_extensions.md b/source/active_support_core_extensions.md index faad34d..d8475b7 100644 --- a/source/active_support_core_extensions.md +++ b/source/active_support_core_extensions.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Support Core Extensions ============================== @@ -170,7 +172,7 @@ NOTE: Defined in `active_support/core_ext/object/duplicable.rb`. ### `deep_dup` -The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, Ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this: +The `deep_dup` method returns a deep copy of a given object. Normally, when you `dup` an object that contains other objects, Ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this: ```ruby array = ['string'] @@ -246,6 +248,13 @@ end @person.try { |p| "#{p.first_name} #{p.last_name}" } ``` +Note that `try` will swallow no-method errors, returning nil instead. If you want to protect against typos, use `try!` instead: + +```ruby +@number.try(:nest) # => nil +@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer +``` + NOTE: Defined in `active_support/core_ext/object/try.rb`. ### `class_eval(*args, &block)` @@ -347,7 +356,7 @@ end we get: ```ruby -current_user.to_query('user') # => user=357-john-smith +current_user.to_query('user') # => "user=357-john-smith" ``` This method escapes whatever is needed, both for the key and the value: @@ -388,7 +397,7 @@ The method `with_options` provides a way to factor out common options in a serie Given a default options hash, `with_options` yields a proxy object to a block. Within the block, methods called on the proxy are forwarded to the receiver with their options merged. For example, you get rid of the duplication in: ```ruby -class Account < ActiveRecord::Base +class Account < ApplicationRecord has_many :customers, dependent: :destroy has_many :products, dependent: :destroy has_many :invoices, dependent: :destroy @@ -399,7 +408,7 @@ end this way: ```ruby -class Account < ActiveRecord::Base +class Account < ApplicationRecord with_options dependent: :destroy do |assoc| assoc.has_many :customers assoc.has_many :products @@ -451,7 +460,7 @@ NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. #### `instance_variable_names` -The method `instance_variable_names` returns an array. Each name includes the "@" sign. +The method `instance_variable_names` returns an array. Each name includes the "@" sign. ```ruby class C @@ -465,7 +474,7 @@ C.new(0, 1).instance_variable_names # => ["@x", "@y"] NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. -### Silencing Warnings, Streams, and Exceptions +### Silencing Warnings and Exceptions The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: @@ -473,26 +482,10 @@ The methods `silence_warnings` and `enable_warnings` change the value of `$VERBO silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } ``` -You can silence any stream while a block runs with `silence_stream`: - -```ruby -silence_stream(STDOUT) do - # STDOUT is silent here -end -``` - -The `quietly` method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses: +Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is not captured: ```ruby -quietly { system 'bundle install' } -``` - -For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status. - -Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is reraised: - -```ruby -# If the user is locked the increment is lost, no big deal. +# If the user is locked, the increment is lost, no big deal. suppress(ActiveRecord::StaleObjectError) do current_user.increment! :visits end @@ -520,19 +513,21 @@ Extensions to `Module` ### `alias_method_chain` +**This method is deprecated in favour of using Module#prepend.** + Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. -For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionController::TestCase#process` this way in `test/test_helper.rb`: +For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionDispatch::IntegrationTest#process` this way in `test/test_helper.rb`: ```ruby -ActionController::TestCase.class_eval do +ActionDispatch::IntegrationTest.class_eval do # save a reference to the original process method alias_method :original_process, :process # now redefine process and delegate to original_process - def process(action, params=nil, session=nil, flash=nil, http_method='GET') + def process('GET', path, params: nil, headers: nil, env: nil, xhr: false) params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - original_process(action, params, session, flash, http_method) + original_process('GET', path, params: params) end end ``` @@ -542,10 +537,10 @@ That's the method `get`, `post`, etc., delegate the work to. That technique has a risk, it could be the case that `:original_process` was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about: ```ruby -ActionController::TestCase.class_eval do +ActionDispatch::IntegrationTest.class_eval do def process_with_stringified_params(...) params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(action, params, session, flash, http_method) + process_without_stringified_params(method, path, params: params) end alias_method :process_without_stringified_params, :process alias_method :process, :process_with_stringified_params @@ -555,17 +550,15 @@ end The method `alias_method_chain` provides a shortcut for that pattern: ```ruby -ActionController::TestCase.class_eval do +ActionDispatch::IntegrationTest.class_eval do def process_with_stringified_params(...) params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(action, params, session, flash, http_method) + process_without_stringified_params(method, path, params: params) end alias_method_chain :process, :stringified_params end ``` -Rails uses `alias_method_chain` all over the code base. For example validations are added to `ActiveRecord::Base#save` by wrapping the method that way in a separate module specialized in validations. - NOTE: Defined in `active_support/core_ext/module/aliasing.rb`. ### Attributes @@ -575,7 +568,7 @@ NOTE: Defined in `active_support/core_ext/module/aliasing.rb`. Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (one mnemonic is that they go in the same order as if you did an assignment): ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord # You can refer to the email column as "login". # This can be meaningful for authentication code. alias_attribute :login, :email @@ -639,8 +632,6 @@ module ActiveSupport mattr_accessor :load_once_paths mattr_accessor :autoloaded_constants mattr_accessor :explicitly_unloadable_constants - mattr_accessor :logger - mattr_accessor :log_activity mattr_accessor :constant_watch_stack mattr_accessor :constant_watch_stack_mutex end @@ -716,32 +707,9 @@ M.parents # => [X::Y, X, Object] NOTE: Defined in `active_support/core_ext/module/introspection.rb`. -### Constants - -The method `local_constants` returns the names of the constants that have been -defined in the receiver module: - -```ruby -module X - X1 = 1 - X2 = 2 - module Y - Y1 = :y1 - X1 = :overrides_X1_above - end -end - -X.local_constants # => [:X1, :X2, :Y] -X::Y.local_constants # => [:Y1, :X1] -``` - -The names are returned as symbols. - -NOTE: Defined in `active_support/core_ext/module/introspection.rb`. - #### Qualified Constant Names -The standard methods `const_defined?`, `const_get` , and `const_set` accept +The standard methods `const_defined?`, `const_get`, and `const_set` accept bare constant names. Active Support extends this API to be able to pass relative qualified constant names. @@ -883,7 +851,7 @@ The macro `delegate` offers an easy way to forward methods. Let's imagine that users in some application have login information in the `User` model but name and other data in a separate `Profile` model: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_one :profile end ``` @@ -891,7 +859,7 @@ end With that configuration you get a user's name via their profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_one :profile def name @@ -903,7 +871,7 @@ end That is what `delegate` does for you: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_one :profile delegate :name, to: :profile @@ -1251,7 +1219,7 @@ Calling `dup` or `clone` on safe strings yields safe strings. The method `remove` will remove all occurrences of the pattern: ```ruby -"Hello World".remove(/Hello /) => "World" +"Hello World".remove(/Hello /) # => "World" ``` There's also the destructive version `String#remove!`. @@ -1717,6 +1685,20 @@ The method `parameterize` normalizes its receiver in a way that can be used in p "Kurt Gödel".parameterize # => "kurt-godel" ``` +To preserve the case of the string, set the `preserve_case` argument to true. By default, `preserve_case` is set to false. + +```ruby +"John Smith".parameterize(preserve_case: true) # => "John-Smith" +"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel" +``` + +To use a custom separator, override the `separator` argument. + +```ruby +"John Smith".parameterize(separator: "_") # => "john\_smith" +"Kurt Gödel".parameterize(separator: "_") # => "kurt\_godel" +``` + In fact, the result string is wrapped in an instance of `ActiveSupport::Multibyte::Chars`. NOTE: Defined in `active_support/core_ext/string/inflections.rb`. @@ -1760,7 +1742,7 @@ NOTE: Defined in `active_support/core_ext/string/inflections.rb`. The method `constantize` resolves the constant reference expression in its receiver: ```ruby -"Fixnum".constantize # => Fixnum +"Integer".constantize # => Integer module M X = 1 @@ -1879,15 +1861,15 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr ```ruby "2010-07-27".to_date # => Tue, 27 Jul 2010 -"2010-07-27 23:37:00".to_time # => Tue Jul 27 23:37:00 UTC 2010 +"2010-07-27 23:37:00".to_time # => 2010-07-27 23:37:00 +0200 "2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000 ``` `to_time` receives an optional argument `:utc` or `:local`, to indicate which time zone you want the time in: ```ruby -"2010-07-27 23:42:00".to_time(:utc) # => Tue Jul 27 23:42:00 UTC 2010 -"2010-07-27 23:42:00".to_time(:local) # => Tue Jul 27 23:42:00 +0200 2010 +"2010-07-27 23:42:00".to_time(:utc) # => 2010-07-27 23:42:00 UTC +"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200 ``` Default is `:utc`. @@ -1950,6 +1932,8 @@ as well as adding or subtracting their results from a Time object. For example: (4.months + 5.years).from_now ``` +NOTE: Defined in `active_support/core_ext/numeric/time.rb` + ### Formatting Enables the formatting of numbers in a variety of ways. @@ -2015,12 +1999,14 @@ Produce a string representation of a number rounded to a precision: Produce a string representation of a number as a human-readable number of bytes: ```ruby -123.to_s(:human_size) # => 123 Bytes -1234.to_s(:human_size) # => 1.21 KB -12345.to_s(:human_size) # => 12.1 KB -1234567.to_s(:human_size) # => 1.18 MB -1234567890.to_s(:human_size) # => 1.15 GB -1234567890123.to_s(:human_size) # => 1.12 TB +123.to_s(:human_size) # => 123 Bytes +1234.to_s(:human_size) # => 1.21 KB +12345.to_s(:human_size) # => 12.1 KB +1234567.to_s(:human_size) # => 1.18 MB +1234567890.to_s(:human_size) # => 1.15 GB +1234567890123.to_s(:human_size) # => 1.12 TB +1234567890123456.to_s(:human_size) # => 1.1 PB +1234567890123456789.to_s(:human_size) # => 1.07 EB ``` Produce a string representation of a number in human-readable words: @@ -2085,30 +2071,22 @@ Extensions to `BigDecimal` -------------------------- ### `to_s` -The method `to_s` is aliased to `to_formatted_s`. This provides a convenient way to display a BigDecimal value in floating-point notation: +The method `to_s` provides a default specifier of "F". This means that a simple call to `to_s` will result in floating point representation instead of engineering notation: ```ruby BigDecimal.new(5.00, 6).to_s # => "5.0" ``` -### `to_formatted_s` - -Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: - -```ruby -BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" -``` - and that symbol specifiers are also supported: ```ruby -BigDecimal.new(5.00, 6).to_formatted_s(:db) # => "5.0" +BigDecimal.new(5.00, 6).to_s(:db) # => "5.0" ``` Engineering notation is still supported: ```ruby -BigDecimal.new(5.00, 6).to_formatted_s("e") # => "0.5E1" +BigDecimal.new(5.00, 6).to_s("e") # => "0.5E1" ``` Extensions to `Enumerable` @@ -2196,6 +2174,27 @@ to_visit << node if visited.exclude?(node) NOTE: Defined in `active_support/core_ext/enumerable.rb`. +### `without` + +The method `without` returns a copy of an enumerable with the specified elements +removed: + +```ruby +["David", "Rafael", "Aaron", "Todd"].without("Aaron", "Todd") # => ["David", "Rafael"] +``` + +NOTE: Defined in `active_support/core_ext/enumerable.rb`. + +### `pluck` + +The method `pluck` returns an array based on the given key: + +```ruby +[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) # => ["David", "Rafael", "Aaron"] +``` + +NOTE: Defined in `active_support/core_ext/enumerable.rb`. + Extensions to `Array` --------------------- @@ -2204,22 +2203,22 @@ Extensions to `Array` Active Support augments the API of arrays to ease certain ways of accessing them. For example, `to` returns the subarray of elements up to the one at the passed index: ```ruby -%w(a b c d).to(2) # => %w(a b c) +%w(a b c d).to(2) # => ["a", "b", "c"] [].to(7) # => [] ``` Similarly, `from` returns the tail from the element at the passed index to the end. If the index is greater than the length of the array, it returns an empty array. ```ruby -%w(a b c d).from(2) # => %w(c d) +%w(a b c d).from(2) # => ["c", "d"] %w(a b c d).from(10) # => [] [].from(0) # => [] ``` -The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element (`first` is built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available. +The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element, as do `second_to_last` and `third_to_last` (`first` and `last` are built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available. ```ruby -%w(a b c d).third # => c +%w(a b c d).third # => "c" %w(a b c d).fifth # => nil ``` @@ -2232,7 +2231,7 @@ NOTE: Defined in `active_support/core_ext/array/access.rb`. This method is an alias of `Array#unshift`. ```ruby -%w(a b c d).prepend('e') # => %w(e a b c d) +%w(a b c d).prepend('e') # => ["e", "a", "b", "c", "d"] [].prepend(10) # => [10] ``` @@ -2243,8 +2242,8 @@ NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. This method is an alias of `Array#<<`. ```ruby -%w(a b c d).append('e') # => %w(a b c d e) -[].append([1,2]) # => [[1,2]] +%w(a b c d).append('e') # => ["a", "b", "c", "d", "e"] +[].append([1,2]) # => [[1, 2]] ``` NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. @@ -2431,7 +2430,7 @@ The method `Array.wrap` wraps its argument in an array unless it is already an a Specifically: -* If the argument is `nil` an empty list is returned. +* If the argument is `nil` an empty array is returned. * Otherwise, if the argument responds to `to_ary` it is invoked, and if the value of `to_ary` is not `nil`, it is returned. * Otherwise, an array with the argument as its single element is returned. @@ -2443,9 +2442,9 @@ Array.wrap(0) # => [0] This method is similar in purpose to `Kernel#Array`, but there are some differences: -* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns `nil` right away. +* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns an array with the argument as its single element right away. * If the returned value from `to_ary` is neither `nil` nor an `Array` object, `Kernel#Array` raises an exception, while `Array.wrap` does not, it just returns the value. -* It does not call `to_a` on the argument, though special-cases `nil` to return an empty array. +* It does not call `to_a` on the argument, if the argument does not respond to +to_ary+ it returns an array with the argument as its single element. The last point is particularly worth comparing for some enumerables: @@ -2468,7 +2467,7 @@ NOTE: Defined in `active_support/core_ext/array/wrap.rb`. ### Duplicating -The method `Array.deep_dup` duplicates itself and all objects inside +The method `Array#deep_dup` duplicates itself and all objects inside recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside. ```ruby @@ -2612,8 +2611,7 @@ To do so, the method loops over the pairs and builds nodes that depend on the _v ```ruby XML_TYPE_NAMES = { "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", + "Integer" => "integer", "BigDecimal" => "decimal", "Float" => "float", "TrueClass" => "boolean", @@ -2690,7 +2688,7 @@ NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`. ### Deep duplicating -The method `Hash.deep_dup` duplicates itself and all keys and values +The method `Hash#deep_dup` duplicates itself and all keys and values inside recursively with Active Support method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside. ```ruby @@ -2886,7 +2884,7 @@ The method `transform_values` accepts a block and returns a hash that has applie ``` There's also the bang variant `transform_values!` that applies the block operations to values in the very receiver. -NOTE: Defined in `active_support/core_text/hash/transform_values.rb`. +NOTE: Defined in `active_support/core_ext/hash/transform_values.rb`. ### Slicing @@ -3043,53 +3041,6 @@ The method `Range#overlaps?` says whether any two given ranges have non-void int NOTE: Defined in `active_support/core_ext/range/overlaps.rb`. -Extensions to `Proc` --------------------- - -### `bind` - -As you surely know Ruby has an `UnboundMethod` class whose instances are methods that belong to the limbo of methods without a self. The method `Module#instance_method` returns an unbound method for example: - -```ruby -Hash.instance_method(:delete) # => # -``` - -An unbound method is not callable as is, you need to bind it first to an object with `bind`: - -```ruby -clear = Hash.instance_method(:clear) -clear.bind({a: 1}).call # => {} -``` - -Active Support defines `Proc#bind` with an analogous purpose: - -```ruby -Proc.new { size }.bind([]).call # => 0 -``` - -As you see that's callable and bound to the argument, the return value is indeed a `Method`. - -NOTE: To do so `Proc#bind` actually creates a method under the hood. If you ever see a method with a weird name like `__bind_1256598120_237302` in a stack trace you know now where it comes from. - -Action Pack uses this trick in `rescue_from` for example, which accepts the name of a method and also a proc as callbacks for a given rescued exception. It has to call them in either case, so a bound method is returned by `handler_for_rescue`, thus simplifying the code in the caller: - -```ruby -def handler_for_rescue(exception) - _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler| - ... - end - - case rescuer - when Symbol - method(rescuer) - when Proc - rescuer.bind(self) - end -end -``` - -NOTE: Defined in `active_support/core_ext/proc.rb`. - Extensions to `Date` -------------------- @@ -3101,7 +3052,7 @@ INFO: The following calculation methods have edge cases in October 1582, since d #### `Date.current` -Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Date.current`. +Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, `future?`, `on_weekday?` and `on_weekend?`, all of them relative to `Date.current`. When making Date comparisons using methods which honor the user time zone, make sure to use `Date.current` and not `Date.today`. There are cases where the user time zone might be in the future compared to the system time zone, which `Date.today` uses by default. This means `Date.today` may equal `Date.yesterday`. @@ -3490,6 +3441,8 @@ years_ago years_since prev_year (last_year) next_year +on_weekday? +on_weekend? ``` The following methods are reimplemented so you do **not** need to load `active_support/core_ext/date/calculations.rb` for these ones: @@ -3676,6 +3629,8 @@ years_ago years_since prev_year (last_year) next_year +on_weekday? +on_weekend? ``` They are analogous. Please refer to their documentation above and take into account the following differences: @@ -3811,50 +3766,6 @@ WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able t NOTE: Defined in `active_support/core_ext/marshal.rb`. -Extensions to `Logger` ----------------------- - -### `around_[level]` - -Takes two arguments, a `before_message` and `after_message` and calls the current level method on the `Logger` instance, passing in the `before_message`, then the specified message, then the `after_message`: - -```ruby -logger = Logger.new("log/development.log") -logger.around_info("before", "after") { |logger| logger.info("during") } -``` - -### `silence` - -Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal. - -```ruby -logger = Logger.new("log/development.log") -logger.silence(Logger::INFO) do - logger.debug("In space, no one can hear you scream.") - logger.info("Scream all you want, small mailman!") -end -``` - -### `datetime_format=` - -Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a `datetime_format` method then this is ignored. - -```ruby -class Logger::FormatWithTime < Logger::Formatter - cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } - - def self.call(severity, timestamp, progname, msg) - "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" - end -end - -logger = Logger.new("log/development.log") -logger.formatter = Logger::FormatWithTime -logger.info("<- is the current time") -``` - -NOTE: Defined in `active_support/core_ext/logger.rb`. - Extensions to `NameError` ------------------------- @@ -3871,7 +3782,7 @@ def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path -rescue MissingSourceFile => e +rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" @@ -3883,7 +3794,7 @@ NOTE: Defined in `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- -Active Support adds `is_missing?` to `LoadError`, and also assigns that class to the constant `MissingSourceFile` for backwards compatibility. +Active Support adds `is_missing?` to `LoadError`. Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). @@ -3894,7 +3805,7 @@ def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore helper module_path -rescue MissingSourceFile => e +rescue LoadError => e raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" diff --git a/source/active_support_instrumentation.md b/source/active_support_instrumentation.md index 9dfacce..e945c15 100644 --- a/source/active_support_instrumentation.md +++ b/source/active_support_instrumentation.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Support Instrumentation ============================== @@ -17,7 +19,7 @@ After reading this guide, you will know: Introduction to instrumentation ------------------------------- -The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in (TODO: link to section detailing each hook point). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code. +The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the [Rails framework](#rails-framework-hooks). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code. For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken. @@ -110,6 +112,7 @@ Action Controller | `:controller` | The controller name | | `:action` | The action | | `:params` | Hash of request parameters without any filtered parameter | +| `:headers` | Request headers | | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | @@ -119,6 +122,7 @@ Action Controller controller: "PostsController", action: "new", params: { "action" => "new", "controller" => "posts" }, + headers: #, format: :html, method: "GET", path: "/posts/new" @@ -132,6 +136,7 @@ Action Controller | `:controller` | The controller name | | `:action` | The action | | `:params` | Hash of request parameters without any filtered parameter | +| `:headers` | Request headers | | `:format` | html/js/json/xml etc | | `:method` | HTTP request verb | | `:path` | Request path | @@ -144,6 +149,7 @@ Action Controller controller: "PostsController", action: "index", params: {"action" => "index", "controller" => "posts"}, + headers: #, format: :html, method: "GET", path: "/posts", @@ -216,7 +222,25 @@ Action View ```ruby { - identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb", + identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb" +} +``` + +### render_collection.action_view + +| Key | Value | +| ------------- | ------------------------------------- | +| `:identifier` | Full path to template | +| `:count` | Size of collection | +| `:cache_hits` | Number of partials fetched from cache | + +`:cache_hits` is only included if the collection is rendered with `cached: true`. + +```ruby +{ + identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb", + count: 3, + cache_hits: 0 } ``` @@ -230,6 +254,7 @@ Active Record | `:sql` | SQL statement | | `:name` | Name of the operation | | `:connection_id` | `self.object_id` | +| `:binds` | Bind parameters | INFO. The adapters will add their own data as well. @@ -242,13 +267,19 @@ INFO. The adapters will add their own data as well. } ``` -### identity.active_record +### instantiation.active_record | Key | Value | | ---------------- | ----------------------------------------- | -| `:line` | Primary Key of object in the identity map | -| `:name` | Record's class | -| `:connection_id` | `self.object_id` | +| `:record_count` | Number of records that instantiated | +| `:class_name` | Record's class | + +```ruby +{ + record_count: 1, + class_name: "User" +} +``` Action Mailer ------------- @@ -305,17 +336,6 @@ Action Mailer } ``` -ActiveResource --------------- - -### request.active_resource - -| Key | Value | -| -------------- | -------------------- | -| `:method` | HTTP method | -| `:request_uri` | Complete URI | -| `:result` | HTTP response object | - Active Support -------------- @@ -398,6 +418,38 @@ INFO. Cache stores may add their own keys } ``` +Active Job +-------- + +### enqueue_at.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + +### enqueue.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + +### perform_start.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + +### perform.active_job + +| Key | Value | +| ------------ | -------------------------------------- | +| `:adapter` | QueueAdapter object processing the job | +| `:job` | Job object | + + Railties -------- @@ -428,7 +480,7 @@ The block receives the following arguments: * The name of the event * Time when it started * Time when it finished -* An unique ID for this event +* A unique ID for this event * The payload (described in previous sections) ```ruby diff --git a/source/api_app.md b/source/api_app.md new file mode 100644 index 0000000..cad8d53 --- /dev/null +++ b/source/api_app.md @@ -0,0 +1,418 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + + +Using Rails for API-only Applications +===================================== + +In this guide you will learn: + +* What Rails provides for API-only applications +* How to configure Rails to start without any browser features +* How to decide which middleware you will want to include +* How to decide which modules to use in your controller + +-------------------------------------------------------------------------------- + +What is an API Application? +--------------------------- + +Traditionally, when people said that they used Rails as an "API", they meant +providing a programmatically accessible API alongside their web application. +For example, GitHub provides [an API](http://developer.github.com) that you +can use from your own custom clients. + +With the advent of client-side frameworks, more developers are using Rails to +build a back-end that is shared between their web application and other native +applications. + +For example, Twitter uses its [public API](https://dev.twitter.com) in its web +application, which is built as a static site that consumes JSON resources. + +Instead of using Rails to generate HTML that communicates with the server +through forms and links, many developers are treating their web application as +just an API client delivered as HTML with JavaScript that consumes a JSON API. + +This guide covers building a Rails application that serves JSON resources to an +API client, including client-side frameworks. + +Why Use Rails for JSON APIs? +---------------------------- + +The first question a lot of people have when thinking about building a JSON API +using Rails is: "isn't using Rails to spit out some JSON overkill? Shouldn't I +just use something like Sinatra?". + +For very simple APIs, this may be true. However, even in very HTML-heavy +applications, most of an application's logic lives outside of the view +layer. + +The reason most people use Rails is that it provides a set of defaults that +allows developers to get up and running quickly, without having to make a lot of trivial +decisions. + +Let's take a look at some of the things that Rails provides out of the box that are +still applicable to API applications. + +Handled at the middleware layer: + +- Reloading: Rails applications support transparent reloading. This works even if + your application gets big and restarting the server for every request becomes + non-viable. +- Development Mode: Rails applications come with smart defaults for development, + making development pleasant without compromising production-time performance. +- Test Mode: Ditto development mode. +- Logging: Rails applications log every request, with a level of verbosity + appropriate for the current mode. Rails logs in development include information + about the request environment, database queries, and basic performance + information. +- Security: Rails detects and thwarts [IP spoofing + attacks](http://en.wikipedia.org/wiki/IP_address_spoofing) and handles + cryptographic signatures in a [timing + attack](http://en.wikipedia.org/wiki/Timing_attack) aware way. Don't know what + an IP spoofing attack or a timing attack is? Exactly. +- Parameter Parsing: Want to specify your parameters as JSON instead of as a + URL-encoded String? No problem. Rails will decode the JSON for you and make + it available in `params`. Want to use nested URL-encoded parameters? That + works too. +- Conditional GETs: Rails handles conditional `GET` (`ETag` and `Last-Modified`) + processing request headers and returning the correct response headers and status + code. All you need to do is use the + [`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) + check in your controller, and Rails will handle all of the HTTP details for you. +- HEAD requests: Rails will transparently convert `HEAD` requests into `GET` ones, + and return just the headers on the way out. This makes `HEAD` work reliably in + all Rails APIs. + +While you could obviously build these up in terms of existing Rack middleware, +this list demonstrates that the default Rails middleware stack provides a lot +of value, even if you're "just generating JSON". + +Handled at the Action Pack layer: + +- Resourceful Routing: If you're building a RESTful JSON API, you want to be + using the Rails router. Clean and conventional mapping from HTTP to controllers + means not having to spend time thinking about how to model your API in terms + of HTTP. +- URL Generation: The flip side of routing is URL generation. A good API based + on HTTP includes URLs (see [the GitHub Gist API](http://developer.github.com/v3/gists/) + for an example). +- Header and Redirection Responses: `head :no_content` and + `redirect_to user_url(https://github.com/current_user)` come in handy. Sure, you could manually + add the response headers, but why? +- Caching: Rails provides page, action and fragment caching. Fragment caching + is especially helpful when building up a nested JSON object. +- Basic, Digest, and Token Authentication: Rails comes with out-of-the-box support + for three kinds of HTTP authentication. +- Instrumentation: Rails has an instrumentation API that triggers registered + handlers for a variety of events, such as action processing, sending a file or + data, redirection, and database queries. The payload of each event comes with + relevant information (for the action processing event, the payload includes + the controller, action, parameters, request format, request method and the + request's full path). +- Generators: It is often handy to generate a resource and get your model, + controller, test stubs, and routes created for you in a single command for + further tweaking. Same for migrations and others. +- Plugins: Many third-party libraries come with support for Rails that reduce + or eliminate the cost of setting up and gluing together the library and the + web framework. This includes things like overriding default generators, adding + Rake tasks, and honoring Rails choices (like the logger and cache back-end). + +Of course, the Rails boot process also glues together all registered components. +For example, the Rails boot process is what uses your `config/database.yml` file +when configuring Active Record. + +**The short version is**: you may not have thought about which parts of Rails +are still applicable even if you remove the view layer, but the answer turns out +to be most of it. + +The Basic Configuration +----------------------- + +If you're building a Rails application that will be an API server first and +foremost, you can start with a more limited subset of Rails and add in features +as needed. + +### Creating a new application + +You can generate a new api Rails app: + +```bash +$ rails new my_api --api +``` + +This will do three main things for you: + +- Configure your application to start with a more limited set of middleware + than normal. Specifically, it will not include any middleware primarily useful + for browser applications (like cookies support) by default. +- Make `ApplicationController` inherit from `ActionController::API` instead of + `ActionController::Base`. As with middleware, this will leave out any Action + Controller modules that provide functionalities primarily used by browser + applications. +- Configure the generators to skip generating views, helpers and assets when + you generate a new resource. + +### Changing an existing application + +If you want to take an existing application and make it an API one, read the +following steps. + +In `config/application.rb` add the following line at the top of the `Application` +class definition: + +```ruby +config.api_only = true +``` + +In `config/environments/development.rb`, set `config.debug_exception_response_format` +to configure the format used in responses when errors occur in development mode. + +To render an HTML page with debugging information, use the value `:default`. + +```ruby +config.debug_exception_response_format = :default +``` + +To render debugging information preserving the response format, use the value `:api`. + +```ruby +config.debug_exception_response_format = :api +``` + +By default, `config.debug_exception_response_format` is set to `:api`, when `config.api_only` is set to true. + +Finally, inside `app/controllers/application_controller.rb`, instead of: + +```ruby +class ApplicationController < ActionController::Base +end +``` + +do: + +```ruby +class ApplicationController < ActionController::API +end +``` + +Choosing Middleware +-------------------- + +An API application comes with the following middleware by default: + +- `Rack::Sendfile` +- `ActionDispatch::Static` +- `ActionDispatch::Executor` +- `ActiveSupport::Cache::Strategy::LocalCache::Middleware` +- `Rack::Runtime` +- `ActionDispatch::RequestId` +- `Rails::Rack::Logger` +- `ActionDispatch::ShowExceptions` +- `ActionDispatch::DebugExceptions` +- `ActionDispatch::RemoteIp` +- `ActionDispatch::Reloader` +- `ActionDispatch::Callbacks` +- `Rack::Head` +- `Rack::ConditionalGet` +- `Rack::ETag` + +See the [internal middleware](rails_on_rack.html#internal-middleware-stack) +section of the Rack guide for further information on them. + +Other plugins, including Active Record, may add additional middleware. In +general, these middleware are agnostic to the type of application you are +building, and make sense in an API-only Rails application. + +You can get a list of all middleware in your application via: + +```bash +$ rails middleware +``` + +### Using the Cache Middleware + +By default, Rails will add a middleware that provides a cache store based on +the configuration of your application (memcache by default). This means that +the built-in HTTP cache will rely on it. + +For instance, using the `stale?` method: + +```ruby +def show + @post = Post.find(params[:id]) + + if stale?(last_modified: @post.updated_at) + render json: @post + end +end +``` + +The call to `stale?` will compare the `If-Modified-Since` header in the request +with `@post.updated_at`. If the header is newer than the last modified, this +action will return a "304 Not Modified" response. Otherwise, it will render the +response and include a `Last-Modified` header in it. + +Normally, this mechanism is used on a per-client basis. The cache middleware +allows us to share this caching mechanism across clients. We can enable +cross-client caching in the call to `stale?`: + +```ruby +def show + @post = Post.find(params[:id]) + + if stale?(last_modified: @post.updated_at, public: true) + render json: @post + end +end +``` + +This means that the cache middleware will store off the `Last-Modified` value +for a URL in the Rails cache, and add an `If-Modified-Since` header to any +subsequent inbound requests for the same URL. + +Think of it as page caching using HTTP semantics. + +### Using Rack::Sendfile + +When you use the `send_file` method inside a Rails controller, it sets the +`X-Sendfile` header. `Rack::Sendfile` is responsible for actually sending the +file. + +If your front-end server supports accelerated file sending, `Rack::Sendfile` +will offload the actual file sending work to the front-end server. + +You can configure the name of the header that your front-end server uses for +this purpose using `config.action_dispatch.x_sendfile_header` in the appropriate +environment's configuration file. + +You can learn more about how to use `Rack::Sendfile` with popular +front-ends in [the Rack::Sendfile +documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile). + +Here are some values for popular servers, once they are configured, to support +accelerated file sending: + +```ruby +# Apache and lighttpd +config.action_dispatch.x_sendfile_header = "X-Sendfile" + +# Nginx +config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" +``` + +Make sure to configure your server to support these options following the +instructions in the `Rack::Sendfile` documentation. + +### Using ActionDispatch::Request + +`ActionDispatch::Request#params` will take parameters from the client in the JSON +format and make them available in your controller inside `params`. + +To use this, your client will need to make a request with JSON-encoded parameters +and specify the `Content-Type` as `application/json`. + +Here's an example in jQuery: + +```javascript +jQuery.ajax({ + type: 'POST', + url: '/people', + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }), + success: function(json) { } +}); +``` + +`ActionDispatch::Request` will see the `Content-Type` and your parameters +will be: + +```ruby +{ :person => { :firstName => "Yehuda", :lastName => "Katz" } } +``` + +### Other Middleware + +Rails ships with a number of other middleware that you might want to use in an +API application, especially if one of your API clients is the browser: + +- `Rack::MethodOverride` +- `ActionDispatch::Cookies` +- `ActionDispatch::Flash` +- For sessions management + * `ActionDispatch::Session::CacheStore` + * `ActionDispatch::Session::CookieStore` + * `ActionDispatch::Session::MemCacheStore` + +Any of these middleware can be added via: + +```ruby +config.middleware.use Rack::MethodOverride +``` + +### Removing Middleware + +If you don't want to use a middleware that is included by default in the API-only +middleware set, you can remove it with: + +```ruby +config.middleware.delete ::Rack::Sendfile +``` + +Keep in mind that removing these middleware will remove support for certain +features in Action Controller. + +Choosing Controller Modules +--------------------------- + +An API application (using `ActionController::API`) comes with the following +controller modules by default: + +- `ActionController::UrlFor`: Makes `url_for` and similar helpers available. +- `ActionController::Redirecting`: Support for `redirect_to`. +- `AbstractController::Rendering` and `ActionController::ApiRendering`: Basic support for rendering. +- `ActionController::Renderers::All`: Support for `render :json` and friends. +- `ActionController::ConditionalGet`: Support for `stale?`. +- `ActionController::BasicImplicitRender`: Makes sure to return an empty response + if there's not an explicit one. +- `ActionController::StrongParameters`: Support for parameters white-listing in + combination with Active Model mass assignment. +- `ActionController::ForceSSL`: Support for `force_ssl`. +- `ActionController::DataStreaming`: Support for `send_file` and `send_data`. +- `AbstractController::Callbacks`: Support for `before_action` and + similar helpers. +- `ActionController::Rescue`: Support for `rescue_from`. +- `ActionController::Instrumentation`: Support for the instrumentation + hooks defined by Action Controller (see [the instrumentation + guide](active_support_instrumentation.html#action-controller) for +more information regarding this). +- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash + so you don't have to specify root elements sending POST requests for instance. + +Other plugins may add additional modules. You can get a list of all modules +included into `ActionController::API` in the rails console: + +```bash +$ bin/rails c +>> ActionController::API.ancestors - ActionController::Metal.ancestors +``` + +### Adding Other Modules + +All Action Controller modules know about their dependent modules, so you can feel +free to include any modules into your controllers, and all dependencies will be +included and set up as well. + +Some common modules you might want to add: + +- `AbstractController::Translation`: Support for the `l` and `t` localization + and translation methods. +- `ActionController::HttpAuthentication::Basic` (or `Digest` or `Token`): Support + for basic, digest or token HTTP authentication. +- `ActionView::Layouts`: Support for layouts when rendering. +- `ActionController::MimeResponds`: Support for `respond_to`. +- `ActionController::Cookies`: Support for `cookies`, which includes + support for signed and encrypted cookies. This requires the cookies middleware. + +The best place to add a module is in your `ApplicationController`, but you can +also add modules to individual controllers. diff --git a/source/api_documentation_guidelines.md b/source/api_documentation_guidelines.md index a2ebf55..34b9c0d 100644 --- a/source/api_documentation_guidelines.md +++ b/source/api_documentation_guidelines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + API Documentation Guidelines ============================ @@ -14,7 +16,8 @@ RDoc ---- The [Rails API documentation](http://api.rubyonrails.org) is generated with -[RDoc](http://docs.seattlerb.org/rdoc/). +[RDoc](http://docs.seattlerb.org/rdoc/). To generate it, make sure you are +in the rails root directory, run `bundle install` and execute: ```bash bundle exec rake rdoc @@ -81,6 +84,12 @@ English Please use American English (*color*, *center*, *modularize*, etc). See [a list of American and British English spelling differences here](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences). +Oxford Comma +------------ + +Please use the [Oxford comma](http://en.wikipedia.org/wiki/Serial_comma) +("red, white, and blue", instead of "red, white and blue"). + Example Code ------------ @@ -111,7 +120,7 @@ On the other hand, big chunks of structured documentation may have a separate "E The results of expressions follow them and are introduced by "# => ", vertically aligned: ```ruby -# For checking if a fixnum is even or odd. +# For checking if an integer is even or odd. # # 1.even? # => false # 1.odd? # => true @@ -231,7 +240,7 @@ You can quickly test the RDoc output with the following command: ``` $ echo "+:to_param+" | rdoc --pipe -#=>

:to_param

+# =>

:to_param

``` ### Regular Font diff --git a/source/asset_pipeline.md b/source/asset_pipeline.md index 156daf1..8dd13a8 100644 --- a/source/asset_pipeline.md +++ b/source/asset_pipeline.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Asset Pipeline ================== @@ -19,21 +21,20 @@ What is the Asset Pipeline? The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages and pre-processors such as CoffeeScript, Sass and ERB. +It allows assets in your application to be automatically combined with assets +from other gems. For example, jquery-rails includes a copy of jquery.js +and enables AJAX features in Rails. -The asset pipeline is technically no longer a core feature of Rails 4, it has -been extracted out of the framework into the -[sprockets-rails](https://github.com/rails/sprockets-rails) gem. - -The asset pipeline is enabled by default. - -You can disable the asset pipeline while creating a new application by +The asset pipeline is implemented by the +[sprockets-rails](https://github.com/rails/sprockets-rails) gem, +and is enabled by default. You can disable it while creating a new application by passing the `--skip-sprockets` option. ```bash rails new appname --skip-sprockets ``` -Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier` +Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier` gems to your Gemfile, which are used by Sprockets for asset compression: ```ruby @@ -42,8 +43,8 @@ gem 'uglifier' gem 'coffee-rails' ``` -Using the `--skip-sprockets` option will prevent Rails 4 from adding -`sass-rails` and `uglifier` to Gemfile, so if you later want to enable +Using the `--skip-sprockets` option will prevent Rails from adding +them to your Gemfile, so if you later want to enable the asset pipeline you will have to add those gems to your Gemfile. Also, creating an application with the `--skip-sprockets` option will generate a slightly different `config/application.rb` file, with a require statement @@ -64,7 +65,7 @@ config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included -in Gemfile and no `config.assets.css_compressor` option is set. +in the Gemfile and no `config.assets.css_compressor` option is set. ### Main Features @@ -147,7 +148,7 @@ clients to fetch them again, even when the content of those assets has not chang Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content. -Fingerprinting is enabled by default for production and disabled for all other +Fingerprinting is enabled by default for both the development and production environments. You can enable or disable it in your configuration through the `config.assets.digest` option. @@ -167,7 +168,7 @@ directory. Files in this directory are served by the Sprockets middleware. Assets can still be placed in the `public` hierarchy. Any assets under `public` will be served as static files by the application or web server when -`config.serve_static_files` is set to true. You should use `app/assets` for +`config.public_file_server.enabled` is set to true. You should use `app/assets` for files that must undergo some pre-processing before they are served. In production, Rails precompiles these files to `public/assets` by default. The @@ -180,12 +181,12 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates -the file scaffolds.css (or scaffolds.css.scss if `sass-rails` is in the +the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the `Gemfile`.) For example, if you generate a `ProjectsController`, Rails will also add a new -file at `app/assets/javascripts/projects.js.coffee` and another at -`app/assets/stylesheets/projects.css.scss`. By default these files will be ready +file at `app/assets/javascripts/projects.coffee` and another at +`app/assets/stylesheets/projects.scss`. By default these files will be ready to use by your application immediately using the `require_tree` directive. See [Manifest Files and Directives](#manifest-files-and-directives) for more details on require_tree. @@ -207,7 +208,7 @@ precompiling works. NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows, you have a JavaScript runtime installed in -your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes. +your operating system. Check [ExecJS](https://github.com/rails/execjs#readme) documentation to know all supported JavaScript runtimes. You can also disable generation of controller specific asset files by adding the following to your `config/application.rb` configuration: @@ -325,13 +326,13 @@ familiar `javascript_include_tag` and `stylesheet_link_tag`: <%= javascript_include_tag "application" %> ``` -If using the turbolinks gem, which is included by default in Rails 4, then +If using the turbolinks gem, which is included by default in Rails, then include the 'data-turbolinks-track' option which causes turbolinks to check if an asset has been updated and if so loads it into the page: ```erb -<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> -<%= javascript_include_tag "application", "data-turbolinks-track" => true %> +<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %> +<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %> ``` In regular views you can access images in the `public/assets/images` directory @@ -401,13 +402,13 @@ When using the asset pipeline, paths to assets must be re-written and underscored in Ruby) for the following asset classes: image, font, video, audio, JavaScript and stylesheet. -* `image-url("https://github.com/rails.png")` becomes `url(https://github.com/assets/rails.png)` -* `image-path("rails.png")` becomes `"/assets/rails.png"`. +* `image-url("https://github.com/rails.png")` returns `url(https://github.com/assets/rails.png)` +* `image-path("rails.png")` returns `"/assets/rails.png"` The more generic form can also be used: -* `asset-url("https://github.com/rails.png")` becomes `url(https://github.com/assets/rails.png)` -* `asset-path("rails.png")` becomes `"/assets/rails.png"` +* `asset-url("https://github.com/rails.png")` returns `url(https://github.com/assets/rails.png)` +* `asset-path("rails.png")` returns `"/assets/rails.png"` #### JavaScript/CoffeeScript and ERB @@ -422,7 +423,7 @@ $('#logo').attr({ src: "<%= asset_path('logo.png') %>" }); This writes the path to the particular asset being referenced. Similarly, you can use the `asset_path` helper in CoffeeScript files with `erb` -extension (e.g., `application.js.coffee.erb`): +extension (e.g., `application.coffee.erb`): ```js $('#logo').attr src: "<%= asset_path('logo.png') %>" @@ -434,14 +435,14 @@ Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ - instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if -necessary, concatenates them into one single file and then compresses them (if -`Rails.application.config.assets.compress` is true). By serving one file rather -than many, the load time of pages can be greatly reduced because the browser -makes fewer requests. Compression also reduces file size, enabling the -browser to download them faster. +necessary, concatenates them into one single file and then compresses them +(based on value of `Rails.application.config.assets.js_compressor`). By serving +one file rather than many, the load time of pages can be greatly reduced because +the browser makes fewer requests. Compression also reduces file size, enabling +the browser to download them faster. -For example, a new Rails 4 application includes a default +For example, a new Rails application includes a default `app/assets/javascripts/application.js` file containing the following lines: ```js @@ -482,7 +483,7 @@ which contains these lines: */ ``` -Rails 4 creates both `app/assets/javascripts/application.js` and +Rails creates both `app/assets/javascripts/application.js` and `app/assets/stylesheets/application.css` regardless of whether the --skip-sprockets option is used when creating a new rails application. This is so you can easily add asset pipelining later if you like. @@ -523,8 +524,8 @@ The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which -generated an `app/assets/javascripts/projects.js.coffee` and an -`app/assets/stylesheets/projects.css.scss` file. +generated an `app/assets/javascripts/projects.coffee` and an +`app/assets/stylesheets/projects.scss` file. In development mode, or if the asset pipeline is disabled, when these files are requested they are processed by the processors provided by the `coffee-script` @@ -536,13 +537,13 @@ web server. Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet -called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB, +called `app/assets/stylesheets/projects.scss.erb` is first processed as ERB, then SCSS, and finally served as CSS. The same applies to a JavaScript file - -`app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then +`app/assets/javascripts/projects.coffee.erb` is processed as ERB, then CoffeeScript, and served as JavaScript. Keep in mind the order of these preprocessors is important. For example, if -you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee` +you called your JavaScript file `app/assets/javascripts/projects.erb.coffee` then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems. @@ -641,7 +642,7 @@ above. By default Rails assumes assets have been precompiled and will be served as static assets by your web server. During the precompilation phase an MD5 is generated from the contents of the -compiled files, and inserted into the filenames as they are written to disc. +compiled files, and inserted into the filenames as they are written to disk. These fingerprinted names are used by the Rails helpers in place of the manifest name. @@ -660,13 +661,12 @@ generates something like this: rel="stylesheet" /> ``` -Note: with the Asset Pipeline the :cache and :concat options aren't used +NOTE: with the Asset Pipeline the `:cache` and `:concat` options aren't used anymore, delete these options from the `javascript_include_tag` and `stylesheet_link_tag`. The fingerprinting behavior is controlled by the `config.assets.digest` -initialization option (which defaults to `true` for production and `false` for -everything else). +initialization option (which defaults to `true`). NOTE: Under normal circumstances the default `config.assets.digest` option should not be changed. If there are no digests in the filenames, and far-future @@ -675,7 +675,7 @@ content changes. ### Precompiling Assets -Rails comes bundled with a rake task to compile the asset manifests and other +Rails comes bundled with a task to compile the asset manifests and other files in the pipeline. Compiled assets are written to the location specified in `config.assets.prefix`. @@ -685,10 +685,10 @@ You can call this task on the server during deployment to create compiled versions of your assets directly on the server. See the next section for information on compiling locally. -The rake task is: +The task is: ```bash -$ RAILS_ENV=production bin/rake assets:precompile +$ RAILS_ENV=production bin/rails assets:precompile ``` Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. @@ -727,31 +727,10 @@ include, you can add them to the `precompile` array in `config/initializers/asse Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] ``` -Or, you can opt to precompile all assets with something like this: - -```ruby -# config/initializers/assets.rb -Rails.application.config.assets.precompile << Proc.new do |path| - if path =~ /\.(css|js)\z/ - full_path = Rails.application.assets.resolve(path).to_path - app_assets_path = Rails.root.join('app', 'assets').to_path - if full_path.starts_with? app_assets_path - logger.info "including asset: " + full_path - true - else - logger.info "excluding asset: " + full_path - false - end - else - false - end -end -``` - NOTE. Always specify an expected compiled filename that ends with .js or .css, even if you want to add Sass or CoffeeScript files to the precompile array. -The rake task also generates a `manifest-md5hash.json` that contains a list with +The task also generates a `manifest-md5hash.json` that contains a list with all your assets and their respective fingerprints. This is used by the Rails helper methods to avoid handing the mapping requests back to Sprockets. A typical manifest file looks like: @@ -806,45 +785,9 @@ location ~ ^/assets/ { add_header Cache-Control public; add_header ETag ""; - break; } ``` -#### GZip Compression - -When files are precompiled, Sprockets also creates a -[gzipped](http://en.wikipedia.org/wiki/Gzip) (.gz) version of your assets. Web -servers are typically configured to use a moderate compression ratio as a -compromise, but since precompilation happens once, Sprockets uses the maximum -compression ratio, thus reducing the size of the data transfer to the minimum. -On the other hand, web servers can be configured to serve compressed content -directly from disk, rather than deflating non-compressed files themselves. - -NGINX is able to do this automatically enabling `gzip_static`: - -```nginx -location ~ ^/(assets)/ { - root /path/to/public; - gzip_static on; # to serve pre-gzipped version - expires max; - add_header Cache-Control public; -} -``` - -This directive is available if the core module that provides this feature was -compiled with the web server. Ubuntu/Debian packages, even `nginx-light`, have -the module compiled. Otherwise, you may need to perform a manual compilation: - -```bash -./configure --with-http_gzip_static_module -``` - -If you're compiling NGINX with Phusion Passenger you'll need to pass that option -when prompted. - -A robust configuration for Apache is possible but tricky; please Google around. -(Or help update this Guide if you have a good configuration example for Apache.) - ### Local Precompilation There are several reasons why you might want to precompile your assets locally. @@ -940,7 +883,7 @@ focus on serving application code as fast as possible. #### Set up a CDN to Serve Static Assets To set up your CDN you have to have your application running in production on -the internet at a publically available URL, for example `example.com`. Next +the internet at a publicly available URL, for example `example.com`. Next you'll need to sign up for a CDN service from a cloud hosting provider. When you do this you need to configure the "origin" of the CDN to point back at your website `example.com`, check your provider for documentation on configuring the @@ -953,7 +896,7 @@ your CDN server, you need to tell browsers to use your CDN to grab assets instead of your Rails server directly. You can do this by configuring Rails to set your CDN as the asset host instead of using a relative path. To set your asset host in Rails, you need to set `config.action_controller.asset_host` in -`config/production.rb`: +`config/environments/production.rb`: ```ruby config.action_controller.asset_host = 'mycdnsubdomain.fictional-cdn.com' @@ -993,7 +936,7 @@ http://mycdnsubdomain.fictional-cdn.com/assets/smile.png If the CDN has a copy of `smile.png` it will serve it to the browser and your server doesn't even know it was requested. If the CDN does not have a copy it -will try to find it a the "origin" `example.com/assets/smile.png` and then store +will try to find it at the "origin" `example.com/assets/smile.png` and then store it for future use. If you want to serve only some assets from your CDN, you can use custom `:host` @@ -1076,7 +1019,7 @@ header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C specification that describes how a request can be cached. When no CDN is used, a browser will use this information to cache contents. This is very helpful for assets that are not modified so that a browser does not need to re-download a -website's CSS or javascript on every request. Generally we want our Rails server +website's CSS or JavaScript on every request. Generally we want our Rails server to tell our CDN (and browser) that the asset is "public", that means any cache can store the request. Also we commonly want to set `max-age` which is how long the cache will store the object before invalidating the cache. The `max-age` @@ -1084,7 +1027,9 @@ value is set to seconds with a maximum possible value of `31536000` which is one year. You can do this in your rails application by setting ``` -config.static_cache_control = "public, max-age=31536000" +config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=31536000' +} ``` Now when your application serves an asset in production, the CDN will store the @@ -1156,15 +1101,21 @@ The following line invokes `uglifier` for JavaScript compression. config.assets.js_compressor = :uglifier ``` -NOTE: You will need an [ExecJS](https://github.com/sstephenson/execjs#readme) +NOTE: You will need an [ExecJS](https://github.com/rails/execjs#readme) supported runtime in order to use `uglifier`. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. -NOTE: The `config.assets.compress` initialization option is no longer used in -Rails 4 to enable either CSS or JavaScript compression. Setting it will have no -effect on the application. Instead, setting `config.assets.css_compressor` and -`config.assets.js_compressor` will control compression of CSS and JavaScript -assets. + + +### Serving GZipped version of assets + +By default, gzipped version of compiled assets will be generated, along +with the non-gzipped version of assets. Gzipped assets help reduce the transmission of +data over the wire. You can configure this by setting the `gzip` flag. + +```ruby +config.assets.gzip = false # disable gzipped assets generation +``` ### Using Your Own Compressor @@ -1230,19 +1181,14 @@ TIP: For further details have a look at the docs of your production web server: Assets Cache Store ------------------ -The default Rails cache store will be used by Sprockets to cache assets in -development and production. This can be changed by setting -`config.assets.cache_store`: +By default, Sprockets caches assets in `tmp/cache/assets` in development +and production environments. This can be changed as follows: ```ruby -config.assets.cache_store = :memory_store -``` - -The options accepted by the assets cache store are the same as the application's -cache store. - -```ruby -config.assets.cache_store = :memory_store, { size: 32.megabytes } +config.assets.configure do |env| + env.cache = ActiveSupport::Cache.lookup_store(:memory_store, + { size: 32.megabytes }) +end ``` To disable the assets cache store: @@ -1333,8 +1279,9 @@ config.assets.debug = true And in `production.rb`: ```ruby -# Choose the compressors to use (if any) config.assets.js_compressor = -# :uglifier config.assets.css_compressor = :yui +# Choose the compressors to use (if any) +config.assets.js_compressor = :uglifier +# config.assets.css_compressor = :yui # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = false @@ -1343,15 +1290,16 @@ config.assets.compile = false config.assets.digest = true # Precompile additional assets (application.js, application.css, and all -# non-JS/CSS are already added) config.assets.precompile += %w( search.js ) +# non-JS/CSS are already added) +# config.assets.precompile += %w( search.js ) ``` -Rails 4 no longer sets default config values for Sprockets in `test.rb`, so +Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so `test.rb` now requires Sprockets configuration. The old defaults in the test environment are: `config.assets.compile = true`, `config.assets.compress = false`, `config.assets.debug = false` and `config.assets.digest = false`. -The following should also be added to `Gemfile`: +The following should also be added to your `Gemfile`: ```ruby gem 'sass-rails', "~> 3.2.3" diff --git a/source/association_basics.md b/source/association_basics.md index 5c05f0c..4977d4f 100644 --- a/source/association_basics.md +++ b/source/association_basics.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Active Record Associations ========================== @@ -14,54 +16,54 @@ After reading this guide, you will know: Why Associations? ----------------- -Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for customers and a model for orders. Each customer can have many orders. Without associations, the model declarations would look like this: +In Rails, an _association_ is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for authors and a model for books. Each author can have many books. Without associations, the model declarations would look like this: ```ruby -class Customer < ActiveRecord::Base +class Author < ApplicationRecord end -class Order < ActiveRecord::Base +class Book < ApplicationRecord end ``` -Now, suppose we wanted to add a new order for an existing customer. We'd need to do something like this: +Now, suppose we wanted to add a new book for an existing author. We'd need to do something like this: ```ruby -@order = Order.create(order_date: Time.now, customer_id: @customer.id) +@book = Book.create(published_at: Time.now, author_id: @author.id) ``` -Or consider deleting a customer, and ensuring that all of its orders get deleted as well: +Or consider deleting an author, and ensuring that all of its books get deleted as well: ```ruby -@orders = Order.where(customer_id: @customer.id) -@orders.each do |order| - order.destroy +@books = Book.where(author_id: @author.id) +@books.each do |book| + book.destroy end -@customer.destroy +@author.destroy ``` -With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: +With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up authors and books: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, dependent: :destroy +class Author < ApplicationRecord + has_many :books, dependent: :destroy end -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author end ``` -With this change, creating a new order for a particular customer is easier: +With this change, creating a new book for a particular author is easier: ```ruby -@order = @customer.orders.create(order_date: Time.now) +@book = @author.books.create(published_at: Time.now) ``` -Deleting a customer and all of its orders is *much* easier: +Deleting an author and all of its books is *much* easier: ```ruby -@customer.destroy +@author.destroy ``` To learn more about the different types of associations, read the next section of this guide. That's followed by some tips and tricks for working with associations, and then by a complete reference to the methods and options for associations in Rails. @@ -69,7 +71,7 @@ To learn more about the different types of associations, read the next section o The Types of Associations ------------------------- -In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations: +Rails supports six types of associations: * `belongs_to` * `has_one` @@ -78,36 +80,38 @@ In Rails, an _association_ is a connection between two Active Record models. Ass * `has_one :through` * `has_and_belongs_to_many` +Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain [Primary Key](https://en.wikipedia.org/wiki/Unique_key)-[Foreign Key](https://en.wikipedia.org/wiki/Foreign_key) information between instances of the two models, and you also get a number of utility methods added to your model. + In the remainder of this guide, you'll learn how to declare and use the various forms of associations. But first, a quick introduction to the situations where each association type is appropriate. ### The `belongs_to` Association -A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way: +A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes authors and books, and each book can be assigned to exactly one author, you'd declare the book model this way: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author end ``` ![belongs_to Association Diagram](images/belongs_to.png) -NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `customer` association in the `Order` model, you would be told that there was an "uninitialized constant Order::Customers". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too. +NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too. The corresponding migration might look like this: ```ruby -class CreateOrders < ActiveRecord::Migration +class CreateBooks < ActiveRecord::Migration[5.0] def change - create_table :customers do |t| + create_table :authors do |t| t.string :name - t.timestamps null: false + t.timestamps end - create_table :orders do |t| - t.belongs_to :customer, index: true - t.datetime :order_date - t.timestamps null: false + create_table :books do |t| + t.belongs_to :author, index: true + t.datetime :published_at + t.timestamps end end end @@ -118,7 +122,7 @@ end A `has_one` association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account end ``` @@ -128,29 +132,40 @@ end The corresponding migration might look like this: ```ruby -class CreateSuppliers < ActiveRecord::Migration +class CreateSuppliers < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number - t.timestamps null: false + t.timestamps end end end ``` +Depending on the use case, you might also need to create a unique index and/or +a foreign key constraint on the supplier column for the accounts table. In this +case, the column definition might look like this: + +```ruby +create_table :accounts do |t| + t.belongs_to :supplier, index: true, unique: true, foreign_key: true + # ... +end +``` + ### The `has_many` Association -A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this: +A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing authors and books, the author model could be declared like this: ```ruby -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` @@ -161,17 +176,17 @@ NOTE: The name of the other model is pluralized when declaring a `has_many` asso The corresponding migration might look like this: ```ruby -class CreateCustomers < ActiveRecord::Migration +class CreateAuthors < ActiveRecord::Migration[5.0] def change - create_table :customers do |t| + create_table :authors do |t| t.string :name - t.timestamps null: false + t.timestamps end - create_table :orders do |t| - t.belongs_to :customer, index:true - t.datetime :order_date - t.timestamps null: false + create_table :books do |t| + t.belongs_to :author, index: true + t.datetime :published_at + t.timestamps end end end @@ -182,17 +197,17 @@ end A `has_many :through` association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this: ```ruby -class Physician < ActiveRecord::Base +class Physician < ApplicationRecord has_many :appointments has_many :patients, through: :appointments end -class Appointment < ActiveRecord::Base +class Appointment < ApplicationRecord belongs_to :physician belongs_to :patient end -class Patient < ActiveRecord::Base +class Patient < ApplicationRecord has_many :appointments has_many :physicians, through: :appointments end @@ -203,52 +218,54 @@ end The corresponding migration might look like this: ```ruby -class CreateAppointments < ActiveRecord::Migration +class CreateAppointments < ActiveRecord::Migration[5.0] def change create_table :physicians do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :patients do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :appointments do |t| t.belongs_to :physician, index: true t.belongs_to :patient, index: true t.datetime :appointment_date - t.timestamps null: false + t.timestamps end end end ``` -The collection of join models can be managed via the API. For example, if you assign +The collection of join models can be managed via the [`has_many` association methods](#has-many-association-reference). +For example, if you assign: ```ruby physician.patients = patients ``` -new join models are created for newly associated objects, and if some are gone their rows are deleted. +Then new join models are automatically created for the newly associated objects. +If some that existed previously are now missing, then their join rows are automatically deleted. WARNING: Automatic deletion of join models is direct, no destroy callbacks are triggered. The `has_many :through` association is also useful for setting up "shortcuts" through nested `has_many` associations. For example, if a document has many sections, and a section has many paragraphs, you may sometimes want to get a simple collection of all paragraphs in the document. You could set that up this way: ```ruby -class Document < ActiveRecord::Base +class Document < ApplicationRecord has_many :sections has_many :paragraphs, through: :sections end -class Section < ActiveRecord::Base +class Section < ApplicationRecord belongs_to :document has_many :paragraphs end -class Paragraph < ActiveRecord::Base +class Paragraph < ApplicationRecord belongs_to :section end ``` @@ -267,17 +284,17 @@ For example, if each supplier has one account, and each account is associated wi supplier model could look like this: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account has_one :account_history, through: :account end -class Account < ActiveRecord::Base +class Account < ApplicationRecord belongs_to :supplier has_one :account_history end -class AccountHistory < ActiveRecord::Base +class AccountHistory < ApplicationRecord belongs_to :account end ``` @@ -287,23 +304,23 @@ end The corresponding migration might look like this: ```ruby -class CreateAccountHistories < ActiveRecord::Migration +class CreateAccountHistories < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number - t.timestamps null: false + t.timestamps end create_table :account_histories do |t| t.belongs_to :account, index: true t.integer :credit_rating - t.timestamps null: false + t.timestamps end end end @@ -314,11 +331,11 @@ end A `has_and_belongs_to_many` association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way: ```ruby -class Assembly < ActiveRecord::Base +class Assembly < ApplicationRecord has_and_belongs_to_many :parts end -class Part < ActiveRecord::Base +class Part < ApplicationRecord has_and_belongs_to_many :assemblies end ``` @@ -328,16 +345,16 @@ end The corresponding migration might look like this: ```ruby -class CreateAssembliesAndParts < ActiveRecord::Migration +class CreateAssembliesAndParts < ActiveRecord::Migration[5.0] def change create_table :assemblies do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :parts do |t| t.string :part_number - t.timestamps null: false + t.timestamps end create_table :assemblies_parts, id: false do |t| @@ -355,11 +372,11 @@ If you want to set up a one-to-one relationship between two models, you'll need The distinction is in where you place the foreign key (it goes on the table for the class declaring the `belongs_to` association), but you should give some thought to the actual meaning of the data as well. The `has_one` relationship says that one of something is yours - that is, that something points back to you. For example, it makes more sense to say that a supplier owns an account than that an account owns a supplier. This suggests that the correct relationships are like this: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account end -class Account < ActiveRecord::Base +class Account < ApplicationRecord belongs_to :supplier end ``` @@ -367,17 +384,17 @@ end The corresponding migration might look like this: ```ruby -class CreateSuppliers < ActiveRecord::Migration +class CreateSuppliers < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name - t.timestamps null: false + t.timestamps end create_table :accounts do |t| t.integer :supplier_id t.string :account_number - t.timestamps null: false + t.timestamps end add_index :accounts, :supplier_id @@ -392,11 +409,11 @@ NOTE: Using `t.integer :supplier_id` makes the foreign key naming obvious and ex Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use `has_and_belongs_to_many`, which allows you to make the association directly: ```ruby -class Assembly < ActiveRecord::Base +class Assembly < ApplicationRecord has_and_belongs_to_many :parts end -class Part < ActiveRecord::Base +class Part < ApplicationRecord has_and_belongs_to_many :assemblies end ``` @@ -404,17 +421,17 @@ end The second way to declare a many-to-many relationship is to use `has_many :through`. This makes the association indirectly, through a join model: ```ruby -class Assembly < ActiveRecord::Base +class Assembly < ApplicationRecord has_many :manifests has_many :parts, through: :manifests end -class Manifest < ActiveRecord::Base +class Manifest < ApplicationRecord belongs_to :assembly belongs_to :part end -class Part < ActiveRecord::Base +class Part < ApplicationRecord has_many :manifests has_many :assemblies, through: :manifests end @@ -422,22 +439,22 @@ end The simplest rule of thumb is that you should set up a `has_many :through` relationship if you need to work with the relationship model as an independent entity. If you don't need to do anything with the relationship model, it may be simpler to set up a `has_and_belongs_to_many` relationship (though you'll need to remember to create the joining table in the database). -You should use `has_many :through` if you need validations, callbacks, or extra attributes on the join model. +You should use `has_many :through` if you need validations, callbacks or extra attributes on the join model. ### Polymorphic Associations A slightly more advanced twist on associations is the _polymorphic association_. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared: ```ruby -class Picture < ActiveRecord::Base +class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end -class Employee < ActiveRecord::Base +class Employee < ApplicationRecord has_many :pictures, as: :imageable end -class Product < ActiveRecord::Base +class Product < ApplicationRecord has_many :pictures, as: :imageable end ``` @@ -449,16 +466,16 @@ Similarly, you can retrieve `@product.pictures`. If you have an instance of the `Picture` model, you can get to its parent via `@picture.imageable`. To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface: ```ruby -class CreatePictures < ActiveRecord::Migration +class CreatePictures < ActiveRecord::Migration[5.0] def change create_table :pictures do |t| t.string :name t.integer :imageable_id t.string :imageable_type - t.timestamps null: false + t.timestamps end - add_index :pictures, :imageable_id + add_index :pictures, [:imageable_type, :imageable_id] end end ``` @@ -466,12 +483,12 @@ end This migration can be simplified by using the `t.references` form: ```ruby -class CreatePictures < ActiveRecord::Migration +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 null: false + t.timestamps end end end @@ -484,7 +501,7 @@ end In designing a data model, you will sometimes find a model that should have a relation to itself. For example, you may want to store all employees in a single database model, but be able to trace relationships such as between manager and subordinates. This situation can be modeled with self-joining associations: ```ruby -class Employee < ActiveRecord::Base +class Employee < ApplicationRecord has_many :subordinates, class_name: "Employee", foreign_key: "manager_id" @@ -497,11 +514,11 @@ With this setup, you can retrieve `@employee.subordinates` and `@employee.manage In your migrations/schema, you will add a references column to the model itself. ```ruby -class CreateEmployees < ActiveRecord::Migration +class CreateEmployees < ActiveRecord::Migration[5.0] def change create_table :employees do |t| t.references :manager, index: true - t.timestamps null: false + t.timestamps end end end @@ -523,17 +540,17 @@ Here are a few things you should know to make efficient use of Active Record ass All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example: ```ruby -customer.orders # retrieves orders from the database -customer.orders.size # uses the cached copy of orders -customer.orders.empty? # uses the cached copy of orders +author.books # retrieves books from the database +author.books.size # uses the cached copy of books +author.books.empty? # uses the cached copy of books ``` -But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call: +But what if you want to reload the cache, because data might have been changed by some other part of the application? Just call `reload` on the association: ```ruby -customer.orders # retrieves orders from the database -customer.orders.size # uses the cached copy of orders -customer.orders(true).empty? # discards the cached copy of orders +author.books # retrieves books from the database +author.books.size # uses the cached copy of books +author.books.reload.empty? # discards the cached copy of books # and goes back to the database ``` @@ -550,23 +567,23 @@ Associations are extremely useful, but they are not magic. You are responsible f When you declare a `belongs_to` association, you need to create foreign keys as appropriate. For example, consider this model: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author end ``` -This declaration needs to be backed up by the proper foreign key declaration on the orders table: +This declaration needs to be backed up by the proper foreign key declaration on the books table: ```ruby -class CreateOrders < ActiveRecord::Migration +class CreateBooks < ActiveRecord::Migration[5.0] def change - create_table :orders do |t| - t.datetime :order_date - t.string :order_number - t.integer :customer_id + create_table :books do |t| + t.datetime :published_at + t.string :book_number + t.integer :author_id end - add_index :orders, :customer_id + add_index :books, :author_id end end ``` @@ -575,18 +592,18 @@ If you create an association some time after you build the underlying model, you #### Creating Join Tables for `has_and_belongs_to_many` Associations -If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering. +If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering. -WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings). +WARNING: The precedence between model names is calculated using the `<=>` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings). Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations: ```ruby -class Assembly < ActiveRecord::Base +class Assembly < ApplicationRecord has_and_belongs_to_many :parts end -class Part < ActiveRecord::Base +class Part < ApplicationRecord has_and_belongs_to_many :assemblies end ``` @@ -594,7 +611,7 @@ end These need to be backed up by a migration to create the `assemblies_parts` table. This table should be created without a primary key: ```ruby -class CreateAssembliesPartsJoinTable < ActiveRecord::Migration +class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0] def change create_table :assemblies_parts, id: false do |t| t.integer :assembly_id @@ -607,7 +624,20 @@ class CreateAssembliesPartsJoinTable < ActiveRecord::Migration end ``` -We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled models IDs, or exceptions about conflicting IDs, chances are you forgot that bit. +We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled model IDs, or exceptions about conflicting IDs, chances are you forgot that bit. + +You can also use the method `create_join_table` + +```ruby +class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0] + def change + create_join_table :assemblies, :parts do |t| + t.index :assembly_id + t.index :part_id + end + end +end +``` ### Controlling Association Scope @@ -616,11 +646,11 @@ By default, associations look for objects only within the current module's scope ```ruby module MyApplication module Business - class Supplier < ActiveRecord::Base + class Supplier < ApplicationRecord has_one :account end - class Account < ActiveRecord::Base + class Account < ApplicationRecord belongs_to :supplier end end @@ -632,13 +662,13 @@ This will work fine, because both the `Supplier` and the `Account` class are def ```ruby module MyApplication module Business - class Supplier < ActiveRecord::Base + class Supplier < ApplicationRecord has_one :account end end module Billing - class Account < ActiveRecord::Base + class Account < ApplicationRecord belongs_to :supplier end end @@ -650,14 +680,14 @@ To associate a model with a model in a different namespace, you must specify the ```ruby module MyApplication module Business - class Supplier < ActiveRecord::Base + class Supplier < ApplicationRecord has_one :account, class_name: "MyApplication::Billing::Account" end end module Billing - class Account < ActiveRecord::Base + class Account < ApplicationRecord belongs_to :supplier, class_name: "MyApplication::Business::Supplier" end @@ -670,45 +700,45 @@ end It's normal for associations to work in two directions, requiring declaration on two different models: ```ruby -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author end ``` By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync: ```ruby -c = Customer.first -o = c.orders.first -c.first_name == o.customer.first_name # => true -c.first_name = 'Manny' -c.first_name == o.customer.first_name # => false +a = Author.first +b = a.books.first +a.first_name == b.author.first_name # => true +a.first_name = 'Manny' +a.first_name == b.author.first_name # => false ``` -This happens because c and o.customer are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations: +This happens because `a` and `b.author` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, inverse_of: :customer +class Author < ApplicationRecord + has_many :books, inverse_of: :author end -class Order < ActiveRecord::Base - belongs_to :customer, inverse_of: :orders +class Book < ApplicationRecord + belongs_to :author, inverse_of: :books end ``` -With these changes, Active Record will only load one copy of the customer object, preventing inconsistencies and making your application more efficient: +With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient: ```ruby -c = Customer.first -o = c.orders.first -c.first_name == o.customer.first_name # => true -c.first_name = 'Manny' -c.first_name == o.customer.first_name # => true +a = Author.first +b = a.books.first +a.first_name == b.author.first_name # => true +a.first_name = 'Manny' +a.first_name == b.author.first_name # => true ``` There are a few limitations to `inverse_of` support: @@ -724,10 +754,10 @@ Most associations with standard names will be supported. However, associations that contain the following options will not have their inverses set automatically: -* :conditions -* :through -* :polymorphic -* :foreign_key +* `:conditions` +* `:through` +* `:polymorphic` +* `:foreign_key` Detailed Association Reference ------------------------------ @@ -742,7 +772,7 @@ The `belongs_to` association creates a one-to-one match with another model. In d When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association: -* `association(force_reload = false)` +* `association` * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` @@ -751,39 +781,43 @@ When you declare a `belongs_to` association, the declaring class automatically g In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author end ``` -Each instance of the `Order` model will have these methods: +Each instance of the `Book` model will have these methods: ```ruby -customer -customer= -build_customer -create_customer -create_customer! +author +author= +build_author +create_author +create_author! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. -##### `association(force_reload = false)` +##### `association` The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`. ```ruby -@customer = @order.customer +@author = @book.author ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), pass `true` as the `force_reload` argument. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. + +```ruby +@author = @book.reload.author +``` ##### `association=(associate)` -The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associate object and setting this object's foreign key to the same value. +The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associated object and setting this object's foreign key to the same value. ```ruby -@order.customer = @customer +@book.author = @author ``` ##### `build_association(attributes = {})` @@ -791,8 +825,8 @@ The `association=` method assigns an associated object to this object. Behind th The `build_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this object's foreign key will be set, but the associated object will _not_ yet be saved. ```ruby -@customer = @order.build_customer(customer_number: 123, - customer_name: "John Doe") +@author = @book.build_author(author_number: 123, + author_name: "John Doe") ``` ##### `create_association(attributes = {})` @@ -800,8 +834,8 @@ The `build_association` method returns a new object of the associated type. This The `create_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through this object's foreign key will be set, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. ```ruby -@customer = @order.create_customer(customer_number: 123, - customer_name: "John Doe") +@author = @book.create_author(author_number: 123, + author_name: "John Doe") ``` ##### `create_association!(attributes = {})` @@ -814,8 +848,8 @@ Does the same as `create_association` above, but raises `ActiveRecord::RecordInv While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `belongs_to` association reference. Such customizations can easily be accomplished by passing options and scope blocks when you create the association. For example, this association uses two such options: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, dependent: :destroy, +class Book < ApplicationRecord + belongs_to :author, dependent: :destroy, counter_cache: true end ``` @@ -827,10 +861,12 @@ The `belongs_to` association supports these options: * `:counter_cache` * `:dependent` * `:foreign_key` +* `:primary_key` * `:inverse_of` * `:polymorphic` * `:touch` * `:validate` +* `:optional` ##### `:autosave` @@ -838,11 +874,11 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members ##### `:class_name` -If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if an order belongs to a customer, but the actual name of the model containing customers is `Patron`, you'd set things up this way: +If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a book belongs to an author, but the actual name of the model containing authors is `Patron`, you'd set things up this way: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, class_name: "Patron" +class Book < ApplicationRecord + belongs_to :author, class_name: "Patron" end ``` @@ -851,49 +887,58 @@ end The `:counter_cache` option can be used to make finding the number of belonging objects more efficient. Consider these models: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author end -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` -With these declarations, asking for the value of `@customer.orders.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model: +With these declarations, asking for the value of `@author.books.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, counter_cache: true +class Book < ApplicationRecord + belongs_to :author, counter_cache: true end -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` With this declaration, Rails will keep the cache value up to date, and then return that value in response to the `size` method. -Although the `:counter_cache` option is specified on the model that includes the `belongs_to` declaration, the actual column must be added to the _associated_ model. In the case above, you would need to add a column named `orders_count` to the `Customer` model. You can override the default column name if you need to: +Although the `:counter_cache` option is specified on the model that includes +the `belongs_to` declaration, the actual column must be added to the +_associated_ (`has_many`) model. In the case above, you would need to add a +column named `books_count` to the `Author` model. + +You can override the default column name by specifying a custom column name in +the `counter_cache` declaration instead of `true`. For example, to use +`count_of_books` instead of `books_count`: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, counter_cache: :count_of_orders +class Book < ApplicationRecord + belongs_to :author, counter_cache: :count_of_books end -class Customer < ActiveRecord::Base - has_many :orders, counter_cache: :count_of_orders +class Author < ApplicationRecord + has_many :books end ``` -NOTE: You only need to specify the :counter_cache option on the "has_many side" of the association when using a custom name for the counter cache. +NOTE: You only need to specify the :counter_cache option on the `belongs_to` +side of the association. Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`. ##### `:dependent` -If you set the `:dependent` option to: +Controls what happens to associated objects when their owner is destroyed: -* `:destroy`, when the object is destroyed, `destroy` will be called on its -associated objects. -* `:delete`, when the object is destroyed, all its associated objects will be -deleted directly from the database without calling their `destroy` method. +* `:destroy` causes the associated objects to also be destroyed. +* `:delete_all` causes the associated objects to be deleted directly from the database (callbacks are not executed). +* `:nullify` causes the foreign keys to be set to `NULL` (callbacks are not executed). +* `:restrict_with_exception` causes an exception to be raised if there are associated records. +* `:restrict_with_error` causes an error to be added to the owner if there are associated objects. WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database. @@ -902,25 +947,45 @@ WARNING: You should not specify this option on a `belongs_to` association that i By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, class_name: "Patron", +class Book < ApplicationRecord + belongs_to :author, class_name: "Patron", foreign_key: "patron_id" end ``` TIP: In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations. +##### `:primary_key` + +By convention, Rails assumes that the `id` column is used to hold the primary key +of its tables. The `:primary_key` option allows you to specify a different column. + +For example, given we have a `users` table with `guid` as the primary key. If we want a separate `todos` table to hold the foreign key `user_id` in the `guid` column, then we can use `primary_key` to achieve this like so: + +```ruby +class User < ApplicationRecord + self.primary_key = 'guid' # primary key is guid and not id +end + +class Todo < ApplicationRecord + belongs_to :user, primary_key: 'guid' +end +``` + +When we execute `@user.todos.create` then the `@todo` record will have its +`user_id` value as the `guid` value of `@user`. + ##### `:inverse_of` The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association. Does not work in combination with the `:polymorphic` options. ```ruby -class Customer < ActiveRecord::Base - has_many :orders, inverse_of: :customer +class Author < ApplicationRecord + has_many :books, inverse_of: :author end -class Order < ActiveRecord::Base - belongs_to :customer, inverse_of: :orders +class Book < ApplicationRecord + belongs_to :author, inverse_of: :books end ``` @@ -930,23 +995,23 @@ Passing `true` to the `:polymorphic` option indicates that this is a polymorphic ##### `:touch` -If you set the `:touch` option to `:true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed: +If you set the `:touch` option to `true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, touch: true +class Book < ApplicationRecord + belongs_to :author, touch: true end -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` -In this case, saving or destroying an order will update the timestamp on the associated customer. You can also specify a particular timestamp attribute to update: +In this case, saving or destroying an book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, touch: :orders_updated_at +class Book < ApplicationRecord + belongs_to :author, touch: :books_updated_at end ``` @@ -954,13 +1019,18 @@ end If you set the `:validate` option to `true`, then associated objects will be validated whenever you save this object. By default, this is `false`: associated objects will not be validated when this object is saved. +##### `:optional` + +If you set the `:optional` option to `true`, then the presence of the associated +object won't be validated. By default, this option is set to `false`. + #### Scopes for `belongs_to` There may be times when you wish to customize the query used by `belongs_to`. Such customizations can be achieved via a scope block. For example: ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, -> { where active: true }, +class Book < ApplicationRecord + belongs_to :author, -> { where active: true }, dependent: :destroy end ``` @@ -977,8 +1047,8 @@ You can use any of the standard [querying methods](active_record_querying.html) The `where` method lets you specify the conditions that the associated object must meet. ```ruby -class Order < ActiveRecord::Base - belongs_to :customer, -> { where active: true } +class book < ApplicationRecord + belongs_to :author, -> { where active: true } end ``` @@ -987,38 +1057,38 @@ end You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: ```ruby -class LineItem < ActiveRecord::Base - belongs_to :order +class LineItem < ApplicationRecord + belongs_to :book end -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author has_many :line_items end -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` -If you frequently retrieve customers directly from line items (`@line_item.order.customer`), then you can make your code somewhat more efficient by including customers in the association from line items to orders: +If you frequently retrieve authors directly from line items (`@line_item.book.author`), then you can make your code somewhat more efficient by including authors in the association from line items to books: ```ruby -class LineItem < ActiveRecord::Base - belongs_to :order, -> { includes :customer } +class LineItem < ApplicationRecord + belongs_to :book, -> { includes :author } end -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author has_many :line_items end -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` -NOTE: There's no need to use `includes` for immediate associations - that is, if you have `Order belongs_to :customer`, then the customer is eager-loaded automatically when it's needed. +NOTE: There's no need to use `includes` for immediate associations - that is, if you have `Book belongs_to :author`, then the author is eager-loaded automatically when it's needed. ##### `readonly` @@ -1035,8 +1105,8 @@ TIP: If you use the `select` method on a `belongs_to` association, you should al You can see if any associated objects exist by using the `association.nil?` method: ```ruby -if @order.customer.nil? - @msg = "No customer found for this order" +if @book.author.nil? + @msg = "No author found for this book" end ``` @@ -1052,7 +1122,7 @@ The `has_one` association creates a one-to-one match with another model. In data When you declare a `has_one` association, the declaring class automatically gains five methods related to the association: -* `association(force_reload = false)` +* `association` * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` @@ -1061,7 +1131,7 @@ When you declare a `has_one` association, the declaring class automatically gain In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account end ``` @@ -1078,7 +1148,7 @@ create_account! NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. -##### `association(force_reload = false)` +##### `association` The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`. @@ -1086,11 +1156,15 @@ The `association` method returns the associated object, if any. If no associated @account = @supplier.account ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), pass `true` as the `force_reload` argument. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. + +```ruby +@account = @supplier.reload.account +``` ##### `association=(associate)` -The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from this object and setting the associate object's foreign key to the same value. +The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from this object and setting the associated object's foreign key to the same value. ```ruby @supplier.account = @account @@ -1121,7 +1195,7 @@ Does the same as `create_association` above, but raises `ActiveRecord::RecordInv While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_one` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, class_name: "Billing", dependent: :nullify end ``` @@ -1153,7 +1227,7 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a supplier has an account, but the actual name of the model containing accounts is `Billing`, you'd set things up this way: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, class_name: "Billing" end ``` @@ -1171,15 +1245,15 @@ Controls what happens to the associated object when its owner is destroyed: It's necessary not to set or leave `:nullify` option for those associations that have `NOT NULL` database constraints. If you don't set `dependent` to destroy such associations you won't be able to change the associated object -because initial associated object foreign key will be set to unallowed `NULL` -value. +because the initial associated object's foreign key will be set to the +unallowed `NULL` value. ##### `:foreign_key` By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, foreign_key: "supp_id" end ``` @@ -1191,11 +1265,11 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options. ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, inverse_of: :supplier end -class Account < ActiveRecord::Base +class Account < ApplicationRecord belongs_to :supplier, inverse_of: :account end ``` @@ -1225,7 +1299,7 @@ If you set the `:validate` option to `true`, then associated objects will be val There may be times when you wish to customize the query used by `has_one`. Such customizations can be achieved via a scope block. For example: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, -> { where active: true } end ``` @@ -1242,7 +1316,7 @@ You can use any of the standard [querying methods](active_record_querying.html) The `where` method lets you specify the conditions that the associated object must meet. ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, -> { where "confirmed = 1" } end ``` @@ -1252,16 +1326,16 @@ end You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account end -class Account < ActiveRecord::Base +class Account < ApplicationRecord belongs_to :supplier belongs_to :representative end -class Representative < ActiveRecord::Base +class Representative < ApplicationRecord has_many :accounts end ``` @@ -1269,16 +1343,16 @@ end If you frequently retrieve representatives directly from suppliers (`@supplier.account.representative`), then you can make your code somewhat more efficient by including representatives in the association from suppliers to accounts: ```ruby -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_one :account, -> { includes :representative } end -class Account < ActiveRecord::Base +class Account < ApplicationRecord belongs_to :supplier belongs_to :representative end -class Representative < ActiveRecord::Base +class Representative < ApplicationRecord has_many :accounts end ``` @@ -1319,7 +1393,7 @@ The `has_many` association creates a one-to-many relationship with another model When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association: -* `collection(force_reload = false)` +* `collection` * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` @@ -1339,38 +1413,38 @@ When you declare a `has_many` association, the declaring class automatically gai In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: ```ruby -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end ``` -Each instance of the `Customer` model will have these methods: +Each instance of the `Author` model will have these methods: ```ruby -orders(force_reload = false) -orders<<(object, ...) -orders.delete(object, ...) -orders.destroy(object, ...) -orders=(objects) -order_ids -order_ids=(ids) -orders.clear -orders.empty? -orders.size -orders.find(...) -orders.where(...) -orders.exists?(...) -orders.build(attributes = {}, ...) -orders.create(attributes = {}) -orders.create!(attributes = {}) +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 = {}) ``` -##### `collection(force_reload = false)` +##### `collection` The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. ```ruby -@orders = @customer.orders +@books = @author.books ``` ##### `collection<<(object, ...)` @@ -1378,7 +1452,7 @@ The `collection` method returns an array of all of the associated objects. If th The `collection<<` method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model. ```ruby -@customer.orders << @order1 +@author.books << @book1 ``` ##### `collection.delete(object, ...)` @@ -1386,7 +1460,7 @@ The `collection<<` method adds one or more objects to the collection by setting The `collection.delete` method removes one or more objects from the collection by setting their foreign keys to `NULL`. ```ruby -@customer.orders.delete(@order1) +@author.books.delete(@book1) ``` WARNING: Additionally, objects will be destroyed if they're associated with `dependent: :destroy`, and deleted if they're associated with `dependent: :delete_all`. @@ -1396,7 +1470,7 @@ WARNING: Additionally, objects will be destroyed if they're associated with `dep The `collection.destroy` method removes one or more objects from the collection by running `destroy` on each object. ```ruby -@customer.orders.destroy(@order1) +@author.books.destroy(@book1) ``` WARNING: Objects will _always_ be removed from the database, ignoring the `:dependent` option. @@ -1410,7 +1484,7 @@ The `collection=` method makes the collection contain only the supplied objects, The `collection_singular_ids` method returns an array of the ids of the objects in the collection. ```ruby -@order_ids = @customer.order_ids +@book_ids = @author.book_ids ``` ##### `collection_singular_ids=(ids)` @@ -1419,15 +1493,22 @@ The `collection_singular_ids=` method makes the collection contain only the obje ##### `collection.clear` -The `collection.clear` method removes every object from the collection. This destroys the associated objects if they are associated with `dependent: :destroy`, deletes them directly from the database if `dependent: :delete_all`, and otherwise sets their foreign keys to `NULL`. +The `collection.clear` method removes all objects from the collection according to the strategy specified by the `dependent` option. If no option is given, it follows the default strategy. The default strategy for `has_many :through` associations is `delete_all`, and for `has_many` associations is to set the foreign keys to `NULL`. + +```ruby +@author.books.clear +``` + +WARNING: Objects will be deleted if they're associated with `dependent: :destroy`, +just like `dependent: :delete_all`. ##### `collection.empty?` The `collection.empty?` method returns `true` if the collection does not contain any associated objects. ```erb -<% if @customer.orders.empty? %> - No Orders Found +<% if @author.books.empty? %> + No Books Found <% end %> ``` @@ -1436,7 +1517,7 @@ The `collection.empty?` method returns `true` if the collection does not contain The `collection.size` method returns the number of objects in the collection. ```ruby -@order_count = @customer.orders.size +@book_count = @author.books.size ``` ##### `collection.find(...)` @@ -1444,7 +1525,7 @@ The `collection.size` method returns the number of objects in the collection. The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`. ```ruby -@open_orders = @customer.orders.find(1) +@available_books = @author.books.find(1) ``` ##### `collection.where(...)` @@ -1452,30 +1533,42 @@ The `collection.find` method finds objects within the collection. It uses the sa The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. ```ruby -@open_orders = @customer.orders.where(open: true) # No query yet -@open_order = @open_orders.first # Now the database will be queried +@available_books = @author.books.where(available: true) # No query yet +@available_book = @available_books.first # Now the database will be queried ``` ##### `collection.exists?(...)` -The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as `ActiveRecord::Base.exists?`. +The `collection.exists?` method checks whether an object meeting the supplied +conditions exists in the collection. It uses the same syntax and options as +[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). ##### `collection.build(attributes = {}, ...)` -The `collection.build` method returns one or more new objects of the associated type. These objects will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved. +The `collection.build` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved. ```ruby -@order = @customer.orders.build(order_date: Time.now, - order_number: "A12345") +@book = @author.books.build(published_at: Time.now, + book_number: "A12345") + +@books = @author.books.build([ + { published_at: Time.now, book_number: "A12346" }, + { published_at: Time.now, book_number: "A12347" } +]) ``` ##### `collection.create(attributes = {})` -The `collection.create` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. +The `collection.create` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. ```ruby -@order = @customer.orders.create(order_date: Time.now, - order_number: "A12345") +@book = @author.books.create(published_at: Time.now, + book_number: "A12345") + +@books = @author.books.create([ + { published_at: Time.now, book_number: "A12346" }, + { published_at: Time.now, book_number: "A12347" } +]) ``` ##### `collection.create!(attributes = {})` @@ -1487,8 +1580,8 @@ Does the same as `collection.create` above, but raises `ActiveRecord::RecordInva While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, dependent: :delete_all, validate: :false +class Author < ApplicationRecord + has_many :books, dependent: :delete_all, validate: false end ``` @@ -1517,15 +1610,16 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members ##### `:class_name` -If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a customer has many orders, but the actual name of the model containing orders is `Transaction`, you'd set things up this way: +If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if an author has many books, but the actual name of the model containing books is `Transaction`, you'd set things up this way: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, class_name: "Transaction" +class Author < ApplicationRecord + has_many :books, class_name: "Transaction" end ``` ##### `:counter_cache` + This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to). ##### `:dependent` @@ -1543,8 +1637,8 @@ Controls what happens to the associated objects when their owner is destroyed: By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, foreign_key: "cust_id" +class Author < ApplicationRecord + has_many :books, foreign_key: "cust_id" end ``` @@ -1555,12 +1649,12 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options. ```ruby -class Customer < ActiveRecord::Base - has_many :orders, inverse_of: :customer +class Author < ApplicationRecord + has_many :books, inverse_of: :author end -class Order < ActiveRecord::Base - belongs_to :customer, inverse_of: :orders +class Book < ApplicationRecord + belongs_to :author, inverse_of: :books end ``` @@ -1568,18 +1662,19 @@ end By convention, Rails assumes that the column used to hold the primary key of the association is `id`. You can override this and explicitly specify the primary key with the `:primary_key` option. -Let's say that `users` table has `id` as the primary_key but it also has -`guid` column. And the requirement is that `todos` table should hold -`guid` column value and not `id` value. This can be achieved like this +Let's say the `users` table has `id` as the primary_key but it also +has a `guid` column. The requirement is that the `todos` table should +hold the `guid` column value as the foreign key and not `id` +value. This can be achieved like this: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_many :todos, primary_key: :guid end ``` -Now if we execute `@user.todos.create` then `@todo` record will have -`user_id` value as the `guid` value of `@user`. +Now if we execute `@todo = @user.todos.create` then the `@todo` +record's `user_id` value will be the `guid` value of `@user`. ##### `:source` @@ -1603,8 +1698,8 @@ If you set the `:validate` option to `false`, then associated objects will not b There may be times when you wish to customize the query used by `has_many`. Such customizations can be achieved via a scope block. For example: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, -> { where processed: true } +class Author < ApplicationRecord + has_many :books, -> { where processed: true } end ``` @@ -1619,29 +1714,29 @@ You can use any of the standard [querying methods](active_record_querying.html) * `order` * `readonly` * `select` -* `uniq` +* `distinct` ##### `where` The `where` method lets you specify the conditions that the associated object must meet. ```ruby -class Customer < ActiveRecord::Base - has_many :confirmed_orders, -> { where "confirmed = 1" }, - class_name: "Order" +class Author < ApplicationRecord + has_many :confirmed_books, -> { where "confirmed = 1" }, + class_name: "Book" end ``` You can also set conditions via a hash: ```ruby -class Customer < ActiveRecord::Base - has_many :confirmed_orders, -> { where confirmed: true }, - class_name: "Order" +class Author < ApplicationRecord + has_many :confirmed_books, -> { where confirmed: true }, + class_name: "Book" end ``` -If you use a hash-style `where` option, then record creation via this association will be automatically scoped using the hash. In this case, using `@customer.confirmed_orders.create` or `@customer.confirmed_orders.build` will create orders where the confirmed column has the value `true`. +If you use a hash-style `where` option, then record creation via this association will be automatically scoped using the hash. In this case, using `@author.confirmed_books.create` or `@author.confirmed_books.build` will create books where the confirmed column has the value `true`. ##### `extending` @@ -1652,9 +1747,9 @@ The `extending` method specifies a named module to extend the association proxy. The `group` method supplies an attribute name to group the result set by, using a `GROUP BY` clause in the finder SQL. ```ruby -class Customer < ActiveRecord::Base - has_many :line_items, -> { group 'orders.id' }, - through: :orders +class Author < ApplicationRecord + has_many :line_items, -> { group 'books.id' }, + through: :books end ``` @@ -1663,34 +1758,34 @@ end You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: ```ruby -class Customer < ActiveRecord::Base - has_many :orders +class Author < ApplicationRecord + has_many :books end -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author has_many :line_items end -class LineItem < ActiveRecord::Base - belongs_to :order +class LineItem < ApplicationRecord + belongs_to :book end ``` -If you frequently retrieve line items directly from customers (`@customer.orders.line_items`), then you can make your code somewhat more efficient by including line items in the association from customers to orders: +If you frequently retrieve line items directly from authors (`@author.books.line_items`), then you can make your code somewhat more efficient by including line items in the association from authors to books: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, -> { includes :line_items } +class Author < ApplicationRecord + has_many :books, -> { includes :line_items } end -class Order < ActiveRecord::Base - belongs_to :customer +class Book < ApplicationRecord + belongs_to :author has_many :line_items end -class LineItem < ActiveRecord::Base - belongs_to :order +class LineItem < ApplicationRecord + belongs_to :book end ``` @@ -1699,10 +1794,10 @@ end The `limit` method lets you restrict the total number of objects that will be fetched through an association. ```ruby -class Customer < ActiveRecord::Base - has_many :recent_orders, - -> { order('order_date desc').limit(100) }, - class_name: "Order", +class Author < ApplicationRecord + has_many :recent_books, + -> { order('published_at desc').limit(100) }, + class_name: "Book", end ``` @@ -1715,8 +1810,8 @@ The `offset` method lets you specify the starting offset for fetching objects vi The `order` method dictates the order in which associated objects will be received (in the syntax used by an SQL `ORDER BY` clause). ```ruby -class Customer < ActiveRecord::Base - has_many :orders, -> { order "date_confirmed DESC" } +class Author < ApplicationRecord + has_many :books, -> { order "date_confirmed DESC" } end ``` @@ -1736,7 +1831,7 @@ Use the `distinct` method to keep the collection free of duplicates. This is mostly useful together with the `:through` option. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord has_many :readings has_many :articles, through: :readings end @@ -1775,11 +1870,21 @@ If you want to make sure that, upon insertion, all of the records in the persisted association are distinct (so that you can be sure that when you inspect the association that you will never find duplicate records), you should add a unique index on the table itself. For example, if you have a table named -`person_articles` and you want to make sure all the articles are unique, you could -add the following in a migration: +`readings` and you want to make sure the articles can only be added to a person once, +you could add the following in a migration: ```ruby -add_index :person_articles, :article, unique: true +add_index :readings, [:person_id, :article_id], unique: true +``` + +Once you have this unique index, attempting to add the article to a person twice +will raise an `ActiveRecord::RecordNotUnique` error: + +```ruby +person = Person.create(name: 'Honda') +article = Article.create(name: 'a1') +person.articles << article +person.articles << article # => ActiveRecord::RecordNotUnique ``` Note that checking for uniqueness using something like `include?` is subject @@ -1810,7 +1915,7 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association: -* `collection(force_reload = false)` +* `collection` * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` @@ -1830,7 +1935,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: ```ruby -class Part < ActiveRecord::Base +class Part < ApplicationRecord has_and_belongs_to_many :assemblies end ``` @@ -1838,7 +1943,7 @@ end Each instance of the `Part` model will have these methods: ```ruby -assemblies(force_reload = false) +assemblies assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) @@ -1863,7 +1968,7 @@ If the join table for a `has_and_belongs_to_many` association has additional col WARNING: The use of extra attributes on the join table in a `has_and_belongs_to_many` association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a `has_many :through` association instead of `has_and_belongs_to_many`. -##### `collection(force_reload = false)` +##### `collection` The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. @@ -1955,7 +2060,9 @@ The `collection.where` method finds objects within the collection based on the c ##### `collection.exists?(...)` -The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as `ActiveRecord::Base.exists?`. +The `collection.exists?` method checks whether an object meeting the supplied +conditions exists in the collection. It uses the same syntax and options as +[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). ##### `collection.build(attributes = {})` @@ -1982,9 +2089,9 @@ Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` i While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: ```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, autosave: true, - readonly: true +class Parts < ApplicationRecord + has_and_belongs_to_many :assemblies, -> { readonly }, + autosave: true end ``` @@ -1996,7 +2103,6 @@ The `has_and_belongs_to_many` association supports these options: * `:foreign_key` * `:join_table` * `:validate` -* `:readonly` ##### `:association_foreign_key` @@ -2005,7 +2111,7 @@ By convention, Rails assumes that the column in the join table used to hold the TIP: The `:foreign_key` and `:association_foreign_key` options are useful when setting up a many-to-many self-join. For example: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", @@ -2022,7 +2128,7 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a part has many assemblies, but the actual name of the model containing assemblies is `Gadget`, you'd set things up this way: ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, class_name: "Gadget" end ``` @@ -2032,7 +2138,7 @@ end By convention, Rails assumes that the column in the join table used to hold the foreign key pointing to this model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", @@ -2053,7 +2159,7 @@ If you set the `:validate` option to `false`, then associated objects will not b There may be times when you wish to customize the query used by `has_and_belongs_to_many`. Such customizations can be achieved via a scope block. For example: ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where active: true } end ``` @@ -2069,14 +2175,14 @@ You can use any of the standard [querying methods](active_record_querying.html) * `order` * `readonly` * `select` -* `uniq` +* `distinct` ##### `where` The `where` method lets you specify the conditions that the associated object must meet. ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where "factory = 'Seattle'" } end @@ -2085,7 +2191,7 @@ end You can also set conditions via a hash: ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where factory: 'Seattle' } end @@ -2102,7 +2208,7 @@ The `extending` method specifies a named module to extend the association proxy. The `group` method supplies an attribute name to group the result set by, using a `GROUP BY` clause in the finder SQL. ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { group "factory" } end ``` @@ -2116,7 +2222,7 @@ You can use the `includes` method to specify second-order associations that shou The `limit` method lets you restrict the total number of objects that will be fetched through an association. ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order("created_at DESC").limit(50) } end @@ -2131,7 +2237,7 @@ The `offset` method lets you specify the starting offset for fetching objects vi The `order` method dictates the order in which associated objects will be received (in the syntax used by an SQL `ORDER BY` clause). ```ruby -class Parts < ActiveRecord::Base +class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order "assembly_name ASC" } end @@ -2145,9 +2251,9 @@ If you use the `readonly` method, then the associated objects will be read-only The `select` method lets you override the SQL `SELECT` clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns. -##### `uniq` +##### `distinct` -Use the `uniq` method to remove duplicates from the collection. +Use the `distinct` method to remove duplicates from the collection. #### When are Objects Saved? @@ -2173,10 +2279,10 @@ Association callbacks are similar to normal callbacks, but they are triggered by You define association callbacks by adding options to the association declaration. For example: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, before_add: :check_credit_limit +class Author < ApplicationRecord + has_many :books, before_add: :check_credit_limit - def check_credit_limit(order) + def check_credit_limit(book) ... end end @@ -2187,15 +2293,15 @@ Rails passes the object being added or removed to the callback. You can stack callbacks on a single event by passing them as an array: ```ruby -class Customer < ActiveRecord::Base - has_many :orders, +class Author < ApplicationRecord + has_many :books, before_add: [:check_credit_limit, :calculate_shipping_charges] - def check_credit_limit(order) + def check_credit_limit(book) ... end - def calculate_shipping_charges(order) + def calculate_shipping_charges(book) ... end end @@ -2208,10 +2314,10 @@ If a `before_add` callback throws an exception, the object does not get added to You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example: ```ruby -class Customer < ActiveRecord::Base - has_many :orders do - def find_by_order_prefix(order_number) - find_by(region_id: order_number[0..2]) +class Author < ApplicationRecord + has_many :books do + def find_by_book_prefix(book_number) + find_by(category_id: book_number[0..2]) end end end @@ -2226,11 +2332,11 @@ module FindRecentExtension end end -class Customer < ActiveRecord::Base - has_many :orders, -> { extending FindRecentExtension } +class Author < ApplicationRecord + has_many :books, -> { extending FindRecentExtension } end -class Supplier < ActiveRecord::Base +class Supplier < ApplicationRecord has_many :deliveries, -> { extending FindRecentExtension } end ``` @@ -2240,3 +2346,67 @@ Extensions can refer to the internals of the association proxy using these three * `proxy_association.owner` returns the object that the association is a part of. * `proxy_association.reflection` returns the reflection object that describes the association. * `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`. + +Single Table Inheritance +------------------------ + +Sometimes, you may want to share fields and behavior between different models. +Let's say we have Car, Motorcycle and Bicycle models. We will want to share +the `color` and `price` fields and some methods for all of them, but having some +specific behavior for each, and separated controllers too. + +Rails makes this quite easy. First, let's generate the base Vehicle model: + +```bash +$ rails generate model vehicle type:string color:string price:decimal{10.2} +``` + +Did you note we are adding a "type" field? Since all models will be saved in a +single database table, Rails will save in this column the name of the model that +is being saved. In our example, this can be "Car", "Motorcycle" or "Bicycle." +STI won't work without a "type" field in the table. + +Next, we will generate the three models that inherit from Vehicle. For this, +we can use the `--parent=PARENT` option, which will generate a model that +inherits from the specified parent and without equivalent migration (since the +table already exists). + +For example, to generate the Car model: + +```bash +$ rails generate model car --parent=Vehicle +``` + +The generated model will look like this: + +```ruby +class Car < Vehicle +end +``` + +This means that all behavior added to Vehicle is available for Car too, as +associations, public methods, etc. + +Creating a car will save it in the `vehicles` table with "Car" as the `type` field: + +```ruby +Car.create(color: 'Red', price: 10000) +``` + +will generate the following SQL: + +```sql +INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000) +``` + +Querying car records will just search for vehicles that are cars: + +```ruby +Car.all +``` + +will run a query like: + +```sql +SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car') +``` diff --git a/source/constant_autoloading_and_reloading.md b/source/autoloading_and_reloading_constants.md similarity index 94% rename from source/constant_autoloading_and_reloading.md rename to source/autoloading_and_reloading_constants.md index c392103..6165702 100644 --- a/source/constant_autoloading_and_reloading.md +++ b/source/autoloading_and_reloading_constants.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Autoloading and Reloading Constants =================================== @@ -78,7 +80,8 @@ end ``` The *nesting* at any given place is the collection of enclosing nested class and -module objects outwards. For example, in the previous example, the nesting at +module objects outwards. The nesting at any given place can be inspected with +`Module.nesting`. For example, in the previous example, the nesting at (1) is ```ruby @@ -111,6 +114,16 @@ certain nesting does not necessarily correlate with the namespaces at the spot. Even more, they are totally independent, take for instance ```ruby +module X + module Y + end +end + +module A + module B + end +end + module X::Y module A::B # (3) @@ -138,9 +151,10 @@ executed, and popped after it. * A singleton class opened with `class << object` gets pushed, and popped later. -* When any of the `*_eval` family of methods is called using a string argument, +* When `instance_eval` is called using a string argument, the singleton class of the receiver is pushed to the nesting of the eval'ed -code. +code. When `class_eval` or `module_eval` is called using a string argument, +the receiver is pushed to the nesting of the eval'ed code. * The nesting at the top-level of code interpreted by `Kernel#load` is empty unless the `load` call receives a true value as second argument, in which case @@ -151,8 +165,6 @@ the blocks that may be passed to `Class.new` and `Module.new` do not get the class or module being defined pushed to their nesting. That's one of the differences between defining classes and modules in one way or another. -The nesting at any given place can be inspected with `Module.nesting`. - ### Class and Module Definitions are Constant Assignments Let's suppose the following snippet creates a class (rather than reopening it): @@ -169,14 +181,14 @@ constant. That is, ```ruby -class Project < ActiveRecord::Base +class Project < ApplicationRecord end ``` performs a constant assignment equivalent to ```ruby -Project = Class.new(ActiveRecord::Base) +Project = Class.new(ApplicationRecord) ``` including setting the name of the class as a side-effect: @@ -234,7 +246,7 @@ end ``` `Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If -all is good, the constant evaluates to an object that responds to `all`. +all is good, the constant is evaluated to an object that responds to `all`. That is why we talk about *constant* autoloading, Rails has the ability to load constants on the fly. @@ -256,7 +268,7 @@ module Colors end ``` -First, when the `module` keyword is processed the interpreter creates a new +First, when the `module` keyword is processed, the interpreter creates a new entry in the constant table of the class object stored in the `Object` constant. Said entry associates the name "Colors" to a newly created module object. Furthermore, the interpreter sets the name of the new module object to be the @@ -289,7 +301,9 @@ order. The ancestors of those elements are ignored. 2. If not found, then the algorithm walks up the ancestor chain of the cref. -3. If not found, `const_missing` is invoked on the cref. The default +3. If not found and the cref is a module, the constant is looked up in `Object`. + +4. If not found, `const_missing` is invoked on the cref. The default implementation of `const_missing` raises `NameError`, but it can be overridden. Rails autoloading **does not emulate this algorithm**, but its starting point is @@ -312,7 +326,7 @@ relative: `::Billing::Invoice`. That would force `Billing` to be looked up only as a top-level constant. `Invoice` on the other hand is qualified by `Billing` and we are going to see -its resolution next. Let's call *parent* to that qualifying class or module +its resolution next. Let's define *parent* to be that qualifying class or module object, that is, `Billing` in the example above. The algorithm for qualified constants goes like this: @@ -328,7 +342,7 @@ checked. Rails autoloading **does not emulate this algorithm**, but its starting point is the name of the constant to be autoloaded, and the parent. See more in -[Qualified References](#qualified-references). +[Qualified References](#autoloading-algorithms-qualified-references). Vocabulary @@ -435,9 +449,10 @@ Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which to look up `post.rb`. That collection is called `autoload_paths` and by default it contains: -* All subdirectories of `app` in the application and engines. For example, - `app/controllers`. They do not need to be the default ones, any custom - directories like `app/workers` belong automatically to `autoload_paths`. +* All subdirectories of `app` in the application and engines present at boot + time. For example, `app/controllers`. They do not need to be the default + ones, any custom directories like `app/workers` belong automatically to + `autoload_paths`. * Any existing second level directories called `app/*/concerns` in the application and engines. @@ -449,9 +464,11 @@ Also, this collection is configurable via `config.autoload_paths`. For example, by adding this to `config/application.rb`: ```ruby -config.autoload_paths += "#{Rails.root}/lib" +config.autoload_paths << "#{Rails.root}/lib" ``` +`config.autoload_paths` is not changeable from environment-specific configuration files. + The value of `autoload_paths` can be inspected. In a just generated application it is (edited): @@ -508,7 +525,7 @@ On the contrary, if `ApplicationController` is unknown, the constant is considered missing and an autoload is going to be attempted by Rails. In order to load `ApplicationController`, Rails iterates over `autoload_paths`. -First checks if `app/assets/application_controller.rb` exists. If it does not, +First it checks if `app/assets/application_controller.rb` exists. If it does not, which is normally the case, it continues and finds `app/controllers/application_controller.rb`. @@ -608,7 +625,7 @@ file is loaded. If the file actually defines `Post` all is fine, otherwise ### Qualified References When a qualified constant is missing Rails does not look for it in the parent -namespaces. But there is a caveat: When a constant is missing, Rails is +namespaces. But there is a caveat: when a constant is missing, Rails is unable to tell if the trigger was a relative reference or a qualified one. For example, consider @@ -669,7 +686,7 @@ to trigger the heuristic is defined in the conflicting place. ### Automatic Modules When a module acts as a namespace, Rails does not require the application to -defines a file for it, a directory matching the namespace is enough. +define a file for it, a directory matching the namespace is enough. Suppose an application has a back office whose controllers are stored in `app/controllers/admin`. If the `Admin` module is not yet loaded when @@ -683,12 +700,12 @@ creates an empty module and assigns it to the `Admin` constant on the fly. ### Generic Procedure Relative references are reported to be missing in the cref where they were hit, -and qualified references are reported to be missing in their parent. (See +and qualified references are reported to be missing in their parent (see [Resolution Algorithm for Relative Constants](#resolution-algorithm-for-relative-constants) at the beginning of this guide for the definition of *cref*, and [Resolution Algorithm for Qualified Constants](#resolution-algorithm-for-qualified-constants) for the definition of -*parent*.) +*parent*). The procedure to autoload constant `C` in an arbitrary situation is as follows: @@ -774,7 +791,7 @@ Constant Reloading When `config.cache_classes` is false Rails is able to reload autoloaded constants. -For example, in you're in a console session and edit some file behind the +For example, if you're in a console session and edit some file behind the scenes, the code can be reloaded with the `reload!` command: ``` @@ -866,8 +883,8 @@ end ``` To resolve `User` Ruby checks `Admin` in the former case, but it does not in -the latter because it does not belong to the nesting. (See [Nesting](#nesting) -and [Resolution Algorithms](#resolution-algorithms).) +the latter because it does not belong to the nesting (see [Nesting](#nesting) +and [Resolution Algorithms](#resolution-algorithms)). Unfortunately Rails autoloading does not know the nesting in the spot where the constant was missing and so it is not able to act as Ruby would. In particular, @@ -896,7 +913,7 @@ these classes: ```ruby # app/models/polygon.rb -class Polygon < ActiveRecord::Base +class Polygon < ApplicationRecord end # app/models/triangle.rb @@ -971,7 +988,7 @@ root class: ```ruby # app/models/polygon.rb -class Polygon < ActiveRecord::Base +class Polygon < ApplicationRecord end require_dependency ‘square’ ``` @@ -1171,7 +1188,8 @@ class Hotel end ``` -the expression `Hotel::Image` is ambiguous, depends on the execution path. +the expression `Hotel::Image` is ambiguous because it depends on the execution +path. As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has @@ -1280,8 +1298,8 @@ c.user # surprisingly fine, User c.user # NameError: uninitialized constant C::User ``` -because it detects a parent namespace already has the constant (see [Qualified -References](#qualified-references).) +because it detects that a parent namespace already has the constant (see [Qualified +References](#autoloading-algorithms-qualified-references)). As with pure Ruby, within the body of a direct descendant of `BasicObject` use always absolute constant paths: diff --git a/source/caching_with_rails.md b/source/caching_with_rails.md index cbcd053..745f09f 100644 --- a/source/caching_with_rails.md +++ b/source/caching_with_rails.md @@ -1,12 +1,26 @@ -Caching with Rails: An overview +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +Caching with Rails: An Overview =============================== -This guide will teach you what you need to know about avoiding that expensive round-trip to your database and returning what you need to return to the web clients in the shortest time possible. +This guide is an introduction to speeding up your Rails application with caching. + +Caching means to store content generated during the request-response cycle and +to reuse it when responding to similar requests. + +Caching is often the most effective way to boost an application's performance. +Through caching, web sites running on a single server with a single database +can sustain a load of thousands of concurrent users. + +Rails provides a set of caching features out of the box. This guide will teach +you the scope and purpose of each one of them. Master these techniques and your +Rails applications can serve millions of views without exorbitant response times +or server bills. After reading this guide, you will know: -* Page and action caching (moved to separate gems as of Rails 4). -* Fragment caching. +* Fragment and Russian doll caching. +* How to manage the caching dependencies. * Alternative cache stores. * Conditional GET support. @@ -16,21 +30,34 @@ Basic Caching ------------- This is an introduction to three types of caching techniques: page, action and -fragment caching. Rails provides by default fragment caching. In order to use -page and action caching, you will need to add `actionpack-page_caching` and +fragment caching. By default Rails provides fragment caching. In order to use +page and action caching you will need to add `actionpack-page_caching` and `actionpack-action_caching` to your Gemfile. -To start playing with caching you'll want to ensure that `config.action_controller.perform_caching` is set to `true`, if you're running in development mode. This flag is normally set in the corresponding `config/environments/*.rb` and caching is disabled by default for development and test, and enabled for production. +By default, caching is only enabled in your production environment. To play +around with caching locally you'll want to enable caching in your local +environment by setting `config.action_controller.perform_caching` to `true` in +the relevant `config/environments/*.rb` file: ```ruby config.action_controller.perform_caching = true ``` +NOTE: Changing the value of `config.action_controller.perform_caching` will +only have an effect on the caching provided by the Action Controller component. +For instance, it will not impact low-level caching, that we address +[below](#low-level-caching). + ### Page Caching -Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or NGINX), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. +Page caching is a Rails mechanism which allows the request for a generated page +to be fulfilled by the webserver (i.e. Apache or NGINX) without having to go +through the entire Rails stack. While this is super fast it can't be applied to +every situation (such as pages that need authentication). Also, because the +webserver is serving a file directly from the filesystem you will need to +implement cache expiration. -INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). ### Action Caching @@ -40,116 +67,215 @@ INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_c ### Fragment Caching -Life would be perfect if we could get away with caching the entire contents of a page or action and serving it out to the world. Unfortunately, dynamic web applications usually build pages with a variety of components not all of which have the same caching characteristics. In order to address such a dynamically created page where different parts of the page need to be cached and expired differently, Rails provides a mechanism called Fragment Caching. +Dynamic web applications usually build pages with a variety of components not +all of which have the same caching characteristics. When different parts of the +page need to be cached and expired separately you can use Fragment Caching. Fragment Caching allows a fragment of view logic to be wrapped in a cache block and served out of the cache store when the next request comes in. -As an example, if you wanted to show all the orders placed on your website in real time and didn't want to cache that part of the page, but did want to cache the part of the page which lists all products available, you could use this piece of code: +For example, if you wanted to cache each product on a page, you could use this +code: ```html+erb -<% Order.find_recent.each do |o| %> - <%= o.buyer.name %> bought <%= o.product.name %> +<% @products.each do |product| %> + <% cache product do %> + <%= render product %> + <% end %> <% end %> +``` -<% cache do %> - All available products: - <% Product.all.each do |p| %> - <%= link_to p.name, product_url(https://github.com/p) %> - <% end %> +When your application receives its first request to this page, Rails will write +a new cache entry with a unique key. A key looks something like this: + +``` +views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901 +``` + +The number in the middle is the `product_id` followed by the timestamp value in +the `updated_at` attribute of the product record. Rails uses the timestamp value +to make sure it is not serving stale data. If the value of `updated_at` has +changed, a new key will be generated. Then Rails will write a new cache to that +key, and the old cache written to the old key will never be used again. This is +called key-based expiration. + +Cache fragments will also be expired when the view fragment changes (e.g., the +HTML in the view changes). The string of characters at the end of the key is a +template tree digest. It is an md5 hash computed based on the contents of the +view fragment you are caching. If you change the view fragment, the md5 hash +will change, expiring the existing file. + +TIP: Cache stores like Memcached will automatically delete old cache files. + +If you want to cache a fragment under certain conditions, you can use +`cache_if` or `cache_unless`: + +```erb +<% cache_if admin?, product do %> + <%= render product %> <% end %> ``` -The cache block in our example will bind to the action that called it and is written out to the same place as the Action Cache, which means that if you want to cache multiple fragments per action, you should provide an `action_suffix` to the cache call: +#### Collection caching + +The `render` helper can also cache individual templates rendered for a collection. +It can even one up the previous example with `each` by reading all cache +templates at once instead of one by one. This is done by passing `cached: true` when rendering the collection: ```html+erb -<% cache(action: 'recent', action_suffix: 'all_products') do %> - All available products: +<%= render partial: 'products/product', collection: @products, cached: true %> ``` -and you can expire it using the `expire_fragment` method, like so: +All cached templates from previous renders will be fetched at once with much +greater speed. Additionally, the templates that haven't yet been cached will be +written to cache and multi fetched on the next render. -```ruby -expire_fragment(controller: 'products', action: 'recent', action_suffix: 'all_products') + +### Russian Doll Caching + +You may want to nest cached fragments inside other cached fragments. This is +called Russian doll caching. + +The advantage of Russian doll caching is that if a single product is updated, +all the other inner fragments can be reused when regenerating the outer +fragment. + +As explained in the previous section, a cached file will expire if the value of +`updated_at` changes for a record on which the cached file directly depends. +However, this will not expire any cache the fragment is nested within. + +For example, take the following view: + +```erb +<% cache product do %> + <%= render product.games %> +<% end %> ``` -If you don't want the cache block to bind to the action that called it, you can also use globally keyed fragments by calling the `cache` method with a key: +Which in turn renders this view: ```erb -<% cache('all_available_products') do %> - All available products: +<% cache game do %> + <%= render game %> <% end %> ``` -This fragment is then available to all actions in the `ProductsController` using the key and can be expired the same way: +If any attribute of game is changed, the `updated_at` value will be set to the +current time, thereby expiring the cache. However, because `updated_at` +will not be changed for the product object, that cache will not be expired and +your app will serve stale data. To fix this, we tie the models together with +the `touch` method: ```ruby -expire_fragment('all_available_products') +class Product < ApplicationRecord + has_many :games +end + +class Game < ApplicationRecord + belongs_to :product, touch: true +end ``` -If you want to avoid expiring the fragment manually, whenever an action updates a product, you can define a helper method: + +With `touch` set to true, any action which changes `updated_at` for a game +record will also change it for the associated product, thereby expiring the +cache. + +### Managing dependencies + +In order to correctly invalidate the cache, you need to properly define the +caching dependencies. Rails is clever enough to handle common cases so you don't +have to specify anything. However, sometimes, when you're dealing with custom +helpers for instance, you need to explicitly define them. + +#### Implicit dependencies + +Most template dependencies can be derived from calls to `render` in the template +itself. Here are some examples of render calls that `ActionView::Digestor` knows +how to decode: ```ruby -module ProductsHelper - def cache_key_for_products - count = Product.count - max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number) - "products/all-#{count}-#{max_updated_at}" - end -end +render partial: "comments/comment", collection: commentable.comments +render "comments/comments" +render 'comments/comments' +render('comments/comments') + +render "header" => render("comments/header") + +render(@topic) => render("topics/topic") +render(topics) => render("topics/topic") +render(message.topics) => render("topics/topic") ``` -This method generates a cache key that depends on all products and can be used in the view: +On the other hand, some calls need to be changed to make caching work properly. +For instance, if you're passing a custom collection, you'll need to change: -```erb -<% cache(cache_key_for_products) do %> - All available products: -<% end %> +```ruby +render @project.documents.where(published: true) ``` -If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless` +to: -```erb -<% cache_if (condition, cache_key_for_products) do %> - All available products: -<% end %> +```ruby +render partial: "documents/document", collection: @project.documents.where(published: true) ``` -You can also use an Active Record model as the cache key: +#### Explicit dependencies -```erb -<% Product.all.each do |p| %> - <% cache(p) do %> - <%= link_to p.name, product_url(https://github.com/p) %> - <% end %> -<% end %> +Sometimes you'll have template dependencies that can't be derived at all. This +is typically the case when rendering happens in helpers. Here's an example: + +```html+erb +<%= render_sortable_todolists @project.todolists %> ``` -Behind the scenes, a method called `cache_key` will be invoked on the model and it returns a string like `products/23-20130109142513`. The cache key includes the model name, the id and finally the updated_at timestamp. Thus it will automatically generate a new fragment when the product is updated because the key changes. +You'll need to use a special comment format to call those out: -You can also combine the two schemes which is called "Russian Doll Caching": +```html+erb +<%# Template Dependency: todolists/todolist %> +<%= render_sortable_todolists @project.todolists %> +``` -```erb -<% cache(cache_key_for_products) do %> - All available products: - <% Product.all.each do |p| %> - <% cache(p) do %> - <%= link_to p.name, product_url(https://github.com/p) %> - <% end %> - <% end %> +In some cases, like a single table inheritance setup, you might have a bunch of +explicit dependencies. Instead of writing every template out, you can use a +wildcard to match any template in a directory: + +```html+erb +<%# Template Dependency: events/* %> +<%= render_categorizable_events @person.events %> +``` + +As for collection caching, if the partial template doesn't start with a clean +cache call, you can still benefit from collection caching by adding a special +comment format anywhere in the template, like: + +```html+erb +<%# Template Collection: notification %> +<% my_helper_that_calls_cache(some_arg, notification) do %> + <%= notification.name %> <% end %> ``` -It's called "Russian Doll Caching" because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment. +#### External dependencies + +If you use a helper method, for example, inside a cached block and you then update +that helper, you'll have to bump the cache as well. It doesn't really matter how +you do it, but the md5 of the template file must change. One recommendation is to +simply be explicit in a comment, like: + +```html+erb +<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %> +<%= some_helper_method(person) %> +``` ### Low-Level Caching -Sometimes you need to cache a particular value or query result, instead of caching view fragments. Rails caching mechanism works great for storing __any__ kind of information. +Sometimes you need to cache a particular value or query result instead of caching view fragments. Rails' caching mechanism works great for storing __any__ kind of information. The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, the result of the block will be cached to the given key and the result is returned. Consider the following example. An application has a `Product` model with an instance method that looks up the product’s price on a competing website. The data returned by this method would be perfect for low-level caching: ```ruby -class Product < ActiveRecord::Base +class Product < ApplicationRecord def competing_price Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do Competitor::API.find_price(id) @@ -158,11 +284,14 @@ class Product < ActiveRecord::Base end ``` -NOTE: Notice that in this example we used `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key. +NOTE: Notice that in this example we used the `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key. ### SQL Caching -Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again. +Query caching is a Rails feature that caches the result set returned by each +query. If Rails encounters the same query again for that request, it will use +the cached result set as opposed to running the query against the database +again. For example: @@ -182,19 +311,27 @@ class ProductsController < ApplicationController end ``` +The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory. + +However, it's important to note that query caches are created at the start of +an action and destroyed at the end of that action and thus persist only for the +duration of the action. If you'd like to store query results in a more +persistent fashion, you can with low level caching. + Cache Stores ------------ -Rails provides different stores for the cached data created by **action** and **fragment** caches. - -TIP: Page caches are always stored on disk. +Rails provides different stores for the cached data (apart from SQL and page +caching). ### Configuration -You can set up your application's default cache store by calling `config.cache_store=` in the Application definition inside your `config/application.rb` file or in an Application.configure block in an environment specific configuration file (i.e. `config/environments/*.rb`). The first argument will be the cache store to use and the rest of the argument will be passed as arguments to the cache store constructor. +You can set up your application's default cache store by setting the +`config.cache_store` configuration option. Other parameters can be passed as +arguments to the cache store's constructor: ```ruby -config.cache_store = :memory_store +config.cache_store = :memory_store, { size: 64.megabytes } ``` NOTE: Alternatively, you can call `ActionController::Base.cache_store` outside of a configuration block. @@ -213,21 +350,42 @@ There are some common options used by all cache implementations. These can be pa * `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network. -* `:compress_threshold` - This options is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes. +* `:compress_threshold` - This option is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes. * `:expires_in` - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache. * `:race_condition_ttl` - This option is used in conjunction with the `:expires_in` option. It will prevent race conditions when cache entries expire by preventing multiple processes from simultaneously regenerating the same entry (also known as the dog pile effect). This option sets the number of seconds that an expired entry can be reused while a new value is being regenerated. It's a good practice to set this value if you use the `:expires_in` option. +#### Custom Cache Stores + +You can create your own custom cache store by simply extending +`ActiveSupport::Cache::Store` and implementing the appropriate methods. This way, +you can swap in any number of caching technologies into your Rails application. + +To use a custom cache store, simply set the cache store to a new instance of your +custom class. + +```ruby +config.cache_store = MyCacheStore.new +``` + ### ActiveSupport::Cache::MemoryStore -This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the `:size` options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed. +This cache store keeps entries in memory in the same Ruby process. The cache +store has a bounded size specified by sending the `:size` option to the +initializer (default is 32Mb). When the cache exceeds the allotted size, a +cleanup will occur and the least recently used entries will be removed. ```ruby config.cache_store = :memory_store, { size: 64.megabytes } ``` -If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments. +If you're running multiple Ruby on Rails server processes (which is the case +if you're using mongrel_cluster or Phusion Passenger), then your Rails server +process instances won't be able to share cache data with each other. This cache +store is not appropriate for large application deployments. However, it can +work well for small, low traffic sites with only a couple of server processes, +as well as development and test environments. ### ActiveSupport::Cache::FileStore @@ -237,9 +395,13 @@ This cache store uses the file system to store entries. The path to the director config.cache_store = :file_store, "/path/to/cache/directory" ``` -With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts. +With this cache store, multiple server processes on the same host can share a +cache. The cache store is appropriate for low to medium traffic sites that are +served off one or two hosts. Server processes running on different hosts could +share a cache by using a shared file system, but that setup is not recommended. -Note that the cache will grow until the disk is full unless you periodically clear out old entries. +As the cache will grow until the disk is full, it is recommended to +periodically clear out old entries. This is the default cache store implementation. @@ -247,65 +409,32 @@ This is the default cache store implementation. This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very high performance and redundancy. -When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites. +When initializing the cache, you need to specify the addresses for all +memcached servers in your cluster. If none are specified, it will assume +memcached is running on localhost on the default port, but this is not an ideal +setup for larger sites. -The `write` and `fetch` methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify `:raw` to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operation like `increment` and `decrement` only on raw values. You can also specify `:unless_exist` if you don't want memcached to overwrite an existing entry. +The `write` and `fetch` methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify `:raw` to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operations like `increment` and `decrement` only on raw values. You can also specify `:unless_exist` if you don't want memcached to overwrite an existing entry. ```ruby config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com" ``` -### ActiveSupport::Cache::EhcacheStore - -If you are using JRuby you can use Terracotta's Ehcache as the cache store for your application. Ehcache is an open source Java cache that also offers an enterprise version with increased scalability, management, and commercial support. You must first install the jruby-ehcache-rails3 gem (version 1.1.0 or later) to use this cache store. - -```ruby -config.cache_store = :ehcache_store -``` - -When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache). - -In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: - -| Property | Argument Type | Description | -| --------------------------- | ------------------- | ----------------------------------------------------------- | -| elementEvictionData | ElementEvictionData | Sets this element's eviction data instance. | -| eternal | boolean | Sets whether the element is eternal. | -| timeToIdle, tti | int | Sets time to idle | -| timeToLive, ttl, expires_in | int | Sets time to Live | -| version | long | Sets the version attribute of the ElementAttributes object. | - -These options are passed to the `write` method as Hash options using either camelCase or underscore notation, as in the following examples: - -```ruby -Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds) -caches_action :index, expires_in: 60.seconds, unless_exist: true -``` - -For more information about Ehcache, see [http://ehcache.org/](http://ehcache.org/) . -For more information about Ehcache for JRuby and Rails, see [http://ehcache.org/documentation/jruby.html](http://ehcache.org/documentation/jruby.html) - ### ActiveSupport::Cache::NullStore -This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache`, but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss. +This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss. ```ruby config.cache_store = :null_store ``` -### Custom Cache Stores - -You can create your own custom cache store by simply extending `ActiveSupport::Cache::Store` and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application. - -To use a custom cache store, simple set the cache store to a new instance of the class. - -```ruby -config.cache_store = MyCacheStore.new -``` - -### Cache Keys +Cache Keys +---------- -The keys used in a cache can be any object that responds to either `:cache_key` or to `:to_param`. You can implement the `:cache_key` method on your classes if you need to generate custom keys. Active Record will generate keys based on the class name and record id. +The keys used in a cache can be any object that responds to either `cache_key` or +`to_param`. You can implement the `cache_key` method on your classes if you need +to generate custom keys. Active Record will generate keys based on the class name +and record id. You can use Hashes and Arrays of values as cache keys. @@ -314,7 +443,12 @@ You can use Hashes and Arrays of values as cache keys. Rails.cache.read(site: "mysite", owners: [owner_1, owner_2]) ``` -The keys you use on `Rails.cache` will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with `Rails.cache` and then try to pull them out with the `memcache-client` gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules. +The keys you use on `Rails.cache` will not be the same as those actually used with +the storage engine. They may be modified with a namespace or altered to fit +technology backend constraints. This means, for instance, that you can't save +values with `Rails.cache` and then try to pull them out with the `dalli` gem. +However, you also don't need to worry about exceeding the memcached size limit or +violating syntax rules. Conditional GET support ----------------------- @@ -347,7 +481,7 @@ class ProductsController < ApplicationController end ``` -Instead of an options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`: +Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`: ```ruby class ProductsController < ApplicationController @@ -377,3 +511,43 @@ class ProductsController < ApplicationController end end ``` + +### Strong v/s Weak ETags + +Rails generates weak ETags by default. Weak ETags allow semantically equivalent +responses to have the same ETags, even if their bodies do not match exactly. +This is useful when we don't want the page to be regenerated for minor changes in +response body. + +Weak ETags have a leading `W/` to differentiate them from strong ETags. + +``` + W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag + "618bbc92e2d35ea1945008b42799b0e7" → Strong ETag +``` + +Unlike weak ETag, strong ETag implies that response should be exactly the same +and byte by byte identical. Useful when doing Range requests within a +large video or PDF file. Some CDNs support only strong ETags, like Akamai. +If you absolutely need to generate a strong ETag, it can be done as follows. + +```ruby + class ProductsController < ApplicationController + def show + @product = Product.find(params[:id]) + fresh_when last_modified: @product.published_at.utc, strong_etag: @product + end + end +``` + +You can also set the strong ETag directly on the response. + +```ruby + response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7" +``` + +References +---------- + +* [DHH's article on key-based expiration](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) +* [Ryan Bates' Railscast on cache digests](http://railscasts.com/episodes/387-cache-digests) diff --git a/source/command_line.md b/source/command_line.md index 713c91d..0e6d119 100644 --- a/source/command_line.md +++ b/source/command_line.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Rails Command Line ====================== @@ -19,7 +21,7 @@ There are a few commands that are absolutely critical to your everyday usage of * `rails console` * `rails server` -* `rake` +* `bin/rails` * `rails generate` * `rails dbconsole` * `rails new app_name` @@ -37,7 +39,7 @@ INFO: You can install the rails gem by typing `gem install rails`, if you don't ```bash $ rails new commandsapp create - create README.rdoc + create README.md create Rakefile create config.ru create .gitignore @@ -53,20 +55,22 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin ### `rails server` -The `rails server` command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to access your application through a web browser. +The `rails server` command launches a web server named Puma which comes bundled with Rails. You'll use this any time you want to access your application through a web browser. With no further work, `rails server` will run our new shiny Rails app: ```bash $ cd commandsapp $ bin/rails server -=> Booting WEBrick -=> Rails 4.2.0 application starting in development on http://localhost:3000 -=> Call with -d to detach -=> Ctrl-C to shutdown server -[2013-08-07 02:00:01] INFO WEBrick 1.3.1 -[2013-08-07 02:00:01] INFO ruby 2.0.0 (2013-06-27) [x86_64-darwin11.2.0] -[2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000 +=> Booting Puma +=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000 +=> Run `rails server -h` for more startup options +Puma starting in single mode... +* Version 3.0.2 (ruby 2.3.0-p0), codename: Plethora of Penguin Pinatas +* Min threads: 5, max threads: 5 +* Environment: development +* Listening on tcp://localhost:3000 +Use Ctrl-C to stop ``` With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open [http://localhost:3000](http://localhost:3000), you will see a basic Rails app running. @@ -151,9 +155,9 @@ $ bin/rails generate controller Greetings hello create app/helpers/greetings_helper.rb invoke assets invoke coffee - create app/assets/javascripts/greetings.js.coffee + create app/assets/javascripts/greetings.coffee invoke scss - create app/assets/stylesheets/greetings.css.scss + create app/assets/stylesheets/greetings.scss ``` What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a JavaScript file and a stylesheet file. @@ -179,7 +183,7 @@ Fire up your server using `rails server`. ```bash $ bin/rails server -=> Booting WEBrick... +=> Booting Puma... ``` The URL will be [http://localhost:3000/greetings/hello](http://localhost:3000/greetings/hello). @@ -239,26 +243,32 @@ $ bin/rails generate scaffold HighScore game:string score:integer create app/views/high_scores/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/high_scores.js.coffee + create app/assets/javascripts/high_scores.coffee invoke scss - create app/assets/stylesheets/high_scores.css.scss + create app/assets/stylesheets/high_scores.scss invoke scss - identical app/assets/stylesheets/scaffolds.css.scss + identical app/assets/stylesheets/scaffolds.scss ``` The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. -The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. +The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `bin/rails db:migrate` command. We'll talk more about bin/rails in-depth in a little while. ```bash -$ bin/rake db:migrate +$ bin/rails db:migrate == CreateHighScores: migrating =============================================== -- create_table(:high_scores) -> 0.0017s == CreateHighScores: migrated (0.0019s) ====================================== ``` -INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment. +INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions +about code. In unit testing, we take a little part of code, say a method of a model, +and test its inputs and outputs. Unit tests are your friend. The sooner you make +peace with the fact that your quality of life will drastically increase when you unit +test your code, the better. Seriously. Please visit +[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth +look at unit testing. Let's see the interface Rails created for us. @@ -284,7 +294,7 @@ If you wish to test out some code without changing any data, you can do that by ```bash $ bin/rails console --sandbox -Loading development environment in sandbox (Rails 4.2.0) +Loading development environment in sandbox (Rails 5.0.0) Any modifications you make will be rolled back on exit irb(main):001:0> ``` @@ -316,7 +326,7 @@ With the `helper` method it is possible to access Rails and your application's h ### `rails dbconsole` -`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. +`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL (including MariaDB), PostgreSQL and SQLite3. INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`. @@ -336,6 +346,12 @@ You can specify the environment in which the `runner` command should operate usi $ bin/rails runner -e staging "Model.long_running_method" ``` +You can even execute ruby code written in a file with runner. + +```bash +$ bin/rails runner lib/code_to_be_run.rb +``` + ### `rails destroy` Think of `destroy` as the opposite of `generate`. It'll figure out what generate did, and undo it. @@ -361,44 +377,63 @@ $ bin/rails destroy model Oops remove test/fixtures/oops.yml ``` -Rake ----- - -Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'make', and uses a 'Rakefile' and `.rake` files to build up a list of tasks. In Rails, Rake is used for common administration tasks, especially sophisticated ones that build off of each other. +bin/rails +--------- -You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need. +Since Rails 5.0+ has rake commands built into the rails executable, `bin/rails` is the new default for running commands. -To get the full backtrace for running rake task you can pass the option `--trace` to command line, for example `rake db:create --trace`. +You can get a list of bin/rails tasks available to you, which will often depend on your current directory, by typing `bin/rails --help`. Each task has a description, and should help you find the thing you need. ```bash -$ bin/rake --tasks -rake about # List versions of all Rails frameworks and the environment -rake assets:clean # Remove old compiled assets -rake assets:clobber # Remove compiled assets -rake assets:precompile # Compile all the assets named in config.assets.precompile -rake db:create # Create the database from config/database.yml for the current Rails.env +$ bin/rails --help +Usage: rails COMMAND [ARGS] + +The most common rails commands are: +generate Generate new code (short-cut alias: "g") +console Start the Rails console (short-cut alias: "c") +server Start the Rails server (short-cut alias: "s") ... -rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development) -rake middleware # Prints out your Rack middleware stack + +All commands can be run with -h (or --help) for more information. + +In addition to those commands, there are: +about List versions of all Rails ... +assets:clean[keep] Remove old compiled assets +assets:clobber Remove compiled assets +assets:environment Load asset compile environment +assets:precompile Compile all the assets ... +... +db:fixtures:load Loads fixtures into the ... +db:migrate Migrate the database ... +db:migrate:status Display status of migrations +db:rollback Rolls the schema back to ... +db:schema:cache:clear Clears a db/schema_cache.dump file +db:schema:cache:dump Creates a db/schema_cache.dump file +db:schema:dump Creates a db/schema.rb file ... +db:schema:load Loads a schema.rb file ... +db:seed Loads the seed data ... +db:structure:dump Dumps the database structure ... +db:structure:load Recreates the databases ... +db:version Retrieves the current schema ... ... -rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) -rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids +restart Restart app by touching ... +tmp:create Creates tmp directories ... ``` -INFO: You can also use `rake -T` to get the list of tasks. +INFO: You can also use `bin/rails -T` to get the list of tasks. ### `about` -`rake about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. +`bin/rails about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. ```bash -$ bin/rake about +$ bin/rails about About your application's environment -Rails version 4.2.0 -Ruby version 1.9.3 (x86_64-linux) -RubyGems version 1.3.6 -Rack version 1.3 +Rails version 5.0.0 +Ruby version 2.2.2 (x86_64-linux) +RubyGems version 2.4.6 +Rack version 1.6 JavaScript Runtime Node.js (V8) -Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag +Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, #, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 @@ -407,30 +442,22 @@ Database schema version 20110805173523 ### `assets` -You can precompile the assets in `app/assets` using `rake assets:precompile`, and remove older compiled assets using `rake assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built. +You can precompile the assets in `app/assets` using `bin/rails assets:precompile`, and remove older compiled assets using `bin/rails assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built. -If you want to clear `public/assets` completely, you can use `rake assets:clobber`. +If you want to clear `public/assets` completely, you can use `bin/rails assets:clobber`. ### `db` -The most common tasks of the `db:` Rake namespace are `migrate` and `create`, and it will pay off to try out all of the migration rake tasks (`up`, `down`, `redo`, `reset`). `rake db:version` is useful when troubleshooting, telling you the current version of the database. - -More information about migrations can be found in the [Migrations](migrations.html) guide. - -### `doc` - -The `doc:` namespace has the tools to generate documentation for your app, API documentation, guides. Documentation can also be stripped which is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. +The most common tasks of the `db:` bin/rails namespace are `migrate` and `create`, and it will pay off to try out all of the migration bin/rails tasks (`up`, `down`, `redo`, `reset`). `bin/rails db:version` is useful when troubleshooting, telling you the current version of the database. -* `rake doc:app` generates documentation for your application in `doc/app`. -* `rake doc:guides` generates Rails guides in `doc/guides`. -* `rake doc:rails` generates API documentation for Rails in `doc/api`. +More information about migrations can be found in the [Migrations](active_record_migrations.html) guide. ### `notes` -`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations. +`bin/rails notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations. ```bash -$ bin/rake notes +$ bin/rails notes (in /home/foobar/commandsapp) app/controllers/admin/users_controller.rb: * [ 20] [TODO] any other way to do this? @@ -447,10 +474,10 @@ You can add support for new file extensions using `config.annotations.register_e config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } ``` -If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name. +If you are looking for a specific annotation, say FIXME, you can use `bin/rails notes:fixme`. Note that you have to lower case the annotation's name. ```bash -$ bin/rake notes:fixme +$ bin/rails notes:fixme (in /home/foobar/commandsapp) app/controllers/admin/users_controller.rb: * [132] high priority for next deploy @@ -459,10 +486,10 @@ app/models/school.rb: * [ 17] ``` -You can also use custom annotations in your code and list them using `rake notes:custom` by specifying the annotation using an environment variable `ANNOTATION`. +You can also use custom annotations in your code and list them using `bin/rails notes:custom` by specifying the annotation using an environment variable `ANNOTATION`. ```bash -$ bin/rake notes:custom ANNOTATION=BUG +$ bin/rails notes:custom ANNOTATION=BUG (in /home/foobar/commandsapp) app/models/article.rb: * [ 23] Have to fix this one before pushing! @@ -470,11 +497,11 @@ app/models/article.rb: NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines. -By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. +By default, `rails notes` will look in the `app`, `config`, `db`, `lib` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. ```bash $ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor' -$ bin/rake notes +$ bin/rails notes (in /home/foobar/commandsapp) app/models/user.rb: * [ 35] [FIXME] User should have a subscription at this point @@ -484,7 +511,7 @@ spec/models/user_spec.rb: ### `routes` -`rake routes` will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. +`rails routes` will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. ### `test` @@ -494,21 +521,20 @@ Rails comes with a test suite called Minitest. Rails owes its stability to the u ### `tmp` -The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for sessions), process id files, and cached actions. +The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like process id files and cached actions. The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory: -* `rake tmp:cache:clear` clears `tmp/cache`. -* `rake tmp:sessions:clear` clears `tmp/sessions`. -* `rake tmp:sockets:clear` clears `tmp/sockets`. -* `rake tmp:clear` clears all the three: cache, sessions and sockets. -* `rake tmp:create` creates tmp directories for sessions, cache, sockets, and pids. +* `rails tmp:cache:clear` clears `tmp/cache`. +* `rails tmp:sockets:clear` clears `tmp/sockets`. +* `rails tmp:clear` clears all cache and sockets files. +* `rails tmp:create` creates tmp directories for cache, sockets and pids. ### Miscellaneous -* `rake stats` is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. -* `rake secret` will give you a pseudo-random key to use for your session secret. -* `rake time:zones:all` lists all the timezones Rails knows about. +* `rails stats` is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. +* `rails secret` will give you a pseudo-random key to use for your session secret. +* `rails time:zones:all` lists all the timezones Rails knows about. ### Custom Rake Tasks @@ -527,8 +553,8 @@ end To pass arguments to your custom rake task: ```ruby -task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args| - # You can use args from here +task :task_name, [:arg_1] => [:prerequisite_1, :prerequisite_2] do |task, args| + argument_1 = args.arg_1 end ``` @@ -546,9 +572,9 @@ end Invocation of the tasks will look like: ```bash -$ bin/rake task_name -$ bin/rake "task_name[value 1]" # entire argument string should be quoted -$ bin/rake db:nothing +$ bin/rails task_name +$ bin/rails "task_name[value 1]" # entire argument string should be quoted +$ bin/rails db:nothing ``` NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the `environment` task, which will load your application code. @@ -579,8 +605,8 @@ $ rails new . --git --database=postgresql create tmp/pids create Rakefile add 'Rakefile' - create README.rdoc -add 'README.rdoc' + create README.md +add 'README.md' create app/controllers/application_controller.rb add 'app/controllers/application_controller.rb' create app/helpers/application_helper.rb @@ -593,7 +619,7 @@ We had to create the **gitapp** directory and initialize an empty git repository ```bash $ cat config/database.yml -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Install the pg driver: # gem install pg diff --git a/source/configuring.md b/source/configuring.md index 0c730ac..b40d7b8 100644 --- a/source/configuring.md +++ b/source/configuring.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Configuring Rails Applications ============================== @@ -33,7 +35,7 @@ In general, the work of configuring Rails means configuring the components of Ra For example, the `config/application.rb` file includes this setting: ```ruby -config.autoload_paths += %W(#{config.root}/extras) +config.time_zone = 'Central Time (US & Canada)' ``` This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same `config` object in `config/application.rb`: @@ -86,33 +88,51 @@ application. Accepts a valid week day symbol (e.g. `:monday`). end ``` -* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. - * `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace. * `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is true. All namespaces in the list must respond to the `eager_load!` method. * `config.eager_load_paths` accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the `app` directory of the application. +* `config.enable_dependency_loading`: when true, enables autoloading, even if the application is eager loaded and `config.cache_classes` is set as true. Defaults to false. + * `config.encoding` sets up the application-wide encoding. Defaults to UTF-8. * `config.exceptions_app` sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`. -* `config.file_watcher` the class used to detect file updates in the filesystem when `config.reload_classes_only_on_change` is true. Must conform to `ActiveSupport::FileUpdateChecker` API. +* `config.debug_exception_response_format` sets the format used in responses when errors occur in development mode. Defaults to `:api` for API only apps and `:default` for normal apps. + +* `config.file_watcher` is the class used to detect file updates in the file system when `config.reload_classes_only_on_change` is true. Rails ships with `ActiveSupport::FileUpdateChecker`, the default, and `ActiveSupport::EventedFileUpdateChecker` (this one depends on the [listen](https://github.com/guard/listen) gem). Custom classes must conform to the `ActiveSupport::FileUpdateChecker` API. * `config.filter_parameters` used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card -numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`. +numbers. By default, Rails filters out passwords by adding `Rails.application.config.filter_parameters += [:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression. + +* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. + +* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you. -* `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware. +* `config.log_level` defines the verbosity of the Rails logger. This option +defaults to `:debug` for all environments. The available log levels are: `:debug`, +`:info`, `:warn`, `:error`, `:fatal`, and `:unknown`. -* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. +* `config.log_tags` accepts a list of: methods that the `request` object responds to, a `Proc` that accepts the `request` object, or something that responds to `to_s`. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. -* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments. +* `config.logger` is the logger that will be used for `Rails.logger` and any related Rails logging such as `ActiveRecord::Base.logger`. It defaults to an instance of `ActiveSupport::TaggedLogging` that wraps an instance of `ActiveSupport::Logger` which outputs a log to the `log/` directory. You can supply a custom logger, to get full compatibility you must follow these guidelines: + * To support a formatter, you must manually assign a formatter from the `config.log_formatter` value to the logger. + * To support tagged logs, the log instance must be wrapped with `ActiveSupport::TaggedLogging`. + * To support silencing, the logger must include `LoggerSilence` and `ActiveSupport::LoggerThreadSafeLevel` modules. The `ActiveSupport::Logger` class already includes these modules. -* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. + ```ruby + class MyLogger < ::Logger + include ActiveSupport::LoggerThreadSafeLevel + include LoggerSilence + end -* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`. + mylogger = MyLogger.new(STDOUT) + mylogger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(mylogger) + ``` * `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below. @@ -120,7 +140,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`. -* `config.serve_static_files` configures Rails to serve static files. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able use page caching and requests for files that exist under the public directory. +* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory. * `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: @@ -139,12 +159,12 @@ pipeline is enabled. It is set to true by default. * `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`. -* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`. - * `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem. * `config.assets.js_compressor` defines the JavaScript compressor to use. Possible values are `:closure`, `:uglifier` and `:yui` which require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems respectively. +* `config.assets.gzip` a flag that enables the creation of gzipped version of compiled assets, along with non-gzipped assets. Set to `true` by default. + * `config.assets.paths` contains the paths which are used to look for assets. Appending paths to this configuration option will cause those paths to be used in the search for assets. * `config.assets.precompile` allows you to specify additional assets (other than `application.css` and `application.js`) which are to be precompiled when `rake assets:precompile` is run. @@ -153,14 +173,10 @@ pipeline is enabled. It is set to true by default. * `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-.json` in the `config.assets.prefix` directory within the public folder. -* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb` and `development.rb`. +* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default. * `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`. -* `config.assets.cache_store` defines the cache store that Sprockets will use. The default is the Rails file store. - -* `config.assets.version` is an option string that is used in MD5 hash generation. This can be changed to force all files to be recompiled. - * `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production. * `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to false will turn off served assets logging. @@ -181,24 +197,27 @@ The full set of methods that can be used in this block are as follows: * `assets` allows to create assets on generating a scaffold. Defaults to `true`. * `force_plural` allows pluralized model names. Defaults to `false`. * `helper` defines whether or not to generate helpers. Defaults to `true`. -* `integration_tool` defines which integration tool to use. Defaults to `nil`. +* `integration_tool` defines which integration tool to use to generate integration tests. Defaults to `:test_unit`. * `javascripts` turns on the hook for JavaScript files in generators. Used in Rails for when the `scaffold` generator is run. Defaults to `true`. -* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `nil`. +* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `:js`. * `orm` defines which orm to use. Defaults to `false` and will use Active Record by default. * `resource_controller` defines which generator to use for generating a controller when using `rails generate resource`. Defaults to `:controller`. +* `resource_route` defines whether a resource route definition should be generated + or not. Defaults to `true`. * `scaffold_controller` different from `resource_controller`, defines which generator to use for generating a _scaffolded_ controller when using `rails generate scaffold`. Defaults to `:scaffold_controller`. * `stylesheets` turns on the hook for stylesheets in generators. Used in Rails for when the `scaffold` generator is run, but this hook can be used in other generates as well. Defaults to `true`. * `stylesheet_engine` configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to `:css`. -* `test_framework` defines which test framework to use. Defaults to `false` and will use Test::Unit by default. +* `scaffold_stylesheet` creates `scaffold.css` when generating a scaffolded resource. Defaults to `true`. +* `test_framework` defines which test framework to use. Defaults to `false` and will use Minitest by default. * `template_engine` defines which template engine to use, such as ERB or Haml. Defaults to `:erb`. ### Configuring Middleware Every Rails application comes with a standard set of middleware which it uses in this order in the development environment: -* `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`. -* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_assets` is `false`. -* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. +* `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`. +* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`. +* `ActionDispatch::Executor` allows thread safe code reloading. Disabled if `config.allow_concurrency` is `false`, which causes `Rack::Lock` to be loaded. `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. * `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs. @@ -212,7 +231,6 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::Cookies` sets cookies for the request. * `ActionDispatch::Session::CookieStore` is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the `config.action_controller.session_store` to an alternate value. Additionally, options passed to this can be configured by using `config.action_controller.session_options`. * `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value. -* `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. * `Rack::Head` converts HEAD requests to GET requests and serves them as so. @@ -243,7 +261,7 @@ config.middleware.swap ActionController::Failsafe, Lifo::Failsafe They can also be removed from the stack completely: ```ruby -config.middleware.delete "Rack::MethodOverride" +config.middleware.delete Rack::MethodOverride ``` ### Configuring i18n @@ -265,8 +283,8 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling `logger` on either an Active Record model class or an Active Record model instance. Set to `nil` to disable logging. * `config.active_record.primary_key_prefix_type` lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named `id` (and this configuration option doesn't need to be set.) There are two other choices: -** `:table_name` would make the primary key for the Customer class `customerid` -** `:table_name_with_underscore` would make the primary key for the Customer class `customer_id` + * `:table_name` would make the primary key for the Customer class `customerid`. + * `:table_name_with_underscore` would make the primary key for the Customer class `customer_id`. * `config.active_record.table_name_prefix` lets you set a global string to be prepended to table names. If you set this to `northwest_`, then the Customer class will look for `northwest_customers` as its table. The default is an empty string. @@ -280,11 +298,13 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements. +* `config.active_record.error_on_ignored_order_or_limit` specifies if an error should be raised if the order or limit of a query is ignored during a batch query. The options are true (raise error) or false (warn). Default is false. + * `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application. * `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default. -* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:number`. +* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:nsec`. * `config.active_record.record_timestamps` is a boolean value which controls whether or not timestamping of `create` and `update` operations on a model occur. The default value is `true`. @@ -298,9 +318,27 @@ All these configuration options are delegated to the `I18n` library. `config/environments/production.rb` which is generated by Rails. The default value is true if this configuration is not set. +* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling db:structure:dump. + The options are `:schema_search_path` (the default) which dumps any schemas listed in schema_search_path, + `:all` which always dumps all schemas regardless of the schema_search_path, + or a string of comma separated schemas. + +* `config.active_record.belongs_to_required_by_default` is a boolean value and + controls whether a record fails validation if `belongs_to` association is not + present. + +* `config.active_record.warn_on_records_fetched_greater_than` allows setting a + warning threshold for query result size. If the number of records returned + by a query exceeds the threshold, a warning is logged. This can be used to + identify queries which might be causing memory bloat. + +* `config.active_record.index_nested_attribute_errors` allows errors for nested + has_many relationships to be displayed with an index as well as the error. + Defaults to false. + The MySQL adapter adds one additional configuration option: -* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. +* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. True by default. The schema dumper adds one additional configuration option: @@ -312,11 +350,11 @@ The schema dumper adds one additional configuration option: * `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself. -* `config.action_controller.perform_caching` configures whether the application should perform caching or not. Set to false in development mode, true in production. +* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to false in development mode, true in production. * `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`. -* `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8". +* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default configuration behavior (when this option is not explicitly set to `true` or `false`) is that all view helpers are available to each controller. * `config.action_controller.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to `nil` to disable logging. @@ -324,6 +362,10 @@ The schema dumper adds one additional configuration option: * `config.action_controller.allow_forgery_protection` enables or disables CSRF protection. By default this is `false` in test mode and `true` in all other modes. +* `config.action_controller.forgery_protection_origin_check` configures whether the HTTP `Origin` header should be checked against the site's origin as an additional CSRF defense. + +* `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for. + * `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`. * `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`. @@ -346,6 +388,8 @@ The schema dumper adds one additional configuration option: } ``` +* `config.action_dispatch.default_charset` specifies the default character set for all renders. Defaults to `nil`. + * `config.action_dispatch.tld_length` sets the TLD (top-level domain) length for the application. Defaults to `1`. * `config.action_dispatch.http_auth_salt` sets the HTTP Auth salt value. Defaults @@ -379,6 +423,8 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. 'ActionDispatch::ParamsParser::ParseError' => :bad_request, 'ActionController::BadRequest' => :bad_request, 'ActionController::ParameterMissing' => :bad_request, + 'Rack::QueryParser::ParameterTypeError' => :bad_request, + 'Rack::QueryParser::InvalidParameterError' => :bad_request, 'ActiveRecord::RecordNotFound' => :not_found, 'ActiveRecord::StaleObjectError' => :conflict, 'ActiveRecord::RecordInvalid' => :unprocessable_entity, @@ -398,7 +444,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. `config.action_view` includes a small number of configuration settings: -* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Record. The default is +* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Model. The default is ```ruby Proc.new do |html_tag, instance| @@ -406,13 +452,23 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. end ``` -* `config.action_view.default_form_builder` tells Rails which form builder to use by default. The default is `ActionView::Helpers::FormBuilder`. If you want your form builder class to be loaded after initialization (so it's reloaded on each request in development), you can pass it as a `String` +* `config.action_view.default_form_builder` tells Rails which form builder to + use by default. The default is `ActionView::Helpers::FormBuilder`. If you + want your form builder class to be loaded after initialization (so it's + reloaded on each request in development), you can pass it as a `String`. * `config.action_view.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to `nil` to disable logging. * `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`, which turns on trimming of tail spaces and newline when using `<%= -%>` or `<%= =%>`. See the [Erubis documentation](http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-trimspaces) for more information. -* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true` +* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to + set the default behavior for `authenticity_token` in forms with `remote: + true`. By default it's set to false, which means that remote forms will not + include `authenticity_token`, which is helpful when you're fragment-caching + the form. Remote forms get the authenticity from the `meta` tag, so embedding + is unnecessary unless you support browsers without JavaScript. In such case + you can either pass `authenticity_token: true` as a form option or set this + config setting to `true`. * `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::ArticlesController` which renders this template: @@ -422,7 +478,13 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. The default setting is `true`, which uses the partial at `/admin/articles/_article.erb`. Setting the value to `false` would render `/articles/_article.erb`, which is the same behavior as rendering from a non-namespaced controller such as `ArticlesController`. -* `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations +* `config.action_view.raise_on_missing_translations` determines whether an + error should be raised for missing translations. + +* `config.action_view.automatically_disable_submit_tag` determines whether + submit_tag should automatically disable on click, this defaults to true. + +* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `` tag or not. This defaults to true. ### Configuring Action Mailer @@ -440,11 +502,11 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.sendmail_settings` allows detailed configuration for the `sendmail` delivery method. It accepts a hash of options, which can include any of these options: * `:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`. - * `:arguments` - The command line arguments. Defaults to `-i -t`. + * `:arguments` - The command line arguments. Defaults to `-i`. * `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true. -* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration) for more info. +* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](action_mailer_basics.html#action-mailer-configuration) for more info. * `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. @@ -489,20 +551,28 @@ There are a number of settings available on `config.action_mailer`: config.action_mailer.show_previews = false ``` +* `config.action_mailer.deliver_later_queue_name` specifies the queue name for + mailers. By default this is `mailers`. + +* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is false in all environments. + + ### Configuring Active Support There are a few configuration options available in Active Support: * `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded. -* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:sorted` and `:random`. Currently defaults to `:sorted`. In Rails 5.0, the default will be changed to `:random` instead. +* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:random` and `:sorted`. This option is set to `:random` in `config/environments/test.rb` in newly-generated applications. If you have an application that does not specify a `test_order`, it will default to `:sorted`, *until* Rails 5.0, when the default will become `:random`. -* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `false`. +* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `true`. * `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`. * `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`. +* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns false (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `callback_terminator.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code. + * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. * `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations. @@ -513,6 +583,69 @@ There are a few configuration options available in Active Support: * `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings. +### Configuring Active Job + +`config.active_job` provides the following configuration options: + +* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). + + ```ruby + # Be sure to have the adapter's gem in your Gemfile + # and follow the adapter's specific installation + # and deployment instructions. + config.active_job.queue_adapter = :sidekiq + ``` + +* `config.active_job.default_queue_name` can be used to change the default queue name. By default this is `"default"`. + + ```ruby + config.active_job.default_queue_name = :medium_priority + ``` + +* `config.active_job.queue_name_prefix` allows you to set an optional, non-blank, queue name prefix for all jobs. By default it is blank and not used. + + The following configuration would queue the given job on the `production_high_priority` queue when run in production: + + ```ruby + config.active_job.queue_name_prefix = Rails.env + ``` + + ```ruby + class GuestsCleanupJob < ActiveJob::Base + queue_as :high_priority + #.... + end + ``` + +* `config.active_job.queue_name_delimiter` has a default value of `'_'`. If `queue_name_prefix` is set, then `queue_name_delimiter` joins the prefix and the non-prefixed queue name. + + The following configuration would queue the provided job on the `video_server.low_priority` queue: + + ```ruby + # prefix must be set for delimiter to be used + config.active_job.queue_name_prefix = 'video_server' + config.active_job.queue_name_delimiter = '.' + ``` + + ```ruby + class EncoderJob < ActiveJob::Base + queue_as :low_priority + #.... + end + ``` + +* `config.active_job.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Job. You can retrieve this logger by calling `logger` on either an Active Job class or an Active Job instance. Set to `nil` to disable logging. + +### Configuring Action Cable + +* `config.action_cable.url` accepts a string for the URL for where + you are hosting your Action Cable server. You would use this option +if you are running Action Cable servers that are separated from your +main application. +* `config.action_cable.mount_path` accepts a string for where to mount Action + Cable, as part of the main server process. Defaults to `/cable`. +You can set this as nil to not mount Action Cable as part of your +normal Rails server. ### Configuring a Database @@ -555,7 +688,7 @@ TIP: You don't have to update the database configurations manually. If you look ### Connection Preference -Since there are two ways to set your connection, via environment variable it is important to understand how the two can interact. +Since there are two ways to configure your connection (using `config/database.yml` or using an environment variable) it is important to understand how they can interact. If you have an empty `config/database.yml` file but your `ENV['DATABASE_URL']` is present, then Rails will connect to the database via your environment variable: @@ -655,11 +788,11 @@ development: timeout: 5000 ``` -NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it. +NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL (including MariaDB) and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it. -#### Configuring a MySQL Database +#### Configuring a MySQL or MariaDB Database -If you choose to use MySQL instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section: +If you choose to use MySQL or MariaDB instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section: ```yaml development: @@ -672,7 +805,7 @@ development: socket: /tmp/mysql.sock ``` -If your development computer's MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate. +If your development database has a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate. #### Configuring a PostgreSQL Database @@ -686,7 +819,7 @@ development: pool: 5 ``` -Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`: +Prepared Statements are enabled by default on PostgreSQL. You can disable prepared statements by setting `prepared_statements` to `false`: ```yaml production: @@ -714,9 +847,9 @@ development: database: db/development.sqlite3 ``` -#### Configuring a MySQL Database for JRuby Platform +#### Configuring a MySQL or MariaDB Database for JRuby Platform -If you choose to use MySQL and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: +If you choose to use MySQL or MariaDB and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: ```yaml development: @@ -769,7 +902,7 @@ Rails will now prepend "/app1" when generating links. #### Using Passenger -Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). +Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](https://www.phusionpassenger.com/library/deploy/apache/deploy/ruby/#deploying-an-app-to-a-sub-uri-or-subdirectory). #### Using a Reverse Proxy @@ -811,15 +944,6 @@ server { Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information. -#### Considerations when deploying to a subdirectory - -Deploying to a subdirectory in production has implications on various parts of -Rails. - -* development environment: -* testing environment: -* serving static assets: -* asset pipeline: Rails Environment Settings -------------------------- @@ -903,95 +1027,110 @@ Because `Rails::Application` inherits from `Rails::Railtie` (indirectly), you ca Below is a comprehensive list of all the initializers found in Rails in the order that they are defined (and therefore run in, unless otherwise stated). -* `load_environment_hook` Serves as a placeholder so that `:load_environment_config` can be defined to run before it. +* `load_environment_hook`: Serves as a placeholder so that `:load_environment_config` can be defined to run before it. + +* `load_active_support`: Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default. + +* `initialize_logger`: Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`. + +* `initialize_cache`: If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. + +* `set_clear_dependencies_hook`: This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. + +* `initialize_dependency_mechanism`: If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. -* `load_active_support` Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default. +* `bootstrap_hook`: Runs all configured `before_initialize` blocks. -* `initialize_logger` Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`. +* `i18n.callbacks`: In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request. -* `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. +* `active_support.deprecation_behavior`: Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values. -* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `active_support.initialize_time_zone`: Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC". -* `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. +* `active_support.initialize_beginning_of_week`: Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`. -* `bootstrap_hook` Runs all configured `before_initialize` blocks. +* `active_support.set_configs`: Sets up Active Support by using the settings in `config.active_support` by `send`'ing the method names as setters to `ActiveSupport` and passing the values through. -* `i18n.callbacks` In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request. +* `action_dispatch.configure`: Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. -* `active_support.deprecation_behavior` Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values. +* `action_view.set_configs`: Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC". +* `action_controller.assets_config`: Initializes the `config.actions_controller.assets_dir` to the app's public directory if not explicitly configured. -* `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`. +* `action_controller.set_helpers_path`: Sets Action Controller's `helpers_path` to the application's `helpers_path`. -* `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. +* `action_controller.parameters_config`: Configures strong parameters options for `ActionController::Parameters`. -* `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. +* `action_controller.set_configs`: Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. -* `action_controller.logger` Sets `ActionController::Base.logger` - if it's not already set - to `Rails.logger`. +* `action_controller.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access. -* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` - if it's not already set - to `Rails.cache`. +* `active_record.initialize_timezone`: Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. -* `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. +* `active_record.logger`: Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. -* `action_controller.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access. +* `active_record.migration_error`: Configures middleware to check for pending migrations. -* `active_record.initialize_timezone` Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. +* `active_record.check_schema_cache_dump`: Loads the schema cache dump if configured and available. -* `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. +* `active_record.warn_on_records_fetched_greater_than`: Enables warnings when queries return large numbers of records. -* `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. +* `active_record.set_configs`: Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. -* `active_record.initialize_database` Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment. +* `active_record.initialize_database`: Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment. -* `active_record.log_runtime` Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger. +* `active_record.log_runtime`: Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger. -* `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. +* `active_record.set_reloader_hooks`: Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. -* `action_mailer.logger` Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`. +* `active_record.add_watchable_files`: Adds `schema.rb` and `structure.sql` files to watchable files. -* `action_mailer.set_configs` Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. +* `active_job.logger`: Sets `ActiveJob::Base.logger` - if it's not already set - + to `Rails.logger`. -* `action_mailer.compile_config_methods` Initializes methods for the config settings specified so that they are quicker to access. +* `active_job.set_configs`: Sets up Active Job by using the settings in `config.active_job` by `send`'ing the method names as setters to `ActiveJob::Base` and passing the values through. -* `set_load_path` This initializer runs before `bootstrap_hook`. Adds the `vendor`, `lib`, all directories of `app` and any paths specified by `config.load_paths` to `$LOAD_PATH`. +* `action_mailer.logger`: Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`. -* `set_autoload_paths` This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths` to `ActiveSupport::Dependencies.autoload_paths`. +* `action_mailer.set_configs`: Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. -* `add_routing_paths` Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application. +* `action_mailer.compile_config_methods`: Initializes methods for the config settings specified so that they are quicker to access. -* `add_locales` Adds the files in `config/locales` (from the application, railties and engines) to `I18n.load_path`, making available the translations in these files. +* `set_load_path`: This initializer runs before `bootstrap_hook`. Adds paths specified by `config.load_paths` and all autoload paths to `$LOAD_PATH`. -* `add_view_paths` Adds the directory `app/views` from the application, railties and engines to the lookup path for view files for the application. +* `set_autoload_paths`: This initializer runs before `bootstrap_hook`. Adds all sub-directories of `app` and paths specified by `config.autoload_paths`, `config.eager_load_paths` and `config.autoload_once_paths` to `ActiveSupport::Dependencies.autoload_paths`. -* `load_environment_config` Loads the `config/environments` file for the current environment. +* `add_routing_paths`: Loads (by default) all `config/routes.rb` files (in the application and railties, including engines) and sets up the routes for the application. -* `append_asset_paths` Finds asset paths for the application and all attached railties and keeps a track of the available directories in `config.static_asset_paths`. +* `add_locales`: Adds the files in `config/locales` (from the application, railties and engines) to `I18n.load_path`, making available the translations in these files. -* `prepend_helpers_path` Adds the directory `app/helpers` from the application, railties and engines to the lookup path for helpers for the application. +* `add_view_paths`: Adds the directory `app/views` from the application, railties and engines to the lookup path for view files for the application. -* `load_config_initializers` Loads all Ruby files from `config/initializers` in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded. +* `load_environment_config`: Loads the `config/environments` file for the current environment. -* `engines_blank_point` Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run. +* `prepend_helpers_path`: Adds the directory `app/helpers` from the application, railties and engines to the lookup path for helpers for the application. -* `add_generator_templates` Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference. +* `load_config_initializers`: Loads all Ruby files from `config/initializers` in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded. -* `ensure_autoload_once_paths_as_subset` Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised. +* `engines_blank_point`: Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run. -* `add_to_prepare_blocks` The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production. +* `add_generator_templates`: Finds templates for generators at `lib/templates` for the application, railties and engines and adds these to the `config.generators.templates` setting, which will make the templates available for all generators to reference. -* `add_builtin_route` If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application. +* `ensure_autoload_once_paths_as_subset`: Ensures that the `config.autoload_once_paths` only contains paths from `config.autoload_paths`. If it contains extra paths, then an exception will be raised. -* `build_middleware_stack` Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request. +* `add_to_prepare_blocks`: The block for every `config.to_prepare` call in the application, a railtie or engine is added to the `to_prepare` callbacks for Action Dispatch which will be run per request in development, or before the first request in production. -* `eager_load!` If `config.eager_load` is true, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`. +* `add_builtin_route`: If the application is running under the development environment then this will append the route for `rails/info/properties` to the application routes. This route provides the detailed information such as Rails and Ruby version for `public/index.html` in a default Rails application. -* `finisher_hook` Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines. +* `build_middleware_stack`: Builds the middleware stack for the application, returning an object which has a `call` method which takes a Rack environment object for the request. -* `set_routes_reloader` Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`. +* `eager_load!`: If `config.eager_load` is true, runs the `config.before_eager_load` hooks and then calls `eager_load!` which will load all `config.eager_load_namespaces`. -* `disable_dependency_loading` Disables the automatic dependency loading if the `config.eager_load` is set to true. +* `finisher_hook`: Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines. + +* `set_routes_reloader`: Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`. + +* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to true. Database pooling ---------------- @@ -1006,22 +1145,22 @@ development: timeout: 5000 ``` -Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit. +Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases it will create them until it reaches the connection pool limit. -Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue. +Any one request will check out a connection the first time it requires access to the database. At the end of the request it will check the connection back in. This means that the additional connection slot will be available again for the next request in the queue. If you try to use more connections than are available, Active Record will block -and wait for a connection from the pool. When it cannot get connection, a timeout -error similar to given below will be thrown. +you and wait for a connection from the pool. If it cannot get a connection, a +timeout error similar to that given below will be thrown. ```ruby -ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it: +ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5.000 seconds (waited 5.000 seconds) ``` -If you get the above error, you might want to increase the size of connection -pool by incrementing the `pool` option in `database.yml` +If you get the above error, you might want to increase the size of the +connection pool by incrementing the `pool` option in `database.yml` -NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections. +NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited number of connections. Custom configuration @@ -1030,16 +1169,85 @@ Custom configuration You can configure your own code through the Rails configuration object with custom configuration. It works like this: ```ruby - config.x.payment_processing.schedule = :daily - config.x.payment_processing.retries = 3 - config.x.super_debugger = true + config.payment_processing.schedule = :daily + config.payment_processing.retries = 3 + config.super_debugger = true ``` These configuration points are then available through the configuration object: ```ruby - Rails.configuration.x.payment_processing.schedule # => :daily - Rails.configuration.x.payment_processing.retries # => 3 - Rails.configuration.x.super_debugger # => true - Rails.configuration.x.super_debugger.not_set # => nil + Rails.configuration.payment_processing.schedule # => :daily + Rails.configuration.payment_processing.retries # => 3 + Rails.configuration.super_debugger # => true + Rails.configuration.super_debugger.not_set # => nil + ``` + +You can also use `Rails::Application.config_for` to load whole configuration files: + + ```ruby + # config/payment.yml: + production: + environment: production + merchant_id: production_merchant_id + public_key: production_public_key + private_key: production_private_key + development: + environment: sandbox + merchant_id: development_merchant_id + public_key: development_public_key + private_key: development_private_key + + # config/application.rb + module MyApp + class Application < Rails::Application + config.payment = config_for(:payment) + end + end + ``` + + ```ruby + Rails.configuration.payment['merchant_id'] # => production_merchant_id or development_merchant_id ``` + +Search Engines Indexing +----------------------- + +Sometimes, you may want to prevent some pages of your application to be visible +on search sites like Google, Bing, Yahoo or Duck Duck Go. The robots that index +these sites will first analyze the `http://your-site.com/robots.txt` file to +know which pages it is allowed to index. + +Rails creates this file for you inside the `/public` folder. By default, it allows +search engines to index all pages of your application. If you want to block +indexing on all pages of you application, use this: + +``` +User-agent: * +Disallow: / +``` + +To block just specific pages, it's necessary to use a more complex syntax. Learn +it on the [official documentation](http://www.robotstxt.org/robotstxt.html). + +Evented File System Monitor +--------------------------- + +If the [listen gem](https://github.com/guard/listen) is loaded Rails uses an +evented file system monitor to detect changes when `config.cache_classes` is +false: + +```ruby +group :development do + gem 'listen', '~> 3.0.4' +end +``` + +Otherwise, in every request Rails walks the application tree to check if +anything has changed. + +On Linux and Mac OS X no additional gems are needed, but some are required +[for *BSD](https://github.com/guard/listen#on-bsd) and +[for Windows](https://github.com/guard/listen#on-windows). + +Note that [some setups are unsupported](https://github.com/guard/listen#issues--limitations). diff --git a/source/contributing_to_ruby_on_rails.md b/source/contributing_to_ruby_on_rails.md index 17afd07..12d0280 100644 --- a/source/contributing_to_ruby_on_rails.md +++ b/source/contributing_to_ruby_on_rails.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Contributing to Ruby on Rails ============================= @@ -13,6 +15,9 @@ After reading this guide, you will know: Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. +As mentioned in [Rails +README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/). + -------------------------------------------------------------------------------- Reporting an Issue @@ -24,21 +29,25 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge ### Creating a Bug Report -If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) +If you've found a problem in Ruby on Rails which is not a security risk, do a search on GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) -Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. +Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to reproduce the bug and figure out a fix. Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment. -### Create a Self-Contained gist for Active Record and Action Controller Issues +### Create an Executable Test Case + +Having a way to reproduce your issue will be very helpful for others to help confirm, investigate and ultimately fix your issue. You can do this by providing an executable test case. To make this process easier, we have prepared several bug report templates for you to use as a starting point: + +* Template for Active Record (models, database) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) +* Template for Action Pack (controllers, routing) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) +* Generic template for other issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_master.rb) -If you are filing a bug report, please use -[Active Record template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) or -[Action Controller template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) -if the bug is found in a published gem, and -[Active Record template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) or -[Action Controller template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) -if the bug happens in the master branch. +These templates include the boilerplate code to set up a test case against either a released version of Rails (`*_gem.rb`) or edge Rails (`*_master.rb`). + +Simply copy the content of the appropriate template into a `.rb` file and make the necessary changes to demonstrate the issue. You can execute it by running `ruby the_file.rb` in your terminal. If all goes well, you should see your test case failing. + +You can then share your executable test case as a [gist](https://gist.github.com), or simply paste the content into the issue description. ### Special Treatment for Security Issues @@ -55,12 +64,12 @@ can expect it to be marked "invalid" as soon as it's reviewed. Sometimes, the line between 'bug' and 'feature' is a hard one to draw. Generally, a feature is anything that adds new behavior, while a bug is -anything that fixes already existing behavior that is misbehaving. Sometimes, +anything that causes incorrect behavior. Sometimes, the core team will have to make a judgement call. That said, the distinction generally just affects which release your patch will get in to; we love feature submissions! They just won't get backported to maintenance branches. -If you'd like feedback on an idea for a feature before doing the work for make +If you'd like feedback on an idea for a feature before doing the work to make a patch, please send an email to the [rails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You might get no response, which means that everyone is indifferent. You might find @@ -73,17 +82,17 @@ discussions new features require. Helping to Resolve Existing Issues ---------------------------------- -As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [Everyone's Issues](https://github.com/rails/rails/issues) list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: +As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [issues list](https://github.com/rails/rails/issues) in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: ### Verifying Bug Reports For starters, it helps just to verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing. -If something is very vague, can you help squash it down into something specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem. +If an issue is very vague, can you help narrow it down to something more specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem. If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section. -Anything you can do to make bug reports more succinct or easier to reproduce is a help to folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. +Anything you can do to make bug reports more succinct or easier to reproduce helps folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. ### Testing Patches @@ -96,7 +105,7 @@ $ git checkout -b testing_branch Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. ```bash -$ git remote add JohnSmith git://github.com/JohnSmith/rails.git +$ git remote add JohnSmith https://github.com/JohnSmith/rails.git $ git pull JohnSmith orange ``` @@ -111,7 +120,7 @@ Once you're happy that the pull request contains a good change, comment on the G >I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too. -If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. +If your comment simply reads "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. Contributing to the Rails Documentation --------------------------------------- @@ -119,11 +128,11 @@ Contributing to the Rails Documentation Ruby on Rails has two main sets of documentation: the guides, which help you learn about Ruby on Rails, and the API, which serves as a reference. -You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). +You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails. You can either open a pull request to [Rails](http://github.com/rails/rails) or ask the [Rails core team](http://rubyonrails.org/core) for commit access on -[docrails](http://github.com/rails/docrails) if you contribute regularly. +docrails if you contribute regularly. Please do not open pull requests in docrails, if you'd like to get feedback on your change, ask for it in [Rails](http://github.com/rails/rails) instead. @@ -139,6 +148,42 @@ NOTE: To help our CI servers you should add [ci skip] to your documentation comm WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. +Translating Rails Guides +------------------------ + +We are happy to have people volunteer to translate the Rails guides into their own language. +If you want to translate the Rails guides in your own language, follows these steps: + +* Fork the project (rails/rails). +* Add a source folder for your own language, for example: *guides/source/it-IT* for Italian. +* Copy the contents of *guides/source* into your own language directory and translate them. +* Do NOT translate the HTML files, as they are automatically generated. + +To generate the guides in HTML format cd into the *guides* directory then run (eg. for it-IT): + +```bash +$ bundle install +$ bundle exec rake guides:generate:html GUIDES_LANGUAGE=it-IT +``` + +This will generate the guides in an *output* directory. + +NOTE: The instructions are for Rails > 4. The Redcarpet Gem doesn't work with JRuby. + +Translation efforts we know about (various versions): + +* **Italian**: [https://github.com/rixlabs/docrails](https://github.com/rixlabs/docrails) +* **Spanish**: [http://wiki.github.com/gramos/docrails](http://wiki.github.com/gramos/docrails) +* **Polish**: [http://github.com/apohllo/docrails/tree/master](http://github.com/apohllo/docrails/tree/master) +* **French** : [http://github.com/railsfrance/docrails](http://github.com/railsfrance/docrails) +* **Czech** : [https://github.com/rubyonrails-cz/docrails/tree/czech](https://github.com/rubyonrails-cz/docrails/tree/czech) +* **Turkish** : [https://github.com/ujk/docrails/tree/master](https://github.com/ujk/docrails/tree/master) +* **Korean** : [https://github.com/rorlakr/rails-guides](https://github.com/rorlakr/rails-guides) +* **Simplified Chinese** : [https://github.com/ruby-china/guides](https://github.com/ruby-china/guides) +* **Traditional Chinese** : [https://github.com/docrails-tw/guides](https://github.com/docrails-tw/guides) +* **Russian** : [https://github.com/morsbox/rusrails](https://github.com/morsbox/rusrails) +* **Japanese** : [https://github.com/yasslab/railsguides.jp](https://github.com/yasslab/railsguides.jp) + Contributing to the Rails Code ------------------------------ @@ -159,7 +204,7 @@ In case you can't use the Rails development box, see [this other guide](developm To be able to contribute code, you need to clone the Rails repository: ```bash -$ git clone git://github.com/rails/rails.git +$ git clone https://github.com/rails/rails.git ``` and create a dedicated branch: @@ -171,6 +216,14 @@ $ git checkout -b my_new_branch It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. +### Bundle install + +Install the required gems. + +```bash +$ bundle install +``` + ### Running an Application Against Your Local Branch In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: @@ -234,11 +287,11 @@ This will generate a report with the following information: ``` Calculating ------------------------------------- - addition 69114 i/100ms - addition with send 64062 i/100ms + addition 132.013k i/100ms + addition with send 125.413k i/100ms ------------------------------------------------- - addition 5307644.4 (±3.5%) i/s - 26539776 in 5.007219s - addition with send 3702897.9 (±3.5%) i/s - 18513918 in 5.006723s + addition 9.677M (± 1.7%) i/s - 48.449M + addition with send 6.794M (± 1.1%) i/s - 33.987M ``` Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information. @@ -281,13 +334,18 @@ You can run a single test through ruby. For instance: ```bash $ cd actionmailer -$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout +$ bundle exec ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout ``` The `-n` option allows you to run a single method instead of the whole file. -##### Testing Active Record +#### Testing Active Record + +First, create the databases you'll need. For MySQL and PostgreSQL, +running the SQL statements `create database activerecord_unittest` and +`create database activerecord_unittest2` is sufficient. This is not +necessary for SQLite3. This is how you run the Active Record test suite only for SQLite3: @@ -296,10 +354,9 @@ $ cd activerecord $ bundle exec rake test:sqlite3 ``` -You can now run the tests as you did for `sqlite3`. The tasks are respectively +You can now run the tests as you did for `sqlite3`. The tasks are respectively: ```bash -test:mysql test:mysql2 test:postgresql ``` @@ -310,12 +367,12 @@ Finally, $ bundle exec rake test ``` -will now run the four of them in turn. +will now run the three of them in turn. You can also run any single test separately: ```bash -$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb +$ ARCONN=sqlite3 bundle exec ruby -Itest test/cases/associations/has_many_associations_test.rb ``` To run a single test against all adapters, use: @@ -340,9 +397,9 @@ $ RUBYOPT=-W0 bundle exec rake test The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version. -You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG. +You should add an entry **to the top** of the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG. -A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry: +A CHANGELOG entry should summarize what was changed and should end with the author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry: ``` * Summary of a change that briefly describes what was changed. You can use multiple @@ -359,7 +416,12 @@ A CHANGELOG entry should summarize what was changed and should end with author's *Your Name* ``` -Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph. +Your name can be added directly after the last word if there are no code +examples or multiple paragraphs. Otherwise, it's best to make a new paragraph. + +### Updating the Gemfile.lock + +Some changes require the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes. ### Sanity Check @@ -379,21 +441,27 @@ When you're happy with the code on your computer, you need to commit the changes $ git commit -a ``` -At this point, your editor should be fired up and you can write a message for this commit. Well formatted and descriptive commit messages are extremely helpful for the others, especially when figuring out why given change was made, so please take the time to write it. +This should fire up your editor to write a commit message. When you have +finished, save and close to continue. + +A well-formatted and descriptive commit message is very helpful to others for +understanding why the change was made, so please take the time to write it. -Good commit message should be formatted according to the following example: +A good commit message looks like this: ``` Short summary (ideally 50 characters or less) -More detailed description, if necessary. It should be wrapped to 72 -characters. Try to be as descriptive as you can, even if you think that -the commit content is obvious, it may not be obvious to others. You -should add such description also if it's already present in bug tracker, -it should not be necessary to visit a webpage to check the history. +More detailed description, if necessary. It should be wrapped to +72 characters. Try to be as descriptive as you can. Even if you +think that the commit content is obvious, it may not be obvious +to others. Add any description that is already present in the +relevant issues; it should not be necessary to visit a webpage +to check the history. + +The description section can have multiple paragraphs. -Description can have multiple paragraphs and you can use code examples -inside, just indent it with 4 spaces: +Code examples can be embedded by indenting them with 4 spaces: class ArticlesController def index @@ -403,13 +471,15 @@ inside, just indent it with 4 spaces: You can also add bullet points: -- you can use dashes or asterisks +- make a bullet point by starting a line with either a dash (-) + or an asterisk (*) -- also, try to indent next line of a point for readability, if it's too - long to fit in 72 characters +- wrap lines at 72 characters, and indent any additional lines + with 2 spaces for readability ``` -TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean. +TIP. Please squash your commits into a single commit when appropriate. This +simplifies future cherry picks and keeps the git log clean. ### Update Your Branch @@ -436,7 +506,7 @@ Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and pr Add the new remote to your local repository on your local machine: ```bash -$ git remote add mine git@github.com:/rails.git +$ git remote add mine https://github.com:/rails.git ``` Push to your remote: @@ -450,7 +520,7 @@ You might have cloned your forked repository into your machine and might want to In the directory you cloned your fork: ```bash -$ git remote add rails git://github.com/rails/rails.git +$ git remote add rails https://github.com/rails/rails.git ``` Download new commits and branches from the official repository: @@ -500,7 +570,7 @@ pull request". The Rails core team will be notified about your submission. Most pull requests will go through a few iterations before they get merged. Different contributors will sometimes have different opinions, and often -patches will need revised before they can get merged. +patches will need to be revised before they can get merged. Some contributors to Rails have email notifications from GitHub turned on, but others do not. Furthermore, (almost) everyone who works on Rails is a @@ -547,8 +617,7 @@ following: ```bash $ git fetch upstream $ git checkout my_pull_request -$ git rebase upstream/master -$ git rebase -i +$ git rebase -i upstream/master < Choose 'squash' for all of your commits except the first one. > < Edit the commit message to make sense, and describe all your changes. > diff --git a/source/credits.html.erb b/source/credits.html.erb index 8767fbe..1d99558 100644 --- a/source/credits.html.erb +++ b/source/credits.html.erb @@ -28,7 +28,7 @@ Ruby on Rails Guides: Credits

Rails Guides Authors

<%= author('Ryan Bigg', 'radar', 'radar.png') do %> - Ryan Bigg works as the Community Manager at Spree Commerce and has been working with Rails since 2006. He's the author of Multi Tenancy With Rails and co-author of Rails 4 in Action. He's written many gems which can be seen on his GitHub page and he also tweets prolifically as @ryanbigg. + Ryan Bigg works as a Rails developer at Marketplacer and has been working with Rails since 2006. He's the author of Multi Tenancy With Rails and co-author of Rails 4 in Action. He's written many gems which can be seen on his GitHub page and he also tweets prolifically as @ryanbigg. <% end %> <%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> @@ -40,7 +40,7 @@ Oscar Del Ben is a software engineer at Wi <% end %> <%= author('Tore Darell', 'toretore') do %> - Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the Internet is his blog Sneaky Abstractions. + Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. You can follow him on Twitter. <% end %> <%= author('Jeff Dean', 'zilkey') do %> diff --git a/source/debugging_rails_applications.md b/source/debugging_rails_applications.md index 1a647f8..f0d0f97 100644 --- a/source/debugging_rails_applications.md +++ b/source/debugging_rails_applications.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Debugging Rails Applications ============================ @@ -15,7 +17,7 @@ After reading this guide, you will know: View Helpers for Debugging -------------------------- -One common task is to inspect the contents of a variable. In Rails, you can do this with three methods: +One common task is to inspect the contents of a variable. Rails provides three different ways to do this: * `debug` * `to_yaml` @@ -52,7 +54,7 @@ Title: Rails debugging guide ### `to_yaml` -Displaying an instance variable, or any other object or method, in YAML format can be achieved this way: +Alternatively, calling `to_yaml` on any object converts it to YAML. You can pass this converted object into the `simple_format` helper method to format the output. This is how `debug` does its magic. ```html+erb <%= simple_format @article.to_yaml %> @@ -62,9 +64,7 @@ Displaying an instance variable, or any other object or method, in YAML format c

``` -The `to_yaml` method converts the method to YAML format leaving it more readable, and then the `simple_format` helper is used to render each line as in the console. This is how `debug` method does its magic. - -As a result of this, you will have something like this in your view: +The above code will render something like this: ```yaml --- !ruby/object Article @@ -92,7 +92,7 @@ Another useful method for displaying object values is `inspect`, especially when

``` -Will be rendered as follows: +Will render: ``` [1, 2, 3, 4, 5] @@ -107,36 +107,41 @@ It can also be useful to save information to log files at runtime. Rails maintai ### What is the Logger? -Rails makes use of the `ActiveSupport::Logger` class to write log information. You can also substitute another logger such as `Log4r` if you wish. +Rails makes use of the `ActiveSupport::Logger` class to write log information. Other loggers, such as `Log4r`, may also be substituted. -You can specify an alternative logger in your `environment.rb` or any environment file: +You can specify an alternative logger in `config/application.rb` or any other environment file, for example: ```ruby -Rails.logger = Logger.new(STDOUT) -Rails.logger = Log4r::Logger.new("Application Log") +config.logger = Logger.new(STDOUT) +config.logger = Log4r::Logger.new("Application Log") ``` Or in the `Initializer` section, add _any_ of the following ```ruby -config.logger = Logger.new(STDOUT) -config.logger = Log4r::Logger.new("Application Log") +Rails.logger = Logger.new(STDOUT) +Rails.logger = Log4r::Logger.new("Application Log") ``` TIP: By default, each log is created under `Rails.root/log/` and the log file is named after the environment in which the application is running. ### Log Levels -When something is logged it's printed into the corresponding log if the log level of the message is equal or higher than the configured log level. If you want to know the current log level you can call the `Rails.logger.level` method. +When something is logged, it's printed into the corresponding log if the log +level of the message is equal to or higher than the configured log level. If you +want to know the current log level, you can call the `Rails.logger.level` +method. -The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, and `:unknown`, corresponding to the log level numbers from 0 up to 5 respectively. To change the default log level, use +The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, +and `:unknown`, corresponding to the log level numbers from 0 up to 5, +respectively. To change the default log level, use ```ruby config.log_level = :warn # In any environment initializer, or Rails.logger.level = 0 # at any time ``` -This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information. +This is useful when you want to log under development or staging without flooding your production log with unnecessary information. TIP: The default Rails log level is `debug` in all environments. @@ -200,7 +205,7 @@ Adding extra logging like this makes it easy to search for unexpected or unusual When running multi-user, multi-account applications, it's often useful to be able to filter the logs using some custom rules. `TaggedLogging` -in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. +in Active Support helps you do exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. ```ruby logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) @@ -210,34 +215,33 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs " ``` ### Impact of Logs on Performance -Logging will always have a small impact on performance of your rails app, - particularly when logging to disk. However, there are a few subtleties: +Logging will always have a small impact on the performance of your Rails app, + particularly when logging to disk. Additionally, there are a few subtleties: Using the `:debug` level will have a greater performance penalty than `:fatal`, as a far greater number of strings are being evaluated and written to the log output (e.g. disk). -Another potential pitfall is that if you have many calls to `Logger` like this - in your code: +Another potential pitfall is too many calls to `Logger` in your code: ```ruby logger.debug "Person attributes hash: #{@person.attributes.inspect}" ``` -In the above example, There will be a performance impact even if the allowed +In the above example, there will be a performance impact even if the allowed output level doesn't include debug. The reason is that Ruby has to evaluate these strings, which includes instantiating the somewhat heavy `String` object -and interpolating the variables, and which takes time. +and interpolating the variables. Therefore, it's recommended to pass blocks to the logger methods, as these are -only evaluated if the output level is the same or included in the allowed level +only evaluated if the output level is the same as — or included in — the allowed level (i.e. lazy loading). The same code rewritten would be: ```ruby logger.debug {"Person attributes hash: #{@person.attributes.inspect}"} ``` -The contents of the block, and therefore the string interpolation, is only -evaluated if debug is enabled. This performance savings is only really +The contents of the block, and therefore the string interpolation, are only +evaluated if debug is enabled. This performance savings are only really noticeable with large amounts of logging, but it's a good practice to employ. Debugging with the `byebug` gem @@ -251,8 +255,8 @@ is your best companion. The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and -use this guide to learn how to move from the code you have written deeper into -Rails code. +use this guide to learn how to move from the code you have written into the +underlying Rails code. ### Setup @@ -283,7 +287,7 @@ As soon as your application calls the `byebug` method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt `(byebug)`. Before the prompt, the code around the line that is about to be run will be -displayed and the current line will be marked by '=>'. Like this: +displayed and the current line will be marked by '=>', like this: ``` [1, 10] in /PathTo/project/app/controllers/articles_controller.rb @@ -308,16 +312,15 @@ processing the entire request. For example: ```bash -=> Booting WEBrick -=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000 +=> Booting Puma +=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000 => Run `rails server -h` for more startup options -=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option) -=> Ctrl-C to shutdown server -[2014-04-11 13:11:47] INFO WEBrick 1.3.1 -[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux] -[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000 - - +Puma starting in single mode... +* Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl +* Min threads: 5, max threads: 5 +* Environment: development +* Listening on tcp://localhost:3000 +Use Ctrl-C to stop Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200 ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by ArticlesController#index as HTML @@ -333,34 +336,57 @@ Processing by ArticlesController#index as HTML 10: respond_to do |format| 11: format.html # index.html.erb 12: format.json { render json: @articles } - (byebug) ``` -Now it's time to explore and dig into your application. A good place to start is +Now it's time to explore your application. A good place to start is by asking the debugger for help. Type: `help` ``` (byebug) help -byebug 2.7.0 - -Type 'help ' for help on a specific command + break -- Sets breakpoints in the source code + catch -- Handles exception catchpoints + condition -- Sets conditions on breakpoints + continue -- Runs until program ends, hits a breakpoint or reaches a line + debug -- Spawns a subdebugger + delete -- Deletes breakpoints + disable -- Disables breakpoints or displays + display -- Evaluates expressions every time the debugger stops + down -- Moves to a lower frame in the stack trace + edit -- Edits source files + enable -- Enables breakpoints or displays + finish -- Runs the program until frame returns + frame -- Moves to a frame in the call stack + help -- Helps you using byebug + history -- Shows byebug's history of commands + info -- Shows several informations about the program being debugged + interrupt -- Interrupts the program + irb -- Starts an IRB session + kill -- Sends a signal to the current process + list -- Lists lines of source code + method -- Shows methods of an object, class or module + next -- Runs one or more lines of code + pry -- Starts a Pry session + quit -- Exits byebug + restart -- Restarts the debugged program + save -- Saves current byebug session to a file + set -- Modifies byebug settings + show -- Shows byebug settings + source -- Restores a previously saved byebug session + step -- Steps into blocks or methods one or more times + thread -- Commands to manipulate threads + tracevar -- Enables tracing of a global variable + undisplay -- Stops displaying all or some expressions when program stops + untracevar -- Stops tracing a global variable + up -- Moves to a higher frame in the stack trace + var -- Shows variables and its values + where -- Displays the backtrace -Available commands: -backtrace delete enable help list pry next restart source up -break disable eval info method ps save step var -catch display exit interrupt next putl set thread -condition down finish irb p quit show trace -continue edit frame kill pp reload skip undisplay +(byebug) ``` -TIP: To view the help menu for any command use `help ` at the -debugger prompt. For example: _`help list`_. You can abbreviate any debugging -command by supplying just enough letters to distinguish them from other -commands, so you can also use `l` for the `list` command, for example. - -To see the previous ten lines you should type `list-` (or `l-`) +To see the previous ten lines you should type `list-` (or `l-`). ``` (byebug) l- @@ -376,12 +402,11 @@ To see the previous ten lines you should type `list-` (or `l-`) 8 @articles = Article.find_recent 9 10 respond_to do |format| - ``` -This way you can move inside the file, being able to see the code above and over -the line where you added the `byebug` call. Finally, to see where you are in -the code again you can type `list=` +This way you can move inside the file and see the code above the line where you +added the `byebug` call. Finally, to see where you are in the code again you can +type `list=` ``` (byebug) list= @@ -397,7 +422,6 @@ the code again you can type `list=` 10: respond_to do |format| 11: format.html # index.html.erb 12: format.json { render json: @articles } - (byebug) ``` @@ -409,8 +433,7 @@ contexts as you go through the different parts of the stack. The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables the debugger to inspect the frame stack, evaluate variables from the perspective of the -debugged program, and contains information about the place where the debugged -program is stopped. +debugged program, and know the place where the debugged program is stopped. At any time you can call the `backtrace` command (or its alias `where`) to print the backtrace of the application. This can be very helpful to know how you got @@ -420,46 +443,45 @@ then `backtrace` will supply the answer. ``` (byebug) where --> #0 ArticlesController.index - at /PathTo/project/test_app/app/controllers/articles_controller.rb:8 - #1 ActionController::ImplicitRender.send_action(method#String, *args#Array) - at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/implicit_render.rb:4 + at /PathToProject/app/controllers/articles_controller.rb:8 + #1 ActionController::BasicImplicitRender.send_action(method#String, *args#Array) + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/basic_implicit_render.rb:4 #2 AbstractController::Base.process_action(action#NilClass, *args#Array) - at /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb:189 - #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass) - at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/rendering.rb:10 + at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:181 + #3 ActionController::Rendering.process_action(action, *args) + at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:30 ... ``` The current frame is marked with `-->`. You can move anywhere you want in this -trace (thus changing the context) by using the `frame _n_` command, where _n_ is +trace (thus changing the context) by using the `frame n` command, where _n_ is the specified frame number. If you do that, `byebug` will display your new context. ``` (byebug) frame 2 -[184, 193] in /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb - 184: # is the intended way to override action dispatching. - 185: # - 186: # Notice that the first argument is the method to be dispatched - 187: # which is *not* necessarily the same as the action name. - 188: def process_action(method_name, *args) -=> 189: send_action(method_name, *args) - 190: end - 191: - 192: # Actually call the method associated with the action. Override - 193: # this method if you wish to change how action methods are called, - +[176, 185] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb + 176: # is the intended way to override action dispatching. + 177: # + 178: # Notice that the first argument is the method to be dispatched + 179: # which is *not* necessarily the same as the action name. + 180: def process_action(method_name, *args) +=> 181: send_action(method_name, *args) + 182: end + 183: + 184: # Actually call the method associated with the action. Override + 185: # this method if you wish to change how action methods are called, (byebug) ``` The available variables are the same as if you were running the code line by line. After all, that's what debugging is. -You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order -to change the context _n_ frames up or down the stack respectively. _n_ defaults -to one. Up in this case is towards higher-numbered stack frames, and down is -towards lower-numbered stack frames. +You can also use `up [n]` and `down [n]` commands in order to change the context +_n_ frames up or down the stack respectively. _n_ defaults to one. Up in this +case is towards higher-numbered stack frames, and down is towards lower-numbered +stack frames. ### Threads @@ -467,16 +489,15 @@ The debugger can list, stop, resume and switch between running threads by using the `thread` command (or the abbreviated `th`). This command has a handful of options: -* `thread` shows the current thread. -* `thread list` is used to list all threads and their statuses. The plus + -character and the number indicates the current thread of execution. -* `thread stop _n_` stop thread _n_. -* `thread resume _n_` resumes thread _n_. -* `thread switch _n_` switches the current thread context to _n_. +* `thread`: shows the current thread. +* `thread list`: is used to list all threads and their statuses. The current +thread is marked with a plus (+) sign. +* `thread stop n`: stops thread _n_. +* `thread resume n`: resumes thread _n_. +* `thread switch n`: switches the current thread context to _n_. -This command is very helpful, among other occasions, when you are debugging -concurrent threads and need to verify that there are no race conditions in your -code. +This command is very helpful when you are debugging concurrent threads and need +to verify that there are no race conditions in your code. ### Inspecting Variables @@ -500,9 +521,9 @@ current context: 12: format.json { render json: @articles } (byebug) instance_variables -[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, - :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name, - :@_response_body, :@marked_for_same_origin_verification, :@_config] +[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context, + :@_action_name, :@_response_body, :@marked_for_same_origin_verification, + :@_config] ``` As you may have figured out, all of the variables that you can access from a @@ -512,6 +533,7 @@ command later in this guide). ``` (byebug) next + [5, 14] in /PathTo/project/app/controllers/articles_controller.rb 5 # GET /articles.json 6 def index @@ -530,31 +552,40 @@ command later in this guide). And then ask again for the instance_variables: ``` -(byebug) instance_variables.include? "@articles" -true +(byebug) instance_variables +[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context, + :@_action_name, :@_response_body, :@marked_for_same_origin_verification, + :@_config, :@articles] ``` -Now `@articles` is included in the instance variables, because the line defining it -was executed. +Now `@articles` is included in the instance variables, because the line defining +it was executed. TIP: You can also step into **irb** mode with the command `irb` (of course!). -This way an irb session will be started within the context you invoked it. But -be warned: this is an experimental feature. +This will start an irb session within the context you invoked it. The `var` method is the most convenient way to show variables and their values. -Let's let `byebug` to help us with it. +Let's have `byebug` help us with it. ``` (byebug) help var -v[ar] cl[ass] show class variables of self -v[ar] const show constants of object -v[ar] g[lobal] show global variables -v[ar] i[nstance] show instance variables of object -v[ar] l[ocal] show local variables + + [v]ar + + Shows variables and its values + + + var all -- Shows local, global and instance variables of self. + var args -- Information about arguments of the current scope + var const -- Shows constants of an object. + var global -- Shows global variables. + var instance -- Shows instance variables of self or a specific object. + var local -- Shows local variables in current scope. + ``` This is a great way to inspect the values of the current context variables. For -example, to check that we have no local variables currently defined. +example, to check that we have no local variables currently defined: ``` (byebug) var local @@ -568,16 +599,17 @@ You can also inspect for an object method this way: @_start_transaction_state = {} @aggregation_cache = {} @association_cache = {} -@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil} -@attributes_cache = {} -@changed_attributes = nil -... +@attributes = ## 5: where('created_at > ?', 1.week.ago).limit(limit) - 6: end - 7: - 8: end +[1, 6] in /PathToProject/app/models/article.rb + 1: class Article < ApplicationRecord + 2: def self.find_recent(limit = 10) + 3: byebug +=> 4: where('created_at > ?', 1.week.ago).limit(limit) + 5: end + 6: end (byebug) ``` -If we use `next`, we want go deep inside method calls. Instead, byebug will go -to the next line within the same context. In this case, this is the last line of -the method, so `byebug` will jump to next next line of the previous frame. +If we use `next`, we won't go deep inside method calls. Instead, `byebug` will +go to the next line within the same context. In this case, it is the last line +of the current method, so `byebug` will return to the next line of the caller +method. ``` (byebug) next -Next went up a frame because previous frame finished - -[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb +[4, 13] in /PathToProject/app/controllers/articles_controller.rb 4: # GET /articles 5: # GET /articles.json 6: def index @@ -649,29 +671,29 @@ Next went up a frame because previous frame finished (byebug) ``` -If we use `step` in the same situation, we will literally go the next ruby -instruction to be executed. In this case, the activesupport's `week` method. +If we use `step` in the same situation, `byebug` will literally go to the next +Ruby instruction to be executed -- in this case, Active Support's `week` method. ``` (byebug) step -[50, 59] in /PathToGems/activesupport-4.2.0/lib/active_support/core_ext/numeric/time.rb - 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) - 51: end - 52: alias :day :days - 53: - 54: def weeks -=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) - 56: end - 57: alias :week :weeks - 58: - 59: def fortnights - +[49, 58] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb + 49: + 50: # Returns a Duration instance matching the number of weeks provided. + 51: # + 52: # 2.weeks # => 14 days + 53: def weeks +=> 54: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) + 55: end + 56: alias :week :weeks + 57: + 58: # Returns a Duration instance matching the number of fortnights provided. (byebug) ``` -This is one of the best ways to find bugs in your code, or perhaps in Ruby on -Rails. +This is one of the best ways to find bugs in your code. + +TIP: You can also use `step n` or `next n` to move forward `n` steps at once. ### Breakpoints @@ -681,19 +703,18 @@ is reached. The debugger shell is invoked in that line. You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually: -* `break line`: set breakpoint in the _line_ in the current source file. -* `break file:line [if expression]`: set breakpoint in the _line_ number inside -the _file_. If an _expression_ is given it must evaluated to _true_ to fire up -the debugger. +* `break n`: set breakpoint in line number _n_ in the current source file. +* `break file:n [if expression]`: set breakpoint in line number _n_ inside +file named _file_. If an _expression_ is given it must evaluated to _true_ to +fire up the debugger. * `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The -_expression_ works the same way as with file:line. - +_expression_ works the same way as with file:n. For example, in the previous situation ``` -[4, 13] in /PathTo/project/app/controllers/articles_controller.rb +[4, 13] in /PathToProject/app/controllers/articles_controller.rb 4: # GET /articles 5: # GET /articles.json 6: def index @@ -706,20 +727,20 @@ For example, in the previous situation 13: end (byebug) break 11 -Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11 +Successfully created breakpoint with id 1 ``` -Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you -supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. +Use `info breakpoints` to list breakpoints. If you supply a number, it lists +that breakpoint. Otherwise it lists all breakpoints. ``` (byebug) info breakpoints Num Enb What -1 y at /PathTo/project/app/controllers/articles_controller.rb:11 +1 y at /PathToProject/app/controllers/articles_controller.rb:11 ``` -To delete breakpoints: use the command `delete _n_` to remove the breakpoint +To delete breakpoints: use the command `delete n` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active. @@ -731,10 +752,11 @@ No breakpoints. You can also enable or disable breakpoints: -* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is -specified, to stop your program. This is the default state when you create a +* `enable breakpoints [n [m [...]]]`: allows a specific breakpoint list or all +breakpoints to stop your program. This is the default state when you create a breakpoint. -* `disable breakpoints`: the _breakpoints_ will have no effect on your program. +* `disable breakpoints [n [m [...]]]`: make certain (or all) breakpoints have +no effect on your program. ### Catching Exceptions @@ -749,59 +771,136 @@ To list all active catchpoints use `catch`. There are two ways to resume execution of an application that is stopped in the debugger: -* `continue` [line-specification] \(or `c`): resume program execution, at the -address where your script last stopped; any breakpoints set at that address are -bypassed. The optional argument line-specification allows you to specify a line -number to set a one-time breakpoint which is deleted when that breakpoint is -reached. -* `finish` [frame-number] \(or `fin`): execute until the selected stack frame -returns. If no frame number is given, the application will run until the -currently selected frame returns. The currently selected frame starts out the -most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been -performed. If a frame number is given it will run until the specified frame -returns. +* `continue [n]`: resumes program execution at the address where your script last +stopped; any breakpoints set at that address are bypassed. The optional argument +`n` allows you to specify a line number to set a one-time breakpoint which is +deleted when that breakpoint is reached. +* `finish [n]`: execute until the selected stack frame returns. If no frame +number is given, the application will run until the currently selected frame +returns. The currently selected frame starts out the most-recent frame or 0 if +no frame positioning (e.g up, down or frame) has been performed. If a frame +number is given it will run until the specified frame returns. ### Editing Two commands allow you to open code from the debugger into an editor: -* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR -environment variable. A specific _line_ can also be given. +* `edit [file:n]`: edit file named _file_ using the editor specified by the +EDITOR environment variable. A specific line _n_ can also be given. ### Quitting -To exit the debugger, use the `quit` command (abbreviated `q`), or its alias -`exit`. +To exit the debugger, use the `quit` command (abbreviated to `q`). Or, type `q!` +to bypass the `Really quit? (y/n)` prompt and exit unconditionally. A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again. ### Settings -`byebug` has a few available options to tweak its behaviour: +`byebug` has a few available options to tweak its behavior: + +``` +(byebug) help set + + set -* `set autoreload`: Reload source code when changed (default: true). -* `set autolist`: Execute `list` command on every breakpoint (default: true). -* `set listsize _n_`: Set number of source lines to list by default to _n_ -(default: 10) -* `set forcestep`: Make sure the `next` and `step` commands always move to a new -line. + Modifies byebug settings -You can see the full list by using `help set`. Use `help set _subcommand_` to -learn about a particular `set` command. + Boolean values take "on", "off", "true", "false", "1" or "0". If you + don't specify a value, the boolean setting will be enabled. Conversely, + you can use "set no" to disable them. + + You can see these environment settings with the "show" command. + + List of supported settings: + + autosave -- Automatically save command history record on exit + autolist -- Invoke list command on every stop + width -- Number of characters per line in byebug's output + autoirb -- Invoke IRB on every stop + basename -- : information after every stop uses short paths + linetrace -- Enable line execution tracing + autopry -- Invoke Pry on every stop + stack_on_error -- Display stack trace when `eval` raises an exception + fullpath -- Display full file names in backtraces + histfile -- File where cmd history is saved to. Default: ./.byebug_history + listsize -- Set number of source lines to list by default + post_mortem -- Enable/disable post-mortem mode + callstyle -- Set how you want method call parameters to be displayed + histsize -- Maximum number of commands that can be stored in byebug history + savefile -- File where settings are saved to. Default: ~/.byebug_save +``` TIP: You can save these settings in an `.byebugrc` file in your home directory. The debugger reads these global settings when it starts. For example: ```bash -set forcestep +set callstyle short set listsize 25 ``` +Debugging with the `web-console` gem +------------------------------------ + +Web Console is a bit like `byebug`, but it runs in the browser. In any page you +are developing, you can request a console in the context of a view or a +controller. The console would be rendered next to your HTML content. + +### Console + +Inside any controller action or view, you can invoke the console by +calling the `console` method. + +For example, in a controller: + +```ruby +class PostsController < ApplicationController + def new + console + @post = Post.new + end +end +``` + +Or in a view: + +```html+erb +<% console %> + +

New Post

+``` + +This will render a console inside your view. You don't need to care about the +location of the `console` call; it won't be rendered on the spot of its +invocation but next to your HTML content. + +The console executes pure Ruby code: You can define and instantiate +custom classes, create new models and inspect variables. + +NOTE: Only one console can be rendered per request. Otherwise `web-console` +will raise an error on the second `console` invocation. + +### Inspecting Variables + +You can invoke `instance_variables` to list all the instance variables +available in your context. If you want to list all the local variables, you can +do that with `local_variables`. + +### Settings + +* `config.web_console.whitelisted_ips`: Authorized list of IPv4 or IPv6 +addresses and networks (defaults: `127.0.0.1/8, ::1`). +* `config.web_console.whiny_requests`: Log a message when a console rendering +is prevented (defaults: `true`). + +Since `web-console` evaluates plain Ruby code remotely on the server, don't try +to use it in production. + Debugging Memory Leaks ---------------------- -A Ruby application (on Rails or not), can leak memory - either in the Ruby code +A Ruby application (on Rails or not), can leak memory — either in the Ruby code or at the C code level. In this section, you will learn how to find and fix such leaks by using tool @@ -809,8 +908,8 @@ such as Valgrind. ### Valgrind -[Valgrind](http://valgrind.org/) is a Linux-only application for detecting -C-based memory leaks and race conditions. +[Valgrind](http://valgrind.org/) is an application for detecting C-based memory +leaks and race conditions. There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C @@ -830,9 +929,9 @@ application. Here is a list of useful plugins for debugging: * [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate. -* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query +* [Query Trace](https://github.com/ruckus/active-record-query-trace/tree/master) Adds query origin tracing to your logs. -* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin +* [Query Reviewer](https://github.com/nesquena/query_reviewer) This Rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed. @@ -844,7 +943,7 @@ standard Rails error page with a new one containing more contextual information, like source code and variable inspection. * [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information -about your Rails app requests in the browser - in the Developer Tools panel. +about your Rails app requests in the browser — in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. @@ -854,6 +953,7 @@ References * [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html) * [debugger Homepage](https://github.com/cldwalker/debugger) * [byebug Homepage](https://github.com/deivid-rodriguez/byebug) +* [web-console Homepage](https://github.com/rails/web-console) * [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/) * [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised) * [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace) diff --git a/source/development_dependencies_install.md b/source/development_dependencies_install.md index 3d9ec57..cc24e6f 100644 --- a/source/development_dependencies_install.md +++ b/source/development_dependencies_install.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Development Dependencies Install ================================ @@ -7,7 +9,7 @@ After reading this guide, you will know: * How to set up your machine for Rails development * How to run specific groups of unit tests from the Rails test suite -* How the ActiveRecord portion of the Rails test suite operates +* How the Active Record portion of the Rails test suite operates -------------------------------------------------------------------------------- @@ -19,16 +21,15 @@ The easiest and recommended way to get a development environment ready to hack i The Hard Way ------------ -In case you can't use the Rails development box, see section above, these are the steps to manually build a development box for Ruby on Rails core development. +In case you can't use the Rails development box, see section below, these are the steps to manually build a development box for Ruby on Rails core development. ### Install Git Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: * [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. -* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git +* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git. * [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. -* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow. * [GitHub](http://help.github.com) offers links to a variety of Git resources. * [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. @@ -58,7 +59,7 @@ In Ubuntu you're done with just: $ sudo apt-get install sqlite3 libsqlite3-dev ``` -And if you are on Fedora or CentOS, you're done with +If you are on Fedora or CentOS, you're done with ```bash $ sudo yum install sqlite3 sqlite3-devel @@ -163,7 +164,7 @@ $ bundle exec ruby -Itest path/to/test.rb -n test_name ### Active Record Setup -The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (`mysql` and `mysql2`), and once for PostgreSQL. We are going to see now how to set up the environment for them. +Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them. WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. @@ -188,7 +189,7 @@ Follow the instructions given by Homebrew to start these. In Ubuntu just run: ```bash -$ sudo apt-get install mysql-server libmysqlclient15-dev +$ sudo apt-get install mysql-server libmysqlclient-dev $ sudo apt-get install postgresql postgresql-client postgresql-contrib libpq-dev ``` @@ -211,7 +212,7 @@ FreeBSD users will have to run the following: ```bash # pkg install mysql56-client mysql56-server -# pkg install postgresql93-client postgresql93-server +# pkg install postgresql94-client postgresql94-server ``` Or install them through ports (they are located under the `databases` folder). diff --git a/source/documents.yaml b/source/documents.yaml index 0c0e182..2925fb4 100644 --- a/source/documents.yaml +++ b/source/documents.yaml @@ -11,7 +11,7 @@ - name: Active Record Basics url: active_record_basics.html - description: This guide will get you started with models, persistence to database and the Active Record pattern and library. + description: This guide will get you started with models, persistence to database, and the Active Record pattern and library. - name: Active Record Migrations url: active_record_migrations.html @@ -19,7 +19,7 @@ - name: Active Record Validations url: active_record_validations.html - description: This guide covers how you can use Active Record validations + description: This guide covers how you can use Active Record validations. - name: Active Record Callbacks url: active_record_callbacks.html @@ -33,7 +33,7 @@ url: active_record_querying.html description: This guide covers the database query interface provided by Active Record. - - name: Active Model basics + name: Active Model Basics url: active_model_basics.html description: This guide covers the use of model classes without Active Record. work_in_progress: true @@ -74,7 +74,7 @@ - name: Rails Internationalization API url: i18n.html - description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on. + description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country, and so on. - name: Action Mailer Basics url: action_mailer_basics.html @@ -82,12 +82,11 @@ - name: Active Job Basics url: active_job_basics.html - description: This guide provides you with all you need to get started in creating, enqueueing and executing background jobs. + description: This guide provides you with all you need to get started creating, enqueuing, and executing background jobs. - name: Testing Rails Applications url: testing.html - work_in_progress: true - description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. + description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to Integration Testing. Enjoy. - name: Securing Rails Applications url: security.html @@ -101,9 +100,9 @@ url: configuring.html description: This guide covers the basic configuration settings for a Rails application. - - name: Rails Command Line Tools and Rake Tasks + name: The Rails Command Line url: command_line.html - description: This guide covers the command line tools and rake tasks provided by Rails. + description: This guide covers the command line tools provided by Rails. - name: Asset Pipeline url: asset_pipeline.html @@ -112,20 +111,38 @@ name: Working with JavaScript in Rails url: working_with_javascript_in_rails.html description: This guide covers the built-in Ajax/JavaScript functionality of Rails. - - - name: Getting Started with Engines - url: engines.html - description: This guide explains how to write a mountable engine. - work_in_progress: true - name: The Rails Initialization Process work_in_progress: true url: initialization.html - description: This guide explains the internals of the Rails initialization process as of Rails 4 + description: This guide explains the internals of the Rails initialization process. + - + name: Autoloading and Reloading Constants + url: autoloading_and_reloading_constants.html + description: This guide documents how autoloading and reloading constants work. + - + name: "Caching with Rails: An Overview" + url: caching_with_rails.html + description: This guide is an introduction to speeding up your Rails application with caching. + - + name: Active Support Instrumentation + work_in_progress: true + url: active_support_instrumentation.html + description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code. - - name: Constant Autoloading and Reloading - url: constant_autoloading_and_reloading.html - description: This guide documents how constant autoloading and reloading work. + name: Profiling Rails Applications + work_in_progress: true + url: profiling.html + description: This guide explains how to profile your Rails applications to improve performance. + - + name: Using Rails for API-only Applications + url: api_app.html + description: This guide explains how to effectively use Rails to develop a JSON API application. + - + name: Action Cable Overview + url: action_cable_overview.html + description: This guide explains how Action Cable works, and how to use WebSockets to create real-time features. + - name: Extending Rails documents: @@ -142,6 +159,11 @@ name: Creating and Customizing Rails Generators url: generators.html description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator). + - + name: Getting Started with Engines + url: engines.html + description: This guide explains how to write a mountable engine. + work_in_progress: true - name: Contributing to Ruby on Rails documents: @@ -171,6 +193,10 @@ name: Upgrading Ruby on Rails url: upgrading_ruby_on_rails.html description: This guide helps in upgrading applications to latest Ruby on Rails versions. + - + name: Ruby on Rails 5.0 Release Notes + url: 5_0_release_notes.html + description: Release notes for Rails 5.0. - name: Ruby on Rails 4.2 Release Notes url: 4_2_release_notes.html diff --git a/source/documents_zh-CN.yaml b/source/documents_zh-CN.yaml deleted file mode 100644 index 0d3cc0a..0000000 --- a/source/documents_zh-CN.yaml +++ /dev/null @@ -1,225 +0,0 @@ -- - name: 入门 - documents: - - - name: Rails 入门 - url: getting_started.html - description: 从安装到建立第一个应用程序所需知道的一切。 -- - name: 模型 - documents: - - - name: Active Record 基础 - url: active_record_basics.html - description: 本篇介绍 Models、数据库持久性以及 Active Record 模式。 - - - name: Active Record 数据库迁移 - url: active_record_migrations.html - description: 本篇介绍如何有条有理地使用 Active Record 来修改数据库。 - - - name: Active Record 数据验证 - url: active_record_validations.html - description: 本篇介绍如何使用 Active Record 验证功能。 - - - name: Active Record 回调 - url: active_record_callbacks.html - description: 本篇介绍如何使用 Active Record 回调功能。 - - - name: Active Record 关联 - url: association_basics.html - description: 本篇介绍如何使用 Active Record 的关联功能。 - - - name: Active Record 查询 - url: active_record_querying.html - description: 本篇介绍如何使用 Active Record 的数据库查询功能。 -- - name: 视图 - documents: - - - name: Action View 基础 - url: action_view_overview.html - description: 本篇介绍 Action View 辅助方法。 - work_in_progress: true - - - name: Rails 布局和视图渲染 - url: layouts_and_rendering.html - description: 本篇介绍 Action Controller 与 Action View 基本的版型功能,包含了渲染、重定向、使用 `content_for` 区块、以及。 - - - name: Action View 表单帮助方法 - url: form_helpers.html - description: 本篇介绍 Action View 的表单帮助方法。 -- - name: 控制器 - documents: - - - name: Action Controller 简介 - url: action_controller_overview.html - description: 本篇介绍 Controller 的工作原理,Controller 在请求周期所扮演的角色。内容包含 Session、滤动器、Cookies、资料串流以及如何处理由请求所发起的异常。 - - - name: Rails 路由全解 - url: routing.html - description: 本篇介绍与使用者息息相关的路由功能。想了解如何使用 Rails 的路由,从这里开始。 -- - name: 深入 - documents: - - - name: Active Support 核心扩展 - url: active_support_core_extensions.html - description: 本篇介绍由 Active Support 定义的核心扩展功能。 - needs_translation: true - - - name: Rails 国际化 API - url: i18n.html - description: 本篇介绍如何国际化应用程序。将应用程序翻译成多种语言、更改单复数规则、对不同的国家使用正确的日期格式等。 - - - name: Action Mailer 基础 - url: action_mailer_basics.html - description: 本篇介绍如何使用 Action Mailer 来收发信件。 - - - name: Active Job 基础 - url: active_job_basics.html - description: 本篇提供创建背景任务、任务排程以及执行任务的所有知识。 - - - name: Rails 程序测试指南 - url: testing.html - work_in_progress: true - description: 本篇介绍 Rails 里的单元与功能性测试,从什么是测试,解说到如何测试 API。 - - - name: Rails 安全指南 - url: security.html - description: 本篇介绍网路应用程序常见的安全问题,如何在 Rails 里处理这些问题。 - - - name: 调试 Rails 程序 - url: debugging_rails_applications.html - description: 本篇介绍如何给 Rails 应用程式除错。包含了多种除错技巧、如何理解与了解程式码背后究竟发生了什么事。 - - - name: 设置 Rails 程序 - url: configuring.html - description: 本篇介绍 Rails 应用程序的基本设置选项。 - - - name: Rails 命令行 - url: command_line.html - description: 本篇介绍 Rails 提供的 Rake 任务与命令行功能。 - - - name: Rails 缓存简介 - work_in_progress: true - url: caching_with_rails.html - description: 本篇介绍 Rails 提供的多种缓存技巧。 - - - name: Asset Pipeline - url: asset_pipeline.html - description: 本篇介绍 Asset Pipeline。 - - - name: 在 Rails 中使用 JavaScript - url: working_with_javascript_in_rails.html - description: 本篇介绍 Rails 内置的 Ajax 与 JavaScript 功能。 - - - name: Engine 入门 - url: engines.html - description: 本篇介绍如何撰写可嵌入至应用程序的 Engine。 - work_in_progress: true - needs_translation: true - - - name: Rails 启动过程 - work_in_progress: true - url: initialization.html - description: 本篇介绍 Rails 内部的启动过程。 - - - name: Constant Autoloading and Reloading - url: constant_autoloading_and_reloading.html - description: This guide documents how constant autoloading and reloading work. -- - name: 扩展 Rails - documents: - - - name: 新建 Rails Plugins 的基础 - work_in_progress: true - url: plugins.html - description: 本篇介绍如何开发 Plugin 来扩展 Rails 的功能。 - work_in_progress: true - needs_translation: true - - - name: Rails on Rack - url: rails_on_rack.html - description: 本篇介绍 Rack 如何与 Rails 整合,以及如何与其他 Rack 组件互动。 - - - name: 客制与新建 Rails 产生器 - url: generators.html - description: 本篇介绍如何加入新的产生器、修改 Rails 内建的产生器。 - needs_translation: true - - - name: Rails 应用程式模版 - url: rails_application_templates.html - description: 本篇介绍如何使用应用程式模版。 - needs_translation: true -- - name: 贡献 Ruby on Rails - documents: - - - name: 贡献 Ruby on Rails - url: contributing_to_ruby_on_rails.html - description: Rails 不是“某个人”独有的框架。本篇介绍如何参与 Rails 开发。 - needs_translation: true - - - name: API 文件准则 - url: api_documentation_guidelines.html - description: 本篇记录了撰写 API 文件的准则。 - needs_translation: true - - - name: Ruby on Rails 指南准则 - url: ruby_on_rails_guides_guidelines.html - description: 本篇记录了撰写 Ruby on Rails 指南的准则。 - needs_translation: true -- - name: 维护方针 - documents: - - - name: 维护方针 - url: maintenance_policy.html - description: Ruby on Rails 目前官方支持的版本、何时发布新版本。 -- - name: 发布记 - documents: - - - name: 升级 Ruby on Rails - url: upgrading_ruby_on_rails.html - description: 本篇帮助您将程序升级至最新版本。 - needs_translation: true - - - name: Ruby on Rails 4.2 发布记 - url: 4_2_release_notes.html - description: Rails 4.2 的发布记。 - - - name: Ruby on Rails 4.1 发布记 - url: 4_1_release_notes.html - description: Rails 4.1 的发布记。 - - - name: Ruby on Rails 4.0 发布记 - url: 4_0_release_notes.html - description: Rails 4.0 的发布记。 - needs_translation: true - - - name: Ruby on Rails 3.2 发布记 - url: 3_2_release_notes.html - description: Rails 3.2 的发布记。 - needs_translation: true - - - name: Ruby on Rails 3.1 发布记 - url: 3_1_release_notes.html - description: Rails 3.1 的发布记。 - needs_translation: true - - - name: Ruby on Rails 3.0 发布记 - url: 3_0_release_notes.html - description: Rails 3.0 的发布记。 - needs_translation: true - - - name: Ruby on Rails 2.3 发布记 - url: 2_3_release_notes.html - description: Rails 2.3 的发布记。 - needs_translation: true - - - name: Ruby on Rails 2.2 发布记 - url: 2_2_release_notes.html - description: Rails 2.2 的发布记。 - needs_translation: true diff --git a/source/engines.md b/source/engines.md index a1f2da1..eafac48 100644 --- a/source/engines.md +++ b/source/engines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Getting Started with Engines ============================ @@ -148,7 +150,7 @@ When you include the engine into an application later on, you will do so with this line in the Rails application's `Gemfile`: ```ruby -gem 'blorgh', path: "vendor/engines/blorgh" +gem 'blorgh', path: 'engines/blorgh' ``` Don't forget to run `bundle install` as usual. By specifying it as a gem within @@ -237,6 +239,27 @@ NOTE: The `ApplicationController` class inside an engine is named just like a Rails application in order to make it easier for you to convert your applications into engines. +NOTE: Because of the way that Ruby does constant lookup you may run into a situation +where your engine controller is inheriting from the main application controller and +not your engine's application controller. Ruby is able to resolve the `ApplicationController` constant, and therefore the autoloading mechanism is not triggered. See the section [When Constants Aren't Missed](autoloading_and_reloading_constants.html#when-constants-aren-t-missed) of the [Autoloading and Reloading Constants](autoloading_and_reloading_constants.html) guide for further details. The best way to prevent this from +happening is to use `require_dependency` to ensure that the engine's application +controller is loaded. For example: + +``` ruby +# app/controllers/blorgh/articles_controller.rb: +require_dependency "blorgh/application_controller" + +module Blorgh + class ArticlesController < ApplicationController + ... + end +end +``` + +WARNING: Don't use `require` because it will break the automatic reloading of classes +in the development environment - using `require_dependency` ensures that classes are +loaded and unloaded in the correct manner. + Lastly, the `app/views` directory contains a `layouts` folder, which contains a file at `blorgh/application.html.erb`. This file allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you @@ -366,7 +389,7 @@ called `Blorgh::ArticlesController` (at `app/controllers/blorgh/articles_controller.rb`) and its related views at `app/views/blorgh/articles`. This generator also generates a test for the controller (`test/controllers/blorgh/articles_controller_test.rb`) and a helper -(`app/helpers/blorgh/articles_controller.rb`). +(`app/helpers/blorgh/articles_helper.rb`). Everything this generator has created is neatly namespaced. The controller's class is defined within the `Blorgh` module: @@ -379,8 +402,8 @@ module Blorgh end ``` -NOTE: The `ApplicationController` class being inherited from here is the -`Blorgh::ApplicationController`, not an application's `ApplicationController`. +NOTE: The `ArticlesController` class inherits from +`Blorgh::ApplicationController`, not the application's `ApplicationController`. The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced: @@ -400,16 +423,7 @@ Finally, the assets for this resource are generated in two files: `app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little later. -By default, the scaffold styling is not applied to the engine because the -engine's layout file, `app/views/layouts/blorgh/application.html.erb`, doesn't -load it. To make the scaffold styling apply, insert this line into the `` -tag of this layout: - -```erb -<%= stylesheet_link_tag "scaffold" %> -``` - -You can see what the engine has so far by running `rake db:migrate` at the root +You can see what the engine has so far by running `bin/rails db:migrate` at the root of our engine to run the migration generated by the scaffold generator, and then running `rails server` in `test/dummy`. When you open `http://localhost:3000/blorgh/articles` you will see the default scaffold that has @@ -447,7 +461,7 @@ model, a comment controller and then modify the articles scaffold to display comments and allow people to create new ones. From the application root, run the model generator. Tell it to generate a -`Comment` model, with the related table having two columns: a `article_id` integer +`Comment` model, with the related table having two columns: an `article_id` integer and `text` text column. ```bash @@ -471,7 +485,7 @@ called `Blorgh::Comment`. Now run the migration to create our blorgh_comments table: ```bash -$ rake db:migrate +$ bin/rails db:migrate ``` To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and @@ -494,7 +508,7 @@ Turning the model into this: ```ruby module Blorgh - class Article < ActiveRecord::Base + class Article < ApplicationRecord has_many :comments end end @@ -589,7 +603,7 @@ the comments, however, is not quite right yet. If you were to create a comment right now, you would see this error: ``` -Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], +Missing partial blorgh/comments/_comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in: * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" * "/Users/ryan/Sites/side_projects/blorgh/app/views" @@ -598,7 +612,7 @@ Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], The engine is unable to find the partial required for rendering the comments. Rails looks first in the application's (`test/dummy`) `app/views` directory and then in the engine's `app/views` directory. When it can't find it, it will throw -this error. The engine knows to look for `blorgh/comments/comment` because the +this error. The engine knows to look for `blorgh/comments/_comment` because the model object it is receiving is from the `Blorgh::Comment` class. This partial will be responsible for rendering just the comment text, for now. @@ -646,7 +660,7 @@ However, because you are developing the `blorgh` engine on your local machine, you will need to specify the `:path` option in your `Gemfile`: ```ruby -gem 'blorgh', path: "/path/to/blorgh" +gem 'blorgh', path: 'engines/blorgh' ``` Then run `bundle` to install the gem. @@ -677,17 +691,17 @@ pre-defined path which may be customizable. The engine contains migrations for the `blorgh_articles` and `blorgh_comments` table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the -application use this command: +application run the following command from the `test/dummy` directory of your Rails engine: ```bash -$ rake blorgh:install:migrations +$ bin/rails blorgh:install:migrations ``` If you have multiple engines that need migrations copied over, use `railties:install:migrations` instead: ```bash -$ rake railties:install:migrations +$ bin/rails railties:install:migrations ``` This command, when run for the first time, will copy over all the migrations @@ -696,8 +710,8 @@ haven't been copied over already. The first run for this command will output something such as this: ```bash -Copied migration [timestamp_1]_create_blorgh_articles.rb from blorgh -Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh +Copied migration [timestamp_1]_create_blorgh_articles.blorgh.rb from blorgh +Copied migration [timestamp_2]_create_blorgh_comments.blorgh.rb from blorgh ``` The first timestamp (`[timestamp_1]`) will be the current time, and the second @@ -705,7 +719,7 @@ timestamp (`[timestamp_2]`) will be the current time plus a second. The reason for this is so that the migrations for the engine are run after any existing migrations in the application. -To run these migrations within the context of the application, simply run `rake +To run these migrations within the context of the application, simply run `bin/rails db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the articles will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the @@ -716,14 +730,14 @@ If you would like to run migrations only from one engine, you can do it by specifying `SCOPE`: ```bash -rake db:migrate SCOPE=blorgh +bin/rails db:migrate SCOPE=blorgh ``` This may be useful if you want to revert engine's migrations before removing it. To revert all migrations from blorgh engine you can run code such as: ```bash -rake db:migrate SCOPE=blorgh VERSION=0 +bin/rails db:migrate SCOPE=blorgh VERSION=0 ``` ### Using a Class Provided by the Application @@ -750,7 +764,7 @@ application: rails g model user name:string ``` -The `rake db:migrate` command needs to be run here to ensure that our +The `bin/rails db:migrate` command needs to be run here to ensure that our application has the `users` table for future use. Also, to keep it simple, the articles form will have a new text field called @@ -785,7 +799,7 @@ before the article is saved. It will also need to have an `attr_accessor` set up for this field, so that the setter and getter methods are defined for it. To do all this, you'll need to add the `attr_accessor` for `author_name`, the -association for the author and the `before_save` call into +association for the author and the `before_validation` call into `app/models/blorgh/article.rb`. The `author` association will be hard-coded to the `User` class for the time being. @@ -793,7 +807,7 @@ association for the author and the `before_save` call into attr_accessor :author_name belongs_to :author, class_name: "User" -before_save :set_author +before_validation :set_author private def set_author @@ -822,24 +836,22 @@ This migration will need to be run on the application. To do that, it must first be copied using this command: ```bash -$ rake blorgh:install:migrations +$ bin/rails blorgh:install:migrations ``` Notice that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run. ``` -NOTE Migration [timestamp]_create_blorgh_articles.rb from blorgh has been -skipped. Migration with the same name already exists. NOTE Migration -[timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration -with the same name already exists. Copied migration -[timestamp]_add_author_id_to_blorgh_articles.rb from blorgh +NOTE Migration [timestamp]_create_blorgh_articles.blorgh.rb from blorgh has been skipped. Migration with the same name already exists. +NOTE Migration [timestamp]_create_blorgh_comments.blorgh.rb from blorgh has been skipped. Migration with the same name already exists. +Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blorgh ``` Run the migration using: ```bash -$ rake db:migrate +$ bin/rails db:migrate ``` Now with all the pieces in place, an action will take place that will associate @@ -852,28 +864,10 @@ above the "Title" output inside `app/views/blorgh/articles/show.html.erb`: ```html+erb

Author: - <%= @article.author %> + <%= @article.author.name %>

``` -By outputting `@article.author` using the `<%=` tag, the `to_s` method will be -called on the object. By default, this will look quite ugly: - -``` -# -``` - -This is undesirable. It would be much better to have the user's name there. To -do this, add a `to_s` method to the `User` class within the application: - -```ruby -def to_s - name -end -``` - -Now instead of the ugly Ruby object output, the author's name will be displayed. - #### Using a Controller Provided by the Application Because Rails controllers generally share code for things like authentication @@ -888,7 +882,9 @@ engine this would be done by changing `app/controllers/blorgh/application_controller.rb` to look like: ```ruby -class Blorgh::ApplicationController < ApplicationController +module Blorgh + class ApplicationController < ::ApplicationController + end end ``` @@ -1037,9 +1033,11 @@ typical `GET` to a controller in a controller's functional test like this: ```ruby module Blorgh - class FooControllerTest < ActionController::TestCase + class FooControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + def test_index - get :index + get foos_url ... end end @@ -1053,13 +1051,15 @@ in your setup code: ```ruby module Blorgh - class FooControllerTest < ActionController::TestCase + class FooControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + setup do @routes = Engine.routes end def test_index - get :index + get foos_url ... end end @@ -1133,7 +1133,7 @@ end ```ruby # Blorgh/app/models/article.rb -class Article < ActiveRecord::Base +class Article < ApplicationRecord has_many :comments end ``` @@ -1154,7 +1154,7 @@ end ```ruby # Blorgh/app/models/article.rb -class Article < ActiveRecord::Base +class Article < ApplicationRecord has_many :comments def summary "#{title}" @@ -1175,7 +1175,7 @@ classes at run time allowing you to significantly modularize your code. ```ruby # MyApp/app/models/blorgh/article.rb -class Blorgh::Article < ActiveRecord::Base +class Blorgh::Article < ApplicationRecord include Blorgh::Concerns::Models::Article def time_since_created @@ -1191,13 +1191,13 @@ end ```ruby # Blorgh/app/models/article.rb -class Article < ActiveRecord::Base +class Article < ApplicationRecord include Blorgh::Concerns::Models::Article end ``` ```ruby -# Blorgh/lib/concerns/models/article +# Blorgh/lib/concerns/models/article.rb module Blorgh::Concerns::Models::Article extend ActiveSupport::Concern @@ -1209,7 +1209,7 @@ module Blorgh::Concerns::Models::Article attr_accessor :author_name belongs_to :author, class_name: "User" - before_save :set_author + before_validation :set_author private def set_author @@ -1358,7 +1358,7 @@ need to require `admin.css` or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorgh/admin.css"` in its stylesheets. In this situation, you should explicitly define these assets for precompilation. This tells sprockets to add -your engine assets when `rake assets:precompile` is triggered. +your engine assets when `bin/rails assets:precompile` is triggered. You can define assets for precompilation in `engine.rb`: diff --git a/source/form_helpers.md b/source/form_helpers.md index f3f7415..048fe19 100644 --- a/source/form_helpers.md +++ b/source/form_helpers.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Form Helpers ============ @@ -38,7 +40,9 @@ When called without arguments like this, it creates a `` tag which, when s ``` -You'll notice that the HTML contains `input` element with type `hidden`. This `input` is important, because the form cannot be successfully submitted without it. The hidden input element has name attribute of `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf). +You'll notice that the HTML contains an `input` element with type `hidden`. This `input` is important, because the form cannot be successfully submitted without it. The hidden input element with the name `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their action is "GET" or "POST". + +The second input element with the name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf). ### A Generic Search Form @@ -101,9 +105,9 @@ checkboxes, text fields, and radio buttons. These basic helpers, with names ending in `_tag` (such as `text_field_tag` and `check_box_tag`), generate just a single `` element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form -data, and will make its way to the `params` hash in the controller with the -value entered by the user for that field. For example, if the form contains `<%= -text_field_tag(:query) %>`, then you would be able to get the value of this +data, and will make its way to the `params` in the controller with the +value entered by the user for that field. For example, if the form contains +`<%= text_field_tag(:query) %>`, then you would be able to get the value of this field in the controller with `params[:query]`. When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in [chapter 7 of this guide](#understanding-parameter-naming-conventions). For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html). @@ -170,7 +174,6 @@ URL fields, email fields, number fields and range fields: <%= search_field(:user, :name) %> <%= telephone_field(:user, :phone) %> <%= date_field(:user, :born_on) %> -<%= datetime_field(:user, :meeting_time) %> <%= datetime_local_field(:user, :graduation_day) %> <%= month_field(:user, :birthday_month) %> <%= week_field(:user, :birthday_week) %> @@ -191,7 +194,6 @@ Output: - @@ -209,9 +211,8 @@ IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, email, number and range inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). -There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are -[Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/), -which provide a simple way to add functionality based on the presence of +There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a popular tool at the moment is +[Modernizr](https://modernizr.com/), which provides a simple way to add functionality based on the presence of detected HTML5 features. TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging). @@ -239,7 +240,7 @@ Upon form submission the value entered by the user will be stored in `params[:pe WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object. -Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations](./active_record_validations.html#displaying-validation-errors-in-views) guide. +Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations](active_record_validations.html#displaying-validation-errors-in-views) guide. ### Binding a Form to an Object @@ -273,14 +274,14 @@ There are a few things to note here: The resulting HTML is: ```html -
+
``` -The name passed to `form_for` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the parameter_names section. +The name passed to `form_for` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the [parameter_names section](#understanding-parameter-naming-conventions). The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. @@ -289,8 +290,8 @@ You can create a similar binding without actually creating `
` tags with th ```erb <%= form_for @person, url: {action: "create"} do |person_form| %> <%= person_form.text_field :name %> - <%= fields_for @person.contact_detail do |contact_details_form| %> - <%= contact_details_form.text_field :phone_number %> + <%= fields_for @person.contact_detail do |contact_detail_form| %> + <%= contact_detail_form.text_field :phone_number %> <% end %> <% end %> ``` @@ -298,7 +299,7 @@ You can create a similar binding without actually creating `` tags with th which produces the following output: ```html - +
@@ -314,7 +315,7 @@ The Article model is directly available to users of the application, so - follow resources :articles ``` -TIP: Declaring a resource has a number of side-affects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources. +TIP: Declaring a resource has a number of side effects. See [Rails Routing From the Outside In](routing.html#resource-routing-the-rails-default) for more information on setting up and using resources. When dealing with RESTful resources, calls to `form_for` can get significantly easier if you rely on **record identification**. In short, you can just pass the model instance and have Rails figure out model name and the rest: @@ -375,7 +376,7 @@ output: ``` -When parsing POSTed data, Rails will take into account the special `_method` parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example). +When parsing POSTed data, Rails will take into account the special `_method` parameter and act as if the HTTP method was the one specified inside it ("PATCH" in this example). Making Select Boxes with Ease ----------------------------- @@ -439,7 +440,7 @@ Whenever Rails sees that the internal value of an option being generated matches TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer `2` you cannot pass `"2"` to `options_for_select` - you must pass `2`. Be aware of values extracted from the `params` hash as they are all strings. -WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true. +WARNING: When `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true. You can add arbitrary attributes to the options using hashes: @@ -654,7 +655,7 @@ NOTE: If the user has not selected a file the corresponding parameter will be an ### Dealing with Ajax -Unlike other forms making an asynchronous file upload form is not as simple as providing `form_for` with `remote: true`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. +Unlike other forms, making an asynchronous file upload form is not as simple as providing `form_for` with `remote: true`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. Customizing Form Builders ------------------------- @@ -685,7 +686,14 @@ class LabellingFormBuilder < ActionView::Helpers::FormBuilder end ``` -If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option. +If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option: + +```ruby +def labeled_form_for(record, options = {}, &block) + options.merge! builder: LabellingFormBuilder + form_for record, options, &block +end +``` The form builder used also determines what happens when you do @@ -703,13 +711,6 @@ action for a Person model, `params[:person]` would usually be a hash of all the Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. -TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rack's parameter parser. For example, - -```ruby -Rack::Utils.parse_query "name=fred&phone=0123456789" -# => {"name"=>"fred", "phone"=>"0123456789"} -``` - ### Basic Structures The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in `params`. For example, if a form contains: @@ -720,7 +721,7 @@ The two basic structures are arrays and hashes. Hashes mirror the syntax used fo the `params` hash will contain -```erb +```ruby {'person' => {'name' => 'Henry'}} ``` @@ -877,12 +878,12 @@ Many apps grow beyond simple forms editing a single object. For example, when cr Active Record provides model level support via the `accepts_nested_attributes_for` method: ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord has_many :addresses accepts_nested_attributes_for :addresses end -class Address < ActiveRecord::Base +class Address < ApplicationRecord belongs_to :person end ``` @@ -970,7 +971,7 @@ private You can allow users to delete associated objects by passing `allow_destroy: true` to `accepts_nested_attributes_for` ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord has_many :addresses accepts_nested_attributes_for :addresses, allow_destroy: true end @@ -1011,7 +1012,7 @@ end It is often useful to ignore sets of fields that the user has not filled in. You can control this by passing a `:reject_if` proc to `accepts_nested_attributes_for`. This proc will be called with each hash of attributes submitted by the form. If the proc returns `false` then Active Record will not build an associated object for that hash. The example below only tries to build an address if the `kind` attribute is set. ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord has_many :addresses accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?} end diff --git a/source/generators.md b/source/generators.md index f5d2c67..32bbdc5 100644 --- a/source/generators.md +++ b/source/generators.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Creating and Customizing Rails Generators & Templates ===================================================== @@ -197,11 +199,11 @@ $ bin/rails generate scaffold User name:string create app/views/users/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/users.js.coffee + create app/assets/javascripts/users.coffee invoke scss - create app/assets/stylesheets/users.css.scss + create app/assets/stylesheets/users.scss invoke scss - create app/assets/stylesheets/scaffolds.css.scss + create app/assets/stylesheets/scaffolds.scss ``` Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. @@ -407,7 +409,7 @@ $ bin/rails generate scaffold Comment body:text create app/views/comments/show.json.jbuilder invoke assets invoke coffee - create app/assets/javascripts/comments.js.coffee + create app/assets/javascripts/comments.coffee invoke scss ``` @@ -501,6 +503,14 @@ Adds a specified source to `Gemfile`: add_source "http://gems.github.com/" ``` +This method also takes a block: + +```ruby +add_source "http://gems.github.com/" do + gem "rspec-rails" +end +``` + ### `inject_into_file` Injects a block of code into a defined position in your file. diff --git a/source/getting_started.md b/source/getting_started.md index e68e07a..662fe78 100644 --- a/source/getting_started.md +++ b/source/getting_started.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Getting Started with Rails ========================== @@ -21,9 +23,12 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. -* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby - versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org). +* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer. +* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you + are using Windows. +* The [RubyGems](https://rubygems.org) packaging system, which is installed with + Ruby by default. To learn more about RubyGems, please read the + [RubyGems Guides](http://guides.rubygems.org). * A working installation of the [SQLite3 Database](https://www.sqlite.org). Rails is a web application framework running on the Ruby programming language. @@ -32,7 +37,7 @@ curve diving straight into Rails. There are several curated lists of online reso for learning Ruby: * [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/) -* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby) +* [List of Free Programming Books](https://github.com/vhf/free-programming-books/blob/master/free-programming-books.md#ruby) Be aware that some resources, while still excellent, cover versions of Ruby as old as 1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day @@ -48,7 +53,7 @@ code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun. -Rails is opinionated software. It makes the assumption that there is the "best" +Rails is opinionated software. It makes the assumption that there is a "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from @@ -67,10 +72,9 @@ The Rails philosophy includes two major guiding principles: Creating a New Rails Project ---------------------------- - -The best way to use this guide is to follow each step as it happens, no code or -step needed to make this example application has been left out, so you can -literally follow along step by step. +The best way to read this guide is to follow it step by step. All steps are +essential to run this example application and no additional code or steps are +needed. By following along with this guide, you'll create a Rails project called `blog`, a (very) simple weblog. Before you can start building the application, @@ -87,17 +91,17 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed: +```bash +$ ruby -v +ruby 2.3.0p0 +``` + TIP: A number of tools exist to help you quickly install Ruby and Ruby on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp). For more installation methods for most Operating Systems take a look at [ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/). -```bash -$ ruby -v -ruby 2.0.0p353 -``` - Many popular UNIX-like OSes ship with an acceptable version of SQLite3. On Windows, if you installed Rails through Rails Installer, you already have SQLite installed. Others can find installation instructions @@ -123,7 +127,7 @@ run the following: $ rails --version ``` -If it says something like "Rails 4.2.0", you are ready to continue. +If it says something like "Rails 5.0.0", you are ready to continue. ### Creating the Blog Application @@ -144,6 +148,10 @@ This will create a Rails application called Blog in a `blog` directory and install the gem dependencies that are already mentioned in `Gemfile` using `bundle install`. +NOTE: If you're using Windows Subsystem for Linux then there are currently some +limitations on file system notifications that mean you should disable the `spring` +and `listen` gems which you can do by running `rails new blog --skip-spring --skip-listen`. + TIP: You can see all of the command line options that the Rails application builder accepts by running `rails new -h`. @@ -161,7 +169,7 @@ of the files and folders that Rails created by default: | File/Folder | Purpose | | ----------- | ------- | |app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| -|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, deploy or run your application.| +|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.| |config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| |config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Contains your current database schema, as well as the database migrations.| @@ -170,9 +178,9 @@ of the files and folders that Rails created by default: |log/|Application log files.| |public/|The only folder seen by the world as-is. Contains static files and compiled assets.| |Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.| -|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| +|README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| -|tmp/|Temporary files (like cache, pid, and session files).| +|tmp/|Temporary files (like cache and pid files).| |vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.| Hello, Rails! @@ -191,6 +199,9 @@ following in the `blog` directory: $ bin/rails server ``` +TIP: If you are using Windows, you have to pass the scripts under the `bin` +folder directly to the Ruby interpreter e.g. `ruby bin\rails server`. + TIP: Compiling CoffeeScript and JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence of a runtime you will see an `execjs` error during asset compilation. @@ -199,9 +210,9 @@ Rails adds the `therubyracer` gem to the generated `Gemfile` in a commented line for new apps and you can uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby users and is added by default to the `Gemfile` in apps generated under JRuby. You can investigate -all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). +all the supported runtimes at [ExecJS](https://github.com/rails/execjs#readme). -This will fire up WEBrick, a web server distributed with Ruby by default. To see +This will fire up Puma, a web server distributed with Rails by default. To see your application in action, open a browser window and navigate to . You should see the Rails default information page: @@ -216,8 +227,7 @@ the server. The "Welcome aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a -page. You can also click on the _About your application's environment_ link to -see a summary of your application's environment. +page. ### Say "Hello", Rails @@ -238,11 +248,11 @@ Ruby) which is processed by the request cycle in Rails before being sent to the user. To create a new controller, you will need to run the "controller" generator and -tell it you want a controller called "welcome" with an action called "index", +tell it you want a controller called "Welcome" with an action called "index", just like this: ```bash -$ bin/rails generate controller welcome index +$ bin/rails generate controller Welcome index ``` Rails will create several files and a route for you. @@ -291,34 +301,32 @@ Open the file `config/routes.rb` in your editor. Rails.application.routes.draw do get 'welcome/index' - # The priority is based upon order of creation: - # first created -> highest priority. - # - # You can have the root of your site routed with "root" - # root 'welcome#index' - # - # ... + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end ``` This is your application's _routing file_ which holds entries in a special [DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language) that tells Rails how to connect incoming requests to -controllers and actions. This file contains many sample routes on commented -lines, and one of them actually shows you how to connect the root of your site -to a specific controller and action. Find the line beginning with `root` and -uncomment it. It should look something like the following: +controllers and actions. +Edit this file by adding the line of code `root 'welcome#index'`. +It should look something like the following: ```ruby -root 'welcome#index' +Rails.application.routes.draw do + get 'welcome/index' + + root 'welcome#index' +end ``` `root 'welcome#index'` tells Rails to map requests to the root of the application to the welcome controller's index action and `get 'welcome/index'` tells Rails to map requests to to the welcome controller's index action. This was created earlier when you ran the -controller generator (`rails generate controller welcome index`). +controller generator (`bin/rails generate controller Welcome index`). -Launch the web server again if you stopped it to generate the controller (`rails +Launch the web server again if you stopped it to generate the controller (`bin/rails server`) and navigate to in your browser. You'll see the "Hello, Rails!" message you put into `app/views/welcome/index.html.erb`, indicating that this new route is indeed going to `WelcomeController`'s `index` @@ -340,7 +348,7 @@ operations are referred to as _CRUD_ operations. Rails provides a `resources` method which can be used to declare a standard REST resource. You need to add the _article resource_ to the -`config/routes.rb` as follows: +`config/routes.rb` so the file will look as follows: ```ruby Rails.application.routes.draw do @@ -351,13 +359,13 @@ Rails.application.routes.draw do end ``` -If you run `rake routes`, you'll see that it has defined routes for all the +If you run `bin/rails routes`, you'll see that it has defined routes for all the standard RESTful actions. The meaning of the prefix column (and other columns) will be seen later, but for now notice that Rails has inferred the singular form `article` and makes meaningful use of the distinction. ```bash -$ bin/rake routes +$ bin/rails routes Prefix Verb URI Pattern Controller#Action articles GET /articles(.:format) articles#index POST /articles(.:format) articles#create @@ -372,7 +380,7 @@ edit_article GET /articles/:id/edit(.:format) articles#edit In the next section, you will add the ability to create new articles in your application and be able to view them. This is the "C" and the "R" from CRUD: -creation and reading. The form for doing this will look like this: +create and read. The form for doing this will look like this: ![The new article form](images/getting_started/new_article.png) @@ -395,7 +403,7 @@ a controller called `ArticlesController`. You can do this by running this command: ```bash -$ bin/rails g controller articles +$ bin/rails generate controller Articles ``` If you open up the newly generated `app/controllers/articles_controller.rb` @@ -450,7 +458,7 @@ available, Rails will raise an exception. In the above image, the bottom line has been truncated. Let's see what the full error message looks like: ->Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" +>ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. That's quite a lot of text! Let's quickly go through and understand what each part of it means. @@ -460,27 +468,24 @@ The first part identifies which template is missing. In this case, it's the then it will attempt to load a template called `application/new`. It looks for one here because the `ArticlesController` inherits from `ApplicationController`. -The next part of the message contains a hash. The `:locale` key in this hash -simply indicates which spoken language template should be retrieved. By default, -this is the English - or "en" - template. The next key, `:formats` specifies the -format of template to be served in response. The default format is `:html`, and -so Rails is looking for an HTML template. The final key, `:handlers`, is telling -us what _template handlers_ could be used to render our template. `:erb` is most -commonly used for HTML templates, `:builder` is used for XML templates, and -`:coffee` uses CoffeeScript to build JavaScript templates. - -The final part of this message tells us where Rails has looked for the templates. -Templates within a basic Rails application like this are kept in a single -location, but in more complex applications it could be many different paths. +The next part of the message contains `request.formats` which specifies +the format of template to be served in response. It is set to `text/html` as we +requested this page via browser, so Rails is looking for an HTML template. +`request.variants` specifies what kind of physical devices would be served by +the response and helps Rails determine which template to use in the response. +It is empty because no information has been provided. The simplest template that would work in this case would be one located at `app/views/articles/new.html.erb`. The extension of this file name is important: the first extension is the _format_ of the template, and the second extension -is the _handler_ that will be used. Rails is attempting to find a template -called `articles/new` within `app/views` for the application. The format for -this template can only be `html` and the handler must be one of `erb`, -`builder` or `coffee`. Because you want to create a new HTML form, you will be -using the `ERB` language which is designed to embed Ruby in HTML. +is the _handler_ that will be used to render the template. Rails is attempting +to find a template called `articles/new` within `app/views` for the +application. The format for this template can only be `html` and the default +handler for HTML is `erb`. Rails uses other handlers for other formats. +`builder` handler is used to build XML templates and `coffee` handler uses +CoffeeScript to build JavaScript templates. Because you want to create a new +HTML form, you will be using the `ERB` language which is designed to embed Ruby +in HTML. Therefore the file should be called `articles/new.html.erb` and needs to be located inside the `app/views` directory of the application. @@ -551,10 +556,10 @@ this: In this example, the `articles_path` helper is passed to the `:url` option. To see what Rails will do with this, we look back at the output of -`rake routes`: +`bin/rails routes`: ```bash -$ bin/rake routes +$ bin/rails routes Prefix Verb URI Pattern Controller#Action articles GET /articles(.:format) articles#index POST /articles(.:format) articles#create @@ -599,9 +604,11 @@ class ArticlesController < ApplicationController end ``` -If you re-submit the form now, you'll see another familiar error: a template is -missing. That's ok, we can ignore that for now. What the `create` action should -be doing is saving our new article to the database. +If you re-submit the form now, you may not see any change on the page. Don't worry! +This is because Rails by default returns `204 No Content` response for an action if +we don't specify what the response should be. We just added the `create` action +but didn't specify anything about how the response should be. In this case, the +`create` action should save our new article to the database. When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller @@ -614,10 +621,10 @@ def create end ``` -The `render` method here is taking a very simple hash with a key of `plain` and +The `render` method here is taking a very simple hash with a key of `:plain` and value of `params[:article].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` -method returns an `ActiveSupport::HashWithIndifferentAccess` object, which +method returns an `ActionController::Parameters` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. @@ -627,7 +634,7 @@ If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following: ```ruby -{"title"=>"First article!", "text"=>"This is my first article."} +"First Article!", "text"=>"This is my first article."} permitted: false> ``` This action is now displaying the parameters for the article that are coming in @@ -645,7 +652,7 @@ run this command in your terminal: $ bin/rails generate model Article title:string text:text ``` -With that command we told Rails that we want a `Article` model, together +With that command we told Rails that we want an `Article` model, together with a _title_ attribute of type string, and a _text_ attribute of type text. Those attributes are automatically added to the `articles` table in the database and mapped to the `Article` model. @@ -661,7 +668,7 @@ models, as that will be done automatically by Active Record. ### Running a Migration -As we've just seen, `rails generate model` created a _database migration_ file +As we've just seen, `bin/rails generate model` created a _database migration_ file inside the `db/migrate` directory. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it's possible to undo a migration after @@ -672,13 +679,13 @@ If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file (remember, yours will have a slightly different name), here's what you'll find: ```ruby -class CreateArticles < ActiveRecord::Migration +class CreateArticles < ActiveRecord::Migration[5.0] def change create_table :articles do |t| t.string :title t.text :text - t.timestamps null: false + t.timestamps end end end @@ -694,10 +701,10 @@ two timestamp fields to allow Rails to track article creation and update times. TIP: For more information about migrations, refer to [Rails Database Migrations] (migrations.html). -At this point, you can use a rake command to run the migration: +At this point, you can use a bin/rails command to run the migration: ```bash -$ bin/rake db:migrate +$ bin/rails db:migrate ``` Rails will execute this migration command and tell you it created the Articles @@ -714,7 +721,7 @@ NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the `development` section of your `config/database.yml` file. If you would like to execute migrations in another environment, for instance in production, you must explicitly pass it when -invoking the command: `rake db:migrate RAILS_ENV=production`. +invoking the command: `bin/rails db:migrate RAILS_ENV=production`. ### Saving data in the controller @@ -759,7 +766,7 @@ Why do you have to bother? The ability to grab and automatically assign all controller parameters to your model in one shot makes the programmer's job easier, but this convenience also allows malicious use. What if a request to the server was crafted to look like a new article form submit but also included -extra fields with values that violated your applications integrity? They would +extra fields with values that violated your application's integrity? They would be 'mass assigned' into your model and then into the database along with the good stuff - potentially breaking your application or worse. @@ -801,7 +808,7 @@ If you submit the form again now, Rails will complain about not finding the `show` action. That's not very useful though, so let's add the `show` action before proceeding. -As we have seen in the output of `rake routes`, the route for `show` action is +As we have seen in the output of `bin/rails routes`, the route for `show` action is as follows: ``` @@ -831,7 +838,7 @@ class ArticlesController < ApplicationController def new end - # snipped for brevity + # snippet for brevity ``` A couple of things to note. We use `Article.find` to find the article we're @@ -863,7 +870,7 @@ Visit and give it a try! ### Listing all articles We still need a way to list all our articles, so let's do that. -The route for this as per output of `rake routes` is: +The route for this as per output of `bin/rails routes` is: ``` articles GET /articles(.:format) articles#index @@ -887,7 +894,7 @@ class ArticlesController < ApplicationController def new end - # snipped for brevity + # snippet for brevity ``` And then finally, add the view for this action, located at @@ -906,6 +913,7 @@ And then finally, add the view for this action, located at <%= article.title %> <%= article.text %> + <%= link_to 'Show', article_path(article) %> <% end %> @@ -982,21 +990,22 @@ and restart the web server when a change is made. The model file, `app/models/article.rb` is about as simple as it can get: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord end ``` There isn't much to this file - but note that the `Article` class inherits from -`ActiveRecord::Base`. Active Record supplies a great deal of functionality to -your Rails models for free, including basic database CRUD (Create, Read, Update, -Destroy) operations, data validation, as well as sophisticated search support -and the ability to relate multiple models to one another. +`ApplicationRecord`. `ApplicationRecord` inherits from `ActiveRecord::Base` +which supplies a great deal of functionality to your Rails models for free, +including basic database CRUD (Create, Read, Update, Destroy) operations, data +validation, as well as sophisticated search support and the ability to relate +multiple models to one another. Rails includes methods to help you validate the data that you send to models. Open the `app/models/article.rb` file and edit it: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord validates :title, presence: true, length: { minimum: 5 } end @@ -1234,10 +1243,9 @@ article we want to show the form back to the user. We reuse the `article_params` method that we defined earlier for the create action. -TIP: You don't need to pass all attributes to `update`. For -example, if you'd call `@article.update(title: 'A new title')` -Rails would only update the `title` attribute, leaving all other -attributes untouched. +TIP: It is not necessary to pass all the attributes to `update`. For example, +if `@article.update(title: 'A new title')` was called, Rails would only update +the `title` attribute, leaving all other attributes untouched. Finally, we want to show a link to the `edit` action in the list of all the articles, so let's add that now to `app/views/articles/index.html.erb` to make @@ -1269,8 +1277,8 @@ bottom of the template: ```html+erb ... -<%= link_to 'Back', articles_path %> | -<%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` And here's how our app looks so far: @@ -1357,7 +1365,7 @@ Then do the same for the `app/views/articles/edit.html.erb` view: We're now ready to cover the "D" part of CRUD, deleting articles from the database. Following the REST convention, the route for -deleting articles as per output of `rake routes` is: +deleting articles as per output of `bin/rails routes` is: ```ruby DELETE /articles/:id(.:format) articles#destroy @@ -1473,16 +1481,20 @@ Finally, add a 'Destroy' link to your `index` action template ``` Here we're using `link_to` in a different way. We pass the named route as the -second argument, and then the options as another argument. The `:method` and -`:'data-confirm'` options are used as HTML5 attributes so that when the link is -clicked, Rails will first show a confirm dialog to the user, and then submit the -link with method `delete`. This is done via the JavaScript file `jquery_ujs` -which is automatically included into your application's layout -(`app/views/layouts/application.html.erb`) when you generated the application. -Without this file, the confirmation dialog box wouldn't appear. +second argument, and then the options as another argument. The `method: :delete` +and `data: { confirm: 'Are you sure?' }` options are used as HTML5 attributes so +that when the link is clicked, Rails will first show a confirm dialog to the +user, and then submit the link with method `delete`. This is done via the +JavaScript file `jquery_ujs` which is automatically included in your +application's layout (`app/views/layouts/application.html.erb`) when you +generated the application. Without this file, the confirmation dialog box won't +appear. ![Confirm Dialog](images/getting_started/confirm_dialog.png) +TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on +[Working With JavaScript in Rails](working_with_javascript_in_rails.html) guide. + Congratulations, you can now create, show, list, update and destroy articles. @@ -1500,7 +1512,7 @@ comments on articles. We're going to see the same generator that we used before when creating the `Article` model. This time we'll create a `Comment` model to hold -reference of article comments. Run this command in your terminal: +reference to an article. Run this command in your terminal: ```bash $ bin/rails generate model Comment commenter:string body:text article:references @@ -1512,13 +1524,13 @@ This command will generate four files: | -------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | db/migrate/20140120201010_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) | | app/models/comment.rb | The Comment model | -| test/models/comment_test.rb | Testing harness for the comments model | +| test/models/comment_test.rb | Testing harness for the comment model | | test/fixtures/comments.yml | Sample comments for use in testing | First, take a look at `app/models/comment.rb`: ```ruby -class Comment < ActiveRecord::Base +class Comment < ApplicationRecord belongs_to :article end ``` @@ -1527,31 +1539,34 @@ This is very similar to the `Article` model that you saw earlier. The difference is the line `belongs_to :article`, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide. +The (`:references`) keyword used in the bash command is a special data type for models. +It creates a new column on your database table with the provided model name appended with an `_id` +that can hold integer values. You can get a better understanding after analyzing the +`db/schema.rb` file below. + In addition to the model, Rails has also made a migration to create the corresponding database table: ```ruby -class CreateComments < ActiveRecord::Migration +class CreateComments < ActiveRecord::Migration[5.0] def change create_table :comments do |t| t.string :commenter t.text :body + t.references :article, foreign_key: true - # this line adds an integer column called `article_id`. - t.references :article, index: true - - t.timestamps null: false + t.timestamps end end end ``` -The `t.references` line sets up a foreign key column for the association between -the two models. An index for this association is also created on this column. -Go ahead and run the migration: +The `t.references` line creates an integer column called `article_id`, an index +for it, and a foreign key constraint that points to the `id` column of the `articles` +table. Go ahead and run the migration: ```bash -$ bin/rake db:migrate +$ bin/rails db:migrate ``` Rails is smart enough to only execute the migrations that have not already been @@ -1578,7 +1593,7 @@ association. You've already seen the line of code inside the `Comment` model (app/models/comment.rb) that makes each comment belong to an Article: ```ruby -class Comment < ActiveRecord::Base +class Comment < ApplicationRecord belongs_to :article end ``` @@ -1587,7 +1602,7 @@ You'll need to edit `app/models/article.rb` to add the other side of the association: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord has_many :comments validates :title, presence: true, length: { minimum: 5 } @@ -1676,8 +1691,8 @@ So first, we'll wire up the Article show template

<% end %> -<%= link_to 'Back', articles_path %> | -<%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` This adds a form on the `Article` show page that creates a new comment by @@ -1757,8 +1772,8 @@ add that to the `app/views/articles/show.html.erb`.

<% end %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` Now you can add articles and comments to your blog and have them show up in the @@ -1823,8 +1838,8 @@ following:

<% end %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` This will now render the partial in `app/views/comments/_comment.html.erb` once @@ -1873,8 +1888,8 @@ Then you make the `app/views/articles/show.html.erb` look like the following:

Add a comment:

<%= render 'comments/form' %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` The second render just defines the partial template we want to render, @@ -1953,7 +1968,7 @@ you to use the `dependent` option of an association to achieve this. Modify the Article model, `app/models/article.rb`, as follows: ```ruby -class Article < ActiveRecord::Base +class Article < ApplicationRecord has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } @@ -1990,7 +2005,7 @@ class ArticlesController < ApplicationController @articles = Article.all end - # snipped for brevity + # snippet for brevity ``` We also want to allow only authenticated users to delete comments, so in the @@ -2006,7 +2021,7 @@ class CommentsController < ApplicationController # ... end - # snipped for brevity + # snippet for brevity ``` Now if you try to create a new article, you will be greeted with a basic HTTP @@ -2032,28 +2047,17 @@ What's Next? ------------ Now that you've seen your first Rails application, you should feel free to -update it and experiment on your own. But you don't have to do everything -without help. As you need assistance getting up and running with Rails, feel -free to consult these support resources: +update it and experiment on your own. + +Remember you don't have to do everything without help. As you need assistance +getting up and running with Rails, feel free to consult these support +resources: * The [Ruby on Rails Guides](index.html) * The [Ruby on Rails Tutorial](http://railstutorial.org/book) * The [Ruby on Rails mailing list](http://groups.google.com/group/rubyonrails-talk) * The [#rubyonrails](irc://irc.freenode.net/#rubyonrails) channel on irc.freenode.net -Rails also comes with built-in help that you can generate using the rake -command-line utility: - -* Running `rake doc:guides` will put a full copy of the Rails Guides in the - `doc/guides` folder of your application. Open `doc/guides/index.html` in your - web browser to explore the Guides. -* Running `rake doc:rails` will put a full copy of the API documentation for - Rails in the `doc/api` folder of your application. Open `doc/api/index.html` - in your web browser to explore the API documentation. - -TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake -task you need to install the RedCloth and Nokogiri gems. Add it to your `Gemfile` and run -`bundle install` and you're ready to go. Configuration Gotchas --------------------- diff --git a/source/i18n.md b/source/i18n.md index 75b5275..64ff6af 100644 --- a/source/i18n.md +++ b/source/i18n.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Internationalization (I18n) API ===================================== @@ -23,7 +25,7 @@ After reading this guide, you will know: * How I18n works in Ruby on Rails * How to correctly use I18n into a RESTful application in various ways -* How to use I18n to translate ActiveRecord errors or ActionMailer E-mail subjects +* How to use I18n to translate Active Record errors or Action Mailer E-mail subjects * Some other tools to go further with the translation process of your application -------------------------------------------------------------------------------- @@ -38,7 +40,7 @@ Internationalization is a complex problem. Natural languages differ in so many w * providing support for English and similar languages out of the box * making it easy to customize and extend everything for other languages -As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. +As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**. _Localization_ of a Rails application means defining translated values for these strings in desired languages. ### The Overall Architecture of the Library @@ -49,7 +51,7 @@ Thus, the Ruby I18n gem is split into two parts: As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. -NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section [Using different backends](#using-different-backends) below. +NOTE: It is possible to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section [Using different backends](#using-different-backends) below. ### The Public I18n API @@ -82,13 +84,13 @@ So, let's internationalize a simple Rails application from the ground up in the Setup the Rails Application for Internationalization ---------------------------------------------------- -There are just a few simple steps to get up and running with I18n support for your application. +There are a few steps to get up and running with I18n support for a Rails application. ### Configure the I18n Module -Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily. +Following the _convention over configuration_ philosophy, Rails I18n provides reasonable default translation strings. When different translation strings are needed, they can be overridden. -Rails adds all `.rb` and `.yml` files from the `config/locales` directory to your **translations load path**, automatically. +Rails adds all `.rb` and `.yml` files from the `config/locales` directory to the **translations load path**, automatically. The default `en.yml` locale in this directory contains a sample pair of translation strings: @@ -99,15 +101,15 @@ en: This means, that in the `:en` locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Model validation messages in the [`activemodel/lib/active_model/locale/en.yml`](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml) file or time and date formats in the [`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml) file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. -The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations. +The I18n library will use **English** as a **default locale**, i.e. if a different locale is not set, `:en` will be used for looking up translations. NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it. -The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. +The **translations load path** (`I18n.load_path`) is an array of paths to files that will be loaded automatically. Configuring this path allows for customization of translations directory structure and file naming scheme. -NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced. +NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced. -The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. +The default `config/application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. ```ruby # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. @@ -115,31 +117,25 @@ The default `application.rb` file has instructions on how to add locales from an # config.i18n.default_locale = :de ``` -### Optional: Custom I18n Configuration Setup - -For the sake of completeness, let's mention that if you do not want to use the `application.rb` file for some reason, you can always wire up things manually, too. - -To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer: +The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `config/application.rb`: ```ruby -# in config/initializers/locale.rb +# config/initializers/locale.rb -# tell the I18n library where to find your translations +# Where the I18n library should search for translation files I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')] -# set default locale to something other than :en +# Set default locale to something other than :en I18n.default_locale = :pt ``` -### Setting and Passing the Locale +### Managing the Locale across Requests -If you want to translate your Rails application to a **single language other than English** (the default locale), you can set I18n.default_locale to your locale in `application.rb` or an initializer as shown above, and it will persist through the requests. +The default locale is used for all translations unless `I18n.locale` is explicitly set. -However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests. +A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request. -WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. - -The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this: +The locale can be set in a `before_action` in the `ApplicationController`: ```ruby before_action :set_locale @@ -149,11 +145,11 @@ def set_locale end ``` -This requires you to pass the locale as a URL query parameter as in `http://example.com/books?locale=pt`. (This is, for example, Google's approach.) So `http://localhost:3000?locale=pt` will load the Portuguese localization, whereas `http://localhost:3000?locale=de` would load the German localization, and so on. You may skip the next section and head over to the **Internationalize your application** section, if you want to try things out by manually placing the locale in the URL and reloading the page. +This example illustrates this using a URL query parameter to set the locale (e.g. `http://example.com/books?locale=pt`). With this approach, `http://localhost:3000?locale=pt` renders the Portuguese localization, while `http://localhost:3000?locale=de` loads a German localization. -Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual `http://example.com/pt/books` versus `http://example.com/en/books`. Let's discuss the different options you have. +The locale can be set using one of many different approaches. -### Setting the Locale from the Domain Name +#### Setting the Locale from the Domain Name One option you have is to set the locale from the domain name where your application runs. For example, we want `www.example.com` to load the English (or default) locale, and `www.example.es` to load the Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages: @@ -199,14 +195,14 @@ end If your application includes a locale switching menu, you would then have something like this in it: ```ruby -link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}") +link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}") ``` assuming you would set `APP_CONFIG[:deutsch_website_url]` to some value like `http://www.application.de`. This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path). -### Setting the Locale from the URL Params +#### Setting the Locale from URL Params The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case. @@ -214,14 +210,14 @@ This approach has almost the same set of advantages as setting the locale from t Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(/service/locale: I18n.locale))`, would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding `default_url_options`). We can include something like this in our `ApplicationController` then: ```ruby # app/controllers/application_controller.rb -def default_url_options(options = {}) - { locale: I18n.locale }.merge options +def default_url_options + { locale: I18n.locale } end ``` @@ -229,7 +225,7 @@ Every helper method dependent on `url_for` (e.g. helpers for named routes like ` You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this. -You probably want URLs to look like this: `www.example.com/en/books` (which loads the English locale) and `www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scoping`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html) option in this way: +You probably want URLs to look like this: `http://www.example.com/en/books` (which loads the English locale) and `http://www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scope`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html): ```ruby # config/routes.rb @@ -238,7 +234,9 @@ scope "/:locale" do end ``` -Now, when you call the `books_path` method you should get `"/en/books"` (for the default locale). An URL like `http://localhost:3001/nl/books` should load the Dutch locale, then, and following calls to `books_path` should return `"/nl/books"` (because the locale changed). +Now, when you call the `books_path` method you should get `"/en/books"` (for the default locale). A URL like `http://localhost:3001/nl/books` should load the Dutch locale, then, and following calls to `books_path` should return `"/nl/books"` (because the locale changed). + +WARNING. Since the return value of `default_url_options` is cached per request, the URLs in a locale selector cannot be generated invoking helpers in a loop that sets the corresponding `I18n.locale` in each iteration. Instead, leave `I18n.locale` untouched, and pass an explicit `:locale` option to the helper, or edit `request.original_fullpath`. If you don't want to force the use of a locale in your routes you can use an optional path scope (denoted by the parentheses) like so: @@ -251,7 +249,7 @@ end With this approach you will not get a `Routing Error` when accessing your resources such as `http://localhost:3001/books` without a locale. This is useful for when you want to use the default locale when one is not specified. -Of course, you need to take special care of the root URL (usually "homepage" or "dashboard") of your application. An URL like `http://localhost:3001/nl` will not work automatically, because the `root to: "books#index"` declaration in your `routes.rb` doesn't take locale into account. (And rightly so: there's only one "root" URL.) +Of course, you need to take special care of the root URL (usually "homepage" or "dashboard") of your application. A URL like `http://localhost:3001/nl` will not work automatically, because the `root to: "books#index"` declaration in your `routes.rb` doesn't take locale into account. (And rightly so: there's only one "root" URL.) You would probably need to map URLs like these: @@ -264,14 +262,23 @@ Do take special care about the **order of your routes**, so this route declarati NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator). -### Setting the Locale from the Client Supplied Information +#### Setting the Locale from User Preferences + +An application with authenticated users may allow users to set a locale preference through the application's interface. With this approach, a user's selected locale preference is persisted in the database and used to set the locale for authenticated requests by that user. + +```ruby +def set_locale + I18n.locale = current_user.try(:locale) || I18n.default_locale +end +``` -In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above. +#### Choosing an Implied Locale +When an explicit locale has not been set for a request (e.g. via one of the above methods), an application should attempt to infer the desired locale. -#### Using `Accept-Language` +##### Inferring Locale from the Language Header -One source of client supplied information would be an `Accept-Language` HTTP header. People may [set this in their browser](http://www.w3.org/International/questions/qa-lang-priorities) or other clients (such as _curl_). +The `Accept-Language` HTTP header indicates the preferred language for request's response. Browsers [set this header value based on the user's language preference settings](http://www.w3.org/International/questions/qa-lang-priorities), making it a good first choice when inferring a locale. A trivial implementation of using an `Accept-Language` header would be: @@ -288,24 +295,27 @@ private end ``` -Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). -#### Using GeoIP (or Similar) Database +In practice, more robust code is necessary to do this reliably. Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) library or Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb) Rack middleware provide solutions to this problem. -Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. +##### Inferring the Locale from IP Geolocation -#### User Profile +The IP address of the client making the request can be used to infer the client's region and thus their locale. Services such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry) or gems like [geocoder](https://github.com/alexreisner/geocoder) can be used to implement this approach. -You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. +In general, this approach is far less reliable than using the language header and is not recommended for most web applications. -Internationalizing your Application +#### Storing the Locale from the Session or Cookies + +WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. + +Internationalization and Localization ----------------------------------- -OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff. +OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. -Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide necessary translations for these abstracts. +Next we need to _internationalize_ our application by abstracting every locale-specific element. Finally, we need to _localize_ it by providing necessary translations for these abstracts. -You most probably have something like this in one of your applications: +Given the following example: ```ruby # config/routes.rb @@ -342,9 +352,9 @@ end ![rails i18n demo untranslated](images/i18n/demo_untranslated.png) -### Adding Translations +### Abstracting Localized Code -Obviously there are **two strings that are localized to English**. In order to internationalize this code, **replace these strings** with calls to Rails' `#t` helper with a key that makes sense for the translation: +There are two strings in our code that are in English and that users will be rendered in our response ("Hello Flash" and "Hello World"). In order to internationalize this code, these strings need to be replaced by calls to Rails' `#t` helper with an appropriate key for each string: ```ruby # app/controllers/home_controller.rb @@ -361,13 +371,15 @@ end

<%= flash[:notice] %>

``` -When you now render this view, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing. +Now, when this view is rendered, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing. ![rails i18n demo translation missing](images/i18n/demo_translation_missing.png) NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do not need to spell out `I18n.t` all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a ``. -So let's add the missing translations into the dictionary files (i.e. do the "localization" part): +### Providing Translations for Internationalized Strings + +Add the missing translations into the translation dictionary files: ```yaml # config/locales/en.yml @@ -381,11 +393,11 @@ pirate: hello_flash: Ahoy Flash ``` -There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows: +Because the `default_locale` hasn't changed, translations use the `:en` locale and the response renders the english strings: ![rails i18n demo translated to English](images/i18n/demo_translated_en.png) -And when you change the URL to pass the pirate locale (`http://localhost:3000?locale=pirate`), you'll get: +If the locale is set via the URL to the pirate locale (`http://localhost:3000?locale=pirate`), the response renders the pirate strings: ![rails i18n demo translated to pirate](images/i18n/demo_translated_pirate.png) @@ -393,21 +405,64 @@ NOTE: You need to restart the server when you add new locale files. You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.) -### Passing variables to translations +### Passing Variables to Translations + +One key consideration for successfully internationalizing an application is to +avoid making incorrect assumptions about grammar rules when abstracting localized +code. Grammar rules that seem fundamental in one locale may not hold true in +another one. -You can use variables in the translation messages and pass their values from the view. +Improper abstraction is shown in the following example, where assumptions are +made about the ordering of the different parts of the translation. Note that Rails +provides a `number_to_currency` helper to handle the following case. ```erb -# app/views/home/index.html.erb -<%=t 'greet_username', user: "Bill", message: "Goodbye" %> +# app/views/products/show.html.erb +<%= "#{t('currency')}#{@product.price}" %> +``` + +```yaml +# config/locales/en.yml +en: + currency: "$" + +# config/locales/es.yml +es: + currency: "€" +``` + +If the product's price is 10 then the proper translation for Spanish is "10 €" +instead of "€10" but the abstraction cannot give it. + +To create proper abstraction, the I18n gem ships with a feature called variable +interpolation that allows you to use variables in translation definitions and +pass the values for these variables to the translation method. + +Proper abstraction is shown in the following example: + +```erb +# app/views/products/show.html.erb +<%= t('product_price', price: @product.price) %> ``` ```yaml # config/locales/en.yml en: - greet_username: "%{message}, %{user}!" + product_price: "$%{price}" + +# config/locales/es.yml +es: + product_price: "%{price} €" ``` +All grammatical and punctuation decisions are made in the definition itself, so +the abstraction can give a proper translation. + +NOTE: The `default` and `scope` keywords are reserved and can't be used as +variable names. If used, an `I18n::ReservedInterpolationKey` exception is raised. +If a translation expects an interpolation variable, but this has not been passed +to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised. + ### Adding Date/Time Formats OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used. @@ -415,7 +470,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali ```erb # app/views/home/index.html.erb

<%=t :hello_world %>

-

<%= flash[:notice] %>

<%= flash[:notice] %>

<%= l Time.now, format: :short %>

``` @@ -447,7 +502,10 @@ You can make use of this feature, e.g. when working with a large amount of stati ### Organization of Locale Files -When you are using the default SimpleStore shipped with the i18n library, dictionaries are stored in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you. +When you are using the default SimpleStore shipped with the i18n library, +dictionaries are stored in plain-text files on the disk. Putting translations +for all parts of your application in one file per locale could be hard to +manage. You can store these files in a hierarchy which makes sense to you. For example, your `config/locales` directory could look like this: @@ -487,7 +545,9 @@ NOTE: The default locale loading mechanism in Rails does not load locale files i Overview of the I18n API Features --------------------------------- -You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. +You should have a good understanding of using the i18n library now and know how +to internationalize a basic Rails application. In the following chapters, we'll +cover its features in more depth. These chapters will show examples using both the `I18n.translate` method as well as the [`translate` view helper method](http://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate) (noting the additional feature provide by the view helper method). @@ -528,7 +588,7 @@ Thus the following calls are equivalent: ```ruby I18n.t 'activerecord.errors.messages.record_invalid' -I18n.t 'errors.messages.record_invalid', scope: :active_record +I18n.t 'errors.messages.record_invalid', scope: :activerecord I18n.t :record_invalid, scope: 'activerecord.errors.messages' I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] ``` @@ -586,20 +646,26 @@ you can look up the `books.index.title` value **inside** `app/views/books/index. NOTE: Automatic translation scoping by partial is only available from the `translate` view helper method. -### Interpolation +"Lazy" lookup can also be used in controllers: -In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature. +```yaml +en: + books: + create: + success: Book created! +``` -All options besides `:default` and `:scope` that are passed to `#translate` will be interpolated to the translation: +This is useful for setting flash messages for instance: ```ruby -I18n.backend.store_translations :en, thanks: 'Thanks %{name}!' -I18n.translate :thanks, name: 'Jeremy' -# => 'Thanks Jeremy!' +class BooksController < ApplicationController + def create + # ... + redirect_to books_url, notice: t('.success') + end +end ``` -If a translation uses `:default` or `:scope` as an interpolation variable, an `I18n::ReservedInterpolationKey` exception is raised. If a translation expects an interpolation variable, but this has not been passed to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised. - ### Pluralization In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature. @@ -685,7 +751,7 @@ you can safely pass the username as set by the user: ```erb <%# This is safe, it is going to be escaped if needed. %> -<%= t('welcome_html', username: @current_user.username %> +<%= t('welcome_html', username: @current_user.username) %> ``` Safe strings on the other hand are interpolated verbatim. @@ -739,6 +805,8 @@ en: Then `User.human_attribute_name("gender.female")` will return "Female". +NOTE: If you are using a class which includes `ActiveModel` and does not inherit from `ActiveRecord::Base`, replace `activerecord` with `activemodel` in the above key paths. + #### Error Message Scopes Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account. @@ -748,7 +816,7 @@ This gives you quite powerful means to flexibly adjust your messages to your app Consider a User model with a validation for the name attribute like this: ```ruby -class User < ActiveRecord::Base +class User < ApplicationRecord validates :name, presence: true end ``` @@ -807,7 +875,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | validation | with option | message | interpolation | | ------------ | ------------------------- | ------------------------- | ------------- | -| confirmation | - | :confirmation | - | +| confirmation | - | :confirmation | attribute | | acceptance | - | :accepted | - | | presence | - | :blank | - | | absence | - | :present | - | @@ -827,6 +895,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | numericality | :equal_to | :equal_to | count | | numericality | :less_than | :less_than | count | | numericality | :less_than_or_equal_to | :less_than_or_equal_to | count | +| numericality | :other_than | :other_than | count | | numericality | :only_integer | :not_an_integer | - | | numericality | :odd | :odd | - | | numericality | :even | :even | - | @@ -1007,7 +1076,7 @@ In other contexts you might want to change this behavior, though. E.g. the defau module I18n class JustRaiseExceptionHandler < ExceptionHandler def call(exception, locale, key, options) - if exception.is_a?(MissingTranslation) + if exception.is_a?(MissingTranslationData) raise exception.to_exception else super @@ -1024,7 +1093,7 @@ This would re-raise only the `MissingTranslationData` exception, passing all oth However, if you are using `I18n::Backend::Pluralization` this handler will also raise `I18n::MissingTranslationData: translation missing: en.i18n.plural.rule` exception that should normally be ignored to fall back to the default pluralization rule for English locale. To avoid this you may use additional check for translation key: ```ruby -if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule' +if exception.is_a?(MissingTranslationData) && key.to_s != 'i18n.plural.rule' raise exception.to_exception else super @@ -1044,7 +1113,7 @@ Conclusion At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. -If you find anything missing or wrong in this guide, please file a ticket on our [issue tracker](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview). If you want to discuss certain portions or have questions, please sign up to our [mailing list](http://groups.google.com/group/rails-i18n). +If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](http://groups.google.com/group/rails-i18n). Contributing to Rails I18n @@ -1052,7 +1121,7 @@ Contributing to Rails I18n I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in gems and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. -Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) +Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n)!) If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://github.com/guides/pull-requests). @@ -1061,20 +1130,15 @@ Resources --------- * [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. -* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. -* [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. -* [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project. -* [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem. +* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n) - Code repository and issue tracker for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. +* [GitHub: i18n](https://github.com/svenfuchs/i18n) - Code repository and issue tracker for the i18n gem. Authors ------- -* [Sven Fuchs](http://www.workingwithrails.com/person/9963-sven-fuchs) (initial author) -* [Karel Minařík](http://www.workingwithrails.com/person/7476-karel-mina-k) - -If you found this guide useful, please consider recommending its authors on [workingwithrails](http://www.workingwithrails.com). - +* [Sven Fuchs](http://svenfuchs.com) (initial author) +* [Karel Minařík](http://www.karmi.cz) Footnotes --------- diff --git a/source/initialization.md b/source/initialization.md index 53bf303..224393d 100644 --- a/source/initialization.md +++ b/source/initialization.md @@ -1,8 +1,10 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Rails Initialization Process ================================ -This guide explains the internals of the initialization process in Rails -as of Rails 4. It is an extremely in-depth guide and recommended for advanced Rails developers. +This guide explains the internals of the initialization process in Rails. +It is an extremely in-depth guide and recommended for advanced Rails developers. After reading this guide, you will know: @@ -14,7 +16,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- This guide goes through every method call that is -required to boot up the Ruby on Rails stack for a default Rails 4 +required to boot up the Ruby on Rails stack for a default Rails application, explaining each part in detail along the way. For this guide, we will be focusing on what happens when you execute `rails server` to boot your app. @@ -32,7 +34,7 @@ Launch! Let's start to boot and initialize the app. A Rails application is usually started by running `rails console` or `rails server`. -### `railties/bin/rails` +### `railties/exe/rails` The `rails` in the command `rails server` is a ruby executable in your load path. This executable contains the following lines: @@ -43,7 +45,7 @@ load Gem.bin_path('railties', 'rails', version) ``` If you try out this command in a Rails console, you would see that this loads -`railties/bin/rails`. A part of the file `railties/bin/rails.rb` has the +`railties/exe/rails`. A part of the file `railties/exe/rails.rb` has the following code: ```ruby @@ -51,11 +53,11 @@ require "rails/cli" ``` The file `railties/lib/rails/cli` in turn calls -`Rails::AppRailsLoader.exec_app_rails`. +`Rails::AppLoader.exec_app`. -### `railties/lib/rails/app_rails_loader.rb` +### `railties/lib/rails/app_loader.rb` -The primary goal of the function `exec_app_rails` is to execute your app's +The primary goal of the function `exec_app` is to execute your app's `bin/rails`. If the current directory does not have a `bin/rails`, it will navigate upwards until it finds a `bin/rails` executable. Thus one can invoke a `rails` command from anywhere inside a rails application. @@ -84,10 +86,9 @@ The `APP_PATH` constant will be used later in `rails/commands`. The `config/boot `config/boot.rb` contains: ```ruby -# Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' # Set up gems listed in the Gemfile. ``` In a standard Rails application, there's a `Gemfile` which declares all @@ -104,6 +105,7 @@ A standard Rails application depends on several gems, specifically: * activemodel * activerecord * activesupport +* activejob * arel * builder * bundler @@ -137,7 +139,8 @@ aliases = { "c" => "console", "s" => "server", "db" => "dbconsole", - "r" => "runner" + "r" => "runner", + "t" => "test" } command = ARGV.shift @@ -154,21 +157,22 @@ snippet. If we had used `s` rather than `server`, Rails would have used the `aliases` defined here to find the matching command. -### `rails/commands/command_tasks.rb` +### `rails/commands/commands_tasks.rb` -When one types an incorrect rails command, the `run_command` is responsible for -throwing an error message. If the command is valid, a method of the same name -is called. +When one types a valid Rails command, `run_command!` a method of the same name +is called. If Rails doesn't recognize the command, it tries to run a Rake task +of the same name. ```ruby -COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) +COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole application runner new version help) def run_command!(command) command = parse_command(command) + if COMMAND_WHITELIST.include?(command) send(command) else - write_error_message(command) + run_rake_task(command) end end ``` @@ -352,12 +356,10 @@ private def print_boot_information ... puts "=> Run `rails server -h` for more startup options" - ... - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] end def create_tmp_directories - %w(cache pids sessions sockets).each do |dir_to_make| + %w(cache pids sockets).each do |dir_to_make| FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) end end @@ -373,13 +375,12 @@ private end ``` -This is where the first output of the Rails initialization happens. This -method creates a trap for `INT` signals, so if you `CTRL-C` the server, -it will exit the process. As we can see from the code here, it will -create the `tmp/cache`, `tmp/pids`, `tmp/sessions` and `tmp/sockets` -directories. It then calls `wrapped_app` which is responsible for -creating the Rack app, before creating and assigning an -instance of `ActiveSupport::Logger`. +This is where the first output of the Rails initialization happens. This method +creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the +process. As we can see from the code here, it will create the `tmp/cache`, +`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is +responsible for creating the Rack app, before creating and assigning an instance +of `ActiveSupport::Logger`. The `super` method will call `Rack::Server.start` which begins its definition like this: @@ -527,15 +528,17 @@ This file is responsible for requiring all the individual frameworks of Rails: require "rails" %w( - active_record - action_controller - action_view - action_mailer - rails/test_unit - sprockets -).each do |framework| + active_record/railtie + action_controller/railtie + action_view/railtie + action_mailer/railtie + active_job/railtie + action_cable/engine + rails/test_unit/railtie + sprockets/railtie +).each do |railtie| begin - require "#{framework}/railtie" + require "#{railtie}" rescue LoadError end end @@ -554,9 +557,8 @@ I18n and Rails configuration are all being defined here. The rest of `config/application.rb` defines the configuration for the `Rails::Application` which will be used once the application is fully initialized. When `config/application.rb` has finished loading Rails and defined -the application namespace, we go back to `config/environment.rb`, -where the application is initialized. For example, if the application was called -`Blog`, here we would find `Rails.application.initialize!`, which is +the application namespace, we go back to `config/environment.rb`. Here, the +application is initialized with `Rails.application.initialize!`, which is defined in `rails/application.rb`. ### `railties/lib/rails/application.rb` diff --git a/source/kindle/layout.html.erb b/source/kindle/layout.html.erb index f0a2862..fd87467 100644 --- a/source/kindle/layout.html.erb +++ b/source/kindle/layout.html.erb @@ -14,12 +14,12 @@ <% if content_for? :header_section %> <%= yield :header_section %> -
+
<% end %> <% if content_for? :index_section %> <%= yield :index_section %> -
+
<% end %> <%= yield.html_safe %> diff --git a/source/kindle/toc.ncx.erb b/source/kindle/toc.ncx.erb index 2c6d8e3..5094fea 100644 --- a/source/kindle/toc.ncx.erb +++ b/source/kindle/toc.ncx.erb @@ -32,12 +32,12 @@ Credits - + Copyright & License - - + + <% play_order = 4 %> @@ -47,7 +47,7 @@ <%= section['name'] %> - + <% section['documents'].each_with_index do |document, document_no| %> diff --git a/source/kindle/welcome.html.erb b/source/kindle/welcome.html.erb index 610a715..ef3397f 100644 --- a/source/kindle/welcome.html.erb +++ b/source/kindle/welcome.html.erb @@ -2,4 +2,6 @@

Kindle Edition

-The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome. Please see the "Feedback" section at the end of each guide for instructions. +
+ The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome. Please see the "Feedback" section at the end of each guide for instructions. +
diff --git a/source/layout.html.erb b/source/layout.html.erb index 1005057..3a3033e 100644 --- a/source/layout.html.erb +++ b/source/layout.html.erb @@ -29,14 +29,11 @@ More Ruby on Rails
@@ -130,13 +127,11 @@ - - - - - + diff --git a/source/layouts_and_rendering.md b/source/layouts_and_rendering.md index 28fa61a..2722789 100644 --- a/source/layouts_and_rendering.md +++ b/source/layouts_and_rendering.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Layouts and Rendering in Rails ============================== @@ -101,34 +103,6 @@ In most cases, the `ActionController::Base#render` method does the heavy lifting TIP: If you want to see the exact results of a call to `render` without needing to inspect it in a browser, you can call `render_to_string`. This method takes exactly the same options as `render`, but it returns a string instead of sending a response back to the browser. -#### Rendering Nothing - -Perhaps the simplest thing you can do with `render` is to render nothing at all: - -```ruby -render nothing: true -``` - -If you look at the response for this using cURL, you will see the following: - -```bash -$ curl -i 127.0.0.1:3000/books -HTTP/1.1 200 OK -Connection: close -Date: Sun, 24 Jan 2010 09:25:18 GMT -Transfer-Encoding: chunked -Content-Type: */*; charset=utf-8 -X-Runtime: 0.014297 -Set-Cookie: _blog_session=...snip...; path=/; HttpOnly -Cache-Control: no-cache - -$ -``` - -We see there is an empty response (no data after the `Cache-Control` line), but the request was successful because Rails has set the response to 200 OK. You can set the `:status` option on render to change this response. Rendering nothing can be useful for Ajax requests where all you want to send back to the browser is an acknowledgment that the request was completed. - -TIP: You should probably be using the `head` method, discussed later in this guide, instead of `render :nothing`. This provides additional flexibility and makes it explicit that you're only generating HTTP headers. - #### Rendering an Action's View If you want to render the view that corresponds to a different template within the same controller, you can use `render` with the name of the view: @@ -175,23 +149,22 @@ render template: "products/show" #### Rendering an Arbitrary File -The `render` method can also use a view that's entirely outside of your application (perhaps you're sharing views between two Rails applications): - -```ruby -render "/u/apps/warehouse_app/current/app/views/products/show" -``` - -Rails determines that this is a file render because of the leading slash character. To be explicit, you can use the `:file` option (which was required on Rails 2.2 and earlier): +The `render` method can also use a view that's entirely outside of your application: ```ruby render file: "/u/apps/warehouse_app/current/app/views/products/show" ``` -The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. +The `:file` option takes an absolute file-system path. Of course, you need to have rights +to the view that you're using to render the content. + +NOTE: Using the `:file` option in combination with users input can lead to security problems +since an attacker could use this action to access security sensitive files in your file system. NOTE: By default, the file is rendered using the current layout. -TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames. +TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to +render a file, because Windows filenames do not have the same format as Unix filenames. #### Wrapping it up @@ -253,7 +226,7 @@ extension for the layout file. #### Rendering HTML -You can send a HTML string back to the browser by using the `:html` option to +You can send an HTML string back to the browser by using the `:html` option to `render`: ```ruby @@ -264,7 +237,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: This option will escape HTML entities if the string is not HTML safe. +NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method. #### Rendering JSON @@ -306,7 +279,7 @@ render body: "raw" ``` TIP: This option should be used only if you don't care about the content type of -the response. Using `:plain` or `:html` might be more appropriate in most of the +the response. Using `:plain` or `:html` might be more appropriate most of the time. NOTE: Unless overridden, your response returned from this render option will be @@ -314,12 +287,13 @@ NOTE: Unless overridden, your response returned from this render option will be #### Options for `render` -Calls to the `render` method generally accept four options: +Calls to the `render` method generally accept five options: * `:content_type` * `:layout` * `:location` * `:status` +* `:formats` ##### The `:content_type` Option @@ -385,7 +359,6 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 303 | :see_other | | | 304 | :not_modified | | | 305 | :use_proxy | -| | 306 | :reserved | | | 307 | :temporary_redirect | | | 308 | :permanent_redirect | | **Client Error** | 400 | :bad_request | @@ -401,10 +374,10 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 410 | :gone | | | 411 | :length_required | | | 412 | :precondition_failed | -| | 413 | :request_entity_too_large | -| | 414 | :request_uri_too_long | +| | 413 | :payload_too_large | +| | 414 | :uri_too_long | | | 415 | :unsupported_media_type | -| | 416 | :requested_range_not_satisfiable | +| | 416 | :range_not_satisfiable | | | 417 | :expectation_failed | | | 422 | :unprocessable_entity | | | 423 | :locked | @@ -425,6 +398,19 @@ Rails understands both numeric status codes and the corresponding symbols shown | | 510 | :not_extended | | | 511 | :network_authentication_required | +NOTE: If you try to render content along with a non-content status code +(100-199, 204, 205 or 304), it will be dropped from the response. + +##### The `:formats` Option + +Rails uses the format specified in the request (or `:html` by default). You can +change this passing the `:formats` option with a symbol or an array: + +```ruby +render formats: :xml +render formats: [:json, :xml] +``` + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. @@ -548,6 +534,42 @@ In this application: * `OldArticlesController#show` will use no layout at all * `OldArticlesController#index` will use the `old` layout +##### Template Inheritance + +Similar to the Layout Inheritance logic, if a template or partial is not found in the conventional path, the controller will look for a template or partial to render in its inheritance chain. For example: + +```ruby +# in app/controllers/application_controller +class ApplicationController < ActionController::Base +end + +# in app/controllers/admin_controller +class AdminController < ApplicationController +end + +# in app/controllers/admin/products_controller +class Admin::ProductsController < AdminController + def index + end +end +``` + +The lookup order for an `admin/products#index` action will be: + +* `app/views/admin/products/` +* `app/views/admin/` +* `app/views/application/` + +This makes `app/views/application/` a great place for your shared partials, which can then be rendered in your ERB as such: + +```erb +<%# app/views/admin/products/index.html.erb %> +<%= render @products || "empty_list" %> + +<%# app/views/application/_empty_list.html.erb %> +There are no items in this list yet. +``` + #### Avoiding Double Render Errors Sooner or later, most Rails developers will see the error message "Can only render or redirect once per action". While this is annoying, it's relatively easy to fix. Usually it happens because of a fundamental misunderstanding of the way that `render` works. @@ -599,10 +621,13 @@ Another way to handle returning responses to an HTTP request is with `redirect_t redirect_to photos_url ``` -You can use `redirect_to` with any arguments that you could use with `link_to` or `url_for`. There's also a special redirect that sends the user back to the page they just came from: +You can use `redirect_back` to return the user to the page they just came from. +This location is pulled from the `HTTP_REFERER` header which is not guaranteed +to be set by the browser, so you must provide the `fallback_location` +to use in this case. ```ruby -redirect_to :back +redirect_back(fallback_location: root_path) ``` #### Getting a Different Redirect Status Code @@ -674,7 +699,7 @@ This would detect that there are no books with the specified ID, populate the `@ ### Using `head` To Build Header-Only Responses -The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: +The `head` method can be used to send responses with only headers to the browser. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing an HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: ```ruby head :bad_request @@ -758,7 +783,7 @@ The `javascript_include_tag` helper returns an HTML `script` tag for each source If you are using Rails with the [Asset Pipeline](asset_pipeline.html) enabled, this helper will generate a link to `/assets/javascripts/` rather than `public/javascripts` which was used in earlier versions of Rails. This link is then served by the asset pipeline. -A JavaScript file within a Rails application or Rails engine goes in one of three locations: `app/assets`, `lib/assets` or `vendor/assets`. These locations are explained in detail in the [Asset Organization section in the Asset Pipeline Guide](asset_pipeline.html#asset-organization) +A JavaScript file within a Rails application or Rails engine goes in one of three locations: `app/assets`, `lib/assets` or `vendor/assets`. These locations are explained in detail in the [Asset Organization section in the Asset Pipeline Guide](asset_pipeline.html#asset-organization). You can specify a full path relative to the document root, or a URL, if you prefer. For example, to link to a JavaScript file that is inside a directory called `javascripts` inside of one of `app/assets`, `lib/assets` or `vendor/assets`, you would do this: @@ -1023,7 +1048,48 @@ One way to use partials is to treat them as the equivalent of subroutines: as a <%= render "shared/footer" %> ``` -Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. +Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain +content that is shared by many pages in your application. You don't need to see +the details of these sections when you're concentrating on a particular page. + +As seen in the previous sections of this guide, `yield` is a very powerful tool +for cleaning up your layouts. Keep in mind that it's pure Ruby, so you can use +it almost everywhere. For example, we can use it to DRY up form layout +definitions for several similar resources: + +* `users/index.html.erb` + + ```html+erb + <%= render "shared/search_filters", search: @q do |f| %> +

+ Name contains: <%= f.text_field :name_contains %> +

+ <% end %> + ``` + +* `roles/index.html.erb` + + ```html+erb + <%= render "shared/search_filters", search: @q do |f| %> +

+ Title contains: <%= f.text_field :title_contains %> +

+ <% end %> + ``` + +* `shared/_search_filters.html.erb` + + ```html+erb + <%= form_for(@q) do |f| %> +

Search form:

+
+ <%= yield f %> +
+

+ <%= f.submit "Search" %> +

+ <% end %> + ``` TIP: For content that is shared among all pages in your application, you can use partials directly from layouts. @@ -1073,6 +1139,34 @@ You can also pass local variables into partials, making them even more powerful Although the same partial will be rendered into both views, Action View's submit helper will return "Create Zone" for the new action and "Update Zone" for the edit action. +To pass a local variable to a partial in only specific cases use the `local_assigns`. + +* `index.html.erb` + + ```erb + <%= render user.articles %> + ``` + +* `show.html.erb` + + ```erb + <%= render article, full: true %> + ``` + +* `_articles.html.erb` + + ```erb +

<%= article.title %>

+ + <% if local_assigns[:full] %> + <%= simple_format article.body %> + <% else %> + <%= truncate article.body %> + <% end %> + ``` + +This way it is possible to use the partial without the need to declare all local variables. + Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the `:object` option: ```erb diff --git a/source/maintenance_policy.md b/source/maintenance_policy.md index 050a64d..7ced3ea 100644 --- a/source/maintenance_policy.md +++ b/source/maintenance_policy.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Maintenance Policy for Ruby on Rails ==================================== @@ -42,7 +44,7 @@ from. In special situations, where someone from the Core Team agrees to support more series, they are included in the list of supported series. -**Currently included series:** `4.2.Z`, `4.1.Z` (Supported by Rafael França). +**Currently included series:** `5.0.Z`, `4.2.Z`. Security Issues --------------- @@ -57,7 +59,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that security releases are easy to upgrade to if you're running the latest version of Rails. -**Currently included series:** `4.2.Z`, `4.1.Z`. +**Currently included series:** `5.0.Z`, `4.2.Z`. Severe Security Issues ---------------------- @@ -66,7 +68,7 @@ For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. -**Currently included series:** `4.2.Z`, `4.1.Z`, `3.2.Z`. +**Currently included series:** `5.0.Z`, `4.2.Z`. Unsupported Release Series -------------------------- diff --git a/source/nested_model_forms.md b/source/nested_model_forms.md index f0ee34c..71efa4b 100644 --- a/source/nested_model_forms.md +++ b/source/nested_model_forms.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Nested Model Forms ======================== @@ -30,7 +32,7 @@ For an ActiveRecord::Base model and association this writer method is commonly d #### has_one ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord has_one :address accepts_nested_attributes_for :address end @@ -39,7 +41,7 @@ end #### belongs_to ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord belongs_to :firm accepts_nested_attributes_for :firm end @@ -48,7 +50,7 @@ end #### has_many / has_and_belongs_to_many ```ruby -class Person < ActiveRecord::Base +class Person < ApplicationRecord has_many :projects accepts_nested_attributes_for :projects end @@ -104,7 +106,7 @@ Consider the following typical RESTful controller which will prepare a new Perso class PeopleController < ApplicationController def new @person = Person.new - @person.built_address + @person.build_address 2.times { @person.projects.build } end diff --git a/source/plugins.md b/source/plugins.md index 7b7eb80..8f055f8 100644 --- a/source/plugins.md +++ b/source/plugins.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + The Basics of Creating Rails Plugins ==================================== @@ -15,7 +17,7 @@ After reading this guide, you will know: This guide describes how to build a test-driven plugin that will: * Extend core Ruby classes like Hash and String. -* Add methods to `ActiveRecord::Base` in the tradition of the `acts_as` plugins. +* Add methods to `ApplicationRecord` in the tradition of the `acts_as` plugins. * Give you information about where to put generators in your plugin. For the purpose of this guide pretend for a moment that you are an avid bird watcher. @@ -35,7 +37,7 @@ different rails applications using RubyGems and Bundler if desired. Rails ships with a `rails plugin new` command which creates a skeleton for developing any kind of Rails extension with the ability -to run integration tests using a dummy Rails application. Create your +to run integration tests using a dummy Rails application. Create your plugin with the command: ```bash @@ -52,7 +54,7 @@ Testing Your Newly Generated Plugin ----------------------------------- You can navigate to the directory that contains the plugin, run the `bundle install` command - and run the one generated test using the `rake` command. + and run the one generated test using the `bin/test` command. You should see: @@ -81,13 +83,23 @@ class CoreExtTest < ActiveSupport::TestCase end ``` -Run `rake` to run the test. This test should fail because we haven't implemented the `to_squawk` method: +Run `bin/test` to run the test. This test should fail because we haven't implemented the `to_squawk` method: ```bash - 1) Error: - CoreExtTest#test_to_squawk_prepends_the_word_squawk: - NoMethodError: undefined method `to_squawk' for "Hello World":String - /path/to/yaffle/test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' +E + +Error: +CoreExtTest#test_to_squawk_prepends_the_word_squawk: +NoMethodError: undefined method `to_squawk' for "Hello World":String + + +bin/test /path/to/yaffle/test/core_ext_test.rb:4 + +. + +Finished in 0.003358s, 595.6483 runs/s, 297.8242 assertions/s. + +2 runs, 1 assertions, 0 failures, 1 errors, 0 skips ``` Great - now you are ready to start development. @@ -115,7 +127,7 @@ String.class_eval do end ``` -To test that your method does what it says it does, run the unit tests with `rake` from your plugin directory. +To test that your method does what it says it does, run the unit tests with `bin/test` from your plugin directory. ```bash 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips @@ -180,7 +192,6 @@ To start out, write a failing test that shows the behavior you'd like: require 'test_helper' class ActsAsYaffleTest < ActiveSupport::TestCase - def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field end @@ -188,24 +199,37 @@ class ActsAsYaffleTest < ActiveSupport::TestCase def test_a_wickwalls_yaffle_text_field_should_be_last_tweet assert_equal "last_tweet", Wickwall.yaffle_text_field end - end ``` -When you run `rake`, you should see the following: +When you run `bin/test`, you should see the following: ``` - 1) Error: - ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: - NameError: uninitialized constant ActsAsYaffleTest::Hickwall - /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' +# Running: + +..E - 2) Error: - ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: - NameError: uninitialized constant ActsAsYaffleTest::Wickwall - /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' +Error: +ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: +NameError: uninitialized constant ActsAsYaffleTest::Wickwall - 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips + +bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8 + +E + +Error: +ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: +NameError: uninitialized constant ActsAsYaffleTest::Hickwall + + +bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4 + + + +Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s. + +4 runs, 2 assertions, 0 failures, 2 errors, 0 skips ``` This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test. @@ -223,7 +247,7 @@ and migrating the database. First, run: ```bash $ cd test/dummy -$ bin/rake db:migrate +$ bin/rails db:migrate ``` While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act @@ -232,22 +256,22 @@ like yaffles. ```ruby # test/dummy/app/models/hickwall.rb -class Hickwall < ActiveRecord::Base +class Hickwall < ApplicationRecord acts_as_yaffle end # test/dummy/app/models/wickwall.rb -class Wickwall < ActiveRecord::Base +class Wickwall < ApplicationRecord acts_as_yaffle yaffle_text_field: :last_tweet end - ``` We will also add code to define the `acts_as_yaffle` method. ```ruby # yaffle/lib/yaffle/acts_as_yaffle.rb + module Yaffle module ActsAsYaffle extend ActiveSupport::Concern @@ -263,26 +287,43 @@ module Yaffle end end -ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle +# test/dummy/app/models/application_record.rb + +class ApplicationRecord < ActiveRecord::Base + include Yaffle::ActsAsYaffle + + self.abstract_class = true +end ``` -You can then return to the root directory (`cd ../..`) of your plugin and rerun the tests using `rake`. +You can then return to the root directory (`cd ../..`) of your plugin and rerun the tests using `bin/test`. ``` - 1) Error: - ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: - NoMethodError: undefined method `yaffle_text_field' for # - activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing' - /path/to/yaffle/test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' +# Running: + +.E + +Error: +ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: +NoMethodError: undefined method `yaffle_text_field' for # + + +bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4 + +E + +Error: +ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: +NoMethodError: undefined method `yaffle_text_field' for # - 2) Error: - ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: - NoMethodError: undefined method `yaffle_text_field' for # - activerecord (4.1.5) lib/active_record/dynamic_matchers.rb:26:in `method_missing' - /path/to/yaffle/test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' - 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips +bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8 +. + +Finished in 0.008263s, 484.0999 runs/s, 242.0500 assertions/s. + +4 runs, 2 assertions, 0 failures, 2 errors, 0 skips ``` Getting closer... Now we will implement the code of the `acts_as_yaffle` method to make the tests pass. @@ -292,7 +333,7 @@ Getting closer... Now we will implement the code of the `acts_as_yaffle` method module Yaffle module ActsAsYaffle - extend ActiveSupport::Concern + extend ActiveSupport::Concern included do end @@ -306,10 +347,16 @@ module Yaffle end end -ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle +# test/dummy/app/models/application_record.rb + +class ApplicationRecord < ActiveRecord::Base + include Yaffle::ActsAsYaffle + + self.abstract_class = true +end ``` -When you run `rake`, you should see the tests all pass: +When you run `bin/test`, you should see the tests all pass: ```bash 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips @@ -327,7 +374,6 @@ To start out, write a failing test that shows the behavior you'd like: require 'test_helper' class ActsAsYaffleTest < ActiveSupport::TestCase - def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field end @@ -380,10 +426,16 @@ module Yaffle end end -ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle +# test/dummy/app/models/application_record.rb + +class ApplicationRecord < ActiveRecord::Base + include Yaffle::ActsAsYaffle + + self.abstract_class = true +end ``` -Run `rake` one final time and you should see: +Run `bin/test` one final time and you should see: ``` 6 runs, 6 assertions, 0 failures, 0 errors, 0 skips @@ -433,7 +485,7 @@ Once your README is solid, go through and add rdoc comments to all of the method Once your comments are good to go, navigate to your plugin directory and run: ```bash -$ bin/rake rdoc +$ bundle exec rake rdoc ``` ### References @@ -441,4 +493,3 @@ $ bin/rake rdoc * [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md) * [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/) * [Gemspec Reference](http://guides.rubygems.org/specification-reference/) -* [GemPlugins: A Brief Introduction to the Future of Rails Plugins](http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins) diff --git a/source/profiling.md b/source/profiling.md new file mode 100644 index 0000000..ce093f7 --- /dev/null +++ b/source/profiling.md @@ -0,0 +1,16 @@ +*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + +A Guide to Profiling Rails Applications +======================================= + +This guide covers built-in mechanisms in Rails for profiling your application. + +After reading this guide, you will know: + +* Rails profiling terminology. +* How to write benchmark tests for your application. +* Other benchmarking approaches and plugins. + +-------------------------------------------------------------------------------- + + diff --git a/source/rails_application_templates.md b/source/rails_application_templates.md index 6512b14..3b773d8 100644 --- a/source/rails_application_templates.md +++ b/source/rails_application_templates.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Application Templates =========================== @@ -20,11 +22,11 @@ $ rails new blog -m ~/template.rb $ rails new blog -m http://example.com/template.rb ``` -You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL. +You can use the task `app:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL. ```bash -$ bin/rake rails:template LOCATION=~/template.rb -$ bin/rake rails:template LOCATION=http://example.com/template.rb +$ bin/rails app:template LOCATION=~/template.rb +$ bin/rails app:template LOCATION=http://example.com/template.rb ``` Template API @@ -36,7 +38,7 @@ The Rails templates API is easy to understand. Here's an example of a typical Ra # template.rb generate(:scaffold, "person name:string") route "root to: 'people#index'" -rake("db:migrate") +rails_command("db:migrate") after_bundle do git :init @@ -76,7 +78,7 @@ gem_group :development, :test do end ``` -### add_source(source, options = {}) +### add_source(source, options={}, &block) Adds the given source to the generated application's `Gemfile`. @@ -86,6 +88,14 @@ For example, if you need to source a gem from `"http://code.whytheluckystiff.net add_source "http://code.whytheluckystiff.net" ``` +If block is given, gem entries in block are wrapped into the source group. + +```ruby +add_source "http://gems.github.com/" do + gem "rspec-rails" +end +``` + ### environment/application(data=nil, options={}, &block) Adds a line inside the `Application` class for `config/application.rb`. @@ -165,18 +175,24 @@ Executes an arbitrary command. Just like the backticks. Let's say you want to re run "rm README.rdoc" ``` -### rake(command, options = {}) +### rails_command(command, options = {}) + +Runs the supplied task in the Rails application. Let's say you want to migrate the database: + +```ruby +rails_command "db:migrate" +``` -Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database: +You can also run tasks with a different Rails environment: ```ruby -rake "db:migrate" +rails_command "db:migrate", env: 'production' ``` -You can also run rake tasks with a different Rails environment: +You can also run tasks as a super-user: ```ruby -rake "db:migrate", env: 'production' +rails_command "log:clear", sudo: true ``` ### route(routing_code) @@ -213,10 +229,10 @@ CODE ### yes?(question) or no?(question) -These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to freeze rails only if the user wants to: +These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to Freeze Rails only if the user wants to: ```ruby -rake("rails:freeze:gems") if yes?("Freeze rails gems?") +rails_command("rails:freeze:gems") if yes?("Freeze rails gems?") # no?(question) acts just the opposite. ``` diff --git a/source/rails_on_rack.md b/source/rails_on_rack.md index 042ebde..d67702f 100644 --- a/source/rails_on_rack.md +++ b/source/rails_on_rack.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails on Rack ============= @@ -56,24 +58,6 @@ class Server < ::Rack::Server end ``` -Here's how it loads the middlewares: - -```ruby -def middleware - middlewares = [] - middlewares << [Rails::Rack::Debugger] if options[:debugger] - middlewares << [::Rack::ContentLength] - Hash.new(middlewares) -end -``` - -`Rails::Rack::Debugger` is primarily useful only in the development environment. The following table explains the usage of the loaded middlewares: - -| Middleware | Purpose | -| ----------------------- | --------------------------------------------------------------------------------- | -| `Rails::Rack::Debugger` | Starts Debugger | -| `Rack::ContentLength` | Counts the number of bytes in the response and set the HTTP Content-Length header | - ### `rackup` To use `rackup` instead of Rails' `rails server`, you can put the following inside `config.ru` of your Rails application's root directory: @@ -81,9 +65,6 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi ```ruby # Rails.root/config.ru require ::File.expand_path('../config/environment', __FILE__) - -use Rails::Rack::Debugger -use Rack::ContentLength run Rails.application ``` @@ -112,10 +93,10 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, ### Inspecting Middleware Stack -Rails has a handy rake task for inspecting the middleware stack in use: +Rails has a handy task for inspecting the middleware stack in use: ```bash -$ bin/rake middleware +$ bin/rails middleware ``` For a freshly generated Rails application, this might produce something like: @@ -123,7 +104,7 @@ For a freshly generated Rails application, this might produce something like: ```ruby use Rack::Sendfile use ActionDispatch::Static -use Rack::Lock +use ActionDispatch::Executor use # use Rack::Runtime use Rack::MethodOverride @@ -140,7 +121,6 @@ use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash -use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag @@ -191,14 +171,14 @@ Add the following lines to your application configuration: ```ruby # config/application.rb -config.middleware.delete "Rack::Lock" +config.middleware.delete Rack::Runtime ``` -And now if you inspect the middleware stack, you'll find that `Rack::Lock` is +And now if you inspect the middleware stack, you'll find that `Rack::Runtime` is not a part of it. ```bash -$ bin/rake middleware +$ bin/rails middleware (in /Users/lifo/Rails/blog) use ActionDispatch::Static use # @@ -211,16 +191,16 @@ If you want to remove session related middleware, do the following: ```ruby # config/application.rb -config.middleware.delete "ActionDispatch::Cookies" -config.middleware.delete "ActionDispatch::Session::CookieStore" -config.middleware.delete "ActionDispatch::Flash" +config.middleware.delete ActionDispatch::Cookies +config.middleware.delete ActionDispatch::Session::CookieStore +config.middleware.delete ActionDispatch::Flash ``` And to remove browser related middleware, ```ruby # config/application.rb -config.middleware.delete "Rack::MethodOverride" +config.middleware.delete Rack::MethodOverride ``` ### Internal Middleware Stack @@ -233,12 +213,16 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::Static`** -* Used to serve static files. Disabled if `config.serve_static_files` is `false`. +* Used to serve static files from the public directory. Disabled if `config.public_file_server.enabled` is `false`. **`Rack::Lock`** * Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex. +**`ActionDispatch::Executor`** + +* Used for thread safe code reloading during development. + **`ActiveSupport::Cache::Strategy::LocalCache::Middleware`** * Used for memory caching. This cache is not thread safe. @@ -253,7 +237,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`ActionDispatch::RequestId`** -* Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#uuid` method. +* Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#request_id` method. **`Rails::Rack::Logger`** @@ -303,10 +287,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Sets up the flash keys. Only available if `config.action_controller.session_store` is set to a value. -**`ActionDispatch::ParamsParser`** - -* Parses out parameters from the request into `params`. - **`Rack::Head`** * Converts HEAD requests to `GET` requests and serves them as so. @@ -328,8 +308,6 @@ Resources * [Official Rack Website](http://rack.github.io) * [Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html) -* [Ruby on Rack #1 - Hello Rack!](http://m.onkey.org/ruby-on-rack-1-hello-rack) -* [Ruby on Rack #2 - The Builder](http://m.onkey.org/ruby-on-rack-2-the-builder) ### Understanding Middlewares diff --git a/source/routing.md b/source/routing.md index b1a287f..81321c7 100644 --- a/source/routing.md +++ b/source/routing.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Rails Routing from the Outside In ================================= @@ -5,7 +7,7 @@ This guide covers the user-facing features of Rails routing. After reading this guide, you will know: -* How to interpret the code in `routes.rb`. +* How to interpret the code in `config/routes.rb`. * How to construct your own routes, using either the preferred resourceful style or the `match` method. * What parameters to expect an action to receive. * How to automatically create paths and URLs using route helpers. @@ -77,11 +79,13 @@ it asks the router to map it to a controller action. If the first matching route resources :photos ``` -Rails would dispatch that request to the `destroy` method on the `photos` controller with `{ id: '17' }` in `params`. +Rails would dispatch that request to the `destroy` action on the `photos` controller with `{ id: '17' }` in `params`. ### CRUD, Verbs, and Actions -In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as: +In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to +controller actions. By convention, each action also maps to a specific CRUD +operation in a database. A single entry in the routing file, such as: ```ruby resources :photos @@ -138,10 +142,10 @@ Sometimes, you have a resource that clients always look up without referencing a get 'profile', to: 'users#show' ``` -Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action: +Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action but you must also specify the `controller:` to use: ```ruby -get 'profile', to: :show +get 'profile', to: :show, controller: 'users' ``` This resourceful route: @@ -175,6 +179,8 @@ WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) preve ```ruby form_for @geocoder, url: geocoder_path do |f| + +# snippet for brevity ``` ### Controller Namespaces and Routing @@ -227,7 +233,7 @@ or, for a single case: resources :articles, path: '/admin/articles' ``` -In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`: +In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `ArticlesController`: | HTTP Verb | Path | Controller#Action | Named Helper | | --------- | ------------------------ | -------------------- | ---------------------- | @@ -246,11 +252,11 @@ TIP: _If you need to use a different controller namespace inside a `namespace` b It's common to have resources that are logically children of other resources. For example, suppose your application includes these models: ```ruby -class Magazine < ActiveRecord::Base +class Magazine < ApplicationRecord has_many :ads end -class Ad < ActiveRecord::Base +class Ad < ApplicationRecord belongs_to :magazine end ``` @@ -386,7 +392,7 @@ The comments resource here will have the following routes generated for it: ### Routing concerns -Routing Concerns allows you to declare common routes that can be reused inside other resources and routes. To define a concern: +Routing concerns allow you to declare common routes that can be reused inside other resources and routes. To define a concern: ```ruby concern :commentable do @@ -611,6 +617,8 @@ get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' } Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`. +NOTE: You cannot override defaults via query parameters - this is for security reasons. The only defaults that can be overridden are dynamic segments via substitution in the URL path. + ### Naming Routes You can specify a name for any route using the `:as` option: @@ -698,6 +706,8 @@ end NOTE: Request constraints work by calling a method on the [Request object](action_controller_overview.html#the-request-object) with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String. +NOTE: There is an exception for the `format` constraint: while it's a method on the Request object, it's also an implicit optional parameter on every path. Segment constraints take precedence and the `format` constraint is only applied as such when enforced through a hash. For example, `get 'foo', constraints: { format: 'json' }` will match `GET /foo` because the format is optional by default. However, you can [use a lambda](#advanced-constraints) like in `get 'foo', constraints: lambda { |req| req.format == :json }` and the route will only match explicit JSON requests. + ### Advanced Constraints If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do: @@ -789,7 +799,11 @@ get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" } ``` -Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. +Please note that default redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. You can use the `:status` option to change the response status: + +```ruby +get '/stories/:name', to: redirect('/articles/%{name}', status: 302) +``` In all of these cases, if you don't provide the leading host (`http://www.example.com`), Rails will take those details from the current request. @@ -798,13 +812,28 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl Instead of a String like `'articles#index'`, which corresponds to the `index` action in the `ArticlesController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher: ```ruby -match '/application.js', to: Sprockets, via: :all +match '/application.js', to: MyRackApp, via: :all ``` -As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. +As long as `MyRackApp` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application. +If you specify a Rack application as the endpoint for a matcher, remember that +the route will be unchanged in the receiving application. With the following +route your Rack application should expect the route to be '/admin': + +```ruby +match '/admin', to: AdminApp, via: :all +``` + +If you would prefer to have your Rack application receive requests at the root +path instead, use mount: + +```ruby +mount AdminApp, at: '/admin' +``` + ### Using `root` You can specify what Rails should route `'/'` to with the `root` method: @@ -907,7 +936,7 @@ The `:as` option lets you override the normal naming for the named route helpers resources :photos, as: 'images' ``` -will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the :as option to name the helpers. +will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the `:as` option to name the helpers. | HTTP Verb | Path | Controller#Action | Named Helper | | --------- | ---------------- | ----------------- | -------------------- | @@ -1004,7 +1033,7 @@ TIP: If your application has many RESTful routes, using `:only` and `:except` to ### Translated Paths -Using `scope`, we can alter path names generated by resources: +Using `scope`, we can alter path names generated by `resources`: ```ruby scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do @@ -1068,6 +1097,20 @@ edit_videos GET /videos/:identifier/edit(.:format) videos#edit Video.find_by(identifier: params[:identifier]) ``` +You can override `ActiveRecord::Base#to_param` of a related model to construct +a URL: + +```ruby +class Video < ApplicationRecord + def to_param + identifier + end +end + +video = Video.find_by(identifier: "Roman-Holiday") +edit_videos_path(video) # => "/videos/Roman-Holiday" +``` + Inspecting and Testing Routes ----------------------------- @@ -1075,16 +1118,16 @@ Rails offers facilities for inspecting and testing your routes. ### Listing Existing Routes -To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rake routes` command in your terminal to produce the same output. +To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rails routes` command in your terminal to produce the same output. -Both methods will list all of your routes, in the same order that they appear in `routes.rb`. For each route, you'll see: +Both methods will list all of your routes, in the same order that they appear in `config/routes.rb`. For each route, you'll see: * The route name (if any) * The HTTP verb used (if the route doesn't respond to all verbs) * The URL pattern to match * The routing parameters for the route -For example, here's a small section of the `rake routes` output for a RESTful route: +For example, here's a small section of the `rails routes` output for a RESTful route: ``` users GET /users(.:format) users#index @@ -1093,13 +1136,24 @@ For example, here's a small section of the `rake routes` output for a RESTful ro edit_user GET /users/:id/edit(.:format) users#edit ``` -You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable: +You can search through your routes with the grep option: -g. This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path. + +``` +$ bin/rails routes -g new_comment +$ bin/rails routes -g POST +$ bin/rails routes -g admin +``` + +If you only want to see the routes that map to a specific controller, there's the -c option. -```bash -$ CONTROLLER=users bin/rake routes +``` +$ bin/rails routes -c users +$ bin/rails routes -c admin/users +$ bin/rails routes -c Comments +$ bin/rails routes -c Articles::CommentsController ``` -TIP: You'll find that the output from `rake routes` is much more readable if you widen your terminal window until the output lines don't wrap. +TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap. ### Testing Routes diff --git a/source/ruby_on_rails_guides_guidelines.md b/source/ruby_on_rails_guides_guidelines.md index c0438f6..5086635 100644 --- a/source/ruby_on_rails_guides_guidelines.md +++ b/source/ruby_on_rails_guides_guidelines.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails Guides Guidelines =============================== @@ -62,7 +64,9 @@ The guides and the API should be coherent and consistent where appropriate. In p HTML Guides ----------- -Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. +Before generating the guides, make sure that you have the latest version of +Bundler installed on your system. As of this writing, you must install Bundler +1.3.5 or later on your device. To install the latest version of Bundler, run `gem install bundler`. @@ -80,6 +84,8 @@ or bundle exec rake guides:generate:html ``` +Resulting HTML files can be found in the `./output` directory. + To process `my_guide.md` and nothing else use the `ONLY` environment variable: ``` diff --git a/source/security.md b/source/security.md index b3869b1..16c5291 100644 --- a/source/security.md +++ b/source/security.md @@ -1,3 +1,5 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** + Ruby on Rails Security Guide ============================ @@ -21,7 +23,7 @@ Web application frameworks are made to help developers build web applications. S In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications). -The Gartner Group however estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person. +The Gartner Group, however, estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person. The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. @@ -60,7 +62,7 @@ Many web applications have an authentication system: a user provides a user name Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: -* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: +* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN, it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: ```ruby config.force_ssl = true @@ -91,9 +93,16 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves * Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_. -* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie. +* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie. + +However, since Rails 4, the default store is EncryptedCookieStore. With +EncryptedCookieStore the session is encrypted before being stored in a cookie. +This prevents the user from accessing and tampering the content of the cookie. +Thus the session becomes a more secure place to store data. The encryption is +done using a server-side secret key `secrets.secret_key_base` stored in +`config/secrets.yml`. -That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_. +That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_. `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.: @@ -151,7 +160,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec reset_session ``` -If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. +If you use the popular [Devise](https://rubygems.org/gems/devise) gem for user management, it will automatically expire sessions on sign in and sign out for you. If you roll your own, remember to expire the session after your sign in action (when the session is created). This will remove values from the session, therefore _you will have to transfer them to the new session_. Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. @@ -162,7 +171,7 @@ NOTE: _Sessions that never expire extend the time-frame for attacks such as cros One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _expire sessions in a database table_. Call `Session.sweep("20 minutes")` to expire sessions that were used longer than 20 minutes ago. ```ruby -class Session < ActiveRecord::Base +class Session < ApplicationRecord def self.sweep(time = 1.hour) if time.is_a?(String) time = time.split.inject { |count, unit| count.to_i.send(unit) } @@ -187,13 +196,12 @@ This attack method works by including malicious code or a link in a page that ac ![](images/csrf.png) -In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is, that it will also send the cookie, if the request comes from a site of a different domain. Let's start with an example: +In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example: -* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file. -* `` -* Bob's session at www.webapp.com is still alive, because he didn't log out a few minutes ago. -* By viewing the post, the browser finds an image tag. It tries to load the suspected image from www.webapp.com. As explained before, it will also send along the cookie with the valid session id. -* The web application at www.webapp.com verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image. +* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file: `` +* Bob's session at `www.webapp.com` is still alive, because he didn't log out a few minutes ago. +* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session id. +* The web application at `www.webapp.com` verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image. * Bob doesn't notice the attack - but a few days later he finds out that project number one is gone. It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email. @@ -216,9 +224,9 @@ The HTTP protocol basically provides two main types of requests - GET and POST ( * The interaction _changes the state_ of the resource in a way that the user would perceive (e.g., a subscription to a service), or * The user is _held accountable for the results_ of the interaction. -If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier. +If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however, do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier. -_POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request. +_POST requests can be sent automatically, too_. In this example, the link www.harmless.com is shown as the destination in the browser's status bar. But it has actually dynamically created a new form that sends a POST request. ```html ``` -There are many other possibilities, like using a ` - - -``` - -#### `register_stylesheet_expansion` - -这个方法注册一到多个样式表文件,把 Symbol 传给 `stylesheet_link_tag` 方法时,会引入相应的文件。这个方法经常用在插件的初始化代码中,注册保存在 `vendor/assets/stylesheets` 文件夹中的样式表文件。 - -```ruby -ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion monkey: ["head", "body", "tail"] - -stylesheet_link_tag :monkey # => - - - -``` - -#### `auto_discovery_link_tag` - -返回一个 `link` 标签,浏览器和 Feed 阅读器用来自动检测 RSS 或 Atom Feed。 - -```ruby -auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # => - -``` - -#### `image_path` - -生成 `app/assets/images` 文件夹中所存图片的地址。得到的地址是从根目录到图片的完整路径。用于 `image_tag` 方法,获取图片的路径。 - -```ruby -image_path("edit.png") # => /assets/edit.png -``` - -如果 `config.assets.digest` 选项为 `true`,图片文件名后会加上指纹码。 - -```ruby -image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png -``` - -#### `image_url` - -生成 `app/assets/images` 文件夹中所存图片的 URL 地址。`image_url` 会调用 `image_path`,然后加上程序的主机地址或静态文件的主机地址。 - -```ruby -image_url("https://github.com/edit.png") # => http://www.example.com/assets/edit.png -``` - -#### `image_tag` - -生成图片的 HTML `image` 标签。图片的地址可以是完整的 URL,或者 `app/assets/images` 文件夹中的图片。 - -```ruby -image_tag("icon.png") # => Icon -``` - -#### `javascript_include_tag` - -为指定的每个资源生成 HTML `script` 标签。可以传入 `app/assets/javascripts` 文件夹中所存 JavaScript 文件的文件名(扩展名 `.js` 可加可不加),或者可以使用相对文件根目录的完整路径。 - -```ruby -javascript_include_tag "common" # => -``` - -如果程序不使用 Asset Pipeline,要想引入 jQuery,可以传入 `:default`。使用 `:default` 时,如果 `app/assets/javascripts` 文件夹中存在 `application.js` 文件,也会将其引入。 - -```ruby -javascript_include_tag :defaults -``` - -还可以使用 `:all` 引入 `app/assets/javascripts` 文件夹中所有的 JavaScript 文件。 - -```ruby -javascript_include_tag :all -``` - -多个 JavaScript 文件还可合并成一个文件,减少 HTTP 连接数,还可以使用 gzip 压缩(提升传输速度)。只有 `ActionController::Base.perform_caching` 为 `true`(生产环境的默认值,开发环境为 `false`)时才会合并文件。 - -```ruby -javascript_include_tag :all, cache: true # => - -``` - -#### `javascript_path` - -生成 `app/assets/javascripts` 文件夹中 JavaScript 文件的地址。如果没指定文件的扩展名,会自动加上 `.js`。参数也可以使用相对文档根路径的完整地址。这个方法在 `javascript_include_tag` 中调用,用来生成脚本的地址。 - -```ruby -javascript_path "common" # => /assets/common.js -``` - -#### `javascript_url` - -生成 `app/assets/javascripts` 文件夹中 JavaScript 文件的 URL 地址。这个方法调用 `javascript_path`,然后再加上当前程序的主机地址或静态资源文件的主机地址。 - -```ruby -javascript_url "common" # => http://www.example.com/assets/common.js -``` - -#### `stylesheet_link_tag` - -返回指定资源的样式表 `link` 标签。如果没提供扩展名,会自动加上 `.css`。 - -```ruby -stylesheet_link_tag "application" # => -``` - -还可以使用 `:all`,引入 `app/assets/stylesheets` 文件夹中的所有样式表。 - -```ruby -stylesheet_link_tag :all -``` - -多个样式表还可合并成一个文件,减少 HTTP 连接数,还可以使用 gzip 压缩(提升传输速度)。只有 `ActionController::Base.perform_caching` 为 `true`(生产环境的默认值,开发环境为 `false`)时才会合并文件。 - -```ruby -stylesheet_link_tag :all, cache: true -# => -``` - -#### `stylesheet_path` - -生成 `app/assets/stylesheets` 文件夹中样式表的地址。如果没指定文件的扩展名,会自动加上 `.css`。参数也可以使用相对文档根路径的完整地址。这个方法在 `stylesheet_link_tag` 中调用,用来生成样式表的地址。 - -```ruby -stylesheet_path "application" # => /assets/application.css -``` - -#### `stylesheet_url` - -生成 `app/assets/stylesheets` 文件夹中样式表的 URL 地址。这个方法调用 `stylesheet_path`,然后再加上当前程序的主机地址或静态资源文件的主机地址。 - -```ruby -stylesheet_url "application" # => http://www.example.com/assets/application.css -``` - -### `AtomFeedHelper` - -#### `atom_feed` - -这个帮助方法可以简化生成 Atom Feed 的过程。下面是个完整的示例: - -```ruby -resources :posts -``` - -```ruby -def index - @posts = Post.all - - respond_to do |format| - format.html - format.atom - end -end -``` - -```ruby -atom_feed do |feed| - feed.title("Posts Index") - feed.updated((@posts.first.created_at)) - - @posts.each do |post| - feed.entry(post) do |entry| - entry.title(post.title) - entry.content(post.body, type: 'html') - - entry.author do |author| - author.name(post.author_name) - end - end - end -end -``` - -### `BenchmarkHelper` - -#### `benchmark` - -这个方法可以计算模板中某个代码块的执行时间,然后把结果写入日志。可以把耗时的操作或瓶颈操作放入 `benchmark` 代码块中,查看此项操作使用的时间。 - -```erb -<% benchmark "Process data files" do %> - <%= expensive_files_operation %> -<% end %> -``` - -上述代码会在日志中写入类似“Process data files (0.34523)”的文本,可用来对比优化前后的时间。 - -### `CacheHelper` - -#### `cache` - -这个方法缓存视图片段,而不是整个动作或页面。常用来缓存目录,新话题列表,静态 HTML 片段等。此方法接受一个代码块,即要缓存的内容。详情参见 `ActionController::Caching::Fragments` 模块的文档。 - -```erb -<% cache do %> - <%= render "shared/footer" %> -<% end %> -``` - -### `CaptureHelper` - -#### `capture` - -`capture` 方法可以把视图中的一段代码赋值给一个变量,这个变量可以在任何模板或视图中使用。 - -```erb -<% @greeting = capture do %> -

Welcome! The date and time is <%= Time.now %>

-<% end %> -``` - -`@greeting` 变量可以在任何地方使用。 - -```erb - - - Welcome! - - - <%= @greeting %> - - -``` - -#### `content_for` - -`content_for` 方法用一个标记符表示一段代码,在其他模板或布局中,可以把这个标记符传给 `yield` 方法,调用这段代码。 - -例如,程序有个通用的布局,但还有一个特殊页面,用到了其他页面不需要的 JavaScript 文件,此时就可以在这个特殊的页面中使用 `content_for` 方法,在不影响其他页面的情况下,引入所需的 JavaScript。 - -```erb - - - Welcome! - <%= yield :special_script %> - - -

Welcome! The date and time is <%= Time.now %>

- - -``` - -```erb -

This is a special page.

- -<% content_for :special_script do %> - -<% end %> -``` - -### `DateHelper` - -#### `date_select` - -这个方法会生成一组选择列表,分别对应年月日,用来设置日期相关的属性。 - -```ruby -date_select("post", "published_on") -``` - -#### `datetime_select` - -这个方法会生成一组选择列表,分别对应年月日时分,用来设置日期和时间相关的属性。 - -```ruby -datetime_select("post", "published_on") -``` - -#### `distance_of_time_in_words` - -这个方法会计算两个时间、两个日期或两个秒数之间的近似间隔。如果想得到更精准的间隔,可以把 `include_seconds` 选项设为 `true`。 - -```ruby -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 -``` - -#### `select_date` - -返回一组 HTML 选择列表标签,分别对应年月日,并且选中指定的日期。 - -```ruby -# Generates a date select that defaults to the date provided (six days after today) -select_date(Time.today + 6.days) - -# Generates a date select that defaults to today (no specified date) -select_date() -``` - -#### `select_datetime` - -返回一组 HTML 选择列表标签,分别对应年月日时分,并且选中指定的日期和时间。 - -```ruby -# Generates a datetime select that defaults to the datetime provided (four days after today) -select_datetime(Time.now + 4.days) - -# Generates a datetime select that defaults to today (no specified datetime) -select_datetime() -``` - -#### `select_day` - -返回一个选择列表标签,其选项是当前月份的每一天,并且选中当日。 - -```ruby -# Generates a select field for days that defaults to the day for the date provided -select_day(Time.today + 2.days) - -# Generates a select field for days that defaults to the number given -select_day(5) -``` - -#### `select_hour` - -返回一个选择列表标签,其选项是一天中的每一个小时(0-23),并且选中当前的小时数。 - -```ruby -# Generates a select field for hours that defaults to the hours for the time provided -select_hour(Time.now + 6.hours) -``` - -#### `select_minute` - -返回一个选择列表标签,其选项是一小时中的每一分钟(0-59),并且选中当前的分钟数。 - -```ruby -# Generates a select field for minutes that defaults to the minutes for the time provided. -select_minute(Time.now + 6.hours) -``` - -#### `select_month` - -返回一个选择列表标签,其选项是一年之中的所有月份(“January”-“December”),并且选中当前月份。 - -```ruby -# Generates a select field for months that defaults to the current month -select_month(Date.today) -``` - -#### `select_second` - -返回一个选择列表标签,其选项是一分钟内的各秒数(0-59),并且选中当前时间的秒数。 - -```ruby -# Generates a select field for seconds that defaults to the seconds for the time provided -select_second(Time.now + 16.minutes) -``` - -#### `select_time` - -返回一组 HTML 选择列表标签,分别对应小时和分钟。 - -```ruby -# Generates a time select that defaults to the time provided -select_time(Time.now) -``` - -#### `select_year` - -返回一个选择列表标签,其选项是今年前后各五年,并且选择今年。年份的前后范围可使用 `:start_year` 和 `:end_year` 选项指定。 - -```ruby -# Generates a select field for five years on either side of Date.today that defaults to the current year -select_year(Date.today) - -# Generates a select field from 1900 to 2009 that defaults to the current year -select_year(Date.today, start_year: 1900, end_year: 2009) -``` - -#### `time_ago_in_words` - -和 `distance_of_time_in_words` 方法作用类似,但是后一个时间点固定为当前时间(`Time.now`)。 - -```ruby -time_ago_in_words(3.minutes.from_now) # => 3 minutes -``` - -#### `time_select` - -返回一组选择列表标签,分别对应小时和分钟,秒数是可选的,用来设置基于时间的属性。选中的值会作为多个参数赋值给 Active Record 对象。 - -```ruby -# Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute -time_select("order", "submitted") -``` - -### `DebugHelper` - -返回一个 `pre` 标签,以 YAML 格式显示对象。用这种方法审查对象,可读性极高。 - -```ruby -my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]} -debug(my_hash) -``` - -```html -
---
-first: 1
-second: two
-third:
-- 1
-- 2
-- 3
-
-``` - -### `FormHelper` - -表单帮助方法的目的是替代标准的 HTML 元素,简化处理模型的过程。`FormHelper` 模块提供了很多方法,基于模型创建表单,不单可以生成表单的 HTML 标签,还能生成各种输入框标签,例如文本输入框,密码输入框,选择列表等。提交表单后(用户点击提交按钮,或者在 JavaScript 中调用 `form.submit`),其输入框中的值会存入 `params` 对象,传给控制器。 - -表单帮助方法分为两类,一种专门处理模型,另一种则不是。前者处理模型的属性;后者不处理模型属性,详情参见 `ActionView::Helpers::FormTagHelper` 模块的文档。 - -`FormHelper` 模块的核心是 `form_for` 方法,生成处理模型实例的表单。例如,有个名为 `Person` 的模型,要创建一个新实例,可使用下面的代码实现: - -```erb -# Note: a @person variable will have been created in the controller (e.g. @person = Person.new) -<%= form_for @person, url: {action: "create"} do |f| %> - <%= f.text_field :first_name %> - <%= f.text_field :last_name %> - <%= submit_tag 'Create' %> -<% end %> -``` - -生成的 HTML 如下: - -```html -
- - - -
-``` - -表单提交后创建的 `params` 对象如下: - -```ruby -{"action" => "create", "controller" => "people", "person" => {"first_name" => "William", "last_name" => "Smith"}} -``` - -`params` 中有个嵌套 Hash `person`,在控制器中使用 `params[:person]` 获取。 - -#### `check_box` - -返回一个复选框标签,处理指定的属性。 - -```ruby -# Let's say that @post.validated? is 1: -check_box("post", "validated") -# => -# -``` - -#### `fields_for` - -类似 `form_for`,为指定的模型创建一个作用域,但不会生成 `form` 标签。特别适合在同一个表单中处理多个模型。 - -```erb -<%= form_for @person, url: {action: "update"} do |person_form| %> - First name: <%= person_form.text_field :first_name %> - Last name : <%= person_form.text_field :last_name %> - - <%= fields_for @person.permission do |permission_fields| %> - Admin? : <%= permission_fields.check_box :admin %> - <% end %> -<% end %> -``` - -#### `file_field` - -返回一个文件上传输入框,处理指定的属性。 - -```ruby -file_field(:user, :avatar) -# => -``` - -#### `form_for` - -为指定的模型创建一个表单和作用域,表单中各字段的值都通过这个模型获取。 - -```erb -<%= form_for @post do |f| %> - <%= f.label :title, 'Title' %>: - <%= f.text_field :title %>
- <%= f.label :body, 'Body' %>: - <%= f.text_area :body %>
-<% end %> -``` - -#### `hidden_field` - -返回一个隐藏 `input` 标签,处理指定的属性。 - -```ruby -hidden_field(:user, :token) -# => -``` - -#### `label` - -返回一个 `label` 标签,为指定属性的输入框加上标签。 - -```ruby -label(:post, :title) -# => -``` - -#### `password_field` - -返回一个密码输入框,处理指定的属性。 - -```ruby -password_field(:login, :pass) -# => -``` - -#### `radio_button` - -返回一个单选框,处理指定的属性。 - -```ruby -# Let's say that @post.category returns "rails": -radio_button("post", "category", "rails") -radio_button("post", "category", "java") -# => -# -``` - -#### `text_area` - -返回一个多行文本输入框,处理指定的属性。 - -```ruby -text_area(:comment, :text, size: "20x30") -# => -``` - -#### `text_field` - -返回一个文本输入框,处理指定的属性。 - -```ruby -text_field(:post, :title) -# => -``` - -#### `email_field` - -返回一个 Email 输入框,处理指定的属性。 - -```ruby -email_field(:user, :email) -# => -``` - -#### `url_field` - -返回一个 URL 输入框,处理指定的属性。 - -```ruby -url_field(:user, :url) -# => -``` - -### `FormOptionsHelper` - -这个模块提供很多方法用来把不同类型的集合转换成一组 `option` 标签。 - -#### `collection_select` - -为 `object` 类的 `method` 方法返回的集合创建 `select` 和 `option` 标签。 - -使用此方法的模型示例: - -```ruby -class Post < ActiveRecord::Base - belongs_to :author -end - -class Author < ActiveRecord::Base - has_many :posts - def name_with_initial - "#{first_name.first}. #{last_name}" - end -end -``` - -使用举例,为文章实例(`@post`)选择作者(`Author`): - -```ruby -collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {prompt: true}) -``` - -如果 `@post.author_id` 的值是 1,上述代码生成的 HTML 如下: - -```html - -``` - -#### `collection_radio_buttons` - -为 `object` 类的 `method` 方法返回的集合创建 `radio_button` 标签。 - -使用此方法的模型示例: - -```ruby -class Post < ActiveRecord::Base - belongs_to :author -end - -class Author < ActiveRecord::Base - has_many :posts - def name_with_initial - "#{first_name.first}. #{last_name}" - end -end -``` - -使用举例,为文章实例(`@post`)选择作者(`Author`): - -```ruby -collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) -``` - -如果 `@post.author_id` 的值是 1,上述代码生成的 HTML 如下: - -```html - - - - - - -``` - -#### `collection_check_boxes` - -为 `object` 类的 `method` 方法返回的集合创建复选框标签。 - -使用此方法的模型示例: - -```ruby -class Post < ActiveRecord::Base - has_and_belongs_to_many :authors -end - -class Author < ActiveRecord::Base - has_and_belongs_to_many :posts - def name_with_initial - "#{first_name.first}. #{last_name}" - end -end -``` - -使用举例,为文章实例(`@post`)选择作者(`Author`): - -```ruby -collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) -``` - -如果 `@post.author_ids` 的值是 `[1]`,上述代码生成的 HTML 如下: - -```html - - - - - - - -``` - -#### `country_options_for_select` - -返回一组 `option` 标签,几乎包含世界上所有国家。 - -#### `country_select` - -返回指定对象和方法的 `select` 和 `option` 标签。使用 `country_options_for_select` 方法生成各个 `option` 标签。 - -#### `option_groups_from_collection_for_select` - -返回一个字符串,由多个 `option` 标签组成。和 `options_from_collection_for_select` 方法类似,但会根据对象之间的关系使用 `optgroup` 标签分组。 - -使用此方法的模型示例: - -```ruby -class Continent < ActiveRecord::Base - has_many :countries - # attribs: id, name -end - -class Country < ActiveRecord::Base - belongs_to :continent - # attribs: id, name, continent_id -end -``` - -使用举例: - -```ruby -option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) -``` - -可能得到的输出如下: - -```html - - - - ... - - - - - - ... - -``` - -注意,这个方法只会返回 `optgroup` 和 `option` 标签,所以你要把输出放入 `select` 标签中。 - -#### `options_for_select` - -接受一个集合(Hash,数组,可枚举的对象等),返回一个由 `option` 标签组成的字符串。 - -```ruby -options_for_select([ "VISA", "MasterCard" ]) -# => -``` - -注意,这个方法只返回 `option` 标签,所以你要把输出放入 `select` 标签中。 - -#### `options_from_collection_for_select` - -遍历 `collection`,返回一组 `option` 标签。每个 `option` 标签的值是在 `collection` 元素上调用 `value_method` 方法得到的结果,`option` 标签的显示文本是在 `collection` 元素上调用 `text_method` 方法得到的结果 - -```ruby -# options_from_collection_for_select(collection, value_method, text_method, selected = nil) -``` - -例如,下面的代码遍历 `@project.people`,生成一组 `option` 标签: - -```ruby -options_from_collection_for_select(@project.people, "id", "name") -# => -``` - -注意:`options_from_collection_for_select` 方法只返回 `option` 标签,你应该将其放在 `select` 标签中。 - -#### `select` - -创建一个 `select` 元素以及根据指定对象和方法得到的一系列 `option` 标签。 - -例如: - -```ruby -select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true}) -``` - -如果 `@post.person_id` 的值为 1,返回的结果是: - -```html - -``` - -#### `time_zone_options_for_select` - -返回一组 `option` 标签,包含几乎世界上所有的时区。 - -#### `time_zone_select` - -为指定的对象和方法返回 `select` 标签和 `option` 标签,`option` 标签使用 `time_zone_options_for_select` 方法生成。 - -```ruby -time_zone_select( "user", "time_zone") -``` - -#### `date_field` - -返回一个 `date` 类型的 `input` 标签,用于访问指定的属性。 - -```ruby -date_field("user", "dob") -``` - -### `FormTagHelper` - -这个模块提供一系列方法用于创建表单标签。`FormHelper` 依赖于传入模板的 Active Record 对象,但 `FormTagHelper` 需要手动指定标签的 `name` 属性和 `value` 属性。 - -#### `check_box_tag` - -为表单创建一个复选框标签。 - -```ruby -check_box_tag 'accept' -# => -``` - -#### `field_set_tag` - -创建 `fieldset` 标签,用于分组 HTML 表单元素。 - -```erb -<%= field_set_tag do %> -

<%= text_field_tag 'name' %>

-<% end %> -# =>

-``` - -#### `file_field_tag` - -创建一个文件上传输入框。 - -```erb -<%= form_tag({action:"post"}, multipart: true) do %> - <%= file_field_tag "file" %> - <%= submit_tag %> -<% end %> -``` - -结果示例: - -```ruby -file_field_tag 'attachment' -# => -``` - -#### `form_tag` - -创建 `form` 标签,指向的地址由 `url_for_options` 选项指定,和 `ActionController::Base#url_for` 方法类似。 - -```erb -<%= form_tag '/posts' do %> -
<%= submit_tag 'Save' %>
-<% end %> -# =>
-``` - -#### `hidden_field_tag` - -为表单创建一个隐藏的 `input` 标签,用于传递由于 HTTP 无状态的特性而丢失的数据,或者隐藏不想让用户看到的数据。 - -```ruby -hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@' -# => -``` - -#### `image_submit_tag` - -显示一个图片,点击后提交表单。 - -```ruby -image_submit_tag("login.png") -# => -``` - -#### `label_tag` - -创建一个 `label` 标签。 - -```ruby -label_tag 'name' -# => -``` - -#### `password_field_tag` - -创建一个密码输入框,用户输入的值会被遮盖。 - -```ruby -password_field_tag 'pass' -# => -``` - -#### `radio_button_tag` - -创建一个单选框。如果希望用户从一组选项中选择,可以使用多个单选框,`name` 属性的值都设为一样的。 - -```ruby -radio_button_tag 'gender', 'male' -# => -``` - -#### `select_tag` - -创建一个下拉选择框。 - -```ruby -select_tag "people", "" -# => -``` - -#### `submit_tag` - -创建一个提交按钮,按钮上显示指定的文本。 - -```ruby -submit_tag "Publish this post" -# => -``` - -#### `text_area_tag` - -创建一个多行文本输入框,用于输入大段文本,例如博客和描述信息。 - -```ruby -text_area_tag 'post' -# => -``` - -#### `text_field_tag` - -创建一个标准文本输入框,用于输入小段文本,例如用户名和搜索关键字。 - -```ruby -text_field_tag 'name' -# => -``` - -#### `email_field_tag` - -创建一个标准文本输入框,用于输入 Email 地址。 - -```ruby -email_field_tag 'email' -# => -``` - -#### `url_field_tag` - -创建一个标准文本输入框,用于输入 URL 地址。 - -```ruby -url_field_tag 'url' -# => -``` - -#### `date_field_tag` - -创建一个标准文本输入框,用于输入日期。 - -```ruby -date_field_tag "dob" -# => -``` - -### `JavaScriptHelper` - -这个模块提供在视图中使用 JavaScript 的相关方法。 - -#### `button_to_function` - -返回一个按钮,点击后触发一个 JavaScript 函数。例如: - -```ruby -button_to_function "Greeting", "alert('Hello world!')" -button_to_function "Delete", "if (confirm('Really?')) do_delete()" -button_to_function "Details" do |page| - page[:details].visual_effect :toggle_slide -end -``` - -#### `define_javascript_functions` - -在一个 `script` 标签中引入 Action Pack JavaScript 代码库。 - -#### `escape_javascript` - -转义 JavaScript 中的回车符、单引号和双引号。 - -#### `javascript_tag` - -返回一个 `script` 标签,把指定的代码放入其中。 - -```ruby -javascript_tag "alert('All is good')" -``` - -```html - -``` - -#### `link_to_function` - -返回一个链接,点击后触发指定的 JavaScript 函数并返回 `false`。 - -```ruby -link_to_function "Greeting", "alert('Hello world!')" -# =>
Greeting -``` - -### `NumberHelper` - -这个模块提供用于把数字转换成格式化字符串所需的方法。包括用于格式化电话号码、货币、百分比、精度、进位制和文件大小的方法。 - -#### `number_to_currency` - -把数字格式化成货币字符串,例如 $13.65。 - -```ruby -number_to_currency(1234567890.50) # => $1,234,567,890.50 -``` - -#### `number_to_human_size` - -把字节数格式化成更易理解的形式,显示文件大小时特别有用。 - -```ruby -number_to_human_size(1234) # => 1.2 KB -number_to_human_size(1234567) # => 1.2 MB -``` - -#### `number_to_percentage` - -把数字格式化成百分数形式。 - -```ruby -number_to_percentage(100, precision: 0) # => 100% -``` - -#### `number_to_phone` - -把数字格式化成美国使用的电话号码形式。 - -```ruby -number_to_phone(1235551234) # => 123-555-1234 -``` - -#### `number_with_delimiter` - -格式化数字,使用分隔符隔开每三位数字。 - -```ruby -number_with_delimiter(12345678) # => 12,345,678 -``` - -#### `number_with_precision` - -使用指定的精度格式化数字,精度默认值为 3。 - -```ruby -number_with_precision(111.2345) # => 111.235 -number_with_precision(111.2345, 2) # => 111.23 -``` - -### `SanitizeHelper` - -`SanitizeHelper` 模块提供一系列方法,用于剔除不想要的 HTML 元素。 - -#### `sanitize` - -`sanitize` 方法会编码所有标签,并删除所有不允许使用的属性。 - -```ruby -sanitize @article.body -``` - -如果指定了 `:attributes` 或 `:tags` 选项,只允许使用指定的标签和属性。 - -```ruby -sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) -``` - -要想修改默认值,例如允许使用 `table` 标签,可以这么设置: - -```ruby -class Application < Rails::Application - config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' -end -``` - -#### `sanitize_css(style)` - -过滤一段 CSS 代码。 - -#### `strip_links(html)` - -删除文本中的所有链接标签,但保留链接文本。 - -```ruby -strip_links("Ruby on Rails") -# => Ruby on Rails -``` - -```ruby -strip_links("emails to me@email.com.") -# => emails to me@email.com. -``` - -```ruby -strip_links('Blog: Visit.') -# => Blog: Visit. -``` - -#### `strip_tags(html)` - -过滤 `html` 中的所有 HTML 标签,以及注释。 - -这个方法使用 `html-scanner` 解析 HTML,所以解析能力受 `html-scanner` 的限制。 - -```ruby -strip_tags("Strip these tags!") -# => Strip these tags! -``` - -```ruby -strip_tags("Bold no more! See more") -# => Bold no more! See more -``` - -注意,得到的结果中可能仍然有字符 `<`、`>` 和 `&`,会导致浏览器显示异常。 - -视图本地化 ---------- - -Action View 可以根据当前的本地化设置渲染不同的模板。 - -例如,假设有个 `PostsController`,在其中定义了 `show` 动作。默认情况下,执行这个动作时渲染的是 `app/views/posts/show.html.erb`。如果设置了 `I18n.locale = :de`,渲染的则是 `app/views/posts/show.de.html.erb`。如果本地化对应的模板不存在就使用默认模板。也就是说,没必要为所有动作编写本地化视图,但如果有本地化对应的模板就会使用。 - -相同的技术还可用在 `public` 文件夹中的错误文件上。例如,设置了 `I18n.locale = :de`,并创建了 `public/500.de.html` 和 `public/404.de.html`,就能显示本地化的错误页面。 - -Rails 并不限制 `I18n.locale` 选项的值,因此可以根据任意需求显示不同的内容。假设想让专业用户看到不同于普通用户的页面,可以在 `app/controllers/application_controller.rb` 中这么设置: - -```ruby -before_action :set_expert_locale - -def set_expert_locale - I18n.locale = :expert if current_user.expert? -end -``` - -然后创建只显示给专业用户的 `app/views/posts/show.expert.html.erb` 视图。 - -详情参阅“[Rails 国际化 API](i18n.html)”一文。 diff --git a/source/zh-CN/active_job_basics.md b/source/zh-CN/active_job_basics.md deleted file mode 100644 index 0237c5b..0000000 --- a/source/zh-CN/active_job_basics.md +++ /dev/null @@ -1,291 +0,0 @@ -Active Job 基础 -================= - -本文提供开始创建任务、将任务加入队列和后台执行任务的所有知识。 - -读完本文,你将学到: - -* 如何新建任务 -* 如何将任务加入队列 -* 如何在后台运行任务 -* 如何在应用中异步发送邮件 - --------------------------------------------------------------------------------- - - - 简介 -------------- - -Active Job 是用来声明任务,并把任务放到多种多样的队列后台中执行的框架。从定期地安排清理,费用账单到发送邮件,任何事情都可以是任务。任何可以切分为小的单元和并行执行的任务都可以用 Active Job 来执行。 - - -Active Job 的目标 ----------------------- - -主要是确保所有的 Rails 程序有一致任务框架,即便是以 “立即执行”的形式存在。然后可以基于 Active Job 来新建框架功能和其他的 RubyGems, 而不用担心多种任务后台,比如 Dalayed Job 和 Resque 之间 API 的差异。之后,选择队列后台更多会变成运维方面的考虑,这样就能切换后台而无需重写任务代码。 - - -创建一个任务 ---------- - -本节将会逐步地创建任务然后把任务加入队列中。 - -### 创建任务 - -Active Job 提供了 Rails 生成器来创建任务。以下代码会在 `app/jobs` 中新建一个任务,(并且会在 `test/jobs` 中创建测试用例): - -```bash -$ bin/rails generate job guests_cleanup -invoke test_unit -create test/jobs/guests_cleanup_job_test.rb -create app/jobs/guests_cleanup_job.rb -``` - -也可以创建运行在一个特定队列上的任务: - -```bash -$ bin/rails generate job guests_cleanup --queue urgent -``` - -如果不想使用生成器,需要自己创建文件,并且替换掉 `app/jobs`。确保任务继承自 `ActiveJob::Base` 即可。 - -以下是一个任务示例: - -```ruby -class GuestsCleanupJob < ActiveJob::Base - queue_as :default - - def perform(*args) - # Do something later - end -end -``` - -### 任务加入队列 - -将任务加入到队列中: - -```ruby -# 将加入到队列系统中任务立即执行 -MyJob.perform_later record -``` - -```ruby -# 在明天中午执行加入队列的任务 -MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) -``` - -```ruby -# 一星期后执行加入到队列的任务 -MyJob.set(wait: 1.week).perform_later(record) -``` - -就这么简单! - - -任务执行 -------- - -如果没有设置连接器,任务会立即执行。 - - -### 后台 - -Active Job 内建支持多种队列后台连接器(Sidekiq、Resque、Delayed Job 等)。最新的连接器的列表详见 [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html) 的 API 文件。 - - -### 设置后台 - -设置队列后台很简单: - -```ruby -# config/application.rb -module YourApp - class Application < Rails::Application - # Be sure to have the adapter's gem in your Gemfile and follow - # the adapter's specific installation and deployment instructions. - config.active_job.queue_adapter = :sidekiq - end -end -``` - - -队列 ----- - -大多数连接器支持多种队列。用 Active Job 可以安排任务运行在特定的队列: - -```ruby -class GuestsCleanupJob < ActiveJob::Base - queue_as :low_priority - #.... -end -``` - -在 `application.rb` 中通过 `config.active_job.queue_name_prefix` 来设置所有任务的队列名称的前缀。 - -```ruby -# config/application.rb -module YourApp - class Application < Rails::Application - config.active_job.queue_name_prefix = Rails.env - end -end - -# app/jobs/guests_cleanup.rb -class GuestsCleanupJob < ActiveJob::Base - queue_as :low_priority - #.... -end - -# Now your job will run on queue production_low_priority on your -# production environment and on staging_low_priority on your staging -# environment -``` - -默认队列名称的前缀是 `_`。可以设置 `config/application.rb` 里 `config.active_job.queue_name_delimiter` 的值来改变: - -```ruby -# config/application.rb -module YourApp - class Application < Rails::Application - config.active_job.queue_name_prefix = Rails.env - config.active_job.queue_name_delimiter = '.' - end -end - -# app/jobs/guests_cleanup.rb -class GuestsCleanupJob < ActiveJob::Base - queue_as :low_priority - #.... -end - -# Now your job will run on queue production.low_priority on your -# production environment and on staging.low_priority on your staging -# environment -``` - -如果想要更细致的控制任务的执行,可以传 `:queue` 选项给 `#set` 方法: - -```ruby -MyJob.set(queue: :another_queue).perform_later(record) -``` - -为了在任务级别控制队列,可以传递一个块给 `#queue_as`。块会在任务的上下文中执行(所以能获得 `self.arguments`)并且必须返回队列的名字: - -```ruby -class ProcessVideoJob < ActiveJob::Base - queue_as do - video = self.arguments.first - if video.owner.premium? - :premium_videojobs - else - :videojobs - end - end - - def perform(video) - # do process video - end -end - -ProcessVideoJob.perform_later(Video.last) -``` - -NOTE: 确认运行的队列后台“监听”队列的名称。某些后台需要明确的指定要“监听”队列的名称。 - - -回调 ---------- - -Active Job 在一个任务的生命周期里提供了钩子。回调允许在任务的生命周期中触发逻辑。 - -### 可用的回调 - -* `before_enqueue` -* `around_enqueue` -* `after_enqueue` -* `before_perform` -* `around_perform` -* `after_perform` - -### 用法 - -```ruby -class GuestsCleanupJob < ActiveJob::Base - queue_as :default - - before_enqueue do |job| - # do something with the job instance - end - - around_perform do |job, block| - # do something before perform - block.call - # do something after perform - end - - def perform - # Do something later - end -end -``` - - -Action Mailer ----------------- - -现代网站应用中最常见的任务之一是,在请求响应周期外发送 Email,这样所有用户不需要焦急地等待邮件的发送。Active Job 集成到 Action Mailer 里了,所以能够简单的实现异步发送邮件: - -```ruby -# If you want to send the email now use #deliver_now -UserMailer.welcome(@user).deliver_now - -# If you want to send the email through Active Job use #deliver_later -UserMailer.welcome(@user).deliver_later -``` - - -GlobalID ------------ - -Active Job 支持 GlobalID 作为参数。这样传递运行中的 Active Record 对象到任务中,来取代通常需要序列化的 class/id 对。之前任务看起来是像这样: - -```ruby -class TrashableCleanupJob < ActiveJob::Base - def perform(trashable_class, trashable_id, depth) - trashable = trashable_class.constantize.find(trashable_id) - trashable.cleanup(depth) - end -end -``` - -现在可以简化为: - -```ruby -class TrashableCleanupJob < ActiveJob::Base - def perform(trashable, depth) - trashable.cleanup(depth) - end -end -``` - - -异常 -------- - -Active Job 提供了在任务执行期间捕获异常的方法: - -```ruby -class GuestsCleanupJob < ActiveJob::Base - queue_as :default - - rescue_from(ActiveRecord::RecordNotFound) do |exception| - # do something with the exception - end - - def perform - # Do something later - end -end -``` diff --git a/source/zh-CN/active_model_basics.md b/source/zh-CN/active_model_basics.md deleted file mode 100644 index 3eaeeff..0000000 --- a/source/zh-CN/active_model_basics.md +++ /dev/null @@ -1,223 +0,0 @@ -Active Model Basics -=================== - -This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-Active Record models. Active Model also helps building custom ORMs for use outside of the Rails framework. - -After reading this guide, you will know: - --------------------------------------------------------------------------------- - -Introduction ------------- - -Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below. - -### AttributeMethods - -The AttributeMethods module can add custom prefixes and suffixes on methods of a class. It is used by defining the prefixes and suffixes and which methods on the object will use them. - -```ruby -class Person - include ActiveModel::AttributeMethods - - attribute_method_prefix 'reset_' - attribute_method_suffix '_highest?' - define_attribute_methods 'age' - - attr_accessor :age - - private - def reset_attribute(attribute) - send("#{attribute}=", 0) - end - - def attribute_highest?(attribute) - send(attribute) > 100 - end -end - -person = Person.new -person.age = 110 -person.age_highest? # true -person.reset_age # 0 -person.age_highest? # false -``` - -### Callbacks - -Callbacks gives Active Record style callbacks. This provides an ability to define callbacks which run at appropriate times. After defining callbacks, you can wrap them with before, after and around custom methods. - -```ruby -class Person - extend ActiveModel::Callbacks - - define_model_callbacks :update - - before_update :reset_me - - def update - run_callbacks(:update) do - # This method is called when update is called on an object. - end - end - - def reset_me - # This method is called when update is called on an object as a before_update callback is defined. - end -end -``` - -### Conversion - -If a class defines `persisted?` and `id` methods, then you can include the `Conversion` module in that class and call the Rails conversion methods on objects of that class. - -```ruby -class Person - include ActiveModel::Conversion - - def persisted? - false - end - - def id - nil - end -end - -person = Person.new -person.to_model == person # => true -person.to_key # => nil -person.to_param # => nil -``` - -### Dirty - -An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes `first_name` and `last_name`: - -```ruby -require 'active_model' - -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 - # do save work... - changes_applied - end -end -``` - -#### Querying object directly for its list of all changed attributes. - -```ruby -person = Person.new -person.changed? # => false - -person.first_name = "First Name" -person.first_name # => "First Name" - -# returns if any attribute has changed. -person.changed? # => true - -# returns a list of attributes that have changed before saving. -person.changed # => ["first_name"] - -# returns a hash of the attributes that have changed with their original values. -person.changed_attributes # => {"first_name"=>nil} - -# returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field. -person.changes # => {"first_name"=>[nil, "First Name"]} -``` - -#### Attribute based accessor methods - -Track whether the particular attribute has been changed or not. - -```ruby -# attr_name_changed? -person.first_name # => "First Name" -person.first_name_changed? # => true -``` - -Track what was the previous value of the attribute. - -```ruby -# attr_name_was accessor -person.first_name_was # => "First Name" -``` - -Track both previous and current value of the changed attribute. Returns an array if changed, else returns nil. - -```ruby -# attr_name_change -person.first_name_change # => [nil, "First Name"] -person.last_name_change # => nil -``` - -### Validations - -Validations module adds the ability to class objects to validate them in Active Record style. - -```ruby -class Person - include ActiveModel::Validations - - attr_accessor :name, :email, :token - - validates :name, presence: true - validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i - validates! :token, presence: true -end - -person = Person.new(token: "2b1f325") -person.valid? # => false -person.name = 'vishnu' -person.email = 'me' -person.valid? # => false -person.email = 'me@vishnuatrai.com' -person.valid? # => true -person.token = nil -person.valid? # => raises ActiveModel::StrictValidationFailed -``` - -### ActiveModel::Naming - -Naming adds a number of class methods which make the naming and routing -easier to manage. The module defines the `model_name` class method which -will define a number of accessors using some `ActiveSupport::Inflector` methods. - -```ruby -class Person - extend ActiveModel::Naming -end - -Person.model_name.name # => "Person" -Person.model_name.singular # => "person" -Person.model_name.plural # => "people" -Person.model_name.element # => "person" -Person.model_name.human # => "Person" -Person.model_name.collection # => "people" -Person.model_name.param_key # => "person" -Person.model_name.i18n_key # => :person -Person.model_name.route_key # => "people" -Person.model_name.singular_route_key # => "person" -``` diff --git a/source/zh-CN/active_record_basics.md b/source/zh-CN/active_record_basics.md deleted file mode 100644 index 0a32534..0000000 --- a/source/zh-CN/active_record_basics.md +++ /dev/null @@ -1,277 +0,0 @@ -Active Record 基础 -================== - -本文介绍 Active Record。 - -读完本文,你将学到: - -* 对象关系映射(Object Relational Mapping,ORM)和 Active Record 是什么,以及如何在 Rails 中使用; -* Active Record 在 MVC 中的作用; -* 如何使用 Active Record 模型处理保存在关系型数据库中的数据; -* Active Record 模式(schema)命名约定; -* 数据库迁移,数据验证和回调; - --------------------------------------------------------------------------------- - -Active Record 是什么? ---------------------- - -Active Record 是 [MVC](getting_started.html#the-mvc-architecture) 中的 M(模型),处理数据和业务逻辑。Active Record 负责创建和使用需要持久存入数据库中的数据。Active Record 实现了 Active Record 模式,是一种对象关系映射系统。 - -### Active Record 模式 - -Active Record 模式出自 [Martin Fowler](http://www.martinfowler.com/eaaCatalog/activeRecord.html) 的《企业应用架构模式》一书。在 Active Record 模式中,对象中既有持久存储的数据,也有针对数据的操作。Active Record 模式把数据存取逻辑作为对象的一部分,处理对象的用户知道如何把数据写入数据库,以及从数据库中读出数据。 - -### 对象关系映射 - -对象关系映射(ORM)是一种技术手段,把程序中的对象和关系型数据库中的数据表连接起来。使用 ORM,程序中对象的属性和对象之间的关系可以通过一种简单的方法从数据库获取,无需直接编写 SQL 语句,也不过度依赖特定的数据库种类。 - -### Active Record 用作 ORM 框架 - -Active Record 提供了很多功能,其中最重要的几个如下: - -* 表示模型和其中的数据; -* 表示模型之间的关系; -* 通过相关联的模型表示继承关系; -* 持久存入数据库之前,验证模型; -* 以面向对象的方式处理数据库操作; - -Active Record 中的“多约定少配置”原则 ----------------------------------- - -使用其他编程语言或框架开发程序时,可能必须要编写很多配置代码。大多数的 ORM 框架都是这样。但是,如果遵循 Rails 的约定,创建 Active Record 模型时不用做多少配置(有时甚至完全不用配置)。Rails 的理念是,如果大多数情况下都要使用相同的方式配置程序,那么就应该把这定为默认的方法。所以,只有常规的方法无法满足要求时,才要额外的配置。 - -### 命名约定 - -默认情况下,Active Record 使用一些命名约定,查找模型和数据表之间的映射关系。Rails 把模型的类名转换成复数,然后查找对应的数据表。例如,模型类名为 `Book`,数据表就是 `books`。Rails 提供的单复数变形功能很强大,常见和不常见的变形方式都能处理。如果类名由多个单词组成,应该按照 Ruby 的约定,使用驼峰式命名法,这时对应的数据表将使用下划线分隔各单词。因此: - -* 数据表名:复数,下划线分隔单词(例如 `book_clubs`) -* 模型类名:单数,每个单词的首字母大写(例如 `BookClub`) - -| 模型 / 类 | 数据表 / 模式 | -| ------------- | -------------- | -| `Post` | `posts` | -| `LineItem` | `line_items` | -| `Deer` | `deers` | -| `Mouse` | `mice` | -| `Person` | `people` | - -### 模式约定 - -根据字段的作用不同,Active Record 对数据表中的字段命名也做了相应的约定: - -* **外键** - 使用 `singularized_table_name_id` 形式命名,例如 `item_id`,`order_id`。创建模型关联后,Active Record 会查找这个字段; -* **主键** - 默认情况下,Active Record 使用整数字段 `id` 作为表的主键。使用 [Active Record 迁移]({{ site.baseurl}}/migrations.html)创建数据表时,会自动创建这个字段; - -还有一些可选的字段,能为 Active Record 实例添加更多的功能: - -* `created_at` - 创建记录时,自动设为当前的时间戳; -* `updated_at` - 更新记录时,自动设为当前的时间戳; -* `lock_version` - 在模型中添加[乐观锁定](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html)功能; -* `type` - 让模型使用[单表继承](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance); -* `(association_name)_type` - [多态关联](association_basics.html#polymorphic-associations)的类型; -* `(table_name)_count` - 缓存关联对象的数量。例如,`posts` 表中的 `comments_count` 字段,缓存每篇文章的评论数; - -NOTE: 虽然这些字段是可选的,但在 Active Record 中是被保留的。如果想使用相应的功能,就不要把这些保留字段用作其他用途。例如,`type` 这个保留字段是用来指定数据表使用“单表继承”(STI)的,如果不用 STI,请使用其他的名字,例如“context”,这也能表明该字段的作用。 - -创建 Active Record 模型 ------------------------ - -创建 Active Record 模型的过程很简单,只要继承 `ActiveRecord::Base` 类就行了: - -```ruby -class Product < ActiveRecord::Base -end -``` - -上面的代码会创建 `Product` 模型,对应于数据库中的 `products` 表。同时,`products` 表中的字段也映射到 `Product` 模型实例的属性上。假如 `products` 表由下面的 SQL 语句创建: - -```sql -CREATE TABLE products ( - id int(11) NOT NULL auto_increment, - name varchar(255), - PRIMARY KEY (id) -); -``` - -按照这样的数据表结构,可以编写出下面的代码: - -```ruby -p = Product.new -p.name = "Some Book" -puts p.name # "Some Book" -``` - -不用默认的命名约定 ----------------- - -如果想使用其他的命名约定,或者在 Rails 程序中使用即有的数据库可以吗?没问题,不用默认的命名约定也很简单。 - -使用 `ActiveRecord::Base.table_name=` 方法可以指定数据表的名字: - -```ruby -class Product < ActiveRecord::Base - self.table_name = "PRODUCT" -end -``` - -如果这么做,还要在测试中调用 `set_fixture_class` 方法,手动指定固件(`class_name.yml`)的类名: - -```ruby -class FunnyJoke < ActiveSupport::TestCase - set_fixture_class funny_jokes: Joke - fixtures :funny_jokes - ... -end -``` - -还可以使用 `ActiveRecord::Base.primary_key=` 方法指定数据表的主键: - -```ruby -class Product < ActiveRecord::Base - self.primary_key = "product_id" -end -``` - -CRUD:读写数据 -------------- - -CURD 是四种数据操作的简称:C 表示创建,R 表示读取,U 表示更新,D 表示删除。Active Record 自动创建了处理数据表中数据的方法。 - -### 创建 - -Active Record 对象可以使用 Hash 创建,在块中创建,或者创建后手动设置属性。`new` 方法会实例化一个对象,`create` 方法实例化一个对象,并将其存入数据库。 - -例如,`User` 模型中有两个属性,`name` 和 `occupation`。调用 `create` 方法会实例化一个对象,并把该对象对应的记录存入数据库: - -```ruby -user = User.create(name: "David", occupation: "Code Artist") -``` - -使用 `new` 方法,可以实例化一个对象,但不会保存: - -```ruby -user = User.new -user.name = "David" -user.occupation = "Code Artist" -``` - -调用 `user.save` 可以把记录存入数据库。 - -`create` 和 `new` 方法从结果来看,都实现了下面代码的功能: - -```ruby -user = User.new do |u| - u.name = "David" - u.occupation = "Code Artist" -end -``` - -### 读取 - -Active Record 为读取数据库中的数据提供了丰富的 API。下面举例说明。 - -```ruby -# return a collection with all users -users = User.all -``` - -```ruby -# return the first user -user = User.first -``` - -```ruby -# return the first user named David -david = User.find_by(name: 'David') -``` - -```ruby -# find all users named David who are Code Artists and sort by created_at -# in reverse chronological order -users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC') -``` - -[Active Record 查询](active_record_querying.html)一文会详细介绍查询 Active Record 模型的方法。 - -### 更新 - -得到 Active Record 对象后,可以修改其属性,然后再存入数据库。 - -```ruby -user = User.find_by(name: 'David') -user.name = 'Dave' -user.save -``` - -还有个简写方式,使用 Hash,指定属性名和属性值,例如: - -```ruby -user = User.find_by(name: 'David') -user.update(name: 'Dave') -``` - -一次更新多个属性时使用这种方法很方便。如果想批量更新多个记录,可以使用类方法 `update_all`: - -```ruby -User.update_all "max_login_attempts = 3, must_change_password = 'true'" -``` - -### 删除 - -类似地,得到 Active Record 对象后还可以将其销毁,从数据库中删除。 - -```ruby -user = User.find_by(name: 'David') -user.destroy -``` - -数据验证 -------- - -在存入数据库之前,Active Record 还可以验证模型。模型验证有很多方法,可以检查属性值是否不为空、是否是唯一的,或者没有在数据库中出现过,等等。 - -把数据存入数据库之前进行验证是十分重要的步骤,所以调用 `create`、`save`、`update` 这三个方法时会做数据验证,验证失败时返回 `false`,此时不会对数据库做任何操作。这三个方法都有对应的爆炸方法(`create!`,`save!`,`update!`),爆炸方法要严格一些,如果验证失败,会抛出 `ActiveRecord::RecordInvalid` 异常。下面是个简单的例子: - -```ruby -class User < ActiveRecord::Base - validates :name, presence: true -end - -User.create # => false -User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank -``` - -[Active Record 数据验证](active_record_validations.html)一文会详细介绍数据验证。 - -回调 ----- - -Active Record 回调可以在模型声明周期的特定事件上绑定代码,相应的事件发生时,执行这些代码。例如创建新纪录时,更新记录时,删除记录时,等等。[Active Record 回调](active_record_callbacks.html)一文会详细介绍回调。 - -迁移 ----- - -Rails 提供了一个 DSL 用来处理数据库模式,叫做“迁移”。迁移的代码存储在特定的文件中,通过 `rake` 调用,可以用在 Active Record 支持的所有数据库上。下面这个迁移会新建一个数据表: - -```ruby -class CreatePublications < ActiveRecord::Migration - def change - create_table :publications do |t| - t.string :title - t.text :description - t.references :publication_type - t.integer :publisher_id - t.string :publisher_type - t.boolean :single_issue - - t.timestamps - end - add_index :publications, :publication_type_id - end -end -``` - -Rails 会跟踪哪些迁移已经应用到数据库中,还提供了回滚功能。创建数据表要执行 `rake db:migrate` 命令;回滚操作要执行 `rake db:rollback` 命令。 - -注意,上面的代码和具体的数据库种类无关,可用于 MySQL、PostgreSQL、Oracle 等数据库。关于迁移的详细介绍,参阅 [Active Record 迁移](active_record_migrations.html)一文。 diff --git a/source/zh-CN/active_record_callbacks.md b/source/zh-CN/active_record_callbacks.md deleted file mode 100644 index dc906fa..0000000 --- a/source/zh-CN/active_record_callbacks.md +++ /dev/null @@ -1,408 +0,0 @@ -Active Record 回调 -================== - -本文介绍如何介入 Active Record 对象的生命周期。 - -读完本文,你将学到: - -* Active Record 对象的生命周期; -* 如何编写回调方法响应对象声明周期内发生的事件; -* 如何把常用的回调封装到特殊的类中; - --------------------------------------------------------------------------------- - -对象的生命周期 ------------- - -在 Rails 程序运行过程中,对象可以被创建、更新和销毁。Active Record 为对象的生命周期提供了很多钩子,让你控制程序及其数据。 - -回调可以在对象的状态改变之前或之后触发指定的逻辑操作。 - -回调简介 -------- - -回调是在对象生命周期的特定时刻执行的方法。回调方法可以在 Active Record 对象创建、保存、更新、删除、验证或从数据库中读出时执行。 - -### 注册回调 - -在使用回调之前,要先注册。回调方法的定义和普通的方法一样,然后使用类方法注册: - -```ruby -class User < ActiveRecord::Base - 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 -``` - -这种类方法还可以接受一个代码块。如果操作可以使用一行代码表述,可以考虑使用代码块形式。 - -```ruby -class User < ActiveRecord::Base - validates :login, :email, presence: true - - before_create do - self.name = login.capitalize if name.blank? - end -end -``` - -注册回调时可以指定只在对象生命周期的特定事件发生时执行: - -```ruby -class User < ActiveRecord::Base - before_validation :normalize_name, on: :create - - # :on takes an array as well - after_validation :set_location, on: [ :create, :update ] - - protected - def normalize_name - self.name = self.name.downcase.titleize - end - - def set_location - self.location = LocationService.query(self) - end -end -``` - -一般情况下,都把回调方法定义为受保护的方法或私有方法。如果定义成公共方法,回调就可以在模型外部调用,违背了对象封装原则。 - -可用的回调 ---------- - -下面列出了所有可用的 Active Record 回调,按照执行各操作时触发的顺序: - -### 创建对象 - -* `before_validation` -* `after_validation` -* `before_save` -* `around_save` -* `before_create` -* `around_create` -* `after_create` -* `after_save` - -### 更新对象 - -* `before_validation` -* `after_validation` -* `before_save` -* `around_save` -* `before_update` -* `around_update` -* `after_update` -* `after_save` - -### 销毁对象 - -* `before_destroy` -* `around_destroy` -* `after_destroy` - -WARNING: 创建和更新对象时都会触发 `after_save`,但不管注册的顺序,总在 `after_create` 和 `after_update` 之后执行。 - -### `after_initialize` 和 `after_find` - -`after_initialize` 回调在 Active Record 对象初始化时执行,包括直接使用 `new` 方法初始化和从数据库中读取记录。`after_initialize` 回调不用直接重定义 Active Record 的 `initialize` 方法。 - -`after_find` 回调在从数据库中读取记录时执行。如果同时注册了 `after_find` 和 `after_initialize` 回调,`after_find` 会先执行。 - -`after_initialize` 和 `after_find` 没有对应的 `before_*` 回调,但可以像其他回调一样注册。 - -```ruby -class User < ActiveRecord::Base - after_initialize do |user| - puts "You have initialized an object!" - end - - after_find do |user| - puts "You have found an object!" - end -end - ->> User.new -You have initialized an object! -=> # - ->> User.first -You have found an object! -You have initialized an object! -=> # -``` - -### `after_touch` - -`after_touch` 回调在触碰 Active Record 对象时执行。 - -```ruby -class User < ActiveRecord::Base - after_touch do |user| - puts "You have touched an object" - end -end - ->> u = User.create(name: 'Kuldeep') -=> # - ->> u.touch -You have touched an object -=> true -``` - -可以结合 `belongs_to` 一起使用: - -```ruby -class Employee < ActiveRecord::Base - belongs_to :company, touch: true - after_touch do - puts 'An Employee was touched' - end -end - -class Company < ActiveRecord::Base - has_many :employees - after_touch :log_when_employees_or_company_touched - - private - def log_when_employees_or_company_touched - puts 'Employee/Company was touched' - end -end - ->> @employee = Employee.last -=> # - -# triggers @employee.company.touch ->> @employee.touch -Employee/Company was touched -An Employee was touched -=> true -``` - -执行回调 -------- - -下面的方法会触发执行回调: - -* `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` 回调在新对象初始化时触发执行。 - -NOTE: `find_by_*` 和 `find_by_*!` 是为每个属性生成的动态查询方法,详情参见“[动态查询方法](active_record_querying.html#dynamic-finders)”一节。 - -跳过回调 --------- - -和数据验证一样,回调也可跳过,使用下列方法即可: - -* `decrement` -* `decrement_counter` -* `delete` -* `delete_all` -* `increment` -* `increment_counter` -* `toggle` -* `touch` -* `update_column` -* `update_columns` -* `update_all` -* `update_counters` - -使用这些方法是要特别留心,因为重要的业务逻辑可能在回调中完成。如果没弄懂回调的作用直接跳过,可能导致数据不合法。 - -终止执行 --------- - -在模型中注册回调后,回调会加入一个执行队列。这个队列中包含模型的数据验证,注册的回调,以及要执行的数据库操作。 - -整个回调链包含在一个事务中。如果任何一个 `before_*` 回调方法返回 `false` 或抛出异常,整个回调链都会终止执行,撤销事务;而 `after_*` 回调只有抛出异常才能达到相同的效果。 - -WARNING: `ActiveRecord::Rollback` 之外的异常在回调链终止之后,还会由 Rails 再次抛出。抛出 `ActiveRecord::Rollback` 之外的异常,可能导致不应该抛出异常的方法(例如 `save` 和 `update_attributes`,应该返回 `true` 或 `false`)无法执行。 - -关联回调 -------- - -回调能在模型关联中使用,甚至可由关联定义。假如一个用户发布了多篇文章,如果用户删除了,他发布的文章也应该删除。下面我们在 `Post` 模型中注册一个 `after_destroy` 回调,应用到 `User` 模型上: - -```ruby -class User < ActiveRecord::Base - has_many :posts, dependent: :destroy -end - -class Post < ActiveRecord::Base - after_destroy :log_destroy_action - - def log_destroy_action - puts 'Post destroyed' - end -end - ->> user = User.first -=> # ->> user.posts.create! -=> # ->> user.destroy -Post destroyed -=> # -``` - -条件回调 -------- - -和数据验证类似,也可以在满足指定条件时再调用回调方法。条件通过 `:if` 和 `:unless` 选项指定,选项的值可以是 Symbol、字符串、`Proc` 或数组。`:if` 选项指定什么时候调用回调。如果要指定何时不调用回调,使用 `:unless` 选项。 - -### 使用 Symbol - -:if 和 :unless 选项的值为 Symbol 时,表示要在调用回调之前执行对应的判断方法。使用 `:if` 选项时,如果判断方法返回 `false`,就不会调用回调;使用 `:unless` 选项时,如果判断方法返回 `true`,就不会调用回调。Symbol 是最常用的设置方式。使用这种方式注册回调时,可以使用多个判断方法检查是否要调用回调。 - -```ruby -class Order < ActiveRecord::Base - before_save :normalize_card_number, if: :paid_with_card? -end -``` - -### 使用字符串 - -`:if` 和 `:unless` 选项的值还可以是字符串,但必须是 RUby 代码,传入 `eval` 方法中执行。当字符串表示的条件非常短时才应该是使用这种形式。 - -```ruby -class Order < ActiveRecord::Base - before_save :normalize_card_number, if: "paid_with_card?" -end -``` - -### 使用 Proc - -`:if` 和 `:unless` 选项的值还可以是 Proc 对象。这种形式最适合用在一行代码能表示的条件上。 - -```ruby -class Order < ActiveRecord::Base - before_save :normalize_card_number, - if: Proc.new { |order| order.paid_with_card? } -end -``` - -### 回调的多重条件 - -注册条件回调时,可以同时使用 `:if` 和 `:unless` 选项: - -```ruby -class Comment < ActiveRecord::Base - after_create :send_email_to_author, if: :author_wants_emails?, - unless: Proc.new { |comment| comment.post.ignore_comments? } -end -``` - -回调类 ------- - -有时回调方法可以在其他模型中重用,我们可以将其封装在类中。 - -在下面这个例子中,我们为 `PictureFile` 模型定义了一个 `after_destroy` 回调: - -```ruby -class PictureFileCallbacks - def after_destroy(picture_file) - if File.exist?(picture_file.filepath) - File.delete(picture_file.filepath) - end - end -end -``` - -在类中定义回调方法时(如上),可把模型对象作为参数传入。然后可以在模型中使用这个回调: - -```ruby -class PictureFile < ActiveRecord::Base - after_destroy PictureFileCallbacks.new -end -``` - -注意,因为回调方法被定义成实例方法,所以要实例化 `PictureFileCallbacks`。如果回调要使用实例化对象的状态,使用这种定义方式很有用。不过,一般情况下,定义为类方法更说得通: - -```ruby -class PictureFileCallbacks - def self.after_destroy(picture_file) - if File.exist?(picture_file.filepath) - File.delete(picture_file.filepath) - end - end -end -``` - -如果按照这种方式定义回调方法,就不用实例化 `PictureFileCallbacks`: - -```ruby -class PictureFile < ActiveRecord::Base - after_destroy PictureFileCallbacks -end -``` - -在回调类中可以定义任意数量的回调方法。 - -事务回调 -------- - -还有两个回调会在数据库事务完成时触发:`after_commit` 和 `after_rollback`。这两个回调和 `after_save` 很像,只不过在数据库操作提交或回滚之前不会执行。如果模型要和数据库事务之外的系统交互,就可以使用这两个回调。 - -例如,在前面的例子中,`PictureFile` 模型中的记录删除后,还要删除相应的文件。如果执行 `after_destroy` 回调之后程序抛出了异常,事务就会回滚,文件会被删除,但模型的状态前后不一致。假设在下面的代码中,`picture_file_2` 是不合法的,那么调用 `save!` 方法会抛出异常。 - -```ruby -PictureFile.transaction do - picture_file_1.destroy - picture_file_2.save! -end -``` - -使用 `after_commit` 回调可以解决这个问题。 - -```ruby -class PictureFile < ActiveRecord::Base - after_commit :delete_picture_file_from_disk, on: [:destroy] - - def delete_picture_file_from_disk - if File.exist?(filepath) - File.delete(filepath) - end - end -end -``` - -NOTE: `:on` 选项指定什么时候出发回调。如果不设置 `:on` 选项,每各个操作都会触发回调。 - -WARNING: `after_commit` 和 `after_rollback` 回调确保模型的创建、更新和销毁等操作在事务中完成。如果这两个回调抛出了异常,会被忽略,因此不会干扰其他回调。因此,如果回调可能抛出异常,就要做适当的补救和处理。 diff --git a/source/zh-CN/active_record_migrations.md b/source/zh-CN/active_record_migrations.md deleted file mode 100644 index 4d6a53c..0000000 --- a/source/zh-CN/active_record_migrations.md +++ /dev/null @@ -1,752 +0,0 @@ -Active Record 数据库迁移 -======================= - -迁移是 Active Record 提供的一个功能,按照时间顺序管理数据库模式。使用迁移,无需编写 SQL,使用简单的 Ruby DSL 就能修改数据表。 - -读完本文,你将学到: - -* 生成迁移文件的生成器; -* Active Record 提供用来修改数据库的方法; -* 管理迁移和数据库模式的 Rake 任务; -* 迁移和 `schema.rb` 文件的关系; - --------------------------------------------------------------------------------- - -迁移简介 -------- - -迁移使用一种统一、简单的方式,按照时间顺序修改数据库的模式。迁移使用 Ruby DSL 编写,因此不用手动编写 SQL 语句,对数据库的操作和所用的数据库种类无关。 - -你可以把每个迁移看做数据库的一个修订版本。数据库中一开始什么也没有,各个迁移会添加或删除数据表、字段或记录。Active Record 知道如何按照时间线更新数据库,不管数据库现在的模式如何,都能更新到最新结构。同时,Active Record 还会更新 `db/schema.rb` 文件,匹配最新的数据库结构。 - -下面是一个迁移示例: - -```ruby -class CreateProducts < ActiveRecord::Migration - def change - create_table :products do |t| - t.string :name - t.text :description - - t.timestamps - end - end -end -``` - -这个迁移创建了一个名为 `products` 的表,然后在表中创建字符串字段 `name` 和文本字段 `description`。名为 `id` 的主键字段会被自动创建。`id` 字段是所有 Active Record 模型的默认主键。`timestamps` 方法创建两个字段:`created_at` 和 `updated_at`。如果数据表中有这两个字段,Active Record 会负责操作。 - -注意,对数据库的改动按照时间向前 推移。运行迁移之前,数据表还不存在。运行迁移后,才会创建数据表。Active Record 知道如何撤销迁移,如果回滚这次迁移,数据表会被删除。 - -在支持事务的数据库中,对模式的改动会在一个事务中执行。如果数据库不支持事务,迁移失败时,成功执行的操作将无法回滚。如要回滚,必须手动改回来。 - -NOTE: 某些查询无法在事务中运行。如果适配器支持 DDL 事务,可以在某个迁移中调用 `disable_ddl_transaction!` 方法禁用。 - -如果想在迁移中执行 Active Record 不知如何撤销的操作,可以使用 `reversible` 方法: - -```ruby -class ChangeProductsPrice < ActiveRecord::Migration - def change - reversible do |dir| - change_table :products do |t| - dir.up { t.change :price, :string } - dir.down { t.change :price, :integer } - end - end - end -end -``` - -或者不用 `change` 方法,分别使用 `up` 和 `down` 方法: - -```ruby -class ChangeProductsPrice < ActiveRecord::Migration - def up - change_table :products do |t| - t.change :price, :string - end - end - - def down - change_table :products do |t| - t.change :price, :integer - end - end -end -``` - -创建迁移 -------- - -### 单独创建迁移 - -迁移文件存储在 `db/migrate` 文件夹中,每个迁移保存在一个文件中。文件名采用 `YYYYMMDDHHMMSS_create_products.rb` 形式,即一个 UTC 时间戳后加以下划线分隔的迁移名。迁移的类名(驼峰式)要和文件名时间戳后面的部分匹配。例如,在 `20080906120000_create_products.rb` 文件中要定义 `CreateProducts` 类;在 `20080906120001_add_details_to_products.rb` 文件中要定义 `AddDetailsToProducts` 类。文件名中的时间戳决定要运行哪个迁移,以及按照什么顺序运行。从其他程序中复制迁移,或者自己生成迁移时,要注意运行的顺序。 - -自己计算时间戳不是件简单的事,所以 Active Record 提供了一个生成器: - -```bash -$ rails generate migration AddPartNumberToProducts -``` - -这个命令生成一个空的迁移,但名字已经起好了: - -```ruby -class AddPartNumberToProducts < ActiveRecord::Migration - def change - end -end -``` - -如果迁移的名字是“AddXXXToYYY”或者“RemoveXXXFromYYY”这种格式,而且后面跟着一个字段名和类型列表,那么迁移中会生成合适的 `add_column` 或 `remove_column` 语句。 - -```bash -$ rails generate migration AddPartNumberToProducts part_number:string -``` - -这个命令生成的迁移如下: - -```ruby -class AddPartNumberToProducts < ActiveRecord::Migration - def change - add_column :products, :part_number, :string - end -end -``` - -如果想为新建的字段创建添加索引,可以这么做: - -```bash -$ rails generate migration AddPartNumberToProducts part_number:string:index -``` - -这个命令生成的迁移如下: - -```ruby -class AddPartNumberToProducts < ActiveRecord::Migration - def change - add_column :products, :part_number, :string - add_index :products, :part_number - end -end -``` - -类似地,还可以生成删除字段的迁移: - -```bash -$ rails generate migration RemovePartNumberFromProducts part_number:string -``` - -这个命令生成的迁移如下: - -```ruby -class RemovePartNumberFromProducts < ActiveRecord::Migration - def change - remove_column :products, :part_number, :string - end -end -``` - -迁移生成器不单只能创建一个字段,例如: - -```bash -$ rails generate migration AddDetailsToProducts part_number:string price:decimal -``` - -生成的迁移如下: - -```ruby -class AddDetailsToProducts < ActiveRecord::Migration - def change - add_column :products, :part_number, :string - add_column :products, :price, :decimal - end -end -``` - -如果迁移名是“CreateXXX”形式,后面跟着一串字段名和类型声明,迁移就会创建名为“XXX”的表,以及相应的字段。例如: - -```bash -$ rails generate migration CreateProducts name:string part_number:string -``` - -生成的迁移如下: - -```ruby -class CreateProducts < ActiveRecord::Migration - def change - create_table :products do |t| - t.string :name - t.string :part_number - end - end -end -``` - -生成器生成的只是一些基础代码,你可以根据需要修改 `db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` 文件,增删代码。 - -在生成器中还可把字段类型设为 `references`(还可使用 `belongs_to`)。例如: - -```bash -$ rails generate migration AddUserRefToProducts user:references -``` - -生成的迁移如下: - -```ruby -class AddUserRefToProducts < ActiveRecord::Migration - def change - add_reference :products, :user, index: true - end -end -``` - -这个迁移会创建 `user_id` 字段,并建立索引。 - -如果迁移名中包含 `JoinTable`,生成器还会创建联合数据表: - -```bash -rails g migration CreateJoinTableCustomerProduct customer product -``` - -生成的迁移如下: - -```ruby -class CreateJoinTableCustomerProduct < ActiveRecord::Migration - def change - create_join_table :customers, :products do |t| - # t.index [:customer_id, :product_id] - # t.index [:product_id, :customer_id] - end - end -end -``` - -### 模型生成器 - -模型生成器和脚手架生成器会生成合适的迁移,创建模型。迁移中会包含创建所需数据表的代码。如果在生成器中指定了字段,还会生成创建字段的代码。例如,运行下面的命令: - -```bash -$ rails generate model Product name:string description:text -``` - -会生成如下的迁移: - -```ruby -class CreateProducts < ActiveRecord::Migration - def change - create_table :products do |t| - t.string :name - t.text :description - - t.timestamps - end - end -end -``` - -字段的名字和类型数量不限。 - -### 支持的类型修饰符 - -在字段类型后面,可以在花括号中添加选项。可用的修饰符如下: - -* `limit`:设置 `string/text/binary/integer` 类型字段的最大值; -* `precision`:设置 `decimal` 类型字段的精度,即数字的位数; -* `scale`:设置 `decimal` 类型字段小数点后的数字位数; -* `polymorphic`:为 `belongs_to` 关联添加 `type` 字段; -* `null`:是否允许该字段的值为 `NULL`; - -例如,执行下面的命令: - -```bash -$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic} -``` - -生成的迁移如下: - -```ruby -class AddDetailsToProducts < ActiveRecord::Migration - def change - add_column :products, :price, :decimal, precision: 5, scale: 2 - add_reference :products, :supplier, polymorphic: true, index: true - end -end -``` - -编写迁移 -------- - -使用前面介绍的生成器生成迁移后,就可以开始写代码了。 - -### 创建数据表 - -`create_table` 方法最常用,大多数时候都会由模型或脚手架生成器生成。典型的用例如下: - -```ruby -create_table :products do |t| - t.string :name -end -``` - -这个迁移会创建 `products` 数据表,在数据表中创建 `name` 字段(后面会介绍,还会自动创建 `id` 字段)。 - -默认情况下,`create_table` 方法会创建名为 `id` 的主键。通过 `:primary_key` 选项可以修改主键名(修改后别忘了修改相应的模型)。如果不想生成主键,可以传入 `id: false` 选项。如果设置数据库的选项,可以在 `:options` 选择中使用 SQL。例如: - -```ruby -create_table :products, options: "ENGINE=BLACKHOLE" do |t| - t.string :name, null: false -end -``` - -这样设置之后,会在创建数据表的 SQL 语句后面加上 `ENGINE=BLACKHOLE`。(MySQL 默认的选项是 `ENGINE=InnoDB`) - -### 创建联合数据表 - -`create_join_table` 方法用来创建 HABTM 联合数据表。典型的用例如下: - -```ruby -create_join_table :products, :categories -``` - -这段代码会创建一个名为 `categories_products` 的数据表,包含两个字段:`category_id` 和 `product_id`。这两个字段的 `:null` 选项默认情况都是 `false`,不过可在 `:column_options` 选项中设置。 - -```ruby -create_join_table :products, :categories, column_options: {null: true} -``` - -这段代码会把 `product_id` 和 `category_id` 字段的 `:null` 选项设为 `true`。 - -如果想修改数据表的名字,可以传入 `:table_name` 选项。例如: - -```ruby -create_join_table :products, :categories, table_name: :categorization -``` - -创建的数据表名为 `categorization`。 - -`create_join_table` 还可接受代码库,用来创建索引(默认无索引)或其他字段。 - -```ruby -create_join_table :products, :categories do |t| - t.index :product_id - t.index :category_id -end -``` - -### 修改数据表 - -有一个和 `create_table` 类似地方法,名为 `change_table`,用来修改现有的数据表。其用法和 `create_table` 类似,不过传入块的参数知道更多技巧。例如: - -```ruby -change_table :products do |t| - t.remove :description, :name - t.string :part_number - t.index :part_number - t.rename :upccode, :upc_code -end -``` - -这段代码删除了 `description` 和 `name` 字段,创建 `part_number` 字符串字段,并建立索引,最后重命名 `upccode` 字段。 - -### 如果帮助方法不够用 - -如果 Active Record 提供的帮助方法不够用,可以使用 `execute` 方法,执行任意的 SQL 语句: - -```ruby -Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1') -``` - -各方法的详细用法请查阅 API 文档: - -- [`ActiveRecord::ConnectionAdapters::SchemaStatements`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html):包含可在 `change`,`up` 和 `down` 中使用的方法; -- [`ActiveRecord::ConnectionAdapters::TableDefinition`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html):包含可在 `create_table` 方法的块参数上调用的方法; -- [`ActiveRecord::ConnectionAdapters::Table`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html):包含可在 `change_table` 方法的块参数上调用的方法; - -### 使用 `change` 方法 - -`change` 是迁移中最常用的方法,大多数情况下都能完成指定的操作,而且 Active Record 知道如何撤这些操作。目前,在 `change` 方法中只能使用下面的方法: - -* `add_column` -* `add_index` -* `add_reference` -* `add_timestamps` -* `create_table` -* `create_join_table` -* `drop_table`(必须提供代码块) -* `drop_join_table`(必须提供代码块) -* `remove_timestamps` -* `rename_column` -* `rename_index` -* `remove_reference` -* `rename_table` - -只要在块中不使用 `change`、`change_default` 或 `remove` 方法,`change_table` 中的操作也是可逆的。 - -如果要使用任何其他方法,可以使用 `reversible` 方法,或者不定义 `change` 方法,而分别定义 `up` 和 `down` 方法。 - -### 使用 `reversible` 方法 - -Active Record 可能不知如何撤销复杂的迁移操作,这时可以使用 `reversible` 方法指定运行迁移和撤销迁移时怎么操作。例如: - -```ruby -class ExampleMigration < ActiveRecord::Migration - def change - create_table :products do |t| - t.references :category - end - - reversible do |dir| - dir.up do - #add a foreign key - execute <<-SQL - ALTER TABLE products - ADD CONSTRAINT fk_products_categories - FOREIGN KEY (category_id) - REFERENCES categories(id) - SQL - end - dir.down do - execute <<-SQL - ALTER TABLE products - DROP FOREIGN KEY fk_products_categories - SQL - end - end - - add_column :users, :home_page_url, :string - rename_column :users, :email, :email_address - end -``` - -使用 `reversible` 方法还能确保操作按顺序执行。在上面的例子中,如果撤销迁移,`down` 代码块会在 `home_page_url` 字段删除后、`products` 数据表删除前运行。 - -有时,迁移的操作根本无法撤销,例如删除数据。这是,可以在 `down` 代码块中抛出 `ActiveRecord::IrreversibleMigration` 异常。如果有人尝试撤销迁移,会看到一个错误消息,告诉他无法撤销。 - -### 使用 `up` 和 `down` 方法 - -在迁移中可以不用 `change` 方法,而用 `up` 和 `down` 方法。`up` 方法定义要对数据库模式做哪些操作,`down` 方法用来撤销这些操作。也就是说,如果执行 `up` 后立即执行 `down`,数据库的模式应该没有任何变化。例如,在 `up` 中创建了数据表,在 `down` 方法中就要将其删除。撤销时最好按照添加的相反顺序进行。前一节中的 `reversible` 用法示例代码可以改成: - -```ruby -class ExampleMigration < ActiveRecord::Migration - def up - create_table :products do |t| - t.references :category - end - - # add a foreign key - execute <<-SQL - ALTER TABLE products - ADD CONSTRAINT fk_products_categories - FOREIGN KEY (category_id) - REFERENCES categories(id) - SQL - - add_column :users, :home_page_url, :string - rename_column :users, :email, :email_address - end - - def down - rename_column :users, :email_address, :email - remove_column :users, :home_page_url - - execute <<-SQL - ALTER TABLE products - DROP FOREIGN KEY fk_products_categories - SQL - - drop_table :products - end -end -``` - -如果迁移不可撤销,应该在 `down` 方法中抛出 `ActiveRecord::IrreversibleMigration` 异常。如果有人尝试撤销迁移,会看到一个错误消息,告诉他无法撤销。 - -### 撤销之前的迁移 - -Active Record 提供了撤销迁移的功能,通过 `revert` 方法实现: - -```ruby -require_relative '2012121212_example_migration' - -class FixupExampleMigration < ActiveRecord::Migration - def change - revert ExampleMigration - - create_table(:apples) do |t| - t.string :variety - end - end -end -``` - -`revert` 方法还可接受一个块,定义撤销操作。`revert` 方法可用来撤销以前迁移的部分操作。例如,`ExampleMigration` 已经执行,但后来觉得最好还是序列化产品列表。那么,可以编写下面的代码: - -```ruby -class SerializeProductListMigration < ActiveRecord::Migration - def change - add_column :categories, :product_list - - reversible do |dir| - dir.up do - # transfer data from Products to Category#product_list - end - dir.down do - # create Products from Category#product_list - end - end - - revert do - # copy-pasted code from ExampleMigration - create_table :products do |t| - t.references :category - end - - reversible do |dir| - dir.up do - #add a foreign key - execute <<-SQL - ALTER TABLE products - ADD CONSTRAINT fk_products_categories - FOREIGN KEY (category_id) - REFERENCES categories(id) - SQL - end - dir.down do - execute <<-SQL - ALTER TABLE products - DROP FOREIGN KEY fk_products_categories - SQL - end - end - - # The rest of the migration was ok - end - end -end -``` - -上面这个迁移也可以不用 `revert` 方法,不过步骤就多了:调换 `create_table` 和 `reversible` 的顺序,把 `create_table` 换成 `drop_table`,还要对调 `up` 和 `down` 中的代码。这些操作都可交给 `revert` 方法完成。 - -运行迁移 -------- - -Rails 提供了很多 Rake 任务,用来执行指定的迁移。 - -其中最常使用的是 `rake db:migrate`,执行还没执行的迁移中的 `change` 或 `up` 方法。如果没有未运行的迁移,直接退出。`rake db:migrate` 按照迁移文件名中时间戳顺序执行迁移。 - -注意,执行 `db:migrate` 时还会执行 `db:schema:dump`,更新 `db/schema.rb` 文件,匹配数据库的结构。 - -如果指定了版本,Active Record 会运行该版本之前的所有迁移。版本就是迁移文件名前的数字部分。例如,要运行 20080906120000 这个迁移,可以执行下面的命令: - -```bash -$ rake db:migrate VERSION=20080906120000 -``` - -如果 20080906120000 比当前的版本高,上面的命令就会执行所有 20080906120000 之前(包括 20080906120000)的迁移中的 `change` 或 `up` 方法,但不会运行 20080906120000 之后的迁移。如果回滚迁移,则会执行 20080906120000 之前(不包括 20080906120000)的迁移中的 `down` 方法。 - -### 回滚 - -还有一个常用的操作时回滚到之前的迁移。例如,迁移代码写错了,想纠正。我们无须查找迁移的版本号,直接执行下面的命令即可: - -```bash -$ rake db:rollback -``` - -这个命令会回滚上一次迁移,撤销 `change` 方法中的操作,或者执行 `down` 方法。如果想撤销多个迁移,可以使用 `STEP` 参数: - -```bash -$ rake db:rollback STEP=3 -``` - -这个命令会撤销前三次迁移。 - -`db:migrate:redo` 命令可以回滚上一次迁移,然后再次执行迁移。和 `db:rollback` 一样,如果想重做多次迁移,可以使用 `STEP` 参数。例如: - -```bash -$ rake db:migrate:redo STEP=3 -``` - -这些 Rake 任务的作用和 `db:migrate` 一样,只是用起来更方便,因为无需查找特定的迁移版本号。 - -### 搭建数据库 - -`rake db:setup` 任务会创建数据库,加载模式,并填充种子数据。 - -### 重建数据库 - -`rake db:reset` 任务会删除数据库,然后重建,等价于 `rake db:drop db:setup`。 - -NOTE: 这个任务和执行所有迁移的作用不同。`rake db:reset` 使用的是 `schema.rb` 文件中的内容。如果迁移无法回滚,`rake db:reset` 起不了作用。详细介绍参见“[导出模式](#schema-dumping-and-you)”一节。 - -### 运行指定的迁移 - -如果想执行指定迁移,或者撤销指定迁移,可以使用 `db:migrate:up` 和 `db:migrate:down` 任务,指定相应的版本号,就会根据需求调用 `change`、`up` 或 `down` 方法。例如: - -```bash -$ rake db:migrate:up VERSION=20080906120000 -``` - -这个命令会执行 20080906120000 迁移中的 `change` 方法或 `up` 方法。`db:migrate:up` 首先会检测指定的迁移是否已经运行,如果 Active Record 任务已经执行,就不会做任何操作。 - -### 在不同的环境中运行迁移 - -默认情况下,`rake db:migrate` 任务在 `development` 环境中执行。要在其他环境中运行迁移,执行命令时可以使用环境变量 `RAILS_ENV` 指定环境。例如,要在 `test` 环境中运行迁移,可以执行下面的命令: - -```bash -$ rake db:migrate RAILS_ENV=test -``` - -### 修改运行迁移时的输出 - -默认情况下,运行迁移时,会输出操作了哪些操作,以及花了多长时间。创建数据表并添加索引的迁移产生的输出如下: - -```bash -== CreateProducts: migrating ================================================= --- create_table(:products) - -> 0.0028s -== CreateProducts: migrated (0.0028s) ======================================== -``` - -在迁移中可以使用很多方法,控制输出: - -| 方法 | 作用 | -| -------------------- | ------- | -| suppress_messages | 接受一个代码块,禁止代码块中所有操作的输出 | -| say | 接受一个消息字符串作为参数,将其输出。第二个参数是布尔值,指定输出结果是否缩进 | -| say_with_time | 输出文本,以及执行代码块中操作所用时间。如果代码块的返回结果是整数,会当做操作的记录数量 | - -例如,下面这个迁移: - -```ruby -class CreateProducts < ActiveRecord::Migration - def change - suppress_messages do - create_table :products do |t| - t.string :name - t.text :description - t.timestamps - end - end - - say "Created a table" - - suppress_messages {add_index :products, :name} - say "and an index!", true - - say_with_time 'Waiting for a while' do - sleep 10 - 250 - end - end -end -``` - -输出结果是: - -```bash -== CreateProducts: migrating ================================================= --- Created a table - -> and an index! --- Waiting for a while - -> 10.0013s - -> 250 rows -== CreateProducts: migrated (10.0054s) ======================================= -``` - -如果不想让 Active Record 输出任何结果,可以使用 `rake db:migrate VERBOSE=false`。 - -修改现有的迁移 ------------- - -有时编写的迁移中可能有错误,如果已经运行了迁移,不能直接编辑迁移文件再运行迁移。Rails 认为这个迁移已经运行,所以执行 `rake db:migrate` 任务时什么也不会做。这种情况必须先回滚迁移(例如,执行 `rake db:rollback` 任务),编辑迁移文件后再执行 `rake db:migrate` 任务执行改正后的版本。 - -一般来说,直接修改现有的迁移不是个好主意。这么做会为你以及你的同事带来额外的工作量,如果这个迁移已经在生产服务器上运行过,还可能带来不必要的麻烦。你应该编写一个新的迁移,做所需的改动。编辑新生成还未纳入版本控制的迁移(或者更宽泛地说,还没有出现在开发设备之外),相对来说是安全的。 - -在新迁移中撤销之前迁移中的全部操作或者部分操作可以使用 `revert` 方法。(参见前面的 [撤销之前的迁移](#reverting-previous-migrations) 一节) - -导出模式 -------- - -### 模式文件的作用 - -迁移的作用并不是为数据库模式提供可信的参考源。`db/schema.rb` 或由 Active Record 生成的 SQL 文件才有这个作用。`db/schema.rb` 这些文件不可修改,其目的是表示数据库的当前结构。 - -部署新程序时,无需运行全部的迁移。直接加载数据库结构要简单快速得多。 - -例如,测试数据库是这样创建的:导出开发数据库的结构(存入文件 `db/schema.rb` 或 `db/structure.sql`),然后导入测试数据库。 - -模式文件还可以用来快速查看 Active Record 中有哪些属性。模型中没有属性信息,而且迁移会频繁修改属性,但是模式文件中有最终的结果。[annotate_models](https://github.com/ctran/annotate_models) gem 会在模型文件的顶部加入注释,自动添加并更新模型的模式。 - -### 导出的模式文件类型 - -导出模式有两种方法,由 `config/application.rb` 文件中的 `config.active_record.schema_format` 选项设置,可以是 `:sql` 或 `:ruby`。 - -如果设为 `:ruby`,导出的模式保存在 `db/schema.rb` 文件中。打开这个文件,你会发现内容很多,就像一个很大的迁移: - -```ruby -ActiveRecord::Schema.define(version: 20080906171750) do - create_table "authors", force: true do |t| - t.string "name" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "products", force: true do |t| - t.string "name" - t.text "description" - t.datetime "created_at" - t.datetime "updated_at" - t.string "part_number" - end -end -``` - -大多数情况下,文件的内容都是这样。这个文件使用 `create_table`、`add_index` 等方法审查数据库的结构。这个文件和使用的数据库类型无关,可以导入任何一种 Active Record 支持的数据库。如果开发的程序需要兼容多种数据库,可以使用这个文件。 - -不过 `db/schema.rb` 也有缺点:无法执行数据库的某些操作,例如外键约束,触发器,存储过程。在迁移文件中可以执行 SQL 语句,但导出模式的程序无法从数据库中重建这些语句。如果你的程序用到了前面提到的数据库操作,可以把模式文件的格式设为 `:sql`。 - -`:sql` 格式的文件不使用 Active Record 的模式导出程序,而使用数据库自带的导出工具(执行 `db:structure:dump` 任务),把数据库模式导入 `db/structure.sql` 文件。例如,PostgreSQL 使用 `pg_dump` 导出模式。如果使用 MySQL,`db/structure.sql` 文件中会出现多个 `SHOW CREATE TABLE` 用来创建数据表的语句。 - -加载模式时,只要执行其中的 SQL 语句即可。按预期,导入后会创建一个完整的数据库结构。使用 `:sql` 格式,就不能把模式导入其他类型的数据库中了。 - -### 模式导出和版本控制 - -因为导出的模式文件是数据库模式的可信源,强烈推荐将其纳入版本控制。 - -Active Record 和引用完整性 -------------------------- - -Active Record 在模型中,而不是数据库中设置关联。因此,需要在数据库中实现的功能,例如触发器、外键约束,不太常用。 - -`validates :foreign_key, uniqueness: true` 这个验证是模型保证数据完整性的一种方法。在关联中设置 `:dependent` 选项,可以保证父对象删除后,子对象也会被删除。和任何一种程序层的操作一样,这些无法完全保证引用完整性,所以很多人还是会在数据库中使用外键约束。 - -Active Record 并没有为使用这些功能提供任何工具,不过 `execute` 方法可以执行任意的 SQL 语句。还可以使用 [foreigner](https://github.com/matthuhiggins/foreigner) 等 gem,为 Active Record 添加外键支持(还能把外键导出到 `db/schema.rb` 文件)。 - -迁移和种子数据 -------------- - -有些人使用迁移把数据存入数据库: - -```ruby -class AddInitialProducts < ActiveRecord::Migration - def up - 5.times do |i| - Product.create(name: "Product ##{i}", description: "A product.") - end - end - - def down - Product.delete_all - end -end -``` - -Rails 提供了“种子”功能,可以把初始化数据存入数据库。这个功能用起来很简单,在 `db/seeds.rb` 文件中写一些 Ruby 代码,然后执行 `rake db:seed` 命令即可: - -```ruby -5.times do |i| - Product.create(name: "Product ##{i}", description: "A product.") -end -``` - -填充新建程序的数据库,使用这种方法操作起来简洁得多。 diff --git a/source/zh-CN/active_record_postgresql.md b/source/zh-CN/active_record_postgresql.md deleted file mode 100644 index a5649e3..0000000 --- a/source/zh-CN/active_record_postgresql.md +++ /dev/null @@ -1,437 +0,0 @@ -Active Record and PostgreSQL -============================ - -This guide covers PostgreSQL specific usage of Active Record. - -After reading this guide, you will know: - -* How to use PostgreSQL's datatypes. -* How to use UUID primary keys. -* How to implement full text search with PostgreSQL. -* How to back your Active Record models with database views. - --------------------------------------------------------------------------------- - -In order to use the PostgreSQL adapter you need to have at least version 8.2 -installed. Older versions are not supported. - -To get started with PostgreSQL have a look at the -[configuring Rails guide](configuring.html#configuring-a-postgresql-database). -It describes how to properly setup Active Record for PostgreSQL. - -Datatypes ---------- - -PostgreSQL offers a number of specific datatypes. Following is a list of types, -that are supported by the PostgreSQL adapter. - -### Bytea - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-binary.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-binarystring.html) - -```ruby -# db/migrate/20140207133952_create_documents.rb -create_table :documents do |t| - t.binary 'payload' -end - -# app/models/document.rb -class Document < ActiveRecord::Base -end - -# Usage -data = File.read(Rails.root + "tmp/output.pdf") -Document.create payload: data -``` - -### Array - -* [type definition](http://www.postgresql.org/docs/9.3/static/arrays.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-array.html) - -```ruby -# db/migrate/20140207133952_create_books.rb -create_table :books do |t| - t.string 'title' - t.string 'tags', array: true - t.integer 'ratings', array: true -end -add_index :books, :tags, using: 'gin' -add_index :books, :ratings, using: 'gin' - -# app/models/book.rb -class Book < ActiveRecord::Base -end - -# Usage -Book.create title: "Brave New World", - tags: ["fantasy", "fiction"], - ratings: [4, 5] - -## Books for a single tag -Book.where("'fantasy' = ANY (tags)") - -## Books for multiple tags -Book.where("tags @> ARRAY[?]::varchar[]", ["fantasy", "fiction"]) - -## Books with 3 or more ratings -Book.where("array_length(ratings, 1) >= 3") -``` - -### Hstore - -* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html) - -```ruby -# db/migrate/20131009135255_create_profiles.rb -ActiveRecord::Schema.define do - create_table :profiles do |t| - t.hstore 'settings' - end -end - -# app/models/profile.rb -class Profile < ActiveRecord::Base -end - -# Usage -Profile.create(settings: { "color" => "blue", "resolution" => "800x600" }) - -profile = Profile.first -profile.settings # => {"color"=>"blue", "resolution"=>"800x600"} - -profile.settings = {"color" => "yellow", "resolution" => "1280x1024"} -profile.save! - -## you need to call _will_change! if you are editing the store in place -profile.settings["color"] = "green" -profile.settings_will_change! -profile.save! -``` - -### JSON - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-json.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-json.html) - -```ruby -# db/migrate/20131220144913_create_events.rb -create_table :events do |t| - t.json 'payload' -end - -# app/models/event.rb -class Event < ActiveRecord::Base -end - -# Usage -Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]}) - -event = Event.first -event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]} - -## Query based on JSON document -Event.where("payload->'kind' = ?", "user_renamed") -``` - -### Range Types - -* [type definition](http://www.postgresql.org/docs/9.3/static/rangetypes.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-range.html) - -This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.1.1/Range.html) objects. - -```ruby -# db/migrate/20130923065404_create_events.rb -create_table :events do |t| - t.daterange 'duration' -end - -# app/models/event.rb -class Event < ActiveRecord::Base -end - -# Usage -Event.create(duration: Date.new(2014, 2, 11)..Date.new(2014, 2, 12)) - -event = Event.first -event.duration # => Tue, 11 Feb 2014...Thu, 13 Feb 2014 - -## All Events on a given date -Event.where("duration @> ?::date", Date.new(2014, 2, 12)) - -## Working with range bounds -event = Event. - select("lower(duration) AS starts_at"). - select("upper(duration) AS ends_at").first - -event.starts_at # => Tue, 11 Feb 2014 -event.ends_at # => Thu, 13 Feb 2014 -``` - -### Composite Types - -* [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html) - -Currently there is no special support for composite types. They are mapped to -normal text columns: - -```sql -CREATE TYPE full_address AS -( - city VARCHAR(90), - street VARCHAR(90) -); -``` - -```ruby -# db/migrate/20140207133952_create_contacts.rb -execute <<-SQL - CREATE TYPE full_address AS - ( - city VARCHAR(90), - street VARCHAR(90) - ); -SQL -create_table :contacts do |t| - t.column :address, :full_address -end - -# app/models/contact.rb -class Contact < ActiveRecord::Base -end - -# Usage -Contact.create address: "(Paris,Champs-Élysées)" -contact = Contact.first -contact.address # => "(Paris,Champs-Élysées)" -contact.address = "(Paris,Rue Basse)" -contact.save! -``` - -### Enumerated Types - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-enum.html) - -Currently there is no special support for enumerated types. They are mapped as -normal text columns: - -```ruby -# db/migrate/20131220144913_create_events.rb -execute <<-SQL - CREATE TYPE article_status AS ENUM ('draft', 'published'); -SQL -create_table :articles do |t| - t.column :status, :article_status -end - -# app/models/article.rb -class Article < ActiveRecord::Base -end - -# Usage -Article.create status: "draft" -article = Article.first -article.status # => "draft" - -article.status = "published" -article.save! -``` - -### UUID - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html) -* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html) - - -```ruby -# db/migrate/20131220144913_create_revisions.rb -create_table :revisions do |t| - t.column :identifier, :uuid -end - -# app/models/revision.rb -class Revision < ActiveRecord::Base -end - -# Usage -Revision.create identifier: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11" - -revision = Revision.first -revision.identifier # => "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" -``` - -### Bit String Types - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-bit.html) -* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-bitstring.html) - -```ruby -# db/migrate/20131220144913_create_users.rb -create_table :users, force: true do |t| - t.column :settings, "bit(8)" -end - -# app/models/device.rb -class User < ActiveRecord::Base -end - -# Usage -User.create settings: "01010011" -user = User.first -user.settings # => "(Paris,Champs-Élysées)" -user.settings = "0xAF" -user.settings # => 10101111 -user.save! -``` - -### Network Address Types - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html) - -The types `inet` and `cidr` are mapped to Ruby -[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html) -objects. The `macaddr` type is mapped to normal text. - -```ruby -# db/migrate/20140508144913_create_devices.rb -create_table(:devices, force: true) do |t| - t.inet 'ip' - t.cidr 'network' - t.macaddr 'address' -end - -# app/models/device.rb -class Device < ActiveRecord::Base -end - -# Usage -macbook = Device.create(ip: "192.168.1.12", - network: "192.168.2.0/24", - address: "32:01:16:6d:05:ef") - -macbook.ip -# => # - -macbook.network -# => # - -macbook.address -# => "32:01:16:6d:05:ef" -``` - -### Geometric Types - -* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-geometric.html) - -All geometric types, with the exception of `points` are mapped to normal text. -A point is casted to an array containing `x` and `y` coordinates. - - -UUID Primary Keys ------------------ - -NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs. - -```ruby -# db/migrate/20131220144913_create_devices.rb -enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp') -create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t| - t.string :kind -end - -# app/models/device.rb -class Device < ActiveRecord::Base -end - -# Usage -device = Device.create -device.id # => "814865cd-5a1d-4771-9306-4268f188fe9e" -``` - -Full Text Search ----------------- - -```ruby -# db/migrate/20131220144913_create_documents.rb -create_table :documents do |t| - t.string 'title' - t.string 'body' -end - -execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));" - -# app/models/document.rb -class Document < ActiveRecord::Base -end - -# Usage -Document.create(title: "Cats and Dogs", body: "are nice!") - -## all documents matching 'cat & dog' -Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)", - "cat & dog") -``` - -Database Views --------------- - -* [view creation](http://www.postgresql.org/docs/9.3/static/sql-createview.html) - -Imagine you need to work with a legacy database containing the following table: - -``` -rails_pg_guide=# \d "TBL_ART" - Table "public.TBL_ART" - Column | Type | Modifiers -------------+-----------------------------+------------------------------------------------------------ - INT_ID | integer | not null default nextval('"TBL_ART_INT_ID_seq"'::regclass) - STR_TITLE | character varying | - STR_STAT | character varying | default 'draft'::character varying - DT_PUBL_AT | timestamp without time zone | - BL_ARCH | boolean | default false -Indexes: - "TBL_ART_pkey" PRIMARY KEY, btree ("INT_ID") -``` - -This table does not follow the Rails conventions at all. -Because simple PostgreSQL views are updateable by default, -we can wrap it as follows: - -```ruby -# db/migrate/20131220144913_create_articles_view.rb -execute <<-SQL -CREATE VIEW articles AS - SELECT "INT_ID" AS id, - "STR_TITLE" AS title, - "STR_STAT" AS status, - "DT_PUBL_AT" AS published_at, - "BL_ARCH" AS archived - FROM "TBL_ART" - WHERE "BL_ARCH" = 'f' - SQL - -# app/models/article.rb -class Article < ActiveRecord::Base - self.primary_key = "id" - def archive! - update_attribute :archived, true - end -end - -# Usage -first = Article.create! title: "Winter is coming", - status: "published", - published_at: 1.year.ago -second = Article.create! title: "Brace yourself", - status: "draft", - published_at: 1.month.ago - -Article.count # => 1 -first.archive! -Article.count # => 2 -``` - -NOTE: This application only cares about non-archived `Articles`. A view also -allows for conditions so we can exclude the archived `Articles` directly. diff --git a/source/zh-CN/active_record_querying.md b/source/zh-CN/active_record_querying.md deleted file mode 100644 index e064823..0000000 --- a/source/zh-CN/active_record_querying.md +++ /dev/null @@ -1,1727 +0,0 @@ -Active Record 查询 -================== - -本文介绍使用 Active Record 从数据库中获取数据的不同方法。 - -读完本文,你将学到: - -* 如何使用各种方法查找满足条件的记录; -* 如何指定查找记录的排序方式,获取哪些属性,分组等; -* 获取数据时如何使用按需加载介绍数据库查询数; -* 如何使用动态查询方法; -* 如何检查某个记录是否存在; -* 如何在 Active Record 模型中做各种计算; -* 如何执行 EXPLAIN 命令; - --------------------------------------------------------------------------------- - -如果习惯使用 SQL 查询数据库,会发现在 Rails 中执行相同的查询有更好的方式。大多数情况下,在 Active Record 中无需直接使用 SQL。 - -文中的实例代码会用到下面一个或多个模型: - -TIP: 下面所有的模型除非有特别说明之外,都使用 `id` 做主键。 - -```ruby -class Client < ActiveRecord::Base - has_one :address - has_many :orders - has_and_belongs_to_many :roles -end -``` - -```ruby -class Address < ActiveRecord::Base - belongs_to :client -end -``` - -```ruby -class Order < ActiveRecord::Base - belongs_to :client, counter_cache: true -end -``` - -```ruby -class Role < ActiveRecord::Base - has_and_belongs_to_many :clients -end -``` - -Active Record 会代你执行数据库查询,可以兼容大多数数据库(MySQL,PostgreSQL 和 SQLite 等)。不管使用哪种数据库,所用的 Active Record 方法都是一样的。 - -从数据库中获取对象 ---------------- - -Active Record 提供了很多查询方法,用来从数据库中获取对象。每个查询方法都接可接受参数,不用直接写 SQL 就能在数据库中执行指定的查询。 - -这些方法是: - -* `find` -* `create_with` -* `distinct` -* `eager_load` -* `extending` -* `from` -* `group` -* `having` -* `includes` -* `joins` -* `limit` -* `lock` -* `none` -* `offset` -* `order` -* `preload` -* `readonly` -* `references` -* `reorder` -* `reverse_order` -* `select` -* `uniq` -* `where` - -上述所有方法都返回一个 `ActiveRecord::Relation` 实例。 - -`Model.find(options)` 方法执行的主要操作概括如下: - -* 把指定的选项转换成等价的 SQL 查询语句; -* 执行 SQL 查询,从数据库中获取结果; -* 为每个查询结果实例化一个对应的模型对象; -* 如果有 `after_find` 回调,再执行 `after_find` 回调; - -### 获取单个对象 - -在 Active Record 中获取单个对象有好几种方法。 - -#### 使用主键 - -使用 `Model.find(primary_key)` 方法可以获取指定主键对应的对象。例如: - -```ruby -# Find the client with primary key (id) 10. -client = Client.find(10) -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1 -``` - -如果未找到匹配的记录,`Model.find(primary_key)` 会抛出 `ActiveRecord::RecordNotFound` 异常。 - -#### `take` - -`Model.take` 方法会获取一个记录,不考虑任何顺序。例如: - -```ruby -client = Client.take -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients LIMIT 1 -``` - -如果没找到记录,`Model.take` 不会抛出异常,而是返回 `nil`。 - -TIP: 获取的记录根据所用的数据库引擎会有所不同。 - -#### `first` - -`Model.first` 获取按主键排序得到的第一个记录。例如: - -```ruby -client = Client.first -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 -``` - -`Model.first` 如果没找到匹配的记录,不会抛出异常,而是返回 `nil`。 - -#### `last` - -`Model.last` 获取按主键排序得到的最后一个记录。例如: - -```ruby -client = Client.last -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 -``` - -`Model.last` 如果没找到匹配的记录,不会抛出异常,而是返回 `nil`。 - -#### `find_by` - -`Model.find_by` 获取满足条件的第一个记录。例如: - -```ruby -Client.find_by first_name: 'Lifo' -# => # - -Client.find_by first_name: 'Jon' -# => nil -``` - -等价于: - -```ruby -Client.where(first_name: 'Lifo').take -``` - -#### `take!` - -`Model.take!` 方法会获取一个记录,不考虑任何顺序。例如: - -```ruby -client = Client.take! -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients LIMIT 1 -``` - -如果未找到匹配的记录,`Model.take!` 会抛出 `ActiveRecord::RecordNotFound` 异常。 - -#### `first!` - -`Model.first!` 获取按主键排序得到的第一个记录。例如: - -```ruby -client = Client.first! -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1 -``` - -如果未找到匹配的记录,`Model.first!` 会抛出 `ActiveRecord::RecordNotFound` 异常。 - -#### `last!` - -`Model.last!` 获取按主键排序得到的最后一个记录。例如: - -```ruby -client = Client.last! -# => # -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 -``` - -如果未找到匹配的记录,`Model.last!` 会抛出 `ActiveRecord::RecordNotFound` 异常。 - -#### `find_by!` - -`Model.find_by!` 获取满足条件的第一个记录。如果没找到匹配的记录,会抛出 `ActiveRecord::RecordNotFound` 异常。例如: - -```ruby -Client.find_by! first_name: 'Lifo' -# => # - -Client.find_by! first_name: 'Jon' -# => ActiveRecord::RecordNotFound -``` - -等价于: - -```ruby -Client.where(first_name: 'Lifo').take! -``` - -### 获取多个对象 - -#### 使用多个主键 - -`Model.find(array_of_primary_key)` 方法可接受一个由主键组成的数组,返回一个由主键对应记录组成的数组。例如: - -```ruby -# Find the clients with primary keys 1 and 10. -client = Client.find([1, 10]) # Or even Client.find(1, 10) -# => [#, #] -``` - -上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients WHERE (clients.id IN (1,10)) -``` - -WARNING: 只要有一个主键的对应的记录未找到,`Model.find(array_of_primary_key)` 方法就会抛出 `ActiveRecord::RecordNotFound` 异常。 - -#### take - -`Model.take(limit)` 方法获取 `limit` 个记录,不考虑任何顺序: - -```ruby -Client.take(2) -# => [#, - #] -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients LIMIT 2 -``` - -#### first - -`Model.first(limit)` 方法获取按主键排序的前 `limit` 个记录: - -```ruby -Client.first(2) -# => [#, - #] -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients ORDER BY id ASC LIMIT 2 -``` - -#### last - -`Model.last(limit)` 方法获取按主键降序排列的前 `limit` 个记录: - -```ruby -Client.last(2) -# => [#, - #] -``` - -和上述方法等价的 SQL 查询是: - -```sql -SELECT * FROM clients ORDER BY id DESC LIMIT 2 -``` - -### 批量获取多个对象 - -我们经常需要遍历由很多记录组成的集合,例如给大量用户发送邮件列表,或者导出数据。 - -我们可能会直接写出如下的代码: - -```ruby -# This is very inefficient when the users table has thousands of rows. -User.all.each do |user| - NewsLetter.weekly_deliver(user) -end -``` - -但这种方法在数据表很大时就有点不现实了,因为 `User.all.each` 会一次读取整个数据表,一行记录创建一个模型对象,然后把整个模型对象数组存入内存。如果记录数非常多,可能会用完内存。 - -Rails 为了解决这种问题提供了两个方法,把记录分成几个批次,不占用过多内存。第一个方法是 `find_each`,获取一批记录,然后分别把每个记录传入代码块。第二个方法是 `find_in_batches`,获取一批记录,然后把整批记录作为数组传入代码块。 - -TIP: `find_each` 和 `find_in_batches` 方法的目的是分批处理无法一次载入内存的巨量记录。如果只想遍历几千个记录,更推荐使用常规的查询方法。 - -#### `find_each` - -`find_each` 方法获取一批记录,然后分别把每个记录传入代码块。在下面的例子中,`find_each` 获取 1000 个记录,然后把每个记录传入代码块,直到所有记录都处理完为止: - -```ruby -User.find_each do |user| - NewsLetter.weekly_deliver(user) -end -``` - -##### `find_each` 方法的选项 - -在 `find_each` 方法中可使用 `find` 方法的大多数选项,但不能使用 `:order` 和 `:limit`,因为这两个选项是保留给 `find_each` 内部使用的。 - -`find_each` 方法还可使用另外两个选项:`:batch_size` 和 `:start`。 - -**`:batch_size`** - -`:batch_size` 选项指定在把各记录传入代码块之前,各批次获取的记录数量。例如,一个批次获取 5000 个记录: - -```ruby -User.find_each(batch_size: 5000) do |user| - NewsLetter.weekly_deliver(user) -end -``` - -**`:start`** - -默认情况下,按主键的升序方式获取记录,其中主键的类型必须是整数。如果不想用最小的 ID,可以使用 `:start` 选项指定批次的起始 ID。例如,前面的批量处理中断了,但保存了中断时的 ID,就可以使用这个选项继续处理。 - -例如,在有 5000 个记录的批次中,只向主键大于 2000 的用户发送邮件列表,可以这么做: - -```ruby -User.find_each(start: 2000, batch_size: 5000) do |user| - NewsLetter.weekly_deliver(user) -end -``` - -还有一个例子是,使用多个 worker 处理同一个进程队列。如果需要每个 worker 处理 10000 个记录,就可以在每个 worker 中设置相应的 `:start` 选项。 - -#### `find_in_batches` - -`find_in_batches` 方法和 `find_each` 类似,都获取一批记录。二者的不同点是,`find_in_batches` 把整批记录作为一个数组传入代码块,而不是单独传入各记录。在下面的例子中,会把 1000 个单据一次性传入代码块,让代码块后面的程序处理剩下的单据: - -```ruby -# Give add_invoices an array of 1000 invoices at a time -Invoice.find_in_batches(include: :invoice_lines) do |invoices| - export.add_invoices(invoices) -end -``` - -NOTE: `:include` 选项可以让指定的关联和模型一同加载。 - -##### `find_in_batches` 方法的选项 - -`find_in_batches` 方法和 `find_each` 方法一样,可以使用 `:batch_size` 和 `:start` 选项,还可使用常规的 `find` 方法中的大多数选项,但不能使用 `:order` 和 `:limit` 选项,因为这两个选项保留给 `find_in_batches` 方法内部使用。 - -条件查询 -------- - -`where` 方法用来指定限制获取记录的条件,用于 SQL 语句的 `WHERE` 子句。条件可使用字符串、数组或 Hash 指定。 - -### 纯字符串条件 - -如果查询时要使用条件,可以直接指定。例如 `Client.where("orders_count = '2'")`,获取 `orders_count` 字段为 `2` 的客户记录。 - -WARNING: 使用纯字符串指定条件可能导致 SQL 注入漏洞。例如,`Client.where("first_name LIKE '%#{params[:first_name]}%'")`,这里的条件就不安全。推荐使用的条件指定方式是数组,请阅读下一节。 - -### 数组条件 - -如果数字是在别处动态生成的话应该怎么处理呢?可用下面的查询: - -```ruby -Client.where("orders_count = ?", params[:orders]) -``` - -Active Record 会先处理第一个元素中的条件,然后使用后续元素替换第一个元素中的问号(`?`)。 - -指定多个条件的方式如下: - -```ruby -Client.where("orders_count = ? AND locked = ?", params[:orders], false) -``` - -在这个例子中,第一个问号会替换成 `params[:orders]` 的值;第二个问号会替换成 `false` 在 SQL 中对应的值,具体的值视所用的适配器而定。 - -下面这种形式 - -```ruby -Client.where("orders_count = ?", params[:orders]) -``` - -要比这种形式好 - -```ruby -Client.where("orders_count = #{params[:orders]}") -``` - -因为前者传入的参数更安全。直接在条件字符串中指定的条件会原封不动的传给数据库。也就是说,即使用户不怀好意,条件也会转义。如果这么做,整个数据库就处在一个危险境地,只要用户发现可以接触数据库,就能做任何想做的事。所以,千万别直接在条件字符串中使用参数。 - -TIP: 关于 SQL 注入更详细的介绍,请阅读“[Ruby on Rails 安全指南](security.html#sql-injection)” - -#### 条件中的占位符 - -除了使用问号占位之外,在数组条件中还可使用键值对 Hash 形式的占位符: - -```ruby -Client.where("created_at >= :start_date AND created_at <= :end_date", - {start_date: params[:start_date], end_date: params[:end_date]}) -``` - -如果条件中有很多参数,使用这种形式可读性更高。 - -### Hash 条件 - -Active Record 还允许使用 Hash 条件,提高条件语句的可读性。使用 Hash 条件时,传入 Hash 的键是要设定条件的字段,值是要设定的条件。 - -NOTE: 在 Hash 条件中只能指定相等。范围和子集这三种条件。 - -#### 相等 - -```ruby -Client.where(locked: true) -``` - -字段的名字还可使用字符串表示: - -```ruby -Client.where('locked' => true) -``` - -在 `belongs_to` 关联中,如果条件中的值是模型对象,可用关联键表示。这种条件指定方式也可用于多态关联。 - -```ruby -Post.where(author: author) -Author.joins(:posts).where(posts: { author: author }) -``` - -NOTE: 条件的值不能为 Symbol。例如,不能这么指定条件:`Client.where(status: :active)`。 - -#### 范围 - -```ruby -Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) -``` - -指定这个条件后,会使用 SQL `BETWEEN` 子句查询昨天创建的客户: - -```sql -SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') -``` - -这段代码演示了[数组条件](#array-conditions)的简写形式。 - -#### 子集 - -如果想使用 `IN` 子句查询记录,可以在 Hash 条件中使用数组: - -```ruby -Client.where(orders_count: [1,3,5]) -``` - -上述代码生成的 SQL 语句如下: - -```sql -SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) -``` - -### `NOT` 条件 - -SQL `NOT` 查询可用 `where.not` 方法构建。 - -```ruby -Post.where.not(author: author) -``` - -也即是说,这个查询首先调用没有参数的 `where` 方法,然后再调用 `not` 方法。 - -排序 ----- - -要想按照特定的顺序从数据库中获取记录,可以使用 `order` 方法。 - -例如,想按照 `created_at` 的升序方式获取一些记录,可以这么做: - -```ruby -Client.order(:created_at) -# OR -Client.order("created_at") -``` - -还可使用 `ASC` 或 `DESC` 指定排序方式: - -```ruby -Client.order(created_at: :desc) -# OR -Client.order(created_at: :asc) -# OR -Client.order("created_at DESC") -# OR -Client.order("created_at ASC") -``` - -或者使用多个字段排序: - -```ruby -Client.order(orders_count: :asc, created_at: :desc) -# OR -Client.order(:orders_count, created_at: :desc) -# OR -Client.order("orders_count ASC, created_at DESC") -# OR -Client.order("orders_count ASC", "created_at DESC") -``` - -如果想在不同的上下文中多次调用 `order`,可以在前一个 `order` 后再调用一次: - -```ruby -Client.order("orders_count ASC").order("created_at DESC") -# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC -``` - -查询指定字段 ------------ - -默认情况下,`Model.find` 使用 `SELECT *` 查询所有字段。 - -要查询部分字段,可使用 `select` 方法。 - -例如,只查询 `viewable_by` 和 `locked` 字段: - -```ruby -Client.select("viewable_by, locked") -``` - -上述查询使用的 SQL 语句如下: - -```sql -SELECT viewable_by, locked FROM clients -``` - -使用时要注意,因为模型对象只会使用选择的字段初始化。如果字段不能初始化模型对象,会得到以下异常: - -```bash -ActiveModel::MissingAttributeError: missing attribute: -``` - -其中 `` 是所查询的字段。`id` 字段不会抛出 `ActiveRecord::MissingAttributeError` 异常,所以在关联中使用时要注意,因为关联需要 `id` 字段才能正常使用。 - -如果查询时希望指定字段的同值记录只出现一次,可以使用 `distinct` 方法: - -```ruby -Client.select(:name).distinct -``` - -上述方法生成的 SQL 语句如下: - -```sql -SELECT DISTINCT name FROM clients -``` - -查询后还可以删除唯一性限制: - -```ruby -query = Client.select(:name).distinct -# => Returns unique names - -query.distinct(false) -# => Returns all names, even if there are duplicates -``` - -限量和偏移 ---------- - -要想在 `Model.find` 方法中使用 SQL `LIMIT` 子句,可使用 `limit` 和 `offset` 方法。 - -`limit` 方法指定获取的记录数量,`offset` 方法指定在返回结果之前跳过多少个记录。例如: - -```ruby -Client.limit(5) -``` - -上述查询最大只会返回 5 各客户对象,因为没指定偏移,多以会返回数据表中的前 5 个记录。生成的 SQL 语句如下: - -```sql -SELECT * FROM clients LIMIT 5 -``` - -再加上 `offset` 方法: - -```ruby -Client.limit(5).offset(30) -``` - -这时会从第 31 个记录开始,返回最多 5 个客户对象。生成的 SQL 语句如下: - -```sql -SELECT * FROM clients LIMIT 5 OFFSET 30 -``` - -分组 ----- - -要想在查询时使用 SQL `GROUP BY` 子句,可以使用 `group` 方法。 - -例如,如果想获取一组订单的创建日期,可以这么做: - -```ruby -Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)") -``` - -上述查询会只会为相同日期下的订单创建一个 `Order` 对象。 - -生成的 SQL 语句如下: - -```sql -SELECT date(created_at) as ordered_date, sum(price) as total_price -FROM orders -GROUP BY date(created_at) -``` - -分组筛选 -------- - -SQL 使用 `HAVING` 子句指定 `GROUP BY` 分组的条件。在 `Model.find` 方法中可使用 `:having` 选项指定 `HAVING` 子句。 - -例如: - -```ruby -Order.select("date(created_at) as ordered_date, sum(price) as total_price"). - group("date(created_at)").having("sum(price) > ?", 100) -``` - -生成的 SQL 如下: - -```sql -SELECT date(created_at) as ordered_date, sum(price) as total_price -FROM orders -GROUP BY date(created_at) -HAVING sum(price) > 100 -``` - -这个查询只会为同一天下的订单创建一个 `Order` 对象,而且这一天的订单总额要大于 $100。 - -条件覆盖 -------- - -### `unscope` - -如果要删除某个条件可使用 `unscope` 方法。例如: - -```ruby -Post.where('id > 10').limit(20).order('id asc').unscope(:order) -``` - -生成的 SQL 语句如下: - -```sql -SELECT * FROM posts WHERE id > 10 LIMIT 20 - -# Original query without `unscope` -SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 -``` - -`unscope` 还可删除 `WHERE` 子句中的条件。例如: - -```ruby -Post.where(id: 10, trashed: false).unscope(where: :id) -# SELECT "posts".* FROM "posts" WHERE trashed = 0 -``` - -`unscope` 还可影响合并后的查询: - -```ruby -Post.order('id asc').merge(Post.unscope(:order)) -# SELECT "posts".* FROM "posts" -``` - -### `only` - -查询条件还可使用 `only` 方法覆盖。例如: - -```ruby -Post.where('id > 10').limit(20).order('id desc').only(:order, :where) -``` - -执行的 SQL 语句如下: - -```sql -SELECT * FROM posts WHERE id > 10 ORDER BY id DESC - -# Original query without `only` -SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 -``` - -### `reorder` - -`reorder` 方法覆盖原来的 `order` 条件。例如: - -```ruby -class Post < ActiveRecord::Base - .. - .. - has_many :comments, -> { order('posted_at DESC') } -end - -Post.find(10).comments.reorder('name') -``` - -执行的 SQL 语句如下: - -```sql -SELECT * FROM posts WHERE id = 10 ORDER BY name -``` - -没用 `reorder` 方法时执行的 SQL 语句如下: - -```sql -SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC -``` - -### `reverse_order` - -`reverse_order` 方法翻转 `ORDER` 子句的条件。 - -```ruby -Client.where("orders_count > 10").order(:name).reverse_order -``` - -执行的 SQL 语句如下: - -```sql -SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC -``` - -如果查询中没有使用 `ORDER` 子句,`reverse_order` 方法会按照主键的逆序查询: - -```ruby -Client.where("orders_count > 10").reverse_order -``` - -执行的 SQL 语句如下: - -```sql -SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC -``` - -这个方法**没有**参数。 - -### `rewhere` - -`rewhere` 方法覆盖前面的 `where` 条件。例如: - -```ruby -Post.where(trashed: true).rewhere(trashed: false) -``` - -执行的 SQL 语句如下: - -```sql -SELECT * FROM posts WHERE `trashed` = 0 -``` - -如果不使用 `rewhere` 方法,写成: - -```ruby -Post.where(trashed: true).where(trashed: false) -``` - -执行的 SQL 语句如下: - -```sql -SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0 -``` - -空关系 ------- - -`none` 返回一个可链接的关系,没有相应的记录。`none` 方法返回对象的后续条件查询,得到的还是空关系。如果想以可链接的方式响应可能无返回结果的方法或者作用域,可使用 `none` 方法。 - -```ruby -Post.none # returns an empty Relation and fires no queries. -``` - -```ruby -# The visible_posts method below is expected to return a Relation. -@posts = current_user.visible_posts.where(name: params[:name]) - -def visible_posts - case role - when 'Country Manager' - Post.where(country: country) - when 'Reviewer' - Post.published - when 'Bad User' - Post.none # => returning [] or nil breaks the caller code in this case - end -end -``` - -只读对象 -------- - -Active Record 提供了 `readonly` 方法,禁止修改获取的对象。试图修改只读记录的操作不会成功,而且会抛出 `ActiveRecord::ReadOnlyRecord` 异常。 - -```ruby -client = Client.readonly.first -client.visits += 1 -client.save -``` - -因为把 `client` 设为了只读对象,所以上述代码调用 `client.save` 方法修改 `visits` 的值时会抛出 `ActiveRecord::ReadOnlyRecord` 异常。 - -更新时锁定记录 ------------- - -锁定可以避免更新记录时的条件竞争,也能保证原子更新。 - -Active Record 提供了两种锁定机制: - -* 乐观锁定 -* 悲观锁定 - -### 乐观锁定 - -乐观锁定允许多个用户编辑同一个记录,假设数据发生冲突的可能性最小。Rails 会检查读取记录后是否有其他程序在修改这个记录。如果检测到有其他程序在修改,就会抛出 `ActiveRecord::StaleObjectError` 异常,忽略改动。 - -**乐观锁定字段** - -为了使用乐观锁定,数据表中要有一个类型为整数的 `lock_version` 字段。每次更新记录时,Active Record 都会增加 `lock_version` 字段的值。如果更新请求中的 `lock_version` 字段值比数据库中的 `lock_version` 字段值小,会抛出 `ActiveRecord::StaleObjectError` 异常,更新失败。例如: - -```ruby -c1 = Client.find(1) -c2 = Client.find(1) - -c1.first_name = "Michael" -c1.save - -c2.name = "should fail" -c2.save # Raises an ActiveRecord::StaleObjectError -``` - -抛出异常后,你要负责处理冲突,可以回滚操作、合并操作或者使用其他业务逻辑处理。 - -乐观锁定可以使用 `ActiveRecord::Base.lock_optimistically = false` 关闭。 - -要想修改 `lock_version` 字段的名字,可以使用 `ActiveRecord::Base` 提供的 `locking_column` 类方法: - -```ruby -class Client < ActiveRecord::Base - self.locking_column = :lock_client_column -end -``` - -### 悲观锁定 - -悲观锁定使用底层数据库提供的锁定机制。使用 `lock` 方法构建的关系在所选记录上生成一个“互斥锁”(exclusive lock)。使用 `lock` 方法构建的关系一般都放入事务中,避免死锁。 - -例如: - -```ruby -Item.transaction do - i = Item.lock.first - i.name = 'Jones' - i.save -end -``` - -在 MySQL 中,上述代码生成的 SQL 如下: - -```sql -SQL (0.2ms) BEGIN -Item Load (0.3ms) SELECT * FROM `items` LIMIT 1 FOR UPDATE -Item Update (0.4ms) UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1 -SQL (0.8ms) COMMIT -``` - -`lock` 方法还可以接受 SQL 语句,使用其他锁定类型。例如,MySQL 中有一个语句是 `LOCK IN SHARE MODE`,会锁定记录,但还是允许其他查询读取记录。要想使用这个语句,直接传入 `lock` 方法即可: - -```ruby -Item.transaction do - i = Item.lock("LOCK IN SHARE MODE").find(1) - i.increment!(:views) -end -``` - -如果已经创建了模型实例,可以在事务中加上这种锁定,如下所示: - -```ruby -item = Item.first -item.with_lock do - # This block is called within a transaction, - # item is already locked. - item.increment!(:views) -end -``` - -连接数据表 ---------- - -Active Record 提供了一个查询方法名为 `joins`,用来指定 SQL `JOIN` 子句。`joins` 方法的用法有很多种。 - -### 使用字符串形式的 SQL 语句 - -在 `joins` 方法中可以直接使用 `JOIN` 子句的 SQL: - -```ruby -Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') -``` - -生成的 SQL 语句如下: - -```sql -SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id -``` - -### 使用数组或 Hash 指定具名关联 - -WARNING: 这种方法只用于 `INNER JOIN`。 - -使用 `joins` 方法时,可以使用声明[关联](association_basics.html)时使用的关联名指定 `JOIN` 子句。 - -例如,假如按照如下方式定义 `Category`、`Post`、`Comment`、`Guest` 和 `Tag` 模型: - -```ruby -class Category < ActiveRecord::Base - has_many :posts -end - -class Post < ActiveRecord::Base - belongs_to :category - has_many :comments - has_many :tags -end - -class Comment < ActiveRecord::Base - belongs_to :post - has_one :guest -end - -class Guest < ActiveRecord::Base - belongs_to :comment -end - -class Tag < ActiveRecord::Base - belongs_to :post -end -``` - -下面各种用法能都使用 `INNER JOIN` 子句生成正确的连接查询: - -#### 连接单个关联 - -```ruby -Category.joins(:posts) -``` - -生成的 SQL 语句如下: - -```sql -SELECT categories.* FROM categories - INNER JOIN posts ON posts.category_id = categories.id -``` - -用人类语言表达,上述查询的意思是,“使用文章的分类创建分类对象”。注意,分类对象可能有重复,因为多篇文章可能属于同一分类。如果不想出现重复,可使用 `Category.joins(:posts).uniq` 方法。 - -#### 连接多个关联 - -```ruby -Post.joins(:category, :comments) -``` - -生成的 SQL 语句如下: - -```sql -SELECT posts.* FROM posts - INNER JOIN categories ON posts.category_id = categories.id - INNER JOIN comments ON comments.post_id = posts.id -``` - -用人类语言表达,上述查询的意思是,“返回指定分类且至少有一个评论的所有文章”。注意,如果文章有多个评论,同个文章对象会出现多次。 - -#### 连接一层嵌套关联 - -```ruby -Post.joins(comments: :guest) -``` - -生成的 SQL 语句如下: - -```sql -SELECT posts.* FROM posts - INNER JOIN comments ON comments.post_id = posts.id - INNER JOIN guests ON guests.comment_id = comments.id -``` - -用人类语言表达,上述查询的意思是,“返回有一个游客发布评论的所有文章”。 - -#### 连接多层嵌套关联 - -```ruby -Category.joins(posts: [{ comments: :guest }, :tags]) -``` - -生成的 SQL 语句如下: - -```sql -SELECT categories.* FROM categories - INNER JOIN posts ON posts.category_id = categories.id - INNER JOIN comments ON comments.post_id = posts.id - INNER JOIN guests ON guests.comment_id = comments.id - INNER JOIN tags ON tags.post_id = posts.id -``` - -### 指定用于连接数据表上的条件 - -作用在连接数据表上的条件可以使用[数组](#array-conditions)和[字符串](#pure-string-conditions)指定。[Hash 形式的条件]((#hash-conditions)使用的句法有点特殊: - -```ruby -time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.joins(:orders).where('orders.created_at' => time_range) -``` - -还有一种更简洁的句法是使用嵌套 Hash: - -```ruby -time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.joins(:orders).where(orders: { created_at: time_range }) -``` - -上述查询会获取昨天下订单的所有客户对象,再次用到了 SQL `BETWEEN` 语句。 - -按需加载关联 ------------ - -使用 `Model.find` 方法获取对象的关联记录时,按需加载机制会使用尽量少的查询次数。 - -**N + 1 查询问题** - -假设有如下的代码,获取 10 个客户对象,并把客户的邮编打印出来 - -```ruby -clients = Client.limit(10) - -clients.each do |client| - puts client.address.postcode -end -``` - -上述代码初看起来很好,但问题在于查询的总次数。上述代码总共会执行 1(获取 10 个客户记录)+ 10(分别获取 10 个客户的地址)= *11* 次查询。 - -**N + 1 查询的解决办法** - -在 Active Record 中可以进一步指定要加载的所有关联,调用 `Model.find` 方法是使用 `includes` 方法实现。使用 `includes` 后,Active Record 会使用尽可能少的查询次数加载所有指定的关联。 - -我们可以使用按需加载机制加载客户的地址,把 `Client.limit(10)` 改写成: - -```ruby -clients = Client.includes(:address).limit(10) - -clients.each do |client| - puts client.address.postcode -end -``` - -和前面的 **11** 次查询不同,上述代码只会执行 **2** 次查询: - -```sql -SELECT * FROM clients LIMIT 10 -SELECT addresses.* FROM addresses - WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10)) -``` - -### 按需加载多个关联 - -调用 `Model.find` 方法时,使用 `includes` 方法可以一次加载任意数量的关联,加载的关联可以通过数组、Hash、嵌套 Hash 指定。 - -#### 用数组指定多个关联 - -```ruby -Post.includes(:category, :comments) -``` - -上述代码会加载所有文章,以及和每篇文章关联的分类和评论。 - -#### 使用 Hash 指定嵌套关联 - -```ruby -Category.includes(posts: [{ comments: :guest }, :tags]).find(1) -``` - -上述代码会获取 ID 为 1 的分类,按需加载所有关联的文章,文章的标签和评论,以及每个评论的 `guest` 关联。 - -### 指定用于按需加载关联上的条件 - -虽然 Active Record 允许使用 `joins` 方法指定用于按需加载关联上的条件,但是推荐的做法是使用[连接数据表](#joining-tables)。 - -如果非要这么做,可以按照常规方式使用 `where` 方法。 - -```ruby -Post.includes(:comments).where("comments.visible" => true) -``` - -上述代码生成的查询中会包含 `LEFT OUTER JOIN` 子句,而 `joins` 方法生成的查询使用的是 `INNER JOIN` 子句。 - -```ruby -SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1) -``` - -如果没指定 `where` 条件,上述代码会生成两个查询语句。 - -如果像上面的代码一样使用 `includes`,即使所有文章都没有评论,也会加载所有文章。使用 `joins` 方法(`INNER JOIN`)时,必须满足连接条件,否则不会得到任何记录。 - -作用域 ------- - -作用域把常用的查询定义成方法,在关联对象或模型上调用。在作用域中可以使用前面介绍的所有方法,例如 `where`、`joins` 和 `includes`。所有作用域方法都会返回一个 `ActiveRecord::Relation` 对象,允许继续调用其他方法(例如另一个作用域方法)。 - -要想定义简单的作用域,可在类中调用 `scope` 方法,传入执行作用域时运行的代码: - -```ruby -class Post < ActiveRecord::Base - scope :published, -> { where(published: true) } -end -``` - -上述方式和直接定义类方法的作用一样,使用哪种方式只是个人喜好: - -```ruby -class Post < ActiveRecord::Base - def self.published - where(published: true) - end -end -``` - -作用域可以链在一起调用: - -```ruby -class Post < ActiveRecord::Base - scope :published, -> { where(published: true) } - scope :published_and_commented, -> { published.where("comments_count > 0") } -end -``` - -可以在模型类上调用 `published` 作用域: - -```ruby -Post.published # => [published posts] -``` - -也可以在包含 `Post` 对象的关联上调用: - -```ruby -category = Category.first -category.posts.published # => [published posts belonging to this category] -``` - -### 传入参数 - -作用域可接受参数: - -```ruby -class Post < ActiveRecord::Base - scope :created_before, ->(time) { where("created_at < ?", time) } -end -``` - -作用域的调用方法和类方法一样: - -```ruby -Post.created_before(Time.zone.now) -``` - -不过这就和类方法的作用一样了。 - -```ruby -class Post < ActiveRecord::Base - def self.created_before(time) - where("created_at < ?", time) - end -end -``` - -如果作用域要接受参数,推荐直接使用类方法。有参数的作用域也可在关联对象上调用: - -```ruby -category.posts.created_before(time) -``` - -### 合并作用域 - -和 `where` 方法一样,作用域也可通过 `AND` 合并查询条件: - -```ruby -class User < ActiveRecord::Base - scope :active, -> { where state: 'active' } - scope :inactive, -> { where state: 'inactive' } -end - -User.active.inactive -# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive' -``` - -作用域还可以 `where` 一起使用,生成的 SQL 语句会使用 `AND` 连接所有条件。 - -```ruby -User.active.where(state: 'finished') -# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' -``` - -如果不想让最后一个 `WHERE` 子句获得优先权,可以使用 `Relation#merge` 方法。 - -```ruby -User.active.merge(User.inactive) -# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive' -``` - -使用作用域时要注意,`default_scope` 会添加到作用域和 `where` 方法指定的条件之前。 - -```ruby -class User < ActiveRecord::Base - default_scope { where state: 'pending' } - scope :active, -> { where state: 'active' } - scope :inactive, -> { where state: 'inactive' } -end - -User.all -# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' - -User.active -# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active' - -User.where(state: 'inactive') -# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive' -``` - -如上所示,`default_scope` 中的条件添加到了 `active` 和 `where` 之前。 - -### 指定默认作用域 - -如果某个作用域要用在模型的所有查询中,可以在模型中使用 `default_scope` 方法指定。 - -```ruby -class Client < ActiveRecord::Base - default_scope { where("removed_at IS NULL") } -end -``` - -执行查询时使用的 SQL 语句如下: - -```sql -SELECT * FROM clients WHERE removed_at IS NULL -``` - -如果默认作用域中的条件比较复杂,可以使用类方法的形式定义: - -```ruby -class Client < ActiveRecord::Base - def self.default_scope - # Should return an ActiveRecord::Relation. - end -end -``` - -### 删除所有作用域 - -如果基于某些原因想删除作用域,可以使用 `unscoped` 方法。如果模型中定义了 `default_scope`,而在这个作用域中不需要使用,就可以使用 `unscoped` 方法。 - -```ruby -Client.unscoped.load -``` - -`unscoped` 方法会删除所有作用域,在数据表中执行常规查询。 - -注意,不能在作用域后链式调用 `unscoped`,这时可以使用代码块形式的 `unscoped` 方法: - -```ruby -Client.unscoped { - Client.created_before(Time.zone.now) -} -``` - -动态查询方法 ------------ - -Active Record 为数据表中的每个字段都提供了一个查询方法。例如,在 `Client` 模型中有个 `first_name` 字段,那么 Active Record 就会生成 `find_by_first_name` 方法。如果在 `Client` 模型中有个 `locked` 字段,就有一个 `find_by_locked` 方法。 - -在这些动态生成的查询方法后,可以加上感叹号(`!`),例如 `Client.find_by_name!("Ryan")`。此时,如果找不到记录就会抛出 `ActiveRecord::RecordNotFound` 异常。 - -如果想同时查询 `first_name` 和 `locked` 字段,可以用 `and` 把两个字段连接起来,获得所需的查询方法,例如 `Client.find_by_first_name_and_locked("Ryan", true)`。 - -查找或构建新对象 --------------- - -NOTE: 某些动态查询方法在 Rails 4.0 中已经启用,会在 Rails 4.1 中删除。推荐的做法是使用 Active Record 作用域。废弃的方法可以在这个 gem 中查看:。 - -我们经常需要在查询不到记录时创建一个新记录。这种需求可以使用 `find_or_create_by` 或 `find_or_create_by!` 方法实现。 - -### `find_or_create_by` - -`find_or_create_by` 方法首先检查指定属性对应的记录是否存在,如果不存在就调用 `create` 方法。我们来看一个例子。 - -假设你想查找一个名为“Andy”的客户,如果这个客户不存在就新建。这个需求可以使用下面的代码完成: - -```ruby -Client.find_or_create_by(first_name: 'Andy') -# => # -``` - -上述方法生成的 SQL 语句如下: - -```sql -SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 -BEGIN -INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') -COMMIT -``` - -`find_or_create_by` 方法返回现有的记录或者新建的记录。在上面的例子中,名为“Andy”的客户不存在,所以会新建一个记录,然后将其返回。 - -新纪录可能没有存入数据库,这取决于是否能通过数据验证(就像 `create` 方法一样)。 - -假设创建新记录时,要把 `locked` 属性设为 `false`,但不想在查询中设置。例如,我们要查询一个名为“Andy”的客户,如果这个客户不存在就新建一个,而且 `locked` 属性为 `false`。 - -这种需求有两种实现方法。第一种,使用 `create_with` 方法: - -```ruby -Client.create_with(locked: false).find_or_create_by(first_name: 'Andy') -``` - -第二种,使用代码块: - -```ruby -Client.find_or_create_by(first_name: 'Andy') do |c| - c.locked = false -end -``` - -代码块中的代码只会在创建客户之后执行。再次运行这段代码时,会忽略代码块中的代码。 - -### `find_or_create_by!` - -还可使用 `find_or_create_by!` 方法,如果新纪录不合法,会抛出异常。本文不涉及数据验证,假设已经在 `Client` 模型中定义了下面的验证: - -```ruby -validates :orders_count, presence: true -``` - -如果创建新 `Client` 对象时没有指定 `orders_count` 属性的值,这个对象就是不合法的,会抛出以下异常: - -```ruby -Client.find_or_create_by!(first_name: 'Andy') -# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank -``` - -### `find_or_initialize_by` - -`find_or_initialize_by` 方法和 `find_or_create_by` 的作用差不多,但不调用 `create` 方法,而是 `new` 方法。也就是说新建的模型实例在内存中,没有存入数据库。继续使用前面的例子,现在我们要查询的客户名为“Nick”: - -```ruby -nick = Client.find_or_initialize_by(first_name: 'Nick') -# => - -nick.persisted? -# => false - -nick.new_record? -# => true -``` - -因为对象不会存入数据库,上述代码生成的 SQL 语句如下: - -```sql -SELECT * FROM clients WHERE (clients.first_name = 'Nick') LIMIT 1 -``` - -如果想把对象存入数据库,调用 `save` 方法即可: - -```ruby -nick.save -# => true -``` - -使用 SQL 语句查询 ----------------- - -如果想使用 SQL 语句查询数据表中的记录,可以使用 `find_by_sql` 方法。就算只找到一个记录,`find_by_sql` 方法也会返回一个由记录组成的数组。例如,可以运行下面的查询: - -```ruby -Client.find_by_sql("SELECT * FROM clients - INNER JOIN orders ON clients.id = orders.client_id - ORDER BY clients.created_at desc") -``` - -`find_by_sql` 方法提供了一种定制查询的简单方式。 - -### `select_all` - -`find_by_sql` 方法有一个近亲,名为 `connection#select_all`。和 `find_by_sql` 一样,`select_all` 方法会使用 SQL 语句查询数据库,获取记录,但不会初始化对象。`select_all` 返回的结果是一个由 Hash 组成的数组,每个 Hash 表示一个记录。 - -```ruby -Client.connection.select_all("SELECT * FROM clients WHERE id = '1'") -``` - -### `pluck` - -`pluck` 方法可以在模型对应的数据表中查询一个或多个字段,其参数是一组字段名,返回结果是由各字段的值组成的数组。 - -```ruby -Client.where(active: true).pluck(:id) -# SELECT id FROM clients WHERE active = 1 -# => [1, 2, 3] - -Client.distinct.pluck(:role) -# SELECT DISTINCT role FROM clients -# => ['admin', 'member', 'guest'] - -Client.pluck(:id, :name) -# SELECT clients.id, clients.name FROM clients -# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] -``` - -如下的代码: - -```ruby -Client.select(:id).map { |c| c.id } -# or -Client.select(:id).map(&:id) -# or -Client.select(:id, :name).map { |c| [c.id, c.name] } -``` - -可用 `pluck` 方法实现: - -```ruby -Client.pluck(:id) -# or -Client.pluck(:id, :name) -``` - -和 `select` 方法不一样,`pluck` 直接把查询结果转换成 Ruby 数组,不生成 Active Record 对象,可以提升大型查询或常用查询的执行效率。但 `pluck` 方法不会使用重新定义的属性方法处理查询结果。例如: - -```ruby -class Client < ActiveRecord::Base - def name - "I am #{super}" - end -end - -Client.select(:name).map &:name -# => ["I am David", "I am Jeremy", "I am Jose"] - -Client.pluck(:name) -# => ["David", "Jeremy", "Jose"] -``` - -而且,与 `select` 和其他 `Relation` 作用域不同的是,`pluck` 方法会直接执行查询,因此后面不能和其他作用域链在一起,但是可以链接到已经执行的作用域之后: - -```ruby -Client.pluck(:name).limit(1) -# => NoMethodError: undefined method `limit' for # - -Client.limit(1).pluck(:name) -# => ["David"] -``` - -### `ids` - -`ids` 方法可以直接获取数据表的主键。 - -```ruby -Person.ids -# SELECT id FROM people -``` - -```ruby -class Person < ActiveRecord::Base - self.primary_key = "person_id" -end - -Person.ids -# SELECT person_id FROM people -``` - -检查对象是否存在 --------------- - -如果只想检查对象是否存在,可以使用 `exists?` 方法。这个方法使用的数据库查询和 `find` 方法一样,但不会返回对象或对象集合,而是返回 `true` 或 `false`。 - -```ruby -Client.exists?(1) -``` - -`exists?` 方法可以接受多个值,但只要其中一个记录存在,就会返回 `true`。 - -```ruby -Client.exists?(id: [1,2,3]) -# or -Client.exists?(name: ['John', 'Sergei']) -``` - -在模型或关系上调用 `exists?` 方法时,可以不指定任何参数。 - -```ruby -Client.where(first_name: 'Ryan').exists? -``` - -在上述代码中,只要有一个客户的 `first_name` 字段值为 `'Ryan'`,就会返回 `true`,否则返回 `false`。 - -```ruby -Client.exists? -``` - -在上述代码中,如果 `clients` 表是空的,会返回 `false`,否则返回 `true`。 - -在模型或关系中检查存在性时还可使用 `any?` 和 `many?` 方法。 - -```ruby -# via a model -Post.any? -Post.many? - -# via a named scope -Post.recent.any? -Post.recent.many? - -# via a relation -Post.where(published: true).any? -Post.where(published: true).many? - -# via an association -Post.first.categories.any? -Post.first.categories.many? -``` - -计算 ----- - -这里先以 `count` 方法为例,所有的选项都可在后面各方法中使用。 - -所有计算型方法都可直接在模型上调用: - -```ruby -Client.count -# SELECT count(*) AS count_all FROM clients -``` - -或者在关系上调用: - -```ruby -Client.where(first_name: 'Ryan').count -# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') -``` - -执行复杂计算时还可使用各种查询方法: - -```ruby -Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received' }).count -``` - -上述代码执行的 SQL 语句如下: - -```sql -SELECT count(DISTINCT clients.id) AS count_all FROM clients - LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE - (clients.first_name = 'Ryan' AND orders.status = 'received') -``` - -### 计数 - -如果想知道模型对应的数据表中有多少条记录,可以使用 `Client.count` 方法。如果想更精确的计算设定了 `age` 字段的记录数,可以使用 `Client.count(:age)`。 - -`count` 方法可用的选项[如前所述](#calculations)。 - -### 平均值 - -如果想查看某个字段的平均值,可以使用 `average` 方法。用法如下: - -```ruby -Client.average("orders_count") -``` - -这个方法会返回指定字段的平均值,得到的有可能是浮点数,例如 3.14159265。 - -`average` 方法可用的选项[如前所述](#calculations)。 - -### 最小值 - -如果想查看某个字段的最小值,可以使用 `minimum` 方法。用法如下: - -```ruby -Client.minimum("age") -``` - -`minimum` 方法可用的选项[如前所述](#calculations)。 - -### 最大值 - -如果想查看某个字段的最大值,可以使用 `maximum` 方法。用法如下: - -```ruby -Client.maximum("age") -``` - -`maximum` 方法可用的选项[如前所述](#calculations)。 - -### 求和 - -如果想查看所有记录中某个字段的总值,可以使用 `sum` 方法。用法如下: - -```ruby -Client.sum("orders_count") -``` - -`sum` 方法可用的选项[如前所述](#calculations)。 - -执行 EXPLAIN 命令 ----------------- - -可以在关系执行的查询中执行 EXPLAIN 命令。例如: - -```ruby -User.where(id: 1).joins(:posts).explain -``` - -在 MySQL 中得到的输出如下: - -``` -EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | -| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ -2 rows in set (0.00 sec) -``` - -Active Record 会按照所用数据库 shell 的方式输出结果。所以,相同的查询在 PostgreSQL 中得到的输出如下: - -``` -EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1 - QUERY PLAN ------------------------------------------------------------------------------- - Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) - Join Filter: (posts.user_id = users.id) - -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) - Index Cond: (id = 1) - -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) - Filter: (posts.user_id = 1) -(6 rows) -``` - -按需加载会触发多次查询,而且有些查询要用到之前查询的结果。鉴于此,`explain` 方法会真正执行查询,然后询问查询计划。例如: - -```ruby -User.where(id: 1).includes(:posts).explain -``` - -在 MySQL 中得到的输出如下: - -``` -EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | -+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ -1 row in set (0.00 sec) - -EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1) -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | -+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ -1 row in set (0.00 sec) -``` - -### 解读 EXPLAIN 命令的输出结果 - -解读 EXPLAIN 命令的输出结果不在本文的范畴之内。下面列出的链接可以帮助你进一步了解相关知识: - -* SQLite3: [EXPLAIN QUERY PLAN](http://www.sqlite.org/eqp.html) -* MySQL: [EXPLAIN 的输出格式](http://dev.mysql.com/doc/refman/5.6/en/explain-output.html) -* PostgreSQL: [使用 EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html) diff --git a/source/zh-CN/active_record_validations.md b/source/zh-CN/active_record_validations.md deleted file mode 100644 index 1d2f79b..0000000 --- a/source/zh-CN/active_record_validations.md +++ /dev/null @@ -1,944 +0,0 @@ -Active Record 数据验证 -===================== - -本文介绍如何使用 Active Record 提供的数据验证功能在数据存入数据库之前验证对象的状态。 - -读完本文,你将学到: - -* 如何使用 Active Record 内建的数据验证帮助方法; -* 如何编写自定义的数据验证方法; -* 如何处理验证时产生的错误消息; - --------------------------------------------------------------------------------- - -数据验证简介 ----------- - -下面演示一个非常简单的数据验证: - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true -end - -Person.create(name: "John Doe").valid? # => true -Person.create(name: nil).valid? # => false -``` - -如上所示,如果 `Person`的 `name` 属性值为空,验证就会将其视为不合法对象。创建的第二个 `Person` 对象不会存入数据库。 - -在深入探讨之前,我们先来介绍数据验证在整个程序中的作用。 - -### 为什么要做数据验证? - -数据验证能确保只有合法的数据才会存入数据库。例如,程序可能需要用户提供一个合法的 Email 地址和邮寄地址。在模型中做验证是最有保障的,只有通过验证的数据才能存入数据库。数据验证和使用的数据库种类无关,终端用户也无法跳过,而且容易测试和维护。在 Rails 中做数据验证很简单,Rails 内置了很多帮助方法,能满足常规的需求,而且还可以编写自定义的验证方法。 - -数据存入数据库之前的验证方法还有其他几种,包括数据库内建的约束,客户端验证和控制器层验证。下面列出了这几种验证方法的优缺点: - -* 数据库约束和“存储过程”无法兼容多种数据库,而且测试和维护较为困难。不过,如果其他程序也要使用这个数据库,最好在数据库层做些约束。数据库层的某些验证(例如在使用量很高的数据表中做唯一性验证)通过其他方式实现起来有点困难。 -* 客户端验证很有用,但单独使用时可靠性不高。如果使用 JavaScript 实现,用户在浏览器中禁用 JavaScript 后很容易跳过验证。客户端验证和其他验证方式结合使用,可以为用户提供实时反馈。 -* 控制器层验证很诱人,但一般都不灵便,难以测试和维护。只要可能,就要保证控制器的代码简洁性,这样才有利于长远发展。 - -你可以根据实际的需求选择使用哪种验证方式。Rails 团队认为,模型层数据验证最具普适性。 - -### 什么时候做数据验证? - -在 Active Record 中对象有两种状态:一种在数据库中有对应的记录,一种没有。新建的对象(例如,使用 `new` 方法)还不属于数据库。在对象上调用 `save` 方法后,才会把对象存入相应的数据表。Active Record 使用实例方法 `new_record?` 判断对象是否已经存入数据库。假如有下面这个简单的 Active Record 类: - -```ruby -class Person < ActiveRecord::Base -end -``` - -我们可以在 `rails console` 中看一下到底怎么回事: - -```ruby -$ rails console ->> p = Person.new(name: "John Doe") -=> # ->> p.new_record? -=> true ->> p.save -=> true ->> p.new_record? -=> false -``` - -新建并保存记录会在数据库中执行 SQL `INSERT` 操作。更新现有的记录会在数据库上执行 SQL `UPDATE` 操作。一般情况下,数据验证发生在这些 SQL 操作执行之前。如果验证失败,对象会被标记为不合法,Active Record 不会向数据库发送 `INSERT` 或 `UPDATE` 指令。这样就可以避免把不合法的数据存入数据库。你可以选择在对象创建、保存或更新时执行哪些数据验证。 - -WARNING: 修改数据库中对象的状态有很多方法。有些方法会做数据验证,有些则不会。所以,如果不小心处理,还是有可能把不合法的数据存入数据库。 - -下列方法会做数据验证,如果验证失败就不会把对象存入数据库: - -* `create` -* `create!` -* `save` -* `save!` -* `update` -* `update!` - -爆炸方法(例如 `save!`)会在验证失败后抛出异常。验证失败后,非爆炸方法不会抛出异常,`save` 和 `update` 返回 `false`,`create` 返回对象本身。 - -### 跳过验证 - -下列方法会跳过验证,不管验证是否通过都会把对象存入数据库,使用时要特别留意。 - -* `decrement!` -* `decrement_counter` -* `increment!` -* `increment_counter` -* `toggle!` -* `touch` -* `update_all` -* `update_attribute` -* `update_column` -* `update_columns` -* `update_counters` - -注意,使用 `save` 时如果传入 `validate: false`,也会跳过验证。使用时要特别留意。 - -* `save(validate: false)` - -### `valid?` 和 `invalid?` - -Rails 使用 `valid?` 方法检查对象是否合法。`valid?` 方法会触发数据验证,如果对象上没有错误,就返回 `true`,否则返回 `false`。前面我们已经用过了: - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true -end - -Person.create(name: "John Doe").valid? # => true -Person.create(name: nil).valid? # => false -``` - -Active Record 验证结束后,所有发现的错误都可以通过实例方法 `errors.messages` 获取,该方法返回一个错误集合。如果数据验证后,这个集合为空,则说明对象是合法的。 - -注意,使用 `new` 方法初始化对象时,即使不合法也不会报错,因为这时还没做数据验证。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true -end - ->> p = Person.new -# => # ->> p.errors.messages -# => {} - ->> p.valid? -# => false ->> p.errors.messages -# => {name:["can't be blank"]} - ->> p = Person.create -# => # ->> p.errors.messages -# => {name:["can't be blank"]} - ->> p.save -# => false - ->> p.save! -# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank - ->> Person.create! -# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank -``` - -`invalid?` 是 `valid?` 的逆测试,会触发数据验证,如果找到错误就返回 `true`,否则返回 `false`。 - -### `errors[]` - -要检查对象的某个属性是否合法,可以使用 `errors[:attribute]`。`errors[:attribute]` 中包含 `:attribute` 的所有错误。如果某个属性没有错误,就会返回空数组。 - -这个方法只在数据验证之后才能使用,因为它只是用来收集错误信息的,并不会触发验证。而且,和前面介绍的 `ActiveRecord::Base#invalid?` 方法不一样,因为 `errors[:attribute]` 不会验证整个对象,只检查对象的某个属性是否出错。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true -end - ->> Person.new.errors[:name].any? # => false ->> Person.create.errors[:name].any? # => true -``` - -我们会在“[处理验证错误](#working-with-validation-errors)”一节详细介绍验证错误。现在,我们来看一下 Rails 默认提供的数据验证帮助方法。 - -数据验证帮助方法 --------------- - -Active Record 预先定义了很多数据验证帮助方法,可以直接在模型类定义中使用。这些帮助方法提供了常用的验证规则。每次验证失败后,都会向对象的 `errors` 集合中添加一个消息,这些消息和所验证的属性是关联的。 - -每个帮助方法都可以接受任意数量的属性名,所以一行代码就能在多个属性上做同一种验证。 - -所有的帮助方法都可指定 `:on` 和 `:message` 选项,指定何时做验证,以及验证失败后向 `errors` 集合添加什么消息。`:on` 选项的可选值是 `:create` 和 `:update`。每个帮助函数都有默认的错误消息,如果没有通过 `:message` 选项指定,则使用默认值。下面分别介绍各帮助方法。 - -### `acceptance` - -这个方法检查表单提交时,用户界面中的复选框是否被选中。这个功能一般用来要求用户接受程序的服务条款,阅读一些文字,等等。这种验证只针对网页程序,不会存入数据库(如果没有对应的字段,该方法会创建一个虚拟属性)。 - -```ruby -class Person < ActiveRecord::Base - validates :terms_of_service, acceptance: true -end -``` - -这个帮助方法的默认错误消息是“must be accepted”。 - -这个方法可以指定 `:accept` 选项,决定可接受什么值。默认为“1”,很容易修改: - -```ruby -class Person < ActiveRecord::Base - validates :terms_of_service, acceptance: { accept: 'yes' } -end -``` - -### `validates_associated` - -如果模型和其他模型有关联,也要验证关联的模型对象,可以使用这个方法。保存对象时,会在相关联的每个对象上调用 `valid?` 方法。 - -```ruby -class Library < ActiveRecord::Base - has_many :books - validates_associated :books -end -``` - -这个帮助方法可用于所有关联类型。 - -WARNING: 不要在关联的两端都使用 `validates_associated`,这样会生成一个循环。 - -`validates_associated` 的默认错误消息是“is invalid”。注意,相关联的每个对象都有各自的 `errors` 集合,错误消息不会都集中在调用该方法的模型对象上。 - -### `confirmation` - -如果要检查两个文本字段的值是否完全相同,可以使用这个帮助方法。例如,确认 Email 地址或密码。这个帮助方法会创建一个虚拟属性,其名字为要验证的属性名后加 `_confirmation`。 - -```ruby -class Person < ActiveRecord::Base - validates :email, confirmation: true -end -``` - -在视图中可以这么写: - -```erb -<%= text_field :person, :email %> -<%= text_field :person, :email_confirmation %> -``` - -只有 `email_confirmation` 的值不是 `nil` 时才会做这个验证。所以要为确认属性加上存在性验证(后文会介绍 `presence` 验证)。 - -```ruby -class Person < ActiveRecord::Base - validates :email, confirmation: true - validates :email_confirmation, presence: true -end -``` - -这个帮助方法的默认错误消息是“doesn't match confirmation”。 - -### `exclusion` - -这个帮助方法检查属性的值是否不在指定的集合中。集合可以是任何一种可枚举的对象。 - -```ruby -class Account < ActiveRecord::Base - validates :subdomain, exclusion: { in: %w(www us ca jp), - message: "%{value} is reserved." } -end -``` - -`exclusion` 方法要指定 `:in` 选项,设置哪些值不能作为属性的值。`:in` 选项有个别名 `:with`,作用相同。上面的例子设置了 `:message` 选项,演示如何获取属性的值。 - -默认的错误消息是“is reserved”。 - -### `format` - -这个帮助方法检查属性的值是否匹配 `:with` 选项指定的正则表达式。 - -```ruby -class Product < ActiveRecord::Base - validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, - message: "only allows letters" } -end -``` - -默认的错误消息是“is invalid”。 - -### `inclusion` - -这个帮助方法检查属性的值是否在指定的集合中。集合可以是任何一种可枚举的对象。 - -```ruby -class Coffee < ActiveRecord::Base - validates :size, inclusion: { in: %w(small medium large), - message: "%{value} is not a valid size" } -end -``` - -`inclusion` 方法要指定 `:in` 选项,设置可接受哪些值。`:in` 选项有个别名 `:within`,作用相同。上面的例子设置了 `:message` 选项,演示如何获取属性的值。 - -该方法的默认错误消息是“is not included in the list”。 - -### `length` - -这个帮助方法验证属性值的长度,有多个选项,可以使用不同的方法指定长度限制: - -```ruby -class Person < ActiveRecord::Base - validates :name, length: { minimum: 2 } - validates :bio, length: { maximum: 500 } - validates :password, length: { in: 6..20 } - validates :registration_number, length: { is: 6 } -end -``` - -可用的长度限制选项有: - -* `:minimum`:属性的值不能比指定的长度短; -* `:maximum`:属性的值不能比指定的长度长; -* `:in`(或 `:within`):属性值的长度在指定值之间。该选项的值必须是一个范围; -* `:is`:属性值的长度必须等于指定值; - -默认的错误消息根据长度验证类型而有所不同,还是可以 `:message` 定制。定制消息时,可以使用 `:wrong_length`、`:too_long` 和 `:too_short` 选项,`%{count}` 表示长度限制的值。 - -```ruby -class Person < ActiveRecord::Base - validates :bio, length: { maximum: 1000, - too_long: "%{count} characters is the maximum allowed" } -end -``` - -这个帮助方法默认统计字符数,但可以使用 `:tokenizer` 选项设置其他的统计方式: - -```ruby -class Essay < ActiveRecord::Base - validates :content, length: { - minimum: 300, - maximum: 400, - tokenizer: lambda { |str| str.scan(/\w+/) }, - too_short: "must have at least %{count} words", - too_long: "must have at most %{count} words" - } -end -``` - -注意,默认的错误消息使用复数形式(例如,“is too short (minimum is %{count} characters”),所以如果长度限制是 `minimum: 1`,就要提供一个定制的消息,或者使用 `presence: true` 代替。`:in` 或 `:within` 的值比 1 小时,都要提供一个定制的消息,或者在 `length` 之前,调用 `presence` 方法。 - -### `numericality` - -这个帮助方法检查属性的值是否值包含数字。默认情况下,匹配的值是可选的正负符号后加整数或浮点数。如果只接受整数,可以把 `:only_integer` 选项设为 `true`。 - -如果 `:only_integer` 为 `true`,则使用下面的正则表达式验证属性的值。 - -```ruby -/\A[+-]?\d+\Z/ -``` - -否则,会尝试使用 `Float` 把值转换成数字。 - -WARNING: 注意上面的正则表达式允许最后出现换行符。 - -```ruby -class Player < ActiveRecord::Base - validates :points, numericality: true - validates :games_played, numericality: { only_integer: true } -end -``` - -除了 `:only_integer` 之外,这个方法还可指定以下选项,限制可接受的值: - -* `: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}”; -* `:odd`:如果设为 `true`,属性值必须是奇数。该选项默认的错误消息是“must be odd”; -* `:even`:如果设为 `true`,属性值必须是偶数。该选项默认的错误消息是“must be even”; - -默认的错误消息是“is not a number”。 - -### `presence` - -这个帮助方法检查指定的属性是否为非空值,调用 `blank?` 方法检查值是否为 `nil` 或空字符串,即空字符串或只包含空白的字符串。 - -```ruby -class Person < ActiveRecord::Base - validates :name, :login, :email, presence: true -end -``` - -如果要确保关联对象存在,需要测试关联的对象本身是否存在,而不是用来映射关联的外键。 - -```ruby -class LineItem < ActiveRecord::Base - belongs_to :order - validates :order, presence: true -end -``` - -为了能验证关联的对象是否存在,要在关联中指定 `:inverse_of` 选项。 - -```ruby -class Order < ActiveRecord::Base - has_many :line_items, inverse_of: :order -end -``` - -如果验证 `has_one` 或 `has_many` 关联的对象是否存在,会在关联的对象上调用 `blank?` 和 `marked_for_destruction?` 方法。 - -因为 `false.blank?` 的返回值是 `true`,所以如果要验证布尔值字段是否存在要使用 `validates :field_name, inclusion: { in: [true, false] }`。 - -默认的错误消息是“can't be blank”。 - -### `absence` - -这个方法验证指定的属性值是否为空,使用 `present?` 方法检测值是否为 `nil` 或空字符串,即空字符串或只包含空白的字符串。 - -```ruby -class Person < ActiveRecord::Base - validates :name, :login, :email, absence: true -end -``` - -如果要确保关联对象为空,需要测试关联的对象本身是否为空,而不是用来映射关联的外键。 - -```ruby -class LineItem < ActiveRecord::Base - belongs_to :order - validates :order, absence: true -end -``` - -为了能验证关联的对象是否为空,要在关联中指定 `:inverse_of` 选项。 - -```ruby -class Order < ActiveRecord::Base - has_many :line_items, inverse_of: :order -end -``` - -如果验证 `has_one` 或 `has_many` 关联的对象是否为空,会在关联的对象上调用 `present?` 和 `marked_for_destruction?` 方法。 - -因为 `false.present?` 的返回值是 `false`,所以如果要验证布尔值字段是否为空要使用 `validates :field_name, exclusion: { in: [true, false] }`。 - -默认的错误消息是“must be blank”。 - -### `uniqueness` - -这个帮助方法会在保存对象之前验证属性值是否是唯一的。该方法不会在数据库中创建唯一性约束,所以有可能两个数据库连接创建的记录字段的值是相同的。为了避免出现这种问题,要在数据库的字段上建立唯一性索引。关于多字段索引的详细介绍,参阅 [MySQL 手册](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html)。 - -```ruby -class Account < ActiveRecord::Base - validates :email, uniqueness: true -end -``` - -这个验证会在模型对应的数据表中执行一个 SQL 查询,检查现有的记录中该字段是否已经出现过相同的值。 - -`:scope` 选项可以指定其他属性,用来约束唯一性验证: - -```ruby -class Holiday < ActiveRecord::Base - validates :name, uniqueness: { scope: :year, - message: "should happen once per year" } -end -``` - -还有个 `:case_sensitive` 选项,指定唯一性验证是否要区分大小写,默认值为 `true`。 - -```ruby -class Person < ActiveRecord::Base - validates :name, uniqueness: { case_sensitive: false } -end -``` - -WARNING: 注意,有些数据库的设置是,查询时不区分大小写。 - -默认的错误消息是“has already been taken”。 - -### `validates_with` - -这个帮助方法把记录交给其他的类做验证。 - -```ruby -class GoodnessValidator < ActiveModel::Validator - def validate(record) - if record.first_name == "Evil" - record.errors[:base] << "This person is evil" - end - end -end - -class Person < ActiveRecord::Base - validates_with GoodnessValidator -end -``` - -NOTE: `record.errors[:base]` 中的错误针对整个对象,而不是特定的属性。 - -`validates_with` 方法的参数是一个类,或一组类,用来做验证。`validates_with` 方法没有默认的错误消息。在做验证的类中要手动把错误添加到记录的错误集合中。 - -实现 `validate` 方法时,必须指定 `record` 参数,这是要做验证的记录。 - -和其他验证一样,`validates_with` 也可指定 `:if`、`:unless` 和 `:on` 选项。如果指定了其他选项,会包含在 `options` 中传递给做验证的类。 - -```ruby -class GoodnessValidator < ActiveModel::Validator - def validate(record) - if options[:fields].any?{|field| record.send(field) == "Evil" } - record.errors[:base] << "This person is evil" - end - end -end - -class Person < ActiveRecord::Base - validates_with GoodnessValidator, fields: [:first_name, :last_name] -end -``` - -注意,做验证的类在整个程序的生命周期内只会初始化一次,而不是每次验证时都初始化,所以使用实例变量时要特别小心。 - -如果做验证的类很复杂,必须要用实例变量,可以用纯粹的 Ruby 对象代替: - -```ruby -class Person < ActiveRecord::Base - validate do |person| - GoodnessValidator.new(person).validate - end -end - -class GoodnessValidator - def initialize(person) - @person = person - end - - def validate - if some_complex_condition_involving_ivars_and_private_methods? - @person.errors[:base] << "This person is evil" - end - end - - # ... -end -``` - -### `validates_each` - -这个帮助方法会把属性值传入代码库做验证,没有预先定义验证的方式,你应该在代码库中定义验证方式。要验证的每个属性都会传入块中做验证。在下面的例子中,我们确保名和姓都不能以小写字母开头: - -```ruby -class Person < ActiveRecord::Base - validates_each :name, :surname do |record, attr, value| - record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ - end -end -``` - -代码块的参数是记录,属性名和属性值。在代码块中可以做任何检查,确保数据合法。如果验证失败,要向模型添加一个错误消息,把数据标记为不合法。 - -常用的验证选项 -------------- - -常用的验证选项包括: - -### `:allow_nil` - -指定 `:allow_nil` 选项后,如果要验证的值为 `nil` 就会跳过验证。 - -```ruby -class Coffee < ActiveRecord::Base - validates :size, inclusion: { in: %w(small medium large), - message: "%{value} is not a valid size" }, allow_nil: true -end -``` - -### `:allow_blank` - -`:allow_blank` 选项和 `:allow_nil` 选项类似。如果要验证的值为空(调用 `blank?` 方法,例如 `nil` 或空字符串),就会跳过验证。 - -```ruby -class Topic < ActiveRecord::Base - validates :title, length: { is: 5 }, allow_blank: true -end - -Topic.create(title: "").valid? # => true -Topic.create(title: nil).valid? # => true -``` - -### `:message` - -前面已经介绍过,如果验证失败,会把 `:message` 选项指定的字符串添加到 `errors` 集合中。如果没指定这个选项,Active Record 会使用各种验证帮助方法的默认错误消息。 - -### `:on` - -`:on` 选项指定什么时候做验证。所有内建的验证帮助方法默认都在保存时(新建记录或更新记录)做验证。如果想修改,可以使用 `on: :create`,指定只在创建记录时做验证;或者使用 `on: :update`,指定只在更新记录时做验证。 - -```ruby -class Person < ActiveRecord::Base - # it will be possible to update email with a duplicated value - validates :email, uniqueness: true, on: :create - - # it will be possible to create the record with a non-numerical age - validates :age, numericality: true, on: :update - - # the default (validates on both create and update) - validates :name, presence: true -end -``` - -严格验证 -------- - -数据验证还可以使用严格模式,失败后会抛出 `ActiveModel::StrictValidationFailed` 异常。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: { strict: true } -end - -Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank -``` - -通过 `:strict` 选项,还可以指定抛出什么异常: - -```ruby -class Person < ActiveRecord::Base - validates :token, presence: true, uniqueness: true, strict: TokenGenerationException -end - -Person.new.valid? # => TokenGenerationException: Token can't be blank -``` - -条件验证 -------- - -有时只有满足特定条件时做验证才说得通。条件可通过 `:if` 和 `:unless` 选项指定,这两个选项的值可以是 Symbol、字符串、`Proc` 或数组。`:if` 选项指定何时做验证。如果要指定何时不做验证,可以使用 `:unless` 选项。 - -### 指定 Symbol - -`:if` 和 `:unless` 选项的值为 Symbol 时,表示要在验证之前执行对应的方法。这是最常用的设置方法。 - -```ruby -class Order < ActiveRecord::Base - validates :card_number, presence: true, if: :paid_with_card? - - def paid_with_card? - payment_type == "card" - end -end -``` - -### 指定字符串 - -`:if` 和 `:unless` 选项的值还可以是字符串,但必须是 Ruby 代码,传入 `eval` 方法中执行。当字符串表示的条件非常短时才应该使用这种形式。 - -```ruby -class Person < ActiveRecord::Base - validates :surname, presence: true, if: "name.nil?" -end -``` - -### 指定 Proc - -`:if` and `:unless` 选项的值还可以是 Proc。使用 Proc 对象可以在行间编写条件,不用定义额外的方法。这种形式最适合用在一行代码能表示的条件上。 - -```ruby -class Account < ActiveRecord::Base - validates :password, confirmation: true, - unless: Proc.new { |a| a.password.blank? } -end -``` - -### 条件组合 - -有时同一个条件会用在多个验证上,这时可以使用 `with_options` 方法: - -```ruby -class User < ActiveRecord::Base - with_options if: :is_admin? do |admin| - admin.validates :password, length: { minimum: 10 } - admin.validates :email, presence: true - end -end -``` - -`with_options` 代码块中的所有验证都会使用 `if: :is_admin?` 这个条件。 - -### 联合条件 - -另一方面,当多个条件规定验证是否应该执行时,可以使用数组。而且,同一个验证可以同时指定 `:if` 和 `:unless` 选项。 - -```ruby -class Computer < ActiveRecord::Base - validates :mouse, presence: true, - if: ["market.retail?", :desktop?] - unless: Proc.new { |c| c.trackpad.present? } -end -``` - -只有当 `:if` 选项的所有条件都返回 `true`,且 `:unless` 选项中的条件返回 `false` 时才会做验证。 - -自定义验证方式 ------------- - -如果内建的数据验证帮助方法无法满足需求时,可以选择自己定义验证使用的类或方法。 - -### 自定义验证使用的类 - -自定义的验证类继承自 `ActiveModel::Validator`,必须实现 `validate` 方法,传入的参数是要验证的记录,然后验证这个记录是否合法。自定义的验证类通过 `validates_with` 方法调用。 - -```ruby -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 -end - -class Person - include ActiveModel::Validations - validates_with MyValidator -end -``` - -在自定义的验证类中验证单个属性,最简单的方法是集成 `ActiveModel::EachValidator` 类。此时,自定义的验证类中要实现 `validate_each` 方法。这个方法接受三个参数:记录,属性名和属性值。 - -```ruby -class EmailValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i - record.errors[attribute] << (options[:message] || "is not an email") - end - end -end - -class Person < ActiveRecord::Base - validates :email, presence: true, email: true -end -``` - -如上面的代码所示,可以同时使用内建的验证方法和自定义的验证类。 - -### 自定义验证使用的方法 - -还可以自定义方法验证模型的状态,如果验证失败,向 `errors` 集合添加错误消息。然后还要使用类方法 `validate` 注册这些方法,传入自定义验证方法名的 Symbol 形式。 - -类方法可以接受多个 Symbol,自定义的验证方法会按照注册的顺序执行。 - -```ruby -class Invoice < ActiveRecord::Base - validate :expiration_date_cannot_be_in_the_past, - :discount_cannot_be_greater_than_total_value - - def expiration_date_cannot_be_in_the_past - if expiration_date.present? && expiration_date < Date.today - errors.add(:expiration_date, "can't be in the past") - end - end - - def discount_cannot_be_greater_than_total_value - if discount > total_value - errors.add(:discount, "can't be greater than total value") - end - end -end -``` - -默认情况下,每次调用 `valid?` 方法时都会执行自定义的验证方法。使用 `validate` 方法注册自定义验证方法时可以设置 `:on` 选项,执行什么时候运行。`:on` 的可选值为 `:create` 和 `:update`。 - -```ruby -class Invoice < ActiveRecord::Base - validate :active_customer, on: :create - - def active_customer - errors.add(:customer_id, "is not active") unless customer.active? - end -end -``` - -处理验证错误 ------------ - -除了前面介绍的 `valid?` 和 `invalid?` 方法之外,Rails 还提供了很多方法用来处理 `errors` 集合,以及查询对象的合法性。 - -下面介绍其中一些常用的方法。所有可用的方法请查阅 `ActiveModel::Errors` 的文档。 - -### `errors` - -`ActiveModel::Errors` 的实例包含所有的错误。其键是每个属性的名字,值是一个数组,包含错误消息字符串。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true, length: { minimum: 3 } -end - -person = Person.new -person.valid? # => false -person.errors.messages - # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]} - -person = Person.new(name: "John Doe") -person.valid? # => true -person.errors.messages # => {} -``` - -### `errors[]` - -`errors[]` 用来获取某个属性上的错误消息,返回结果是一个由该属性所有错误消息字符串组成的数组,每个字符串表示一个错误消息。如果字段上没有错误,则返回空数组。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true, length: { minimum: 3 } -end - -person = Person.new(name: "John Doe") -person.valid? # => true -person.errors[:name] # => [] - -person = Person.new(name: "JD") -person.valid? # => false -person.errors[:name] # => ["is too short (minimum is 3 characters)"] - -person = Person.new -person.valid? # => false -person.errors[:name] - # => ["can't be blank", "is too short (minimum is 3 characters)"] -``` - -### `errors.add` - -`add` 方法可以手动添加某属性的错误消息。使用 `errors.full_messages` 或 `errors.to_a` 方法会以最终显示给用户的形式显示错误消息。这些错误消息的前面都会加上字段名可读形式(并且首字母大写)。`add` 方法接受两个参数:错误消息要添加到的字段名和错误消息本身。 - -```ruby -class Person < ActiveRecord::Base - def a_method_used_for_validation_purposes - errors.add(:name, "cannot contain the characters !@#%*()_-+=") - end -end - -person = Person.create(name: "!@#") - -person.errors[:name] - # => ["cannot contain the characters !@#%*()_-+="] - -person.errors.full_messages - # => ["Name cannot contain the characters !@#%*()_-+="] -``` - -还有一种方法可以实现同样地效果,使用 `[]=` 设置方法: - -```ruby - class Person < ActiveRecord::Base - def a_method_used_for_validation_purposes - errors[:name] = "cannot contain the characters !@#%*()_-+=" - end - end - - person = Person.create(name: "!@#") - - person.errors[:name] - # => ["cannot contain the characters !@#%*()_-+="] - - person.errors.to_a - # => ["Name cannot contain the characters !@#%*()_-+="] -``` - -### `errors[:base]` - -错误消息可以添加到整个对象上,而不是针对某个属性。如果不想管是哪个属性导致对象不合法,只想把对象标记为不合法状态,就可以使用这个方法。`errors[:base]` 是个数组,可以添加字符串作为错误消息。 - -```ruby -class Person < ActiveRecord::Base - def a_method_used_for_validation_purposes - errors[:base] << "This person is invalid because ..." - end -end -``` - -### `errors.clear` - -如果想清除 `errors` 集合中的所有错误消息,可以使用 `clear` 方法。当然了,在不合法的对象上调用 `errors.clear` 方法后,这个对象还是不合法的,虽然 `errors` 集合为空了,但下次调用 `valid?` 方法,或调用其他把对象存入数据库的方法时, 会再次进行验证。如果任何一个验证失败了,`errors` 集合中就再次出现值了。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true, length: { minimum: 3 } -end - -person = Person.new -person.valid? # => false -person.errors[:name] - # => ["can't be blank", "is too short (minimum is 3 characters)"] - -person.errors.clear -person.errors.empty? # => true - -p.save # => false - -p.errors[:name] -# => ["can't be blank", "is too short (minimum is 3 characters)"] -``` - -### `errors.size` - -`size` 方法返回对象上错误消息的总数。 - -```ruby -class Person < ActiveRecord::Base - validates :name, presence: true, length: { minimum: 3 } -end - -person = Person.new -person.valid? # => false -person.errors.size # => 2 - -person = Person.new(name: "Andrea", email: "andrea@example.com") -person.valid? # => true -person.errors.size # => 0 -``` - -在视图中显示验证错误 ------------------ - -在模型中加入数据验证后,如果在表单中创建模型,出错时,你或许想把错误消息显示出来。 - -因为每个程序显示错误消息的方式不同,所以 Rails 没有直接提供用来显示错误消息的视图帮助方法。不过,Rails 提供了这么多方法用来处理验证,自己编写一个也不难。使用脚手架时,Rails 会在生成的 `_form.html.erb` 中加入一些 ERB 代码,显示模型错误消息的完整列表。 - -假设有个模型对象存储在实例变量 `@post` 中,视图的代码可以这么写: - -```ruby -<% if @post.errors.any? %> -
-

<%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:

- -
    - <% @post.errors.full_messages.each do |msg| %> -
  • <%= msg %>
  • - <% end %> -
-
-<% end %> -``` - -而且,如果使用 Rails 的表单帮助方法生成表单,如果某个表单字段验证失败,会把字段包含在一个 `
` 中: - -``` -
- -
-``` - -然后可以根据需求为这个 `div` 添加样式。脚手架默认添加的 CSS 如下: - -``` -.field_with_errors { - padding: 2px; - background-color: red; - display: table; -} -``` - -所有出错的表单字段都会放入一个内边距为 2 像素的红色框内。 diff --git a/source/zh-CN/active_support_core_extensions.md b/source/zh-CN/active_support_core_extensions.md deleted file mode 100644 index d19927f..0000000 --- a/source/zh-CN/active_support_core_extensions.md +++ /dev/null @@ -1,3882 +0,0 @@ -Active Support 核心扩展 -============================== - -Active Support 作为 Ruby on Rails 的一个组件,可以用来添加 Ruby 语言扩展、工具集以及其他这类事物。 - -它从语言的层面上进行了强化,既可起效于一般 Rails 程序开发,又能增强 Ruby on Rails 框架自身。 - -读完本文,你将学到: - -* 核心扩展是什么。 -* 如何加载全部扩展。 -* 如何恰如其分的选出你需要的扩展。 -* Active Support 都提供了哪些功能。 - --------------------------------------------------------------------------------- - -如何加载核心扩展 ---------------------------- - -### 单独的 Active Support - -为了使初始空间尽可能干净,默认情况下 Active Support 什么都不加载。它被拆分成许多小组件,这样一来你便可以只加载自己需要的那部分,同时它也提供了一系列便捷入口使你很容易加载相关的扩展,甚至把全部扩展都加载进来。 - -因而,像下面这样只简单用一个 require: - -```ruby -require 'active_support' -``` - -对象会连`blank?`都没法响应。让我们来看下该如何加载它的定义。 - -#### 选出合适的定义 - -找到`blank?`最轻便的方法就是直接找出定义它的那个文件。 - -对于每一个定义在核心扩展里的方法,本指南都会注明此方法定义于何处。例如这里提到的`blank?`,会像这样注明: - -NOTE: 定义于 `active_support/core_ext/object/blank.rb`。 - -这意味着你可以像下面这样 require 它: - -```ruby -require 'active_support' -require 'active_support/core_ext/object/blank' -``` - -Active Support 经过了严格的修订,确保选定的文件只会加载必要的依赖,若没有则不加载。 - -#### 加载一组核心扩展 - -接下来加载`Object`下的全部扩展。一般来说,想加载`SomeClass`下的全部可用扩展,只需加载`active_support/core_ext/some_class`即可。 - -所以,若要加载`Object`下的全部扩展(包含`blank?`): - -```ruby -require 'active_support' -require 'active_support/core_ext/object' -``` - -#### 加载全部核心扩展 - -你可能更倾向于加载全部核心扩展,有一个文件能办到: - -```ruby -require 'active_support' -require 'active_support/core_ext' -``` - -#### 加载全部 Active Support - -最后,如果你想要 Active Support 的全部内容,只需: - -```ruby -require 'active_support/all' -``` - -这样做并不会把整个 Active Support 预加载到内存里,鉴于`autoload`的机制,其只有在真正用到时才会加载。 - -### Ruby on Rails 程序里的 Active Support - -除非把`config.active_support.bare`设置为 true, 否则 Ruby on Rails 的程序会加载全部的 Active Support。如此一来,程序只会加载框架为自身需要挑选出来的扩展,同时也可像上文所示,可以从任何级别加载特定扩展。 - -所有对象都可用的扩展 -------------------------- - -### `blank?` and `present?` - -以下各值在 Rails 程序里都看作 blank。 - -* `nil` 和 `false`, - -* 只包含空白的字符串(参照下文注释), - -* 空的数组和散列表 - -* 任何其他能响应 `empty?` 方法且为空的对象。 - -INFO: 判断字符串是否为空依据了 Unicode-aware 字符类 `[:space:]`,所以例如 U+2029(段落分隔符)这种会被当作空白。 - -WARNING: 注意这里没有提到数字。通常来说,0和0.0都**不是**blank。 - -例如,`ActionController::HttpAuthentication::Token::ControllerMethods`里的一个方法使用了`blank?`来检验 token 是否存在。 - -```ruby -def authenticate(controller, &login_procedure) - token, options = token_and_options(controller.request) - unless token.blank? - login_procedure.call(token, options) - end -end -``` - -`present?` 方法等同于 `!blank?`, 下面的例子出自`ActionDispatch::Http::Cache::Response`: - -```ruby -def set_conditional_cache_control! - return if self["Cache-Control"].present? - ... -end -``` - -NOTE: 定义于 `active_support/core_ext/object/blank.rb`. - -### `presence` - -`presence`方法如果满足`present?`则返回调用者,否则返回`nil`。它适用于下面这种情况: - -```ruby -host = config[:host].presence || 'localhost' -``` - -NOTE: 定义于 `active_support/core_ext/object/blank.rb`. - -### `duplicable?` - -A few fundamental objects in Ruby are singletons. For example, in the whole life of a program the integer 1 refers always to the same instance: -Ruby 里有些基本对象是单例的。比如,在整个程序的生命周期里,数字1永远指向同一个实例。 - -```ruby -1.object_id # => 3 -Math.cos(0).to_i.object_id # => 3 -``` - -因而,这些对象永远没法用`dup`或`clone`复制。 - -```ruby -true.dup # => TypeError: can't dup TrueClass -``` - -有些数字虽然不是单例的,但也同样无法复制: - -```ruby -0.0.clone # => allocator undefined for Float -(2**1024).clone # => allocator undefined for Bignum -``` - -Active Support 提供了 `duplicable?` 方法来判断一个对象是否能够被复制: - -```ruby -"foo".duplicable? # => true -"".duplicable? # => true -0.0.duplicable? # => false -false.duplicable? # => false -``` - -根据定义,所有的对象的`duplicated?`的,除了:`nil`、`false`、 `true`、 符号、 数字、 类和模块。 - -WARNING: 任何的类都可以通过移除`dup`和`clone`方法,或者在其中抛出异常,来禁用其复制功能。虽然`duplicable?`方法是基于上面的硬编码列表,但是它比用`rescue`快的多。确保仅在你的情况合乎上面的硬编码列表时候再使用它。 - -NOTE: 定义于 `active_support/core_ext/object/duplicable.rb`. - -### `deep_dup` - -`deep_dup`方法返回一个对象的深度拷贝。一般来说,当你`dup`一个包含其他对象的对象时,Ruby 并不会把被包含的对象一同`dup`,它只会创建一个对象的浅表拷贝。假如你有一个字符串数组,如下例所示: - -```ruby -array = ['string'] -duplicate = array.dup - -duplicate.push 'another-string' - -# 对象被复制了,所以只有 duplicate 的数组元素有所增加 -array # => ['string'] -duplicate # => ['string', 'another-string'] - -duplicate.first.gsub!('string', 'foo') - -# 第一个数组元素并未被复制,所以两个数组都发生了变化 -array # => ['foo'] -duplicate # => ['foo', 'another-string'] -``` - -如你所见,对`Array`实例进行复制后,我们得到了另一个对象,因而我们修改它时,原始对象并未跟着有所变化。不过对数组元素而言,情况却有所不同。因为`dup`不会创建深度拷贝,所以数组里的字符串依然是同一个对象。 - -如果你需要一个对象的深度拷贝,就应该使用`deep_dup`。我们再来看下面这个例子: - -```ruby -array = ['string'] -duplicate = array.deep_dup - -duplicate.first.gsub!('string', 'foo') - -array # => ['string'] -duplicate # => ['foo'] -``` - -如果一个对象是不可复制的,`deep_dup`会返回其自身: - -```ruby -number = 1 -duplicate = number.deep_dup -number.object_id == duplicate.object_id # => true -``` - -NOTE: 定义于 `active_support/core_ext/object/deep_dup.rb`. - -### `try` - -如果你想在一个对象不为`nil`时,对其调用一个方法,最简单的办法就是使用条件从句,但这么做也会使代码变得乱七八糟。另一个选择就是使用`try`。`try`就好比`Object#send`,只不过如果接收者为`nil`,那么返回值也会是`nil`。 - -看下这个例子: - -```ruby -# 不使用 try -unless @number.nil? - @number.next -end - -# 使用 try -@number.try(:next) -``` - -接下来的这个例子,代码出自`ActiveRecord::ConnectionAdapters::AbstractAdapter`,这里的`@logger`有可能为`nil`。能够看到,代码里使用了`try`来避免不必要的检查。 - -```ruby -def log_info(sql, name, ms) - if @logger.try(:debug?) - name = '%s (%.1fms)' % [name || 'SQL', ms] - @logger.debug(format_log_entry(name, sql.squeeze(' '))) - end -end -``` - -调用`try`时也可以不传参数而是用代码快,其中的代码只有在对象不为`nil`时才会执行: - -```ruby -@person.try { |p| "#{p.first_name} #{p.last_name}" } -``` - -NOTE: 定义于 `active_support/core_ext/object/try.rb`. - -### `class_eval(*args, &block)` - -You can evaluate code in the context of any object's singleton class using `class_eval`: -使用`class_eval`,可以使代码在对象的单件类的上下文里执行: - -```ruby -class Proc - def bind(object) - block, time = self, Time.current - object.class_eval do - method_name = "__bind_#{time.to_i}_#{time.usec}" - define_method(method_name, &block) - method = instance_method(method_name) - remove_method(method_name) - method - end.bind(object) - end -end -``` - -NOTE: 定义于 `active_support/core_ext/kernel/singleton_class.rb`. - -### `acts_like?(duck)` - -`acts_like?`方法可以用来判断某个类与另一个类是否有相同的行为,它基于一个简单的惯例:这个类是否提供了与`String`相同的接口: - -```ruby -def acts_like_string? -end -``` - -上述代码只是一个标识,它的方法体或返回值都是不相关的。之后,就可以像下述代码那样判断其代码是否为“鸭子类型安全”的代码了: - -```ruby -some_klass.acts_like?(:string) -``` - -Rails 里的许多类,例如`Date`和`Time`,都遵循上述约定。 - -NOTE: 定义于 `active_support/core_ext/object/acts_like.rb`. - -### `to_param` - -所有 Rails 对象都可以响应`to_param`方法,它会把对象的值转换为查询字符串,或者 URL 片段,并返回该值。 - -默认情况下,`to_param`仅仅调用了`to_s`: - -```ruby -7.to_param # => "7" -``` - -**不要**对`to_param`方法的返回值进行转义: - -```ruby -"Tom & Jerry".to_param # => "Tom & Jerry" -``` - -Rails 里的许多类重写了这个方法。 - -例如`nil`、`true`和`false`会返回其自身。`Array#to_param`会对数组元素调用`to_param`并把结果用"/"连接成字符串: - -```ruby -[0, true, String].to_param # => "0/true/String" -``` - -需要注意的是, Rails 的路由系统会在模型上调用`to_param`并把结果作为`:id`占位符。`ActiveRecord::Base#to_param`会返回模型的`id`,但是你也可以在自己模型里重新定义它。例如: - -```ruby -class User - def to_param - "#{id}-#{name.parameterize}" - end -end -``` - -会得到: - -```ruby -user_path(@user) # => "/users/357-john-smith" -``` - -WARNING. 控制器里需要注意被重定义过的`to_param`,因为一个类似上述的请求里,会把"357-john-smith"当作`params[:id]`的值。 - -NOTE: 定义于 `active_support/core_ext/object/to_param.rb`. - -### `to_query` - -除了散列表之外,给定一个未转义的`key`,这个方法就会基于这个键和`to_param`的返回值,构造出一个新的查询字符串。例如: - -```ruby -class User - def to_param - "#{id}-#{name.parameterize}" - end -end -``` - -会得到: - -```ruby -current_user.to_query('user') # => "user=357-john-smith" -``` - -无论对于键还是值,本方法都会根据需要进行转义: - -```ruby -account.to_query('company[name]') -# => "company%5Bname%5D=Johnson+%26+Johnson" -``` - -所以它的输出已经完全适合于用作查询字符串。 - -对于数组,会对其中每个元素以`_key_[]`为键执行`to_query`方法,并把结果用"&"连接为字符串: - -```ruby -[3.4, -45.6].to_query('sample') -# => "sample%5B%5D=3.4&sample%5B%5D=-45.6" -``` - -哈系表也可以响应`to_query`方法但是用法有所不同。如果调用时没传参数,会先生成一系列排过序的键值对并在值上调用`to_query(键)`。然后把所得结果用"&"连接为字符串: - -```ruby -{c: 3, b: 2, a: 1}.to_query # => "a=1&b=2&c=3" -``` - -`Hash#to_query`方法也可接受一个可选的命名空间作为键: - -```ruby -{id: 89, name: "John Smith"}.to_query('user') -# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith" -``` - -NOTE: 定义于 `active_support/core_ext/object/to_query.rb`. - -### `with_options` - -`with_options`方法可以为一组方法调用提取出共有的选项。 - -假定有一个默认的散列表选项,`with_options`方法会引入一个代理对象到代码块。在代码块内部,代理对象上的方法调用,会连同被混入的选项一起,被转发至原方法接收者。例如,若要去除下述代码的重复内容: - -```ruby -class Account < ActiveRecord::Base - has_many :customers, dependent: :destroy - has_many :products, dependent: :destroy - has_many :invoices, dependent: :destroy - has_many :expenses, dependent: :destroy -end -``` - -可按此法书写: - -```ruby -class Account < ActiveRecord::Base - with_options dependent: :destroy do |assoc| - assoc.has_many :customers - assoc.has_many :products - assoc.has_many :invoices - assoc.has_many :expenses - end -end -``` - -#TODO: clear this after totally understanding what these statnances means... -That idiom may convey _grouping_ to the reader as well. For example, say you want to send a newsletter whose language depends on the user. Somewhere in the mailer you could group locale-dependent bits like this: -上述写法也可用于对读取器进行分组。例如,假设你要发一份新闻通讯,通讯所用语言取决于用户。便可以利用如下例所示代码,对用户按照地区依赖进行分组: - - -```ruby -I18n.with_options locale: user.locale, scope: "newsletter" do |i18n| - subject i18n.t :subject - body i18n.t :body, user_name: user.name -end -``` - -TIP: 由于`with_options`会把方法调用转发给其自身的接收者,所以可以进行嵌套。每层嵌套都会把继承来的默认值混入到自身的默认值里。 - -NOTE: 定义于 `active_support/core_ext/object/with_options.rb`. - -### JSON 支持 - -相较于 `json` gem 为 Ruby 对象提供的`to_json`方法,Active Support 给出了一个更好的实现。因为有许多类,诸如`Hash`、`OrderedHash`和`Process::Status`,都需要做特殊处理才能到适合的 JSON 替换。 - -NOTE: 定义于 `active_support/core_ext/object/json.rb`. - -### 实例变量 - -Active Support 提供了若干方法以简化对实例变量的访问。 - -#### `instance_values` - -`instance_values`方法返回一个散列表,其中会把实例变量名去掉"@"作为键,把相应的实例变量值作为值。键全部是字符串: - -```ruby -class C - def initialize(x, y) - @x, @y = x, y - end -end - -C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} -``` - -NOTE: 定义于 `active_support/core_ext/object/instance_variables.rb`. - -#### `instance_variable_names` - -`instance_variable_names`方法返回一个数组。数组中所有的实例变量名都带有"@"标志。 - -```ruby -class C - def initialize(x, y) - @x, @y = x, y - end -end - -C.new(0, 1).instance_variable_names # => ["@x", "@y"] -``` - -NOTE: 定义于 `active_support/core_ext/object/instance_variables.rb`. - -### Silencing Warnings, Streams, 和 Exceptions - -`silence_warnings`和`enable_warnings`方法都可以在其代码块里改变`$VERBOSE`的值,并在之后把值重置: - -```ruby -silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } -``` - -You can silence any stream while a block runs with `silence_stream`: -在通过`silence_stream`执行的代码块里,可以使任意流安静的运行: - -```ruby -silence_stream(STDOUT) do - # 这里的代码不会输出到 STDOUT -end -``` - -`quietly`方法可以使 STDOUT 和 STDERR 保持安静,即便在子进程里也如此: - -```ruby -quietly { system 'bundle install' } -``` - -例如,railties 测试组件会用到上述方法,来阻止普通消息与进度状态混到一起。 - -也可以用`suppress`方法来使异常保持安静。方法接收任意数量的异常类。如果代码块的代码执行时报出异常,并且该异常`kind_of?`满足任一参数,`suppress`便会将异其捕获并安静的返回。否则会重新抛出该异常: - -```ruby -# If the user is locked the increment is lost, no big deal. -suppress(ActiveRecord::StaleObjectError) do - current_user.increment! :visits -end -``` - -NOTE: 定义于 `active_support/core_ext/kernel/reporting.rb`. - -### `in?` - -判断式`in?`用于测试一个对象是否被包含在另一个对象里。当传入的参数无法响应`include?`时,会抛出`ArgumentError`异常。 - -使用`in?`的例子: - -```ruby -1.in?([1,2]) # => true -"lo".in?("hello") # => true -25.in?(30..50) # => false -1.in?(1) # => ArgumentError -``` - -NOTE: 定义于 `active_support/core_ext/object/inclusion.rb`. - -对`Module`的扩展 ----------------------- - -### `alias_method_chain` - -使用纯 Ruby 可以用方法环绕其他的方法,这种做法被称为环绕别名。 - -例如,我们假设在功能测试里你希望参数都是字符串,就如同真实的请求中那样,但是同时你也希望对于数字和其他类型的值能够很方便的赋值。为了做到这点,你可以把`test/test_helper.rb`里的`ActionController::TestCase#process`方法像下面这样环绕: - -```ruby -ActionController::TestCase.class_eval do - # save a reference to the original process method - alias_method :original_process, :process - - # now redefine process and delegate to original_process - def process(action, params=nil, session=nil, flash=nil, http_method='GET') - params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - original_process(action, params, session, flash, http_method) - end -end -``` - -`get`、`post`等最终会通过此方法执行。 - -这么做有一定风险,`:original_process`有可能已经被占用了。为了避免方法名发生碰撞,通常会添加标签来表明这是个关于什么的别名: - -```ruby -ActionController::TestCase.class_eval do - def process_with_stringified_params(...) - params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(action, params, session, flash, http_method) - end - alias_method :process_without_stringified_params, :process - alias_method :process, :process_with_stringified_params -end -``` - -`alias_method_chain`为上述技巧提供了一个便捷之法: - -```ruby -ActionController::TestCase.class_eval do - def process_with_stringified_params(...) - params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(action, params, session, flash, http_method) - end - alias_method_chain :process, :stringified_params -end -``` - -Rails 源代码中随处可见`alias_method_chain`。例如`ActiveRecord::Base#save`里,就通过这种方式对方法进行环绕,从 validations 下一个专门的模块里为其增加了验证。 - -NOTE: 定义于 `active_support/core_ext/module/aliasing.rb`. - -### 属性 - -#### `alias_attribute` - -模型属性包含读取器、写入器和判断式。只需添加一行代码,就可以为模型属性添加一个包含以上三个方法的别名。与其他别名方法一样,新名称充当第一个参数,原有名称是第二个参数(为了方便记忆,可以类比下赋值时的书写顺序)。 - -```ruby -class User < ActiveRecord::Base - # You can refer to the email column as "login". - # This can be meaningful for authentication code. - alias_attribute :login, :email -end -``` - -NOTE: 定义于 `active_support/core_ext/module/aliasing.rb`. - -#### 内部属性 - -当你在一个被继承的类里定义一条属性时,属性名称有可能会发生碰撞。这一点对许多库而言尤为重要。 - -Active Support 定义了`attr_internal_reader`、`attr_internal_writer`和`attr_internal_accessor`这些类宏。它们的作用与 Ruby 内建的`attr_*`相当,只不过实例变量名多了下划线以避免碰撞。 - -类宏`attr_internal`与`attr_internal_accessor`是同义: - -```ruby -# library -class ThirdPartyLibrary::Crawler - attr_internal :log_level -end - -# client code -class MyCrawler < ThirdPartyLibrary::Crawler - attr_accessor :log_level -end -``` - -上述例子里的情况可能是,`:log_level`并不属于库的公共接口,而是只用于开发。而在客户代码里,由于不知道可能出现的冲突,便在子类里又定义了`:log_level`。多亏了`attr_internal`才没有出项碰撞。 - -默认情况下,内部实例变量名以下划线开头,如上例中即为`@_log_level`。不过这点可以通过`Module.attr_internal_naming_format`进行配置,你可以传入任何`sprintf`这一类的格式化字符串,并在开头加上`@`,同时还要加上`%s`表示变量名称的位置。默认值为`"@_%s"`。 - -Rails 在若干地方使用了内部属性,比如在视图层: - -```ruby -module ActionView - class Base - attr_internal :captures - attr_internal :request, :layout - attr_internal :controller, :template - end -end -``` - -NOTE: 定义于 `active_support/core_ext/module/attr_internal.rb`. - -#### Module Attributes - -类宏`mattr_reader`、`mattr_writer`和`mattr_accessor`与为类定义的`cattr_*`是相同的。实际上,`cattr_*`系列的类宏只不过是`mattr_*`这些类宏的别名。详见[Class Attributes](#class-attributes)。 - -例如,依赖性机制就用到了它们: - -```ruby -module ActiveSupport - module Dependencies - mattr_accessor :warnings_on_first_load - mattr_accessor :history - mattr_accessor :loaded - mattr_accessor :mechanism - mattr_accessor :load_paths - mattr_accessor :load_once_paths - mattr_accessor :autoloaded_constants - mattr_accessor :explicitly_unloadable_constants - mattr_accessor :logger - mattr_accessor :log_activity - mattr_accessor :constant_watch_stack - mattr_accessor :constant_watch_stack_mutex - end -end -``` - -NOTE: 定义于 `active_support/core_ext/module/attribute_accessors.rb`. - -### Parents - -#### `parent` - -对一个嵌套的模块调用`parent`方法,会返回其相应的常量: - -```ruby -module X - module Y - module Z - end - end -end -M = X::Y::Z - -X::Y::Z.parent # => X::Y -M.parent # => X::Y -``` - -如果这个模块是匿名的或者属于顶级作用域, `parent`会返回`Object`。 - -WARNING: 若有上述情况,则`parent_name`会返回`nil`。 - -NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. - -#### `parent_name` - -对一个嵌套的模块调用`parent_name`方法,会返回其相应常量的完全限定名: - -```ruby -module X - module Y - module Z - end - end -end -M = X::Y::Z - -X::Y::Z.parent_name # => "X::Y" -M.parent_name # => "X::Y" -``` - -定义在顶级作用域里的模块或匿名的模块,`parent_name`会返回`nil`。 - -WARNING: 若有上述情况,则`parent`返回`Object`。 - -NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. - -#### `parents` - -`parents`方法会对接收者调用`parent`,并向上追溯直至`Object`。之后所得结果链按由低到高顺序组成一个数组被返回。 - -```ruby -module X - module Y - module Z - end - end -end -M = X::Y::Z - -X::Y::Z.parents # => [X::Y, X, Object] -M.parents # => [X::Y, X, Object] -``` - -NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. - -### 常量 - -defined in the receiver module: -`local_constants`方法返回在接收者模块中定义的常量。 - -```ruby -module X - X1 = 1 - X2 = 2 - module Y - Y1 = :y1 - X1 = :overrides_X1_above - end -end - -X.local_constants # => [:X1, :X2, :Y] -X::Y.local_constants # => [:Y1, :X1] -``` - -常量名会作为符号被返回。 - -NOTE: 定义于 `active_support/core_ext/module/introspection.rb`. - -#### 限定常量名 - -标准方法`const_defined?`、`const_get`和`const_set`接受裸常量名。 -Active Support 扩展了这些API使其可以接受相对限定常量名。 - -新的方法名是`qualified_const_defined?`,`qualified_const_get`和`qualified_const_set`。 -它们的参数被假定为相对于其接收者的限定常量名: - -```ruby -Object.qualified_const_defined?("Math::PI") # => true -Object.qualified_const_get("Math::PI") # => 3.141592653589793 -Object.qualified_const_set("Math::Phi", 1.618034) # => 1.618034 -``` - -参数可以使用裸常量名: - -```ruby -Math.qualified_const_get("E") # => 2.718281828459045 -``` - -These methods are analogous to their built-in counterparts. In particular, -`qualified_constant_defined?` accepts an optional second argument to be -able to say whether you want the predicate to look in the ancestors. -This flag is taken into account for each constant in the expression while -walking down the path. -这些方法与其内建的对应方法很类似。尤为值得一提的是,`qualified_constant_defined?`接收一个可选的第二参数,以此来标明你是否要在祖先链中进行查找。 - -例如,假定: - -```ruby -module M - X = 1 -end - -module N - class C - include M - end -end -``` - -`qualified_const_defined?`会这样执行: - -```ruby -N.qualified_const_defined?("C::X", false) # => false -N.qualified_const_defined?("C::X", true) # => true -N.qualified_const_defined?("C::X") # => true -``` - -As the last example implies, the second argument defaults to true, -as in `const_defined?`. - -For coherence with the built-in methods only relative paths are accepted. -Absolute qualified constant names like `::Math::PI` raise `NameError`. - -NOTE: 定义于 `active_support/core_ext/module/qualified_const.rb`. - -### Reachable - -A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant. - -That is what ordinarily happens, if a module is called "M", the `M` constant exists and holds it: - -```ruby -module M -end - -M.reachable? # => true -``` - -But since constants and modules are indeed kind of decoupled, module objects can become unreachable: - -```ruby -module M -end - -orphan = Object.send(:remove_const, :M) - -# The module object is orphan now but it still has a name. -orphan.name # => "M" - -# You cannot reach it via the constant M because it does not even exist. -orphan.reachable? # => false - -# Let's define a module called "M" again. -module M -end - -# The constant M exists now again, and it stores a module -# object called "M", but it is a new instance. -orphan.reachable? # => false -``` - -NOTE: 定义于 `active_support/core_ext/module/reachable.rb`. - -### Anonymous - -A module may or may not have a name: - -```ruby -module M -end -M.name # => "M" - -N = Module.new -N.name # => "N" - -Module.new.name # => nil -``` - -You can check whether a module has a name with the predicate `anonymous?`: - -```ruby -module M -end -M.anonymous? # => false - -Module.new.anonymous? # => true -``` - -Note that being unreachable does not imply being anonymous: - -```ruby -module M -end - -m = Object.send(:remove_const, :M) - -m.reachable? # => false -m.anonymous? # => false -``` - -though an anonymous module is unreachable by definition. - -NOTE: 定义于 `active_support/core_ext/module/anonymous.rb`. - -### Method Delegation - -The macro `delegate` offers an easy way to forward methods. - -Let's imagine that users in some application have login information in the `User` model but name and other data in a separate `Profile` model: - -```ruby -class User < ActiveRecord::Base - has_one :profile -end -``` - -With that configuration you get a user's name via their profile, `user.profile.name`, but it could be handy to still be able to access such attribute directly: - -```ruby -class User < ActiveRecord::Base - has_one :profile - - def name - profile.name - end -end -``` - -That is what `delegate` does for you: - -```ruby -class User < ActiveRecord::Base - has_one :profile - - delegate :name, to: :profile -end -``` - -It is shorter, and the intention more obvious. - -The method must be public in the target. - -The `delegate` macro accepts several methods: - -```ruby -delegate :name, :age, :address, :twitter, to: :profile -``` - -When interpolated into a string, the `:to` option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such an expression is evaluated in the context of the receiver: - -```ruby -# delegates to the Rails constant -delegate :logger, to: :Rails - -# delegates to the receiver's class -delegate :table_name, to: :class -``` - -WARNING: If the `:prefix` option is `true` this is less generic, see below. - -By default, if the delegation raises `NoMethodError` and the target is `nil` the exception is propagated. You can ask that `nil` is returned instead with the `:allow_nil` option: - -```ruby -delegate :name, to: :profile, allow_nil: true -``` - -With `:allow_nil` the call `user.name` returns `nil` if the user has no profile. - -The option `:prefix` adds a prefix to the name of the generated method. This may be handy for example to get a better name: - -```ruby -delegate :street, to: :address, prefix: true -``` - -The previous example generates `address_street` rather than `street`. - -WARNING: Since in this case the name of the generated method is composed of the target object and target method names, the `:to` option must be a method name. - -A custom prefix may also be configured: - -```ruby -delegate :size, to: :attachment, prefix: :avatar -``` - -In the previous example the macro generates `avatar_size` rather than `size`. - -NOTE: 定义于 `active_support/core_ext/module/delegation.rb` - -### Redefining Methods - -There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either. - -The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. - -NOTE: 定义于 `active_support/core_ext/module/remove_method.rb` - -Extensions to `Class` ---------------------- - -### Class Attributes - -#### `class_attribute` - -The method `class_attribute` declares one or more inheritable class attributes that can be overridden at any level down the hierarchy. - -```ruby -class A - class_attribute :x -end - -class B < A; end - -class C < B; end - -A.x = :a -B.x # => :a -C.x # => :a - -B.x = :b -A.x # => :a -C.x # => :b - -C.x = :c -A.x # => :a -B.x # => :b -``` - -For example `ActionMailer::Base` defines: - -```ruby -class_attribute :default_params -self.default_params = { - mime_version: "1.0", - charset: "UTF-8", - content_type: "text/plain", - parts_order: [ "text/plain", "text/enriched", "text/html" ] -}.freeze -``` - -They can be also accessed and overridden at the instance level. - -```ruby -A.x = 1 - -a1 = A.new -a2 = A.new -a2.x = 2 - -a1.x # => 1, comes from A -a2.x # => 2, overridden in a2 -``` - -The generation of the writer instance method can be prevented by setting the option `:instance_writer` to `false`. - -```ruby -module ActiveRecord - class Base - class_attribute :table_name_prefix, instance_writer: false - self.table_name_prefix = "" - end -end -``` - -A model may find that option useful as a way to prevent mass-assignment from setting the attribute. - -The generation of the reader instance method can be prevented by setting the option `:instance_reader` to `false`. - -```ruby -class A - class_attribute :x, instance_reader: false -end - -A.new.x = 1 # NoMethodError -``` - -For convenience `class_attribute` also defines an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called `x?`. - -When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method. - -If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. - -NOTE: 定义于 `active_support/core_ext/class/attribute.rb` - -#### `cattr_reader`, `cattr_writer`, and `cattr_accessor` - -The macros `cattr_reader`, `cattr_writer`, and `cattr_accessor` are analogous to their `attr_*` counterparts but for classes. They initialize a class variable to `nil` unless it already exists, and generate the corresponding class methods to access it: - -```ruby -class MysqlAdapter < AbstractAdapter - # Generates class methods to access @@emulate_booleans. - cattr_accessor :emulate_booleans - self.emulate_booleans = true -end -``` - -Instance methods are created as well for convenience, they are just proxies to the class attribute. So, instances can change the class attribute, but cannot override it as it happens with `class_attribute` (see above). For example given - -```ruby -module ActionView - class Base - cattr_accessor :field_error_proc - @@field_error_proc = Proc.new{ ... } - end -end -``` - -we can access `field_error_proc` in views. - -Also, you can pass a block to `cattr_*` to set up the attribute with a default value: - -```ruby -class MysqlAdapter < AbstractAdapter - # Generates class methods to access @@emulate_booleans with default value of true. - cattr_accessor(:emulate_booleans) { true } -end -``` - -The generation of the reader instance method can be prevented by setting `:instance_reader` to `false` and the generation of the writer instance method can be prevented by setting `:instance_writer` to `false`. Generation of both methods can be prevented by setting `:instance_accessor` to `false`. In all cases, the value must be exactly `false` and not any false value. - -```ruby -module A - class B - # No first_name instance reader is generated. - cattr_accessor :first_name, instance_reader: false - # No last_name= instance writer is generated. - cattr_accessor :last_name, instance_writer: false - # No surname instance reader or surname= writer is generated. - cattr_accessor :surname, instance_accessor: false - end -end -``` - -A model may find it useful to set `:instance_accessor` to `false` as a way to prevent mass-assignment from setting the attribute. - -NOTE: 定义于 `active_support/core_ext/module/attribute_accessors.rb`. - -### Subclasses & Descendants - -#### `subclasses` - -The `subclasses` method returns the subclasses of the receiver: - -```ruby -class C; end -C.subclasses # => [] - -class B < C; end -C.subclasses # => [B] - -class A < B; end -C.subclasses # => [B] - -class D < C; end -C.subclasses # => [B, D] -``` - -The order in which these classes are returned is unspecified. - -NOTE: 定义于 `active_support/core_ext/class/subclasses.rb`. - -#### `descendants` - -The `descendants` method returns all classes that are `<` than its receiver: - -```ruby -class C; end -C.descendants # => [] - -class B < C; end -C.descendants # => [B] - -class A < B; end -C.descendants # => [B, A] - -class D < C; end -C.descendants # => [B, A, D] -``` - -The order in which these classes are returned is unspecified. - -NOTE: 定义于 `active_support/core_ext/class/subclasses.rb`. - -Extensions to `String` ----------------------- - -### Output Safety - -#### Motivation - -Inserting data into HTML templates needs extra care. For example, you can't just interpolate `@review.title` verbatim into an HTML page. For one thing, if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;". What's more, depending on the application, that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the section about cross-site scripting in the [Security guide](security.html#cross-site-scripting-xss) for further information about the risks. - -#### Safe Strings - -Active Support has the concept of _(html) safe_ strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. - -Strings are considered to be _unsafe_ by default: - -```ruby -"".html_safe? # => false -``` - -You can obtain a safe string from a given one with the `html_safe` method: - -```ruby -s = "".html_safe -s.html_safe? # => true -``` - -It is important to understand that `html_safe` performs no escaping whatsoever, it is just an assertion: - -```ruby -s = "".html_safe -s.html_safe? # => true -s # => "" -``` - -It is your responsibility to ensure calling `html_safe` on a particular string is fine. - -If you append onto a safe string, either in-place with `concat`/`<<`, or with `+`, the result is a safe string. Unsafe arguments are escaped: - -```ruby -"".html_safe + "<" # => "<" -``` - -Safe arguments are directly appended: - -```ruby -"".html_safe + "<".html_safe # => "<" -``` - -These methods should not be used in ordinary views. Unsafe values are automatically escaped: - -```erb -<%= @review.title %> <%# fine, escaped if needed %> -``` - -To insert something verbatim use the `raw` helper rather than calling `html_safe`: - -```erb -<%= raw @cms.current_template %> <%# inserts @cms.current_template as is %> -``` - -or, equivalently, use `<%==`: - -```erb -<%== @cms.current_template %> <%# inserts @cms.current_template as is %> -``` - -The `raw` helper calls `html_safe` for you: - -```ruby -def raw(stringish) - stringish.to_s.html_safe -end -``` - -NOTE: 定义于 `active_support/core_ext/string/output_safety.rb`. - -#### Transformation - -As a rule of thumb, except perhaps for concatenation as explained above, any method that may change a string gives you an unsafe string. These are `downcase`, `gsub`, `strip`, `chomp`, `underscore`, etc. - -In the case of in-place transformations like `gsub!` the receiver itself becomes unsafe. - -INFO: The safety bit is lost always, no matter whether the transformation actually changed something. - -#### Conversion and Coercion - -Calling `to_s` on a safe string returns a safe string, but coercion with `to_str` returns an unsafe string. - -#### Copying - -Calling `dup` or `clone` on safe strings yields safe strings. - -### `remove` - -The method `remove` will remove all occurrences of the pattern: - -```ruby -"Hello World".remove(/Hello /) => "World" -``` - -There's also the destructive version `String#remove!`. - -NOTE: 定义于 `active_support/core_ext/string/filters.rb`. - -### `squish` - -The method `squish` strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: - -```ruby -" \n foo\n\r \t bar \n".squish # => "foo bar" -``` - -There's also the destructive version `String#squish!`. - -Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). - -NOTE: 定义于 `active_support/core_ext/string/filters.rb`. - -### `truncate` - -The method `truncate` returns a copy of its receiver truncated after a given `length`: - -```ruby -"Oh dear! Oh dear! I shall be late!".truncate(20) -# => "Oh dear! Oh dear!..." -``` - -Ellipsis can be customized with the `:omission` option: - -```ruby -"Oh dear! Oh dear! I shall be late!".truncate(20, omission: '…') -# => "Oh dear! Oh …" -``` - -Note in particular that truncation takes into account the length of the omission string. - -Pass a `:separator` to truncate the string at a natural break: - -```ruby -"Oh dear! Oh dear! I shall be late!".truncate(18) -# => "Oh dear! Oh dea..." -"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ') -# => "Oh dear! Oh..." -``` - -The option `:separator` can be a regexp: - -```ruby -"Oh dear! Oh dear! I shall be late!".truncate(18, separator: /\s/) -# => "Oh dear! Oh..." -``` - -In above examples "dear" gets cut first, but then `:separator` prevents it. - -NOTE: 定义于 `active_support/core_ext/string/filters.rb`. - -### `inquiry` - -The `inquiry` method converts a string into a `StringInquirer` object making equality checks prettier. - -```ruby -"production".inquiry.production? # => true -"active".inquiry.inactive? # => false -``` - -### `starts_with?` and `ends_with?` - -Active Support defines 3rd person aliases of `String#start_with?` and `String#end_with?`: - -```ruby -"foo".starts_with?("f") # => true -"foo".ends_with?("o") # => true -``` - -NOTE: 定义于 `active_support/core_ext/string/starts_ends_with.rb`. - -### `strip_heredoc` - -The method `strip_heredoc` strips indentation in heredocs. - -For example in - -```ruby -if options[:usage] - puts <<-USAGE.strip_heredoc - This command does such and such. - - Supported options are: - -h This message - ... - USAGE -end -``` - -the user would see the usage message aligned against the left margin. - -Technically, it looks for the least indented line in the whole string, and removes -that amount of leading whitespace. - -NOTE: 定义于 `active_support/core_ext/string/strip.rb`. - -### `indent` - -Indents the lines in the receiver: - -```ruby -< - def some_method - some_code - end -``` - -The second argument, `indent_string`, specifies which indent string to use. The default is `nil`, which tells the method to make an educated guess peeking at the first indented line, and fallback to a space if there is none. - -```ruby -" foo".indent(2) # => " foo" -"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar" -"foo".indent(2, "\t") # => "\t\tfoo" -``` - -While `indent_string` is typically one space or tab, it may be any string. - -The third argument, `indent_empty_lines`, is a flag that says whether empty lines should be indented. Default is false. - -```ruby -"foo\n\nbar".indent(2) # => " foo\n\n bar" -"foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar" -``` - -The `indent!` method performs indentation in-place. - -NOTE: 定义于 `active_support/core_ext/string/indent.rb`. - -### Access - -#### `at(position)` - -Returns the character of the string at position `position`: - -```ruby -"hello".at(0) # => "h" -"hello".at(4) # => "o" -"hello".at(-1) # => "o" -"hello".at(10) # => nil -``` - -NOTE: 定义于 `active_support/core_ext/string/access.rb`. - -#### `from(position)` - -Returns the substring of the string starting at position `position`: - -```ruby -"hello".from(0) # => "hello" -"hello".from(2) # => "llo" -"hello".from(-2) # => "lo" -"hello".from(10) # => "" if < 1.9, nil in 1.9 -``` - -NOTE: 定义于 `active_support/core_ext/string/access.rb`. - -#### `to(position)` - -Returns the substring of the string up to position `position`: - -```ruby -"hello".to(0) # => "h" -"hello".to(2) # => "hel" -"hello".to(-2) # => "hell" -"hello".to(10) # => "hello" -``` - -NOTE: 定义于 `active_support/core_ext/string/access.rb`. - -#### `first(limit = 1)` - -The call `str.first(n)` is equivalent to `str.to(n-1)` if `n` > 0, and returns an empty string for `n` == 0. - -NOTE: 定义于 `active_support/core_ext/string/access.rb`. - -#### `last(limit = 1)` - -The call `str.last(n)` is equivalent to `str.from(-n)` if `n` > 0, and returns an empty string for `n` == 0. - -NOTE: 定义于 `active_support/core_ext/string/access.rb`. - -### Inflections - -#### `pluralize` - -The method `pluralize` returns the plural of its receiver: - -```ruby -"table".pluralize # => "tables" -"ruby".pluralize # => "rubies" -"equipment".pluralize # => "equipment" -``` - -As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in `config/initializers/inflections.rb`. That file is generated by the `rails` command and has instructions in comments. - -`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: - -```ruby -"dude".pluralize(0) # => "dudes" -"dude".pluralize(1) # => "dude" -"dude".pluralize(2) # => "dudes" -``` - -Active Record uses this method to compute the default table name that corresponds to a model: - -```ruby -# active_record/model_schema.rb -def undecorated_table_name(class_name = base_class.name) - table_name = class_name.to_s.demodulize.underscore - pluralize_table_names ? table_name.pluralize : table_name -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `singularize` - -The inverse of `pluralize`: - -```ruby -"tables".singularize # => "table" -"rubies".singularize # => "ruby" -"equipment".singularize # => "equipment" -``` - -Associations compute the name of the corresponding default associated class using this method: - -```ruby -# active_record/reflection.rb -def derive_class_name - class_name = name.to_s.camelize - class_name = class_name.singularize if collection? - class_name -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `camelize` - -The method `camelize` returns its receiver in camel case: - -```ruby -"product".camelize # => "Product" -"admin_user".camelize # => "AdminUser" -``` - -As a rule of thumb you can think of this method as the one that transforms paths into Ruby class or module names, where slashes separate namespaces: - -```ruby -"backoffice/session".camelize # => "Backoffice::Session" -``` - -For example, Action Pack uses this method to load the class that provides a certain session store: - -```ruby -# action_controller/metal/session_management.rb -def session_store=(store) - @@session_store = store.is_a?(Symbol) ? - ActionDispatch::Session.const_get(store.to_s.camelize) : - store -end -``` - -`camelize` accepts an optional argument, it can be `:upper` (default), or `:lower`. With the latter the first letter becomes lowercase: - -```ruby -"visual_effect".camelize(:lower) # => "visualEffect" -``` - -That may be handy to compute method names in a language that follows that convention, for example JavaScript. - -INFO: As a rule of thumb you can think of `camelize` as the inverse of `underscore`, though there are cases where that does not hold: `"SSLError".underscore.camelize` gives back `"SslError"`. To support cases such as this, Active Support allows you to specify acronyms in `config/initializers/inflections.rb`: - -```ruby -ActiveSupport::Inflector.inflections do |inflect| - inflect.acronym 'SSL' -end - -"SSLError".underscore.camelize # => "SSLError" -``` - -`camelize` is aliased to `camelcase`. - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `underscore` - -The method `underscore` goes the other way around, from camel case to paths: - -```ruby -"Product".underscore # => "product" -"AdminUser".underscore # => "admin_user" -``` - -Also converts "::" back to "/": - -```ruby -"Backoffice::Session".underscore # => "backoffice/session" -``` - -and understands strings that start with lowercase: - -```ruby -"visualEffect".underscore # => "visual_effect" -``` - -`underscore` accepts no argument though. - -Rails class and module autoloading uses `underscore` to infer the relative path without extension of a file that would define a given missing constant: - -```ruby -# active_support/dependencies.rb -def load_missing_constant(from_mod, const_name) - ... - qualified_name = qualified_name_for from_mod, const_name - path_suffix = qualified_name.underscore - ... -end -``` - -INFO: As a rule of thumb you can think of `underscore` as the inverse of `camelize`, though there are cases where that does not hold. For example, `"SSLError".underscore.camelize` gives back `"SslError"`. - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `titleize` - -The method `titleize` capitalizes the words in the receiver: - -```ruby -"alice in wonderland".titleize # => "Alice In Wonderland" -"fermat's enigma".titleize # => "Fermat's Enigma" -``` - -`titleize` is aliased to `titlecase`. - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `dasherize` - -The method `dasherize` replaces the underscores in the receiver with dashes: - -```ruby -"name".dasherize # => "name" -"contact_data".dasherize # => "contact-data" -``` - -The XML serializer of models uses this method to dasherize node names: - -```ruby -# active_model/serializers/xml.rb -def reformat_name(name) - name = name.camelize if camelize? - dasherize? ? name.dasherize : name -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `demodulize` - -Given a string with a qualified constant name, `demodulize` returns the very constant name, that is, the rightmost part of it: - -```ruby -"Product".demodulize # => "Product" -"Backoffice::UsersController".demodulize # => "UsersController" -"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils" -"::Inflections".demodulize # => "Inflections" -"".demodulize # => "" - -``` - -Active Record for example uses this method to compute the name of a counter cache column: - -```ruby -# active_record/reflection.rb -def counter_cache_column - if options[:counter_cache] == true - "#{active_record.name.demodulize.underscore.pluralize}_count" - elsif options[:counter_cache] - options[:counter_cache] - end -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `deconstantize` - -Given a string with a qualified constant reference expression, `deconstantize` removes the rightmost segment, generally leaving the name of the constant's container: - -```ruby -"Product".deconstantize # => "" -"Backoffice::UsersController".deconstantize # => "Backoffice" -"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel" -``` - -Active Support for example uses this method in `Module#qualified_const_set`: - -```ruby -def qualified_const_set(path, value) - QualifiedConstUtils.raise_if_absolute(path) - - const_name = path.demodulize - mod_name = path.deconstantize - mod = mod_name.empty? ? self : qualified_const_get(mod_name) - mod.const_set(const_name, value) -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `parameterize` - -The method `parameterize` normalizes its receiver in a way that can be used in pretty URLs. - -```ruby -"John Smith".parameterize # => "john-smith" -"Kurt Gödel".parameterize # => "kurt-godel" -``` - -In fact, the result string is wrapped in an instance of `ActiveSupport::Multibyte::Chars`. - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `tableize` - -The method `tableize` is `underscore` followed by `pluralize`. - -```ruby -"Person".tableize # => "people" -"Invoice".tableize # => "invoices" -"InvoiceLine".tableize # => "invoice_lines" -``` - -As a rule of thumb, `tableize` returns the table name that corresponds to a given model for simple cases. The actual implementation in Active Record is not straight `tableize` indeed, because it also demodulizes the class name and checks a few options that may affect the returned string. - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `classify` - -The method `classify` is the inverse of `tableize`. It gives you the class name corresponding to a table name: - -```ruby -"people".classify # => "Person" -"invoices".classify # => "Invoice" -"invoice_lines".classify # => "InvoiceLine" -``` - -The method understands qualified table names: - -```ruby -"highrise_production.companies".classify # => "Company" -``` - -Note that `classify` returns a class name as a string. You can get the actual class object invoking `constantize` on it, explained next. - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `constantize` - -The method `constantize` resolves the constant reference expression in its receiver: - -```ruby -"Fixnum".constantize # => Fixnum - -module M - X = 1 -end -"M::X".constantize # => 1 -``` - -If the string evaluates to no known constant, or its content is not even a valid constant name, `constantize` raises `NameError`. - -Constant name resolution by `constantize` starts always at the top-level `Object` even if there is no leading "::". - -```ruby -X = :in_Object -module M - X = :in_M - - X # => :in_M - "::X".constantize # => :in_Object - "X".constantize # => :in_Object (!) -end -``` - -So, it is in general not equivalent to what Ruby would do in the same spot, had a real constant be evaluated. - -Mailer test cases obtain the mailer being tested from the name of the test class using `constantize`: - -```ruby -# action_mailer/test_case.rb -def determine_default_mailer(name) - name.sub(/Test$/, '').constantize -rescue NameError => e - raise NonInferrableMailerError.new(name) -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `humanize` - -The method `humanize` tweaks an attribute name for display to end users. - -Specifically performs these transformations: - - * Applies human inflection rules to the argument. - * Deletes leading underscores, if any. - * Removes a "_id" suffix if present. - * Replaces underscores with spaces, if any. - * Downcases all words except acronyms. - * Capitalizes the first word. - -The capitalization of the first word can be turned off by setting the -+:capitalize+ option to false (default is true). - -```ruby -"name".humanize # => "Name" -"author_id".humanize # => "Author" -"author_id".humanize(capitalize: false) # => "author" -"comments_count".humanize # => "Comments count" -"_id".humanize # => "Id" -``` - -If "SSL" was defined to be an acronym: - -```ruby -'ssl_error'.humanize # => "SSL error" -``` - -The helper method `full_messages` uses `humanize` as a fallback to include -attribute names: - -```ruby -def full_messages - full_messages = [] - - each do |attribute, messages| - ... - attr_name = attribute.to_s.gsub('.', '_').humanize - attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - ... - end - - full_messages -end -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -#### `foreign_key` - -The method `foreign_key` gives a foreign key column name from a class name. To do so it demodulizes, underscores, and adds "_id": - -```ruby -"User".foreign_key # => "user_id" -"InvoiceLine".foreign_key # => "invoice_line_id" -"Admin::Session".foreign_key # => "session_id" -``` - -Pass a false argument if you do not want the underscore in "_id": - -```ruby -"User".foreign_key(false) # => "userid" -``` - -Associations use this method to infer foreign keys, for example `has_one` and `has_many` do this: - -```ruby -# active_record/associations.rb -foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key -``` - -NOTE: 定义于 `active_support/core_ext/string/inflections.rb`. - -### Conversions - -#### `to_date`, `to_time`, `to_datetime` - -The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wrappers around `Date._parse`: - -```ruby -"2010-07-27".to_date # => Tue, 27 Jul 2010 -"2010-07-27 23:37:00".to_time # => Tue Jul 27 23:37:00 UTC 2010 -"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000 -``` - -`to_time` receives an optional argument `:utc` or `:local`, to indicate which time zone you want the time in: - -```ruby -"2010-07-27 23:42:00".to_time(:utc) # => Tue Jul 27 23:42:00 UTC 2010 -"2010-07-27 23:42:00".to_time(:local) # => Tue Jul 27 23:42:00 +0200 2010 -``` - -Default is `:utc`. - -Please refer to the documentation of `Date._parse` for further details. - -INFO: The three of them return `nil` for blank receivers. - -NOTE: 定义于 `active_support/core_ext/string/conversions.rb`. - -Extensions to `Numeric` ------------------------ - -### Bytes - -All numbers respond to these methods: - -```ruby -bytes -kilobytes -megabytes -gigabytes -terabytes -petabytes -exabytes -``` - -They return the corresponding amount of bytes, using a conversion factor of 1024: - -```ruby -2.kilobytes # => 2048 -3.megabytes # => 3145728 -3.5.gigabytes # => 3758096384 --4.exabytes # => -4611686018427387904 -``` - -Singular forms are aliased so you are able to say: - -```ruby -1.megabyte # => 1048576 -``` - -NOTE: 定义于 `active_support/core_ext/numeric/bytes.rb`. - -### Time - -Enables the use of time calculations and declarations, like `45.minutes + 2.hours + 4.years`. - -These methods use Time#advance for precise date calculations when using from_now, ago, etc. -as well as adding or subtracting their results from a Time object. For example: - -```ruby -# equivalent to Time.current.advance(months: 1) -1.month.from_now - -# equivalent to Time.current.advance(years: 2) -2.years.from_now - -# equivalent to Time.current.advance(months: 4, years: 5) -(4.months + 5.years).from_now -``` - -While these methods provide precise calculation when used as in the examples above, care -should be taken to note that this is not true if the result of `months', `years', etc is -converted before use: - -```ruby -# equivalent to 30.days.to_i.from_now -1.month.to_i.from_now - -# equivalent to 365.25.days.to_f.from_now -1.year.to_f.from_now -``` - -In such cases, Ruby's core [Date](http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html) and -[Time](http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html) should be used for precision -date and time arithmetic. - -NOTE: 定义于 `active_support/core_ext/numeric/time.rb`. - -### Formatting - -Enables the formatting of numbers in a variety of ways. - -Produce a string representation of a number as a telephone number: - -```ruby -5551234.to_s(:phone) -# => 555-1234 -1235551234.to_s(:phone) -# => 123-555-1234 -1235551234.to_s(:phone, area_code: true) -# => (123) 555-1234 -1235551234.to_s(:phone, delimiter: " ") -# => 123 555 1234 -1235551234.to_s(:phone, area_code: true, extension: 555) -# => (123) 555-1234 x 555 -1235551234.to_s(:phone, country_code: 1) -# => +1-123-555-1234 -``` - -Produce a string representation of a number as currency: - -```ruby -1234567890.50.to_s(:currency) # => $1,234,567,890.50 -1234567890.506.to_s(:currency) # => $1,234,567,890.51 -1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506 -``` - -Produce a string representation of a number as a percentage: - -```ruby -100.to_s(:percentage) -# => 100.000% -100.to_s(:percentage, precision: 0) -# => 100% -1000.to_s(:percentage, delimiter: '.', separator: ',') -# => 1.000,000% -302.24398923423.to_s(:percentage, precision: 5) -# => 302.24399% -``` - -Produce a string representation of a number in delimited form: - -```ruby -12345678.to_s(:delimited) # => 12,345,678 -12345678.05.to_s(:delimited) # => 12,345,678.05 -12345678.to_s(:delimited, delimiter: ".") # => 12.345.678 -12345678.to_s(:delimited, delimiter: ",") # => 12,345,678 -12345678.05.to_s(:delimited, separator: " ") # => 12,345,678 05 -``` - -Produce a string representation of a number rounded to a precision: - -```ruby -111.2345.to_s(:rounded) # => 111.235 -111.2345.to_s(:rounded, precision: 2) # => 111.23 -13.to_s(:rounded, precision: 5) # => 13.00000 -389.32314.to_s(:rounded, precision: 0) # => 389 -111.2345.to_s(:rounded, significant: true) # => 111 -``` - -Produce a string representation of a number as a human-readable number of bytes: - -```ruby -123.to_s(:human_size) # => 123 Bytes -1234.to_s(:human_size) # => 1.21 KB -12345.to_s(:human_size) # => 12.1 KB -1234567.to_s(:human_size) # => 1.18 MB -1234567890.to_s(:human_size) # => 1.15 GB -1234567890123.to_s(:human_size) # => 1.12 TB -``` - -Produce a string representation of a number in human-readable words: - -```ruby -123.to_s(:human) # => "123" -1234.to_s(:human) # => "1.23 Thousand" -12345.to_s(:human) # => "12.3 Thousand" -1234567.to_s(:human) # => "1.23 Million" -1234567890.to_s(:human) # => "1.23 Billion" -1234567890123.to_s(:human) # => "1.23 Trillion" -1234567890123456.to_s(:human) # => "1.23 Quadrillion" -``` - -NOTE: 定义于 `active_support/core_ext/numeric/conversions.rb`. - -Extensions to `Integer` ------------------------ - -### `multiple_of?` - -The method `multiple_of?` tests whether an integer is multiple of the argument: - -```ruby -2.multiple_of?(1) # => true -1.multiple_of?(2) # => false -``` - -NOTE: 定义于 `active_support/core_ext/integer/multiple.rb`. - -### `ordinal` - -The method `ordinal` returns the ordinal suffix string corresponding to the receiver integer: - -```ruby -1.ordinal # => "st" -2.ordinal # => "nd" -53.ordinal # => "rd" -2009.ordinal # => "th" --21.ordinal # => "st" --134.ordinal # => "th" -``` - -NOTE: 定义于 `active_support/core_ext/integer/inflections.rb`. - -### `ordinalize` - -The method `ordinalize` returns the ordinal string corresponding to the receiver integer. In comparison, note that the `ordinal` method returns **only** the suffix string. - -```ruby -1.ordinalize # => "1st" -2.ordinalize # => "2nd" -53.ordinalize # => "53rd" -2009.ordinalize # => "2009th" --21.ordinalize # => "-21st" --134.ordinalize # => "-134th" -``` - -NOTE: 定义于 `active_support/core_ext/integer/inflections.rb`. - -Extensions to `BigDecimal` --------------------------- -### `to_s` - -The method `to_s` is aliased to `to_formatted_s`. This provides a convenient way to display a BigDecimal value in floating-point notation: - -```ruby -BigDecimal.new(5.00, 6).to_s # => "5.0" -``` - -### `to_formatted_s` - -Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: - -```ruby -BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" -``` - -and that symbol specifiers are also supported: - -```ruby -BigDecimal.new(5.00, 6).to_formatted_s(:db) # => "5.0" -``` - -Engineering notation is still supported: - -```ruby -BigDecimal.new(5.00, 6).to_formatted_s("e") # => "0.5E1" -``` - -Extensions to `Enumerable` --------------------------- - -### `sum` - -The method `sum` adds the elements of an enumerable: - -```ruby -[1, 2, 3].sum # => 6 -(1..100).sum # => 5050 -``` - -Addition only assumes the elements respond to `+`: - -```ruby -[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4] -%w(foo bar baz).sum # => "foobarbaz" -{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1] -``` - -The sum of an empty collection is zero by default, but this is customizable: - -```ruby -[].sum # => 0 -[].sum(1) # => 1 -``` - -If a block is given, `sum` becomes an iterator that yields the elements of the collection and sums the returned values: - -```ruby -(1..5).sum {|n| n * 2 } # => 30 -[2, 4, 6, 8, 10].sum # => 30 -``` - -The sum of an empty receiver can be customized in this form as well: - -```ruby -[].sum(1) {|n| n**3} # => 1 -``` - -NOTE: 定义于 `active_support/core_ext/enumerable.rb`. - -### `index_by` - -The method `index_by` generates a hash with the elements of an enumerable indexed by some key. - -It iterates through the collection and passes each element to a block. The element will be keyed by the value returned by the block: - -```ruby -invoices.index_by(&:number) -# => {'2009-032' => , '2009-008' => , ...} -``` - -WARNING. Keys should normally be unique. If the block returns the same value for different elements no collection is built for that key. The last item will win. - -NOTE: 定义于 `active_support/core_ext/enumerable.rb`. - -### `many?` - -The method `many?` is shorthand for `collection.size > 1`: - -```erb -<% if pages.many? %> - <%= pagination_links %> -<% end %> -``` - -If an optional block is given, `many?` only takes into account those elements that return true: - -```ruby -@see_more = videos.many? {|video| video.category == params[:category]} -``` - -NOTE: 定义于 `active_support/core_ext/enumerable.rb`. - -### `exclude?` - -The predicate `exclude?` tests whether a given object does **not** belong to the collection. It is the negation of the built-in `include?`: - -```ruby -to_visit << node if visited.exclude?(node) -``` - -NOTE: 定义于 `active_support/core_ext/enumerable.rb`. - -Extensions to `Array` ---------------------- - -### Accessing - -Active Support augments the API of arrays to ease certain ways of accessing them. For example, `to` returns the subarray of elements up to the one at the passed index: - -```ruby -%w(a b c d).to(2) # => %w(a b c) -[].to(7) # => [] -``` - -Similarly, `from` returns the tail from the element at the passed index to the end. If the index is greater than the length of the array, it returns an empty array. - -```ruby -%w(a b c d).from(2) # => %w(c d) -%w(a b c d).from(10) # => [] -[].from(0) # => [] -``` - -The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element (`first` is built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available. - -```ruby -%w(a b c d).third # => c -%w(a b c d).fifth # => nil -``` - -NOTE: 定义于 `active_support/core_ext/array/access.rb`. - -### Adding Elements - -#### `prepend` - -This method is an alias of `Array#unshift`. - -```ruby -%w(a b c d).prepend('e') # => %w(e a b c d) -[].prepend(10) # => [10] -``` - -NOTE: 定义于 `active_support/core_ext/array/prepend_and_append.rb`. - -#### `append` - -This method is an alias of `Array#<<`. - -```ruby -%w(a b c d).append('e') # => %w(a b c d e) -[].append([1,2]) # => [[1,2]] -``` - -NOTE: 定义于 `active_support/core_ext/array/prepend_and_append.rb`. - -### Options Extraction - -When the last argument in a method call is a hash, except perhaps for a `&block` argument, Ruby allows you to omit the brackets: - -```ruby -User.exists?(email: params[:email]) -``` - -That syntactic sugar is used a lot in Rails to avoid positional arguments where there would be too many, offering instead interfaces that emulate named parameters. In particular it is very idiomatic to use a trailing hash for options. - -If a method expects a variable number of arguments and uses `*` in its declaration, however, such an options hash ends up being an item of the array of arguments, where it loses its role. - -In those cases, you may give an options hash a distinguished treatment with `extract_options!`. This method checks the type of the last item of an array. If it is a hash it pops it and returns it, otherwise it returns an empty hash. - -Let's see for example the definition of the `caches_action` controller macro: - -```ruby -def caches_action(*actions) - return unless cache_configured? - options = actions.extract_options! - ... -end -``` - -This method receives an arbitrary number of action names, and an optional hash of options as last argument. With the call to `extract_options!` you obtain the options hash and remove it from `actions` in a simple and explicit way. - -NOTE: 定义于 `active_support/core_ext/array/extract_options.rb`. - -### Conversions - -#### `to_sentence` - -The method `to_sentence` turns an array into a string containing a sentence that enumerates its items: - -```ruby -%w().to_sentence # => "" -%w(Earth).to_sentence # => "Earth" -%w(Earth Wind).to_sentence # => "Earth and Wind" -%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire" -``` - -This method accepts three options: - -* `:two_words_connector`: What is used for arrays of length 2. Default is " and ". -* `:words_connector`: What is used to join the elements of arrays with 3 or more elements, except for the last two. Default is ", ". -* `:last_word_connector`: What is used to join the last items of an array with 3 or more elements. Default is ", and ". - -The defaults for these options can be localized, their keys are: - -| Option | I18n key | -| ---------------------- | ----------------------------------- | -| `:two_words_connector` | `support.array.two_words_connector` | -| `:words_connector` | `support.array.words_connector` | -| `:last_word_connector` | `support.array.last_word_connector` | - -NOTE: 定义于 `active_support/core_ext/array/conversions.rb`. - -#### `to_formatted_s` - -The method `to_formatted_s` acts like `to_s` by default. - -If the array contains items that respond to `id`, however, the symbol -`:db` may be passed as argument. That's typically used with -collections of Active Record objects. Returned strings are: - -```ruby -[].to_formatted_s(:db) # => "null" -[user].to_formatted_s(:db) # => "8456" -invoice.lines.to_formatted_s(:db) # => "23,567,556,12" -``` - -Integers in the example above are supposed to come from the respective calls to `id`. - -NOTE: 定义于 `active_support/core_ext/array/conversions.rb`. - -#### `to_xml` - -The method `to_xml` returns a string containing an XML representation of its receiver: - -```ruby -Contributor.limit(2).order(:rank).to_xml -# => -# -# -# -# 4356 -# Jeremy Kemper -# 1 -# jeremy-kemper -# -# -# 4404 -# David Heinemeier Hansson -# 2 -# david-heinemeier-hansson -# -# -``` - -To do so it sends `to_xml` to every item in turn, and collects the results under a root node. All items must respond to `to_xml`, an exception is raised otherwise. - -By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with `is_a?`) and they are not hashes. In the example above that's "contributors". - -If there's any element that does not belong to the type of the first one the root node becomes "objects": - -```ruby -[Contributor.first, Commit.first].to_xml -# => -# -# -# -# 4583 -# Aaron Batalion -# 53 -# aaron-batalion -# -# -# Joshua Peek -# 2009-09-02T16:44:36Z -# origin/master -# 2009-09-02T16:44:36Z -# Joshua Peek -# -# 190316 -# false -# Kill AMo observing wrap_with_notifications since ARes was only using it -# 723a47bfb3708f968821bc969a9a3fc873a3ed58 -# -# -``` - -If the receiver is an array of hashes the root element is by default also "objects": - -```ruby -[{a: 1, b: 2}, {c: 3}].to_xml -# => -# -# -# -# 2 -# 1 -# -# -# 3 -# -# -``` - -WARNING. If the collection is empty the root element is by default "nil-classes". That's a gotcha, for example the root element of the list of contributors above would not be "contributors" if the collection was empty, but "nil-classes". You may use the `:root` option to ensure a consistent root element. - -The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "object". The option `:children` allows you to set these node names. - -The default XML builder is a fresh instance of `Builder::XmlMarkup`. You can configure your own builder via the `:builder` option. The method also accepts options like `:dasherize` and friends, they are forwarded to the builder: - -```ruby -Contributor.limit(2).order(:rank).to_xml(skip_types: true) -# => -# -# -# -# 4356 -# Jeremy Kemper -# 1 -# jeremy-kemper -# -# -# 4404 -# David Heinemeier Hansson -# 2 -# david-heinemeier-hansson -# -# -``` - -NOTE: 定义于 `active_support/core_ext/array/conversions.rb`. - -### Wrapping - -The method `Array.wrap` wraps its argument in an array unless it is already an array (or array-like). - -Specifically: - -* If the argument is `nil` an empty list is returned. -* Otherwise, if the argument responds to `to_ary` it is invoked, and if the value of `to_ary` is not `nil`, it is returned. -* Otherwise, an array with the argument as its single element is returned. - -```ruby -Array.wrap(nil) # => [] -Array.wrap([1, 2, 3]) # => [1, 2, 3] -Array.wrap(0) # => [0] -``` - -This method is similar in purpose to `Kernel#Array`, but there are some differences: - -* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns `nil` right away. -* If the returned value from `to_ary` is neither `nil` nor an `Array` object, `Kernel#Array` raises an exception, while `Array.wrap` does not, it just returns the value. -* It does not call `to_a` on the argument, though special-cases `nil` to return an empty array. - -The last point is particularly worth comparing for some enumerables: - -```ruby -Array.wrap(foo: :bar) # => [{:foo=>:bar}] -Array(foo: :bar) # => [[:foo, :bar]] -``` - -There's also a related idiom that uses the splat operator: - -```ruby -[*object] -``` - -which in Ruby 1.8 returns `[nil]` for `nil`, and calls to `Array(object)` otherwise. (Please if you know the exact behavior in 1.9 contact fxn.) - -Thus, in this case the behavior is different for `nil`, and the differences with `Kernel#Array` explained above apply to the rest of `object`s. - -NOTE: 定义于 `active_support/core_ext/array/wrap.rb`. - -### Duplicating - -The method `Array.deep_dup` duplicates itself and all objects inside -recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside. - -```ruby -array = [1, [2, 3]] -dup = array.deep_dup -dup[1][2] = 4 -array[1][2] == nil # => true -``` - -NOTE: 定义于 `active_support/core_ext/object/deep_dup.rb`. - -### Grouping - -#### `in_groups_of(number, fill_with = nil)` - -The method `in_groups_of` splits an array into consecutive groups of a certain size. It returns an array with the groups: - -```ruby -[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]] -``` - -or yields them in turn if a block is passed: - -```html+erb -<% sample.in_groups_of(3) do |a, b, c| %> - - <%= a %> - <%= b %> - <%= c %> - -<% end %> -``` - -The first example shows `in_groups_of` fills the last group with as many `nil` elements as needed to have the requested size. You can change this padding value using the second optional argument: - -```ruby -[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]] -``` - -And you can tell the method not to fill the last group passing `false`: - -```ruby -[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]] -``` - -As a consequence `false` can't be a used as a padding value. - -NOTE: 定义于 `active_support/core_ext/array/grouping.rb`. - -#### `in_groups(number, fill_with = nil)` - -The method `in_groups` splits an array into a certain number of groups. The method returns an array with the groups: - -```ruby -%w(1 2 3 4 5 6 7).in_groups(3) -# => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]] -``` - -or yields them in turn if a block is passed: - -```ruby -%w(1 2 3 4 5 6 7).in_groups(3) {|group| p group} -["1", "2", "3"] -["4", "5", nil] -["6", "7", nil] -``` - -The examples above show that `in_groups` fills some groups with a trailing `nil` element as needed. A group can get at most one of these extra elements, the rightmost one if any. And the groups that have them are always the last ones. - -You can change this padding value using the second optional argument: - -```ruby -%w(1 2 3 4 5 6 7).in_groups(3, "0") -# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]] -``` - -And you can tell the method not to fill the smaller groups passing `false`: - -```ruby -%w(1 2 3 4 5 6 7).in_groups(3, false) -# => [["1", "2", "3"], ["4", "5"], ["6", "7"]] -``` - -As a consequence `false` can't be a used as a padding value. - -NOTE: 定义于 `active_support/core_ext/array/grouping.rb`. - -#### `split(value = nil)` - -The method `split` divides an array by a separator and returns the resulting chunks. - -If a block is passed the separators are those elements of the array for which the block returns true: - -```ruby -(-5..5).to_a.split { |i| i.multiple_of?(4) } -# => [[-5], [-3, -2, -1], [1, 2, 3], [5]] -``` - -Otherwise, the value received as argument, which defaults to `nil`, is the separator: - -```ruby -[0, 1, -5, 1, 1, "foo", "bar"].split(1) -# => [[0], [-5], [], ["foo", "bar"]] -``` - -TIP: Observe in the previous example that consecutive separators result in empty arrays. - -NOTE: 定义于 `active_support/core_ext/array/grouping.rb`. - -Extensions to `Hash` --------------------- - -### Conversions - -#### `to_xml` - -The method `to_xml` returns a string containing an XML representation of its receiver: - -```ruby -{"foo" => 1, "bar" => 2}.to_xml -# => -# -# -# 1 -# 2 -# -``` - -To do so, the method loops over the pairs and builds nodes that depend on the _values_. Given a pair `key`, `value`: - -* If `value` is a hash there's a recursive call with `key` as `:root`. - -* If `value` is an array there's a recursive call with `key` as `:root`, and `key` singularized as `:children`. - -* If `value` is a callable object it must expect one or two arguments. Depending on the arity, the callable is invoked with the `options` hash as first argument with `key` as `:root`, and `key` singularized as second argument. Its return value becomes a new node. - -* If `value` responds to `to_xml` the method is invoked with `key` as `:root`. - -* Otherwise, a node with `key` as tag is created with a string representation of `value` as text node. If `value` is `nil` an attribute "nil" set to "true" is added. Unless the option `:skip_types` exists and is true, an attribute "type" is added as well according to the following mapping: - -```ruby -XML_TYPE_NAMES = { - "Symbol" => "symbol", - "Fixnum" => "integer", - "Bignum" => "integer", - "BigDecimal" => "decimal", - "Float" => "float", - "TrueClass" => "boolean", - "FalseClass" => "boolean", - "Date" => "date", - "DateTime" => "datetime", - "Time" => "datetime" -} -``` - -By default the root node is "hash", but that's configurable via the `:root` option. - -The default XML builder is a fresh instance of `Builder::XmlMarkup`. You can configure your own builder with the `:builder` option. The method also accepts options like `:dasherize` and friends, they are forwarded to the builder. - -NOTE: 定义于 `active_support/core_ext/hash/conversions.rb`. - -### Merging - -Ruby has a built-in method `Hash#merge` that merges two hashes: - -```ruby -{a: 1, b: 1}.merge(a: 0, c: 2) -# => {:a=>0, :b=>1, :c=>2} -``` - -Active Support defines a few more ways of merging hashes that may be convenient. - -#### `reverse_merge` and `reverse_merge!` - -In case of collision the key in the hash of the argument wins in `merge`. You can support option hashes with default values in a compact way with this idiom: - -```ruby -options = {length: 30, omission: "..."}.merge(options) -``` - -Active Support defines `reverse_merge` in case you prefer this alternative notation: - -```ruby -options = options.reverse_merge(length: 30, omission: "...") -``` - -And a bang version `reverse_merge!` that performs the merge in place: - -```ruby -options.reverse_merge!(length: 30, omission: "...") -``` - -WARNING. Take into account that `reverse_merge!` may change the hash in the caller, which may or may not be a good idea. - -NOTE: 定义于 `active_support/core_ext/hash/reverse_merge.rb`. - -#### `reverse_update` - -The method `reverse_update` is an alias for `reverse_merge!`, explained above. - -WARNING. Note that `reverse_update` has no bang. - -NOTE: 定义于 `active_support/core_ext/hash/reverse_merge.rb`. - -#### `deep_merge` and `deep_merge!` - -As you can see in the previous example if a key is found in both hashes the value in the one in the argument wins. - -Active Support defines `Hash#deep_merge`. In a deep merge, if a key is found in both hashes and their values are hashes in turn, then their _merge_ becomes the value in the resulting hash: - -```ruby -{a: {b: 1}}.deep_merge(a: {c: 2}) -# => {:a=>{:b=>1, :c=>2}} -``` - -The method `deep_merge!` performs a deep merge in place. - -NOTE: 定义于 `active_support/core_ext/hash/deep_merge.rb`. - -### Deep duplicating - -The method `Hash.deep_dup` duplicates itself and all keys and values -inside recursively with Active Support method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside. - -```ruby -hash = { a: 1, b: { c: 2, d: [3, 4] } } - -dup = hash.deep_dup -dup[:b][:e] = 5 -dup[:b][:d] << 5 - -hash[:b][:e] == nil # => true -hash[:b][:d] == [3, 4] # => true -``` - -NOTE: 定义于 `active_support/core_ext/object/deep_dup.rb`. - -### Working with Keys - -#### `except` and `except!` - -The method `except` returns a hash with the keys in the argument list removed, if present: - -```ruby -{a: 1, b: 2}.except(:a) # => {:b=>2} -``` - -If the receiver responds to `convert_key`, the method is called on each of the arguments. This allows `except` to play nice with hashes with indifferent access for instance: - -```ruby -{a: 1}.with_indifferent_access.except(:a) # => {} -{a: 1}.with_indifferent_access.except("a") # => {} -``` - -There's also the bang variant `except!` that removes keys in the very receiver. - -NOTE: 定义于 `active_support/core_ext/hash/except.rb`. - -#### `transform_keys` and `transform_keys!` - -The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver: - -```ruby -{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase } -# => {"" => nil, "A" => :a, "1" => 1} -``` - -In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: - -```ruby -{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase } -# The result could either be -# => {"A"=>2} -# or -# => {"A"=>1} -``` - -This method may be useful for example to build specialized conversions. For instance `stringify_keys` and `symbolize_keys` use `transform_keys` to perform their key conversions: - -```ruby -def stringify_keys - transform_keys { |key| key.to_s } -end -... -def symbolize_keys - transform_keys { |key| key.to_sym rescue key } -end -``` - -There's also the bang variant `transform_keys!` that applies the block operations to keys in the very receiver. - -Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is: - -```ruby -{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase } -# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} -``` - -NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. - -#### `stringify_keys` and `stringify_keys!` - -The method `stringify_keys` returns a hash that has a stringified version of the keys in the receiver. It does so by sending `to_s` to them: - -```ruby -{nil => nil, 1 => 1, a: :a}.stringify_keys -# => {"" => nil, "a" => :a, "1" => 1} -``` - -In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: - -```ruby -{"a" => 1, a: 2}.stringify_keys -# The result could either be -# => {"a"=>2} -# or -# => {"a"=>1} -``` - -This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionView::Helpers::FormHelper` defines: - -```ruby -def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0") - options = options.stringify_keys - options["type"] = "checkbox" - ... -end -``` - -The second line can safely access the "type" key, and let the user to pass either `:type` or "type". - -There's also the bang variant `stringify_keys!` that stringifies keys in the very receiver. - -Besides that, one can use `deep_stringify_keys` and `deep_stringify_keys!` to stringify all the keys in the given hash and all the hashes nested into it. An example of the result is: - -```ruby -{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_stringify_keys -# => {""=>nil, "1"=>1, "nested"=>{"a"=>3, "5"=>5}} -``` - -NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. - -#### `symbolize_keys` and `symbolize_keys!` - -The method `symbolize_keys` returns a hash that has a symbolized version of the keys in the receiver, where possible. It does so by sending `to_sym` to them: - -```ruby -{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys -# => {1=>1, nil=>nil, :a=>"a"} -``` - -WARNING. Note in the previous example only one key was symbolized. - -In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: - -```ruby -{"a" => 1, a: 2}.symbolize_keys -# The result could either be -# => {:a=>2} -# or -# => {:a=>1} -``` - -This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionController::UrlRewriter` defines - -```ruby -def rewrite_path(options) - options = options.symbolize_keys - options.update(options[:params].symbolize_keys) if options[:params] - ... -end -``` - -The second line can safely access the `:params` key, and let the user to pass either `:params` or "params". - -There's also the bang variant `symbolize_keys!` that symbolizes keys in the very receiver. - -Besides that, one can use `deep_symbolize_keys` and `deep_symbolize_keys!` to symbolize all the keys in the given hash and all the hashes nested into it. An example of the result is: - -```ruby -{nil => nil, 1 => 1, "nested" => {"a" => 3, 5 => 5}}.deep_symbolize_keys -# => {nil=>nil, 1=>1, nested:{a:3, 5=>5}} -``` - -NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. - -#### `to_options` and `to_options!` - -The methods `to_options` and `to_options!` are respectively aliases of `symbolize_keys` and `symbolize_keys!`. - -NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. - -#### `assert_valid_keys` - -The method `assert_valid_keys` receives an arbitrary number of arguments, and checks whether the receiver has any key outside that white list. If it does `ArgumentError` is raised. - -```ruby -{a: 1}.assert_valid_keys(:a) # passes -{a: 1}.assert_valid_keys("a") # ArgumentError -``` - -Active Record does not accept unknown options when building associations, for example. It implements that control via `assert_valid_keys`. - -NOTE: 定义于 `active_support/core_ext/hash/keys.rb`. - -### Slicing - -Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes: - -```ruby -{a: 1, b: 2, c: 3}.slice(:a, :c) -# => {:c=>3, :a=>1} - -{a: 1, b: 2, c: 3}.slice(:b, :X) -# => {:b=>2} # non-existing keys are ignored -``` - -If the receiver responds to `convert_key` keys are normalized: - -```ruby -{a: 1, b: 2}.with_indifferent_access.slice("a") -# => {:a=>1} -``` - -NOTE. Slicing may come in handy for sanitizing option hashes with a white list of keys. - -There's also `slice!` which in addition to perform a slice in place returns what's removed: - -```ruby -hash = {a: 1, b: 2} -rest = hash.slice!(:a) # => {:b=>2} -hash # => {:a=>1} -``` - -NOTE: 定义于 `active_support/core_ext/hash/slice.rb`. - -### Extracting - -The method `extract!` removes and returns the key/value pairs matching the given keys. - -```ruby -hash = {a: 1, b: 2} -rest = hash.extract!(:a) # => {:a=>1} -hash # => {:b=>2} -``` - -The method `extract!` returns the same subclass of Hash, that the receiver is. - -```ruby -hash = {a: 1, b: 2}.with_indifferent_access -rest = hash.extract!(:a).class -# => ActiveSupport::HashWithIndifferentAccess -``` - -NOTE: 定义于 `active_support/core_ext/hash/slice.rb`. - -### Indifferent Access - -The method `with_indifferent_access` returns an `ActiveSupport::HashWithIndifferentAccess` out of its receiver: - -```ruby -{a: 1}.with_indifferent_access["a"] # => 1 -``` - -NOTE: 定义于 `active_support/core_ext/hash/indifferent_access.rb`. - -### Compacting - -The methods `compact` and `compact!` return a Hash without items with `nil` value. - -```ruby -{a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2} -``` - -NOTE: 定义于 `active_support/core_ext/hash/compact.rb`. - -Extensions to `Regexp` ----------------------- - -### `multiline?` - -The method `multiline?` says whether a regexp has the `/m` flag set, that is, whether the dot matches newlines. - -```ruby -%r{.}.multiline? # => false -%r{.}m.multiline? # => true - -Regexp.new('.').multiline? # => false -Regexp.new('.', Regexp::MULTILINE).multiline? # => true -``` - -Rails uses this method in a single place, also in the routing code. Multiline regexps are disallowed for route requirements and this flag eases enforcing that constraint. - -```ruby -def assign_route_options(segments, defaults, requirements) - ... - if requirement.multiline? - raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" - end - ... -end -``` - -NOTE: 定义于 `active_support/core_ext/regexp.rb`. - -Extensions to `Range` ---------------------- - -### `to_s` - -Active Support extends the method `Range#to_s` so that it understands an optional format argument. As of this writing the only supported non-default format is `:db`: - -```ruby -(Date.today..Date.tomorrow).to_s -# => "2009-10-25..2009-10-26" - -(Date.today..Date.tomorrow).to_s(:db) -# => "BETWEEN '2009-10-25' AND '2009-10-26'" -``` - -As the example depicts, the `:db` format generates a `BETWEEN` SQL clause. That is used by Active Record in its support for range values in conditions. - -NOTE: 定义于 `active_support/core_ext/range/conversions.rb`. - -### `include?` - -The methods `Range#include?` and `Range#===` say whether some value falls between the ends of a given instance: - -```ruby -(2..3).include?(Math::E) # => true -``` - -Active Support extends these methods so that the argument may be another range in turn. In that case we test whether the ends of the argument range belong to the receiver themselves: - -```ruby -(1..10).include?(3..7) # => true -(1..10).include?(0..7) # => false -(1..10).include?(3..11) # => false -(1...9).include?(3..9) # => false - -(1..10) === (3..7) # => true -(1..10) === (0..7) # => false -(1..10) === (3..11) # => false -(1...9) === (3..9) # => false -``` - -NOTE: 定义于 `active_support/core_ext/range/include_range.rb`. - -### `overlaps?` - -The method `Range#overlaps?` says whether any two given ranges have non-void intersection: - -```ruby -(1..10).overlaps?(7..11) # => true -(1..10).overlaps?(0..7) # => true -(1..10).overlaps?(11..27) # => false -``` - -NOTE: 定义于 `active_support/core_ext/range/overlaps.rb`. - -Extensions to `Proc` --------------------- - -### `bind` - -As you surely know Ruby has an `UnboundMethod` class whose instances are methods that belong to the limbo of methods without a self. The method `Module#instance_method` returns an unbound method for example: - -```ruby -Hash.instance_method(:delete) # => # -``` - -An unbound method is not callable as is, you need to bind it first to an object with `bind`: - -```ruby -clear = Hash.instance_method(:clear) -clear.bind({a: 1}).call # => {} -``` - -Active Support defines `Proc#bind` with an analogous purpose: - -```ruby -Proc.new { size }.bind([]).call # => 0 -``` - -As you see that's callable and bound to the argument, the return value is indeed a `Method`. - -NOTE: To do so `Proc#bind` actually creates a method under the hood. If you ever see a method with a weird name like `__bind_1256598120_237302` in a stack trace you know now where it comes from. - -Action Pack uses this trick in `rescue_from` for example, which accepts the name of a method and also a proc as callbacks for a given rescued exception. It has to call them in either case, so a bound method is returned by `handler_for_rescue`, thus simplifying the code in the caller: - -```ruby -def handler_for_rescue(exception) - _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler| - ... - end - - case rescuer - when Symbol - method(rescuer) - when Proc - rescuer.bind(self) - end -end -``` - -NOTE: 定义于 `active_support/core_ext/proc.rb`. - -Extensions to `Date` --------------------- - -### Calculations - -NOTE: All the following methods are defined in `active_support/core_ext/date/calculations.rb`. - -INFO: The following calculation methods have edge cases in October 1582, since days 5..14 just do not exist. This guide does not document their behavior around those days for brevity, but it is enough to say that they do what you would expect. That is, `Date.new(1582, 10, 4).tomorrow` returns `Date.new(1582, 10, 15)` and so on. Please check `test/core_ext/date_ext_test.rb` in the Active Support test suite for expected behavior. - -#### `Date.current` - -Active Support defines `Date.current` to be today in the current time zone. That's like `Date.today`, except that it honors the user time zone, if defined. It also defines `Date.yesterday` and `Date.tomorrow`, and the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Date.current`. - -When making Date comparisons using methods which honor the user time zone, make sure to use `Date.current` and not `Date.today`. There are cases where the user time zone might be in the future compared to the system time zone, which `Date.today` uses by default. This means `Date.today` may equal `Date.yesterday`. - -#### Named dates - -##### `prev_year`, `next_year` - -In Ruby 1.9 `prev_year` and `next_year` return a date with the same day/month in the last or next year: - -```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_year # => Fri, 08 May 2009 -d.next_year # => Sun, 08 May 2011 -``` - -If date is the 29th of February of a leap year, you obtain the 28th: - -```ruby -d = Date.new(2000, 2, 29) # => Tue, 29 Feb 2000 -d.prev_year # => Sun, 28 Feb 1999 -d.next_year # => Wed, 28 Feb 2001 -``` - -`prev_year` is aliased to `last_year`. - -##### `prev_month`, `next_month` - -In Ruby 1.9 `prev_month` and `next_month` return the date with the same day in the last or next month: - -```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_month # => Thu, 08 Apr 2010 -d.next_month # => Tue, 08 Jun 2010 -``` - -If such a day does not exist, the last day of the corresponding month is returned: - -```ruby -Date.new(2000, 5, 31).prev_month # => Sun, 30 Apr 2000 -Date.new(2000, 3, 31).prev_month # => Tue, 29 Feb 2000 -Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000 -Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000 -``` - -`prev_month` is aliased to `last_month`. - -##### `prev_quarter`, `next_quarter` - -Same as `prev_month` and `next_month`. It returns the date with the same day in the previous or next quarter: - -```ruby -t = Time.local(2010, 5, 8) # => Sat, 08 May 2010 -t.prev_quarter # => Mon, 08 Feb 2010 -t.next_quarter # => Sun, 08 Aug 2010 -``` - -If such a day does not exist, the last day of the corresponding month is returned: - -```ruby -Time.local(2000, 7, 31).prev_quarter # => Sun, 30 Apr 2000 -Time.local(2000, 5, 31).prev_quarter # => Tue, 29 Feb 2000 -Time.local(2000, 10, 31).prev_quarter # => Mon, 30 Oct 2000 -Time.local(2000, 11, 31).next_quarter # => Wed, 28 Feb 2001 -``` - -`prev_quarter` is aliased to `last_quarter`. - -##### `beginning_of_week`, `end_of_week` - -The methods `beginning_of_week` and `end_of_week` return the dates for the -beginning and end of the week, respectively. Weeks are assumed to start on -Monday, but that can be changed passing an argument, setting thread local -`Date.beginning_of_week` or `config.beginning_of_week`. - -```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.beginning_of_week # => Mon, 03 May 2010 -d.beginning_of_week(:sunday) # => Sun, 02 May 2010 -d.end_of_week # => Sun, 09 May 2010 -d.end_of_week(:sunday) # => Sat, 08 May 2010 -``` - -`beginning_of_week` is aliased to `at_beginning_of_week` and `end_of_week` is aliased to `at_end_of_week`. - -##### `monday`, `sunday` - -The methods `monday` and `sunday` return the dates for the previous Monday and -next Sunday, respectively. - -```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.monday # => Mon, 03 May 2010 -d.sunday # => Sun, 09 May 2010 - -d = Date.new(2012, 9, 10) # => Mon, 10 Sep 2012 -d.monday # => Mon, 10 Sep 2012 - -d = Date.new(2012, 9, 16) # => Sun, 16 Sep 2012 -d.sunday # => Sun, 16 Sep 2012 -``` - -##### `prev_week`, `next_week` - -The method `next_week` receives a symbol with a day name in English (default is the thread local `Date.beginning_of_week`, or `config.beginning_of_week`, or `:monday`) and it returns the date corresponding to that day. - -```ruby -d = Date.new(2010, 5, 9) # => Sun, 09 May 2010 -d.next_week # => Mon, 10 May 2010 -d.next_week(:saturday) # => Sat, 15 May 2010 -``` - -The method `prev_week` is analogous: - -```ruby -d.prev_week # => Mon, 26 Apr 2010 -d.prev_week(:saturday) # => Sat, 01 May 2010 -d.prev_week(:friday) # => Fri, 30 Apr 2010 -``` - -`prev_week` is aliased to `last_week`. - -Both `next_week` and `prev_week` work as expected when `Date.beginning_of_week` or `config.beginning_of_week` are set. - -##### `beginning_of_month`, `end_of_month` - -The methods `beginning_of_month` and `end_of_month` return the dates for the beginning and end of the month: - -```ruby -d = Date.new(2010, 5, 9) # => Sun, 09 May 2010 -d.beginning_of_month # => Sat, 01 May 2010 -d.end_of_month # => Mon, 31 May 2010 -``` - -`beginning_of_month` is aliased to `at_beginning_of_month`, and `end_of_month` is aliased to `at_end_of_month`. - -##### `beginning_of_quarter`, `end_of_quarter` - -The methods `beginning_of_quarter` and `end_of_quarter` return the dates for the beginning and end of the quarter of the receiver's calendar year: - -```ruby -d = Date.new(2010, 5, 9) # => Sun, 09 May 2010 -d.beginning_of_quarter # => Thu, 01 Apr 2010 -d.end_of_quarter # => Wed, 30 Jun 2010 -``` - -`beginning_of_quarter` is aliased to `at_beginning_of_quarter`, and `end_of_quarter` is aliased to `at_end_of_quarter`. - -##### `beginning_of_year`, `end_of_year` - -The methods `beginning_of_year` and `end_of_year` return the dates for the beginning and end of the year: - -```ruby -d = Date.new(2010, 5, 9) # => Sun, 09 May 2010 -d.beginning_of_year # => Fri, 01 Jan 2010 -d.end_of_year # => Fri, 31 Dec 2010 -``` - -`beginning_of_year` is aliased to `at_beginning_of_year`, and `end_of_year` is aliased to `at_end_of_year`. - -#### Other Date Computations - -##### `years_ago`, `years_since` - -The method `years_ago` receives a number of years and returns the same date those many years ago: - -```ruby -date = Date.new(2010, 6, 7) -date.years_ago(10) # => Wed, 07 Jun 2000 -``` - -`years_since` moves forward in time: - -```ruby -date = Date.new(2010, 6, 7) -date.years_since(10) # => Sun, 07 Jun 2020 -``` - -If such a day does not exist, the last day of the corresponding month is returned: - -```ruby -Date.new(2012, 2, 29).years_ago(3) # => Sat, 28 Feb 2009 -Date.new(2012, 2, 29).years_since(3) # => Sat, 28 Feb 2015 -``` - -##### `months_ago`, `months_since` - -The methods `months_ago` and `months_since` work analogously for months: - -```ruby -Date.new(2010, 4, 30).months_ago(2) # => Sun, 28 Feb 2010 -Date.new(2010, 4, 30).months_since(2) # => Wed, 30 Jun 2010 -``` - -If such a day does not exist, the last day of the corresponding month is returned: - -```ruby -Date.new(2010, 4, 30).months_ago(2) # => Sun, 28 Feb 2010 -Date.new(2009, 12, 31).months_since(2) # => Sun, 28 Feb 2010 -``` - -##### `weeks_ago` - -The method `weeks_ago` works analogously for weeks: - -```ruby -Date.new(2010, 5, 24).weeks_ago(1) # => Mon, 17 May 2010 -Date.new(2010, 5, 24).weeks_ago(2) # => Mon, 10 May 2010 -``` - -##### `advance` - -The most generic way to jump to other days is `advance`. This method receives a hash with keys `:years`, `:months`, `:weeks`, `:days`, and returns a date advanced as much as the present keys indicate: - -```ruby -date = Date.new(2010, 6, 6) -date.advance(years: 1, weeks: 2) # => Mon, 20 Jun 2011 -date.advance(months: 2, days: -2) # => Wed, 04 Aug 2010 -``` - -Note in the previous example that increments may be negative. - -To perform the computation the method first increments years, then months, then weeks, and finally days. This order is important towards the end of months. Say for example we are at the end of February of 2010, and we want to move one month and one day forward. - -The method `advance` advances first one month, and then one day, the result is: - -```ruby -Date.new(2010, 2, 28).advance(months: 1, days: 1) -# => Sun, 29 Mar 2010 -``` - -While if it did it the other way around the result would be different: - -```ruby -Date.new(2010, 2, 28).advance(days: 1).advance(months: 1) -# => Thu, 01 Apr 2010 -``` - -#### Changing Components - -The method `change` allows you to get a new date which is the same as the receiver except for the given year, month, or day: - -```ruby -Date.new(2010, 12, 23).change(year: 2011, month: 11) -# => Wed, 23 Nov 2011 -``` - -This method is not tolerant to non-existing dates, if the change is invalid `ArgumentError` is raised: - -```ruby -Date.new(2010, 1, 31).change(month: 2) -# => ArgumentError: invalid date -``` - -#### Durations - -Durations can be added to and subtracted from dates: - -```ruby -d = Date.current -# => Mon, 09 Aug 2010 -d + 1.year -# => Tue, 09 Aug 2011 -d - 3.hours -# => Sun, 08 Aug 2010 21:00:00 UTC +00:00 -``` - -They translate to calls to `since` or `advance`. For example here we get the correct jump in the calendar reform: - -```ruby -Date.new(1582, 10, 4) + 1.day -# => Fri, 15 Oct 1582 -``` - -#### Timestamps - -INFO: The following methods return a `Time` object if possible, otherwise a `DateTime`. If set, they honor the user time zone. - -##### `beginning_of_day`, `end_of_day` - -The method `beginning_of_day` returns a timestamp at the beginning of the day (00:00:00): - -```ruby -date = Date.new(2010, 6, 7) -date.beginning_of_day # => Mon Jun 07 00:00:00 +0200 2010 -``` - -The method `end_of_day` returns a timestamp at the end of the day (23:59:59): - -```ruby -date = Date.new(2010, 6, 7) -date.end_of_day # => Mon Jun 07 23:59:59 +0200 2010 -``` - -`beginning_of_day` is aliased to `at_beginning_of_day`, `midnight`, `at_midnight`. - -##### `beginning_of_hour`, `end_of_hour` - -The method `beginning_of_hour` returns a timestamp at the beginning of the hour (hh:00:00): - -```ruby -date = DateTime.new(2010, 6, 7, 19, 55, 25) -date.beginning_of_hour # => Mon Jun 07 19:00:00 +0200 2010 -``` - -The method `end_of_hour` returns a timestamp at the end of the hour (hh:59:59): - -```ruby -date = DateTime.new(2010, 6, 7, 19, 55, 25) -date.end_of_hour # => Mon Jun 07 19:59:59 +0200 2010 -``` - -`beginning_of_hour` is aliased to `at_beginning_of_hour`. - -##### `beginning_of_minute`, `end_of_minute` - -The method `beginning_of_minute` returns a timestamp at the beginning of the minute (hh:mm:00): - -```ruby -date = DateTime.new(2010, 6, 7, 19, 55, 25) -date.beginning_of_minute # => Mon Jun 07 19:55:00 +0200 2010 -``` - -The method `end_of_minute` returns a timestamp at the end of the minute (hh:mm:59): - -```ruby -date = DateTime.new(2010, 6, 7, 19, 55, 25) -date.end_of_minute # => Mon Jun 07 19:55:59 +0200 2010 -``` - -`beginning_of_minute` is aliased to `at_beginning_of_minute`. - -INFO: `beginning_of_hour`, `end_of_hour`, `beginning_of_minute` and `end_of_minute` are implemented for `Time` and `DateTime` but **not** `Date` as it does not make sense to request the beginning or end of an hour or minute on a `Date` instance. - -##### `ago`, `since` - -The method `ago` receives a number of seconds as argument and returns a timestamp those many seconds ago from midnight: - -```ruby -date = Date.current # => Fri, 11 Jun 2010 -date.ago(1) # => Thu, 10 Jun 2010 23:59:59 EDT -04:00 -``` - -Similarly, `since` moves forward: - -```ruby -date = Date.current # => Fri, 11 Jun 2010 -date.since(1) # => Fri, 11 Jun 2010 00:00:01 EDT -04:00 -``` - -#### Other Time Computations - -### Conversions - -Extensions to `DateTime` ------------------------- - -WARNING: `DateTime` is not aware of DST rules and so some of these methods have edge cases when a DST change is going on. For example `seconds_since_midnight` might not return the real amount in such a day. - -### Calculations - -NOTE: All the following methods are defined in `active_support/core_ext/date_time/calculations.rb`. - -The class `DateTime` is a subclass of `Date` so by loading `active_support/core_ext/date/calculations.rb` you inherit these methods and their aliases, except that they will always return datetimes: - -```ruby -yesterday -tomorrow -beginning_of_week (at_beginning_of_week) -end_of_week (at_end_of_week) -monday -sunday -weeks_ago -prev_week (last_week) -next_week -months_ago -months_since -beginning_of_month (at_beginning_of_month) -end_of_month (at_end_of_month) -prev_month (last_month) -next_month -beginning_of_quarter (at_beginning_of_quarter) -end_of_quarter (at_end_of_quarter) -beginning_of_year (at_beginning_of_year) -end_of_year (at_end_of_year) -years_ago -years_since -prev_year (last_year) -next_year -``` - -The following methods are reimplemented so you do **not** need to load `active_support/core_ext/date/calculations.rb` for these ones: - -```ruby -beginning_of_day (midnight, at_midnight, at_beginning_of_day) -end_of_day -ago -since (in) -``` - -On the other hand, `advance` and `change` are also defined and support more options, they are documented below. - -The following methods are only implemented in `active_support/core_ext/date_time/calculations.rb` as they only make sense when used with a `DateTime` instance: - -```ruby -beginning_of_hour (at_beginning_of_hour) -end_of_hour -``` - -#### Named Datetimes - -##### `DateTime.current` - -Active Support defines `DateTime.current` to be like `Time.now.to_datetime`, except that it honors the user time zone, if defined. It also defines `DateTime.yesterday` and `DateTime.tomorrow`, and the instance predicates `past?`, and `future?` relative to `DateTime.current`. - -#### Other Extensions - -##### `seconds_since_midnight` - -The method `seconds_since_midnight` returns the number of seconds since midnight: - -```ruby -now = DateTime.current # => Mon, 07 Jun 2010 20:26:36 +0000 -now.seconds_since_midnight # => 73596 -``` - -##### `utc` - -The method `utc` gives you the same datetime in the receiver expressed in UTC. - -```ruby -now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400 -now.utc # => Mon, 07 Jun 2010 23:27:52 +0000 -``` - -This method is also aliased as `getutc`. - -##### `utc?` - -The predicate `utc?` says whether the receiver has UTC as its time zone: - -```ruby -now = DateTime.now # => Mon, 07 Jun 2010 19:30:47 -0400 -now.utc? # => false -now.utc.utc? # => true -``` - -##### `advance` - -The most generic way to jump to another datetime is `advance`. This method receives a hash with keys `:years`, `:months`, `:weeks`, `:days`, `:hours`, `:minutes`, and `:seconds`, and returns a datetime advanced as much as the present keys indicate. - -```ruby -d = DateTime.current -# => Thu, 05 Aug 2010 11:33:31 +0000 -d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1) -# => Tue, 06 Sep 2011 12:34:32 +0000 -``` - -This method first computes the destination date passing `:years`, `:months`, `:weeks`, and `:days` to `Date#advance` documented above. After that, it adjusts the time calling `since` with the number of seconds to advance. This order is relevant, a different ordering would give different datetimes in some edge-cases. The example in `Date#advance` applies, and we can extend it to show order relevance related to the time bits. - -If we first move the date bits (that have also a relative order of processing, as documented before), and then the time bits we get for example the following computation: - -```ruby -d = DateTime.new(2010, 2, 28, 23, 59, 59) -# => Sun, 28 Feb 2010 23:59:59 +0000 -d.advance(months: 1, seconds: 1) -# => Mon, 29 Mar 2010 00:00:00 +0000 -``` - -but if we computed them the other way around, the result would be different: - -```ruby -d.advance(seconds: 1).advance(months: 1) -# => Thu, 01 Apr 2010 00:00:00 +0000 -``` - -WARNING: Since `DateTime` is not DST-aware you can end up in a non-existing point in time with no warning or error telling you so. - -#### Changing Components - -The method `change` allows you to get a new datetime which is the same as the receiver except for the given options, which may include `:year`, `:month`, `:day`, `:hour`, `:min`, `:sec`, `:offset`, `:start`: - -```ruby -now = DateTime.current -# => Tue, 08 Jun 2010 01:56:22 +0000 -now.change(year: 2011, offset: Rational(-6, 24)) -# => Wed, 08 Jun 2011 01:56:22 -0600 -``` - -If hours are zeroed, then minutes and seconds are too (unless they have given values): - -```ruby -now.change(hour: 0) -# => Tue, 08 Jun 2010 00:00:00 +0000 -``` - -Similarly, if minutes are zeroed, then seconds are too (unless it has given a value): - -```ruby -now.change(min: 0) -# => Tue, 08 Jun 2010 01:00:00 +0000 -``` - -This method is not tolerant to non-existing dates, if the change is invalid `ArgumentError` is raised: - -```ruby -DateTime.current.change(month: 2, day: 30) -# => ArgumentError: invalid date -``` - -#### Durations - -Durations can be added to and subtracted from datetimes: - -```ruby -now = DateTime.current -# => Mon, 09 Aug 2010 23:15:17 +0000 -now + 1.year -# => Tue, 09 Aug 2011 23:15:17 +0000 -now - 1.week -# => Mon, 02 Aug 2010 23:15:17 +0000 -``` - -They translate to calls to `since` or `advance`. For example here we get the correct jump in the calendar reform: - -```ruby -DateTime.new(1582, 10, 4, 23) + 1.hour -# => Fri, 15 Oct 1582 00:00:00 +0000 -``` - -Extensions to `Time` --------------------- - -### Calculations - -NOTE: All the following methods are defined in `active_support/core_ext/time/calculations.rb`. - -Active Support adds to `Time` many of the methods available for `DateTime`: - -```ruby -past? -today? -future? -yesterday -tomorrow -seconds_since_midnight -change -advance -ago -since (in) -beginning_of_day (midnight, at_midnight, at_beginning_of_day) -end_of_day -beginning_of_hour (at_beginning_of_hour) -end_of_hour -beginning_of_week (at_beginning_of_week) -end_of_week (at_end_of_week) -monday -sunday -weeks_ago -prev_week (last_week) -next_week -months_ago -months_since -beginning_of_month (at_beginning_of_month) -end_of_month (at_end_of_month) -prev_month (last_month) -next_month -beginning_of_quarter (at_beginning_of_quarter) -end_of_quarter (at_end_of_quarter) -beginning_of_year (at_beginning_of_year) -end_of_year (at_end_of_year) -years_ago -years_since -prev_year (last_year) -next_year -``` - -They are analogous. Please refer to their documentation above and take into account the following differences: - -* `change` accepts an additional `:usec` option. -* `Time` understands DST, so you get correct DST calculations as in - -```ruby -Time.zone_default -# => # - -# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST. -t = Time.local(2010, 3, 28, 1, 59, 59) -# => Sun Mar 28 01:59:59 +0100 2010 -t.advance(seconds: 1) -# => Sun Mar 28 03:00:00 +0200 2010 -``` - -* If `since` or `ago` jump to a time that can't be expressed with `Time` a `DateTime` object is returned instead. - -#### `Time.current` - -Active Support defines `Time.current` to be today in the current time zone. That's like `Time.now`, except that it honors the user time zone, if defined. It also defines the instance predicates `past?`, `today?`, and `future?`, all of them relative to `Time.current`. - -When making Time comparisons using methods which honor the user time zone, make sure to use `Time.current` instead of `Time.now`. There are cases where the user time zone might be in the future compared to the system time zone, which `Time.now` uses by default. This means `Time.now.to_date` may equal `Date.yesterday`. - -#### `all_day`, `all_week`, `all_month`, `all_quarter` and `all_year` - -The method `all_day` returns a range representing the whole day of the current time. - -```ruby -now = Time.current -# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 -now.all_day -# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00 -``` - -Analogously, `all_week`, `all_month`, `all_quarter` and `all_year` all serve the purpose of generating time ranges. - -```ruby -now = Time.current -# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 -now.all_week -# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00 -now.all_week(:sunday) -# => Sun, 16 Sep 2012 00:00:00 UTC +00:00..Sat, 22 Sep 2012 23:59:59 UTC +00:00 -now.all_month -# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00 -now.all_quarter -# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00 -now.all_year -# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00 -``` - -### Time Constructors - -Active Support defines `Time.current` to be `Time.zone.now` if there's a user time zone defined, with fallback to `Time.now`: - -```ruby -Time.zone_default -# => # -Time.current -# => Fri, 06 Aug 2010 17:11:58 CEST +02:00 -``` - -Analogously to `DateTime`, the predicates `past?`, and `future?` are relative to `Time.current`. - -If the time to be constructed lies beyond the range supported by `Time` in the runtime platform, usecs are discarded and a `DateTime` object is returned instead. - -#### Durations - -Durations can be added to and subtracted from time objects: - -```ruby -now = Time.current -# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 -now + 1.year -# => Tue, 09 Aug 2011 23:21:11 UTC +00:00 -now - 1.week -# => Mon, 02 Aug 2010 23:21:11 UTC +00:00 -``` - -They translate to calls to `since` or `advance`. For example here we get the correct jump in the calendar reform: - -```ruby -Time.utc(1582, 10, 3) + 5.days -# => Mon Oct 18 00:00:00 UTC 1582 -``` - -Extensions to `File` --------------------- - -### `atomic_write` - -With the class method `File.atomic_write` you can write to a file in a way that will prevent any reader from seeing half-written content. - -The name of the file is passed as an argument, and the method yields a file handle opened for writing. Once the block is done `atomic_write` closes the file handle and completes its job. - -For example, Action Pack uses this method to write asset cache files like `all.css`: - -```ruby -File.atomic_write(joined_asset_path) do |cache| - cache.write(join_asset_file_contents(asset_paths)) -end -``` - -To accomplish this `atomic_write` creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed, which is an atomic operation on POSIX systems. If the target file exists `atomic_write` overwrites it and keeps owners and permissions. However there are a few cases where `atomic_write` cannot change the file ownership or permissions, this error is caught and skipped over trusting in the user/filesystem to ensure the file is accessible to the processes that need it. - -NOTE. Due to the chmod operation `atomic_write` performs, if the target file has an ACL set on it this ACL will be recalculated/modified. - -WARNING. Note you can't append with `atomic_write`. - -The auxiliary file is written in a standard directory for temporary files, but you can pass a directory of your choice as second argument. - -NOTE: 定义于 `active_support/core_ext/file/atomic.rb`. - -Extensions to `Marshal` ------------------------ - -### `load` - -Active Support adds constant autoloading support to `load`. - -For example, the file cache store deserializes this way: - -```ruby -File.open(file_name) { |f| Marshal.load(f) } -``` - -If the cached data refers to a constant that is unknown at that point, the autoloading mechanism is triggered and if it succeeds the deserialization is retried transparently. - -WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able to retry. Regular files respond to `rewind`. - -NOTE: 定义于 `active_support/core_ext/marshal.rb`. - -Extensions to `Logger` ----------------------- - -### `around_[level]` - -Takes two arguments, a `before_message` and `after_message` and calls the current level method on the `Logger` instance, passing in the `before_message`, then the specified message, then the `after_message`: - -```ruby -logger = Logger.new("log/development.log") -logger.around_info("before", "after") { |logger| logger.info("during") } -``` - -### `silence` - -Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal. - -```ruby -logger = Logger.new("log/development.log") -logger.silence(Logger::INFO) do - logger.debug("In space, no one can hear you scream.") - logger.info("Scream all you want, small mailman!") -end -``` - -### `datetime_format=` - -Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a `datetime_format` method then this is ignored. - -```ruby -class Logger::FormatWithTime < Logger::Formatter - cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } - - def self.call(severity, timestamp, progname, msg) - "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" - end -end - -logger = Logger.new("log/development.log") -logger.formatter = Logger::FormatWithTime -logger.info("<- is the current time") -``` - -NOTE: 定义于 `active_support/core_ext/logger.rb`. - -Extensions to `NameError` -------------------------- - -Active Support adds `missing_name?` to `NameError`, which tests whether the exception was raised because of the name passed as argument. - -The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name. - -TIP: A symbol can represent a fully-qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically. - -For example, when an action of `ArticlesController` is called Rails tries optimistically to use `ArticlesHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `articles_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases: - -```ruby -def default_helper_module! - module_name = name.sub(/Controller$/, '') - module_path = module_name.underscore - helper module_path -rescue MissingSourceFile => e - raise e unless e.is_missing? "helpers/#{module_path}_helper" -rescue NameError => e - raise e unless e.missing_name? "#{module_name}Helper" -end -``` - -NOTE: 定义于 `active_support/core_ext/name_error.rb`. - -Extensions to `LoadError` -------------------------- - -Active Support adds `is_missing?` to `LoadError`, and also assigns that class to the constant `MissingSourceFile` for backwards compatibility. - -Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). - -For example, when an action of `ArticlesController` is called Rails tries to load `articles_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases: - -```ruby -def default_helper_module! - module_name = name.sub(/Controller$/, '') - module_path = module_name.underscore - helper module_path -rescue MissingSourceFile => e - raise e unless e.is_missing? "helpers/#{module_path}_helper" -rescue NameError => e - raise e unless e.missing_name? "#{module_name}Helper" -end -``` - -NOTE: 定义于 `active_support/core_ext/load_error.rb`. diff --git a/source/zh-CN/active_support_instrumentation.md b/source/zh-CN/active_support_instrumentation.md deleted file mode 100644 index 7033947..0000000 --- a/source/zh-CN/active_support_instrumentation.md +++ /dev/null @@ -1,497 +0,0 @@ -Active Support Instrumentation -============================== - -Active Support is a part of core Rails that provides Ruby language extensions, utilities and other things. One of the things it includes is an instrumentation API that can be used inside an application to measure certain actions that occur within Ruby code, such as that inside a Rails application or the framework itself. It is not limited to Rails, however. It can be used independently in other Ruby scripts if it is so desired. - -In this guide, you will learn how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code. - -After reading this guide, you will know: - -* What instrumentation can provide. -* The hooks inside the Rails framework for instrumentation. -* Adding a subscriber to a hook. -* Building a custom instrumentation implementation. - --------------------------------------------------------------------------------- - -Introduction to instrumentation -------------------------------- - -The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in (TODO: link to section detailing each hook point). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code. - -For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken. - -You are even able to create your own events inside your application which you can later subscribe to. - -Rails framework hooks ---------------------- - -Within the Ruby on Rails framework, there are a number of hooks provided for common events. These are detailed below. - -Action Controller ------------------ - -### write_fragment.action_controller - -| Key | Value | -| ------ | ---------------- | -| `:key` | The complete key | - -```ruby -{ - key: 'posts/1-dashboard-view' -} -``` - -### read_fragment.action_controller - -| Key | Value | -| ------ | ---------------- | -| `:key` | The complete key | - -```ruby -{ - key: 'posts/1-dashboard-view' -} -``` - -### expire_fragment.action_controller - -| Key | Value | -| ------ | ---------------- | -| `:key` | The complete key | - -```ruby -{ - key: 'posts/1-dashboard-view' -} -``` - -### exist_fragment?.action_controller - -| Key | Value | -| ------ | ---------------- | -| `:key` | The complete key | - -```ruby -{ - key: 'posts/1-dashboard-view' -} -``` - -### write_page.action_controller - -| Key | Value | -| ------- | ----------------- | -| `:path` | The complete path | - -```ruby -{ - path: '/users/1' -} -``` - -### expire_page.action_controller - -| Key | Value | -| ------- | ----------------- | -| `:path` | The complete path | - -```ruby -{ - path: '/users/1' -} -``` - -### start_processing.action_controller - -| Key | Value | -| ------------- | --------------------------------------------------------- | -| `:controller` | The controller name | -| `:action` | The action | -| `:params` | Hash of request parameters without any filtered parameter | -| `:format` | html/js/json/xml etc | -| `:method` | HTTP request verb | -| `:path` | Request path | - -```ruby -{ - controller: "PostsController", - action: "new", - params: { "action" => "new", "controller" => "posts" }, - format: :html, - method: "GET", - path: "/posts/new" -} -``` - -### process_action.action_controller - -| Key | Value | -| --------------- | --------------------------------------------------------- | -| `:controller` | The controller name | -| `:action` | The action | -| `:params` | Hash of request parameters without any filtered parameter | -| `:format` | html/js/json/xml etc | -| `:method` | HTTP request verb | -| `:path` | Request path | -| `:view_runtime` | Amount spent in view in ms | - -```ruby -{ - controller: "PostsController", - action: "index", - params: {"action" => "index", "controller" => "posts"}, - format: :html, - method: "GET", - path: "/posts", - status: 200, - view_runtime: 46.848, - db_runtime: 0.157 -} -``` - -### send_file.action_controller - -| Key | Value | -| ------- | ------------------------- | -| `:path` | Complete path to the file | - -INFO. Additional keys may be added by the caller. - -### send_data.action_controller - -`ActionController` does not had any specific information to the payload. All options are passed through to the payload. - -### redirect_to.action_controller - -| Key | Value | -| ----------- | ------------------ | -| `:status` | HTTP response code | -| `:location` | URL to redirect to | - -```ruby -{ - status: 302, - location: "http://localhost:3000/posts/new" -} -``` - -### halted_callback.action_controller - -| Key | Value | -| --------- | ----------------------------- | -| `:filter` | Filter that halted the action | - -```ruby -{ - filter: ":halting_filter" -} -``` - -Action View ------------ - -### render_template.action_view - -| Key | Value | -| ------------- | --------------------- | -| `:identifier` | Full path to template | -| `:layout` | Applicable layout | - -```ruby -{ - identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb", - layout: "layouts/application" -} -``` - -### render_partial.action_view - -| Key | Value | -| ------------- | --------------------- | -| `:identifier` | Full path to template | - -```ruby -{ - identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb", -} -``` - -Active Record ------------- - -### sql.active_record - -| Key | Value | -| ------------ | --------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:object_id` | `self.object_id` | - -INFO. The adapters will add their own data as well. - -```ruby -{ - sql: "SELECT \"posts\".* FROM \"posts\" ", - name: "Post Load", - connection_id: 70307250813140, - binds: [] -} -``` - -### identity.active_record - -| Key | Value | -| ---------------- | ----------------------------------------- | -| `:line` | Primary Key of object in the identity map | -| `:name` | Record's class | -| `:connection_id` | `self.object_id` | - -Action Mailer -------------- - -### receive.action_mailer - -| Key | Value | -| ------------- | -------------------------------------------- | -| `:mailer` | Name of the mailer class | -| `:message_id` | ID of the message, generated by the Mail gem | -| `:subject` | Subject of the mail | -| `:to` | To address(es) of the mail | -| `:from` | From address of the mail | -| `:bcc` | BCC addresses of the mail | -| `:cc` | CC addresses of the mail | -| `:date` | Date of the mail | -| `:mail` | The encoded form of the mail | - -```ruby -{ - mailer: "Notification", - message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail", - subject: "Rails Guides", - to: ["users@rails.com", "ddh@rails.com"], - from: ["me@rails.com"], - date: Sat, 10 Mar 2012 14:18:09 +0100, - mail: "..." # omitted for brevity -} -``` - -### deliver.action_mailer - -| Key | Value | -| ------------- | -------------------------------------------- | -| `:mailer` | Name of the mailer class | -| `:message_id` | ID of the message, generated by the Mail gem | -| `:subject` | Subject of the mail | -| `:to` | To address(es) of the mail | -| `:from` | From address of the mail | -| `:bcc` | BCC addresses of the mail | -| `:cc` | CC addresses of the mail | -| `:date` | Date of the mail | -| `:mail` | The encoded form of the mail | - -```ruby -{ - mailer: "Notification", - message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail", - subject: "Rails Guides", - to: ["users@rails.com", "ddh@rails.com"], - from: ["me@rails.com"], - date: Sat, 10 Mar 2012 14:18:09 +0100, - mail: "..." # omitted for brevity -} -``` - -ActiveResource --------------- - -### request.active_resource - -| Key | Value | -| -------------- | -------------------- | -| `:method` | HTTP method | -| `:request_uri` | Complete URI | -| `:result` | HTTP response object | - -Active Support --------------- - -### cache_read.active_support - -| Key | Value | -| ------------------ | ------------------------------------------------- | -| `:key` | Key used in the store | -| `:hit` | If this read is a hit | -| `:super_operation` | :fetch is added when a read is used with `#fetch` | - -### cache_generate.active_support - -This event is only used when `#fetch` is called with a block. - -| Key | Value | -| ------ | --------------------- | -| `:key` | Key used in the store | - -INFO. Options passed to fetch will be merged with the payload when writing to the store - -```ruby -{ - key: 'name-of-complicated-computation' -} -``` - - -### cache_fetch_hit.active_support - -This event is only used when `#fetch` is called with a block. - -| Key | Value | -| ------ | --------------------- | -| `:key` | Key used in the store | - -INFO. Options passed to fetch will be merged with the payload. - -```ruby -{ - key: 'name-of-complicated-computation' -} -``` - -### cache_write.active_support - -| Key | Value | -| ------ | --------------------- | -| `:key` | Key used in the store | - -INFO. Cache stores may add their own keys - -```ruby -{ - key: 'name-of-complicated-computation' -} -``` - -### cache_delete.active_support - -| Key | Value | -| ------ | --------------------- | -| `:key` | Key used in the store | - -```ruby -{ - key: 'name-of-complicated-computation' -} -``` - -### cache_exist?.active_support - -| Key | Value | -| ------ | --------------------- | -| `:key` | Key used in the store | - -```ruby -{ - key: 'name-of-complicated-computation' -} -``` - -Railties --------- - -### load_config_initializer.railties - -| Key | Value | -| -------------- | ----------------------------------------------------- | -| `:initializer` | Path to loaded initializer from `config/initializers` | - -Rails ------ - -### deprecation.rails - -| Key | Value | -| ------------ | ------------------------------- | -| `:message` | The deprecation warning | -| `:callstack` | Where the deprecation came from | - -Subscribing to an event ------------------------ - -Subscribing to an event is easy. Use `ActiveSupport::Notifications.subscribe` with a block to -listen to any notification. - -The block receives the following arguments: - -* The name of the event -* Time when it started -* Time when it finished -* An unique ID for this event -* The payload (described in previous sections) - -```ruby -ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data| - # your own custom stuff - Rails.logger.info "#{name} Received!" -end -``` - -Defining all those block arguments each time can be tedious. You can easily create an `ActiveSupport::Notifications::Event` -from block arguments like this: - -```ruby -ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args| - event = ActiveSupport::Notifications::Event.new *args - - event.name # => "process_action.action_controller" - event.duration # => 10 (in milliseconds) - event.payload # => {:extra=>information} - - Rails.logger.info "#{event} Received!" -end -``` - -Most times you only care about the data itself. Here is a shortcut to just get the data. - -```ruby -ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args| - data = args.extract_options! - data # { extra: :information } -end -``` - -You may also subscribe to events matching a regular expression. This enables you to subscribe to -multiple events at once. Here's you could subscribe to everything from `ActionController`. - -```ruby -ActiveSupport::Notifications.subscribe /action_controller/ do |*args| - # inspect all ActionController events -end -``` - -Creating custom events ----------------------- - -Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of -all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block. -The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times -as well as the unique ID. All data passed into the `instrument` call will make it into the payload. - -Here's an example: - -```ruby -ActiveSupport::Notifications.instrument "my.custom.event", this: :data do - # do your custom stuff here -end -``` - -Now you can listen to this event with: - -```ruby -ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data| - puts data.inspect # {:this=>:data} -end -``` - -You should follow Rails conventions when defining your own events. The format is: `event.library`. -If you application is sending Tweets, you should create an event named `tweet.twitter`. diff --git a/source/zh-CN/api_documentation_guidelines.md b/source/zh-CN/api_documentation_guidelines.md deleted file mode 100644 index 7e9b288..0000000 --- a/source/zh-CN/api_documentation_guidelines.md +++ /dev/null @@ -1,361 +0,0 @@ -API Documentation Guidelines -============================ - -This guide documents the Ruby on Rails API documentation guidelines. - -After reading this guide, you will know: - -* How to write effective prose for documentation purposes. -* Style guidelines for documenting different kinds of Ruby code. - --------------------------------------------------------------------------------- - -RDoc ----- - -The [Rails API documentation](http://api.rubyonrails.org) is generated with -[RDoc](http://docs.seattlerb.org/rdoc/). - -```bash - bundle exec rake rdoc -``` - -Resulting HTML files can be found in the ./doc/rdoc directory. - -Please consult the RDoc documentation for help with the -[markup](http://docs.seattlerb.org/rdoc/RDoc/Markup.html), -and also take into account these [additional -directives](http://docs.seattlerb.org/rdoc/RDoc/Parser/Ruby.html). - -Wording -------- - -Write simple, declarative sentences. Brevity is a plus: get to the point. - -Write in present tense: "Returns a hash that...", rather than "Returned a hash that..." or "Will return a hash that...". - -Start comments in upper case. Follow regular punctuation rules: - -```ruby -# Declares an attribute reader backed by an internally-named -# instance variable. -def attr_internal_reader(*attrs) - ... -end -``` - -Communicate to the reader the current way of doing things, both explicitly and implicitly. Use the idioms recommended in edge. Reorder sections to emphasize favored approaches if needed, etc. The documentation should be a model for best practices and canonical, modern Rails usage. - -Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil? - -The proper names of Rails components have a space in between the words, like "Active Support". `ActiveRecord` is a Ruby module, whereas Active Record is an ORM. All Rails documentation should consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be phenomenal. - -Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERB. When in doubt, please have a look at some authoritative source like their official documentation. - -Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database". - -Prefer wordings that avoid "you"s and "your"s. For example, instead of - -```markdown -If you need to use `return` statements in your callbacks, it is recommended that you explicitly define them as methods. -``` - -use this style: - -```markdown -If `return` is needed it is recommended to explicitly define a method. -``` - -That said, when using pronouns in reference to a hypothetical person, such as "a -user with a session cookie", gender neutral pronouns (they/their/them) should be -used. Instead of: - -* he or she... use they. -* him or her... use them. -* his or her... use their. -* his or hers... use theirs. -* himself or herself... use themselves. - -English -------- - -Please use American English (color, center, modularize, etc). See [a list of American and British English spelling differences here](http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences). - -Example Code ------------- - -Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas. - -Use two spaces to indent chunks of code--that is, for markup purposes, two spaces with respect to the left margin. The examples themselves should use [Rails coding conventions](contributing_to_ruby_on_rails.html#follow-the-coding-conventions). - -Short docs do not need an explicit "Examples" label to introduce snippets; they just follow paragraphs: - -```ruby -# Converts a collection of elements into a formatted string by -# calling +to_s+ on all elements and joining them. -# -# Blog.all.to_formatted_s # => "First PostSecond PostThird Post" -``` - -On the other hand, big chunks of structured documentation may have a separate "Examples" section: - -```ruby -# ==== Examples -# -# Person.exists?(5) -# Person.exists?('5') -# Person.exists?(name: "David") -# Person.exists?(['name LIKE ?', "%#{query}%"]) -``` - -The results of expressions follow them and are introduced by "# => ", vertically aligned: - -```ruby -# For checking if a fixnum is even or odd. -# -# 1.even? # => false -# 1.odd? # => true -# 2.even? # => true -# 2.odd? # => false -``` - -If a line is too long, the comment may be placed on the next line: - -```ruby -# label(:article, :title) -# # => -# -# label(:article, :title, "A short title") -# # => -# -# label(:article, :title, "A short title", class: "title_label") -# # => -``` - -Avoid using any printing methods like `puts` or `p` for that purpose. - -On the other hand, regular comments do not use an arrow: - -```ruby -# polymorphic_url(https://github.com/record) # same as comment_url(https://github.com/record) -``` - -Booleans --------- - -In predicates and flags prefer documenting boolean semantics over exact values. - -When "true" or "false" are used as defined in Ruby use regular font. The -singletons `true` and `false` need fixed-width font. Please avoid terms like -"truthy", Ruby defines what is true and false in the language, and thus those -words have a technical meaning and need no substitutes. - -As a rule of thumb, do not document singletons unless absolutely necessary. That -prevents artificial constructs like `!!` or ternaries, allows refactors, and the -code does not need to rely on the exact values returned by methods being called -in the implementation. - -For example: - -```markdown -`config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default -``` - -the user does not need to know which is the actual default value of the flag, -and so we only document its boolean semantics. - -An example with a predicate: - -```ruby -# Returns true if the collection is empty. -# -# If the collection has been loaded -# it is equivalent to collection.size.zero?. If the -# collection has not been loaded, it is equivalent to -# collection.exists?. If the collection has not already been -# loaded and you are going to fetch the records anyway it is better to -# check collection.length.zero?. -def empty? - if loaded? - size.zero? - else - @target.blank? && !scope.exists? - end -end -``` - -The API is careful not to commit to any particular value, the method has -predicate semantics, that's enough. - -File Names ----------- - -As a rule of thumb, use filenames relative to the application root: - -``` -config/routes.rb # YES -routes.rb # NO -RAILS_ROOT/config/routes.rb # NO -``` - -Fonts ------ - -### Fixed-width Font - -Use fixed-width fonts for: - -* Constants, in particular class and module names. -* Method names. -* Literals like `nil`, `false`, `true`, `self`. -* Symbols. -* Method parameters. -* File names. - -```ruby -class Array - # Calls +to_param+ on all its elements and joins the result with - # slashes. This is used by +url_for+ in Action Pack. - def to_param - collect { |e| e.to_param }.join '/' - end -end -``` - -WARNING: Using `+...+` for fixed-width font only works with simple content like -ordinary method names, symbols, paths (with forward slashes), etc. Please use -`...` for everything else, notably class or module names with a -namespace as in `ActiveRecord::Base`. - -You can quickly test the RDoc output with the following command: - -``` -$ echo "+:to_param+" | rdoc --pipe -#=>

:to_param

-``` - -### Regular Font - -When "true" and "false" are English words rather than Ruby keywords use a regular font: - -```ruby -# Runs all the validations within the specified context. -# Returns true if no errors are found, false otherwise. -# -# If the argument is false (default is +nil+), the context is -# set to :create if new_record? is true, -# and to :update if it is not. -# -# Validations with no :on option will run no -# matter the context. Validations with # some :on -# option will only run in the specified context. -def valid?(context = nil) - ... -end -``` - -Description Lists ------------------ - -In lists of options, parameters, etc. use a hyphen between the item and its description (reads better than a colon because normally options are symbols): - -```ruby -# * :allow_nil - Skip validation if attribute is +nil+. -``` - -The description starts in upper case and ends with a full stop-it's standard English. - -Dynamically Generated Methods ------------------------------ - -Methods created with `(module|class)_eval(STRING)` have a comment by their side with an instance of the generated code. That comment is 2 spaces away from the template: - -```ruby -for severity in Severity.constants - class_eval <<-EOT, __FILE__, __LINE__ - def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block) - add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block) - end # end - # - def #{severity.downcase}? # def debug? - #{severity} >= @level # DEBUG >= @level - end # end - EOT -end -``` - -If the resulting lines are too wide, say 200 columns or more, put the comment above the call: - -```ruby -# def self.find_by_login_and_activated(*args) -# options = args.extract_options! -# ... -# end -self.class_eval %{ - def self.#{method_id}(*args) - options = args.extract_options! - ... - end -} -``` - -Method Visibility ------------------ - -When writing documentation for Rails, it's important to understand the difference between public user-facing API vs internal API. - -Rails, like most libraries, uses the private keyword from Ruby for defining internal API. However, public API follows a slightly different convention. Instead of assuming all public methods are designed for user consumption, Rails uses the `:nodoc:` directive to annotate these kinds of methods as internal API. - -This means that there are methods in Rails with `public` visibility that aren't meant for user consumption. - -An example of this is `ActiveRecord::Core::ClassMethods#arel_table`: - -```ruby -module ActiveRecord::Core::ClassMethods - def arel_table #:nodoc: - # do some magic.. - end -end -``` - -If you thought, "this method looks like a public class method for `ActiveRecord::Core`", you were right. But actually the Rails team doesn't want users to rely on this method. So they mark it as `:nodoc:` and it's removed from public documentation. The reasoning behind this is to allow the team to change these methods according to their internal needs across releases as they see fit. The name of this method could change, or the return value, or this entire class may disappear; there's no guarantee and so you shouldn't depend on this API in your plugins or applications. Otherwise, you risk your app or gem breaking when you upgrade to a newer release of Rails. - -As a contributor, it's important to think about whether this API is meant for end-user consumption. The Rails team is committed to not making any breaking changes to public API across releases without going through a full deprecation cycle. It's recommended that you `:nodoc:` any of your internal methods/classes unless they're already private (meaning visibility), in which case it's internal by default. Once the API stabilizes the visibility can change, but changing public API is much harder due to backwards compatibility. - -A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly. - -If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project. - -A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails. - -To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first. - -Regarding the Rails Stack -------------------------- - -When documenting parts of Rails API, it's important to remember all of the -pieces that go into the Rails stack. - -This means that behavior may change depending on the scope or context of the -method or class you're trying to document. - -In various places there is different behavior when you take the entire stack -into account, one such example is -`ActionView::Helpers::AssetTagHelper#image_tag`: - -```ruby -# image_tag("icon.png") -# # => Icon -``` - -Although the default behavior for `#image_tag` is to always return -`/images/icon.png`, we take into account the full Rails stack (including the -Asset Pipeline) we may see the result seen above. - -We're only concerned with the behavior experienced when using the full default -Rails stack. - -In this case, we want to document the behavior of the _framework_, and not just -this specific method. - -If you have a question on how the Rails team handles certain API, don't hesitate to open a ticket or send a patch to the [issue tracker](https://github.com/rails/rails/issues). diff --git a/source/zh-CN/asset_pipeline.md b/source/zh-CN/asset_pipeline.md deleted file mode 100644 index 3d38ca9..0000000 --- a/source/zh-CN/asset_pipeline.md +++ /dev/null @@ -1,828 +0,0 @@ -Asset Pipeline -============== - -本文介绍 Asset Pipeline。 - -读完本文,你将学到: - -* Asset Pipeline 是什么以及其作用; -* 如何合理组织程序的静态资源; -* Asset Pipeline 的优势; -* 如何向 Asset Pipeline 中添加预处理器; -* 如何在 gem 中打包静态资源; - --------------------------------------------------------------------------------- - -Asset Pipeline 是什么? ---------------------- - -Asset Pipeline 提供了一个框架,用于连接、压缩 JavaScript 和 CSS 文件。还允许使用其他语言和预处理器编写 JavaScript 和 CSS,例如 CoffeeScript、Sass 和 ERB。 - -严格来说,Asset Pipeline 不是 Rails 4 的核心功能,已经从框架中提取出来,制成了 [sprockets-rails](https://github.com/rails/sprockets-rails) gem。 - -Asset Pipeline 功能默认是启用的。 - -新建程序时如果想禁用 Asset Pipeline,可以在命令行中指定 `--skip-sprockets` 选项。 - -```bash -rails new appname --skip-sprockets -``` - -Rails 4 会自动把 `sass-rails`、`coffee-rails` 和 `uglifier` 三个 gem 加入 `Gemfile`。Sprockets 使用这三个 gem 压缩静态资源: - -```ruby -gem 'sass-rails' -gem 'uglifier' -gem 'coffee-rails' -``` - -指定 `--skip-sprockets` 命令行选项后,Rails 4 不会把 `sass-rails` 和 `uglifier` 加入 `Gemfile`。如果后续需要使用 Asset Pipeline,需要手动添加这些 gem。而且,指定 `--skip-sprockets` 命令行选项后,生成的 `config/application.rb` 文件也会有点不同,把加载 `sprockets/railtie` 的代码注释掉了。如果后续启用 Asset Pipeline,要把这行前面的注释去掉: - -```ruby -# require "sprockets/railtie" -``` - -`production.rb` 文件中有相应的选项设置静态资源的压缩方式:`config.assets.css_compressor` 针对 CSS,`config.assets.js_compressor` 针对 Javascript。 - -```ruby -config.assets.css_compressor = :yui -config.assets.js_compressor = :uglify -``` - -NOTE: 如果 `Gemfile` 中有 `sass-rails`,就会自动用来压缩 CSS,无需设置 `config.assets.css_compressor` 选项。 - -### 主要功能 - -Asset Pipeline 的第一个功能是连接静态资源,减少渲染页面时浏览器发起的请求数。浏览器对并行的请求数量有限制,所以较少的请求数可以提升程序的加载速度。 - -Sprockets 会把所有 JavaScript 文件合并到一个主 `.js` 文件中,把所有 CSS 文件合并到一个主 `.css` 文件中。后文会介绍,合并的方式可按需求随意定制。在生产环境中,Rails 会在文件名后加上 MD5 指纹,以便浏览器缓存,指纹变了缓存就会过期。修改文件的内容后,指纹会自动变化。 - -Asset Pipeline 的第二个功能是压缩静态资源。对 CSS 文件来说,会删除空白和注释。对 JavaScript 来说,可以做更复杂的处理。处理方式可以从内建的选项中选择,也可使用定制的处理程序。 - -Asset Pipeline 的第三个功能是允许使用高级语言编写静态资源,再使用预处理器转换成真正的静态资源。默认支持的高级语言有:用来编写 CSS 的 Sass,用来编写 JavaScript 的 CoffeeScript,以及 ERB。 - -### 指纹是什么,我为什么要关心它? - -指纹可以根据文件内容生成文件名。文件内容变化后,文件名也会改变。对于静态内容,或者很少改动的内容,在不同的服务器之间,不同的部署日期之间,使用指纹可以区别文件的两个版本内容是否一样。 - -如果文件名基于内容而定,而且文件名是唯一的,HTTP 报头会建议在所有可能的地方(CDN,ISP,网络设备,网页浏览器)存储一份该文件的副本。修改文件内容后,指纹会发生变化,因此远程客户端会重新请求文件。这种技术叫做“缓存爆裂”(cache busting)。 - -Sprockets 使用指纹的方式是在文件名中加入内容的哈希值,一般加在文件名的末尾。例如,`global.css` 加入指纹后的文件名如下: - -``` -global-908e25f4bf641868d8683022a5b62f54.css -``` - -Asset Pipeline 使用的就是这种指纹实现方式。 - -以前,Rails 使用内建的帮助方法,在文件名后加上一个基于日期生成的请求字符串,如下所示: - -``` -/stylesheets/global.css?1309495796 -``` - -使用请求字符串有很多缺点: - -1. **文件名只是请求字符串不同时,缓存并不可靠**
- [Steve Souders 建议](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/):不在要缓存的资源上使用请求字符串。他发现,使用请求字符串的文件不被缓存的可能性有 5-20%。有些 CDN 验证缓存时根本无法识别请求字符串。 - -2. **在多服务器环境中,不同节点上的文件名可能不同**
- 在 Rails 2.x 中,默认的请求字符串由文件的修改时间生成。静态资源文件部署到集群后,无法保证时间戳都是一样的,得到的值取决于使用哪台服务器处理请求。 - -3. **缓存验证失败过多**
- 部署新版代码时,所有静态资源文件的最后修改时间都变了。即便内容没变,客户端也要重新请求这些文件。 - -使用指纹就无需再用请求字符串了,而且文件名基于文件内容,始终保持一致。 - -默认情况下,指纹只在生产环境中启用,其他环境都被禁用。可以设置 `config.assets.digest` 选项启用或禁用。 - -扩展阅读: - -* [Optimize caching](http://code.google.com/speed/page-speed/docs/caching.html) -* [Revving Filenames: don't use querystring](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/) - -如何使用 Asset Pipeline ----------------------- - -在以前的 Rails 版本中,所有静态资源都放在 `public` 文件夹的子文件夹中,例如 `images`、`javascripts` 和 `stylesheets`。使用 Asset Pipeline 后,建议把静态资源放在 `app/assets` 文件夹中。这个文件夹中的文件会经由 Sprockets 中间件处理。 - -静态资源仍然可以放在 `public` 文件夹中,其中所有文件都会被程序或网页服务器视为静态文件。如果文件要经过预处理器处理,就得放在 `app/assets` 文件夹中。 - -默认情况下,在生产环境中,Rails 会把预先编译好的文件保存到 `public/assets` 文件夹中,网页服务器会把这些文件视为静态资源。在生产环境中,不会直接伺服 `app/assets` 文件夹中的文件。 - -### 控制器相关的静态资源 - -生成脚手架或控制器时,Rails 会生成一个 JavaScript 文件(如果 `Gemfile` 中有 `coffee-rails`,会生成 CoffeeScript 文件)和 CSS 文件(如果 `Gemfile` 中有 `sass-rails`,会生成 SCSS 文件)。生成脚手架时,Rails 还会生成 `scaffolds.css` 文件(如果 `Gemfile` 中有 `sass-rails`,会生成 `scaffolds.css.scss` 文件)。 - -例如,生成 `ProjectsController` 时,Rails 会新建 `app/assets/javascripts/projects.js.coffee` 和 `app/assets/stylesheets/projects.css.scss` 两个文件。默认情况下,这两个文件立即就可以使用 `require_tree` 引入程序。关于 `require_tree` 的介绍,请阅读“[清单文件和指令](#manifest-files-and-directives)”一节。 - -针对控制器的样式表和 JavaScript 文件也可只在相应的控制器中引入: - -`<%= javascript_include_tag params[:controller] %>` 或 `<%= stylesheet_link_tag params[:controller] %>` - -如果需要这么做,切记不要使用 `require_tree`。如果使用了这个指令,会多次引入相同的静态资源。 - -WARNING: 预处理静态资源时要确保同时处理控制器相关的静态资源。默认情况下,不会自动编译 `.coffee` 和 `.scss` 文件。在开发环境中没什么问题,因为会自动编译。但在生产环境中会得到 500 错误,因为此时自动编译默认是关闭的。关于预编译的工作机理,请阅读“[事先编译好静态资源](#precompiling-assets)”一节。 - -NOTE: 要想使用 CoffeeScript,必须安装支持 ExecJS 的运行时。如果使用 Mac OS X 和 Windows,系统中已经安装了 JavaScript 运行时。所有支持的 JavaScript 运行时参见 [ExecJS](https://github.com/sstephenson/execjs#readme) 的文档。 - -在 `config/application.rb` 文件中加入以下代码可以禁止生成控制器相关的静态资源: - -```ruby -config.generators do |g| - g.assets false -end -``` - -### 静态资源的组织方式 - -Asset Pipeline 的静态文件可以放在三个位置:`app/assets`,`lib/assets` 或 `vendor/assets`。 - -* `app/assets`:存放程序的静态资源,例如图片、JavaScript 和样式表; -* `lib/assets`:存放自己的代码库,或者共用代码库的静态资源; -* `vendor/assets`:存放他人的静态资源,例如 JavaScript 插件,或者 CSS 框架; - -WARNING: 如果从 Rails 3 升级过来,请注意,`lib/assets` 和 `vendor/assets` 中的静态资源可以引入程序,但不在预编译的范围内。详情参见“[事先编译好静态资源](#precompiling-assets)”一节。 - -#### 搜索路径 - -在清单文件或帮助方法中引用静态资源时,Sprockets 会在默认的三个位置中查找对应的文件。 - -默认的位置是 `apps/assets` 文件夹中的 `images`、`javascripts` 和 `stylesheets` 三个子文件夹。这三个文件夹没什么特别之处,其实 Sprockets 会搜索 `apps/assets` 文件夹中的所有子文件夹。 - -例如,如下的文件: - -``` -app/assets/javascripts/home.js -lib/assets/javascripts/moovinator.js -vendor/assets/javascripts/slider.js -vendor/assets/somepackage/phonebox.js -``` - -在清单文件中可以这么引用: - -```js -//= require home -//= require moovinator -//= require slider -//= require phonebox -``` - -子文件夹中的静态资源也可引用: - -``` -app/assets/javascripts/sub/something.js -``` - -引用方式如下: - -```js -//= require sub/something -``` - -在 Rails 控制台中执行 `Rails.application.config.assets.paths`,可以查看所有的搜索路径。 - -除了标准的 `assets/*` 路径之外,还可以在 `config/application.rb` 文件中向 Asset Pipeline 添加其他路径。例如: - -```ruby -config.assets.paths << Rails.root.join("lib", "videoplayer", "flash") -``` - -Sprockets 会按照搜索路径中各路径出现的顺序进行搜索。默认情况下,这意味着 `app/assets` 文件夹中的静态资源优先级较高,会遮盖 `lib` 和 `vendor` 文件夹中的相应文件。 - -有一点要注意,如果静态资源不会在清单文件中引入,就要添加到预编译的文件列表中,否则在生产环境中就无法访问文件。 - -#### 使用索引文件 - -在 Sprockets 中,名为 `index` 的文件(扩展名各异)有特殊作用。 - -例如,程序中使用了 jQuery 代码库和许多模块,都保存在 `lib/assets/javascripts/library_name` 文件夹中,那么 `lib/assets/javascripts/library_name/index.js` 文件的作用就是这个代码库的清单。在这个清单中可以按顺序列出所需的文件,或者干脆使用 `require_tree` 指令。 - -在程序的清单文件中,可以把这个库作为一个整体引入: - -```js -//= require library_name -``` - -这么做可以减少维护成本,保持代码整洁。 - -### 链接静态资源 - -Sprockets 并没有为获取静态资源添加新的方法,还是使用熟悉的 `javascript_include_tag` 和 `stylesheet_link_tag`: - -```erb -<%= stylesheet_link_tag "application", media: "all" %> -<%= javascript_include_tag "application" %> -``` - -如果使用 Turbolinks(Rails 4 默认启用),加上 `data-turbolinks-track` 选项后,Turbolinks 会检查静态资源是否有更新,如果更新了就会将其载入页面: - -```erb -<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> -<%= javascript_include_tag "application", "data-turbolinks-track" => true %> -``` - -在普通的视图中可以像下面这样获取 `public/assets/images` 文件夹中的图片: - -```erb -<%= image_tag "rails.png" %> -``` - -如果程序启用了 Asset Pipeline,且在当前环境中没有禁用,那么这个文件会经由 Sprockets 伺服。如果文件的存放位置是 `public/assets/rails.png`,则直接由网页服务器伺服。 - -如果请求的文件中包含 MD5 哈希,处理的方式还是一样。关于这个哈希是怎么生成的,请阅读“[在生产环境中](#in-production)”一节。 - -Sprockets 还会检查 `config.assets.paths` 中指定的路径。`config.assets.paths` 包含标准路径和其他 Rails 引擎添加的路径。 - -图片还可以放入子文件夹中,获取时指定文件夹的名字即可: - -```erb -<%= image_tag "icons/rails.png" %> -``` - -WARNING: 如果预编译了静态资源(参见“[在生产环境中](#in-production)”一节),链接不存在的资源(也包括链接到空字符串的情况)会在调用页面抛出异常。因此,在处理用户提交的数据时,使用 `image_tag` 等帮助方法要小心一点。 - -#### CSS 和 ERB - -Asset Pipeline 会自动执行 ERB 代码,所以如果在 CSS 文件名后加上扩展名 `erb`(例如 `application.css.erb`),那么在 CSS 规则中就可使用 `asset_path` 等帮助方法。 - -```css -.class { background-image: url(https://github.com/%3C%=%20asset_path%20'image.png'%20%%3E) } -``` - -Asset Pipeline 会计算出静态资源的真实路径。在上面的代码中,指定的图片要出现在加载路径中。如果在 `public/assets` 中有该文件带指纹版本,则会使用这个文件的路径。 - -如果想使用 [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme)(直接把图片数据内嵌在 CSS 文件中),可以使用 `asset_data_uri` 帮助方法。 - -```css -#logo { background: url(https://github.com/%3C%=%20asset_data_uri%20'logo.png'%20%%3E) } -``` - -`asset_data_uri` 会把正确格式化后的 data URI 写入 CSS 文件。 - -注意,关闭标签不能使用 `-%>` 形式。 - -#### CSS 和 Sass - -使用 Asset Pipeline,静态资源的路径要使用 `sass-rails` 提供的 `-url` 和 `-path` 帮助方法(在 Sass 中使用连字符,在 Ruby 中使用下划线)重写。这两种帮助方法可用于引用图片,字体,视频,音频,JavaScript 和样式表。 - -* `image-url("https://github.com/rails.png")` 编译成 `url(https://github.com/assets/rails.png)` -* `image-path("rails.png")` 编译成 `"/assets/rails.png"`. - -还可使用通用方法: - -* `asset-url("https://github.com/rails.png")` 编译成 `url(https://github.com/assets/rails.png)` -* `asset-path("rails.png")` 编译成 `"/assets/rails.png"` - -#### JavaScript/CoffeeScript 和 ERB - -如果在 JavaScript 文件后加上扩展名 `erb`,例如 `application.js.erb`,就可以在 JavaScript 代码中使用帮助方法 `asset_path`: - -```js -$('#logo').attr({ src: "<%= asset_path('logo.png') %>" }); -``` - -Asset Pipeline 会计算出静态资源的真实路径。 - -类似地,如果在 CoffeeScript 文件后加上扩展名 `erb`,例如 `application.js.coffee.erb`,也可在代码中使用帮助方法 `asset_path`: - -```js -$('#logo').attr src: "<%= asset_path('logo.png') %>" -``` - -### 清单文件和指令 - -Sprockets 通过清单文件决定要引入和伺服哪些静态资源。清单文件中包含一些指令,告知 Sprockets 使用哪些文件生成主 CSS 或 JavaScript 文件。Sprockets 会解析这些指令,加载指定的文件,如有需要还会处理文件,然后再把各个文件合并成一个文件,最后再压缩文件(如果 `Rails.application.config.assets.compress` 选项为 `true`)。只伺服一个文件可以大大减少页面加载时间,因为浏览器发起的请求数更少。压缩能减小文件大小,加快浏览器下载速度。 - -例如,新建的 Rails 4 程序中有个 `app/assets/javascripts/application.js` 文件,包含以下内容: - -```js -// ... -//= require jquery -//= require jquery_ujs -//= require_tree . -``` - -在 JavaScript 文件中,Sprockets 的指令以 `//=` 开头。在上面的文件中,用到了 `require` 和 the `require_tree` 指令。`require` 指令告知 Sprockets 要加载的文件。在上面的文件中,加载了 Sprockets 搜索路径中的 `jquery.js` 和 `jquery_ujs.js` 两个文件。文件名后无需加上扩展名,在 `.js` 文件中 Sprockets 默认会加载 `.js` 文件。 - -`require_tree` 指令告知 Sprockets 递归引入指定文件夹中的所有 JavaScript 文件。文件夹的路径必须相对于清单文件。也可使用 `require_directory` 指令加载指定文件夹中的所有 JavaScript 文件,但不会递归。 - -Sprockets 会按照从上至下的顺序处理指令,但 `require_tree` 引入的文件顺序是不可预期的,不要设想能得到一个期望的顺序。如果要确保某些 JavaScript 文件出现在其他文件之前,就要先在清单文件中引入。注意,`require` 等指令不会多次加载同一个文件。 - -Rails 还会生成 `app/assets/stylesheets/application.css` 文件,内容如下: - -```css -/* ... -*= require_self -*= require_tree . -*/ -``` - -不管创建新程序时有没有指定 `--skip-sprockets` 选项,Rails 4 都会生成 `app/assets/javascripts/application.js` 和 `app/assets/stylesheets/application.css`。这样如果后续需要使用 Asset Pipelining,操作就方便了。 - -样式表中使用的指令和 JavaScript 文件一样,不过加载的是样式表而不是 JavaScript 文件。`require_tree` 指令在 CSS 清单文件中的作用和在 JavaScript 清单文件中一样,从指定的文件夹中递归加载所有样式表。 - -上面的代码中还用到了 `require_self`。这么做可以把当前文件中的 CSS 加入调用 `require_self` 的位置。如果多次调用 `require_self`,只有最后一次调用有效。 - -NOTE: 如果想使用多个 Sass 文件,应该使用 [Sass 中的 `@import` 规则](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import),不要使用 Sprockets 指令。如果使用 Sprockets 指令,Sass 文件只出现在各自的作用域中,Sass 变量和混入只在定义所在文件中有效。为了达到 `require_tree` 指令的效果,可以使用通配符,例如 `@import "https://github.com/*"` 和 `@import "https://github.com/**/*"`。详情参见 [sass-rails 的文档](https://github.com/rails/sass-rails#features)。 - -清单文件可以有多个。例如,`admin.css` 和 `admin.js` 这两个清单文件包含程序管理后台所需的 JS 和 CSS 文件。 - -CSS 清单中的指令也适用前面介绍的加载顺序。分别引入各文件,Sprockets 会按照顺序编译。例如,可以按照下面的方式合并三个 CSS 文件: - -```css -/* ... -*= require reset -*= require layout -*= require chrome -*/ -``` - -### 预处理 - -静态资源的文件扩展名决定了使用哪个预处理器处理。如果使用默认的 gem,生成控制器或脚手架时,会生成 CoffeeScript 和 SCSS 文件,而不是普通的 JavaScript 和 CSS 文件。前文举过例子,生成 `projects` 控制器时会创建 `app/assets/javascripts/projects.js.coffee` 和 `app/assets/stylesheets/projects.css.scss` 两个文件。 - -在开发环境中,或者禁用 Asset Pipeline 时,这些文件会使用 `coffee-script` 和 `sass` 提供的预处理器处理,然后再发给浏览器。启用 Asset Pipeline 时,这些文件会先使用预处理器处理,然后保存到 `public/assets` 文件夹中,再由 Rails 程序或网页服务器伺服。 - -添加额外的扩展名可以增加预处理次数,预处理程序会按照扩展名从右至左的顺序处理文件内容。所以,扩展名的顺序要和处理的顺序一致。例如,名为 `app/assets/stylesheets/projects.css.scss.erb` 的样式表首先会使用 ERB 处理,然后是 SCSS,最后才以 CSS 格式发送给浏览器。JavaScript 文件类似,`app/assets/javascripts/projects.js.coffee.erb` 文件先由 ERB 处理,然后是 CoffeeScript,最后以 JavaScript 格式发送给浏览器。 - -记住,预处理器的执行顺序很重要。例如,名为 `app/assets/javascripts/projects.js.erb.coffee` 的文件首先由 CoffeeScript 处理,但是 CoffeeScript 预处理器并不懂 ERB 代码,因此会导致错误。 - -开发环境 --------- - -在开发环境中,Asset Pipeline 按照清单文件中指定的顺序伺服各静态资源。 - -清单 `app/assets/javascripts/application.js` 的内容如下: - -```js -//= require core -//= require projects -//= require tickets -``` - -生成的 HTML 如下: - -```html - - - -``` - -Sprockets 要求必须使用 `body` 参数。 - -### 检查运行时错误 - -默认情况下,在生产环境中 Asset Pipeline 会检查潜在的错误。要想禁用这一功能,可以做如下设置: - -```ruby -config.assets.raise_runtime_errors = false -``` - -`raise_runtime_errors` 设为 `false` 时,Sprockets 不会检查静态资源的依赖关系是否正确。遇到下面这种情况时,必须告知 Asset Pipeline 其中的依赖关系。 - -如果在 `application.css.erb` 中引用了 `logo.png`,如下所示: - -```css -#logo { background: url(https://github.com/%3C%=%20asset_data_uri%20'logo.png'%20%%3E) } -``` - -就必须声明 `logo.png` 是 `application.css.erb` 的一个依赖件,这样重新编译图片时才会同时重新编译 CSS 文件。依赖关系可以使用 `//= depend_on_asset` 声明: - -```css -//= depend_on_asset "logo.png" -#logo { background: url(https://github.com/%3C%=%20asset_data_uri%20'logo.png'%20%%3E) } -``` - -如果没有这个声明,在生产环境中可能遇到难以查找的奇怪问题。`raise_runtime_errors` 设为 `true` 时,运行时会自动检查依赖关系。 - -### 关闭调试功能 - -在 `config/environments/development.rb` 中添加如下设置可以关闭调试功能: - -```ruby -config.assets.debug = false -``` - -关闭调试功能后,Sprockets 会预处理所有文件,然后合并。关闭调试功能后,前文的清单文件生成的 HTML 如下: - -```html - -``` - -服务器启动后,首次请求发出后会编译并缓存静态资源。Sprockets 会把 `Cache-Control` 报头设为 `must-revalidate`。再次请求时,浏览器会得到 304 (Not Modified) 响应。 - -如果清单中的文件内容发生了变化,服务器会返回重新编译后的文件。 - -调试功能可以在 Rails 帮助方法中启用: - -```erb -<%= stylesheet_link_tag "application", debug: true %> -<%= javascript_include_tag "application", debug: true %> -``` - -如果已经启用了调试模式,再使用 `:debug` 选项就有点多余了。 - -在开发环境中也可启用压缩功能,检查是否能正常运行。需要调试时再禁用压缩即可。 - -生产环境 -------- - -在生产环境中,Sprockets 使用前文介绍的指纹机制。默认情况下,Rails 认为静态资源已经事先编译好了,直接由网页服务器伺服。 - -在预先编译的过程中,会根据文件的内容生成 MD5,写入硬盘时把 MD5 加到文件名中。Rails 帮助方法会使用加上指纹的文件名代替清单文件中使用的文件名。 - -例如: - -```erb -<%= javascript_include_tag "application" %> -<%= stylesheet_link_tag "application" %> -``` - -生成的 HTML 如下: - -```html - - -``` - -注意,推出 Asset Pipeline 功能后不再使用 `:cache` 和 `:concat` 选项了,请从 `javascript_include_tag` 和 `stylesheet_link_tag` 标签上将其删除。 - -指纹由 `config.assets.digest` 初始化选项控制(生产环境默认为 `true`,其他环境为 `false`)。 - -NOTE: 一般情况下,请勿修改 `config.assets.digest` 的默认值。如果文件名中没有指纹,而且缓存报头的时间设置为很久以后,那么即使文件的内容变了,客户端也不会重新获取文件。 - -### 事先编译好静态资源 - -Rails 提供了一个 rake 任务用来编译清单文件中的静态资源和其他相关文件。 - -编译后的静态资源保存在 `config.assets.prefix` 选项指定的位置。默认是 `/assets` 文件夹。 - -部署时可以在服务器上执行这个任务,直接在服务器上编译静态资源。下一节会介绍如何在本地编译。 - -这个 rake 任务是: - -```bash -$ RAILS_ENV=production bundle exec rake assets:precompile -``` - -Capistrano(v2.15.1 及以上版本)提供了一个配方,可在部署时编译静态资源。把下面这行加入 `Capfile` 文件即可: - -```ruby -load 'deploy/assets' -``` - -这个配方会把 `config.assets.prefix` 选项指定的文件夹链接到 `shared/assets`。如果 `shared/assets` 已经占用,就要修改部署任务。 - -在多次部署之间共用这个文件夹是十分重要的,这样只要缓存的页面可用,其中引用的编译后的静态资源就能正常使用。 - -默认编译的文件包括 `application.js`、`application.css` 以及 gem 中 `app/assets` 文件夹中的所有非 JS/CSS 文件(会自动加载所有图片): - -```ruby -[ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) }, -/application.(css|js)$/ ] -``` - -NOTE: 这个正则表达式表示最终要编译的文件。也就是说,JS/CSS 文件不包含在内。例如,因为 `.coffee` 和 `.scss` 文件能编译成 JS 和 CSS 文件,所以**不在**自动编译的范围内。 - -如果想编译其他清单,或者单独的样式表和 JavaScript,可以添加到 `config/application.rb` 文件中的 `precompile` 选项: - -```ruby -config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] -``` - -或者可以按照下面的方式,设置编译所有静态资源: - -```ruby -# config/application.rb -config.assets.precompile << Proc.new do |path| - if path =~ /\.(css|js)\z/ - full_path = Rails.application.assets.resolve(path).to_path - app_assets_path = Rails.root.join('app', 'assets').to_path - if full_path.starts_with? app_assets_path - puts "including asset: " + full_path - true - else - puts "excluding asset: " + full_path - false - end - else - false - end -end -``` - -NOTE: 即便想添加 Sass 或 CoffeeScript 文件,也要把希望编译的文件名设为 .js 或 .css。 - -这个 rake 任务还会生成一个名为 `manifest-md5hash.json` 的文件,列出所有静态资源和对应的指纹。这样 Rails 帮助方法就不用再通过 Sprockets 获取指纹了。下面是一个 `manifest-md5hash.json` 文件内容示例: - -```ruby -{"files":{"application-723d1be6cc741a3aabb1cec24276d681.js":{"logical_path":"application.js","mtime":"2013-07-26T22:55:03-07:00","size":302506, -"digest":"723d1be6cc741a3aabb1cec24276d681"},"application-12b3c7dd74d2e9df37e7cbb1efa76a6d.css":{"logical_path":"application.css","mtime":"2013-07-26T22:54:54-07:00","size":1560, -"digest":"12b3c7dd74d2e9df37e7cbb1efa76a6d"},"application-1c5752789588ac18d7e1a50b1f0fd4c2.css":{"logical_path":"application.css","mtime":"2013-07-26T22:56:17-07:00","size":1591, -"digest":"1c5752789588ac18d7e1a50b1f0fd4c2"},"favicon-a9c641bf2b81f0476e876f7c5e375969.ico":{"logical_path":"favicon.ico","mtime":"2013-07-26T23:00:10-07:00","size":1406, -"digest":"a9c641bf2b81f0476e876f7c5e375969"},"my_image-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"my_image.png","mtime":"2013-07-26T23:00:27-07:00","size":6646, -"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets"{"application.js": -"application-723d1be6cc741a3aabb1cec24276d681.js","application.css": -"application-1c5752789588ac18d7e1a50b1f0fd4c2.css", -"favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png": -"my_image-231a680f23887d9dd70710ea5efd3c62.png"}} -``` - -`manifest-md5hash.json` 文件的存放位置是 `config.assets.prefix` 选项指定位置(默认为 `/assets`)的根目录。 - -NOTE: 在生产环境中,如果找不到编译好的文件,会抛出 `Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError` 异常,并提示找不到哪个文件。 - -#### 把 Expires 报头设置为很久以后 - -编译好的静态资源存放在服务器的文件系统中,直接由网页服务器伺服。默认情况下,没有为这些文件设置一个很长的过期时间。为了能充分发挥指纹的作用,需要修改服务器的设置,添加相关的报头。 - -针对 Apache 的设置: - -```conf -# The Expires* directives requires the Apache module -# `mod_expires` to be enabled. - - # Use of ETag is discouraged when Last-Modified is present - Header unset ETag FileETag None - # RFC says only cache for 1 year - ExpiresActive On ExpiresDefault "access plus 1 year" - -``` - -针对 Nginx 的设置: - -```conf -location ~ ^/assets/ { - expires 1y; - add_header Cache-Control public; - - add_header ETag ""; - break; -} -``` - -#### GZip 压缩 - -Sprockets 预编译文件时还会创建静态资源的 [gzip](http://en.wikipedia.org/wiki/Gzip) 版本(.gz)。网页服务器一般使用中等压缩比例,不过因为预编译只发生一次,所以 Sprockets 会使用最大的压缩比例,尽量减少传输的数据大小。网页服务器可以设置成直接从硬盘伺服压缩版文件,无需直接压缩文件本身。 - -在 Nginx 中启动 `gzip_static` 模块后就能自动实现这一功能: - -```nginx -location ~ ^/(assets)/ { - root /path/to/public; - gzip_static on; # to serve pre-gzipped version - expires max; - add_header Cache-Control public; -} -``` - -如果编译 Nginx 时加入了 `gzip_static` 模块,就能使用这个指令。Nginx 针对 Ubuntu/Debian 的安装包,以及 `nginx-light` 都会编译这个模块。否则就要手动编译: - -```bash -./configure --with-http_gzip_static_module -``` - -如果编译支持 Phusion Passenger 的 Nginx,就必须加入这个命令行选项。 - -针对 Apache 的设置很复杂,请自行 Google。 - -### 在本地预编译 - -为什么要在本地预编译静态文件呢?原因如下: - -* 可能无权限访问生产环境服务器的文件系统; -* 可能要部署到多个服务器,避免重复编译; -* 可能会经常部署,但静态资源很少改动; - -在本地预编译后,可以把编译好的文件纳入版本控制系统,再按照常规的方式部署。 - -不过有两点要注意: - -* 一定不能运行 Capistrano 部署任务来预编译静态资源; -* 必须修改下面这个设置; - -在 `config/environments/development.rb` 中加入下面这行代码: - -```ruby -config.assets.prefix = "/dev-assets" -``` - -修改 `prefix` 后,在开发环境中 Sprockets 会使用其他的 URL 伺服静态资源,把请求都交给 Sprockets 处理。但在生产环境中 `prefix` 仍是 `/assets`。如果没作上述修改,在生产环境中会从 `/assets` 伺服静态资源,除非再次编译,否则看不到文件的变化。 - -同时还要确保所需的压缩程序在生产环境中可用。 - -在本地预编译静态资源,这些文件就会出现在工作目录中,而且可以根据需要纳入版本控制系统。开发环境仍能按照预期正常运行。 - -### 实时编译 - -某些情况下可能需要实时编译,此时静态资源直接由 Sprockets 处理。 - -要想使用实时编译,要做如下设置: - -```ruby -config.assets.compile = true -``` - -初次请求时,Asset Pipeline 会编译静态资源,并缓存,这一过程前文已经提过了。引用文件时,会使用加上 MD5 哈希的文件名代替清单文件中的名字。 - -Sprockets 还会把 `Cache-Control` 报头设为 `max-age=31536000`。这个报头的意思是,服务器和客户端浏览器之间的缓存可以存储一年,以减少从服务器上获取静态资源的请求数量。静态资源的内容可能存在本地浏览器的缓存或者其他中间缓存中。 - -实时编译消耗的内存更多,比默认的编译方式性能更低,因此不推荐使用。 - -如果要把程序部署到没有安装 JavaScript 运行时的服务器,可以在 `Gemfile` 中加入: - -```ruby -group :production do - gem 'therubyracer' -end -``` - -### CDN - -如果用 CDN 分发静态资源,要确保文件不会被缓存,因为缓存会导致问题。如果设置了 `config.action_controller.perform_caching = true`,`Rack::Cache` 会使用 `Rails.cache` 存储静态文件,很快缓存空间就会用完。 - -每种缓存的工作方式都不一样,所以要了解你所用 CDN 是如何处理缓存的,确保能和 Asset Pipeline 和谐相处。有时你会发现某些设置能导致诡异的表现,而有时又不会。例如,作为 HTTP 缓存使用时,Nginx 的默认设置就不会出现什么问题。 - -定制 Asset Pipeline -------------------- - -### 压缩 CSS - -压缩 CSS 的方式之一是使用 YUI。[YUI CSS compressor](http://yui.github.io/yuicompressor/css.html) 提供了压缩功能。 - -下面这行设置会启用 YUI 压缩,在此之前要先安装 `yui-compressor` gem: - -```ruby -config.assets.css_compressor = :yui -``` - -如果安装了 `sass-rails` gem,还可以使用其他的方式压缩 CSS: - -```ruby -config.assets.css_compressor = :sass -``` - -### 压缩 JavaScript - -压缩 JavaScript 的方式有:`:closure`,`:uglifier` 和 `:yui`。这三种方式分别需要安装 `closure-compiler`、`uglifier` 和 `yui-compressor`。 - -默认的 `Gemfile` 中使用的是 [uglifier](https://github.com/lautis/uglifier)。这个 gem 使用 Ruby 包装了 [UglifyJS](https://github.com/mishoo/UglifyJS)(为 NodeJS 开发)。uglifier 可以删除空白和注释,缩短本地变量名,还会做些微小的优化,例如把 `if...else` 语句改写成三元操作符形式。 - -下面这行设置使用 `uglifier` 压缩 JavaScript: - -```ruby -config.assets.js_compressor = :uglifier -``` - -NOTE: 系统中要安装支持 [ExecJS](https://github.com/sstephenson/execjs#readme) 的运行时才能使用 `uglifier`。Mac OS X 和 Windows 系统中已经安装了 JavaScript 运行时。 -I> -NOTE: `config.assets.compress` 初始化选项在 Rails 4 中不可用,即便设置了也没有效果。请分别使用 `config.assets.css_compressor` 和 `config.assets.js_compressor` 这两个选项设置 CSS 和 JavaScript 的压缩方式。 - -### 使用自己的压缩程序 - -设置压缩 CSS 和 JavaScript 所用压缩程序的选项还可接受对象,这个对象必须能响应 `compress` 方法。`compress` 方法只接受一个字符串参数,返回值也必须是字符串。 - -```ruby -class Transformer - def compress(string) - do_something_returning_a_string(string) - end -end -``` - -要想使用这个压缩程序,请在 `application.rb` 中做如下设置: - -```ruby -config.assets.css_compressor = Transformer.new -``` - -### 修改 `assets` 的路径 - -Sprockets 默认使用的公开路径是 `/assets`。 - -这个路径可以修改成其他值: - -```ruby -config.assets.prefix = "/some_other_path" -``` - -升级没使用 Asset Pipeline 的旧项目时,或者默认路径已有其他用途,或者希望指定一个新资源路径时,可以设置这个选项。 - -### X-Sendfile 报头 - -X-Sendfile 报头的作用是让服务器忽略程序的响应,直接从硬盘上伺服指定的文件。默认情况下服务器不会发送这个报头,但在支持该报头的服务器上可以启用。启用后,会跳过响应直接由服务器伺服文件,速度更快。X-Sendfile 报头的用法参见 [API 文档](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file)。 - -Apache 和 Nginx 都支持这个报头,可以在 `config/environments/production.rb` 中启用: - -```ruby -# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache -# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx -``` - -WARNING: 如果升级现有程序,请把这两个设置写入 `production.rb`,以及其他类似生产环境的设置文件中。不能写入 `application.rb`。 - -TIP: 详情参见生产环境所用服务器的文档: -T> -TIP: - [Apache](https://tn123.org/mod_xsendfile/) -TIP: - [Nginx](http://wiki.nginx.org/XSendfile) - -静态资源缓存的存储方式 -------------------- - -在开发环境和生产环境中,Sprockets 使用 Rails 默认的存储方式缓存静态资源。可以使用 `config.assets.cache_store` 设置使用其他存储方式: - -```ruby -config.assets.cache_store = :memory_store -``` - -静态资源缓存可用的存储方式和程序的缓存存储一样。 - -```ruby -config.assets.cache_store = :memory_store, { size: 32.megabytes } -``` - -在 gem 中使用静态资源 -------------------- - -静态资源也可由 gem 提供。 - -为 Rails 提供标准 JavaScript 代码库的 `jquery-rails` gem 是个很好的例子。这个 gem 中有个引擎类,继承自 `Rails::Engine`。添加这层继承关系后,Rails 就知道这个 gem 中可能包含静态资源文件,会把这个引擎中的 `app/assets`、`lib/assets` 和 `vendor/assets` 三个文件夹加入 Sprockets 的搜索路径中。 - -把代码库或者 gem 变成预处理器 --------------------------- - -Sprockets 使用 [Tilt](https://github.com/rtomayko/tilt) 作为不同模板引擎的通用接口。在你自己的 gem 中也可实现 Tilt 的模板协议。一般情况下,需要继承 `Tilt::Template` 类,然后重新定义 `prepare` 方法(初始化模板),以及 `evaluate` 方法(返回处理后的内容)。原始数据存储在 `data` 中。详情参见 [`Tilt::Template`](https://github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb) 类的源码。 - -```ruby -module BangBang - class Template < ::Tilt::Template - def prepare - # Do any initialization here - end - - # Adds a "!" to original template. - def evaluate(scope, locals, &block) - "#{data}!" - end - end -end -``` - -上述代码定义了 `Template` 类,然后还需要关联模板文件的扩展名: - -```ruby -Sprockets.register_engine '.bang', BangBang::Template -``` - -升级旧版本 Rails ---------------- - -从 Rails 3.0 或 Rails 2.x 升级,有一些问题要解决。首先,要把 `public/` 文件夹中的文件移到新位置。不同类型文件的存放位置参见“[静态资源的组织方式](#asset-organization)”一节。 - -其次,避免 JavaScript 文件重复出现。因为从 Rails 3.1 开始,jQuery 是默认的 JavaScript 库,因此不用把 `jquery.js` 复制到 `app/assets` 文件夹中。Rails 会自动加载 jQuery。 - -然后,更新各环境的设置文件,添加默认设置。 - -在 `application.rb` 中加入: - -```ruby -# Version of your assets, change this if you want to expire all your assets -config.assets.version = '1.0' - -# Change the path that assets are served from config.assets.prefix = "/assets" -``` - -在 `development.rb` 中加入: - -```ruby -# Expands the lines which load the assets -config.assets.debug = true -``` - -在 `production.rb` 中加入: - -```ruby -# Choose the compressors to use (if any) config.assets.js_compressor = -# :uglifier config.assets.css_compressor = :yui - -# Don't fallback to assets pipeline if a precompiled asset is missed -config.assets.compile = false - -# Generate digests for assets URLs. This is planned for deprecation. -config.assets.digest = true - -# Precompile additional assets (application.js, application.css, and all -# non-JS/CSS are already added) config.assets.precompile += %w( search.js ) -``` - -Rails 4 不会在 `test.rb` 中添加 Sprockets 的默认设置,所以要手动添加。测试环境中以前的默认设置是:`config.assets.compile = true`,`config.assets.compress = false`,`config.assets.debug = false` 和 `config.assets.digest = false`。 - -最后,还要在 `Gemfile` 中加入以下 gem: - -```ruby -gem 'sass-rails', "~> 3.2.3" -gem 'coffee-rails', "~> 3.2.1" -gem 'uglifier' -``` diff --git a/source/zh-CN/association_basics.md b/source/zh-CN/association_basics.md deleted file mode 100644 index 4979784..0000000 --- a/source/zh-CN/association_basics.md +++ /dev/null @@ -1,2198 +0,0 @@ -Active Record 关联 -================== - -本文介绍 Active Record 中的关联功能。 - -读完本文,你将学到: - -* 如何声明 Active Record 模型间的关联; -* 怎么理解不同的 Active Record 关联类型; -* 如何使用关联添加的方法; - --------------------------------------------------------------------------------- - -为什么要使用关联 --------------- - -模型之间为什么要有关联?因为关联让常规操作更简单。例如,在一个简单的 Rails 程序中,有一个顾客模型和一个订单模型。每个顾客可以下多个订单。没用关联的模型定义如下: - -```ruby -class Customer < ActiveRecord::Base -end - -class Order < ActiveRecord::Base -end -``` - -假如我们要为一个顾客添加一个订单,得这么做: - -```ruby -@order = Order.create(order_date: Time.now, customer_id: @customer.id) -``` - -或者说要删除一个顾客,确保他的所有订单都会被删除,得这么做: - -```ruby -@orders = Order.where(customer_id: @customer.id) -@orders.each do |order| - order.destroy -end -@customer.destroy -``` - -使用 Active Record 关联,告诉 Rails 这两个模型是有一定联系的,就可以把这些操作连在一起。下面使用关联重新定义顾客和订单模型: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, dependent: :destroy -end - -class Order < ActiveRecord::Base - belongs_to :customer -end -``` - -这么修改之后,为某个顾客添加新订单就变得简单了: - -```ruby -@order = @customer.orders.create(order_date: Time.now) -``` - -删除顾客及其所有订单更容易: - -```ruby -@customer.destroy -``` - -学习更多关联类型,请阅读下一节。下一节介绍了一些使用关联时的小技巧,然后列出了关联添加的所有方法和选项。 - -关联的类型 ---------- - -在 Rails 中,关联是两个 Active Record 模型之间的关系。关联使用宏的方式实现,用声明的形式为模型添加功能。例如,声明一个模型属于(`belongs_to`)另一个模型后,Rails 会维护两个模型之间的“主键-外键”关系,而且还向模型中添加了很多实用的方法。Rails 支持六种关联: - -* `belongs_to` -* `has_one` -* `has_many` -* `has_many :through` -* `has_one :through` -* `has_and_belongs_to_many` - -在后面的几节中,你会学到如何声明并使用这些关联。首先来看一下各种关联适用的场景。 - -### `belongs_to` 关联 - -`belongs_to` 关联创建两个模型之间一对一的关系,声明所在的模型实例属于另一个模型的实例。例如,如果程序中有顾客和订单两个模型,每个订单只能指定给一个顾客,就要这么声明订单模型: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer -end -``` - -![belongs_to 关联](images/belongs_to.png) - -NOTE: 在 `belongs_to` 关联声明中必须使用单数形式。如果在上面的代码中使用复数形式,程序会报错,提示未初始化常量 `Order::Customers`。因为 Rails 自动使用关联中的名字引用类名。如果关联中的名字错误的使用复数,引用的类也就变成了复数。 - -相应的迁移如下: - -```ruby -class CreateOrders < ActiveRecord::Migration - def change - create_table :customers do |t| - t.string :name - t.timestamps - end - - create_table :orders do |t| - t.belongs_to :customer - t.datetime :order_date - t.timestamps - end - end -end -``` - -### `has_one` 关联 - -`has_one` 关联也会建立两个模型之间的一对一关系,但语义和结果有点不一样。这种关联表示模型的实例包含或拥有另一个模型的实例。例如,在程序中,每个供应商只有一个账户,可以这么定义供应商模型: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account -end -``` - -![has_one 关联](images/has_one.png) - -相应的迁移如下: - -```ruby -class CreateSuppliers < ActiveRecord::Migration - def change - create_table :suppliers do |t| - t.string :name - t.timestamps - end - - create_table :accounts do |t| - t.belongs_to :supplier - t.string :account_number - t.timestamps - end - end -end -``` - -### `has_many` 关联 - -`has_many` 关联建立两个模型之间的一对多关系。在 `belongs_to` 关联的另一端经常会使用这个关联。`has_many` 关联表示模型的实例有零个或多个另一个模型的实例。例如,在程序中有顾客和订单两个模型,顾客模型可以这么定义: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -NOTE: 声明 `has_many` 关联时,另一个模型使用复数形式。 - -![has_many 关联](images/has_many.png) - -相应的迁移如下: - -```ruby -class CreateCustomers < ActiveRecord::Migration - def change - create_table :customers do |t| - t.string :name - t.timestamps - end - - create_table :orders do |t| - t.belongs_to :customer - t.datetime :order_date - t.timestamps - end - end -end -``` - -### `has_many :through` 关联 - -`has_many :through` 关联经常用来建立两个模型之间的多对多关联。这种关联表示一个模型的实例可以借由第三个模型,拥有零个和多个另一个模型的实例。例如,在看病过程中,病人要和医生预约时间。这中间的关联声明如下: - -```ruby -class Physician < ActiveRecord::Base - has_many :appointments - has_many :patients, through: :appointments -end - -class Appointment < ActiveRecord::Base - belongs_to :physician - belongs_to :patient -end - -class Patient < ActiveRecord::Base - has_many :appointments - has_many :physicians, through: :appointments -end -``` - -![has_many :through 关联](images/has_many_through.png) - -相应的迁移如下: - -```ruby -class CreateAppointments < ActiveRecord::Migration - def change - create_table :physicians do |t| - t.string :name - t.timestamps - end - - create_table :patients do |t| - t.string :name - t.timestamps - end - - create_table :appointments do |t| - t.belongs_to :physician - t.belongs_to :patient - t.datetime :appointment_date - t.timestamps - end - end -end -``` - -连接模型中的集合可以使用 API 关联。例如: - -```ruby -physician.patients = patients -``` - -会为新建立的关联对象创建连接模型实例,如果其中一个对象删除了,相应的记录也会删除。 - - -WARNING: 自动删除连接模型的操作直接执行,不会触发 `*_destroy` 回调。 - -`has_many :through` 还可用来简化嵌套的 `has_many` 关联。例如,一个文档分为多个部分,每一部分又有多个段落,如果想使用简单的方式获取文档中的所有段落,可以这么做: - -```ruby -class Document < ActiveRecord::Base - has_many :sections - has_many :paragraphs, through: :sections -end - -class Section < ActiveRecord::Base - belongs_to :document - has_many :paragraphs -end - -class Paragraph < ActiveRecord::Base - belongs_to :section -end -``` - -加上 `through: :sections` 后,Rails 就能理解这段代码: - -```ruby -@document.paragraphs -``` - -### `has_one :through` 关联 - -`has_one :through` 关联建立两个模型之间的一对一关系。这种关联表示一个模型通过第三个模型拥有另一个模型的实例。例如,每个供应商只有一个账户,而且每个账户都有一个历史账户,那么可以这么定义模型: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account - has_one :account_history, through: :account -end - -class Account < ActiveRecord::Base - belongs_to :supplier - has_one :account_history -end - -class AccountHistory < ActiveRecord::Base - belongs_to :account -end -``` - -![has_one :through 关联](images/has_one_through.png) - -相应的迁移如下: - -```ruby -class CreateAccountHistories < ActiveRecord::Migration - def change - create_table :suppliers do |t| - t.string :name - t.timestamps - end - - create_table :accounts do |t| - t.belongs_to :supplier - t.string :account_number - t.timestamps - end - - create_table :account_histories do |t| - t.belongs_to :account - t.integer :credit_rating - t.timestamps - end - end -end -``` - -### `has_and_belongs_to_many` 关联 - -`has_and_belongs_to_many` 关联之间建立两个模型之间的多对多关系,不借由第三个模型。例如,程序中有装配体和零件两个模型,每个装配体中有多个零件,每个零件又可用于多个装配体,这时可以按照下面的方式定义模型: - -```ruby -class Assembly < ActiveRecord::Base - has_and_belongs_to_many :parts -end - -class Part < ActiveRecord::Base - has_and_belongs_to_many :assemblies -end -``` - -![has_and_belongs_to_many 关联](images/habtm.png) - -相应的迁移如下: - -```ruby -class CreateAssembliesAndParts < ActiveRecord::Migration - def change - create_table :assemblies do |t| - t.string :name - t.timestamps - end - - create_table :parts do |t| - t.string :part_number - t.timestamps - end - - create_table :assemblies_parts, id: false do |t| - t.belongs_to :assembly - t.belongs_to :part - end - end -end -``` - -### 使用 `belongs_to` 还是 `has_one` - -如果想建立两个模型之间的一对一关系,可以在一个模型中声明 `belongs_to`,然后在另一模型中声明 `has_one`。但是怎么知道在哪个模型中声明哪种关联? - -不同的声明方式带来的区别是外键放在哪个模型对应的数据表中(外键在声明 `belongs_to` 关联所在模型对应的数据表中)。不过声明时要考虑一下语义,`has_one` 的意思是某样东西属于我。例如,说供应商有一个账户,比账户拥有供应商更合理,所以正确的关联应该这么声明: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account -end - -class Account < ActiveRecord::Base - belongs_to :supplier -end -``` - -相应的迁移如下: - -```ruby -class CreateSuppliers < ActiveRecord::Migration - def change - create_table :suppliers do |t| - t.string :name - t.timestamps - end - - create_table :accounts do |t| - t.integer :supplier_id - t.string :account_number - t.timestamps - end - end -end -``` - -NOTE: `t.integer :supplier_id` 更明确的表明了外键的名字。在目前的 Rails 版本中,可以抽象实现的细节,使用 `t.references :supplier` 代替。 - -### 使用 `has_many :through` 还是 `has_and_belongs_to_many` - -Rails 提供了两种建立模型之间多对多关系的方法。其中比较简单的是 `has_and_belongs_to_many`,可以直接建立关联: - -```ruby -class Assembly < ActiveRecord::Base - has_and_belongs_to_many :parts -end - -class Part < ActiveRecord::Base - has_and_belongs_to_many :assemblies -end -``` - -第二种方法是使用 `has_many :through`,但无法直接建立关联,要通过第三个模型: - -```ruby -class Assembly < ActiveRecord::Base - has_many :manifests - has_many :parts, through: :manifests -end - -class Manifest < ActiveRecord::Base - belongs_to :assembly - belongs_to :part -end - -class Part < ActiveRecord::Base - has_many :manifests - has_many :assemblies, through: :manifests -end -``` - -根据经验,如果关联的第三个模型要作为独立实体使用,要用 `has_many :through` 关联;如果不需要使用第三个模型,用简单的 `has_and_belongs_to_many` 关联即可(不过要记得在数据库中创建连接数据表)。 - -如果需要做数据验证、回调,或者连接模型上要用到其他属性,此时就要使用 `has_many :through` 关联。 - -### 多态关联 - -关联还有一种高级用法,“多态关联”。在多态关联中,在同一个关联中,模型可以属于其他多个模型。例如,图片模型可以属于雇员模型或者产品模型,模型的定义如下: - -```ruby -class Picture < ActiveRecord::Base - belongs_to :imageable, polymorphic: true -end - -class Employee < ActiveRecord::Base - has_many :pictures, as: :imageable -end - -class Product < ActiveRecord::Base - has_many :pictures, as: :imageable -end -``` - -在 `belongs_to` 中指定使用多态,可以理解成创建了一个接口,可供任何一个模型使用。在 `Employee` 模型实例上,可以使用 `@employee.pictures` 获取图片集合。类似地,可使用 `@product.pictures` 获取产品的图片。 - -在 `Picture` 模型的实例上,可以使用 `@picture.imageable` 获取父对象。不过事先要在声明多态接口的模型中创建外键字段和类型字段: - -```ruby -class CreatePictures < ActiveRecord::Migration - def change - create_table :pictures do |t| - t.string :name - t.integer :imageable_id - t.string :imageable_type - t.timestamps - end - end -end -``` - -上面的迁移可以使用 `t.references` 简化: - -```ruby -class CreatePictures < ActiveRecord::Migration - def change - create_table :pictures do |t| - t.string :name - t.references :imageable, polymorphic: true - t.timestamps - end - end -end -``` - -![多态关联](images/polymorphic.png) - -### 自连接 - -设计数据模型时会发现,有时模型要和自己建立关联。例如,在一个数据表中保存所有雇员的信息,但要建立经理和下属之间的关系。这种情况可以使用自连接关联解决: - -```ruby -class Employee < ActiveRecord::Base - has_many :subordinates, class_name: "Employee", - foreign_key: "manager_id" - - belongs_to :manager, class_name: "Employee" -end -``` - -这样定义模型后,就可以使用 `@employee.subordinates` 和 `@employee.manager` 了。 - -在迁移中,要添加一个引用字段,指向模型自身: - -```ruby -class CreateEmployees < ActiveRecord::Migration - def change - create_table :employees do |t| - t.references :manager - t.timestamps - end - end -end -``` - -小技巧和注意事项 --------------- - -在 Rails 程序中高效地使用 Active Record 关联,要了解以下几个知识: - -* 缓存控制 -* 避免命名冲突 -* 更新模式 -* 控制关联的作用域 -* Bi-directional associations - -### 缓存控制 - -关联添加的方法都会使用缓存,记录最近一次查询结果,以备后用。缓存还会在方法之间共享。例如: - -```ruby -customer.orders # retrieves orders from the database -customer.orders.size # uses the cached copy of orders -customer.orders.empty? # uses the cached copy of orders -``` - -程序的其他部分会修改数据,那么应该怎么重载缓存呢?调用关联方法时传入 `true` 参数即可: - -```ruby -customer.orders # retrieves orders from the database -customer.orders.size # uses the cached copy of orders -customer.orders(true).empty? # discards the cached copy of orders - # and goes back to the database -``` - -### 避免命名冲突 - -关联的名字并不能随意使用。因为创建关联时,会向模型添加同名方法,所以关联的名字不能和 `ActiveRecord::Base` 中的实例方法同名。如果同名,关联方法会覆盖 `ActiveRecord::Base` 中的实例方法,导致错误。例如,关联的名字不能为 `attributes` 或 `connection`。 - -### 更新模式 - -关联非常有用,但没什么魔法。关联对应的数据库模式需要你自己编写。不同的关联类型,要做的事也不同。对 `belongs_to` 关联来说,要创建外键;对 `has_and_belongs_to_many` 来说,要创建相应的连接数据表。 - -#### 创建 `belongs_to` 关联所需的外键 - -声明 `belongs_to` 关联后,要创建相应的外键。例如,有下面这个模型: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer -end -``` - -这种关联需要在数据表中创建合适的外键: - -```ruby -class CreateOrders < ActiveRecord::Migration - def change - create_table :orders do |t| - t.datetime :order_date - t.string :order_number - t.integer :customer_id - end - end -end -``` - -如果声明关联之前已经定义了模型,则要在迁移中使用 `add_column` 创建外键。 - -#### 创建 `has_and_belongs_to_many` 关联所需的连接数据表 - -声明 `has_and_belongs_to_many` 关联后,必须手动创建连接数据表。除非在 `:join_table` 选项中指定了连接数据表的名字,否则 Active Record 会按照类名出现在字典中的顺序为数据表起名字。那么,顾客和订单模型使用的连接数据表默认名为“customers_orders”,因为在字典中,“c”在“o”前面。 - -WARNING: 模型名的顺序使用字符串的 `<` 操作符确定。所以,如果两个字符串的长度不同,比较最短长度时,两个字符串是相等的,但长字符串的排序比短字符串靠前。例如,你可能以为“"paper\_boxes”和“papers”这两个表生成的连接表名为“papers\_paper\_boxes”,因为“paper\_boxes”比“papers”长。其实生成的连接表名为“paper\_boxes\_papers”,因为在一般的编码方式中,“\_”比“s”靠前。 - -不管名字是什么,你都要在迁移中手动创建连接数据表。例如下面的关联声明: - -```ruby -class Assembly < ActiveRecord::Base - has_and_belongs_to_many :parts -end - -class Part < ActiveRecord::Base - has_and_belongs_to_many :assemblies -end -``` - -需要在迁移中创建 `assemblies_parts` 数据表,而且该表无主键: - -```ruby -class CreateAssembliesPartsJoinTable < ActiveRecord::Migration - def change - create_table :assemblies_parts, id: false do |t| - t.integer :assembly_id - t.integer :part_id - end - end -end -``` - -我们把 `id: false` 选项传给 `create_table` 方法,因为这个表不对应模型。只有这样,关联才能正常建立。如果在使用 `has_and_belongs_to_many` 关联时遇到奇怪的表现,例如提示模型 ID 损坏,或 ID 冲突,有可能就是因为创建了主键。 - -### 控制关联的作用域 - -默认情况下,关联只会查找当前模块作用域中的对象。如果在模块中定义 Active Record 模型,知道这一点很重要。例如: - -```ruby -module MyApplication - module Business - class Supplier < ActiveRecord::Base - has_one :account - end - - class Account < ActiveRecord::Base - belongs_to :supplier - end - end -end -``` - -上面的代码能正常运行,因为 `Supplier` 和 `Account` 在同一个作用域内。但下面这段代码就不行了,因为 `Supplier` 和 `Account` 在不同的作用域中: - -```ruby -module MyApplication - module Business - class Supplier < ActiveRecord::Base - has_one :account - end - end - - module Billing - class Account < ActiveRecord::Base - belongs_to :supplier - end - end -end -``` - -要想让处在不同命名空间中的模型正常建立关联,声明关联时要指定完整的类名: - -```ruby -module MyApplication - module Business - class Supplier < ActiveRecord::Base - has_one :account, - class_name: "MyApplication::Billing::Account" - end - end - - module Billing - class Account < ActiveRecord::Base - belongs_to :supplier, - class_name: "MyApplication::Business::Supplier" - end - end -end -``` - -### 双向关联 - -一般情况下,都要求能在关联的两端进行操作。例如,有下面的关联声明: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders -end - -class Order < ActiveRecord::Base - belongs_to :customer -end -``` - -默认情况下,Active Record 并不知道这个关联中两个模型之间的联系。可能导致同一对象的两个副本不同步: - -```ruby -c = Customer.first -o = c.orders.first -c.first_name == o.customer.first_name # => true -c.first_name = 'Manny' -c.first_name == o.customer.first_name # => false -``` - -之所以会发生这种情况,是因为 `c` 和 `o.customer` 在内存中是同一数据的两种表示,修改其中一个并不会刷新另一个。Active Record 提供了 `:inverse_of` 选项,可以告知 Rails 两者之间的关系: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, inverse_of: :customer -end - -class Order < ActiveRecord::Base - belongs_to :customer, inverse_of: :orders -end -``` - -这么修改之后,Active Record 就只会加载一个顾客对象,避免数据的不一致性,提高程序的执行效率: - -```ruby -c = Customer.first -o = c.orders.first -c.first_name == o.customer.first_name # => true -c.first_name = 'Manny' -c.first_name == o.customer.first_name # => true -``` - -`inverse_of` 有些限制: - -* 不能和 `:through` 选项同时使用; -* 不能和 `:polymorphic` 选项同时使用; -* 不能和 `:as` 选项同时使用; -* 在 `belongs_to` 关联中,会忽略 `has_many` 关联的 `inverse_of` 选项; - -每种关联都会尝试自动找到关联的另一端,设置 `:inverse_of` 选项(根据关联的名字)。使用标准名字的关联都有这种功能。但是,如果在关联中设置了下面这些选项,将无法自动设置 `:inverse_of`: - -* `:conditions` -* `:through` -* `:polymorphic` -* `:foreign_key` - -关联详解 -------- - -下面几节详细说明各种关联,包括添加的方法和声明关联时可以使用的选项。 - -### `belongs_to` 关联详解 - -`belongs_to` 关联创建一个模型与另一个模型之间的一对一关系。用数据库的行话来说,就是这个类中包含了外键。如果外键在另一个类中,就应该使用 `has_one` 关联。 - -#### `belongs_to` 关联添加的方法 - -声明 `belongs_to` 关联后,所在的类自动获得了五个和关联相关的方法: - -* `association(force_reload = false)` -* `association=(associate)` -* `build_association(attributes = {})` -* `create_association(attributes = {})` -* `create_association!(attributes = {})` - -这五个方法中的 `association` 要替换成传入 `belongs_to` 方法的第一个参数。例如,如下的声明: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer -end -``` - -每个 `Order` 模型实例都获得了这些方法: - -```ruby -customer -customer= -build_customer -create_customer -create_customer! -``` - -NOTE: 在 `has_one` 和 `belongs_to` 关联中,必须使用 `build_*` 方法构建关联对象。`association.build` 方法是在 `has_many` 和 `has_and_belongs_to_many` 关联中使用的。创建关联对象要使用 `create_*` 方法。 - -##### `association(force_reload = false)` - -如果关联的对象存在,`association` 方法会返回关联对象。如果找不到关联对象,则返回 `nil`。 - -```ruby -@customer = @order.customer -``` - -如果关联对象之前已经取回,会返回缓存版本。如果不想使用缓存版本,强制重新从数据库中读取,可以把 `force_reload` 参数设为 `true`。 - -##### `association=(associate)` - -`association=` 方法用来赋值关联的对象。这个方法的底层操作是,从关联对象上读取主键,然后把值赋给该主键对应的对象。 - -```ruby -@order.customer = @customer -``` - -##### `build_association(attributes = {})` - -`build_association` 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,和对象连接的外键会自动设置,但关联对象不会存入数据库。 - -```ruby -@customer = @order.build_customer(customer_number: 123, - customer_name: "John Doe") -``` - -##### `create_association(attributes = {})` - -`create_association` 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,和对象连接的外键会自动设置,只要能通过所有数据验证,就会把关联对象存入数据库。 - -```ruby -@customer = @order.create_customer(customer_number: 123, - customer_name: "John Doe") -``` - -##### `create_association!(attributes = {})` - -和 `create_association` 方法作用相同,但是如果记录不合法,会抛出 `ActiveRecord::RecordInvalid` 异常。 - -#### `belongs_to` 方法的选项 - -Rails 的默认设置足够智能,能满足常见需求。但有时还是需要定制 `belongs_to` 关联的行为。定制的方法很简单,声明关联时传入选项或者使用代码块即可。例如,下面的关联使用了两个选项: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, dependent: :destroy, - counter_cache: true -end -``` - -`belongs_to` 关联支持以下选项: - -* `:autosave` -* `:class_name` -* `:counter_cache` -* `:dependent` -* `:foreign_key` -* `:inverse_of` -* `:polymorphic` -* `:touch` -* `:validate` - -##### `:autosave` - -如果把 `:autosave` 选项设为 `true`,保存父对象时,会自动保存所有子对象,并把标记为析构的子对象销毁。 - -##### `:class_name` - -如果另一个模型无法从关联的名字获取,可以使用 `:class_name` 选项指定模型名。例如,如果订单属于顾客,但表示顾客的模型是 `Patron`,就可以这样声明关联: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, class_name: "Patron" -end -``` - -##### `:counter_cache` - -`:counter_cache` 选项可以提高统计所属对象数量操作的效率。假如如下的模型: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer -end -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -这样声明关联后,如果想知道 `@customer.orders.size` 的结果,就要在数据库中执行 `COUNT(*)` 查询。如果不想执行这个查询,可以在声明 `belongs_to` 关联的模型中加入计数缓存功能: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, counter_cache: true -end -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -这样声明关联后,Rails 会及时更新缓存,调用 `size` 方法时返回缓存中的值。 - -虽然 `:counter_cache` 选项在声明 `belongs_to` 关联的模型中设置,但实际使用的字段要添加到关联的模型中。针对上面的例子,要把 `orders_count` 字段加入 `Customer` 模型。这个字段的默认名也是可以设置的: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, counter_cache: :count_of_orders -end -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -计数缓存字段通过 `attr_readonly` 方法加入关联模型的只读属性列表中。 - -##### `:dependent` - -`:dependent` 选项的值有两个: - -* `:destroy`:销毁对象时,也会在关联对象上调用 `destroy` 方法; -* `:delete`:销毁对象时,关联的对象不会调用 `destroy` 方法,而是直接从数据库中删除; - -WARNING: 在 `belongs_to` 关联和 `has_many` 关联配对时,不应该设置这个选项,否则会导致数据库中出现孤儿记录。 - -##### `:foreign_key` - -按照约定,用来存储外键的字段名是关联名后加 `_id`。`:foreign_key` 选项可以设置要使用的外键名: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, class_name: "Patron", - foreign_key: "patron_id" -end -``` - -TIP: 不管怎样,Rails 都不会自动创建外键字段,你要自己在迁移中创建。 - -##### `:inverse_of` - -`:inverse_of` 选项指定 `belongs_to` 关联另一端的 `has_many` 和 `has_one` 关联名。不能和 `:polymorphic` 选项一起使用。 - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, inverse_of: :customer -end - -class Order < ActiveRecord::Base - belongs_to :customer, inverse_of: :orders -end -``` - -##### `:polymorphic` - -`:polymorphic` 选项为 `true` 时表明这是个多态关联。[前文](#polymorphic-associations)已经详细介绍过多态关联。 - -##### `:touch` - -如果把 `:touch` 选项设为 `true`,保存或销毁对象时,关联对象的 `updated_at` 或 `updated_on` 字段会自动设为当前时间戳。 - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, touch: true -end - -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -在这个例子中,保存或销毁订单后,会更新关联的顾客中的时间戳。还可指定要更新哪个字段的时间戳: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, touch: :orders_updated_at -end -``` - -##### `:validate` - -如果把 `:validate` 选项设为 `true`,保存对象时,会同时验证关联对象。该选项的默认值是 `false`,保存对象时不验证关联对象。 - -#### `belongs_to` 的作用域 - -有时可能需要定制 `belongs_to` 关联使用的查询方式,定制的查询可在作用域代码块中指定。例如: - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, -> { where active: true }, - dependent: :destroy -end -``` - -在作用域代码块中可以使用任何一个标准的[查询方法](active_record_querying.html)。下面分别介绍这几个方法: - -* `where` -* `includes` -* `readonly` -* `select` - -##### `where` - -`where` 方法指定关联对象必须满足的条件。 - -```ruby -class Order < ActiveRecord::Base - belongs_to :customer, -> { where active: true } -end -``` - -##### `includes` - -`includes` 方法指定使用关联时要按需加载的间接关联。例如,有如下的模型: - -```ruby -class LineItem < ActiveRecord::Base - belongs_to :order -end - -class Order < ActiveRecord::Base - belongs_to :customer - has_many :line_items -end - -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -如果经常要直接从商品上获取顾客对象(`@line_item.order.customer`),就可以把顾客引入商品和订单的关联中: - -```ruby -class LineItem < ActiveRecord::Base - belongs_to :order, -> { includes :customer } -end - -class Order < ActiveRecord::Base - belongs_to :customer - has_many :line_items -end - -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -NOTE: 直接关联没必要使用 `includes`。如果 `Order belongs_to :customer`,那么顾客会自动按需加载。 - -##### `readonly` - -如果使用 `readonly`,通过关联获取的对象就是只读的。 - -##### `select` - -`select` 方法会覆盖获取关联对象使用的 SQL `SELECT` 子句。默认情况下,Rails 会读取所有字段。 - -TIP: 如果在 `belongs_to` 关联中使用 `select` 方法,应该同时设置 `:foreign_key` 选项,确保返回正确的结果。 - -#### 检查关联的对象是否存在 - -检查关联的对象是否存在可以使用 `association.nil?` 方法: - -```ruby -if @order.customer.nil? - @msg = "No customer found for this order" -end -``` - -#### 什么时候保存对象 - -把对象赋值给 `belongs_to` 关联不会自动保存对象,也不会保存关联的对象。 - -### `has_one` 关联详解 - -`has_one` 关联建立两个模型之间的一对一关系。用数据库的行话说,这种关联的意思是外键在另一个类中。如果外键在这个类中,应该使用 `belongs_to` 关联。 - -#### `has_one` 关联添加的方法 - -声明 `has_one` 关联后,声明所在的类自动获得了五个关联相关的方法: - -* `association(force_reload = false)` -* `association=(associate)` -* `build_association(attributes = {})` -* `create_association(attributes = {})` -* `create_association!(attributes = {})` - -这五个方法中的 `association` 要替换成传入 `has_one` 方法的第一个参数。例如,如下的声明: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account -end -``` - -每个 `Supplier` 模型实例都获得了这些方法: - -```ruby -account -account= -build_account -create_account -create_account! -``` - -NOTE: 在 `has_one` 和 `belongs_to` 关联中,必须使用 `build_*` 方法构建关联对象。`association.build` 方法是在 `has_many` 和 `has_and_belongs_to_many` 关联中使用的。创建关联对象要使用 `create_*` 方法。 - -##### `association(force_reload = false)` - -如果关联的对象存在,`association` 方法会返回关联对象。如果找不到关联对象,则返回 `nil`。 - -```ruby -@account = @supplier.account -``` - -如果关联对象之前已经取回,会返回缓存版本。如果不想使用缓存版本,强制重新从数据库中读取,可以把 `force_reload` 参数设为 `true`。 - -##### `association=(associate)` - -`association=` 方法用来赋值关联的对象。这个方法的底层操作是,从关联对象上读取主键,然后把值赋给该主键对应的关联对象。 - -```ruby -@supplier.account = @account -``` - -##### `build_association(attributes = {})` - -`build_association` 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,和对象连接的外键会自动设置,但关联对象不会存入数据库。 - -```ruby -@account = @supplier.build_account(terms: "Net 30") -``` - -##### `create_association(attributes = {})` - -`create_association` 方法返回该关联类型的一个新对象。这个对象使用传入的属性初始化,和对象连接的外键会自动设置,只要能通过所有数据验证,就会把关联对象存入数据库。 - -```ruby -@account = @supplier.create_account(terms: "Net 30") -``` - -##### `create_association!(attributes = {})` - -和 `create_association` 方法作用相同,但是如果记录不合法,会抛出 `ActiveRecord::RecordInvalid` 异常。 - -#### `has_one` 方法的选项 - -Rails 的默认设置足够智能,能满足常见需求。但有时还是需要定制 `has_one` 关联的行为。定制的方法很简单,声明关联时传入选项即可。例如,下面的关联使用了两个选项: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, class_name: "Billing", dependent: :nullify -end -``` - -`has_one` 关联支持以下选项: - -* `:as` -* `:autosave` -* `:class_name` -* `:dependent` -* `:foreign_key` -* `:inverse_of` -* `:primary_key` -* `:source` -* `:source_type` -* `:through` -* `:validate` - -##### `:as` - -`:as` 选项表明这是多态关联。[前文](#polymorphic-associations)已经详细介绍过多态关联。 - -##### `:autosave` - -如果把 `:autosave` 选项设为 `true`,保存父对象时,会自动保存所有子对象,并把标记为析构的子对象销毁。 - -##### `:class_name` - -如果另一个模型无法从关联的名字获取,可以使用 `:class_name` 选项指定模型名。例如,供应商有一个账户,但表示账户的模型是 `Billing`,就可以这样声明关联: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, class_name: "Billing" -end -``` - -##### `:dependent` - -设置销毁拥有者时要怎么处理关联对象: - -* `:destroy`:也销毁关联对象; -* `:delete`:直接把关联对象对数据库中删除,因此不会执行回调; -* `:nullify`:把外键设为 `NULL`,不会执行回调; -* `:restrict_with_exception`:有关联的对象时抛出异常; -* `:restrict_with_error`:有关联的对象时,向拥有者添加一个错误; - -如果在数据库层设置了 `NOT NULL` 约束,就不能使用 `:nullify` 选项。如果 `:dependent` 选项没有销毁关联,就无法修改关联对象,因为关联对象的外键设置为不接受 `NULL`。 - -##### `:foreign_key` - -按照约定,在另一个模型中用来存储外键的字段名是模型名后加 `_id`。`:foreign_key` 选项可以设置要使用的外键名: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, foreign_key: "supp_id" -end -``` - -TIP: 不管怎样,Rails 都不会自动创建外键字段,你要自己在迁移中创建。 - -##### `:inverse_of` - -`:inverse_of` 选项指定 `has_one` 关联另一端的 `belongs_to` 关联名。不能和 `:through` 或 `:as` 选项一起使用。 - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, inverse_of: :supplier -end - -class Account < ActiveRecord::Base - belongs_to :supplier, inverse_of: :account -end -``` - -##### `:primary_key` - -按照约定,用来存储该模型主键的字段名 `id`。`:primary_key` 选项可以设置要使用的主键名。 - -##### `:source` - -`:source` 选项指定 `has_one :through` 关联的关联源名字。 - -##### `:source_type` - -`:source_type` 选项指定 `has_one :through` 关联中用来处理多态关联的关联源类型。 - -##### `:through` - -`:through` 选项指定用来执行查询的连接模型。[前文](#the-has-one-through-association)详细介绍过 `has_one :through` 关联。 - -##### `:validate` - -如果把 `:validate` 选项设为 `true`,保存对象时,会同时验证关联对象。该选项的默认值是 `false`,保存对象时不验证关联对象。 - -#### `has_one` 的作用域 - -有时可能需要定制 `has_one` 关联使用的查询方式,定制的查询可在作用域代码块中指定。例如: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, -> { where active: true } -end -``` - -在作用域代码块中可以使用任何一个标准的[查询方法](active_record_querying.html)。下面分别介绍这几个方法: - -* `where` -* `includes` -* `readonly` -* `select` - -##### `where` - -`where` 方法指定关联对象必须满足的条件。 - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, -> { where "confirmed = 1" } -end -``` - -##### `includes` - -`includes` 方法指定使用关联时要按需加载的间接关联。例如,有如下的模型: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account -end - -class Account < ActiveRecord::Base - belongs_to :supplier - belongs_to :representative -end - -class Representative < ActiveRecord::Base - has_many :accounts -end -``` - -如果经常要直接获取供应商代表(`@supplier.account.representative`),就可以把代表引入供应商和账户的关联中: - -```ruby -class Supplier < ActiveRecord::Base - has_one :account, -> { includes :representative } -end - -class Account < ActiveRecord::Base - belongs_to :supplier - belongs_to :representative -end - -class Representative < ActiveRecord::Base - has_many :accounts -end -``` - -##### `readonly` - -如果使用 `readonly`,通过关联获取的对象就是只读的。 - -##### `select` - -`select` 方法会覆盖获取关联对象使用的 SQL `SELECT` 子句。默认情况下,Rails 会读取所有字段。 - -#### 检查关联的对象是否存在 - -检查关联的对象是否存在可以使用 `association.nil?` 方法: - -```ruby -if @supplier.account.nil? - @msg = "No account found for this supplier" -end -``` - -#### 什么时候保存对象 - -把对象赋值给 `has_one` 关联时,会自动保存对象(因为要更新外键)。而且所有被替换的对象也会自动保存,因为外键也变了。 - -如果无法通过验证,随便哪一次保存失败了,赋值语句就会返回 `false`,赋值操作会取消。 - -如果父对象(`has_one` 关联声明所在的模型)没保存(`new_record?` 方法返回 `true`),那么子对象也不会保存。只有保存了父对象,才会保存子对象。 - -如果赋值给 `has_one` 关联时不想保存对象,可以使用 `association.build` 方法。 - -### `has_many` 关联详解 - -`has_many` 关联建立两个模型之间的一对多关系。用数据库的行话说,这种关联的意思是外键在另一个类中,指向这个类的实例。 - -#### `has_many` 关联添加的方法 - -声明 `has_many` 关联后,声明所在的类自动获得了 16 个关联相关的方法: - -* `collection(force_reload = false)` -* `collection<<(object, ...)` -* `collection.delete(object, ...)` -* `collection.destroy(object, ...)` -* `collection=objects` -* `collection_singular_ids` -* `collection_singular_ids=ids` -* `collection.clear` -* `collection.empty?` -* `collection.size` -* `collection.find(...)` -* `collection.where(...)` -* `collection.exists?(...)` -* `collection.build(attributes = {}, ...)` -* `collection.create(attributes = {})` -* `collection.create!(attributes = {})` - -这些个方法中的 `collection` 要替换成传入 `has_many` 方法的第一个参数。`collection_singular` 要替换成第一个参数的单数形式。例如,如下的声明: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders -end -``` - -每个 `Customer` 模型实例都获得了这些方法: - -```ruby -orders(force_reload = false) -orders<<(object, ...) -orders.delete(object, ...) -orders.destroy(object, ...) -orders=objects -order_ids -order_ids=ids -orders.clear -orders.empty? -orders.size -orders.find(...) -orders.where(...) -orders.exists?(...) -orders.build(attributes = {}, ...) -orders.create(attributes = {}) -orders.create!(attributes = {}) -``` - -##### `collection(force_reload = false)` - -`collection` 方法返回一个数组,包含所有关联的对象。如果没有关联的对象,则返回空数组。 - -```ruby -@orders = @customer.orders -``` - -##### `collection<<(object, ...)` - -`collection<<` 方法向关联对象数组中添加一个或多个对象,并把各所加对象的外键设为调用此方法的模型的主键。 - -```ruby -@customer.orders << @order1 -``` - -##### `collection.delete(object, ...)` - -`collection.delete` 方法从关联对象数组中删除一个或多个对象,并把删除的对象外键设为 `NULL`。 - -```ruby -@customer.orders.delete(@order1) -``` - -WARNING: 如果关联设置了 `dependent: :destroy`,还会销毁关联对象;如果关联设置了 `dependent: :delete_all`,还会删除关联对象。 - -##### `collection.destroy(object, ...)` - -`collection.destroy` 方法在关联对象上调用 `destroy` 方法,从关联对象数组中删除一个或多个对象。 - -```ruby -@customer.orders.destroy(@order1) -``` - -WARNING: 对象会从数据库中删除,忽略 `:dependent` 选项。 - -##### `collection=objects` - -`collection=` 让关联对象数组只包含指定的对象,根据需求会添加或删除对象。 - -##### `collection_singular_ids` - -`collection_singular_ids` 返回一个数组,包含关联对象数组中各对象的 ID。 - -```ruby -@order_ids = @customer.order_ids -``` - -##### `collection_singular_ids=ids` - -`collection_singular_ids=` 方法让数组中只包含指定的主键,根据需要增删 ID。 - -##### `collection.clear` - -`collection.clear` 方法删除数组中的所有对象。如果关联中指定了 `dependent: :destroy` 选项,会销毁关联对象;如果关联中指定了 `dependent: :delete_all` 选项,会直接从数据库中删除对象,然后再把外键设为 `NULL`。 - -##### `collection.empty?` - -如果关联数组中没有关联对象,`collection.empty?` 方法返回 `true`。 - -```erb -<% if @customer.orders.empty? %> - No Orders Found -<% end %> -``` - -##### `collection.size` - -`collection.size` 返回关联对象数组中的对象数量。 - -```ruby -@order_count = @customer.orders.size -``` - -##### `collection.find(...)` - -`collection.find` 方法在关联对象数组中查找对象,句法和可用选项跟 `ActiveRecord::Base.find` 方法一样。 - -```ruby -@open_orders = @customer.orders.find(1) -``` - -##### `collection.where(...)` - -`collection.where` 方法根据指定的条件在关联对象数组中查找对象,但会惰性加载对象,用到对象时才会执行查询。 - -```ruby -@open_orders = @customer.orders.where(open: true) # No query yet -@open_order = @open_orders.first # Now the database will be queried -``` - -##### `collection.exists?(...)` - -`collection.exists?` 方法根据指定的条件检查关联对象数组中是否有符合条件的对象,句法和可用选项跟 `ActiveRecord::Base.exists?` 方法一样。 - -##### `collection.build(attributes = {}, ...)` - -`collection.build` 方法返回一个或多个此种关联类型的新对象。这些对象会使用传入的属性初始化,还会创建对应的外键,但不会保存关联对象。 - -```ruby -@order = @customer.orders.build(order_date: Time.now, - order_number: "A12345") -``` - -##### `collection.create(attributes = {})` - -`collection.create` 方法返回一个此种关联类型的新对象。这个对象会使用传入的属性初始化,还会创建对应的外键,只要能通过所有数据验证,就会保存关联对象。 - -```ruby -@order = @customer.orders.create(order_date: Time.now, - order_number: "A12345") -``` - -##### `collection.create!(attributes = {})` - -作用和 `collection.create` 相同,但如果记录不合法会抛出 `ActiveRecord::RecordInvalid` 异常。 - -#### `has_many` 方法的选项 - -Rails 的默认设置足够智能,能满足常见需求。但有时还是需要定制 `has_many` 关联的行为。定制的方法很简单,声明关联时传入选项即可。例如,下面的关联使用了两个选项: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, dependent: :delete_all, validate: :false -end -``` - -`has_many` 关联支持以下选项: - -* `:as` -* `:autosave` -* `:class_name` -* `:dependent` -* `:foreign_key` -* `:inverse_of` -* `:primary_key` -* `:source` -* `:source_type` -* `:through` -* `:validate` - -##### `:as` - -`:as` 选项表明这是多态关联。[前文](#polymorphic-associations)已经详细介绍过多态关联。 - -##### `:autosave` - -如果把 `:autosave` 选项设为 `true`,保存父对象时,会自动保存所有子对象,并把标记为析构的子对象销毁。 - -##### `:class_name` - -如果另一个模型无法从关联的名字获取,可以使用 `:class_name` 选项指定模型名。例如,顾客有多个订单,但表示订单的模型是 `Transaction`,就可以这样声明关联: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, class_name: "Transaction" -end -``` - -##### `:dependent` - -设置销毁拥有者时要怎么处理关联对象: - -* `:destroy`:也销毁所有关联的对象; -* `:delete_all`:直接把所有关联对象对数据库中删除,因此不会执行回调; -* `:nullify`:把外键设为 `NULL`,不会执行回调; -* `:restrict_with_exception`:有关联的对象时抛出异常; -* `:restrict_with_error`:有关联的对象时,向拥有者添加一个错误; - -NOTE: 如果声明关联时指定了 `:through` 选项,会忽略这个选项。 - -##### `:foreign_key` - -按照约定,另一个模型中用来存储外键的字段名是模型名后加 `_id`。`:foreign_key` 选项可以设置要使用的外键名: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, foreign_key: "cust_id" -end -``` - -TIP: 不管怎样,Rails 都不会自动创建外键字段,你要自己在迁移中创建。 - -##### `:inverse_of` - -`:inverse_of` 选项指定 `has_many` 关联另一端的 `belongs_to` 关联名。不能和 `:through` 或 `:as` 选项一起使用。 - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, inverse_of: :customer -end - -class Order < ActiveRecord::Base - belongs_to :customer, inverse_of: :orders -end -``` - -##### `:primary_key` - -按照约定,用来存储该模型主键的字段名 `id`。`:primary_key` 选项可以设置要使用的主键名。 - -假设 `users` 表的主键是 `id`,但还有一个 `guid` 字段。根据要求,`todos` 表中应该使用 `guid` 字段,而不是 `id` 字段。这种需求可以这么实现: - -```ruby -class User < ActiveRecord::Base - has_many :todos, primary_key: :guid -end -``` - -如果执行 `@user.todos.create` 创建新的待办事项,那么 `@todo.user_id` 就是 `guid` 字段中的值。 - -##### `:source` - -`:source` 选项指定 `has_many :through` 关联的关联源名字。只有无法从关联名种解出关联源的名字时才需要设置这个选项。 - -##### `:source_type` - -`:source_type` 选项指定 `has_many :through` 关联中用来处理多态关联的关联源类型。 - -##### `:through` - -`:through` 选项指定用来执行查询的连接模型。`has_many :through` 关联是实现多对多关联的一种方式,[前文](#the-has-many-through-association)已经介绍过。 - -##### `:validate` - -如果把 `:validate` 选项设为 `false`,保存对象时,不会验证关联对象。该选项的默认值是 `true`,保存对象验证关联的对象。 - -#### `has_many` 的作用域 - -有时可能需要定制 `has_many` 关联使用的查询方式,定制的查询可在作用域代码块中指定。例如: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, -> { where processed: true } -end -``` - -在作用域代码块中可以使用任何一个标准的[查询方法](active_record_querying.html)。下面分别介绍这几个方法: - -* `where` -* `extending` -* `group` -* `includes` -* `limit` -* `offset` -* `order` -* `readonly` -* `select` -* `uniq` - -##### `where` - -`where` 方法指定关联对象必须满足的条件。 - -```ruby -class Customer < ActiveRecord::Base - has_many :confirmed_orders, -> { where "confirmed = 1" }, - class_name: "Order" -end -``` - -条件还可以使用 Hash 的形式指定: - -```ruby -class Customer < ActiveRecord::Base - has_many :confirmed_orders, -> { where confirmed: true }, - class_name: "Order" -end -``` - -如果 `where` 使用 Hash 形式,通过这个关联创建的记录会自动使用 Hash 中的作用域。针对上面的例子,使用 `@customer.confirmed_orders.create` 或 `@customer.confirmed_orders.build` 创建订单时,会自动把 `confirmed` 字段的值设为 `true`。 - -##### `extending` - -`extending` 方法指定一个模块名,用来扩展关联代理。[后文](#association-extensions)会详细介绍关联扩展。 - -##### `group` - -`group` 方法指定一个属性名,用在 SQL `GROUP BY` 子句中,分组查询结果。 - -```ruby -class Customer < ActiveRecord::Base - has_many :line_items, -> { group 'orders.id' }, - through: :orders -end -``` - -##### `includes` - -`includes` 方法指定使用关联时要按需加载的间接关联。例如,有如下的模型: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders -end - -class Order < ActiveRecord::Base - belongs_to :customer - has_many :line_items -end - -class LineItem < ActiveRecord::Base - belongs_to :order -end -``` - -如果经常要直接获取顾客购买的商品(`@customer.orders.line_items`),就可以把商品引入顾客和订单的关联中: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, -> { includes :line_items } -end - -class Order < ActiveRecord::Base - belongs_to :customer - has_many :line_items -end - -class LineItem < ActiveRecord::Base - belongs_to :order -end -``` - -##### `limit` - -`limit` 方法限制通过关联获取的对象数量。 - -```ruby -class Customer < ActiveRecord::Base - has_many :recent_orders, - -> { order('order_date desc').limit(100) }, - class_name: "Order", -end -``` - -##### `offset` - -`offset` 方法指定通过关联获取对象时的偏移量。例如,`-> { offset(11) }` 会跳过前 11 个记录。 - -##### `order` - -`order` 方法指定获取关联对象时使用的排序方式,用于 SQL `ORDER BY` 子句。 - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, -> { order "date_confirmed DESC" } -end -``` - -##### `readonly` - -如果使用 `readonly`,通过关联获取的对象就是只读的。 - -##### `select` - -`select` 方法用来覆盖获取关联对象数据的 SQL `SELECT` 子句。默认情况下,Rails 会读取所有字段。 - -WARNING: 如果设置了 `select`,记得要包含主键和关联模型的外键。否则,Rails 会抛出异常。 - -##### `distinct` - -使用 `distinct` 方法可以确保集合中没有重复的对象,和 `:through` 选项一起使用最有用。 - -```ruby -class Person < ActiveRecord::Base - has_many :readings - has_many :posts, through: :readings -end - -person = Person.create(name: 'John') -post = Post.create(name: 'a1') -person.posts << post -person.posts << post -person.posts.inspect # => [#, #] -Reading.all.inspect # => [#, #] -``` - -在上面的代码中,读者读了两篇文章,即使是同一篇文章,`person.posts` 也会返回两个对象。 - -下面我们加入 `distinct` 方法: - -```ruby -class Person - has_many :readings - has_many :posts, -> { distinct }, through: :readings -end - -person = Person.create(name: 'Honda') -post = Post.create(name: 'a1') -person.posts << post -person.posts << post -person.posts.inspect # => [#] -Reading.all.inspect # => [#, #] -``` - -在这段代码中,读者还是读了两篇文章,但 `person.posts` 只返回一个对象,因为加载的集合已经去除了重复元素。 - -如果要确保只把不重复的记录写入关联模型的数据表(这样就不会从数据库中获取重复记录了),需要在数据表上添加唯一性索引。例如,数据表名为 `person_posts`,我们要保证其中所有的文章都没重复,可以在迁移中加入以下代码: - -```ruby -add_index :person_posts, :post, unique: true -``` - -注意,使用 `include?` 等方法检查唯一性可能导致条件竞争。不要使用 `include?` 确保关联的唯一性。还是以前面的文章模型为例,下面的代码会导致条件竞争,因为多个用户可能会同时执行这一操作: - -```ruby -person.posts << post unless person.posts.include?(post) -``` - -#### 什么时候保存对象 - -把对象赋值给 `has_many` 关联时,会自动保存对象(因为要更新外键)。如果一次赋值多个对象,所有对象都会自动保存。 - -如果无法通过验证,随便哪一次保存失败了,赋值语句就会返回 `false`,赋值操作会取消。 - -如果父对象(`has_many` 关联声明所在的模型)没保存(`new_record?` 方法返回 `true`),那么子对象也不会保存。只有保存了父对象,才会保存子对象。 - -如果赋值给 `has_many` 关联时不想保存对象,可以使用 `collection.build` 方法。 - -### `has_and_belongs_to_many` 关联详解 - -`has_and_belongs_to_many` 关联建立两个模型之间的多对多关系。用数据库的行话说,这种关联的意思是有个连接数据表包含指向这两个类的外键。 - -#### `has_and_belongs_to_many` 关联添加的方法 - -声明 `has_and_belongs_to_many` 关联后,声明所在的类自动获得了 16 个关联相关的方法: - -* `collection(force_reload = false)` -* `collection<<(object, ...)` -* `collection.delete(object, ...)` -* `collection.destroy(object, ...)` -* `collection=objects` -* `collection_singular_ids` -* `collection_singular_ids=ids` -* `collection.clear` -* `collection.empty?` -* `collection.size` -* `collection.find(...)` -* `collection.where(...)` -* `collection.exists?(...)` -* `collection.build(attributes = {})` -* `collection.create(attributes = {})` -* `collection.create!(attributes = {})` - -这些个方法中的 `collection` 要替换成传入 `has_and_belongs_to_many` 方法的第一个参数。`collection_singular` 要替换成第一个参数的单数形式。例如,如下的声明: - -```ruby -class Part < ActiveRecord::Base - has_and_belongs_to_many :assemblies -end -``` - -每个 `Part` 模型实例都获得了这些方法: - -```ruby -assemblies(force_reload = false) -assemblies<<(object, ...) -assemblies.delete(object, ...) -assemblies.destroy(object, ...) -assemblies=objects -assembly_ids -assembly_ids=ids -assemblies.clear -assemblies.empty? -assemblies.size -assemblies.find(...) -assemblies.where(...) -assemblies.exists?(...) -assemblies.build(attributes = {}, ...) -assemblies.create(attributes = {}) -assemblies.create!(attributes = {}) -``` - -##### 额外的字段方法 - -如果 `has_and_belongs_to_many` 关联使用的连接数据表中,除了两个外键之外还有其他字段,通过关联获取的记录中会包含这些字段,但是只读字段,因为 Rails 不知道如何保存对这些字段的改动。 - -WARNING: 在 `has_and_belongs_to_many` 关联的连接数据表中使用其他字段的功能已经废弃。如果在多对多关联中需要使用这么复杂的数据表,可以用 `has_many :through` 关联代替 `has_and_belongs_to_many` 关联。 - -##### `collection(force_reload = false)` - -`collection` 方法返回一个数组,包含所有关联的对象。如果没有关联的对象,则返回空数组。 - -```ruby -@assemblies = @part.assemblies -``` - -##### `collection<<(object, ...)` - -`collection<<` 方法向关联对象数组中添加一个或多个对象,并在连接数据表中创建相应的记录。 - -```ruby -@part.assemblies << @assembly1 -``` - -NOTE: 这个方法与 `collection.concat` 和 `collection.push` 是同名方法。 - -##### `collection.delete(object, ...)` - -`collection.delete` 方法从关联对象数组中删除一个或多个对象,并删除连接数据表中相应的记录。 - -```ruby -@part.assemblies.delete(@assembly1) -``` - -WARNING: 这个方法不会触发连接记录上的回调。 - -##### `collection.destroy(object, ...)` - -`collection.destroy` 方法在连接数据表中的记录上调用 `destroy` 方法,从关联对象数组中删除一个或多个对象,还会触发回调。这个方法不会销毁对象本身。 - -```ruby -@part.assemblies.destroy(@assembly1) -``` - -##### `collection=objects` - -`collection=` 让关联对象数组只包含指定的对象,根据需求会添加或删除对象。 - -##### `collection_singular_ids` - -`collection_singular_ids` 返回一个数组,包含关联对象数组中各对象的 ID。 - -```ruby -@assembly_ids = @part.assembly_ids -``` - -##### `collection_singular_ids=ids` - -`collection_singular_ids=` 方法让数组中只包含指定的主键,根据需要增删 ID。 - -##### `collection.clear` - -`collection.clear` 方法删除数组中的所有对象,并把连接数据表中的相应记录删除。这个方法不会销毁关联对象。 - -##### `collection.empty?` - -如果关联数组中没有关联对象,`collection.empty?` 方法返回 `true`。 - -```ruby -<% if @part.assemblies.empty? %> - This part is not used in any assemblies -<% end %> -``` - -##### `collection.size` - -`collection.size` 返回关联对象数组中的对象数量。 - -```ruby -@assembly_count = @part.assemblies.size -``` - -##### `collection.find(...)` - -`collection.find` 方法在关联对象数组中查找对象,句法和可用选项跟 `ActiveRecord::Base.find` 方法一样。同时还限制对象必须在集合中。 - -```ruby -@assembly = @part.assemblies.find(1) -``` - -##### `collection.where(...)` - -`collection.where` 方法根据指定的条件在关联对象数组中查找对象,但会惰性加载对象,用到对象时才会执行查询。同时还限制对象必须在集合中。 - -```ruby -@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago) -``` - -##### `collection.exists?(...)` - -`collection.exists?` 方法根据指定的条件检查关联对象数组中是否有符合条件的对象,句法和可用选项跟 `ActiveRecord::Base.exists?` 方法一样。 - -##### `collection.build(attributes = {})` - -`collection.build` 方法返回一个此种关联类型的新对象。这个对象会使用传入的属性初始化,还会在连接数据表中创建对应的记录,但不会保存关联对象。 - -```ruby -@assembly = @part.assemblies.build({assembly_name: "Transmission housing"}) -``` - -##### `collection.create(attributes = {})` - -`collection.create` 方法返回一个此种关联类型的新对象。这个对象会使用传入的属性初始化,还会在连接数据表中创建对应的记录,只要能通过所有数据验证,就会保存关联对象。 - -```ruby -@assembly = @part.assemblies.create({assembly_name: "Transmission housing"}) -``` - -##### `collection.create!(attributes = {})` - -作用和 `collection.create` 相同,但如果记录不合法会抛出 `ActiveRecord::RecordInvalid` 异常。 - -#### `has_and_belongs_to_many` 方法的选项 - -Rails 的默认设置足够智能,能满足常见需求。但有时还是需要定制 `has_and_belongs_to_many` 关联的行为。定制的方法很简单,声明关联时传入选项即可。例如,下面的关联使用了两个选项: - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, autosave: true, - readonly: true -end -``` - -`has_and_belongs_to_many` 关联支持以下选项: - -* `:association_foreign_key` -* `:autosave` -* `:class_name` -* `:foreign_key` -* `:join_table` -* `:validate` -* `:readonly` - -##### `:association_foreign_key` - -按照约定,在连接数据表中用来指向另一个模型的外键名是模型名后加 `_id`。`:association_foreign_key` 选项可以设置要使用的外键名: - -TIP: `:foreign_key` 和 `:association_foreign_key` 这两个选项在设置多对多自连接时很有用。 - -```ruby -class User < ActiveRecord::Base - has_and_belongs_to_many :friends, - class_name: "User", - foreign_key: "this_user_id", - association_foreign_key: "other_user_id" -end -``` - -##### `:autosave` - -如果把 `:autosave` 选项设为 `true`,保存父对象时,会自动保存所有子对象,并把标记为析构的子对象销毁。 - -##### `:class_name` - -如果另一个模型无法从关联的名字获取,可以使用 `:class_name` 选项指定模型名。例如,一个部件由多个装配件组成,但表示装配件的模型是 `Gadget`,就可以这样声明关联: - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, class_name: "Gadget" -end -``` - -##### `:foreign_key` - -按照约定,在连接数据表中用来指向模型的外键名是模型名后加 `_id`。`:foreign_key` 选项可以设置要使用的外键名: - -```ruby -class User < ActiveRecord::Base - has_and_belongs_to_many :friends, - class_name: "User", - foreign_key: "this_user_id", - association_foreign_key: "other_user_id" -end -``` - -##### `:join_table` - -如果默认按照字典顺序生成的默认名不能满足要求,可以使用 `:join_table` 选项指定。 - -##### `:validate` - -如果把 `:validate` 选项设为 `false`,保存对象时,不会验证关联对象。该选项的默认值是 `true`,保存对象验证关联的对象。 - -#### `has_and_belongs_to_many` 的作用域 - -有时可能需要定制 `has_and_belongs_to_many` 关联使用的查询方式,定制的查询可在作用域代码块中指定。例如: - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, -> { where active: true } -end -``` - -在作用域代码块中可以使用任何一个标准的[查询方法](active_record_querying.html)。下面分别介绍这几个方法: - -* `where` -* `extending` -* `group` -* `includes` -* `limit` -* `offset` -* `order` -* `readonly` -* `select` -* `uniq` - -##### `where` - -`where` 方法指定关联对象必须满足的条件。 - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, - -> { where "factory = 'Seattle'" } -end -``` - -条件还可以使用 Hash 的形式指定: - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, - -> { where factory: 'Seattle' } -end -``` - -如果 `where` 使用 Hash 形式,通过这个关联创建的记录会自动使用 Hash 中的作用域。针对上面的例子,使用 `@parts.assemblies.create` 或 `@parts.assemblies.build` 创建订单时,会自动把 `factory` 字段的值设为 `"Seattle"`。 - -##### `extending` - -`extending` 方法指定一个模块名,用来扩展关联代理。[后文](#association-extensions)会详细介绍关联扩展。 - -##### `group` - -`group` 方法指定一个属性名,用在 SQL `GROUP BY` 子句中,分组查询结果。 - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, -> { group "factory" } -end -``` - -##### `includes` - -`includes` 方法指定使用关联时要按需加载的间接关联。 - -##### `limit` - -`limit` 方法限制通过关联获取的对象数量。 - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, - -> { order("created_at DESC").limit(50) } -end -``` - -##### `offset` - -`offset` 方法指定通过关联获取对象时的偏移量。例如,`-> { offset(11) }` 会跳过前 11 个记录。 - -##### `order` - -`order` 方法指定获取关联对象时使用的排序方式,用于 SQL `ORDER BY` 子句。 - -```ruby -class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, - -> { order "assembly_name ASC" } -end -``` - -##### `readonly` - -如果使用 `readonly`,通过关联获取的对象就是只读的。 - -##### `select` - -`select` 方法用来覆盖获取关联对象数据的 SQL `SELECT` 子句。默认情况下,Rails 会读取所有字段。 - -##### `uniq` - -`uniq` 方法用来删除集合中重复的对象。 - -#### 什么时候保存对象 - -把对象赋值给 `has_and_belongs_to_many` 关联时,会自动保存对象(因为要更新外键)。如果一次赋值多个对象,所有对象都会自动保存。 - -如果无法通过验证,随便哪一次保存失败了,赋值语句就会返回 `false`,赋值操作会取消。 - -如果父对象(`has_and_belongs_to_many` 关联声明所在的模型)没保存(`new_record?` 方法返回 `true`),那么子对象也不会保存。只有保存了父对象,才会保存子对象。 - -如果赋值给 `has_and_belongs_to_many` 关联时不想保存对象,可以使用 `collection.build` 方法。 - -### 关联回调 - -普通回调会介入 Active Record 对象的生命周期,在很多时刻处理对象。例如,可以使用 `:before_save` 回调在保存对象之前处理对象。 - -关联回调和普通回调差不多,只不过由集合生命周期中的事件触发。关联回调有四种: - -* `before_add` -* `after_add` -* `before_remove` -* `after_remove` - -关联回调在声明关联时定义。例如: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, before_add: :check_credit_limit - - def check_credit_limit(order) - ... - end -end -``` - -Rails 会把添加或删除的对象传入回调。 - -同一事件可触发多个回调,多个回调使用数组指定: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders, - before_add: [:check_credit_limit, :calculate_shipping_charges] - - def check_credit_limit(order) - ... - end - - def calculate_shipping_charges(order) - ... - end -end -``` - -如果 `before_add` 回调抛出异常,不会把对象加入集合。类似地,如果 `before_remove` 抛出异常,对象不会从集合中删除。 - -### 关联扩展 - -Rails 基于关联代理对象自动创建的功能是死的,但是可以通过匿名模块、新的查询方法、创建对象的方法等进行扩展。例如: - -```ruby -class Customer < ActiveRecord::Base - has_many :orders do - def find_by_order_prefix(order_number) - find_by(region_id: order_number[0..2]) - end - end -end -``` - -如果扩展要在多个关联中使用,可以将其写入具名扩展模块。例如: - -```ruby -module FindRecentExtension - def find_recent - where("created_at > ?", 5.days.ago) - end -end - -class Customer < ActiveRecord::Base - has_many :orders, -> { extending FindRecentExtension } -end - -class Supplier < ActiveRecord::Base - has_many :deliveries, -> { extending FindRecentExtension } -end -``` - -在扩展中可以使用如下 `proxy_association` 方法的三个属性获取关联代理的内部信息: - -* `proxy_association.owner`:返回关联所属的对象; -* `proxy_association.reflection`:返回描述关联的反射对象; -* `proxy_association.target`:返回 `belongs_to` 或 `has_one` 关联的关联对象,或者 `has_many` 或 `has_and_belongs_to_many` 关联的关联对象集合; diff --git a/source/zh-CN/caching_with_rails.md b/source/zh-CN/caching_with_rails.md deleted file mode 100644 index e833ead..0000000 --- a/source/zh-CN/caching_with_rails.md +++ /dev/null @@ -1,371 +0,0 @@ -Rails 缓存简介 -============= - -本文要教你如果避免频繁查询数据库,在最短的时间内把真正需要的内容返回给客户端。 - -读完本文,你将学到: - -* 页面和动作缓存(在 Rails 4 中被提取成单独的 gem); -* 片段缓存; -* 存储缓存的方法; -* Rails 对条件 GET 请求的支持; - --------------------------------------------------------------------------------- - -缓存基础 -------- - -本节介绍三种缓存技术:页面,动作和片段。Rails 默认支持片段缓存。如果想使用页面缓存和动作缓存,要在 `Gemfile` 中加入 `actionpack-page_caching` 和 `actionpack-action_caching`。 - -在开发环境中若想使用缓存,要把 `config.action_controller.perform_caching` 选项设为 `true`。这个选项一般都在各环境的设置文件(`config/environments/*.rb`)中设置,在开发环境和测试环境默认是禁用的,在生产环境中默认是开启的。 - -```ruby -config.action_controller.perform_caching = true -``` - -### 页面缓存 - -页面缓存机制允许网页服务器(Apache 或 Nginx 等)直接处理请求,不经 Rails 处理。这么做显然速度超快,但并不适用于所有情况(例如需要身份认证的页面)。服务器直接从文件系统上伺服文件,所以缓存过期是一个很棘手的问题。 - -NOTE: Rails 4 删除了对页面缓存的支持,如想使用就得安装 [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching)。最新推荐的缓存方法参见 [DHH 对键基缓存过期的介绍](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)。 - -### 动作缓存 - -如果动作上有前置过滤器就不能使用页面缓存,例如需要身份认证的页面,这时需要使用动作缓存。动作缓存和页面缓存的工作方式差不多,但请求还是会经由 Rails 处理,所以在伺服缓存之前会执行前置过滤器。使用动作缓存可以执行身份认证等限制,然后再从缓存中取出结果返回客户端。 - -NOTE: Rails 4 删除了对动作缓存的支持,如想使用就得安装 [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching)。最新推荐的缓存方法参见 [DHH 对键基缓存过期的介绍](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)。 - -### 片段缓存 - -如果能缓存整个页面或动作的内容,再伺服给客户端,这个世界就完美了。但是,动态网页程序的页面一般都由很多部分组成,使用的缓存机制也不尽相同。在动态生成的页面中,不同的内容要使用不同的缓存方式和过期日期。为此,Rails 提供了一种缓存机制叫做“片段缓存”。 - -片段缓存把视图逻辑的一部分打包放在 `cache` 块中,后续请求都会从缓存中伺服这部分内容。 - -例如,如果想实时显示网站的订单,而且不想缓存这部分内容,但想缓存显示所有可选商品的部分,就可以使用下面这段代码: - -```erb -<% Order.find_recent.each do |o| %> - <%= o.buyer.name %> bought <%= o.product.name %> -<% end %> - -<% cache do %> - All available products: - <% Product.all.each do |p| %> - <%= link_to p.name, product_url(https://github.com/p) %> - <% end %> -<% end %> -``` - -上述代码中的 `cache` 块会绑定到调用它的动作上,输出到动作缓存的所在位置。因此,如果要在动作中使用多个片段缓存,就要使用 `action_suffix` 为 `cache` 块指定前缀: - -```erb -<% cache(action: 'recent', action_suffix: 'all_products') do %> - All available products: -``` - -`expire_fragment` 方法可以把缓存设为过期,例如: - -```ruby -expire_fragment(controller: 'products', action: 'recent', action_suffix: 'all_products') -``` - -如果不想把缓存绑定到调用它的动作上,调用 `cahce` 方法时可以使用全局片段名: - -```erb -<% cache('all_available_products') do %> - All available products: -<% end %> -``` - -在 `ProductsController` 的所有动作中都可以使用片段名调用这个片段缓存,而且过期的设置方式不变: - -```ruby -expire_fragment('all_available_products') -``` - -如果不想手动设置片段缓存过期,而想每次更新商品后自动过期,可以定义一个帮助方法: - -```ruby -module ProductsHelper - def cache_key_for_products - count = Product.count - max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number) - "products/all-#{count}-#{max_updated_at}" - end -end -``` - -这个方法生成一个缓存键,用于所有商品的缓存。在视图中可以这么做: - -```erb -<% cache(cache_key_for_products) do %> - All available products: -<% end %> -``` - -如果想在满足某个条件时缓存片段,可以使用 `cache_if` 或 `cache_unless` 方法: - -```erb -<% cache_if (condition, cache_key_for_products) do %> - All available products: -<% end %> -``` - -缓存的键名还可使用 Active Record 模型: - -```erb -<% Product.all.each do |p| %> - <% cache(p) do %> - <%= link_to p.name, product_url(https://github.com/p) %> - <% end %> -<% end %> -``` - -Rails 会在模型上调用 `cache_key` 方法,返回一个字符串,例如 `products/23-20130109142513`。键名中包含模型名,ID 以及 `updated_at` 字段的时间戳。所以更新商品后会自动生成一个新片段缓存,因为键名变了。 - -上述两种缓存机制还可以结合在一起使用,这叫做“俄罗斯套娃缓存”(Russian Doll Caching): - -```erb -<% cache(cache_key_for_products) do %> - All available products: - <% Product.all.each do |p| %> - <% cache(p) do %> - <%= link_to p.name, product_url(https://github.com/p) %> - <% end %> - <% end %> -<% end %> -``` - -之所以叫“俄罗斯套娃缓存”,是因为嵌套了多个片段缓存。这种缓存的优点是,更新单个商品后,重新生成外层片段缓存时可以继续使用内层片段缓存。 - -### 底层缓存 - -有时不想缓存视图片段,只想缓存特定的值或者查询结果。Rails 中的缓存机制可以存储各种信息。 - -实现底层缓存最有效地方式是使用 `Rails.cache.fetch` 方法。这个方法既可以从缓存中读取数据,也可以把数据写入缓存。传入单个参数时,读取指定键对应的值。传入代码块时,会把代码块的计算结果存入缓存的指定键中,然后返回计算结果。 - -以下面的代码为例。程序中有个 `Product` 模型,其中定义了一个实例方法,用来查询竞争对手网站上的商品价格。这个方法的返回结果最好使用底层缓存: - -```ruby -class Product < ActiveRecord::Base - def competing_price - Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do - Competitor::API.find_price(id) - end - end -end -``` - -NOTE: 注意,在这个例子中使用了 `cache_key` 方法,所以得到的缓存键名是这种形式:`products/233-20140225082222765838000/competing_price`。`cache_key` 方法根据模型的 `id` 和 `updated_at` 属性生成键名。这是最常见的做法,因为商品更新后,缓存就失效了。一般情况下,使用底层缓存保存实例的相关信息时,都要生成缓存键。 - -### SQL 缓存 - -查询缓存是 Rails 的一个特性,把每次查询的结果缓存起来,如果在同一次请求中遇到相同的查询,直接从缓存中读取结果,不用再次查询数据库。 - -例如: - -```ruby -class ProductsController < ApplicationController - - def index - # Run a find query - @products = Product.all - - ... - - # Run the same query again - @products = Product.all - end - -end -``` - -缓存的存储方式 ------------- - -Rails 为动作缓存和片段缓存提供了不同的存储方式。 - -TIP: 页面缓存全部存储在硬盘中。 - -### 设置 - -程序默认使用的缓存存储方式可以在文件 `config/application.rb` 的 `Application` 类中或者环境设置文件(`config/environments/*.rb`)的 `Application.configure` 代码块中调用 `config.cache_store=` 方法设置。该方法的第一个参数是存储方式,后续参数都是传给对应存储方式构造器的参数。 - -```ruby -config.cache_store = :memory_store -``` - -NOTE: 在设置代码块外部可以调用 `ActionController::Base.cache_store` 方法设置存储方式。 - -缓存中的数据通过 `Rails.cache` 方法获取。 - -### ActiveSupport::Cache::Store - -这个类提供了在 Rails 中和缓存交互的基本方法。这是个抽象类,不能直接使用,应该使用针对各存储引擎的具体实现。Rails 实现了几种存储方式,介绍参见后几节。 - -和缓存交互常用的方法有:`read`,`write`,`delete`,`exist?`,`fetch`。`fetch` 方法接受一个代码块,如果缓存中有对应的数据,将其返回;否则,执行代码块,把结果写入缓存。 - -Rails 实现的所有存储方式都共用了下面几个选项。这些选项可以传给构造器,也可传给不同的方法,和缓存中的记录交互。 - -* `:namespace`:在缓存存储中创建命名空间。如果和其他程序共用同一个存储,可以使用这个选项。 - -* `:compress`:是否压缩缓存。便于在低速网络中传输大型缓存记录。 - -* `:compress_threshold`:结合 `:compress` 选项使用,设定一个阈值,低于这个值就不压缩缓存。默认为 16 KB。 - -* `:expires_in`:为缓存记录设定一个过期时间,单位为秒,过期后把记录从缓存中删除。 - -* `:race_condition_ttl`:结合 `:expires_in` 选项使用。缓存过期后,禁止多个进程同时重新生成同一个缓存记录(叫做 dog pile effect),从而避免条件竞争。这个选项设置一个秒数,在这个时间之后才能再次使用重新生成的新值。如果设置了 `:expires_in` 选项,最好也设置这个选项。 - -### ActiveSupport::Cache::MemoryStore - -这种存储方式在 Ruby 进程中把缓存保存在内存中。存储空间的大小由 `:size` 选项指定,默认为 32MB。如果超出分配的大小,系统会清理缓存,把最不常使用的记录删除。 - -```ruby -config.cache_store = :memory_store, { size: 64.megabytes } -``` - -如果运行多个 Rails 服务器进程(使用 mongrel_cluster 或 Phusion Passenger 时),进程间无法共用缓存数据。这种存储方式不适合在大型程序中使用,不过很适合只有几个服务器进程的小型、低流量网站,也可在开发环境和测试环境中使用。 - -### ActiveSupport::Cache::FileStore - -这种存储方式使用文件系统保存缓存。缓存文件的存储位置必须在初始化时指定。 - -```ruby -config.cache_store = :file_store, "/path/to/cache/directory" -``` - -使用这种存储方式,同一主机上的服务器进程之间可以共用缓存。运行在不同主机上的服务器进程之间也可以通过共享的文件系统共用缓存,但这种用法不是最好的方式,因此不推荐使用。这种存储方式适合在只用了一到两台主机的中低流量网站中使用。 - -注意,如果不定期清理,缓存会不断增多,最终会用完硬盘空间。 - -这是默认使用的缓存存储方式。 - -### ActiveSupport::Cache::MemCacheStore - -这种存储方式使用 Danga 开发的 `memcached` 服务器,为程序提供一个中心化的缓存存储。Rails 默认使用附带安装的 `dalli` gem 实现这种存储方式。这是目前在生产环境中使用最广泛的缓存存储方式,可以提供单个缓存存储,或者共享的缓存集群,性能高,冗余度低。 - -初始化时要指定集群中所有 memcached 服务器的地址。如果没有指定地址,默认运行在本地主机的默认端口上,这对大型网站来说不是个好主意。 - -在这种缓存存储中使用 `write` 和 `fetch` 方法还可指定两个额外的选项,充分利用 memcached 的特有功能。指定 `:raw` 选项可以直接把没有序列化的数据传给 memcached 服务器。在这种类型的数据上可以使用 memcached 的原生操作,例如 `increment` 和 `decrement`。如果不想让 memcached 覆盖已经存在的记录,可以指定 `:unless_exist` 选项。 - -```ruby -config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com" -``` - -### ActiveSupport::Cache::EhcacheStore - -如果在 JRuby 平台上运行程序,可以使用 Terracotta 开发的 Ehcache 存储缓存。Ehcache 是使用 Java 开发的开源缓存存储,同时也提供企业版,增强了稳定性、操作便利性,以及商用支持。使用这种存储方式要先安装 `jruby-ehcache-rails3` gem(1.1.0 及以上版本)。 - -```ruby -config.cache_store = :ehcache_store -``` - -初始化时,可以使用 `:ehcache_config` 选项指定 Ehcache 设置文件的位置(默认为 Rails 程序根目录中的 `ehcache.xml`),还可使用 `:cache_name` 选项定制缓存名(默认为 `rails_cache`)。 - -使用 `write` 方法时,除了可以使用通用的 `:expires_in` 选项之外,还可指定 `:unless_exist` 选项,让 Ehcache 使用 `putIfAbsent` 方法代替 `put` 方法,不覆盖已经存在的记录。除此之外,`write` 方法还可接受 [Ehcache Element 类](http://ehcache.org/apidocs/net/sf/ehcache/Element.html)开放的所有属性,包括: - -| 属性 | 参数类型 | 说明 | -| --------------------------- | ------------------- | ----------------------------------------------------------- | -| elementEvictionData | ElementEvictionData | 设置元素的 eviction 数据实例 | -| eternal | boolean | 设置元素是否为 eternal | -| timeToIdle, tti | int | 设置空闲时间 | -| timeToLive, ttl, expires_in | int | 设置在线时间 | -| version | long | 设置 ElementAttributes 对象的 `version` 属性 | - -这些选项通过 Hash 传给 `write` 方法,可以使用驼峰式或者下划线分隔形式。例如: - -```ruby -Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds) -caches_action :index, expires_in: 60.seconds, unless_exist: true -``` - -关于 Ehcache 更多的介绍,请访问 。关于如何在运行于 JRuby 平台之上的 Rails 中使用 Ehcache,请访问 。 - -### ActiveSupport::Cache::NullStore - -这种存储方式只可在开发环境和测试环境中使用,并不会存储任何数据。如果在开发过程中必须和 `Rails.cache` 交互,而且会影响到修改代码后的效果,使用这种存储方式尤其方便。使用这种存储方式时调用 `fetch` 和 `read` 方法没有实际作用。 - -```ruby -config.cache_store = :null_store -``` - -### 自建存储方式 - -要想自建缓存存储方式,可以继承 `ActiveSupport::Cache::Store` 类,并实现相应的方法。自建存储方式时,可以使用任何缓存技术。 - -使用自建的存储方式,把 `cache_store` 设为类的新实例即可。 - -```ruby -config.cache_store = MyCacheStore.new -``` - -### 缓存键 - -缓存中使用的键可以是任意对象,只要能响应 `:cache_key` 或 `:to_param` 方法即可。如果想生成自定义键,可以在类中定义 `:cache_key` 方法。Active Record 根据类名和记录的 ID 生成缓存键。 - -缓存键也可使用 Hash 或者数组。 - -```ruby -# This is a legal cache key -Rails.cache.read(site: "mysite", owners: [owner_1, owner_2]) -``` - -`Rails.cache` 方法中使用的键和保存到存储引擎中的键并不一样。保存时,可能会根据命名空间或引擎的限制做修改。也就是说,不能使用 `memcache-client` gem 调用 `Rails.cache` 方法保存缓存再尝试读取缓存。不过,无需担心会超出 memcached 的大小限制,或者违反句法规则。 - -支持条件 GET 请求 ---------------- - -条件请求是 HTTP 规范的一个特性,网页服务器告诉浏览器 GET 请求的响应自上次请求以来没有发生变化,可以直接读取浏览器缓存中的副本。 - -条件请求通过 `If-None-Match` 和 `If-Modified-Since` 报头实现,这两个报头的值分别是内容的唯一 ID 和上次修改内容的时间戳,在服务器和客户端之间来回传送。如果浏览器发送的请求中内容 ID(ETag)或上次修改时间戳和服务器上保存的值一样,服务器只需返回一个空响应,并把状态码设为未修改。 - -服务器负责查看上次修改时间戳和 `If-None-Match` 报头的值,决定是否返回完整的响应。在 Rails 中使用条件 GET 请求很简单: - -```ruby -class ProductsController < ApplicationController - - def show - @product = Product.find(params[:id]) - - # If the request is stale according to the given timestamp and etag value - # (i.e. it needs to be processed again) then execute this block - if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key) - respond_to do |wants| - # ... normal response processing - end - end - - # If the request is fresh (i.e. it's not modified) then you don't need to do - # anything. The default render checks for this using the parameters - # used in the previous call to stale? and will automatically send a - # :not_modified. So that's it, you're done. - end -end -``` - -如果不想使用 Hash,还可直接传入模型实例,Rails 会调用 `updated_at` 和 `cache_key` 方法分别设置 `last_modified` 和 `etag`: - -```ruby -class ProductsController < ApplicationController - def show - @product = Product.find(params[:id]) - respond_with(@product) if stale?(@product) - end -end -``` - -如果没有使用特殊的方式处理响应,使用默认的渲染机制(例如,没有使用 `respond_to` 代码块,或者没有手动调用 `render` 方法),还可使用十分便利的 `fresh_when` 方法: - -```ruby -class ProductsController < ApplicationController - - # This will automatically send back a :not_modified if the request is fresh, - # and will render the default template (product.*) if it's stale. - - def show - @product = Product.find(params[:id]) - fresh_when last_modified: @product.published_at.utc, etag: @product - end -end -``` diff --git a/source/zh-CN/command_line.md b/source/zh-CN/command_line.md deleted file mode 100644 index a5c7882..0000000 --- a/source/zh-CN/command_line.md +++ /dev/null @@ -1,602 +0,0 @@ -Rails 命令行 -=========== - -读完本文,你将学到: - -* 如何新建 Rails 程序; -* 如何生成模型、控制器、数据库迁移和单元测试; -* 如何启动开发服务器; -* 如果在交互 shell 中测试对象; -* 如何分析、评测程序; - --------------------------------------------------------------------------------- - -NOTE: 阅读本文前要具备一些 Rails 基础知识,可以阅读“[Rails 入门](getting_started.html)”一文。 - -命令行基础 ---------- - -有些命令在 Rails 开发过程中经常会用到,下面按照使用频率倒序列出: - -* `rails console` -* `rails server` -* `rake` -* `rails generate` -* `rails dbconsole` -* `rails new app_name` - -这些命令都可指定 `-h` 或 `--help` 选项显示具体用法。 - -下面我们来新建一个 Rails 程序,介绍各命令的用法。 - -### `rails new` - -安装 Rails 后首先要做的就是使用 `rails new` 命令新建 Rails 程序。 - -NOTE: 如果还没安装 Rails ,可以执行 `gem install rails` 命令安装。 - -```bash -$ rails new commandsapp - create - create README.rdoc - create Rakefile - create config.ru - create .gitignore - create Gemfile - create app - ... - create tmp/cache - ... - run bundle install -``` - -这个简单的命令会生成很多文件,组成一个完整的 Rails 程序,直接就可运行。 - -### `rails server` - -`rails server` 命令会启动 Ruby 内建的小型服务器 WEBrick。要想在浏览器中访问程序,就要执行这个命令。 - -无需其他操作,执行 `rails server` 命令后就能运行刚创建的 Rails 程序: - -```bash -$ cd commandsapp -$ rails server -=> Booting WEBrick -=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000 -=> Call with -d to detach -=> Ctrl-C to shutdown server -[2013-08-07 02:00:01] INFO WEBrick 1.3.1 -[2013-08-07 02:00:01] INFO ruby 2.0.0 (2013-06-27) [x86_64-darwin11.2.0] -[2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000 -``` - -只执行了三个命令,我们就启动了一个 Rails 服务器,监听端口 3000。打开浏览器,访问 ,会看到一个简单的 Rails 程序。 - -NOTE: 启动服务器的命令还可使用别名“s”:`rails s`。 - -如果想让服务器监听其他端口,可通过 `-p` 选项指定。所处的环境可由 `-e` 选项指定。 - -```bash -$ rails server -e production -p 4000 -``` - -`-b` 选项把 Rails 绑定到指定的 IP,默认 IP 是 0.0.0.0。指定 `-d` 选项后,服务器会以守护进程的形式运行。 - -### `rails generate` - -`rails generate` 使用模板生成很多东西。单独执行 `rails generate` 命令,会列出可用的生成器: - -NOTE: 还可使用别名“g”执行生成器命令:`rails g`。 - -```bash -$ rails generate -Usage: rails generate GENERATOR [args] [options] - -... -... - -Please choose a generator below. - -Rails: - assets - controller - generator - ... - ... -``` - -NOTE: 使用其他生成器 gem 可以安装更多的生成器,或者使用插件中提供的生成器,甚至还可以自己编写生成器。 - -使用生成器可以节省大量编写程序骨架的时间。 - -下面我们使用控制器生成器生成控制器。但应该使用哪个命令呢?我们问一下生成器: - -NOTE: 所有的 Rails 命令都有帮助信息。和其他 *nix 命令一样,可以在命令后加上 `--help` 或 `-h` 选项,例如 `rails server --help`。 - -```bash -$ rails generate controller -Usage: rails generate controller NAME [action action] [options] - -... -... - -Description: - ... - - To create a controller within a module, specify the controller name as a - path like 'parent_module/controller_name'. - - ... - -Example: - `rails generate controller CreditCard open debit credit close` - - Credit card controller with URLs like /credit_card/debit. - Controller: app/controllers/credit_card_controller.rb - Test: test/controllers/credit_card_controller_test.rb - Views: app/views/credit_card/debit.html.erb [...] - Helper: app/helpers/credit_card_helper.rb -``` - -控制器生成器接受的参数形式是 `generate controller ControllerName action1 action2`。下面我们来生成 `Greetings` 控制器,包含一个动作 `hello`,跟读者打个招呼。 - -```bash -$ rails generate controller Greetings hello - create app/controllers/greetings_controller.rb - route get "greetings/hello" - invoke erb - create app/views/greetings - create app/views/greetings/hello.html.erb - invoke test_unit - create test/controllers/greetings_controller_test.rb - invoke helper - create app/helpers/greetings_helper.rb - invoke test_unit - create test/helpers/greetings_helper_test.rb - invoke assets - invoke coffee - create app/assets/javascripts/greetings.js.coffee - invoke scss - create app/assets/stylesheets/greetings.css.scss -``` - -这个命令生成了什么呢?在程序中创建了一堆文件夹,还有控制器文件、视图文件、功能测试文件、视图帮助方法文件、JavaScript 文件盒样式表文件。 - -打开控制器文件(`app/controllers/greetings_controller.rb`),做些改动: - -```ruby -class GreetingsController < ApplicationController - def hello - @message = "Hello, how are you today?" - end -end -``` - -然后修改视图文件(`app/views/greetings/hello.html.erb`),显示消息: - -```erb -

A Greeting for You!

-

<%= @message %>

-``` - -执行 `rails server` 命令启动服务器: - -```bash -$ rails server -=> Booting WEBrick... -``` - -要查看的地址是 。 - -NOTE: 在常规的 Rails 程序中,URL 的格式是 http://(host)/(controller)/(action),访问 http://(host)/(controller) 会进入控制器的 `index` 动作。 - -Rails 也为数据模型提供了生成器。 - -```bash -$ rails generate model -Usage: - rails generate model NAME [field[:type][:index] field[:type][:index]] [options] - -... - -Active Record options: - [--migration] # Indicates when to generate migration - # Default: true - -... - -Description: - Create rails files for model generator. -``` - -NOTE: 全部可用的字段类型,请查看 `TableDefinition#column` 方法的[文档](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column)。 - -不过我们暂且不单独生成模型(后文再生成),先使用脚手架。Rails 中的脚手架会生成资源所需的全部文件,包括:模型,模型所用的迁移,处理模型的控制器,查看数据的视图,以及测试组件。 - -我们要创建一个名为“HighScore”的资源,记录视频游戏的最高得分。 - -```bash -$ rails generate scaffold HighScore game:string score:integer - invoke active_record - create db/migrate/20130717151933_create_high_scores.rb - create app/models/high_score.rb - invoke test_unit - create test/models/high_score_test.rb - create test/fixtures/high_scores.yml - invoke resource_route - route resources :high_scores - invoke scaffold_controller - create app/controllers/high_scores_controller.rb - invoke erb - create app/views/high_scores - create app/views/high_scores/index.html.erb - create app/views/high_scores/edit.html.erb - create app/views/high_scores/show.html.erb - create app/views/high_scores/new.html.erb - create app/views/high_scores/_form.html.erb - invoke test_unit - create test/controllers/high_scores_controller_test.rb - invoke helper - create app/helpers/high_scores_helper.rb - invoke test_unit - create test/helpers/high_scores_helper_test.rb - invoke jbuilder - create app/views/high_scores/index.json.jbuilder - create app/views/high_scores/show.json.jbuilder - invoke assets - invoke coffee - create app/assets/javascripts/high_scores.js.coffee - invoke scss - create app/assets/stylesheets/high_scores.css.scss - invoke scss - identical app/assets/stylesheets/scaffolds.css.scss -``` - -这个生成器检测到以下各组件对应的文件夹已经存储在:模型,控制器,帮助方法,布局,功能测试,单元测试,样式表。然后创建“HighScore”资源的视图、控制器、模型和迁移文件(用来创建 `high_scores` 数据表和字段),并设置好路由,以及测试等。 - -我们要运行迁移,执行文件 `20130717151933_create_high_scores.rb` 中的代码,这才能修改数据库的模式。那么要修改哪个数据库呢?执行 `rake db:migrate` 命令后会生成 SQLite3 数据库。稍后再详细介绍 Rake。 - -```bash -$ rake db:migrate -== CreateHighScores: migrating =============================================== --- create_table(:high_scores) - -> 0.0017s -== CreateHighScores: migrated (0.0019s) ====================================== -``` - -NOTE: 介绍一下单元测试。单元测试是用来测试代码、做断定的代码。在单元测试中,我们只关注代码的一部分,例如模型中的一个方法,测试其输入和输出。单元测试是你的好伙伴,你逐渐会意识到,单元测试的程度越高,生活的质量才能提上来。真的。稍后我们会编写一个单元测试。 - -我们来看一下 Rails 创建的界面。 - -```bash -$ rails server -``` - -打开浏览器,访问 ,现在可以创建新的最高得分了(太空入侵者得了 55,160 分)。 - -### `rails console` - -执行 `console` 命令后,可以在命令行中和 Rails 程序交互。`rails` console` 使用的是 IRB,所以如果你用过 IRB 的话,操作起来很顺手。在终端里可以快速测试想法,或者修改服务器端的数据,而无需在网站中操作。 - -NOTE: 这个命令还可以使用别名“c”:`rails c`。 - -执行 `console` 命令时可以指定终端在哪个环境中打开: - -```bash -$ rails console staging -``` - -如果你想测试一些代码,但不想改变存储的数据,可以执行 `rails console --sandbox`。 - -```bash -$ rails console --sandbox -Loading development environment in sandbox (Rails 4.2.0) -Any modifications you make will be rolled back on exit -irb(main):001:0> -``` - -### `rails dbconsole` - -`rails dbconsole` 能检测到你正在使用的数据库类型(还能理解传入的命令行参数),然后进入该数据库的命令行界面。该命令支持 MySQL,PostgreSQL,SQLite 和 SQLite3。 - -NOTE: 这个命令还可使用别名“db”:`rails db`。 - -### `rails runner` - -`runner` 可以以非交互的方式在 Rails 中运行 Ruby 代码。例如: - -```bash -$ rails runner "Model.long_running_method" -``` - -NOTE: 这个命令还可使用别名“r”:`rails r`。 - -可使用 `-e` 选项指定 `runner` 命令在哪个环境中运行。 - -```bash -$ rails runner -e staging "Model.long_running_method" -``` - -### `rails destroy` - -`destroy` 可以理解成 `generate` 的逆操作,能识别生成了什么,然后将其删除。 - -NOTE: 这个命令还可使用别名“d”:`rails d`。 - -```bash -$ rails generate model Oops - invoke active_record - create db/migrate/20120528062523_create_oops.rb - create app/models/oops.rb - invoke test_unit - create test/models/oops_test.rb - create test/fixtures/oops.yml -``` - -```bash -$ rails destroy model Oops - invoke active_record - remove db/migrate/20120528062523_create_oops.rb - remove app/models/oops.rb - invoke test_unit - remove test/models/oops_test.rb - remove test/fixtures/oops.yml -``` - -Rake ----- - -Rake 是 Ruby 领域的 Make,是个独立的 Ruby 工具,目的是代替 Unix 中的 make。Rake 根据 `Rakefile` 和 `.rake` 文件构建任务。Rails 使用 Rake 实现常见的管理任务,尤其是较为复杂的任务。 - -执行 `rake -- tasks` 命令可以列出所有可用的 Rake 任务,具体的任务根据所在文件夹会有所不同。每个任务都有描述信息,帮助你找到所需的命令。 - -要想查看执行 Rake 任务时的完整调用栈,可以在命令中使用 `--trace` 选项,例如 `rake db:create --trace`。 - -```bash -$ rake --tasks -rake about # List versions of all Rails frameworks and the environment -rake assets:clean # Remove compiled assets -rake assets:precompile # Compile all the assets named in config.assets.precompile -rake db:create # Create the database from config/database.yml for the current Rails.env -... -rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development) -rake middleware # Prints out your Rack middleware stack -... -rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) -rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids -``` - -NOTE: 还可以执行 `rake -T` 查看所有任务。 - -### `about` - -`rake about` 任务输出以下信息:Ruby、RubyGems、Rails 的版本号,Rails 使用的组件,程序所在的文件夹,Rails 当前所处的环境名,程序使用的数据库适配器,数据库模式版本号。如果想向他人需求帮助,检查安全补丁是否影响程序,或者需要查看现有 Rails 程序的信息,可以使用这个任务。 - -```bash -$ rake about -About your application's environment -Ruby version 1.9.3 (x86_64-linux) -RubyGems version 1.3.6 -Rack version 1.3 -Rails version 4.2.0 -JavaScript Runtime Node.js (V8) -Active Record version 4.2.0 -Action Pack version 4.2.0 -Action View version 4.2.0 -Action Mailer version 4.2.0 -Active Support version 4.2.0 -Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag -Application root /home/foobar/commandsapp -Environment development -Database adapter sqlite3 -Database schema version 20110805173523 -``` - -### `assets` - -`rake assets:precompile` 任务会预编译 `app/assets` 文件夹中的静态资源文件。`rake assets:clean` 任务会把编译好的静态资源文件删除。 - -### `db` - -Rake 命名空间 `db:` 中最常用的任务是 `migrate` 和 `create`,这两个任务会尝试运行所有迁移相关的 Rake 任务(`up`,`down`,`redo`,`reset`)。`rake db:version` 在排查问题时很有用,会输出数据库的当前版本。 - -关于数据库迁移的更多介绍,参阅“[Active Record 数据库迁移](active_record_migrations.html)”一文。 - -### `doc` - -`doc:` 命名空间中的任务可以生成程序的文档,Rails API 文档和 Rails 指南。生成的文档可以随意分割,减少程序的大小,适合在嵌入式平台使用。 - -* `rake doc:app` 在 `doc/app` 文件夹中生成程序的文档; -* `rake doc:guides` 在 `doc/guides` 文件夹中生成 Rails 指南; -* `rake doc:rails` 在 `doc/api` 文件夹中生成 Rails API 文档; - -### `notes` - -`rake notes` 会搜索整个程序,寻找以 FIXME、OPTIMIZE 或 TODO 开头的注释。搜索的文件包括 `.builder`,`.rb`,`.erb`,`.haml`,`.slim`,`.css`,`.scss`,`.js`,`.coffee`,`.rake`,`.sass` 和 `.less`。搜索的内容包括默认注解和自定义注解。 - -```bash -$ rake notes -(in /home/foobar/commandsapp) -app/controllers/admin/users_controller.rb: - * [ 20] [TODO] any other way to do this? - * [132] [FIXME] high priority for next deploy - -app/models/school.rb: - * [ 13] [OPTIMIZE] refactor this code to make it faster - * [ 17] [FIXME] -``` - -如果想查找特定的注解,例如 FIXME,可以执行 `rake notes:fixme` 任务。注意,在命令中注解的名字要使用小写形式。 - -```bash -$ rake notes:fixme -(in /home/foobar/commandsapp) -app/controllers/admin/users_controller.rb: - * [132] high priority for next deploy - -app/models/school.rb: - * [ 17] -``` - -在代码中可以使用自定义的注解,然后执行 `rake notes:custom` 任务,并使用 `ANNOTATION` 环境变量指定要查找的注解。 - -```bash -$ rake notes:custom ANNOTATION=BUG -(in /home/foobar/commandsapp) -app/models/post.rb: - * [ 23] Have to fix this one before pushing! -``` - -NOTE: 注意,不管查找的是默认的注解还是自定义的直接,注解名(例如 FIXME,BUG 等)不会在输出结果中显示。 - -默认情况下,`rake notes` 会搜索 `app`、`config`、`lib`、`bin` 和 `test` 这几个文件夹中的文件。如果想在其他的文件夹中查找,可以使用 `SOURCE_ANNOTATION_DIRECTORIES` 环境变量指定一个以逗号分隔的列表。 - -```bash -$ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor' -$ rake notes -(in /home/foobar/commandsapp) -app/models/user.rb: - * [ 35] [FIXME] User should have a subscription at this point -spec/models/user_spec.rb: - * [122] [TODO] Verify the user that has a subscription works -``` - -### `routes` - -`rake routes` 会列出程序中定义的所有路由,可为解决路由问题提供帮助,还可以让你对程序中的所有 URL 有个整体了解。 - -### `test` - -NOTE: Rails 中的单元测试详情,参见“[Rails 程序测试指南](testing.html)”一文。 - -Rails 提供了一个名为 Minitest 的测试组件。Rails 的稳定性也由测试决定。`test:` 命名空间中的任务可用于运行各种测试。 - -### `tmp` - -`Rails.root/tmp` 文件夹和 *nix 中的 `/tmp` 作用相同,用来存放临时文件,例如会话(如果使用文件存储会话)、PID 文件和缓存文件等。 - -`tmp:` 命名空间中的任务可以清理或创建 `Rails.root/tmp` 文件夹: - -* `rake tmp:cache:clear` 清理 `tmp/cache` 文件夹; -* `rake tmp:sessions:clear` 清理 `tmp/sessions` 文件夹; -* `rake tmp:sockets:clear` 清理 `tmp/sockets` 文件夹; -* `rake tmp:clear` 清理以上三个文件夹; -* `rake tmp:create` 创建会话、缓存、套接字和 PID 所需的临时文件夹; - -### 其他任务 - -* `rake stats` 用来统计代码状况,显示千行代码数和测试比例等; -* `rake secret` 会生成一个伪随机字符串,作为会话的密钥; -* `rake time:zones:all` 列出 Rails 能理解的所有时区; - -### 编写 Rake 任务 - -自己编写的 Rake 任务保存在 `Rails.root/lib/tasks` 文件夹中,文件的扩展名是 `.rake`。执行 `bin/rails generate task` 命令会生成一个新的自定义任务文件。 - -```ruby -desc "I am short, but comprehensive description for my cool task" -task task_name: [:prerequisite_task, :another_task_we_depend_on] do - # All your magic here - # Any valid Ruby code is allowed -end -``` - -向自定义的任务中传入参数的方式如下: - -```ruby -task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args| - # You can use args from here -end -``` - -任务可以分组,放入命名空间: - -```ruby -namespace :db do - desc "This task does nothing" - task :nothing do - # Seriously, nothing - end -end -``` - -执行任务的方法如下: - -```bash -rake task_name -rake "task_name[value 1]" # entire argument string should be quoted -rake db:nothing -``` - -NOTE: 如果在任务中要和程序的模型交互,例如查询数据库等,可以使用 `environment` 任务,加载程序代码。 - -Rails 命令行高级用法 ------------------- - -Rails 命令行的高级用法就是找到实用的参数,满足特定需求或者工作流程。下面是一些常用的高级命令。 - -### 新建程序时指定数据库和源码管理系统 - -新建程序时,可设置一些选项指定使用哪种数据库和源码管理系统。这么做可以节省一点时间,减少敲击键盘的次数。 - -我们来看一下 `--git` 和 `--database=postgresql` 选项有什么作用: - -```bash -$ mkdir gitapp -$ cd gitapp -$ git init -Initialized empty Git repository in .git/ -$ rails new . --git --database=postgresql - exists - create app/controllers - create app/helpers -... -... - create tmp/cache - create tmp/pids - create Rakefile -add 'Rakefile' - create README.rdoc -add 'README.rdoc' - create app/controllers/application_controller.rb -add 'app/controllers/application_controller.rb' - create app/helpers/application_helper.rb -... - create log/test.log -add 'log/test.log' -``` - -上面的命令先新建一个 `gitapp` 文件夹,初始化一个空的 git 仓库,然后再把 Rails 生成的文件加入仓库。再来看一下在数据库设置文件中添加了什么: - -```bash -$ cat config/database.yml -# PostgreSQL. Versions 8.2 and up are supported. -# -# Install the pg driver: -# gem install pg -# On OS X with Homebrew: -# gem install pg -- --with-pg-config=/usr/local/bin/pg_config -# On OS X with MacPorts: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# -# Configure Using Gemfile -# gem 'pg' -# -development: - adapter: postgresql - encoding: unicode - database: gitapp_development - pool: 5 - username: gitapp - password: -... -... -``` - -这个命令还根据我们选择的 PostgreSQL 数据库在 `database.yml` 中添加了一些设置。 - -NOTE: 指定源码管理系统选项时唯一的不便是,要先新建程序的文件夹,再初始化源码管理系统,然后才能执行 `rails new` 命令生成程序骨架。 diff --git a/source/zh-CN/configuring.md b/source/zh-CN/configuring.md deleted file mode 100644 index 943e202..0000000 --- a/source/zh-CN/configuring.md +++ /dev/null @@ -1,915 +0,0 @@ -设置 Rails 程序 -============== - -本文介绍 Rails 程序的设置和初始化。 - -读完本文,你将学到: - -* 如何调整 Rails 程序的表现; -* 如何在程序启动时运行其他代码; - --------------------------------------------------------------------------------- - -初始化代码的存放位置 ------------------ - -Rails 的初始化代码存放在四个标准位置: - -* `config/application.rb` 文件 -* 针对特定环境的设置文件; -* 初始化脚本; -* 后置初始化脚本; - -加载 Rails 前运行代码 -------------------- - -如果想在加载 Rails 之前运行代码,可以把代码添加到 `config/application.rb` 文件的 `require 'rails/all'` 之前。 - -设置 Rails 组件 --------------- - -总的来说,设置 Rails 的工作包括设置 Rails 的组件以及 Rails 本身。在设置文件 `config/application.rb` 和针对特定环境的设置文件(例如 `config/environments/production.rb`)中可以指定传给各个组件的不同设置项目。 - -例如,在文件 `config/application.rb` 中有下面这个设置: - -```ruby -config.autoload_paths += %W(#{config.root}/extras) -``` - -这是针对 Rails 本身的设置项目。如果想设置单独的 Rails 组件,一样可以在 `config/application.rb` 文件中使用同一个 `config` 对象: - -```ruby -config.active_record.schema_format = :ruby -``` - -Rails 会使用指定的设置配置 Active Record。 - -###3常规选项 - -下面这些设置方法在 `Rails::Railtie` 对象上调用,例如 `Rails::Engine` 或 `Rails::Application` 的子类。 - -* `config.after_initialize`:接受一个代码块,在 Rails 初始化程序之后执行。初始化的过程包括框架本身,引擎,以及 `config/initializers` 文件夹中所有的初始化脚本。注意,Rake 任务也会执行代码块中的代码。常用于设置初始化脚本用到的值。 - -```ruby -config.after_initialize do - ActionView::Base.sanitized_allowed_tags.delete 'div' -end -``` - -* `config.asset_host`:设置静态资源的主机。可用于设置静态资源所用的 CDN,或者通过不同的域名绕过浏览器对并发请求数量的限制。是 `config.action_controller.asset_host` 的简化。 - -* `config.autoload_once_paths`:一个由路径组成的数组,Rails 从这些路径中自动加载常量,且在多次请求之间一直可用。只有 `config.cache_classes` 为 `false`(开发环境中的默认值)时才有效。如果为 `true`,所有自动加载的代码每次请求时都会重新加载。这个数组中的路径必须出现在 `autoload_paths` 设置中。默认为空数组。 - -* `config.autoload_paths`:一个由路径组成的数组,Rails 从这些路径中自动加载常量。默认值为 `app` 文件夹中的所有子文件夹。 - -* `config.cache_classes`:决定程序中的类和模块在每次请求中是否要重新加载。在开发环境中的默认值是 `false`,在测试环境和生产环境中的默认值是 `true`。调用 `threadsafe!` 方法的作用和设为 `true` 一样。 - -* `config.action_view.cache_template_loading`:决定模板是否要在每次请求时重新加载。默认值等于 `config.cache_classes` 的值。 - -* `config.beginning_of_week`:设置一周从哪天开始。可使用的值是一周七天名称的符号形式,例如 `:monday`。 - -* `config.cache_store`:设置 Rails 缓存的存储方式。可选值有:`:memory_store`,`:file_store`,`:mem_cache_store`,`:null_store`,以及实现了缓存 API 的对象。如果文件夹 `tmp/cache` 存在,默认值为 `:file_store`,否则为 `:memory_store`。 - -* `config.colorize_logging`:设定日志信息是否使用 ANSI 颜色代码。默认值为 `true`。 - -* `config.consider_all_requests_local`:如果设为 `true`,在 HTTP 响应中会显示详细的调试信息,而且 `Rails::Info` 控制器会在地址 `/rails/info/properties` 上显示程序的运行时上下文。在开发环境和测试环境中默认值为 `true`,在生产环境中默认值为 `false`。要想更精确的控制,可以把这个选项设为 `false`,然后在控制器中实现 `local_request?` 方法,指定哪些请求要显示调试信息。 - -* `config.console`:设置执行 `rails console` 命令时使用哪个类实现控制台,最好在 `console` 代码块中设置: - -```ruby -console do - # this block is called only when running console, - # so we can safely require pry here - require "pry" - config.console = Pry -end -``` - -* `config.dependency_loading`:设为 `false` 时禁止自动加载常量。只有 `config.cache_classes` 为 `true`(生产环境的默认值)时才有效。`config.threadsafe!` 为 `true` 时,这个选项为 `false`。 - -* `config.eager_load`:设为 `true` 是按需加载 `config.eager_load_namespaces` 中的所有命名空间,包括程序本身、引擎、Rails 框架和其他注册的命名空间。 - -* `config.eager_load_namespaces`:注册命名空间,`config.eager_load` 为 `true` 时按需加载。所有命名空间都要能响应 `eager_load!` 方法。 - -* `config.eager_load_paths`:一个由路径组成的数组,`config.cache_classes` 为 `true` 时,Rails 启动时按需加载对应的代码。 - -* `config.encoding`:设置程序全局编码,默认为 UTF-8。 - -* `config.exceptions_app`:设置抛出异常后中间件 ShowException 调用哪个异常处理程序。默认为 `ActionDispatch::PublicExceptions.new(Rails.public_path)`。 - -* `config.file_watcher`:设置监视文件系统上文件变化使用的类,`config.reload_classes_only_on_change` 为 `true` 时才有效。指定的类必须符合 `ActiveSupport::FileUpdateChecker` API。 - -* `config.filter_parameters`:过滤不想写入日志的参数,例如密码,信用卡卡号。把 `config.filter_parameters+=[:password]` 加入文件 `config/initializers/filter_parameter_logging.rb`,可以过滤密码。 - -* `config.force_ssl`:强制所有请求使用 HTTPS 协议,通过 `ActionDispatch::SSL` 中间件实现。 - -* `config.log_formatter`:设置 Rails 日志的格式化工具。在生产环境中默认值为 `Logger::Formatter`,其他环境默认值为 `ActiveSupport::Logger::SimpleFormatter`。 - -* `config.log_level`:设置 Rails 日志等级。在生产环境中默认值为 `:info`,其他环境默认值为 `:debug`。 - -* `config.log_tags`:一组可响应 `request` 对象的方法。可在日志消息中加入更多信息,例如二级域名和请求 ID,便于调试多用户程序。 - -* `config.logger`:接受一个实现了 Log4r 接口的类,或者使用默认的 `Logger` 类。默认值为 `ActiveSupport::Logger`,在生产环境中关闭了自动冲刷功能。 - -* `config.middleware`:设置程序使用的中间件。详情参阅“[设置中间件](#configuring-middleware)”一节。 - -* `config.reload_classes_only_on_change`:只当监视的文件变化时才重新加载。默认值为 `true`,监视 `autoload_paths` 中所有路径。如果 `config.cache_classes` 为 `true`,忽略这个设置。 - -* `secrets.secret_key_base`: -指定一个密令,和已知的安全密令比对,防止篡改会话。新建程序时会生成一个随机密令,保存在文件 `config/secrets.yml` 中。 - -* `config.serve_static_assets`:让 Rails 伺服静态资源文件。默认值为 `true`,但在生产环境中为 `false`,因为应该使用服务器软件(例如 Nginx 或 Apache)伺服静态资源文件。 如果测试程序,或者在生产环境中使用 WEBrick(极力不推荐),应该设为 `true`,否则无法使用页面缓存,请求 `public` 文件夹中的文件时也会经由 Rails 处理。 - -* `config.session_store`:一般在 `config/initializers/session_store.rb` 文件中设置,指定使用什么方式存储会话。可用值有:`:cookie_store`(默认),`:mem_cache_store` 和 `:disabled`。`:disabled` 指明不让 Rails 处理会话。当然也可指定自定义的会话存储: - -```ruby -config.session_store :my_custom_store -``` - -这个自定义的存储方式必须定义为 `ActionDispatch::Session::MyCustomStore`。 - -* `config.time_zone`:设置程序使用的默认时区,也让 Active Record 使用这个时区。 - -###3设置静态资源 - -* `config.assets.enabled`:设置是否启用 Asset Pipeline。默认启用。 - -* `config.assets.raise_runtime_errors`:设为 `true`,启用额外的运行时错误检查。建议在 `config/environments/development.rb` 中设置,这样可以尽量减少部署到生产环境后的异常表现。 - -* `config.assets.compress`:是否压缩编译后的静态资源文件。在 `config/environments/production.rb` 中为 `true`。 - -* `config.assets.css_compressor`:设定使用的 CSS 压缩程序,默认为 `sass-rails`。目前,唯一可用的另一个值是 `:yui`,使用 `yui-compressor` gem 压缩文件。 - -* `config.assets.js_compressor`:设定使用的 JavaScript 压缩程序。可用值有:`:closure`,`:uglifier` 和 `:yui`。分别需要安装 `closure-compiler`,`uglifier` 和 `yui-compressor` 这三个 gem。 - -* `config.assets.paths`:查找静态资源文件的路径。Rails 会在这个选项添加的路径中查找静态资源文件。 - -* `config.assets.precompile`:指定执行 `rake assets:precompile` 任务时除 `application.css` 和 `application.js` 之外要编译的其他资源文件。 - -* `config.assets.prefix`:指定伺服静态资源文件时使用的地址前缀,默认为 `/assets`。 - -* `config.assets.digest`:在静态资源文件名中加入 MD5 指纹。在 `production.rb` 中默认设为 `true`。 - -* `config.assets.debug`:禁止合并和压缩静态资源文件。在 `development.rb` 中默认设为 `true`。 - -* `config.assets.cache_store`:设置 Sprockets 使用的缓存方式,默认使用文件存储。 - -* `config.assets.version`:生成 MD5 哈希时用到的一个字符串。可用来强制重新编译所有文件。 - -* `config.assets.compile`:布尔值,用于在生产环境中启用 Sprockets 实时编译功能。 - -* `config.assets.logger`:接受一个实现了 Log4r 接口的类,或者使用默认的 `Logger` 类。默认值等于 `config.logger` 选项的值。把 `config.assets.logger` 设为 `false`,可以关闭静态资源相关的日志。 - -###3设置生成器 - -Rails 允许使用 `config.generators` 方法设置使用的生成器。这个方法接受一个代码块: - -```ruby -config.generators do |g| - g.orm :active_record - g.test_framework :test_unit -end -``` - -在代码块中可用的方法如下所示: - -* `assets`:是否允许脚手架创建静态资源文件,默认为 `true`。 -* `force_plural`:是否允许使用复数形式的模型名,默认为 `false`。 -* `helper`:是否生成帮助方法文件,默认为 `true`。 -* `integration_tool`:设置使用哪个集成工具,默认为 `nil`。 -* `javascripts`:是否允许脚手架创建 JavaScript 文件,默认为 `true`。 -* `javascript_engine`:设置生成静态资源文件时使用的预处理引擎(例如 CoffeeScript),默认为 `nil`。 -* `orm`:设置使用哪个 ORM。默认为 `false`,使用 Active Record。 -* `resource_controller`:设定执行 `rails generate resource` 命令时使用哪个生成器生成控制器,默认为 `:controller`。 -* `scaffold_controller`:和 `resource_controller` 不同,设定执行 `rails generate scaffold` 命令时使用哪个生成器生成控制器,默认为 `:scaffold_controller`。 -* `stylesheets`:是否启用生成器中的样式表文件钩子,在执行脚手架时使用,也可用于其他生成器,默认值为 `true`。 -* `stylesheet_engine`:设置生成静态资源文件时使用的预处理引擎(例如 Sass),默认为 `:css`。 -* `test_framework`:设置使用哪个测试框架,默认为 `false`,使用 Test::Unit。 -* `template_engine`:设置使用哪个模板引擎,例如 ERB 或 Haml,默认为 `:erb`。 - -###3设置中间件 - -每个 Rails 程序都使用了一组标准的中间件,在开发环境中的加载顺序如下: - -* `ActionDispatch::SSL`:强制使用 HTTPS 协议处理每个请求。`config.force_ssl` 设为 `true` 时才可用。`config.ssl_options` 选项的值会传给这个中间件。 -* `ActionDispatch::Static`:用来伺服静态资源文件。如果 `config.serve_static_assets` 设为 `false`,则不会使用这个中间件。 -* `Rack::Lock`:把程序放入互斥锁中,一次只能在一个线程中运行。`config.cache_classes` 设为 `false` 时才会使用这个中间件。 -* `ActiveSupport::Cache::Strategy::LocalCache`:使用内存存储缓存。这种存储方式对线程不安全,而且只能在单个线程中做临时存储。 -* `Rack::Runtime`:设定 `X-Runtime` 报头,其值为处理请求花费的时间,单位为秒。 -* `Rails::Rack::Logger`:开始处理请求时写入日志,请求处理完成后冲刷所有日志。 -* `ActionDispatch::ShowExceptions`:捕获程序抛出的异常,如果在本地处理请求,或者 `config.consider_all_requests_local` 设为 `true`,会渲染一个精美的异常页面。如果 `config.action_dispatch.show_exceptions` 设为 `false`,则会直接抛出异常。 -* `ActionDispatch::RequestId`:在响应中加入一个唯一的 `X-Request-Id` 报头,并启用 `ActionDispatch::Request#uuid` 方法。 -* `ActionDispatch::RemoteIp`:从请求报头中获取正确的 `client_ip`,检测 IP 地址欺骗攻击。通过 `config.action_dispatch.ip_spoofing_check` 和 `config.action_dispatch.trusted_proxies` 设置。 -* `Rack::Sendfile`:响应主体为一个文件,并设置 `X-Sendfile` 报头。通过 `config.action_dispatch.x_sendfile_header` 设置。 -* `ActionDispatch::Callbacks`:处理请求之前运行指定的回调。 -* `ActiveRecord::ConnectionAdapters::ConnectionManagement`:每次请求后都清理可用的连接,除非把在请求环境变量中把 `rack.test` 键设为 `true`。 -* `ActiveRecord::QueryCache`:缓存请求中使用的 `SELECT` 查询。如果用到了 `INSERT` 或 `UPDATE` 语句,则清除缓存。 -* `ActionDispatch::Cookies`:设置请求的 cookie。 -* `ActionDispatch::Session::CookieStore`:把会话存储在 cookie 中。`config.action_controller.session_store` 设为其他值时则使用其他中间件。`config.action_controller.session_options` 的值会传给这个中间件。 -* `ActionDispatch::Flash`:设定 `flash` 键。必须为 `config.action_controller.session_store` 设置一个值,才能使用这个中间件。 -* `ActionDispatch::ParamsParser`:解析请求中的参数,生成 `params`。 -* `Rack::MethodOverride`:如果设置了 `params[:_method]`,则使用相应的方法作为此次请求的方法。这个中间件提供了对 PATCH、PUT 和 DELETE 三个 HTTP 请求方法的支持。 -* `ActionDispatch::Head`:把 HEAD 请求转换成 GET 请求,并处理请求。 - -除了上述标准中间件之外,还可使用 `config.middleware.use` 方法添加其他中间件: - -```ruby -config.middleware.use Magical::Unicorns -``` - -上述代码会把中间件 `Magical::Unicorns` 放入中间件列表的最后。如果想在某个中间件之前插入中间件,可以使用 `insert_before`: - -```ruby -config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns -``` - -如果想在某个中间件之后插入中间件,可以使用 `insert_after`: - -```ruby -config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns -``` - -中间件还可替换成其他中间件: - -```ruby -config.middleware.swap ActionController::Failsafe, Lifo::Failsafe -``` - -也可从中间件列表中删除: - -```ruby -config.middleware.delete "Rack::MethodOverride" -``` - -###3设置 i18n - -下述设置项目都针对 `I18n` 代码库。 - -* `config.i18n.available_locales`:设置程序可用本地语言的白名单。默认值为可在本地化文件中找到的所有本地语言,在新建程序中一般是 `:en`。 - -* `config.i18n.default_locale`:设置程序的默认本地化语言,默认值为 `:en`。 - -* `config.i18n.enforce_available_locales`:确保传给 i18n 的本地语言在 `available_locales` 列表中,否则抛出 `I18n::InvalidLocale` 异常。默认值为 `true`。除非特别需要,不建议禁用这个选项,因为这是一项安全措施,能防止用户提供不可用的本地语言。 - -* `config.i18n.load_path`:设置 Rails 搜寻本地化文件的路径。默认为 config/locales/*.{yml,rb}`。 - -###3设置 Active Record - -`config.active_record` 包含很多设置项: - -* `config.active_record.logger`:接受一个实现了 Log4r 接口的类,或者使用默认的 `Logger` 类,然后传给新建的数据库连接。在 Active Record 模型类或模型实例上调用 `logger` 方法可以获取这个日志类。设为 `nil` 禁用日志。 - -* `config.active_record.primary_key_prefix_type`:调整主键的命名方式。默认情况下,Rails 把主键命名为 `id`(无需设置这个选项)。除此之外还有另外两个选择: - * `:table_name`:`Customer` 模型的主键为 `customerid`; - * `:table_name_with_underscore`: `Customer` 模型的主键为 `customer_id`; - -* `config.active_record.table_name_prefix`:设置一个全局字符串,作为数据表名的前缀。如果设为 `northwest_`,那么 `Customer` 模型对应的表名为 `northwest_customers`。默认为空字符串。 - -* `config.active_record.table_name_suffix`:设置一个全局字符串,作为数据表名的后缀。如果设为 `_northwest`,那么 `Customer` 模型对应的表名为 `customers_northwest`。默认为空字符串。 - -* `config.active_record.schema_migrations_table_name`:设置模式迁移数据表的表名。 - -* `config.active_record.pluralize_table_names`:设置 Rails 在数据库中要寻找单数形式还是复数形式的数据表。如果设为 `true`(默认值),`Customer` 类对应的数据表是 `customers`。如果设为 `false`,`Customer` 类对应的数据表是 `customer`。 - -* `config.active_record.default_timezone`:从数据库中查询日期和时间时使用 `Time.local`(设为 `:local` 时)还是 `Time.utc`(设为 `:utc` 时)。默认为 `:utc`。 - -* `config.active_record.schema_format`:设置导出数据库模式到文件时使用的格式。可选项包括:`:ruby`,默认值,根据迁移导出模式,与数据库种类无关;`:sql`,导出为 SQL 语句,受数据库种类影响。 - -* `config.active_record.timestamped_migrations`:设置迁移编号使用连续的数字还是时间戳。默认值为 `true`,使用时间戳。如果有多名开发者协作,建议使用时间戳。 - -* `config.active_record.lock_optimistically`:设置 Active Record 是否使用乐观锁定,默认使用。 - -* `config.active_record.cache_timestamp_format`:设置缓存键中使用的时间戳格式,默认为 `:number`。 - -* `config.active_record.record_timestamps`:设置是否记录 `create` 和 `update` 动作的时间戳。默认为 `true`。 - -* `config.active_record.partial_writes`: 布尔值,设置是否局部写入(例如,只更新有变化的属性)。注意,如果使用局部写入,还要使用乐观锁定,因为并发更新写入的数据可能已经过期。默认值为 `true`。 - -* `config.active_record.attribute_types_cached_by_default`:设置读取时 `ActiveRecord::AttributeMethods` 缓存的字段类型。默认值为 `[:datetime, :timestamp, :time, :date]`。 - -* `config.active_record.maintain_test_schema`:设置运行测试时 Active Record 是否要保持测试数据库的模式和 `db/schema.rb` 文件(或 `db/structure.sql`)一致,默认为 `true`。 - -* `config.active_record.dump_schema_after_migration`:设置运行迁移后是否要导出数据库模式到文件 `db/schema.rb` 或 `db/structure.sql` 中。这项设置在 Rails 生成的 `config/environments/production.rb` 文件中为 `false`。如果不设置这个选项,则值为 `true`。 - -MySQL 适配器添加了一项额外设置: - -* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans`:设置 Active Record 是否要把 MySQL 数据库中 `tinyint(1)` 类型的字段视为布尔值,默认为 `true`。 - -模式导出程序添加了一项额外设置: - -* `ActiveRecord::SchemaDumper.ignore_tables`:指定一个由数据表组成的数组,导出模式时不会出现在模式文件中。仅当 `config.active_record.schema_format == :ruby` 时才有效。 - -###3设置 Action Controller - -`config.action_controller` 包含以下设置项: - -* `config.action_controller.asset_host`:设置静态资源的主机,不用程序的服务器伺服静态资源,而使用 CDN。 - -* `config.action_controller.perform_caching`:设置程序是否要缓存。在开发模式中为 `false`,生产环境中为 `true`。 - -* `config.action_controller.default_static_extension`:设置缓存文件的扩展名,默认为 `.html`。 - -* `config.action_controller.default_charset`:设置默认字符集,默认为 `utf-8`。 - -* `config.action_controller.logger`:接受一个实现了 Log4r 接口的类,或者使用默认的 `Logger` 类,用于写 Action Controller 中的日志消息。设为 `nil` 禁用日志。 - -* `config.action_controller.request_forgery_protection_token`:设置请求伪造保护的权标参数名。默认情况下调用 `protect_from_forgery` 方法,将其设为 `:authenticity_token`。 - -* `config.action_controller.allow_forgery_protection`:是否启用跨站请求伪造保护功能。在测试环境中默认为 `false`,其他环境中默认为 `true`。 - -* `config.action_controller.relative_url_root`:用来告知 Rails 程序[部署在子目录中](#deploy-to-a-subdirectory-relative-url-root)。默认值为 `ENV['RAILS_RELATIVE_URL_ROOT']`。 - -* `config.action_controller.permit_all_parameters`:设置默认允许在批量赋值中使用的参数,默认为 `false`。 - -* `config.action_controller.action_on_unpermitted_parameters`:发现禁止使用的参数时,写入日志还是抛出异常(分别设为 `:log` 和 `:raise`)。在开发环境和测试环境中的默认值为 `:log`,在其他环境中的默认值为 `false`。 - -###3设置 Action Dispatch - -* `config.action_dispatch.session_store`:设置存储会话的方式,默认为 `:cookie_store`,其他可用值有:`:active_record_store`,`:mem_cache_store`,以及自定义类的名字。 - -* `config.action_dispatch.default_headers`:一个 Hash,设置响应的默认报头。默认设定的报头为: - -```ruby -config.action_dispatch.default_headers = { - 'X-Frame-Options' => 'SAMEORIGIN', - 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff' -} -``` - -* `config.action_dispatch.tld_length`:设置顶级域名(top-level domain,简称 TLD)的长度,默认为 `1`。 - -* `config.action_dispatch.http_auth_salt`:设置 HTTP Auth 认证的加盐值,默认为 `'http authentication'`。 - -* `config.action_dispatch.signed_cookie_salt`:设置签名 cookie 的加盐值,默认为 `'signed cookie'`。 - -* `config.action_dispatch.encrypted_cookie_salt`:设置加密 cookie 的加盐值,默认为 `'encrypted cookie'`。 - -* `config.action_dispatch.encrypted_signed_cookie_salt`:设置签名加密 cookie 的加盐值,默认为 `'signed encrypted cookie'`。 - -* `config.action_dispatch.perform_deep_munge`:设置是否在参数上调用 `deep_munge` 方法。详情参阅“[Rails 安全指南](security.html#unsafe-query-generation)”一文。默认值为 `true`。 - -* `ActionDispatch::Callbacks.before`:设置在处理请求前运行的代码块。 - -* `ActionDispatch::Callbacks.to_prepare`:设置在 `ActionDispatch::Callbacks.before` 之后、处理请求之前运行的代码块。这个代码块在开发环境中的每次请求中都会运行,但在生产环境或 `cache_classes` 设为 `true` 的环境中只运行一次。 - -* `ActionDispatch::Callbacks.after`:设置处理请求之后运行的代码块。 - -###3设置 Action View - -`config.action_view` 包含以下设置项: - -* `config.action_view.field_error_proc`:设置用于生成 Active Record 表单错误的 HTML,默认为: - -```ruby -Proc.new do |html_tag, instance| - %Q(
#{html_tag}
).html_safe -end -``` - -* `config.action_view.default_form_builder`:设置默认使用的表单构造器。默认值为 `ActionView::Helpers::FormBuilder`。如果想让表单构造器在程序初始化完成后加载(在开发环境中每次请求都会重新加载),可使用字符串形式。 - -* `config.action_view.logger`:接受一个实现了 Log4r 接口的类,或者使用默认的 `Logger` 类,用于写入来自 Action View 的日志。设为 `nil` 禁用日志。 - -* `config.action_view.erb_trim_mode`:设置 ERB 使用的删除空白模式,默认为 `'-'`,使用 `<%= -%>` 或 `<%= =%>` 时,删除行尾的空白和换行。详情参阅 [Erubis 的文档](http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-trimspaces)。 - -* `config.action_view.embed_authenticity_token_in_remote_forms`:设置启用 `:remote => true` 选项的表单如何处理 `authenticity_token` 字段。默认值为 `false`,即不加入 `authenticity_token` 字段,有助于使用片段缓存缓存表单。远程表单可从 `meta` 标签中获取认证权标,因此没必要再加入 `authenticity_token` 字段,除非要支持没启用 JavaScript 的浏览器。如果要支持没启用 JavaScript 的浏览器,可以在表单的选项中加入 `:authenticity_token => true`,或者把这个设置设为 `true`。 - -* `config.action_view.prefix_partial_path_with_controller_namespace`:设置渲染命名空间中的控制器时是否要在子文件夹中查找局部视图。例如,控制器名为 `Admin::PostsController`,渲染了以下视图: - -```erb -<%= render @post %> -``` - -这个设置的默认值为 `true`,渲染的局部视图为 `/admin/posts/_post.erb`。如果设为 `false`,就会渲染 `/posts/_post.erb`,和没加命名空间的控制器(例如 `PostsController`)行为一致。 - -* `config.action_view.raise_on_missing_translations`:找不到翻译时是否抛出异常。 - -###3设置 Action Mailer - -`config.action_mailer` 包含以下设置项: - -* `config.action_mailer.logger`:接受一个实现了 Log4r 接口的类,或者使用默认的 `Logger` 类,用于写入来自 Action Mailer 的日志。设为 `nil` 禁用日志。 - -* `config.action_mailer.smtp_settings`:详细设置 `:smtp` 发送方式。接受一个 Hash,包含以下选项: - * `:address`:设置远程邮件服务器,把默认值 `"localhost"` 改成所需值即可; - * `:port`:如果邮件服务器不使用端口 25,可通过这个选项修改; - * `:domain`:如果想指定一个 HELO 域名,可通过这个选项修改; - * `:user_name`:如果所用邮件服务器需要身份认证,可通过这个选项设置用户名; - * `:password`:如果所用邮件服务器需要身份认证,可通过这个选项设置密码; - * `:authentication`:如果所用邮件服务器需要身份认证,可通过这个选项指定认证类型,可选值包括:`:plain`,`:login`,`:cram_md5`; - -* `config.action_mailer.sendmail_settings`:详细设置 `sendmail` 发送方式。接受一个 Hash,包含以下选项: - * `:location`:`sendmail` 可执行文件的位置,默认为 `/usr/sbin/sendmail`; - * `:arguments`:传入命令行的参数,默认为 `-i -t`; - -* `config.action_mailer.raise_delivery_errors`:如果无法发送邮件,是否抛出异常。默认为 `true`。 - -* `config.action_mailer.delivery_method`:设置发送方式,默认为 `:smtp`。详情参阅“Action Mailer 基础”一文中的“[设置](action_mailer_basics.html#action-mailer-configuration)”一节。。 - -* `config.action_mailer.perform_deliveries`:设置是否真的发送邮件,默认为 `true`。测试时可设为 `false`。 - -* `config.action_mailer.default_options`:设置 Action Mailer 的默认选项。可设置各个邮件发送程序的 `from` 或 `reply_to` 等选项。默认值为: - -```ruby -mime_version: "1.0", -charset: "UTF-8", -content_type: "text/plain", -parts_order: ["text/plain", "text/enriched", "text/html"] -``` - - 设置时要使用 Hash: - -```ruby -config.action_mailer.default_options = { - from: "noreply@example.com" -} -``` - -* `config.action_mailer.observers`:注册邮件发送后触发的监控器。 - -```ruby -config.action_mailer.observers = ["MailObserver"] -``` - -* `config.action_mailer.interceptors`:注册发送邮件前调用的拦截程序。 - -```ruby -config.action_mailer.interceptors = ["MailInterceptor"] -``` - -###3设置 Active Support - -Active Support 包含以下设置项: - -* `config.active_support.bare`:启动 Rails 时是否加载 `active_support/all`。默认值为 `nil`,即加载 `active_support/all`。 - -* `config.active_support.escape_html_entities_in_json`:在 JSON 格式的数据中是否转义 HTML 实体。默认为 `false`。 - -* `config.active_support.use_standard_json_time_format`:在 JSON 格式的数据中是否把日期转换成 ISO 8601 格式。默认为 `true`。 - -* `config.active_support.time_precision`:设置 JSON 编码的时间精度,默认为 `3`。 - -* `ActiveSupport::Logger.silencer`:设为 `false` 可以静默代码块中的日志消息。默认为 `true`。 - -* `ActiveSupport::Cache::Store.logger`:设置缓存存储中使用的写日志程序。 - -* `ActiveSupport::Deprecation.behavior`:作用和 `config.active_support.deprecation` 一样,设置是否显示 Rails 废弃提醒。 - -* `ActiveSupport::Deprecation.silence`:接受一个代码块,静默废弃提醒。 - -* `ActiveSupport::Deprecation.silenced`:设置是否显示废弃提醒。 - -###3设置数据库 - -几乎每个 Rails 程序都要用到数据库。数据库信息可以在环境变量 `ENV['DATABASE_URL']` 中设定,也可在 `config/database.yml` 文件中设置。 - -在 `config/database.yml` 文件中可以设置连接数据库所需的所有信息: - -```yaml -development: - adapter: postgresql - database: blog_development - pool: 5 -``` - -上述设置使用 `postgresql` 适配器连接名为 `blog_development` 的数据库。这些信息也可存储在 URL 中,通过下面的环境变量提供: - -```ruby -> puts ENV['DATABASE_URL'] -postgresql://localhost/blog_development?pool=5 -``` - -`config/database.yml` 文件包含三个区域,分别对应 Rails 中的三个默认环境: - -* `development` 环境在本地开发电脑上运行,手动与程序交互; -* `test` 环境用于运行自动化测试; -* `production` 环境用于部署后的程序; - -如果需要使用 URL 形式,也可在 `config/database.yml` 文件中按照下面的方式设置: - -``` -development: - url: postgresql://localhost/blog_development?pool=5 -``` - -`config/database.yml` 文件中可以包含 ERB 标签 `<%= %>`。这个标签中的代码被视为 Ruby 代码。使用 ERB 标签可以从环境变量中获取数据,或者计算所需的连接信息。 - -TIP: 你无须手动更新数据库设置信息。查看新建程序生成器,会发现一个名为 `--database` 的选项。使用这个选项可以从一组常用的关系型数据库中选择想用的数据库。甚至还可重复执行生成器:`cd .. && rails new blog --database=mysql`。确认覆盖文件 `config/database.yml` 后,程序就设置成使用 MySQL,而不是 SQLite。常用数据库的设置如下所示。 - -###3连接设置 - -既然数据库的连接信息有两种设置方式,就要知道两者之间的关系。 - -如果 `config/database.yml` 文件为空,而且设置了环境变量 `ENV['DATABASE_URL']`,Rails 就会使用环境变量连接数据库: - -```sh -$ cat config/database.yml - -$ echo $DATABASE_URL -postgresql://localhost/my_database -``` - -如果 `config/database.yml` 文件存在,且没有设置环境变量 `ENV['DATABASE_URL']`,Rails 会使用设置文件中的信息连接数据库: - -```sh -$ cat config/database.yml -development: - adapter: postgresql - database: my_database - host: localhost - -$ echo $DATABASE_URL -``` - -如果有 `config/database.yml` 文件,也设置了环境变量 `ENV['DATABASE_URL']`,Rails 会合并二者提供的信息。下面举个例子说明。 - -如果二者提供的信息有重复,环境变量中的信息优先级更高: - -```sh -$ cat config/database.yml -development: - adapter: sqlite3 - database: NOT_my_database - host: localhost - -$ echo $DATABASE_URL -postgresql://localhost/my_database - -$ rails runner 'puts ActiveRecord::Base.connections' -{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}} -``` - -这里的适配器、主机和数据库名都和 `ENV['DATABASE_URL']` 中的信息一致。 - -如果没有重复,则会从这两个信息源获取信息。如果有冲突,环境变量的优先级更高。 - -```sh -$ cat config/database.yml -development: - adapter: sqlite3 - pool: 5 - -$ echo $DATABASE_URL -postgresql://localhost/my_database - -$ rails runner 'puts ActiveRecord::Base.connections' -{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}} -``` - -因为 `ENV['DATABASE_URL']` 中没有提供数据库连接池信息,所以从设置文件中获取。二者都提供了 `adapter` 信息,但使用的是 `ENV['DATABASE_URL']` 中的信息。 - -如果完全不想使用 `ENV['DATABASE_URL']` 中的信息,要使用 `url` 子建指定一个 URL: - -```sh -$ cat config/database.yml -development: - url: sqlite3://localhost/NOT_my_database - -$ echo $DATABASE_URL -postgresql://localhost/my_database - -$ rails runner 'puts ActiveRecord::Base.connections' -{"development"=>{"adapter"=>"sqlite3", "host"=>"localhost", "database"=>"NOT_my_database"}} -``` - -如上所示,`ENV['DATABASE_URL']` 中的连接信息被忽略了,使用了不同的适配器和数据库名。 - -既然 `config/database.yml` 文件中可以使用 ERB,最好使用 `ENV['DATABASE_URL']` 中的信息连接数据库。这种方式在生产环境中特别有用,因为我们并不想把数据库密码等信息纳入版本控制系统(例如 Git)。 - -```sh -$ cat config/database.yml -production: - url: <%= ENV['DATABASE_URL'] %> -``` - -注意,这种设置方式很明确,只使用 `ENV['DATABASE_URL']` 中的信息。 - -####4设置 SQLite3 数据库 - -Rails 内建支持 [SQLite3](http://www.sqlite.org)。SQLite 是个轻量级数据库,无需单独的服务器。大型线上环境可能并不适合使用 SQLite,但在开发环境和测试环境中使用却很便利。新建程序时,Rails 默认使用 SQLite,但可以随时换用其他数据库。 - -下面是默认的设置文件(`config/database.yml`)中针对开发环境的数据库设置: - -```yaml -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 -``` - -NOTE: Rails 默认使用 SQLite3 存储数据,因为 SQLite3 无需设置即可使用。Rails 还内建支持 MySQL 和 PostgreSQL。还提供了很多插件,支持更多的数据库系统。如果在生产环境中使用了数据库,Rails 很可能已经提供了对应的适配器。 - -####4设置 MySQL 数据库 - -如果不想使用 SQLite3,而是使用 MySQL,`config/database.yml` 文件的内容会有些不同。下面是针对开发环境的设置: - -```yaml -development: - adapter: mysql2 - encoding: utf8 - database: blog_development - pool: 5 - username: root - password: - socket: /tmp/mysql.sock -``` - -如果开发电脑中的 MySQL 使用 root 用户,且没有密码,可以直接使用上述设置。否则就要相应的修改用户名和密码。 - -####4设置 PostgreSQL 数据库 - -如果选择使用 PostgreSQL,`config/database.yml` 会准备好连接 PostgreSQL 数据库的信息: - -```yaml -development: - adapter: postgresql - encoding: unicode - database: blog_development - pool: 5 - username: blog - password: -``` - -`PREPARE` 语句可使用下述方法禁用: - -```yaml -production: - adapter: postgresql - prepared_statements: false -``` - -####4在 JRuby 平台上设置 SQLite3 数据库 - -如果在 JRuby 中使用 SQLite3,`config/database.yml` 文件的内容会有点不同。下面是针对开发环境的设置: - -```yaml -development: - adapter: jdbcsqlite3 - database: db/development.sqlite3 -``` - -####4在 JRuby 平台上设置 MySQL 数据库 - -如果在 JRuby 中使用 MySQL,`config/database.yml` 文件的内容会有点不同。下面是针对开发环境的设置: - -```yaml -development: - adapter: jdbcmysql - database: blog_development - username: root - password: -``` - -####4在 JRuby 平台上设置 PostgreSQL 数据库 - -如果在 JRuby 中使用 PostgreSQL,`config/database.yml` 文件的内容会有点不同。下面是针对开发环境的设置: - -```yaml -development: - adapter: jdbcpostgresql - encoding: unicode - database: blog_development - username: blog - password: -``` - -请相应地修改 `development` 区中的用户名和密码。 - -###3新建 Rails 环境 - -默认情况下,Rails 提供了三个环境:开发,测试和生产。这三个环境能满足大多数需求,但有时需要更多的环境。 - -假设有个服务器镜像了生产环境,但只用于测试。这种服务器一般叫做“交付准备服务器”(staging server)。要想为这个服务器定义一个名为“staging”的环境,新建文件 `config/environments/staging.rb` 即可。请使用 `config/environments` 文件夹中的任一文件作为模板,以此为基础修改设置。 - -新建的环境和默认提供的环境没什么区别,可以执行 `rails server -e staging` 命令启动服务器,执行 `rails console staging` 命令进入控制台,`Rails.env.staging?` 也可使用。 - -###3部署到子目录中 - -默认情况下,Rails 在根目录(例如 `/`)中运行程序。本节说明如何在子目录中运行程序。 - -假设想把网站部署到 `/app1` 目录中。生成路由时,Rails 要知道这个目录: - -```ruby -config.relative_url_root = "/app1" -``` - -或者,设置环境变量 `RAILS_RELATIVE_URL_ROOT` 也行。 - -这样设置之后,Rails 生成的链接都会加上前缀 `/app1`。 - -####4使用 Passenger - -使用 Passenger 时,在子目录中运行程序更简单。具体做法参见 [Passenger 手册](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri)。 - -####4使用反向代理 - -TODO - -####4部署到子目录时的注意事项 - -在生产环境中部署到子目录中会影响 Rails 的多个功能: - -* 开发环境 -* 测试环境 -* 伺服静态资源文件 -* Asset Pipeline - -Rails 环境设置 -------------- - -Rails 的某些功能只能通过外部的环境变量设置。下面介绍的环境变量可以被 Rails 识别: - -* `ENV["RAILS_ENV"]`:指定 Rails 运行在哪个环境中:生成环境,开发环境,测试环境等。 - -* `ENV["RAILS_RELATIVE_URL_ROOT"]`:[部署到子目录](#deploy-to-a-subdirectory-relative-url-root)时,路由用来识别 URL。 - -* `ENV["RAILS_CACHE_ID"]` 和 `ENV["RAILS_APP_VERSION"]`:用于生成缓存扩展键。允许在同一程序中使用多个缓存。 - -使用初始化脚本 ------------- - -加载完框架以及程序中使用的 gem 后,Rails 会加载初始化脚本。初始化脚本是个 Ruby 文件,存储在程序的 `config/initializers` 文件夹中。初始化脚本可在框架和 gem 加载完成后做设置。 - -NOTE: 如果有需求,可以使用子文件夹组织初始化脚本,Rails 会加载整个 `config/initializers` 文件夹中的内容。 - -TIP: 如果对初始化脚本的加载顺序有要求,可以通过文件名控制。初始化脚本的加载顺序按照文件名的字母表顺序进行。例如,`01_critical.rb` 在 `02_normal.rb` 之前加载。 - -初始化事件 --------- - -Rails 提供了 5 个初始化事件,可做钩子使用。下面按照事件的加载顺序介绍: - -* `before_configuration`:程序常量继承自 `Rails::Application` 之后立即运行。`config` 方法在此事件之前调用。 - -* `before_initialize`:在程序初始化过程中的 `:bootstrap_hook` 之前运行,接近初始化过程的开头。 - -* `to_prepare`:所有 Railtie(包括程序本身)的初始化都运行完之后,但在按需加载代码和构建中间件列表之前运行。更重要的是,在开发环境中,每次请求都会运行,但在生产环境和测试环境中只运行一次(在启动阶段)。 - -* `before_eager_load`:在按需加载代码之前运行。这是在生产环境中的默认表现,但在开发环境中不是。 - -* `after_initialize`:在程序初始化完成之后运行,即 `config/initializers` 文件夹中的初始化脚本运行完毕之后。 - -要想为这些钩子定义事件,可以在 `Rails::Application`、`Rails::Railtie` 或 `Rails::Engine` 的子类中使用代码块: - -```ruby -module YourApp - class Application < Rails::Application - config.before_initialize do - # initialization code goes here - end - end -end -``` - -或者,在 `Rails.application` 对象上调用 `config` 方法: - -```ruby -Rails.application.config.before_initialize do - # initialization code goes here -end -``` - -WARNING: 程序的某些功能,尤其是路由,在 `after_initialize` 之后还不可用。 - -###3`Rails::Railtie#initializer` - -Rails 中有几个初始化脚本使用 `Rails::Railtie` 的 `initializer` 方法定义,在程序启动时运行。下面这段代码摘自 Action Controller 中的 `set_helpers_path` 初始化脚本: - -```ruby -initializer "action_controller.set_helpers_path" do |app| - ActionController::Helpers.helpers_path = app.helpers_paths -end -``` - -`initializer` 方法接受三个参数,第一个是初始化脚本的名字,第二个是选项 Hash(上述代码中没用到),第三个参数是代码块。参数 Hash 中的 `:before` 键指定在特定的初始化脚本之前运行,`:after` 键指定在特定的初始化脚本之后运行。 - -使用 `initializer` 方法定义的初始化脚本按照定义的顺序运行,但指定 `:before` 或 `:after` 参数的初始化脚本例外。 - -WARNING: 初始化脚本可放在任一初始化脚本的前面或后面,只要符合逻辑即可。假设定义了四个初始化脚本,名字为 `"one"` 到 `"four"`(就按照这个顺序定义),其中 `"four"` 在 `"four"` 之前,且在 `"three"` 之后,这就不符合逻辑,Rails 无法判断初始化脚本的加载顺序。 - -`initializer` 方法的代码块参数是程序实例,因此可以调用 `config` 方法,如上例所示。 - -因为 `Rails::Application` 直接继承自 `Rails::Railtie`,因此可在文件 `config/application.rb` 中使用 `initializer` 方法定义程序的初始化脚本。 - -###3初始化脚本 - -下面列出了 Rails 中的所有初始化脚本,按照定义的顺序,除非特别说明,也按照这个顺序执行。 - -* `load_environment_hook`:只是一个占位符,让 `:load_environment_config` 在其前运行。 - -* `load_active_support`:加载 `active_support/dependencies`,加入 Active Support 的基础。如果 `config.active_support.bare` 为非真值(默认),还会加载 `active_support/all`。 - -* `initialize_logger`:初始化日志程序(`ActiveSupport::Logger` 对象),可通过 `Rails.logger` 调用。在此之前的初始化脚本中不能定义 `Rails.logger`。 - -* `initialize_cache`:如果还没创建 `Rails.cache`,使用 `config.cache_store` 指定的方式初始化缓存,并存入 `Rails.cache`。如果对象能响应 `middleware` 方法,对应的中间件会插入 `Rack::Runtime` 之前。 - -* `set_clear_dependencies_hook`:为 `active_record.set_dispatch_hooks` 提供钩子,在本初始化脚本之前运行。这个初始化脚本只有当 `cache_classes` 为 `false` 时才会运行,使用 `ActionDispatch::Callbacks.after` 从对象空间中删除请求过程中已经引用的常量,以便下次请求重新加载。 - -* `initialize_dependency_mechanism`:如果 `config.cache_classes` 为 `true`,设置 `ActiveSupport::Dependencies.mechanism` 使用 `require` 而不是 `load` 加载依赖件。 - -* `bootstrap_hook`:运行所有 `before_initialize` 代码块。 - -* `i18n.callbacks`:在开发环境中,设置一个 `to_prepare` 回调,调用 `I18n.reload!` 加载上次请求后修改的本地化翻译。在生产环境中这个回调只会在首次请求时运行。 - -* `active_support.deprecation_behavior`:设置各环境的废弃提醒方式,开发环境的方式为 `:log`,生产环境的方式为 `:notify`,测试环境的方式为 `:stderr`。如果没有为 `config.active_support.deprecation` 设定值,这个初始化脚本会提醒用户在当前环境的设置文件中设置。可以设为一个数组。 - -* `active_support.initialize_time_zone`:根据 `config.time_zone` 设置程序的默认时区,默认值为 `"UTC"`。 - -* `active_support.initialize_beginning_of_week`:根据 `config.beginning_of_week` 设置程序默认使用的一周开始日,默认值为 `:monday`。 - -* `action_dispatch.configure`:把 `ActionDispatch::Http::URL.tld_length` 设置为 `config.action_dispatch.tld_length` 指定的值。 - -* `action_view.set_configs`:根据 `config.action_view` 设置 Action View,把指定的方法名做为赋值方法发送给 `ActionView::Base`,并传入指定的值。 - -* `action_controller.logger`:如果还未创建,把 `ActionController::Base.logger` 设为 `Rails.logger`。 - -* `action_controller.initialize_framework_caches`:如果还未创建,把 `ActionController::Base.cache_store` 设为 `Rails.cache`。 - -* `action_controller.set_configs`:根据 `config.action_controller` 设置 Action Controller,把指定的方法名作为赋值方法发送给 `ActionController::Base`,并传入指定的值。 - -* `action_controller.compile_config_methods`:初始化指定的设置方法,以便快速访问。 - -* `active_record.initialize_timezone`:把 `ActiveRecord::Base.time_zone_aware_attributes` 设为 `true`,并把 `ActiveRecord::Base.default_timezone` 设为 UTC。从数据库中读取数据时,转换成 `Time.zone` 中指定的时区。 - -* `active_record.logger`:如果还未创建,把 `ActiveRecord::Base.logger` 设为 `Rails.logger`。 - -* `active_record.set_configs`:根据 `config.active_record` 设置 Active Record,把指定的方法名作为赋值方法传给 `ActiveRecord::Base`,并传入指定的值。 - -* `active_record.initialize_database`:从 `config/database.yml` 中加载数据库设置信息,并为当前环境建立数据库连接。 - -* `active_record.log_runtime`:引入 `ActiveRecord::Railties::ControllerRuntime`,这个模块负责把 Active Record 查询花费的时间写入日志。 - -* `active_record.set_dispatch_hooks`:如果 `config.cache_classes` 为 `false`,重置所有可重新加载的数据库连接。 - -* `action_mailer.logger`:如果还未创建,把 `ActionMailer::Base.logger` 设为 `Rails.logger`。 - -* `action_mailer.set_configs`:根据 `config.action_mailer` 设置 Action Mailer,把指定的方法名作为赋值方法发送给 `ActionMailer::Base`,并传入指定的值。 - -* `action_mailer.compile_config_methods`:初始化指定的设置方法,以便快速访问。 - -* `set_load_path`:在 `bootstrap_hook` 之前运行。把 `vendor` 文件夹、`lib` 文件夹、`app` 文件夹中的所有子文件夹,以及 `config.load_paths` 中指定的路径加入 `$LOAD_PATH`。 - -* `set_autoload_paths`:在 `bootstrap_hook` 之前运行。把 `app` 文件夹中的所有子文件夹,以及 `config.autoload_paths` 指定的路径加入 `ActiveSupport::Dependencies.autoload_paths`。 - -* `add_routing_paths`:加载所有 `config/routes.rb` 文件(程序中的,Railtie 中的,以及引擎中的),并创建程序的路由。 - -* `add_locales`:把 `config/locales` 文件夹中的所有文件(程序中的,Railties 中的,以及引擎中的)加入 `I18n.load_path`,让这些文件中的翻译可用。 - -* `add_view_paths`:把程序、Railtie 和引擎中的 `app/views` 文件夹加入视图文件查找路径。 - -* `load_environment_config`:加载 `config/environments` 文件夹中当前环境对应的设置文件。 - -* `append_asset_paths`:查找程序的静态资源文件路径,Railtie 中的静态资源文件路径,以及 `config.static_asset_paths` 中可用的文件夹。 - -* `prepend_helpers_path`:把程序、Railtie、引擎中的 `app/helpers` 文件夹加入帮助文件查找路径。 - -* `load_config_initializers`:加载程序、Railtie、引擎中 `config/initializers` 文件夹里所有的 Ruby 文件。这些文件可在框架加载后做设置。 - -* `engines_blank_point`:在初始化过程加入一个时间点,以防加载引擎之前要做什么处理。在这一点之后,会运行所有 Railtie 和引擎的初始化脚本。 - -* `add_generator_templates`:在程序、Railtie、引擎的 `lib/templates` 文件夹中查找生成器使用的模板,并把这些模板添加到 `config.generators.templates`,让所有生成器都能使用。 - -* `ensure_autoload_once_paths_as_subset`:确保 `config.autoload_once_paths` 只包含 `config.autoload_paths` 中的路径。如果包含其他路径,会抛出异常。 - -* `add_to_prepare_blocks`:把程序、Railtie、引擎中的 `config.to_prepare` 加入 Action Dispatch 的 `to_prepare` 回调中,这些回调在开发环境中每次请求都会运行,但在生产环境中只在首次请求前运行。 - -* `add_builtin_route`:如果程序运行在开发环境中,这个初始化脚本会把 `rails/info/properties` 添加到程序的路由中。这个路由对应的页面显示了程序的详细信息,例如 Rails 和 Ruby 版本。 - -* `build_middleware_stack`:构建程序的中间件列表,返回一个对象,可响应 `call` 方法,参数为 Rack 请求的环境对象。 - -* `eager_load!`:如果 `config.eager_load` 为 `true`,运行 `config.before_eager_load` 钩子,然后调用 `eager_load!`,加载所有 `config.eager_load_namespaces` 中的命名空间。 - -* `finisher_hook`:为程序初始化完成点提供一个钩子,还会运行程序、Railtie、引擎中的所有 `config.after_initialize` 代码块。 - -* `set_routes_reloader`:设置 Action Dispatch 使用 `ActionDispatch::Callbacks.to_prepare` 重新加载路由文件。 - -* `disable_dependency_loading`:如果 `config.eager_load` 为 `true`,禁止自动加载依赖件。 - -数据库连接池 ----------- - -Active Record 数据库连接由 `ActiveRecord::ConnectionAdapters::ConnectionPool` 管理,确保一个连接池的线程量限制在有限的数据库连接数之内。这个限制量默认为 5,但可以在文件 `database.yml` 中设置。 - -```ruby -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 -``` - -因为连接池在 Active Record 内部处理,因此程序服务器(Thin,mongrel,Unicorn 等)要表现一致。一开始数据库连接池是空的,然后按需创建更多的链接,直到达到连接池数量限制为止。 - -任何一个请求在初次需要连接数据库时都要检查连接,请求处理完成后还会再次检查,确保后续连接可使用这个连接池。 - -如果尝试使用比可用限制更多的连接,Active Record 会阻塞连接,等待连接池分配新的连接。如果无法获得连接,会抛出如下所示的异常。 - -```ruby -ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it: -``` - -如果看到以上异常,可能需要增加连接池限制数量,方法是修改 `database.yml` 文件中的 `pool` 选项。 - -NOTE: 如果在多线程环境中运行程序,有可能多个线程同时使用多个连接。所以,如果程序的请求量很大,有可能出现多个线程抢用有限的连接。 diff --git a/source/zh-CN/constant_autoloading_and_reloading.md b/source/zh-CN/constant_autoloading_and_reloading.md deleted file mode 100644 index c392103..0000000 --- a/source/zh-CN/constant_autoloading_and_reloading.md +++ /dev/null @@ -1,1297 +0,0 @@ -Autoloading and Reloading Constants -=================================== - -This guide documents how constant autoloading and reloading works. - -After reading this guide, you will know: - -* Key aspects of Ruby constants -* What is `autoload_paths` -* How constant autoloading works -* What is `require_dependency` -* How constant reloading works -* Solutions to common autoloading gotchas - --------------------------------------------------------------------------------- - - -Introduction ------------- - -Ruby on Rails allows applications to be written as if their code was preloaded. - -In a normal Ruby program classes need to load their dependencies: - -```ruby -require 'application_controller' -require 'post' - -class PostsController < ApplicationController - def index - @posts = Post.all - end -end -``` - -Our Rubyist instinct quickly sees some redundancy in there: If classes were -defined in files matching their name, couldn't their loading be automated -somehow? We could save scanning the file for dependencies, which is brittle. - -Moreover, `Kernel#require` loads files once, but development is much more smooth -if code gets refreshed when it changes without restarting the server. It would -be nice to be able to use `Kernel#load` in development, and `Kernel#require` in -production. - -Indeed, those features are provided by Ruby on Rails, where we just write - -```ruby -class PostsController < ApplicationController - def index - @posts = Post.all - end -end -``` - -This guide documents how that works. - - -Constants Refresher -------------------- - -While constants are trivial in most programming languages, they are a rich -topic in Ruby. - -It is beyond the scope of this guide to document Ruby constants, but we are -nevertheless going to highlight a few key topics. Truly grasping the following -sections is instrumental to understanding constant autoloading and reloading. - -### Nesting - -Class and module definitions can be nested to create namespaces: - -```ruby -module XML - class SAXParser - # (1) - end -end -``` - -The *nesting* at any given place is the collection of enclosing nested class and -module objects outwards. For example, in the previous example, the nesting at -(1) is - -```ruby -[XML::SAXParser, XML] -``` - -It is important to understand that the nesting is composed of class and module -*objects*, it has nothing to do with the constants used to access them, and is -also unrelated to their names. - -For instance, while this definition is similar to the previous one: - -```ruby -class XML::SAXParser - # (2) -end -``` - -the nesting in (2) is different: - -```ruby -[XML::SAXParser] -``` - -`XML` does not belong to it. - -We can see in this example that the name of a class or module that belongs to a -certain nesting does not necessarily correlate with the namespaces at the spot. - -Even more, they are totally independent, take for instance - -```ruby -module X::Y - module A::B - # (3) - end -end -``` - -The nesting in (3) consists of two module objects: - -```ruby -[A::B, X::Y] -``` - -So, it not only doesn't end in `A`, which does not even belong to the nesting, -but it also contains `X::Y`, which is independent from `A::B`. - -The nesting is an internal stack maintained by the interpreter, and it gets -modified according to these rules: - -* The class object following a `class` keyword gets pushed when its body is -executed, and popped after it. - -* The module object following a `module` keyword gets pushed when its body is -executed, and popped after it. - -* A singleton class opened with `class << object` gets pushed, and popped later. - -* When any of the `*_eval` family of methods is called using a string argument, -the singleton class of the receiver is pushed to the nesting of the eval'ed -code. - -* The nesting at the top-level of code interpreted by `Kernel#load` is empty -unless the `load` call receives a true value as second argument, in which case -a newly created anonymous module is pushed by Ruby. - -It is interesting to observe that blocks do not modify the stack. In particular -the blocks that may be passed to `Class.new` and `Module.new` do not get the -class or module being defined pushed to their nesting. That's one of the -differences between defining classes and modules in one way or another. - -The nesting at any given place can be inspected with `Module.nesting`. - -### Class and Module Definitions are Constant Assignments - -Let's suppose the following snippet creates a class (rather than reopening it): - -```ruby -class C -end -``` - -Ruby creates a constant `C` in `Object` and stores in that constant a class -object. The name of the class instance is "C", a string, named after the -constant. - -That is, - -```ruby -class Project < ActiveRecord::Base -end -``` - -performs a constant assignment equivalent to - -```ruby -Project = Class.new(ActiveRecord::Base) -``` - -including setting the name of the class as a side-effect: - -```ruby -Project.name # => "Project" -``` - -Constant assignment has a special rule to make that happen: if the object -being assigned is an anonymous class or module, Ruby sets the object's name to -the name of the constant. - -INFO. From then on, what happens to the constant and the instance does not -matter. For example, the constant could be deleted, the class object could be -assigned to a different constant, be stored in no constant anymore, etc. Once -the name is set, it doesn't change. - -Similarly, module creation using the `module` keyword as in - -```ruby -module Admin -end -``` - -performs a constant assignment equivalent to - -```ruby -Admin = Module.new -``` - -including setting the name as a side-effect: - -```ruby -Admin.name # => "Admin" -``` - -WARNING. The execution context of a block passed to `Class.new` or `Module.new` -is not entirely equivalent to the one of the body of the definitions using the -`class` and `module` keywords. But both idioms result in the same constant -assignment. - -Thus, when one informally says "the `String` class", that really means: the -class object stored in the constant called "String" in the class object stored -in the `Object` constant. `String` is otherwise an ordinary Ruby constant and -everything related to constants such as resolution algorithms applies to it. - -Likewise, in the controller - -```ruby -class PostsController < ApplicationController - def index - @posts = Post.all - end -end -``` - -`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If -all is good, the constant evaluates to an object that responds to `all`. - -That is why we talk about *constant* autoloading, Rails has the ability to -load constants on the fly. - -### Constants are Stored in Modules - -Constants belong to modules in a very literal sense. Classes and modules have -a constant table; think of it as a hash table. - -Let's analyze an example to really understand what that means. While common -abuses of language like "the `String` class" are convenient, the exposition is -going to be precise here for didactic purposes. - -Let's consider the following module definition: - -```ruby -module Colors - RED = '0xff0000' -end -``` - -First, when the `module` keyword is processed the interpreter creates a new -entry in the constant table of the class object stored in the `Object` constant. -Said entry associates the name "Colors" to a newly created module object. -Furthermore, the interpreter sets the name of the new module object to be the -string "Colors". - -Later, when the body of the module definition is interpreted, a new entry is -created in the constant table of the module object stored in the `Colors` -constant. That entry maps the name "RED" to the string "0xff0000". - -In particular, `Colors::RED` is totally unrelated to any other `RED` constant -that may live in any other class or module object. If there were any, they -would have separate entries in their respective constant tables. - -Pay special attention in the previous paragraphs to the distinction between -class and module objects, constant names, and value objects associated to them -in constant tables. - -### Resolution Algorithms - -#### Resolution Algorithm for Relative Constants - -At any given place in the code, let's define *cref* to be the first element of -the nesting if it is not empty, or `Object` otherwise. - -Without getting too much into the details, the resolution algorithm for relative -constant references goes like this: - -1. If the nesting is not empty the constant is looked up in its elements and in -order. The ancestors of those elements are ignored. - -2. If not found, then the algorithm walks up the ancestor chain of the cref. - -3. If not found, `const_missing` is invoked on the cref. The default -implementation of `const_missing` raises `NameError`, but it can be overridden. - -Rails autoloading **does not emulate this algorithm**, but its starting point is -the name of the constant to be autoloaded, and the cref. See more in [Relative -References](#autoloading-algorithms-relative-references). - -#### Resolution Algorithm for Qualified Constants - -Qualified constants look like this: - -```ruby -Billing::Invoice -``` - -`Billing::Invoice` is composed of two constants: `Billing` is relative and is -resolved using the algorithm of the previous section. - -INFO. Leading colons would make the first segment absolute rather than -relative: `::Billing::Invoice`. That would force `Billing` to be looked up -only as a top-level constant. - -`Invoice` on the other hand is qualified by `Billing` and we are going to see -its resolution next. Let's call *parent* to that qualifying class or module -object, that is, `Billing` in the example above. The algorithm for qualified -constants goes like this: - -1. The constant is looked up in the parent and its ancestors. - -2. If the lookup fails, `const_missing` is invoked in the parent. The default -implementation of `const_missing` raises `NameError`, but it can be overridden. - -As you see, this algorithm is simpler than the one for relative constants. In -particular, the nesting plays no role here, and modules are not special-cased, -if neither they nor their ancestors have the constants, `Object` is **not** -checked. - -Rails autoloading **does not emulate this algorithm**, but its starting point is -the name of the constant to be autoloaded, and the parent. See more in -[Qualified References](#qualified-references). - - -Vocabulary ----------- - -### Parent Namespaces - -Given a string with a constant path we define its *parent namespace* to be the -string that results from removing its rightmost segment. - -For example, the parent namespace of the string "A::B::C" is the string "A::B", -the parent namespace of "A::B" is "A", and the parent namespace of "A" is "". - -The interpretation of a parent namespace when thinking about classes and modules -is tricky though. Let's consider a module M named "A::B": - -* The parent namespace, "A", may not reflect nesting at a given spot. - -* The constant `A` may no longer exist, some code could have removed it from -`Object`. - -* If `A` exists, the class or module that was originally in `A` may not be there -anymore. For example, if after a constant removal there was another constant -assignment there would generally be a different object in there. - -* In such case, it could even happen that the reassigned `A` held a new class or -module called also "A"! - -* In the previous scenarios M would no longer be reachable through `A::B` but -the module object itself could still be alive somewhere and its name would -still be "A::B". - -The idea of a parent namespace is at the core of the autoloading algorithms -and helps explain and understand their motivation intuitively, but as you see -that metaphor leaks easily. Given an edge case to reason about, take always into -account that by "parent namespace" the guide means exactly that specific string -derivation. - -### Loading Mechanism - -Rails autoloads files with `Kernel#load` when `config.cache_classes` is false, -the default in development mode, and with `Kernel#require` otherwise, the -default in production mode. - -`Kernel#load` allows Rails to execute files more than once if [constant -reloading](#constant-reloading) is enabled. - -This guide uses the word "load" freely to mean a given file is interpreted, but -the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that -flag. - - -Autoloading Availability ------------------------- - -Rails is always able to autoload provided its environment is in place. For -example the `runner` command autoloads: - -``` -$ bin/rails runner 'p User.column_names' -["id", "email", "created_at", "updated_at"] -``` - -The console autoloads, the test suite autoloads, and of course the application -autoloads. - -By default, Rails eager loads the application files when it boots in production -mode, so most of the autoloading going on in development does not happen. But -autoloading may still be triggered during eager loading. - -For example, given - -```ruby -class BeachHouse < House -end -``` - -if `House` is still unknown when `app/models/beach_house.rb` is being eager -loaded, Rails autoloads it. - - -autoload_paths --------------- - -As you probably know, when `require` gets a relative file name: - -```ruby -require 'erb' -``` - -Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby -iterates over all its directories and for each one of them checks whether they -have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds -any of them, the interpreter loads it and ends the search. Otherwise, it tries -again in the next directory of the list. If the list gets exhausted, `LoadError` -is raised. - -We are going to cover how constant autoloading works in more detail later, but -the idea is that when a constant like `Post` is hit and missing, if there's a -`post.rb` file for example in `app/models` Rails is going to find it, evaluate -it, and have `Post` defined as a side-effect. - -Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which -to look up `post.rb`. That collection is called `autoload_paths` and by -default it contains: - -* All subdirectories of `app` in the application and engines. For example, - `app/controllers`. They do not need to be the default ones, any custom - directories like `app/workers` belong automatically to `autoload_paths`. - -* Any existing second level directories called `app/*/concerns` in the - application and engines. - -* The directory `test/mailers/previews`. - -Also, this collection is configurable via `config.autoload_paths`. For example, -`lib` was in the list years ago, but no longer is. An application can opt-in -by adding this to `config/application.rb`: - -```ruby -config.autoload_paths += "#{Rails.root}/lib" -``` - -The value of `autoload_paths` can be inspected. In a just generated application -it is (edited): - -``` -$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' -.../app/assets -.../app/controllers -.../app/helpers -.../app/mailers -.../app/models -.../app/controllers/concerns -.../app/models/concerns -.../test/mailers/previews -``` - -INFO. `autoload_paths` is computed and cached during the initialization process. -The application needs to be restarted to reflect any changes in the directory -structure. - - -Autoloading Algorithms ----------------------- - -### Relative References - -A relative constant reference may appear in several places, for example, in - -```ruby -class PostsController < ApplicationController - def index - @posts = Post.all - end -end -``` - -all three constant references are relative. - -#### Constants after the `class` and `module` Keywords - -Ruby performs a lookup for the constant that follows a `class` or `module` -keyword because it needs to know if the class or module is going to be created -or reopened. - -If the constant is not defined at that point it is not considered to be a -missing constant, autoloading is **not** triggered. - -So, in the previous example, if `PostsController` is not defined when the file -is interpreted Rails autoloading is not going to be triggered, Ruby will just -define the controller. - -#### Top-Level Constants - -On the contrary, if `ApplicationController` is unknown, the constant is -considered missing and an autoload is going to be attempted by Rails. - -In order to load `ApplicationController`, Rails iterates over `autoload_paths`. -First checks if `app/assets/application_controller.rb` exists. If it does not, -which is normally the case, it continues and finds -`app/controllers/application_controller.rb`. - -If the file defines the constant `ApplicationController` all is fine, otherwise -`LoadError` is raised: - -``` -unable to autoload constant ApplicationController, expected - to define it (LoadError) -``` - -INFO. Rails does not require the value of autoloaded constants to be a class or -module object. For example, if the file `app/models/max_clients.rb` defines -`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine. - -#### Namespaces - -Autoloading `ApplicationController` looks directly under the directories of -`autoload_paths` because the nesting in that spot is empty. The situation of -`Post` is different, the nesting in that line is `[PostsController]` and support -for namespaces comes into play. - -The basic idea is that given - -```ruby -module Admin - class BaseController < ApplicationController - @@all_roles = Role.all - end -end -``` - -to autoload `Role` we are going to check if it is defined in the current or -parent namespaces, one at a time. So, conceptually we want to try to autoload -any of - -``` -Admin::BaseController::Role -Admin::Role -Role -``` - -in that order. That's the idea. To do so, Rails looks in `autoload_paths` -respectively for file names like these: - -``` -admin/base_controller/role.rb -admin/role.rb -role.rb -``` - -modulus some additional directory lookups we are going to cover soon. - -INFO. `'Constant::Name'.underscore` gives the relative path without extension of -the file name where `Constant::Name` is expected to be defined. - -Let's see how Rails autoloads the `Post` constant in the `PostsController` -above assuming the application has a `Post` model defined in -`app/models/post.rb`. - -First it checks for `posts_controller/post.rb` in `autoload_paths`: - -``` -app/assets/posts_controller/post.rb -app/controllers/posts_controller/post.rb -app/helpers/posts_controller/post.rb -... -test/mailers/previews/posts_controller/post.rb -``` - -Since the lookup is exhausted without success, a similar search for a directory -is performed, we are going to see why in the [next section](#automatic-modules): - -``` -app/assets/posts_controller/post -app/controllers/posts_controller/post -app/helpers/posts_controller/post -... -test/mailers/previews/posts_controller/post -``` - -If all those attempts fail, then Rails starts the lookup again in the parent -namespace. In this case only the top-level remains: - -``` -app/assets/post.rb -app/controllers/post.rb -app/helpers/post.rb -app/mailers/post.rb -app/models/post.rb -``` - -A matching file is found in `app/models/post.rb`. The lookup stops there and the -file is loaded. If the file actually defines `Post` all is fine, otherwise -`LoadError` is raised. - -### Qualified References - -When a qualified constant is missing Rails does not look for it in the parent -namespaces. But there is a caveat: When a constant is missing, Rails is -unable to tell if the trigger was a relative reference or a qualified one. - -For example, consider - -```ruby -module Admin - User -end -``` - -and - -```ruby -Admin::User -``` - -If `User` is missing, in either case all Rails knows is that a constant called -"User" was missing in a module called "Admin". - -If there is a top-level `User` Ruby would resolve it in the former example, but -wouldn't in the latter. In general, Rails does not emulate the Ruby constant -resolution algorithms, but in this case it tries using the following heuristic: - -> If none of the parent namespaces of the class or module has the missing -> constant then Rails assumes the reference is relative. Otherwise qualified. - -For example, if this code triggers autoloading - -```ruby -Admin::User -``` - -and the `User` constant is already present in `Object`, it is not possible that -the situation is - -```ruby -module Admin - User -end -``` - -because otherwise Ruby would have resolved `User` and no autoloading would have -been triggered in the first place. Thus, Rails assumes a qualified reference and -considers the file `admin/user.rb` and directory `admin/user` to be the only -valid options. - -In practice, this works quite well as long as the nesting matches all parent -namespaces respectively and the constants that make the rule apply are known at -that time. - -However, autoloading happens on demand. If by chance the top-level `User` was -not yet loaded, then Rails assumes a relative reference by contract. - -Naming conflicts of this kind are rare in practice, but if one occurs, -`require_dependency` provides a solution by ensuring that the constant needed -to trigger the heuristic is defined in the conflicting place. - -### Automatic Modules - -When a module acts as a namespace, Rails does not require the application to -defines a file for it, a directory matching the namespace is enough. - -Suppose an application has a back office whose controllers are stored in -`app/controllers/admin`. If the `Admin` module is not yet loaded when -`Admin::UsersController` is hit, Rails needs first to autoload the constant -`Admin`. - -If `autoload_paths` has a file called `admin.rb` Rails is going to load that -one, but if there's no such file and a directory called `admin` is found, Rails -creates an empty module and assigns it to the `Admin` constant on the fly. - -### Generic Procedure - -Relative references are reported to be missing in the cref where they were hit, -and qualified references are reported to be missing in their parent. (See -[Resolution Algorithm for Relative -Constants](#resolution-algorithm-for-relative-constants) at the beginning of -this guide for the definition of *cref*, and [Resolution Algorithm for Qualified -Constants](#resolution-algorithm-for-qualified-constants) for the definition of -*parent*.) - -The procedure to autoload constant `C` in an arbitrary situation is as follows: - -``` -if the class or module in which C is missing is Object - let ns = '' -else - let M = the class or module in which C is missing - - if M is anonymous - let ns = '' - else - let ns = M.name - end -end - -loop do - # Look for a regular file. - for dir in autoload_paths - if the file "#{dir}/#{ns.underscore}/c.rb" exists - load/require "#{dir}/#{ns.underscore}/c.rb" - - if C is now defined - return - else - raise LoadError - end - end - end - - # Look for an automatic module. - for dir in autoload_paths - if the directory "#{dir}/#{ns.underscore}/c" exists - if ns is an empty string - let C = Module.new in Object and return - else - let C = Module.new in ns.constantize and return - end - end - end - - if ns is empty - # We reached the top-level without finding the constant. - raise NameError - else - if C exists in any of the parent namespaces - # Qualified constants heuristic. - raise NameError - else - # Try again in the parent namespace. - let ns = the parent namespace of ns and retry - end - end -end -``` - - -require_dependency ------------------- - -Constant autoloading is triggered on demand and therefore code that uses a -certain constant may have it already defined or may trigger an autoload. That -depends on the execution path and it may vary between runs. - -There are times, however, in which you want to make sure a certain constant is -known when the execution reaches some code. `require_dependency` provides a way -to load a file using the current [loading mechanism](#loading-mechanism), and -keeping track of constants defined in that file as if they were autoloaded to -have them reloaded as needed. - -`require_dependency` is rarely needed, but see a couple of use-cases in -[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't -Triggered](#when-constants-aren-t-missed). - -WARNING. Unlike autoloading, `require_dependency` does not expect the file to -define any particular constant. Exploiting this behavior would be a bad practice -though, file and constant paths should match. - - -Constant Reloading ------------------- - -When `config.cache_classes` is false Rails is able to reload autoloaded -constants. - -For example, in you're in a console session and edit some file behind the -scenes, the code can be reloaded with the `reload!` command: - -``` -> reload! -``` - -When the application runs, code is reloaded when something relevant to this -logic changes. In order to do that, Rails monitors a number of things: - -* `config/routes.rb`. - -* Locales. - -* Ruby files under `autoload_paths`. - -* `db/schema.rb` and `db/structure.sql`. - -If anything in there changes, there is a middleware that detects it and reloads -the code. - -Autoloading keeps track of autoloaded constants. Reloading is implemented by -removing them all from their respective classes and modules using -`Module#remove_const`. That way, when the code goes on, those constants are -going to be unknown again, and files reloaded on demand. - -INFO. This is an all-or-nothing operation, Rails does not attempt to reload only -what changed since dependencies between classes makes that really tricky. -Instead, everything is wiped. - - -Module#autoload isn't Involved ------------------------------- - -`Module#autoload` provides a lazy way to load constants that is fully integrated -with the Ruby constant lookup algorithms, dynamic constant API, etc. It is quite -transparent. - -Rails internals make extensive use of it to defer as much work as possible from -the boot process. But constant autoloading in Rails is **not** implemented with -`Module#autoload`. - -One possible implementation based on `Module#autoload` would be to walk the -application tree and issue `autoload` calls that map existing file names to -their conventional constant name. - -There are a number of reasons that prevent Rails from using that implementation. - -For example, `Module#autoload` is only capable of loading files using `require`, -so reloading would not be possible. Not only that, it uses an internal `require` -which is not `Kernel#require`. - -Then, it provides no way to remove declarations in case a file is deleted. If a -constant gets removed with `Module#remove_const` its `autoload` is not triggered -again. Also, it doesn't support qualified names, so files with namespaces should -be interpreted during the walk tree to install their own `autoload` calls, but -those files could have constant references not yet configured. - -An implementation based on `Module#autoload` would be awesome but, as you see, -at least as of today it is not possible. Constant autoloading in Rails is -implemented with `Module#const_missing`, and that's why it has its own contract, -documented in this guide. - - -Common Gotchas --------------- - -### Nesting and Qualified Constants - -Let's consider - -```ruby -module Admin - class UsersController < ApplicationController - def index - @users = User.all - end - end -end -``` - -and - -```ruby -class Admin::UsersController < ApplicationController - def index - @users = User.all - end -end -``` - -To resolve `User` Ruby checks `Admin` in the former case, but it does not in -the latter because it does not belong to the nesting. (See [Nesting](#nesting) -and [Resolution Algorithms](#resolution-algorithms).) - -Unfortunately Rails autoloading does not know the nesting in the spot where the -constant was missing and so it is not able to act as Ruby would. In particular, -`Admin::User` will get autoloaded in either case. - -Albeit qualified constants with `class` and `module` keywords may technically -work with autoloading in some cases, it is preferable to use relative constants -instead: - -```ruby -module Admin - class UsersController < ApplicationController - def index - @users = User.all - end - end -end -``` - -### Autoloading and STI - -Single Table Inheritance (STI) is a feature of Active Record that enables -storing a hierarchy of models in one single table. The API of such models is -aware of the hierarchy and encapsulates some common needs. For example, given -these classes: - -```ruby -# app/models/polygon.rb -class Polygon < ActiveRecord::Base -end - -# app/models/triangle.rb -class Triangle < Polygon -end - -# app/models/rectangle.rb -class Rectangle < Polygon -end -``` - -`Triangle.create` creates a row that represents a triangle, and -`Rectangle.create` creates a row that represents a rectangle. If `id` is the -ID of an existing record, `Polygon.find(id)` returns an object of the correct -type. - -Methods that operate on collections are also aware of the hierarchy. For -example, `Polygon.all` returns all the records of the table, because all -rectangles and triangles are polygons. Active Record takes care of returning -instances of their corresponding class in the result set. - -Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle -and `Rectangle` has not yet been loaded, Active Record autoloads it and the -record is correctly instantiated. - -All good, but if instead of performing queries based on the root class we need -to work on some subclass, things get interesting. - -While working with `Polygon` you do not need to be aware of all its descendants, -because anything in the table is by definition a polygon, but when working with -subclasses Active Record needs to be able to enumerate the types it is looking -for. Let’s see an example. - -`Rectangle.all` only loads rectangles by adding a type constraint to the query: - -```sql -SELECT "polygons".* FROM "polygons" -WHERE "polygons"."type" IN ("Rectangle") -``` - -Let’s introduce now a subclass of `Rectangle`: - -```ruby -# app/models/square.rb -class Square < Rectangle -end -``` - -`Rectangle.all` should now return rectangles **and** squares: - -```sql -SELECT "polygons".* FROM "polygons" -WHERE "polygons"."type" IN ("Rectangle", "Square") -``` - -But there’s a caveat here: How does Active Record know that the class `Square` -exists at all? - -Even if the file `app/models/square.rb` exists and defines the `Square` class, -if no code yet used that class, `Rectangle.all` issues the query - -```sql -SELECT "polygons".* FROM "polygons" -WHERE "polygons"."type" IN ("Rectangle") -``` - -That is not a bug, the query includes all *known* descendants of `Rectangle`. - -A way to ensure this works correctly regardless of the order of execution is to -load the leaves of the tree by hand at the bottom of the file that defines the -root class: - -```ruby -# app/models/polygon.rb -class Polygon < ActiveRecord::Base -end -require_dependency ‘square’ -``` - -Only the leaves that are **at least grandchildren** need to be loaded this -way. Direct subclasses do not need to be preloaded. If the hierarchy is -deeper, intermediate classes will be autoloaded recursively from the bottom -because their constant will appear in the class definitions as superclass. - -### Autoloading and `require` - -Files defining constants to be autoloaded should never be `require`d: - -```ruby -require 'user' # DO NOT DO THIS - -class UsersController < ApplicationController - ... -end -``` - -There are two possible gotchas here in development mode: - -1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb` -runs again because `load` does not update `$LOADED_FEATURES`. - -2. If the `require` runs first Rails does not mark `User` as an autoloaded -constant and changes to `app/models/user.rb` aren't reloaded. - -Just follow the flow and use constant autoloading always, never mix -autoloading and `require`. As a last resort, if some file absolutely needs to -load a certain file use `require_dependency` to play nice with constant -autoloading. This option is rarely needed in practice, though. - -Of course, using `require` in autoloaded files to load ordinary 3rd party -libraries is fine, and Rails is able to distinguish their constants, they are -not marked as autoloaded. - -### Autoloading and Initializers - -Consider this assignment in `config/initializers/set_auth_service.rb`: - -```ruby -AUTH_SERVICE = if Rails.env.production? - RealAuthService -else - MockedAuthService -end -``` - -The purpose of this setup would be that the application uses the class that -corresponds to the environment via `AUTH_SERVICE`. In development mode -`MockedAuthService` gets autoloaded when the initializer runs. Let’s suppose -we do some requests, change its implementation, and hit the application again. -To our surprise the changes are not reflected. Why? - -As [we saw earlier](#constant-reloading), Rails removes autoloaded constants, -but `AUTH_SERVICE` stores the original class object. Stale, non-reachable -using the original constant, but perfectly functional. - -The following code summarizes the situation: - -```ruby -class C - def quack - 'quack!' - end -end - -X = C -Object.instance_eval { remove_const(:C) } -X.new.quack # => quack! -X.name # => C -C # => uninitialized constant C (NameError) -``` - -Because of that, it is not a good idea to autoload constants on application -initialization. - -In the case above we could implement a dynamic access point: - -```ruby -# app/models/auth_service.rb -class AuthService - if Rails.env.production? - def self.instance - RealAuthService - end - else - def self.instance - MockedAuthService - end - end -end -``` - -and have the application use `AuthService.instance` instead. `AuthService` -would be loaded on demand and be autoload-friendly. - -### `require_dependency` and Initializers - -As we saw before, `require_dependency` loads files in an autoloading-friendly -way. Normally, though, such a call does not make sense in an initializer. - -One could think about doing some [`require_dependency`](#require-dependency) -calls in an initializer to make sure certain constants are loaded upfront, for -example as an attempt to address the [gotcha with STIs](#autoloading-and-sti). - -Problem is, in development mode [autoloaded constants are wiped](#constant-reloading) -if there is any relevant change in the file system. If that happens then -we are in the very same situation the initializer wanted to avoid! - -Calls to `require_dependency` have to be strategically written in autoloaded -spots. - -### When Constants aren't Missed - -#### Relative References - -Let's consider a flight simulator. The application has a default flight model - -```ruby -# app/models/flight_model.rb -class FlightModel -end -``` - -that can be overridden by each airplane, for instance - -```ruby -# app/models/bell_x1/flight_model.rb -module BellX1 - class FlightModel < FlightModel - end -end - -# app/models/bell_x1/aircraft.rb -module BellX1 - class Aircraft - def initialize - @flight_model = FlightModel.new - end - end -end -``` - -The initializer wants to create a `BellX1::FlightModel` and nesting has -`BellX1`, that looks good. But if the default flight model is loaded and the -one for the Bell-X1 is not, the interpreter is able to resolve the top-level -`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`. - -That code depends on the execution path. - -These kind of ambiguities can often be resolved using qualified constants: - -```ruby -module BellX1 - class Plane - def flight_model - @flight_model ||= BellX1::FlightModel.new - end - end -end -``` - -Also, `require_dependency` is a solution: - -```ruby -require_dependency 'bell_x1/flight_model' - -module BellX1 - class Plane - def flight_model - @flight_model ||= FlightModel.new - end - end -end -``` - -#### Qualified References - -Given - -```ruby -# app/models/hotel.rb -class Hotel -end - -# app/models/image.rb -class Image -end - -# app/models/hotel/image.rb -class Hotel - class Image < Image - end -end -``` - -the expression `Hotel::Image` is ambiguous, depends on the execution path. - -As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks -up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has -been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image` -in `Hotel`, but it does in `Object`: - -``` -$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null -Image # NOT Hotel::Image! -``` - -The code evaluating `Hotel::Image` needs to make sure -`app/models/hotel/image.rb` has been loaded, possibly with -`require_dependency`. - -In these cases the interpreter issues a warning though: - -``` -warning: toplevel constant Image referenced by Hotel::Image -``` - -This surprising constant resolution can be observed with any qualifying class: - -``` -2.1.5 :001 > String::Array -(irb):1: warning: toplevel constant Array referenced by String::Array - => Array -``` - -WARNING. To find this gotcha the qualifying namespace has to be a class, -`Object` is not an ancestor of modules. - -### Autoloading within Singleton Classes - -Let's suppose we have these class definitions: - -```ruby -# app/models/hotel/services.rb -module Hotel - class Services - end -end - -# app/models/hotel/geo_location.rb -module Hotel - class GeoLocation - class << self - Services - end - end -end -``` - -If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb` -is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the -nesting when the singleton class of `Hotel::GeoLocation` is opened. - -But if `Hotel::Services` is not known, Rails is not able to autoload it, the -application raises `NameError`. - -The reason is that autoloading is triggered for the singleton class, which is -anonymous, and as [we saw before](#generic-procedure), Rails only checks the -top-level namespace in that edge case. - -An easy solution to this caveat is to qualify the constant: - -```ruby -module Hotel - class GeoLocation - class << self - Hotel::Services - end - end -end -``` - -### Autoloading in `BasicObject` - -Direct descendants of `BasicObject` do not have `Object` among their ancestors -and cannot resolve top-level constants: - -```ruby -class C < BasicObject - String # NameError: uninitialized constant C::String -end -``` - -When autoloading is involved that plot has a twist. Let's consider: - -```ruby -class C < BasicObject - def user - User # WRONG - end -end -``` - -Since Rails checks the top-level namespace `User` gets autoloaded just fine the -first time the `user` method is invoked. You only get the exception if the -`User` constant is known at that point, in particular in a *second* call to -`user`: - -```ruby -c = C.new -c.user # surprisingly fine, User -c.user # NameError: uninitialized constant C::User -``` - -because it detects a parent namespace already has the constant (see [Qualified -References](#qualified-references).) - -As with pure Ruby, within the body of a direct descendant of `BasicObject` use -always absolute constant paths: - -```ruby -class C < BasicObject - ::String # RIGHT - - def user - ::User # RIGHT - end -end -``` diff --git a/source/zh-CN/contributing_to_ruby_on_rails.md b/source/zh-CN/contributing_to_ruby_on_rails.md deleted file mode 100644 index 133ef58..0000000 --- a/source/zh-CN/contributing_to_ruby_on_rails.md +++ /dev/null @@ -1,603 +0,0 @@ -Contributing to Ruby on Rails -============================= - -This guide covers ways in which _you_ can become a part of the ongoing development of Ruby on Rails. - -After reading this guide, you will know: - -* How to use GitHub to report issues. -* How to clone master and run the test suite. -* How to help resolve existing issues. -* How to contribute to the Ruby on Rails documentation. -* How to contribute to the Ruby on Rails code. - -Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. - --------------------------------------------------------------------------------- - -Reporting an Issue ------------------- - -Ruby on Rails uses [GitHub Issue Tracking](https://github.com/rails/rails/issues) to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to submit an issue, to comment on them or to create pull requests. - -NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing. - -### Creating a Bug Report - -If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you do not find any issue addressing it you may proceed to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) - -Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. - -Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment. - -### Create a Self-Contained gist for Active Record and Action Controller Issues - -If you are filing a bug report, please use -[Active Record template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) or -[Action Controller template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) -if the bug is found in a published gem, and -[Active Record template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) or -[Action Controller template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) -if the bug happens in the master branch. - -### Special Treatment for Security Issues - -WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The [Rails security policy page](http://rubyonrails.org/security) details the procedure to follow for security issues. - -### What about Feature Requests? - -Please don't put "feature request" items into GitHub Issues. If there's a new -feature that you want to see added to Ruby on Rails, you'll need to write the -code yourself - or convince someone else to partner with you to write the code. -Later in this guide you'll find detailed instructions for proposing a patch to -Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you -can expect it to be marked "invalid" as soon as it's reviewed. - -Sometimes, the line between 'bug' and 'feature' is a hard one to draw. -Generally, a feature is anything that adds new behavior, while a bug is -anything that fixes already existing behavior that is misbehaving. Sometimes, -the core team will have to make a judgement call. That said, the distinction -generally just affects which release your patch will get in to; we love feature -submissions! They just won't get backported to maintenance branches. - -If you'd like feedback on an idea for a feature before doing the work for make -a patch, please send an email to the [rails-core mailing -list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You -might get no response, which means that everyone is indifferent. You might find -someone who's also interested in building that feature. You might get a "This -won't be accepted." But it's the proper place to discuss new ideas. GitHub -Issues are not a particularly good venue for the sometimes long and involved -discussions new features require. - - -Helping to Resolve Existing Issues ----------------------------------- - -As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [Everyone's Issues](https://github.com/rails/rails/issues) list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: - -### Verifying Bug Reports - -For starters, it helps just to verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing. - -If something is very vague, can you help squash it down into something specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem. - -If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section. - -Anything you can do to make bug reports more succinct or easier to reproduce is a help to folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. - -### Testing Patches - -You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need first to create a dedicated branch: - -```bash -$ git checkout -b testing_branch -``` - -Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. - -```bash -$ git remote add JohnSmith git://github.com/JohnSmith/rails.git -$ git pull JohnSmith orange -``` - -After applying their branch, test it out! Here are some things to think about: - -* Does the change actually work? -* Are you happy with the tests? Can you follow what they're testing? Are there any tests missing? -* Does it have the proper documentation coverage? Should documentation elsewhere be updated? -* Do you like the implementation? Can you think of a nicer or faster way to implement a part of their change? - -Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: - -
-I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too. -
- -If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. - -Contributing to the Rails Documentation ---------------------------------------- - -Ruby on Rails has two main sets of documentation: the guides, which help you -learn about Ruby on Rails, and the API, which serves as a reference. - -You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). - -You can either open a pull request to [Rails](http://github.com/rails/rails) or -ask the [Rails core team](http://rubyonrails.org/core) for commit access on -[docrails](http://github.com/rails/docrails) if you contribute regularly. -Please do not open pull requests in docrails, if you'd like to get feedback on your -change, ask for it in [Rails](http://github.com/rails/rails) instead. - -Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. - -If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub. - -When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html). - -NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements. - -NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. - -WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. - -Contributing to the Rails Code ------------------------------- - -### Setting Up a Development Environment - -To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer. - -#### The Easy Way - -The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box). - -#### The Hard Way - -In case you can't use the Rails development box, see [this other guide](development_dependencies_install.html). - -### Clone the Rails Repository - -To be able to contribute code, you need to clone the Rails repository: - -```bash -$ git clone git://github.com/rails/rails.git -``` - -and create a dedicated branch: - -```bash -$ cd rails -$ git checkout -b my_new_branch -``` - -It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. - -### Running an Application Against Your Local Branch - -In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: - -```bash -$ cd rails -$ bundle exec rails new ~/my-test-app --dev -``` - -The application generated in `~/my-test-app` runs against your local branch -and in particular sees any modifications upon server reboot. - -### Write Your Code - -Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: - -* Get the code right. -* Use Rails idioms and helpers. -* Include tests that fail without your code, and pass with it. -* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution. - - -TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. - -#### Follow the Coding Conventions - -Rails follows a simple set of coding style conventions: - -* Two spaces, no tabs (for indentation). -* No trailing whitespace. Blank lines should not have any spaces. -* Indent after private/protected. -* Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`. -* Prefer `&&`/`||` over `and`/`or`. -* Prefer class << self over self.method for class methods. -* Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`. -* Use `a = b` and not `a=b`. -* Use assert_not methods instead of refute. -* Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks. -* Follow the conventions in the source you see used already. - -The above are guidelines - please use your best judgment in using them. - -### Benchmark Your Code - -If your change has an impact on the performance of Rails, please use the -[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide -benchmark results for comparison. - -Here's an example of using benchmark-ips: - -```ruby -require 'benchmark/ips' - -Benchmark.ips do |x| - x.report('addition') { 1 + 2 } - x.report('addition with send') { 1.send(:+, 2) } -end -``` - -This will generate a report with the following information: - -``` -Calculating ------------------------------------- - addition 69114 i/100ms - addition with send 64062 i/100ms -------------------------------------------------- - addition 5307644.4 (±3.5%) i/s - 26539776 in 5.007219s - addition with send 3702897.9 (±3.5%) i/s - 18513918 in 5.006723s -``` - -Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information. - -### Running Tests - -It is not customary in Rails to run the full test suite before pushing -changes. The railties test suite in particular takes a long time, and even -more if the source code is mounted in `/vagrant` as happens in the recommended -workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). - -As a compromise, test what your code obviously affects, and if the change is -not in railties, run the whole test suite of the affected component. If all -tests are passing, that's enough to propose your contribution. We have -[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching -unexpected breakages elsewhere. - -#### Entire Rails: - -To run all the tests, do: - -```bash -$ cd rails -$ bundle exec rake test -``` - -#### For a Particular Component - -You can run tests only for a particular component (e.g. Action Pack). For example, -to run Action Mailer tests: - -```bash -$ cd actionmailer -$ bundle exec rake test -``` - -#### Running a Single Test - -You can run a single test through ruby. For instance: - -```bash -$ cd actionmailer -$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout -``` - -The `-n` option allows you to run a single method instead of the whole -file. - -##### Testing Active Record - -This is how you run the Active Record test suite only for SQLite3: - -```bash -$ cd activerecord -$ bundle exec rake test:sqlite3 -``` - -You can now run the tests as you did for `sqlite3`. The tasks are respectively - -```bash -test:mysql -test:mysql2 -test:postgresql -``` - -Finally, - -```bash -$ bundle exec rake test -``` - -will now run the four of them in turn. - -You can also run any single test separately: - -```bash -$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb -``` - -You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. - -### Warnings - -The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. - -If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: - -```bash -$ RUBYOPT=-W0 bundle exec rake test -``` - -### Updating the CHANGELOG - -The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version. - -You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG. - -A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry: - -``` -* Summary of a change that briefly describes what was changed. You can use multiple - lines and wrap them at around 80 characters. Code examples are ok, too, if needed: - - class Foo - def bar - puts 'baz' - end - end - - You can continue after the code example and you can attach issue number. GH#1234 - - *Your Name* -``` - -Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph. - -### Sanity Check - -You should not be the only person who looks at the code before you submit it. -If you know someone else who uses Rails, try asking them if they'll check out -your work. If you don't know anyone else using Rails, try hopping into the IRC -room or posting about your idea to the rails-core mailing list. Doing this in -private before you push a patch out publicly is the "smoke test" for a patch: -if you can't convince one other developer of the beauty of your code, you’re -unlikely to convince the core team either. - -### Commit Your Changes - -When you're happy with the code on your computer, you need to commit the changes to Git: - -```bash -$ git commit -a -``` - -At this point, your editor should be fired up and you can write a message for this commit. Well formatted and descriptive commit messages are extremely helpful for the others, especially when figuring out why given change was made, so please take the time to write it. - -Good commit message should be formatted according to the following example: - -``` -Short summary (ideally 50 characters or less) - -More detailed description, if necessary. It should be wrapped to 72 -characters. Try to be as descriptive as you can, even if you think that -the commit content is obvious, it may not be obvious to others. You -should add such description also if it's already present in bug tracker, -it should not be necessary to visit a webpage to check the history. - -Description can have multiple paragraphs and you can use code examples -inside, just indent it with 4 spaces: - - class ArticlesController - def index - respond_with Article.limit(10) - end - end - -You can also add bullet points: - -- you can use dashes or asterisks - -- also, try to indent next line of a point for readability, if it's too - long to fit in 72 characters -``` - -TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean. - -### Update Your Branch - -It's pretty likely that other changes to master have happened while you were working. Go get them: - -```bash -$ git checkout master -$ git pull --rebase -``` - -Now reapply your patch on top of the latest changes: - -```bash -$ git checkout my_new_branch -$ git rebase master -``` - -No conflicts? Tests still pass? Change still seems reasonable to you? Then move on. - -### Fork - -Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and press "Fork" in the upper right hand corner. - -Add the new remote to your local repository on your local machine: - -```bash -$ git remote add mine git@github.com:/rails.git -``` - -Push to your remote: - -```bash -$ git push mine my_new_branch -``` - -You might have cloned your forked repository into your machine and might want to add the original Rails repository as a remote instead, if that's the case here's what you have to do. - -In the directory you cloned your fork: - -```bash -$ git remote add rails git://github.com/rails/rails.git -``` - -Download new commits and branches from the official repository: - -```bash -$ git fetch rails -``` - -Merge the new content: - -```bash -$ git checkout master -$ git rebase rails/master -``` - -Update your fork: - -```bash -$ git push origin master -``` - -If you want to update another branch: - -```bash -$ git checkout branch_name -$ git rebase rails/branch_name -$ git push origin branch_name -``` - - -### Issue a Pull Request - -Navigate to the Rails repository you just pushed to (e.g. -https://github.com/your-user-name/rails) and click on "Pull Requests" seen in -the right panel. On the next page, press "New pull request" in the upper right -hand corner. - -Click on "Edit", if you need to change the branches being compared (it compares -"master" by default) and press "Click to create a pull request for this -comparison". - -Ensure the changesets you introduced are included. Fill in some details about -your potential patch including a meaningful title. When finished, press "Send -pull request". The Rails core team will be notified about your submission. - -### Get some Feedback - -Most pull requests will go through a few iterations before they get merged. -Different contributors will sometimes have different opinions, and often -patches will need revised before they can get merged. - -Some contributors to Rails have email notifications from GitHub turned on, but -others do not. Furthermore, (almost) everyone who works on Rails is a -volunteer, and so it may take a few days for you to get your first feedback on -a pull request. Don't despair! Sometimes it's quick, sometimes it's slow. Such -is the open source life. - -If it's been over a week, and you haven't heard anything, you might want to try -and nudge things along. You can use the [rubyonrails-core mailing -list](http://groups.google.com/group/rubyonrails-core/) for this. You can also -leave another comment on the pull request. - -While you're waiting for feedback on your pull request, open up a few other -pull requests and give someone else some! I'm sure they'll appreciate it in -the same way that you appreciate feedback on your patches. - -### Iterate as Necessary - -It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into the knowledge of the community. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem. - -#### Squashing commits - -One of the things that we may ask you to do is to "squash your commits", which -will combine all of your commits into a single commit. We prefer pull requests -that are a single commit. This makes it easier to backport changes to stable -branches, squashing makes it easier to revert bad commits, and the git history -can be a bit easier to follow. Rails is a large project, and a bunch of -extraneous commits can add a lot of noise. - -In order to do this, you'll need to have a git remote that points at the main -Rails repository. This is useful anyway, but just in case you don't have it set -up, make sure that you do this first: - -```bash -$ git remote add upstream https://github.com/rails/rails.git -``` - -You can call this remote whatever you'd like, but if you don't use `upstream`, -then change the name to your own in the instructions below. - -Given that your remote branch is called `my_pull_request`, then you can do the -following: - -```bash -$ git fetch upstream -$ git checkout my_pull_request -$ git rebase upstream/master -$ git rebase -i - -< Choose 'squash' for all of your commits except the first one. > -< Edit the commit message to make sense, and describe all your changes. > - -$ git push origin my_pull_request -f -``` - -You should be able to refresh the pull request on GitHub and see that it has -been updated. - -### Older Versions of Ruby on Rails - -If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 4-0-stable branch: - -```bash -$ git branch --track 4-0-stable origin/4-0-stable -$ git checkout 4-0-stable -``` - -TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with. - -#### Backporting - -Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort. - -For simple fixes, the easiest way to backport your changes is to [extract a diff from your changes in master and apply them to the target branch](http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git). - -First make sure your changes are the only difference between your current branch and master: - -```bash -$ git log master..HEAD -``` - -Then extract the diff: - -```bash -$ git format-patch master --stdout > ~/my_changes.patch -``` - -Switch over to the target branch and apply your changes: - -```bash -$ git checkout -b my_backport_branch 3-2-stable -$ git apply ~/my_changes.patch -``` - -This works well for simple changes. However, if your changes are complicated or if the code in master has deviated significantly from your target branch, it might require more work on your part. The difficulty of a backport varies greatly from case to case, and sometimes it is simply not worth the effort. - -Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the Ruby versions listed in `.travis.yml` before submitting your pull request. - -And then... think about your next contribution! - -Rails Contributors ------------------- - -All contributions, either via master or docrails, get credit in [Rails Contributors](http://contributors.rubyonrails.org). diff --git a/source/zh-CN/credits.html.erb b/source/zh-CN/credits.html.erb deleted file mode 100644 index d5deb29..0000000 --- a/source/zh-CN/credits.html.erb +++ /dev/null @@ -1,80 +0,0 @@ -<% content_for :page_title do %> -Ruby on Rails 指南:致谢 -<% end %> - -<% content_for :header_section do %> -

致谢

- -

感谢以下人士孜孜不倦为本项目贡献

- -<% end %> - -

Rails 指南审阅者

- -<%= author('Vijay Dev', 'vijaydev', 'vijaydev.jpg') do %> - Vijayakumar, found as Vijay Dev on the web, is a web applications developer and an open source enthusiast who lives in Chennai, India. He started using Rails in 2009 and began actively contributing to Rails documentation in late 2010. He tweets a lot and also blogs. -<% end %> - -<%= author('Xavier Noria', 'fxn', 'fxn.png') do %> - Xavier Noria has been into Ruby on Rails since 2005. He is a Rails core team member and enjoys combining his passion for Rails and his past life as a proofreader of math textbooks. Xavier is currently an independent Ruby on Rails consultant. Oh, he also tweets and can be found everywhere as "fxn". -<% end %> - -

Rails 指南设计师

- -<%= author('Jason Zimdars', 'jz') do %> - Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at Thinkcage.com or follow him on Twitter. -<% end %> - -

Rails 指南作者群

- -<%= author('Ryan Bigg', 'radar', 'radar.png') do %> - Ryan Bigg works as the Community Manager at Spree Commerce and has been working with Rails since 2006. He's the author of Multi Tenancy With Rails and co-author of Rails 4 in Action. He's written many gems which can be seen on his GitHub page and he also tweets prolifically as @ryanbigg. -<% end %> - -<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> -Oscar Del Ben is a software engineer at Wildfire. He's a regular open source contributor (GitHub account) and tweets regularly at @oscardelben. - <% end %> - -<%= author('Frederick Cheung', 'fcheung') do %> - Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at spacevatican.org. -<% end %> - -<%= author('Tore Darell', 'toretore') do %> - Tore Darell is an independent developer based in Menton, France who specialises in cruft-free web applications using Ruby, Rails and unobtrusive JavaScript. His home on the Internet is his blog Sneaky Abstractions. -<% end %> - -<%= author('Jeff Dean', 'zilkey') do %> - Jeff Dean is a software engineer with Pivotal Labs. -<% end %> - -<%= author('Mike Gunderloy', 'mgunderloy') do %> - Mike Gunderloy is a consultant with ActionRails. He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at A Fresh Cup and he twitters too much. -<% end %> - -<%= author('Mikel Lindsaar', 'raasdnil') do %> - Mikel Lindsaar has been working with Rails since 2006 and is the author of the Ruby Mail gem and core contributor (he helped re-write Action Mailer's API). Mikel is the founder of RubyX, has a blog and tweets. -<% end %> - -<%= author('Cássio Marques', 'cmarques') do %> - Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, CPP and Java, as an independent consultant. He blogs at /* CODIFICANDO */, which is mainly written in Portuguese, but will soon get a new section for posts with English translation. -<% end %> - -<%= author('James Miller', 'bensie') do %> - James Miller is a software developer for JK Tech in San Diego, CA. You can find James on GitHub, Gmail, Twitter, and Freenode as "bensie". -<% end %> - -<%= author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails developer at Basecamp and also a member of the Rails core team. He maintains a blog at has_many :bugs, :through => :rails and has a semi-active twitter account. -<% end %> - -<%= author('Emilio Tagua', 'miloops') do %> - Emilio Tagua —a.k.a. miloops— is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of Eventioz. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as "miloops". -<% end %> - -<%= author('Heiko Webers', 'hawe') do %> - Heiko Webers is the founder of bauland42, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the Ruby on Rails Security Project. After 10 years of desktop application development, Heiko has rarely looked back. -<% end %> - -<%= author('Akshay Surve', 'startupjockey', 'akshaysurve.jpg') do %> - Akshay Surve is the Founder at DeltaX, hackathon specialist, a midnight code junkie and occasionally writes prose. You can connect with him on Twitter, Linkedin, Personal Blog or Quora. -<% end %> diff --git a/source/zh-CN/debugging_rails_applications.md b/source/zh-CN/debugging_rails_applications.md deleted file mode 100644 index 35c2984..0000000 --- a/source/zh-CN/debugging_rails_applications.md +++ /dev/null @@ -1,684 +0,0 @@ -调试 Rails 程序 -============== - -本文介绍如何调试 Rails 程序。 - -读完本文,你将学到: - -* 调试的目的; -* 如何追查测试没有发现的问题; -* 不同的调试方法; -* 如何分析调用堆栈; - --------------------------------------------------------------------------------- - -调试相关的视图帮助方法 -------------------- - -调试一个常见的需求是查看变量的值。在 Rails 中,可以使用下面这三个方法: - -* `debug` -* `to_yaml` -* `inspect` - -### `debug` - -`debug` 方法使用 YAML 格式渲染对象,把结果包含在 `
` 标签中,可以把任何对象转换成人类可读的数据格式。例如,在视图中有以下代码:
-
-```erb
-<%= debug @post %>
-

- Title: - <%= @post.title %> -

-``` - -渲染后会看到如下结果: - -```yaml ---- !ruby/object:Post -attributes: - updated_at: 2008-09-05 22:55:47 - body: It's a very helpful guide for debugging your Rails app. - title: Rails debugging guide - published: t - id: "1" - created_at: 2008-09-05 22:55:47 -attributes_cache: {} - - -Title: Rails debugging guide -``` - -### `to_yaml` - -使用 YAML 格式显示实例变量、对象的值或者方法的返回值,可以这么做: - -```erb -<%= simple_format @post.to_yaml %> -

- Title: - <%= @post.title %> -

-``` - -`to_yaml` 方法把对象转换成可读性较好地 YAML 格式,`simple_format` 方法按照终端中的方式渲染每一行。`debug` 方法就是包装了这两个步骤。 - -上述代码在渲染后的页面中会显示如下内容: - -```yaml ---- !ruby/object:Post -attributes: -updated_at: 2008-09-05 22:55:47 -body: It's a very helpful guide for debugging your Rails app. -title: Rails debugging guide -published: t -id: "1" -created_at: 2008-09-05 22:55:47 -attributes_cache: {} - -Title: Rails debugging guide -``` - -### `inspect` - -另一个用于显示对象值的方法是 `inspect`,显示数组和 Hash 时使用这个方法特别方便。`inspect` 方法以字符串的形式显示对象的值。例如: - -```erb -<%= [1, 2, 3, 4, 5].inspect %> -

- Title: - <%= @post.title %> -

-``` - -渲染后得到的结果如下: - -``` -[1, 2, 3, 4, 5] - -Title: Rails debugging guide -``` - -Logger ------- - -运行时把信息写入日志文件也很有用。Rails 分别为各运行环境都维护着单独的日志文件。 - -### Logger 是什么 - -Rails 使用 `ActiveSupport::Logger` 类把信息写入日志。当然也可换用其他代码库,比如 `Log4r`。 - -替换日志代码库可以在 `environment.rb` 或其他环境文件中设置: - -```ruby -Rails.logger = Logger.new(STDOUT) -Rails.logger = Log4r::Logger.new("Application Log") -``` - -TIP: 默认情况下,日志文件都保存在 `Rails.root/log/` 文件夹中,日志文件名为 `environment_name.log`。 - -### 日志等级 - -如果消息的日志等级等于或高于设定的等级,就会写入对应的日志文件中。如果想知道当前的日志等级,可以调用 `Rails.logger.level` 方法。 - -可用的日志等级包括:`:debug`,`:info`,`:warn`,`:error`,`:fatal` 和 `:unknown`,分别对应数字 0-5。修改默认日志等级的方式如下: - -```ruby -config.log_level = :warn # In any environment initializer, or -Rails.logger.level = 0 # at any time -``` - -这么设置在开发环境和交付准备环境中很有用,在生产环境中则不会写入大量不必要的信息。 - -TIP: Rails 所有环境的默认日志等级是 `debug`。 - -### 写日志 - -把消息写入日志文件可以在控制器、模型或邮件发送程序中调用 `logger.(debug|info|warn|error|fatal)` 方法。 - -```ruby -logger.debug "Person attributes hash: #{@person.attributes.inspect}" -logger.info "Processing the request..." -logger.fatal "Terminating application, raised unrecoverable error!!!" -``` - -下面这个例子增加了额外的写日志功能: - -```ruby -class PostsController < ApplicationController - # ... - - def create - @post = Post.new(params[:post]) - logger.debug "New post: #{@post.attributes.inspect}" - logger.debug "Post should be valid: #{@post.valid?}" - - if @post.save - flash[:notice] = 'Post was successfully created.' - logger.debug "The post was saved and now the user is going to be redirected..." - redirect_to(@post) - else - render action: "new" - end - end - - # ... -end -``` - -执行上述动作后得到的日志如下: - -``` -Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST] - Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl -vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4 - Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails", - "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, - "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"posts"} -New post: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", - "published"=>false, "created_at"=>nil} -Post should be valid: true - Post Create (0.000443) INSERT INTO "posts" ("updated_at", "title", "body", "published", - "created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails', - 'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54') -The post was saved and now the user is going to be redirected... -Redirected to # -Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts] -``` - -加入这种日志信息有助于发现异常现象。如果添加了额外的日志消息,记得要合理设定日志等级,免得把大量无用的消息写入生产环境的日志文件。 - -### 日志标签 - -运行多用户/多账户的程序时,使用自定义的规则筛选日志信息能节省很多时间。Active Support 中的 `TaggedLogging` 模块可以实现这种功能,可以在日志消息中加入二级域名、请求 ID 等有助于调试的信息。 - -```ruby -logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) -logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff" -logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff" -logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" -``` - -### 日志对性能的影响 - -如果把日志写入硬盘,肯定会对程序有点小的性能影响。不过可以做些小调整:`:debug` 等级比 `:fatal` 等级对性能的影响更大,因为写入的日志消息量更多。 - -如果按照下面的方式大量调用 `Logger`,也有潜在的问题: - -```ruby -logger.debug "Person attributes hash: #{@person.attributes.inspect}" -``` - -在上述代码中,即使日志等级不包含 `:debug` 也会对性能产生影响。因为 Ruby 要初始化字符串,再花时间做插值。因此推荐把代码块传给 `logger` 方法,只有等于或大于设定的日志等级时才会执行其中的代码。重写后的代码如下: - -```ruby -logger.debug {"Person attributes hash: #{@person.attributes.inspect}"} -``` - -代码块中的内容,即字符串插值,仅当允许 `:debug` 日志等级时才会执行。这种降低性能的方式只有在日志量比较大时才能体现出来,但却是个好的编程习惯。 - -使用 `debugger` gem 调试 ------------------------ - -如果代码表现异常,可以在日志文件或者控制台查找原因。但有时使用这种方法效率不高,无法找到导致问题的根源。如果需要检查源码,`debugger` gem 可以助你一臂之力。 - -如果想学习 Rails 源码但却无从下手,也可使用 `debugger` gem。随便找个请求,然后按照这里介绍的方法,从你编写的代码一直研究到 Rails 框架的代码。 - -### 安装 - -`debugger` gem 可以设置断点,实时查看执行的 Rails 代码。安装方法如下: - -```bash -$ gem install debugger -``` - -从 2.0 版本开始,Rails 内置了调试功能。在任何 Rails 程序中都可以使用 `debugger` 方法调出调试器。 - -下面举个例子: - -```ruby -class PeopleController < ApplicationController - def new - debugger - @person = Person.new - end -end -``` - -然后就能在控制台或者日志中看到如下信息: - -``` -***** Debugger requested, but was not available: Start server with --debugger to enable ***** -``` - -记得启动服务器时要加上 `--debugger` 选项: - -```bash -$ rails server --debugger -=> Booting WEBrick -=> Rails 4.2.0 application starting on http://0.0.0.0:3000 -=> Debugger enabled -... -``` - -TIP: 在开发环境中,如果启动服务器时没有指定 `--debugger` 选项,不用重启服务器,加入 `require "debugger"` 即可。 - -### Shell - -在程序中调用 `debugger` 方法后,会在启动程序所在的终端窗口中启用调试器 shell,并进入调试器的终端 `(rdb:n)` 中。其中 `n` 是线程编号。在调试器的终端中会显示接下来要执行哪行代码。 - -如果在浏览器中执行的请求触发了调试器,当前浏览器选项卡会处于停顿状态,等待调试器启动,跟踪完整个请求。 - -例如: - -```bash -@posts = Post.all -(rdb:7) -``` - -现在可以深入分析程序的代码了。首先我们来查看一下调试器的帮助信息,输入 `help`: - -```bash -(rdb:7) help -ruby-debug help v0.10.2 -Type 'help ' for help on a specific command - -Available commands: -backtrace delete enable help next quit show trace -break disable eval info p reload source undisplay -catch display exit irb pp restart step up -condition down finish list ps save thread var -continue edit frame method putl set tmate where -``` - -TIP: 要想查看某个命令的帮助信息,可以在终端里输入 `help `,例如 `help var`。 - -接下来要学习最有用的命令之一:`list`。调试器中的命令可以使用简写形式,只要输入的字母数量足够和其他命令区分即可。因此,可使用 `l` 代替 `list`。 - -`list` 命令输出当前执行代码的前后 5 行代码。下面的例子中,当前行是第 6 行,前面用 `=>` 符号标记。 - -```bash -(rdb:7) list -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } -``` - -如果再次执行 `list` 命令,请用 `l` 试试。接下来要执行的 10 行代码会显示出来: - -```bash -(rdb:7) l -[11, 20] in /PathTo/project/app/controllers/posts_controller.rb - 11 end - 12 end - 13 - 14 # GET /posts/1 - 15 # GET /posts/1.json - 16 def show - 17 @post = Post.find(params[:id]) - 18 - 19 respond_to do |format| - 20 format.html # show.html.erb -``` - -可以一直这么执行下去,直到文件的末尾。如果到文件末尾了,`list` 命令会回到该文件的开头,再次从头开始执行一遍,把文件视为一个环形缓冲。 - -如果想查看前面 10 行代码,可以输入 `list-`(或者 `l-`): - -```bash -(rdb:7) l- -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger - 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } -``` - -使用 `list` 命令可以在文件中来回移动,查看 `debugger` 方法所在位置前后的代码。如果想知道 `debugger` 方法在文件的什么位置,可以输入 `list=`: - -```bash -(rdb:7) list= -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } -``` - -### 上下文 - -开始调试程序时,会进入堆栈中不同部分对应的不同上下文。 - -到达一个停止点或者触发某个事件时,调试器就会创建一个上下文。上下文中包含被终止程序的信息,调试器用这些信息审查调用帧,计算变量的值,以及调试器在程序的什么地方终止执行。 - -任何时候都可执行 `backtrace` 命令(简写形式为 `where`)显示程序的调用堆栈。这有助于理解如何执行到当前位置。只要你想知道程序是怎么执行到当前代码的,就可以通过 `backtrace` 命令获得答案。 - -```bash -(rdb:5) where - #0 PostsController.index - at line /PathTo/project/app/controllers/posts_controller.rb:6 - #1 Kernel.send - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...) - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617 -... -``` - -执行 `frame n` 命令可以进入指定的调用帧,其中 `n` 为帧序号。 - -```bash -(rdb:5) frame 2 -#2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 -``` - -可用的变量和逐行执行代码时一样。毕竟,这就是调试的目的。 - -向前或向后移动调用帧可以执行 `up [n]`(简写形式为 `u`)和 `down [n]` 命令,分别向前或向后移动 n 帧。n 的默认值为 1。向前移动是指向更高的帧数移动,向下移动是指向更低的帧数移动。 - -### 线程 - -`thread` 命令(缩略形式为 `th`)可以列出所有线程,停止线程,恢复线程,或者在线程之间切换。其选项如下: - -* `thread`:显示当前线程; -* `thread list`:列出所有线程及其状态,`+` 符号和数字表示当前线程; -* `thread stop n`:停止线程 `n`; -* `thread resume n`:恢复线程 `n`; -* `thread switch n`:把当前线程切换到线程 `n`; - -`thread` 命令有很多作用。调试并发线程时,如果想确认代码中没有条件竞争,使用这个命令十分方便。 - -### 审查变量 - -任何表达式都可在当前上下文中运行。如果想计算表达式的值,直接输入表达式即可。 - -下面这个例子说明如何查看在当前上下文中 `instance_variables` 的值: - -``` -@posts = Post.all -(rdb:11) instance_variables -["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"] -``` - -你可能已经看出来了,在控制器中可使用的所有实例变量都显示出来了。这个列表随着代码的执行会动态更新。例如,使用 `next` 命令执行下一行代码: - -``` -(rdb:11) next -Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET] - Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e - Parameters: {"action"=>"index", "controller"=>"posts"} -/PathToProject/posts_controller.rb:8 -respond_to do |format| -``` - -然后再查看 `instance_variables` 的值: - -``` -(rdb:11) instance_variables.include? "@posts" -true -``` - -实例变量中出现了 `@posts`,因为执行了定义这个变量的代码。 - -TIP: 执行 `irb` 命令可进入 **irb** 模式,irb 会话使用当前上下文。警告:这是实验性功能。 - -`var` 命令是显示变量值最便捷的方式: - -``` -var -(rdb:1) v[ar] const show constants of object -(rdb:1) v[ar] g[lobal] show global variables -(rdb:1) v[ar] i[nstance] show instance variables of object -(rdb:1) v[ar] l[ocal] show local variables -``` - -上述方法可以很轻易的查看当前上下文中的变量值。例如: - -``` -(rdb:9) var local - __dbg_verbose_save => false -``` - -审查对象的方法可以使用下述方式: - -``` -(rdb:9) var instance Post.new -@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"... -@attributes_cache = {} -@new_record = true -``` - -TIP: 命令 `p`(print,打印)和 `pp`(pretty print,精美格式化打印)可用来执行 Ruby 表达式并把结果显示在终端里。 - -`display` 命令可用来监视变量,查看在代码执行过程中变量值的变化: - -``` -(rdb:1) display @recent_comments -1: @recent_comments = -``` - -`display` 命令后跟的变量值会随着执行堆栈的推移而变化。如果想停止显示变量值,可以执行 `undisplay n` 命令,其中 `n` 是变量的代号,在上例中是 `1`。 - -### 逐步执行 - -现在你知道在运行代码的什么位置,以及如何查看变量的值。下面我们继续执行程序。 - -`step` 命令(缩写形式为 `s`)可以一直执行程序,直到下一个逻辑停止点,再把控制权交给调试器。 - -TIP: `step+ n` 和 `step- n` 可以相应的向前或向后 `n` 步。 - -`next` 命令的作用和 `step` 命令类似,但执行的方法不会停止。和 `step` 命令一样,也可使用加号前进 `n` 步。 - -`next` 命令和 `step` 命令的区别是,`step` 命令会在执行下一行代码之前停止,一次只执行一步;`next` 命令会执行下一行代码,但不跳出方法。 - -例如,下面这段代码调用了 `debugger` 方法: - -```ruby -class Author < ActiveRecord::Base - has_one :editorial - has_many :comments - - def find_recent_comments(limit = 10) - debugger - @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - end -end -``` - -TIP: 在控制台中也可启用调试器,但要记得在调用 `debugger` 方法之前先 `require "debugger"`。 - -```bash -$ rails console -Loading development environment (Rails 4.2.0) ->> require "debugger" -=> [] ->> author = Author.first -=> # ->> author.find_recent_comments -/PathTo/project/app/models/author.rb:11 -) -``` - -停止执行代码时,看一下输出: - -```bash -(rdb:1) list -[2, 9] in /PathTo/project/app/models/author.rb - 2 has_one :editorial - 3 has_many :comments - 4 - 5 def find_recent_comments(limit = 10) - 6 debugger -=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - 8 end - 9 end -``` - -在方法内的最后一行停止了。但是这行代码执行了吗?你可以审查一下实例变量。 - -```bash -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -``` - -`@recent_comments` 还未定义,所以这行代码还没执行。执行 `next` 命令执行这行代码: - -```bash -(rdb:1) next -/PathTo/project/app/models/author.rb:12 -@recent_comments -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -@comments = [] -@recent_comments = [] -``` - -现在看以看到,因为执行了这行代码,所以加载了 `@comments` 关联,也定义了 `@recent_comments`。 - -如果想深入方法和 Rails 代码执行堆栈,可以使用 `step` 命令,一步一步执行。这是发现代码问题(或者 Rails 框架问题)最好的方式。 - -### 断点 - -断点设置在何处终止执行代码。调试器会在断点设定行调用。 - -断点可以使用 `break` 命令(缩写形式为 `b`)动态添加。设置断点有三种方式: - -* `break line`:在当前源码文件的第 `line` 行设置断点; -* `break file:line [if expression]`:在文件 `file` 的第 `line` 行设置断点。如果指定了表达式 `expression`,其返回结果必须为 `true` 才会启动调试器; -* `break class(.|\#)method [if expression]`:在 `class` 类的 `method` 方法中设置断点,`.` 和 `\#` 分别表示类和实例方法。表达式 `expression` 的作用和上个命令一样; - -```bash -(rdb:5) break 10 -Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10 -``` - -`info breakpoints n` 或 `info break n` 命令可以列出断点。如果指定了数字 `n`,只会列出对应的断点,否则列出所有断点。 - -```bash -(rdb:5) info breakpoints -Num Enb What - 1 y at filters.rb:10 -``` - -如果想删除断点,可以执行 `delete n` 命令,删除编号为 `n` 的断点。如果不指定数字 `n`,则删除所有在用的断点。 - -```bash -(rdb:5) delete 1 -(rdb:5) info breakpoints -No breakpoints. -``` - -启用和禁用断点的方法如下: - -* `enable breakpoints`:允许使用指定的断点列表或者所有断点终止执行程序。这是创建断点后的默认状态。 -* `disable breakpoints`:指定的断点 `breakpoints` 在程序中不起作用。 - -### 捕获异常 - -`catch exception-name` 命令(或 `cat exception-name`)可捕获 `exception-name` 类型的异常,源码很有可能没有处理这个异常。 - -执行 `catch` 命令可以列出所有可用的捕获点。 - -### 恢复执行 - -有两种方法可以恢复被调试器终止执行的程序: - -* `continue [line-specification]`(或 `c`):从停止的地方恢复执行程序,设置的断点失效。可选的参数 `line-specification` 指定一个代码行数,设定一个一次性断点,程序执行到这一行时,断点会被删除。 -* `finish [frame-number]`(或 `fin`):一直执行程序,直到指定的堆栈帧结束为止。如果没有指定 `frame-number` 参数,程序会一直执行,直到当前堆栈帧结束为止。当前堆栈帧就是最近刚使用过的帧,如果之前没有移动帧的位置(执行 `up`,`down` 或 `frame` 命令),就是第 0 帧。如果指定了帧数,则运行到指定的帧结束为止。 - -### 编辑 - -下面两种方法可以从调试器中使用编辑器打开源码: - -* `edit [file:line]`:使用环境变量 `EDITOR` 指定的编辑器打开文件 `file`。还可指定文件的行数(`line`)。 -* `tmate n`(简写形式为 `tm`):在 TextMate 中打开当前文件。如果指定了参数 `n`,则使用第 n 帧。 - -### 退出 - -要想退出调试器,请执行 `quit` 命令(缩写形式为 `q`),或者别名 `exit`。 - -退出后会终止所有线程,所以服务器也会被停止,因此需要重启。 - -### 设置 - -`debugger` gem 能自动显示你正在分析的代码,在编辑器中修改代码后,还会重新加载源码。下面是可用的选项: - -* `set reload`:修改代码后重新加载; -* `set autolist`:在每个断点处执行 `list` 命令; -* `set listsize n`:设置显示 `n` 行源码; -* `set forcestep`:强制 `next` 和 `step` 命令移到终点后的下一行; - -执行 `help set` 命令可以查看完整说明。执行 `help set subcommand` 可以查看 `subcommand` 的帮助信息。 - -TIP: 设置可以保存到家目录中的 `.rdebugrc` 文件中。启动调试器时会读取这个文件中的全局设置。 - -下面是 `.rdebugrc` 文件示例: - -```bash -set autolist -set forcestep -set listsize 25 -``` - -调试内存泄露 ------------ - -Ruby 程序(Rails 或其他)可能会导致内存泄露,泄露可能由 Ruby 代码引起,也可能由 C 代码引起。 - -本节介绍如何使用 Valgrind 等工具查找并修正内存泄露问题。 - -### Valgrind - -[Valgrind](http://valgrind.org/) 这个程序只能在 Linux 系统中使用,用于侦察 C 语言层的内存泄露和条件竞争。 - -Valgrind 提供了很多工具,可用来侦察内存管理和线程问题,也能详细分析程序。例如,如果 C 扩展调用了 `malloc()` 函数,但没调用 `free()` 函数,这部分内存就会一直被占用,直到程序结束。 - -关于如何安装 Valgrind 及在 Ruby 中使用,请阅读 Evan Weaver 编写的 [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) 一文。 - -用于调试的插件 ------------- - -有很多 Rails 插件可以帮助你查找问题和调试程序。下面列出一些常用的调试插件: - -* [Footnotes](https://github.com/josevalim/rails-footnotes):在程序的每个页面底部显示请求信息,并链接到 TextMate 中的源码; -* [Query Trace](https://github.com/ntalbott/query_trace/tree/master):在日志中写入请求源信息; -* [Query Reviewer](https://github.com/nesquena/query_reviewer):这个 Rails 插件在开发环境中会在每个 `SELECT` 查询前执行 `EXPLAIN` 查询,并在每个页面中添加一个 `div` 元素,显示分析到的查询问题; -* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master):提供了一个邮件发送程序和一组默认的邮件模板,Rails 程序出现问题后发送邮件提醒; -* [Better Errors](https://github.com/charliesome/better_errors):使用全新的页面替换 Rails 默认的错误页面,显示更多的上下文信息,例如源码和变量的值; -* [RailsPanel](https://github.com/dejan/rails_panel):一个 Chrome 插件,在浏览器的开发者工具中显示 `development.log` 文件的内容,显示的内容包括:数据库查询时间,渲染时间,总时间,参数列表,渲染的视图等。 - -参考资源 -------- - -* [ruby-debug 首页](http://bashdb.sourceforge.net/ruby-debug/home-page.html) -* [debugger 首页](https://github.com/cldwalker/debugger) -* [文章:使用 ruby-debug 调试 Rails 程序](http://www.sitepoint.com/debug-rails-app-ruby-debug/) -* [Ryan Bates 制作的视频“Debugging Ruby (revised)”](http://railscasts.com/episodes/54-debugging-ruby-revised) -* [Ryan Bates 制作的视频“The Stack Trace”](http://railscasts.com/episodes/24-the-stack-trace) -* [Ryan Bates 制作的视频“The Logger”](http://railscasts.com/episodes/56-the-logger) -* [使用 ruby-debug 调试](http://bashdb.sourceforge.net/ruby-debug.html) diff --git a/source/zh-CN/development_dependencies_install.md b/source/zh-CN/development_dependencies_install.md deleted file mode 100644 index b134c9d..0000000 --- a/source/zh-CN/development_dependencies_install.md +++ /dev/null @@ -1,291 +0,0 @@ -Development Dependencies Install -================================ - -This guide covers how to setup an environment for Ruby on Rails core development. - -After reading this guide, you will know: - -* How to set up your machine for Rails development -* How to run specific groups of unit tests from the Rails test suite -* How the ActiveRecord portion of the Rails test suite operates - --------------------------------------------------------------------------------- - -The Easy Way ------------- - -The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box). - -The Hard Way ------------- - -In case you can't use the Rails development box, see section above, these are the steps to manually build a development box for Ruby on Rails core development. - -### Install Git - -Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: - -* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. -* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git -* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. -* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow. -* [GitHub](http://help.github.com) offers links to a variety of Git resources. -* [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. - -### Clone the Ruby on Rails Repository - -Navigate to the folder where you want the Ruby on Rails source code (it will create its own `rails` subdirectory) and run: - -```bash -$ git clone git://github.com/rails/rails.git -$ cd rails -``` - -### Set up and Run the Tests - -The test suite must pass with any submitted code. No matter whether you are writing a new patch, or evaluating someone else's, you need to be able to run the tests. - -Install first libxml2 and libxslt together with their development files for Nokogiri. In Ubuntu that's - -```bash -$ sudo apt-get install libxml2 libxml2-dev libxslt1-dev -``` - -If you are on Fedora or CentOS, you can run - -```bash -$ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel -``` - -If you are running Arch Linux, you're done with: - -```bash -$ sudo pacman -S libxml2 libxslt -``` - -On FreeBSD, you just have to run: - -```bash -# pkg_add -r libxml2 libxslt -``` - -Alternatively, you can install the `textproc/libxml2` and `textproc/libxslt` -ports. - -If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . - -Also, SQLite3 and its development files for the `sqlite3-ruby` gem - in Ubuntu you're done with just - -```bash -$ sudo apt-get install sqlite3 libsqlite3-dev -``` - -And if you are on Fedora or CentOS, you're done with - -```bash -$ sudo yum install sqlite3 sqlite3-devel -``` - -If you are on Arch Linux, you will need to run: - -```bash -$ sudo pacman -S sqlite -``` - -For FreeBSD users, you're done with: - -```bash -# pkg_add -r sqlite3 -``` - -Or compile the `databases/sqlite3` port. - -Get a recent version of [Bundler](http://gembundler.com/) - -```bash -$ gem install bundler -$ gem update bundler -``` - -and run: - -```bash -$ bundle install --without db -``` - -This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. - -NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. - -You can use [Homebrew](http://brew.sh/) to install memcached on OSX: - -```bash -$ brew install memcached -``` - -On Ubuntu you can install it with apt-get: - -```bash -$ sudo apt-get install memcached -``` - -Or use yum on Fedora or CentOS: - -```bash -$ sudo yum install memcached -``` - -With the dependencies now installed, you can run the test suite with: - -```bash -$ bundle exec rake test -``` - -You can also run tests for a specific component, like Action Pack, by going into its directory and executing the same command: - -```bash -$ cd actionpack -$ bundle exec rake test -``` - -If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests in the `railties/test/generators` directory only: - -```bash -$ cd railties -$ TEST_DIR=generators bundle exec rake test -``` - -You can run the tests for a particular file by using: - -```bash -$ cd actionpack -$ bundle exec ruby -Itest test/template/form_helper_test.rb -``` - -Or, you can run a single test in a particular file: - -```bash -$ cd actionpack -$ bundle exec ruby -Itest path/to/test.rb -n test_name -``` - -### Active Record Setup - -The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (`mysql` and `mysql2`), and once for PostgreSQL. We are going to see now how to set up the environment for them. - -WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. - -#### Database Configuration - -The Active Record test suite requires a custom config file: `activerecord/test/config.yml`. An example is provided in `activerecord/test/config.example.yml` which can be copied and used as needed for your environment. - -#### MySQL and PostgreSQL - -To be able to run the suite for MySQL and PostgreSQL we need their gems. Install first the servers, their client libraries, and their development files. In Ubuntu just run - -```bash -$ sudo apt-get install mysql-server libmysqlclient15-dev -$ sudo apt-get install postgresql postgresql-client postgresql-contrib libpq-dev -``` - -On Fedora or CentOS, just run: - -```bash -$ sudo yum install mysql-server mysql-devel -$ sudo yum install postgresql-server postgresql-devel -``` - -If you are running Arch Linux, MySQL isn't supported anymore so you will need to -use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)): - -```bash -$ sudo pacman -S mariadb libmariadbclient mariadb-clients -$ sudo pacman -S postgresql postgresql-libs -``` - -FreeBSD users will have to run the following: - -```bash -# pkg_add -r mysql56-client mysql56-server -# pkg_add -r postgresql92-client postgresql92-server -``` - -You can use [Homebrew](http://brew.sh/) to install MySQL and PostgreSQL on OSX: - -```bash -$ brew install mysql -$ brew install postgresql -``` -Follow instructions given by [Homebrew](http://brew.sh/) to start these. - -Or install them through ports (they are located under the `databases` folder). -If you run into troubles during the installation of MySQL, please see -[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html). - -After that, run: - -```bash -$ rm .bundle/config -$ bundle install -``` - -First, we need to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file). - -In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases: - -```bash -$ mysql -uroot -p - -mysql> CREATE USER 'rails'@'localhost'; -mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* - to 'rails'@'localhost'; -mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* - to 'rails'@'localhost'; -mysql> GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.* - to 'rails'@'localhost'; -``` - -and create the test databases: - -```bash -$ cd activerecord -$ bundle exec rake db:mysql:build -``` - -PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account -This is not needed when installed via [Homebrew](http://brew.sh). - -```bash -$ sudo -u postgres createuser --superuser $USER -``` -And for OS X (when installed via [Homebrew](http://brew.sh)) -```bash -$ createuser --superuser $USER -``` - -and then create the test databases with - -```bash -$ cd activerecord -$ bundle exec rake db:postgresql:build -``` - -It is possible to build databases for both PostgreSQL and MySQL with - -```bash -$ cd activerecord -$ bundle exec rake db:create -``` - -You can cleanup the databases using - -```bash -$ cd activerecord -$ bundle exec rake db:drop -``` - -NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. - -NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". - -If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails. diff --git a/source/zh-CN/documents.yaml b/source/zh-CN/documents.yaml deleted file mode 100644 index 82e248e..0000000 --- a/source/zh-CN/documents.yaml +++ /dev/null @@ -1,188 +0,0 @@ -- - name: Start Here - documents: - - - name: Getting Started with Rails - url: getting_started.html - description: Everything you need to know to install Rails and create your first application. -- - name: Models - documents: - - - name: Active Record Basics - url: active_record_basics.html - description: This guide will get you started with models, persistence to database and the Active Record pattern and library. - - - name: Active Record Migrations - url: active_record_migrations.html - description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. - - - name: Active Record Validations - url: active_record_validations.html - description: This guide covers how you can use Active Record validations - - - name: Active Record Callbacks - url: active_record_callbacks.html - description: This guide covers how you can use Active Record callbacks. - - - name: Active Record Associations - url: association_basics.html - description: This guide covers all the associations provided by Active Record. - - - name: Active Record Query Interface - url: active_record_querying.html - description: This guide covers the database query interface provided by Active Record. -- - name: Views - documents: - - - name: Action View Overview - url: action_view_overview.html - description: This guide provides an introduction to Action View and introduces a few of the more common view helpers. - work_in_progress: true - - - name: Layouts and Rendering in Rails - url: layouts_and_rendering.html - description: This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials. - - - name: Action View Form Helpers - url: form_helpers.html - description: Guide to using built-in Form helpers. -- - name: Controllers - documents: - - - name: Action Controller Overview - url: action_controller_overview.html - description: This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics. - - - name: Rails Routing from the Outside In - url: routing.html - description: This guide covers the user-facing features of Rails routing. If you want to understand how to use routing in your own Rails applications, start here. -- - name: Digging Deeper - documents: - - - name: Active Support Core Extensions - url: active_support_core_extensions.html - description: This guide documents the Ruby core extensions defined in Active Support. - - - name: Rails Internationalization API - url: i18n.html - description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on. - - - name: Action Mailer Basics - url: action_mailer_basics.html - description: This guide describes how to use Action Mailer to send and receive emails. - - - name: Testing Rails Applications - url: testing.html - work_in_progress: true - description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. - - - name: Securing Rails Applications - url: security.html - description: This guide describes common security problems in web applications and how to avoid them with Rails. - - - name: Debugging Rails Applications - url: debugging_rails_applications.html - description: This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code. - - - name: Configuring Rails Applications - url: configuring.html - description: This guide covers the basic configuration settings for a Rails application. - - - name: Rails Command Line Tools and Rake Tasks - url: command_line.html - description: This guide covers the command line tools and rake tasks provided by Rails. - - - name: Asset Pipeline - url: asset_pipeline.html - description: This guide documents the asset pipeline. - - - name: Working with JavaScript in Rails - url: working_with_javascript_in_rails.html - description: This guide covers the built-in Ajax/JavaScript functionality of Rails. - - - name: Getting Started with Engines - url: engines.html - description: This guide explains how to write a mountable engine. - work_in_progress: true - - - name: The Rails Initialization Process - work_in_progress: true - url: initialization.html - description: This guide explains the internals of the Rails initialization process as of Rails 4 -- - name: Extending Rails - documents: - - - name: The Basics of Creating Rails Plugins - work_in_progress: true - url: plugins.html - description: This guide covers how to build a plugin to extend the functionality of Rails. - - - name: Rails on Rack - url: rails_on_rack.html - description: This guide covers Rails integration with Rack and interfacing with other Rack components. - - - name: Creating and Customizing Rails Generators - url: generators.html - description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator). -- - name: Contributing to Ruby on Rails - documents: - - - name: Contributing to Ruby on Rails - url: contributing_to_ruby_on_rails.html - description: Rails is not 'somebody else's framework.' This guide covers a variety of ways that you can get involved in the ongoing development of Rails. - - - name: API Documentation Guidelines - url: api_documentation_guidelines.html - description: This guide documents the Ruby on Rails API documentation guidelines. - - - name: Ruby on Rails Guides Guidelines - url: ruby_on_rails_guides_guidelines.html - description: This guide documents the Ruby on Rails guides guidelines. -- - name: Maintenance Policy - documents: - - - name: Maintenance Policy - url: maintenance_policy.html - description: What versions of Ruby on Rails are currently supported, and when to expect new versions. -- - name: Release Notes - documents: - - - name: Upgrading Ruby on Rails - url: upgrading_ruby_on_rails.html - description: This guide helps in upgrading applications to latest Ruby on Rails versions. - - - name: Ruby on Rails 4.1 Release Notes - url: 4_1_release_notes.html - description: Release notes for Rails 4.1. - - - name: Ruby on Rails 4.0 Release Notes - url: 4_0_release_notes.html - description: Release notes for Rails 4.0. - - - name: Ruby on Rails 3.2 Release Notes - url: 3_2_release_notes.html - description: Release notes for Rails 3.2. - - - name: Ruby on Rails 3.1 Release Notes - url: 3_1_release_notes.html - description: Release notes for Rails 3.1. - - - name: Ruby on Rails 3.0 Release Notes - url: 3_0_release_notes.html - description: Release notes for Rails 3.0. - - - name: Ruby on Rails 2.3 Release Notes - url: 2_3_release_notes.html - description: Release notes for Rails 2.3. - - - name: Ruby on Rails 2.2 Release Notes - url: 2_2_release_notes.html - description: Release notes for Rails 2.2. diff --git a/source/zh-CN/engines.md b/source/zh-CN/engines.md deleted file mode 100644 index 18d6d5c..0000000 --- a/source/zh-CN/engines.md +++ /dev/null @@ -1,1042 +0,0 @@ -引擎入门 -============================ - -本章节中您将学习有关引擎的知识,以及引擎如何通过简洁易用的方式为Rails应用插上飞翔的翅膀。 - -通过学习本章节,您将获得如下知识: - -* 引擎是什么 -* 如何生成一个引擎 -* 为引擎添加特性 -* 为Rails应用添加引擎 -* 给Rails中的引擎提供重载功能 - --------------------------------------------------------------------------------- - -引擎是什么? ------------------ - -引擎可以被认为是一个可以为其宿主提供函数功能的中间件。一个Rails应用可以被看作一个"超级给力"的引擎,因为`Rails::Application` 类是继承自 `Rails::Engine`的。 - - -从某种意义上说,引擎和Rails应用几乎可以说是双胞胎,差别很小。通过本章节的学习,你会发现引擎和Rails应用的结构几乎是一样的。 - - -引擎和插件也是近亲,拥有相同的`lib`目录结构,并且都是使用`rails plugin new`命令生成。不同之处在于,一个引擎对于Rails来说是一个"发育完全的插件"(使用命令行生成引擎时会加`--full`选项)。在这里我们将使用几乎包含`--full`选项所有特性的`--mountable` 来代替。本章节中"发育完全的插件"和引擎是等价的。一个引擎可以是一个插件,但一个插件不能被看作是引擎。 - -我们将创建一个叫"blorgh"的引擎。这个引擎将为其宿主提供添加主题和主题评论等功能。刚出生的"blorgh"引擎也许会显得孤单,不过用不了多久,我们将看到她和自己的小伙伴一起愉快的聊天。 - -引擎也可以离开他的应用宿主独立存在。这意味着一个应用可以通过一个路径助手获得一个`articles_path`方法,使用引擎也可以生成一个名为`articles_path`的方法,而且两者不会冲突。同理,控制器,模型,数据库表名都是属于不同命名空间的。接下来我们来讨论该如何实现。 - -你心里须清楚Rails应用是老大,引擎是老大的小弟。一个Rails应用在他的地盘里面是老大,引擎的作用只是锦上添花。 - - -可以看看下面的一些优秀引擎项目,比如[Devise](https://github.com/plataformatec/devise) ,一个为其宿主应用提供权限认证功能的引擎;[Forem](https://github.com/radar/forem), 一个提供论坛功能的引擎;[Spree](https://github.com/spree/spree),一个提供电子商务平台功能的引擎。[RefineryCMS](https://github.com/refinery/refinerycms), 一个 CMS 引擎 。 - - -最后,大部分引擎开发工作离不开James Adam,Piotr Sarnacki 等Rails核心开发成员,以及很多默默无闻付出的人们。如果你见到他们,别忘了向他们致谢! - -生成一个引擎 --------------------- - -为了生成一个引擎,你必须将生成插件命令和适当的选项配合使用。比如你要生成"blorgh"应用 ,你需要一个"mountable"引擎。那么在命令行终端你就要敲下如下代码: - -```bash -$ bin/rails plugin new blorgh --mountable -``` - -生成插件命令相关的帮助信息可以敲下面代码得到: - -```bash -$ bin/rails plugin --help -``` - -`--mountable` 选项告诉生成器你想创建一个"mountable",并且命名空间独立的引擎。如果你用选项`--full`的话,生成器几乎会做一样的操作。`--full` 选项告诉生成器你想创建一个引擎,包含如下结构: - - - * 一个 `app` 目录树 - * 一个 `config/routes.rb` 文件: - - ```ruby - Rails.application.routes.draw do - end - ``` - - * 一个`lib/blorgh/engine.rb`文件,以及在一个标准的Rails应用文件目录的`config/application.rb`中的如下声明: - - ```ruby - module Blorgh - class Engine < ::Rails::Engine - end - end - ``` - -`--mountable`选项会比`--full`选项多做的事情有: - - * 生成若干资源文件(`application.js` and `application.css`) - * 添加一个命名空间为`ApplicationController` 的子集 - * 添加一个命名空间为`ApplicationHelper` 的子集 - * 添加 一个引擎的布局视图模版 - * 在`config/routes.rb`中声明独立的命名空间 ; - - - ```ruby - Blorgh::Engine.routes.draw do - end - ``` - - 在`lib/blorgh/engine.rb`中声明独立的命名空间: - - ```ruby - module Blorgh - class Engine < ::Rails::Engine - isolate_namespace Blorgh - end - end - ``` - -除此之外,`--mountable`选项告诉生成器在引擎内部的 `test/dummy` 文件夹中创建一个简单应用,在`test/dummy/config/routes.rb`中添加简单应用的路径。 - -```ruby -mount Blorgh::Engine, at: "blorgh" -``` - -### 引擎探秘 - -#### 文件冲突 - - -在我们刚才创建的引擎根目录下有一个`blorgh.gemspec`文件。如果你想把引擎和Rails应用整合,那么接下来要做的是在目标Rails应用的`Gemfile`文件中添加如下代码: - -```ruby -gem 'blorgh', path: "vendor/engines/blorgh" -``` - -接下来别忘了运行`bundle install`命令,Bundler通过解析刚才在`Gemfile`文件中关于引擎的声明,会去解析引擎的`blorgh.gemspec`文件,以及`lib`文件夹中名为`lib/blorgh.rb`的文件,然后定义一个`Blorgh`模块: - -```ruby -require "blorgh/engine" - -module Blorgh -end -``` -提示: 某些引擎会使用一个全局配置文件来配置引擎,这的确是个好主意,所以如果你提供了一个全局配置文件来配置引擎的模块,那么这会更好的将你的模块的功能封装起来。 - -`lib/blorgh/engine.rb`文件中定义了引擎的基类。 - -```ruby -module Blorgh - class Engine < Rails::Engine - isolate_namespace Blorgh - end -end -``` -因为引擎继承自`Rails::Engine`类,gem会通知Rails有一个引擎的特别路径,之后会正确的整合引擎到Rails应用中。会为Rails应用中的模型,控制器,视图和邮件等配置加载引擎的`app`目录路径。 - -`isolate_namespace`方法必须拿出来单独谈谈。这个方法会把引擎模块中与控制器,模型,路径等模块内的同名组件隔离。如果没它的话,可能会把引擎的内部方法暴露给其它模块,这样会破坏引擎的封装性,可能会引发不可预期的风险,比如引擎的内部方法被其他模块重载。举个例子,如果没有用命名空间对模块进行隔离,各模块的helpers方法会发生冲突,那么引擎内部的helper方法会被Rails应用的控制器所调用。 - - -提示:强烈建议您使用`isolate_namespace`方法定义引擎的模块,如果没使用它,这可能会在一个Rails应用中和其它模块冲突。 - -命名空间对于执行像`bin/rails g model`的命令意味者什么呢? 比如`bin/rails g model article`,这个操作不会产生一个`Article`,而是`Blorgh::Article`。此外,模型的数据库表名也是命名空间化的,会用`blorgh_articles` 代替`articles`。与模型的命名空间类似,控制器中的 `ArticlesController`会被`Blorgh::ArticlesController`取代。而且和控制器相关的视图也会从`app/views/articles`变成`app/views/blorgh/articles`,邮件模块也是如此。 - -总而言之,路径同引擎一样也是有命名空间的,命名空间的重要性将会在本指南中的[Routes](#routes)继续讨论。 - - -#### `app` 目录 - -`app`内部的结构和一般的Rails应用差不多,都包含 `assets`, `controllers`, `helpers`, -`mailers`, `models` and `views` 等文件。`helpers`, `mailers` and `models` 文件夹是空的,我们就不详谈了。我们将会在将来的章节中讨论引擎的模型的时候,深入介绍。 - -`app/assets`文件夹包含`images`, `javascripts`和`stylesheets`,这些你在一个Rails应用中应该很熟悉了。不同在于,它们每个文件夹下包含一个和引擎同名的子目录,因为引擎是命名空间化的,那么assets也会遵循这一规定 。 - -`app/controllers`文件夹下有一个`blorgh`文件夹,他包含一个名为`application_controller.rb`的文件。这个文件为引擎提供控制器的一般功能。`blorgh`文件夹是专属于`blorgh`引擎的,通过命名空间化的目录结构,可以很好的将引擎的控制器与外部隔离起来,免受其它引擎或Rails应用的影响。 - -提示:在引擎内部的`ApplicationController`类命名方式和Rails 应用类似是为了方便你将Rails应用和引擎整合。 - -最后,`app/views` 文件夹包含一个`layouts`文件。他包含一个`blorgh/application.html.erb`文件。这个文件可以为你的引擎定制视图。如果这个引擎被当作独立的组件使用,那么你可以通过这个视图文件来定制引擎的视图,就和Rails应用中的`app/views/layouts/application.html.erb`一样、 - -如果你不希望强制引擎的使用者使用你的布局样式,那么可以删除这个文件,使用其他控制器的视图文件。 - -#### `bin` 目录 - -这个目录包含了一个`bin/rails`文件,它为你像在Rails应用中使用`rails` 等命令提供了支持,比如为该引擎生成模型和视图等操作: - -```bash -$ bin/rails g model -``` - -必须要注意的是,在引擎内部使用命令行工具生成的组件都会自动调用 `isolate_namespace`方法,以达到组件命名空间化的目的。 - -#### `test`目录 - -`test`目录是引擎执行测试的地方,为了方便测试,`test/dummy`内置了一个精简版本的Rails 应用,这个应用可以和引擎整合,方便测试,他在`test/dummy/config/routes.rb` 中的声明如下: - -```ruby -Rails.application.routes.draw do - mount Blorgh::Engine => "/blorgh" -end -``` - -mounts这行的意思是Rails应用只能通过`/blorgh`路径来访问引擎。 - -在测试目录下面有一个`test/integration`子目录,该子目录是为了实现引擎的的交互测试而存在的。其它的目录也可以如此创建。举个例子,你想为你的模型创建一个测试目录,那么他的文件结构和`test/models`是一样的。 - -引擎功能简介 ------------------------------- - - -本章中创建的引擎需要提供发布主题, 主题评论,关注[Getting Started -Guide](getting_started.html)某人是否有新主题发布等功能。 - -### 生成一个Article 资源 - - -一个博客引擎首先要做的是生成一个`Article` 模型和相关的控制器。为了快速生成这些,你可以使用Rails的generator和 scaffold命令来实现: - -```bash -$ bin/rails generate scaffold article title:string text:text -``` - -这个命令执行后会得到如下输出: - -``` -invoke active_record -create db/migrate/[timestamp]_create_blorgh_articles.rb -create app/models/blorgh/article.rb -invoke test_unit -create test/models/blorgh/article_test.rb -create test/fixtures/blorgh/articles.yml -invoke resource_route - route resources :articles -invoke scaffold_controller -create app/controllers/blorgh/articles_controller.rb -invoke erb -create app/views/blorgh/articles -create app/views/blorgh/articles/index.html.erb -create app/views/blorgh/articles/edit.html.erb -create app/views/blorgh/articles/show.html.erb -create app/views/blorgh/articles/new.html.erb -create app/views/blorgh/articles/_form.html.erb -invoke test_unit -create test/controllers/blorgh/articles_controller_test.rb -invoke helper -create app/helpers/blorgh/articles_helper.rb -invoke test_unit -create test/helpers/blorgh/articles_helper_test.rb -invoke assets -invoke js -create app/assets/javascripts/blorgh/articles.js -invoke css -create app/assets/stylesheets/blorgh/articles.css -invoke css -create app/assets/stylesheets/scaffold.css -``` - -scaffold生成器做的第一件事情是执行生成`active_record`操作,这将会为资源生成一个模型和迁移集,这里要注意的是,生成的迁移集的名字是 `create_blorgh_articles`而非Rails应用中`create_articles`。这归功于`Blorgh::Engine`类中`isolate_namespace`方法。这里的模型也是命名空间化的,本来应该是`app/models/article.rb`,现在被 `app/models/blorgh/article.rb`取代。 - -接下来,模型的单元测试`test_unit`生成器会生成一个测试文件`test/models/blorgh/article_test.rb`(有别于`test/models/article_test.rb`),和一个fixture`test/fixtures/blorgh/articles.yml`文件 - - -接下来,该资源作为引擎的一部分会被插入`config/routes.rb`中。该引擎的资源`resources :articles`在`config/routes.rb`的声明如下: - -```ruby -Blorgh::Engine.routes.draw do - resources :articles -end -``` - -这里需要注意的是该资源的路径已经和引擎`Blorgh::Engine` 关联上了,就像普通的`YourApp::Application`一样。这样访问引擎的资源路径就被限制在特定的范围。可以提供给[test directory](#test-directory)访问。这样也可以让引擎的资源与Rails应用隔离开来。具体的详情亏参考[Routes](#routes)。 - -接下来,`scaffold_controller`生成器被触发了,生成一个名为`Blorgh::ArticlesController`的控制器(`app/controllers/blorgh/articles_controller.rb`),以及和控制器相关的视图`app/views/blorgh/articles`。这个生成器同时也会自动为控制器生成一个测试用例(`test/controllers/blorgh/articles_controller_test.rb`)和帮助方法(`app/helpers/blorgh/articles_controller.rb`)。 - -生成器创建的所有对象几乎都是命名空间化的,控制器的类被定义在`Blorgh`模块中: - -```ruby -module Blorgh - class ArticlesController < ApplicationController - ... - end -end -``` - -提示:`Blorgh::ApplicationController`类继承了`ApplicationController`类,而非Rails应用的`ApplicationController`类。 - -`app/helpers/blorgh/articles_helper.rb`中的helper模块也是命名空间化的: -```ruby -module Blorgh - module ArticlesHelper - ... - end -end -``` -这样有助于避免和其它引擎或应用的同名资源发生冲突。 - -最后,生成该资源相关的样式表和js脚本文件,文件路径分别是`app/assets/javascripts/blorgh/articles.js` 和 -`app/assets/stylesheets/blorgh/articles.css`。稍后你将了解如何使用它们。 - -一般情况下,基本的样式表并不会应用到引擎中,因为引擎的布局文件`app/views/layouts/blorgh/application.html.erb`并没载入。如果要让基本的样式表文件对引擎生效。必须在``标签内插入如下代码: - -```erb -<%= stylesheet_link_tag "scaffold" %> -``` - -现在,你已经了解了在引擎根目录下使用 scaffold 生成器进行数据库创建和迁移的整个过程,接下来,在`test/dummy`目录下运行`rails server` 后,用浏览器打开`http://localhost:3000/blorgh/articles` 后,随便浏览一下,刚才你生成的第一个引擎的功能。 - - -如果你喜欢在控制台工作,那么`rails console`就像一个Rails应用。记住:`Article`是命名空间化的,所以你必须使用`Blorgh::Article`来访问它。 - - -```ruby ->> Blorgh::Article.find(1) -=> # -``` - -最后要做的一件事是让`articles`资源通过引擎的根目录就能访问。比如我打开`http://localhost:3000/blorgh`后,就能看到一个博客的主题列表。要实现这个目的,我们可以在引擎的`config/routes.rb`中做如下配置: - - -```ruby -root to: "articles#index" -``` - -现在人们不需要到引擎的`/articles`目录下浏览主题了,这意味着`http://localhost:3000/blorgh`获得的内容和`http://localhost:3000/blorgh/articles`是相同的。 - -### 生成评论资源 - -现在,这个引擎可以创建一个新主题,那么自然需要能够评论的功能。为了实现这个功能,你需要生成一个评论模型,以及和评论相关的控制器,并修改主题的结构用以显示评论和添加评论。 - -在Rails应用的根目录下,运行模型生成器,生成一个`Comment`模型,相关的表包含下面两个字段:整型 `article_id`和文本`text`。 - -```bash -$ bin/rails generate model Comment article_id:integer text:text -``` - -上述操作将会输出下面的信息: - -``` -invoke active_record -create db/migrate/[timestamp]_create_blorgh_comments.rb -create app/models/blorgh/comment.rb -invoke test_unit -create test/models/blorgh/comment_test.rb -create test/fixtures/blorgh/comments.yml -``` - -生成器会生成必要的模型文件,由于是命名空间化的,所以会在`blorgh`目录下生成`Blorgh::Comment`类。然后使用数据迁移命令对blorgh_comments表进行操作: - -```bash -$ rake db:migrate -``` - -为了在主题中显示评论,需要在`app/views/blorgh/articles/show.html.erb`的 "Edit" 按钮之前添加如下代码: - -```html+erb -

Comments

-<%= render @article.comments %> -``` - -上述代码需要为评论在`Blorgh::Article`模型中添加一个"一对多"(`has_many`)的关联声明。为了添加上述声明,请打开`app/models/blorgh/article.rb`,并添加如下代码: - -```ruby -has_many :comments -``` - -修改过的模型关系是这样的: - -```ruby -module Blorgh - class Article < ActiveRecord::Base - has_many :comments - end -end -``` - -提示: 因为 `一对多`(`has_many`) 的关联是在`Blorgh` 内部定义的,Rails明白你想为这些对象使用`Blorgh::Comment`模型。所以不需要特别使用类名来声明。 - -接下来,我们需要为主题提供一个表单提交评论,为了实现这个功能,请在 `app/views/blorgh/articles/show.html.erb` 中调用 `render @article.comments` 方法来显示表单: - -```erb -<%= render "blorgh/comments/form" %> -``` - -接下来,上述代码中的表单必须存在才能被渲染,我们需要做的就是在`app/views/blorgh/comments`目录下创建一个`_form.html.erb`文件: - -```html+erb -

New comment

-<%= form_for [@article, @article.comments.build] do |f| %> -

- <%= f.label :text %>
- <%= f.text_area :text %> -

- <%= f.submit %> -<% end %> -``` - -当表单被提交后,它将通过路径`/articles/:article_id/comments`给引擎发送一个`POST`请求。现在这个路径还不存在,所以我们可以修改`config/routes.rb`中的`resources :articles`的相关路径来实现它: - -```ruby -resources :articles do - resources :comments -end -``` - -给表单请求创建一个和评论相关的嵌套路径。 - -现在路径创建好了,相关的控制器却不存在,为了创建它们,我们使用命令行工具来创建它们: - -```bash -$ bin/rails g controller comments -``` - -执行上述操作后,会输出下面的信息: - -``` -create app/controllers/blorgh/comments_controller.rb -invoke erb - exist app/views/blorgh/comments -invoke test_unit -create test/controllers/blorgh/comments_controller_test.rb -invoke helper -create app/helpers/blorgh/comments_helper.rb -invoke test_unit -create test/helpers/blorgh/comments_helper_test.rb -invoke assets -invoke js -create app/assets/javascripts/blorgh/comments.js -invoke css -create app/assets/stylesheets/blorgh/comments.css -``` - -表单通过路径`/articles/:article_id/comments`提交`POST`请求后,`Blorgh::CommentsController`会响应一个`create`动作。 -这个的动作在`app/controllers/blorgh/comments_controller.rb`的定义如下: - -```ruby -def create - @article = Article.find(params[:article_id]) - @comment = @article.comments.create(comment_params) - flash[:notice] = "Comment has been created!" - redirect_to articles_path -end - -private - def comment_params - params.require(:comment).permit(:text) - end -``` - -最后,我们希望在浏览主题时显示和主题相关的评论,但是如果你现在想提交一条评论,会发现遇到如下错误:  - -``` -Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], -:formats=>[:html], :locale=>[:en, :en]}. Searched in: * -"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" * -"/Users/ryan/Sites/side_projects/blorgh/app/views" -``` - -显示上述错误是因为引擎无法知道和评论相关的内容。Rails 应用会首先去该应用的(`test/dummy`) `app/views`目录搜索,之后才会到引擎的`app/views` 目录下搜索匹配的内容。当找不到匹配的内容时,会抛出异常。引擎知道去`blorgh/comments/comment`目录下搜索,是因为模型对象是从`Blorgh::Comment`接收到请求的。 - - -现在,为了显示评论,我们需要创建一个新文件 `app/views/blorgh/comments/_comment.html.erb`,并在该文件中添加如下代码: - -```erb -<%= comment_counter + 1 %>. <%= comment.text %> -``` - -本地变量 `comment_counter`是通过`<%= render @article.comments %>`获取的。这个变量是评论计数器,用来显示评论总数。 - -现在,我们完成一个带评论功能的博客引擎后,接下来我们将介绍如何将引擎与Rails应用整合。 - -和Rails应用整合 ---------------------------- - -在Rails应用中可以很方便的使用引擎,本节将介绍如何将引擎和Rails应用整合。当然通常会把引擎和Rails应中的`User`类关联起来。 - - -### 整合前的准备工作 - -首先,引擎需要在一个Rails应用中的`Gemfile`进行声明。如果我们无法知道Rails应用中是否有这些声明,那么我们可以在引擎目录之外创建一个新的Rails应用: - -```bash -$ rails new unicorn -``` - -一般而言,在Gemfile声明引擎和在Rails应用的一般Gem声明没有区别: - -```ruby -gem 'devise' -``` - -但是,假如你在自己的本地机器上开发`blorgh`引擎,那么你需要在`Gemfile`中特别声明`:path`项: - -```ruby -gem 'blorgh', path: "/path/to/blorgh" -``` - -运行`bundle`命令,安装gem 。 - -如前所述,在`Gemfile`中声明的gem将会与Rails框架一起加载。应用会从引擎中加载 `lib/blorgh.rb`和`lib/blorgh/engine.rb`等与引擎相关的主要文件。 - -为了在Rails应用内部调用引擎,我们必须在Rails应用的`config/routes.rb`中做如下声明: - -```ruby -mount Blorgh::Engine, at: "/blog" -``` -上述代码的意思是引擎将被整合到Rails应用中的"/blog"下。当Rails应用通过 `rails server`启动时,可通过`http://localhost:3000/blog`访问。 - -提示: 对于其他引擎,比如 `Devise` ,它在处理路径的方式上稍有不同,可以通过自定义的助手方法比如`devise_for`来处理路径。这些路径助理方法工作千篇一律,为引擎大部分功能提供预定义路径的个性化支持。 - -### 建立引擎 - -和引擎相关的两个`blorgh_articles` 和 `blorgh_comments`表需要迁移到Rails应用数据库中,以保证引擎的模型能正确查询。迁移引擎的数据可以使用下面的命令: - -```bash -$ rake blorgh:install:migrations -``` - -如果你有多个引擎需要数据迁移,可以使用`railties:install:migrations`命令来实现: - -```bash -$ rake railties:install:migrations -``` - -第一次运行上述命令的时候,将会从引擎中复制所有的迁移集。当下次运行的时候,他只会迁移没被迁移过的数据。第一次运行该命令会显示如下信息: - -```bash -Copied migration [timestamp_1]_create_blorgh_articles.rb from blorgh -Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh -``` - -第一个时间戳(`[timestamp_1]`)将会是当前时间,接着第二个时间戳(`[timestamp_2]`) 将会是当前时间+1妙。这样做的原因是之前已经为引擎做过数据迁移操作。 - -在Rails应用中为引擎做数据迁移可以简单的使用`rake db:migrate` 执行操作。当通过`http://localhost:3000/blog`访问引擎的时候,你会发现主题列表是空的。这是因为在应用中创建的表与在引擎中创建的表是不同的。接下来你将发现应用中的引擎和独立环境中的引擎有很多不同之处。 - -如果你只想对某一个引擎执行数据迁移操作,那么可以通过`SCOPE`声明来实现: - -```bash -rake db:migrate SCOPE=blorgh -``` - -这将有利于你的引擎执行数据迁移的回滚操作。 -如果想让引擎的数据回到原始状态,那么可以执行下面的操作: - -```bash -rake db:migrate SCOPE=blorgh VERSION=0 -``` - -### 访问Rails应用中的类 - -#### 访问Rails应用中的模型 - -当一个引擎创建之后,那么就需要Rails应用提供一个专属的类,将引擎和Rails应用关联起来。在本例中,`blorgh`引擎需要Rails应用提供作者来发表主题和评论。 - -一个典型的Rails应用会有一个`User`类来实现发布主题和评论的功能。也许某些应用里面会用`Person`类来做这些事情。因此,引擎不应该硬编码到一个`User`类中。 - -为了简单起见,我们的应用将会使用`User`类来实现和引擎的关联。那么我们可以在应用中使用命令: - -```bash -rails g model user name:string -``` - -在这里执行`rake db:migrate`命令是为了我们的应用中有`users`表,以备将来使用。 - -为了简单起见,主题表单也会添加一个新的字段`author_name`,这样方便用户填写他们的名字。 -当用户提交了他们的名字后,引擎将会判断是否存在该用户,如果不存在,就将该用户添加到数据库里面,并通过`User`对象把该用户和主题关联起来。 - -首先需要在引擎内部的`app/views/blorgh/articles/_form.html.erb`文件中添加 -`author_name`项。这些内容可以添加到`title`之前,代码如下: - -```html+erb -
- <%= f.label :author_name %>
- <%= f.text_field :author_name %> -
-``` - -接下来我们需要更新`Blorgh::ArticleController#article_params`方法接受参数的格式: - -```ruby -def article_params - params.require(:article).permit(:title, :text, :author_name) -end -``` - -模型`Blorgh::Article`需要添加一些代码把`author_name`和`User`对象关联起来。以确保保存主题时,主题相关的 `author`也被同时保存了。同时我们需要为这个字段定义一个`attr_accessor`。以方便我们读取或设置它的属性。 - - -上述工作完成后,你需要为`author_name`添加一个属性读写器(`attr_accessor`),调用在`app/models/blorgh/article.rb`的`before_save`方法以便关联。`author` 将会通过硬编码的方式和`User`关联: - -```ruby -attr_accessor :author_name -belongs_to :author, class_name: "User" - -before_save :set_author - -private - def set_author - self.author = User.find_or_create_by(name: author_name) - end -``` - -和`author`关联的`User`类,成了引擎和Rails应用之间联系的纽带。与此同时,还需要把`blorgh_articles`和 `users` 表进行关联。因为通过`author`关联,那么需要给`blorgh_articles`表添加一个`author_id`字段来实现关联。 - - -为了生成这个新字段,我们需要在引擎中执行如下操作: - -```bash -$ bin/rails g migration add_author_id_to_blorgh_articles author_id:integer -``` - -提示:假如数据迁移命令后面跟了一个字段声明。那么Rails会认为你想添加一个新字段到声明的表中,而无需做其他操作。 - - -这个数据迁移操作必须在Rails应用中执行,为此,你必须保证是第一次在命令行中执行下面的操作: - - -```bash -$ rake blorgh:install:migrations -``` - -需要注意的是,这里只会发生一次数据迁移,这是因为前两个数据迁移拷贝已经执行过迁移操作了。 - -``` -NOTE Migration [timestamp]_create_blorgh_articles.rb from blorgh has been -skipped. Migration with the same name already exists. NOTE Migration -[timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration -with the same name already exists. Copied migration -[timestamp]_add_author_id_to_blorgh_articles.rb from blorgh -``` - -运行数据迁移命令: - - -```bash -$ rake db:migrate -``` - -现在所有准备工作都就绪了。上述操作实现了Rails应用中的`User`表和作者关联,引擎中的`blorgh_articles`表和主题关联。 - -最后,主题的作者将会显示在主题页面。在`app/views/blorgh/articles/show.html.erb`文件中的`Title`之前添加如下代码: - -```html+erb -

- Author: - <%= @article.author %> -

-``` - -使用`<%=` 标签和`to_s`方法将会输出`@article.author`。默认情况下,这看上去很丑: - -``` -# -``` - -这不是我们希望看到的,所以最好显示用户的名字。为此,我去需要给Rails应用中的`User`类添加`to_s`方法: - -```ruby -def to_s - name -end -``` - -现在,我们将看到主题的作者名字 。 - -#### 与控制器交互 - -Rails应用的控制器一般都会和权限控制,会话变量访问模块共享代码,因为它们都是默认继承自 - `ApplicationController`类。Rails的引擎因为是命名空间化的,和主应用独立的模块。所以每个引擎都会有自己的`ApplicationController`类。这样做有利于避免代码冲突,但很多时候,引擎控制器需要调用主应用的`ApplicationController`。这里有一个简单的方法是让引擎的控制器继承主应用的`ApplicationController`。我们的Blorgh引擎会在`app/controllers/blorgh/application_controller.rb`中实现上述操作: - - -```ruby -class Blorgh::ApplicationController < ApplicationController -end -``` - -一般情况下,引擎的控制器是继承自`Blorgh::ApplicationController`,所以,做了上述改变后,引擎可以访问主应用的`ApplicationController`了,也就是说,它变成了主应用的一部分。 - -上述操作的一个必要条件是:和引擎相关的Rails应用必须包含一个`ApplicationController`类。 - -### 配置引擎 - - -本章节将介绍如何让`User`类可配置化。下面我们将介绍配置引擎的细节。 - -#### 配置应用的配置文件 - -接下来的内容我们将讲述如何让应用中诸如`User`的类对象为引擎提供定制化的服务。如前所述,引擎要访问应用中的类不一定每次都叫`User`,所以我来实现可定制化的访问,必须在引擎里面设置一个名为`author_class`和应用中的`User`类进行交互。 - -为了定义这个设置,你将在引擎的`Blorgh` 模块中声明一个`mattr_accessor`方法和`author_class`关联。在引擎中的`lib/blorgh.rb`代码如下: - - -```ruby -mattr_accessor :author_class -``` - -这个方法的功能和它的兄弟`attr_accessor`和`cattr_accessor`功能类似,但是特别提供了一个方法,可以根据指定名字来对类或模块访问。我们使用它的时候,必须加上`Blorgh.author_class`前缀。 - - -接下来要做的是通过新的设置器来选择`Blorgh::Article`的模型,将模型关联`belongs_to`(`app/models/blorgh/article.rb`)修改如下: - -```ruby -belongs_to :author, class_name: Blorgh.author_class -``` - -模型`Blorgh::Article`中的`set_author`方法也可以使用这个类: - -```ruby -self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name) -``` - -为了确保`author_class`调用`constantize`的结果一致,你需要重载`lib/blorgh.rb`中`Blorgh` 模块的`author_class`的get方法,确保在获取返回值之前调用`constantize`方法: -```ruby -def self.author_class - @@author_class.constantize -end -``` - -上述代码将会让`set_author` 方法变成这样: - -```ruby -self.author = Blorgh.author_class.find_or_create_by(name: author_name) -``` - -总之,这样会更明确它的行为,`author_class`方法会保证返回一个`Class`对象。 - - -我们让`author_class`方法返回一个`Class`替代`String`后,我们也必须修改`Blorgh::Article`模块中的`belongs_to`定义: - -```ruby -belongs_to :author, class_name: Blorgh.author_class.to_s -``` - -为了让这些配置在应用中生效,必须使用一个初始化器。使用初始化器可以保证这种配置在Rails应用调用引擎模块之前就生效,因为应用和引擎交互时也许需要用到某些配置。 - -在应用中的`config/initializers/blorgh.rb`添加一个新的初始化器,并添加如下代码: - -```ruby -Blorgh.author_class = "User" -``` - -警告:使用`String`版本的类对象要比使用类对象本身更好。如果你使用类对象,Rails会尝试加载和类相关的数据库表。如果这个表不存在,就会抛出异常。所以,稍后在引擎中最好使用`String`类型,并且把类用`constantize`方法转换一下。 - -接下来我们创建一个新主题,除了让引擎读取`config/initializers/blorgh.rb`中的类信息之外,你将发现它和之前没什么区别, - - -这里对类没有严格的定义,只是提供了一个类必须做什么的指导。引擎也只是调用`find_or_create_by`方法来获取符合条件的类对象。当然这个对象也可以被其他对象引用。 - -#### 配置引擎 - - -在引擎内部,有很多配置引擎的方法,比如initializers, internationalization和其他配置项。一个Rails引擎和一个Rails应用具有很多相同的功能。实际上一个Rails应用就是一个超级引擎。 - -如果你想使用一个初始化器,必须在引擎载入之前使用,配置文件在`config/initializers` 目录下。这个目录的详细使用说明在[Initializers section](configuring.html#initializers)中,它和一个应用中的`config/initializers`文件相对目录是一致的。可以把它当作一个Rails应用中的初始化器来配置。 - -关于本地文件,和一个应用中的目录类似,都在`config/locales`目录下。 - - -引擎测试 ------------------ - -生成一个引擎后,引擎内部的`test/dummy`目录下会生成一个简单的Rails应用。这个应用被用来给引擎提供集成测试环境。你可以扩展这个应用的功能来测试你的引擎。 - -`test`目录将会被当作一个典型的Rails测试环境,允许单元测试,功能测试和交互测试。 - -### 功能测试 - -在编写引擎的功能测试时,我们会假定这个引擎会在一个应用中使用。`test/dummy`目录中的应用和你引擎结构差不多。这是因为建立测试环境后,引擎需要一个宿主来测试它的功能,特别是控制器。这意味着你需要在一个控制器功能测试函数中下如下代码: - -```ruby -get :index -``` - -这似乎不能称为函数,因为这个应用不知道如何给引擎发送的请求做响应,除非你明确告诉他怎么做。为此,你必须在请求的参数中加上`:use_route`选项来声明: - -```ruby -get :index, use_route: :blorgh -``` - -上述代码会告诉Rails应用你想让它的控制器响应一个`GET`请求,并执行`index`动作,但是你最好使用引擎的路径来代替。 - -另外一种方法是在你的测试总建立一个setup方法,把`Engine.routes`赋值给变量`@routes` 。 - -```ruby -setup do - @routes = Engine.routes -end -``` - -上诉操作也同时保证了引擎的url助手方法在你的测试中正常使用。 - -引擎优化 ------------------------------- - -本章节将介绍在Rails应用中如何添加或重载引擎的MVC功能。 - -### 重载模型和控制器 - -应用中的公共类可以扩展引擎的模型和控制器的功能。(因为模型和控制器类都继承了Rails应用的特定功能)应用中的公共类和引擎只是对模型和控制器根据需要进行了扩展。这种模式通常被称为装饰模式。 - -举个例子,`ActiveSupport::Concern`类使用`Class#class_eval`方法扩展了他的功能。 - -#### 装饰器的特点以及加载代码 - -因为装饰器不是引用Rails应用本身,Rails自动载入系统不会识别和载入你的装饰器。这意味着你需要用代码声明他们。 - -这是一个简单的例子: - -```ruby -# lib/blorgh/engine.rb -module Blorgh - class Engine < ::Rails::Engine - isolate_namespace Blorgh - - config.to_prepare do - Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| - require_dependency(c) - end - end - end -end -``` -上述操作不会应用到当前的装饰器,但是在引擎中添加的内容不会影响你的应用。 - -#### 使用 Class#class_eval 方法实现装饰模式 - -**添加** `Article#time_since_created`方法: - -```ruby -# MyApp/app/decorators/models/blorgh/article_decorator.rb - -Blorgh::Article.class_eval do - def time_since_created - Time.current - created_at - end -end -``` - -```ruby -# Blorgh/app/models/article.rb - -class Article < ActiveRecord::Base - has_many :comments -end -``` - - -**重载** `Article#summary`方法: - -```ruby -# MyApp/app/decorators/models/blorgh/article_decorator.rb - -Blorgh::Article.class_eval do - def summary - "#{title} - #{truncate(text)}" - end -end -``` - -```ruby -# Blorgh/app/models/article.rb - -class Article < ActiveRecord::Base - has_many :comments - def summary - "#{title}" - end -end -``` - -#### 使用ActiveSupport::Concern类实现装饰模式 - -使用`Class#class_eval`方法可以应付一些简单的修改。但是如果要实现更复杂的操作,你可以考虑使用[`ActiveSupport::Concern`](http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html)。`ActiveSupport::Concern`管理着所有独立模块的内部链接指令,并且允许你在运行时声明模块代码。 - - -**添加** `Article#time_since_created`方法和**重载** `Article#summary`方法: - -```ruby -# MyApp/app/models/blorgh/article.rb - -class Blorgh::Article < ActiveRecord::Base - include Blorgh::Concerns::Models::Article - - def time_since_created - Time.current - created_at - end - - def summary - "#{title} - #{truncate(text)}" - end -end -``` - -```ruby -# Blorgh/app/models/article.rb - -class Article < ActiveRecord::Base - include Blorgh::Concerns::Models::Article -end -``` - -```ruby -# Blorgh/lib/concerns/models/article - -module Blorgh::Concerns::Models::Article - extend ActiveSupport::Concern - - # 'included do' causes the included code to be evaluated in the - # context where it is included (article.rb), rather than being - # executed in the module's context (blorgh/concerns/models/article). - included do - attr_accessor :author_name - belongs_to :author, class_name: "User" - - before_save :set_author - - private - def set_author - self.author = User.find_or_create_by(name: author_name) - end - end - - def summary - "#{title}" - end - - module ClassMethods - def some_class_method - 'some class method string' - end - end -end -``` - -### 视图重载 - -Rails在寻找一个需要渲染的视图时,首先会去寻找应用的`app/views`目录下的文件。如果找不到,那么就会去当前应用目录下的所有引擎中找`app/views`目录下的内容。 - - -当一个应用被要求为`Blorgh::ArticlesController`的`index`动作渲染视图时,它首先会在应用目录下去找`app/views/blorgh/articles/index.html.erb`,如果找不到,它将深入引擎内部寻找。 - - -你可以在应用中创建一个新的`app/views/blorgh/articles/index.html.erb`文件来重载这个视图。接下来你会看到你改过的视图内容。 - -修改`app/views/blorgh/articles/index.html.erb`中的内容,代码如下: - -```html+erb -

Articles

-<%= link_to "New Article", new_article_path %> -<% @articles.each do |article| %> -

<%= article.title %>

- By <%= article.author %> - <%= simple_format(article.text) %> -
-<% end %> -``` - -### 路径 - -引擎中的路径默认是和Rails应用隔离开的。主要通过`Engine`类的`isolate_namespace`方法 实现的。这意味着引擎和Rails应可以拥有同名的路径,但却不会冲突。 - -引擎内部的`config/routes.rb`中的`Engine`类是这样绑定路径的: - -```ruby -Blorgh::Engine.routes.draw do - resources :articles -end -``` - -因为拥有相对独立的路径,如果你希望在应用内部链接到引擎的某个地方,你需要使用引擎的路径代理方法。如果调用普通的路径方法,比如`articles_path`等,将不会得到你希望的结果。 - -举个例子。下面的`articles_path`方法根据情况自动识别,并渲染来自应用或引擎的内容。 - -```erb -<%= link_to "Blog articles", articles_path %> -``` - -为了确保这个路径使用引擎的`articles_path`方法,我们必须使用路径代理方法来实现: - - -```erb -<%= link_to "Blog articles", blorgh.articles_path %> -``` -如果你希望在引擎内部访问Rails应用的路径,可以使用`main_app`方法: - -```erb -<%= link_to "Home", main_app.root_path %> -``` - -如果你在引擎中使用了上诉方法,那么这将一直指向Rails应用的根目录。如果你没有使用`main_app`的 -`routing proxy`路径代理调用方法,那么会根据调用源来指向引擎或Rails应用的根目录。 - - -如果你引擎内的模板渲染想调用一个应用的路径帮助方法,这可能导致一个未定义的方法调用异常。如果你想解决这个问题,必须确保在引擎内部调用Rails应用的路径帮助方法时加上`main_app`前缀。 - - -### 渲染页面相关的Assets文件 - -引擎内部的Assets文件位置和Rails应用的的相似。因为引擎类是继承自`Rails::Engine`的。应用会自动去引擎的`aapp/assets`和`lib/assets`目录搜索和页面渲染相关的文件。 - - -像其他引擎组件一样,assets文件是可以命名空间化的。这意味着如果你有一个名为`style.css`的话,那么他的存放路径是`app/assets/stylesheets/[engine name]/style.css`, 而非 -`app/assets/stylesheets/style.css`. 如果资源文件没有命名空间化,很有可能引擎的宿主中有一个和引擎同名的资源文件,这就会导致引擎相关的资源文件被忽略或覆盖。 - -假如你想在应用的中引用一个名为`app/assets/stylesheets/blorgh/style.css`文件, ,只需要使用`stylesheet_link_tag`就可以了: - -```erb -<%= stylesheet_link_tag "blorgh/style.css" %> -``` - -你也可以在Asset Pipeline中声明你的资源文件是独立于其他资源文件的: - -``` -/* - *= require blorgh/style -*/ -``` - -提示: 如果你使用的是Sass或CoffeeScript语言,那么需要在你的引擎的`.gemspec`文件中设定相对路径。 - -### 页面资源文件分组和预编译 - -在某些情况下,你的引擎内部用到的资源文件,在Rails应用宿主中是不会用到的。举个例子,你为引擎创建了一个管理页面,它只在引擎内部使用,在这种情况下,Rails应用宿主并不需要用到`admin.css` 和`admin.js`文件,只是gem内部的管理页面需要用到它们。那么应用宿主就没必要添加`"blorgh/admin.css"`到他的样式表文件中 -,这种情况下,你可以预编译这些文件。这会在你的引擎内部添加一个`rake assets:precompile`任务。 - -你可以在引擎的`engine.rb`中定义需要预编译的资源文件: - -```ruby -initializer "blorgh.assets.precompile" do |app| - app.config.assets.precompile += %w(admin.css admin.js) -end -``` - -想要了解更多详情,可以参考 [Asset Pipeline guide](asset_pipeline.html) - -### 其他Gem依赖项 - -一个引擎的相关依赖项会在引擎的根目录下的`.gemspec`中声明。因为引擎也许会被当作一个gem安装到Rails应用中。如果在`Gemfile`中声明依赖项,那么这些依赖项就会被认为不是一个普通Gem,所以他们不会被安装,这会导致引擎发生故障。 - - -为了让引擎被当作一个普通的Gem安装,需要声明他的依赖项已经安装过了。那么可以在引擎根目录下的`.gemspec`文件中添加`Gem::Specification`配置项: - -```ruby -s.add_dependency "moo" -``` - -声明一个依赖项只作为开发应用时的依赖项,可以这么做: - - -```ruby -s.add_development_dependency "moo" -``` -所有的依赖项都会在执行`bundle install`命令时安装。gem开发环境的依赖项仅会在测试时用到。 - -注意,如果你希望引擎引用依赖项时马上引用。你应该在引擎初始化时就引用它们,比如: - -```ruby -require 'other_engine/engine' -require 'yet_another_engine/engine' - -module MyEngine - class Engine < ::Rails::Engine - end -end -``` diff --git a/source/zh-CN/form_helpers.md b/source/zh-CN/form_helpers.md deleted file mode 100644 index 6b51754..0000000 --- a/source/zh-CN/form_helpers.md +++ /dev/null @@ -1,988 +0,0 @@ -表单帮助方法 -=========== - -表单是网页程序的基本组成部分,用于接收用户的输入。然而,由于表单中控件的名称和各种属性,使用标记语言难以编写和维护。Rails 提供了很多视图帮助方法简化表单的创建过程。因为各帮助方法的用途不一样,所以开发者在使用之前必须要知道相似帮助方法的差异。 - -读完本文,你将学到: - -* 如何创建搜索表单等不需要操作模型的普通表单; -* 如何使用针对模型的表单创建和编辑数据库中的记录; -* 如何使用各种类型的数据生成选择列表; -* 如何使用 Rails 提供用于处理日期和时间的帮助方法; -* 上传文件的表单有什么特殊之处; -* 创建操作外部资源的案例; -* 如何编写复杂的表单; - --------------------------------------------------------------------------------- - -NOTE: 本文的目的不是全面解说每个表单方法和其参数,完整的说明请阅读 [Rails API 文档](http://api.rubyonrails.org/)。 - -编写简单的表单 ------------- - -最基本的表单帮助方法是 `form_tag`。 - -```erb -<%= form_tag do %> - Form contents -<% end %> -``` - -像上面这样不传入参数时,`form_tag` 会创建一个 `
` 标签,提交表单后,向当前页面发起 POST 请求。假设当前页面是 `/home/index`,生成的 HTML 如下(为了提升可读性,添加了一些换行): - -```html - -
- - -
- Form contents -
-``` - -你会发现 HTML 中多了一个 `div` 元素,其中有两个隐藏的 `input` 元素。这个 `div` 元素很重要,没有就无法提交表单。第一个 `input` 元素的 `name` 属性值为 `utf8`,其作用是强制浏览器使用指定的编码处理表单,不管是 GET 还是 POST。第二个 `input` 元素的 `name` 属性值为 `authenticity_token`,这是 Rails 的一项安全措施,称为“跨站请求伪造保护”。`form_tag` 帮助方法会为每个非 GET 表单生成这个元素(表明启用了这项安全保护措施)。详情参阅“[Rails 安全指南](security.html#cross-site-request-forgery-csrf)”。 - -NOTE: 为了行文简洁,后续代码没有包含这个 `div` 元素。 - -### 普通的搜索表单 - -在网上见到最多的表单是搜索表单,搜索表单包含以下元素: - -* `form` 元素,`action` 属性值为 `GET`; -* 输入框的 `label` 元素; -* 文本输入框 ; -* 提交按钮; - -创建这样一个表单要分别使用帮助方法 `form_tag`、`label_tag`、`text_field_tag` 和 `submit_tag`,如下所示: - -```erb -<%= form_tag("/search", method: "get") do %> - <%= label_tag(:q, "Search for:") %> - <%= text_field_tag(:q) %> - <%= submit_tag("Search") %> -<% end %> -``` - -生成的 HTML 如下: - -```html -
-
- - - -
-``` - -TIP: 表单中的每个 `input` 元素都有 ID 属性,其值和 `name` 属性的值一样(上例中是 `q`)。ID 可用于 CSS 样式或使用 JavaScript 处理表单控件。 - -除了 `text_field_tag` 和 `submit_tag` 之外,每个 HTML 表单控件都有对应的帮助方法。 - -NOTE: 搜索表单的请求类型一定要用 GET,这样用户才能把某个搜索结果页面加入收藏夹,以便后续访问。一般来说,Rails 建议使用合适的请求方法处理表单。 - -### 调用 `form_tag` 时使用多个 Hash 参数 - -`form_tag` 方法可接受两个参数:表单提交地址和一个 Hash 选项。Hash 选项指定提交表单使用的请求方法和 HTML 选项,例如 `form` 元素的 `class` 属性。 - -和 `link_to` 方法一样,提交地址不一定非得使用字符串,也可使用一个由 URL 参数组成的 Hash,这个 Hash 经 Rails 路由转换成 URL 地址。这种情况下,`form_tag` 方法的两个参数都是 Hash,同时指定两个参数时很容易产生问题。假设写成下面这样: - -```ruby -form_tag(controller: "people", action: "search", method: "get", class: "nifty_form") -# => '
' -``` - -在这段代码中,`method` 和 `class` 会作为生成 URL 的请求参数,虽然你想传入两个 Hash,但实际上只传入了一个。所以,你要把第一个 Hash(或两个 Hash)放在一对花括号中,告诉 Ruby 哪个是哪个,写成这样: - -```ruby -form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_form") -# => '' -``` - -### 生成表单中控件的帮助方法 - -Rails 提供了很多用来生成表单中控件的帮助方法,例如复选框,文本输入框和单选框。这些基本的帮助方法都以 `_tag` 结尾,例如 `text_field_tag` 和 `check_box_tag`,生成单个 `input` 元素。这些帮助方法的第一个参数都是 `input` 元素的 `name` 属性值。提交表单后,`name` 属性的值会随表单中的数据一起传入控制器,在控制器中可通过 `params` 这个 Hash 获取各输入框中的值。例如,如果表单中包含 `<%= text_field_tag(:query) %>`,就可以在控制器中使用 `params[:query]` 获取这个输入框中的值。 - -Rails 使用特定的规则生成 `input` 的 `name` 属性值,便于提交非标量值,例如数组和 Hash,这些值也可通过 `params` 获取。 - -各帮助方法的详细用法请查阅 [API 文档](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html)。 - -#### 复选框 - -复选框是一种表单控件,给用户一些选项,可用于启用或禁用某项功能。 - -```erb -<%= check_box_tag(:pet_dog) %> -<%= label_tag(:pet_dog, "I own a dog") %> -<%= check_box_tag(:pet_cat) %> -<%= label_tag(:pet_cat, "I own a cat") %> -``` - -生成的 HTML 如下: - -```html - - - - -``` - -`check_box_tag` 方法的第一个参数是 `name` 属性的值。第二个参数是 `value` 属性的值。选中复选框后,`value` 属性的值会包含在提交的表单数据中,因此可以通过 `params` 获取。 - -#### 单选框 - -单选框有点类似复选框,但是各单选框之间是互斥的,只能选择一组中的一个: - -```erb -<%= radio_button_tag(:age, "child") %> -<%= label_tag(:age_child, "I am younger than 21") %> -<%= radio_button_tag(:age, "adult") %> -<%= label_tag(:age_adult, "I'm over 21") %> -``` - -生成的 HTML 如下: - -```html - - - - -``` - -和 `check_box_tag` 方法一样,`radio_button_tag` 方法的第二个参数也是 `value` 属性的值。因为两个单选框的 `name` 属性值一样(都是 `age`),所以用户只能选择其中一个单选框,`params[:age]` 的值不是 `"child"` 就是 `"adult"`。 - -NOTE: 复选框和单选框一定要指定 `label` 标签。`label` 标签可以为指定的选项框附加文字说明,还能增加选项框的点选范围,让用户更容易选中。 - -### 其他帮助方法 - -其他值得说明的表单控件包括:多行文本输入框,密码输入框,隐藏输入框,搜索关键字输入框,电话号码输入框,日期输入框,时间输入框,颜色输入框,日期时间输入框,本地日期时间输入框,月份输入框,星期输入框,URL 地址输入框,Email 地址输入框,数字输入框和范围输入框: - -```erb -<%= 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_field(:user, :meeting_time) %> -<%= 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) %> -``` - -生成的 HTML 如下: - -```html - - - - - - - - - - - - - - - - -``` - -用户看不到隐藏输入框,但却和其他文本类输入框一样,能保存数据。隐藏输入框中的值可以通过 JavaScript 修改。 - -NOTE: 搜索关键字输入框,电话号码输入框,日期输入框,时间输入框,颜色输入框,日期时间输入框,本地日期时间输入框,月份输入框,星期输入框,URL 地址输入框,Email 地址输入框,数字输入框和范围输入框是 HTML5 提供的控件。如果想在旧版本的浏览器中保持体验一致,需要使用 HTML5 polyfill(使用 CSS 或 JavaScript 编写)。polyfill 虽[无不足之处](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills),但现今比较流行的工具是 [Modernizr](http://www.modernizr.com/) 和 [yepnope](http://yepnopejs.com/),根据检测到的 HTML5 特性添加相应的功能。 - -TIP: 如果使用密码输入框,或许还不想把其中的值写入日志。具体做法参见“[Rails 安全指南](security.html#logging)”。 - -处理模型对象 ------------ - -### 模型对象帮助方法 - -表单的一个特别常见的用途是编辑或创建模型对象。这时可以使用 `*_tag` 帮助方法,但是太麻烦了,每个元素都要设置正确的参数名称和默认值。Rails 提供了很多帮助方法可以简化这一过程,这些帮助方法没有 `_tag` 后缀,例如 `text_field` 和 `text_area`。 - -这些帮助方法的第一个参数是实例变量的名字,第二个参数是在对象上调用的方法名(一般都是模型的属性)。Rails 会把在对象上调用方法得到的值设为控件的 `value` 属性值,并且设置相应的 `name` 属性值。如果在控制器中定义了 `@person` 实例变量,其名字为“Henry”,在表单中有以下代码: - -```erb -<%= text_field(:person, :name) %> -``` - -生成的结果如下: - -```erb - -``` - -提交表单后,用户输入的值存储在 `params[:person][:name]` 中。`params[:person]` 这个 Hash 可以传递给 `Person.new` 方法;如果 `@person` 是 `Person` 的实例,还可传递给 `@person.update`。一般来说,这些帮助方法的第二个参数是对象属性的名字,但 Rails 并不对此做强制要求,只要对象能响应 `name` 和 `name=` 方法即可。 - -WARNING: 传入的参数必须是实例变量的名字,例如 `:person` 或 `"person"`,而不是模型对象的实例本身。 - -Rails 还提供了用于显示模型对象数据验证错误的帮助方法,详情参阅“[Active Record 数据验证](active_record_validations.html#displaying-validation-errors-in-views)”一文。 - -### 把表单绑定到对象上 - -虽然上述用法很方便,但却不是最好的使用方式。如果 `Person` 有很多要编辑的属性,我们就得不断重复编写要编辑对象的名字。我们想要的是能把表单绑定到对象上的方法,`form_for` 帮助方法就是为此而生。 - -假设有个用来处理文章的控制器 `app/controllers/articles_controller.rb`: - -```ruby -def new - @article = Article.new -end -``` - -在 `new` 动作对应的视图 `app/views/articles/new.html.erb` 中可以像下面这样使用 `form_for` 方法: - -```erb -<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %> - <%= f.text_field :title %> - <%= f.text_area :body, size: "60x12" %> - <%= f.submit "Create" %> -<% end %> -``` - -有几点要注意: - -* `@article` 是要编辑的对象; -* `form_for` 方法的参数中只有一个 Hash。路由选项传入嵌套 Hash `:url` 中,HTML 选项传入嵌套 Hash `:html` 中。还可指定 `:namespace` 选项为 `form` 元素生成一个唯一的 ID 属性值。`:namespace` 选项的值会作为自动生成的 ID 的前缀。 -* `form_for` 方法会拽入一个**表单构造器**对象(`f` 变量); -* 生成表单控件的帮助方法在表单构造器对象 `f` 上调用; - -上述代码生成的 HTML 如下: - -```html - - - - -
-``` - -`form_for` 方法的第一个参数指明通过 `params` 的哪个键获取表单中的数据。在上面的例子中,第一个参数名为 `article`,因此所有控件的 `name` 属性都是 `article[attribute_name]` 这种形式。所以,在 `create` 动作中,`params[:article]` 这个 Hash 有两个键:`:title` 和 `:body`。`name` 属性的重要性参阅“[理解参数命名约定](#understanding-parameter-naming-conventions)”一节。 - -在表单构造器对象上调用帮助方法和在模型对象上调用的效果一样,唯有一点区别,无法指定编辑哪个模型对象,因为这由表单构造器负责。 - -使用 `fields_for` 帮助方法也可创建类似的绑定,但不会生成 `
` 标签。在同一表单中编辑多个模型对象时经常使用 `fields_for` 方法。例如,有个 `Person` 模型,和 `ContactDetail` 模型关联,编写如下的表单可以同时创建两个模型的对象: - -```erb -<%= form_for @person, url: {action: "create"} do |person_form| %> - <%= person_form.text_field :name %> - <%= fields_for @person.contact_detail do |contact_details_form| %> - <%= contact_details_form.text_field :phone_number %> - <% end %> -<% end %> -``` - -生成的 HTML 如下: - -```html - - - -
-``` - -`fields_for` 方法拽入的对象和 `form_for` 方法一样,都是表单构造器(其实在代码内部 `form_for` 会调用 `fields_for` 方法)。 - -### 记录辨别技术 - -用户可以直接处理程序中的 `Article` 模型,根据开发 Rails 的最佳实践,应该将其视为一个资源: - -```ruby -resources :articles -``` - -TIP: 声明资源有很多附属作用。资源的创建与使用请阅读“[Rails 路由全解](routing.html#resource-routing-the-rails-default)”一文。 - -处理 REST 资源时,使用“记录辨别”技术可以简化 `form_for` 方法的调用。简单来说,你可以只把模型实例传给 `form_for`,让 Rails 查找模型名等其他信息: - -```ruby -## Creating a new article -# long-style: -form_for(@article, url: articles_path) -# same thing, short-style (record identification gets used): -form_for(@article) - -## Editing an existing article -# long-style: -form_for(@article, url: article_path(@article), html: {method: "patch"}) -# short-style: -form_for(@article) -``` - -注意,不管记录是否存在,使用简短形式的 `form_for` 调用都很方便。记录辨别技术很智能,会调用 `record.new_record?` 方法检查是否为新记录;而且还能自动选择正确的提交地址,根据对象所属的类生成 `name` 属性的值。 - -Rails 还会自动设置 `class` 和 `id` 属性。在新建文章的表单中,`id` 和 `class` 属性的值都是 `new_article`。如果编辑 ID 为 23 的文章,表单的 `class` 为 `edit_article`,`id` 为 `edit_article_23`。为了行文简洁,后文会省略这些属性。 - -WARNING: 如果在模型中使用单表继承(single-table inheritance,简称 STI),且只有父类声明为资源,子类就不能依赖记录辨别技术,必须指定模型名,`:url` 和 `:method` 选项。 - -#### 处理命名空间 - -如果在路由中使用了命名空间,`form_for` 方法也有相应的简写形式。如果程序中有个 `admin` 命名空间,表单可以写成: - -```ruby -form_for [:admin, @article] -``` - -这个表单会提交到命名空间 `admin` 中的 `ArticlesController`(更新文章时提交到 `admin_article_path(@article)`)。如果命名空间有很多层,句法类似: - -```ruby -form_for [:admin, :management, @article] -``` - -关于 Rails 路由的详细信息以及相关的约定,请阅读“[Rails 路由全解](routing.html)”一文。 - -### 表单如何处理 PATCH,PUT 或 DELETE 请求? - -Rails 框架建议使用 REST 架构设计程序,因此除了 GET 和 POST 请求之外,还要处理 PATCH 和 DELETE 请求。但是大多数浏览器不支持从表单中提交 GET 和 POST 之外的请求。 - -为了解决这个问题,Rails 使用 POST 请求进行模拟,并在表单中加入一个名为 `_method` 的隐藏字段,其值表示真正希望使用的请求方法: - -```ruby -form_tag(search_path, method: "patch") -``` - -生成的 HTML 为: - -```html -
-
- - - -
- ... -``` - -处理提交的数据时,Rails 以 `_method` 的值为准,发起相应类型的请求(在这个例子中是 PATCH 请求)。 - -快速创建选择列表 --------------- - -HTML 中的选择列表往往需要编写很多标记语言(每个选项都要创建一个 `option` 元素),因此最适合自动生成。 - -选择列表的标记语言如下所示: - -```html - -``` - -这个列表列出了一组城市名。在程序内部只需要处理各选项的 ID,因此把各选项的 `value` 属性设为 ID。下面来看一下 Rails 为我们提供了哪些帮助方法。 - -### `select` 和 `option` 标签 - -最常见的帮助方法是 `select_tag`,如其名所示,其作用是生成 `select` 标签,其中可以包含一个由选项组成的字符串: - -```erb -<%= select_tag(:city_id, '...') %> -``` - -这只是个开始,还无法动态生成 `option` 标签。`option` 标签可以使用帮助方法 `options_for_select` 生成: - -```erb -<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %> -``` - -生成的 HTML 为: - -```html - - -... -``` - -`options_for_select` 方法的第一个参数是一个嵌套数组,每个元素都有两个子元素:选项的文本(城市名)和选项的 `value` 属性值(城市 ID)。选项的 `value` 属性值会提交到控制器中。ID 的值经常表示数据库对象,但这个例子除外。 - -知道上述用法后,就可以结合 `select_tag` 和 `options_for_select` 两个方法生成所需的完整 HTML 标记: - -```erb -<%= select_tag(:city_id, options_for_select(...)) %> -``` - -`options_for_select` 方法还可预先选中一个选项,通过第二个参数指定: - -```erb -<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %> -``` - -生成的 HTML 如下: - -```html - - -... -``` - -当 Rails 发现生成的选项 `value` 属性值和指定的值一样时,就会在这个选项中加上 `selected` 属性。 - -TIP: `options_for_select` 方法的第二个参数必须完全和需要选中的选项 `value` 属性值相等。如果 `value` 的值是整数 2,就不能传入字符串 `"2"`,必须传入数字 `2`。注意,从 `params` 中获取的值都是字符串。 - -使用 Hash 可以为选项指定任意属性: - -```erb -<%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %> -``` - -生成的 HTML 如下: - -```html - - -... -``` - -### 处理模型的选择列表 - -大多数情况下,表单的控件用于处理指定的数据库模型,正如你所期望的,Rails 为此提供了很多用于生成选择列表的帮助方法。和其他表单帮助方法一样,处理模型时要去掉 `select_tag` 中的 `_tag`: - -```ruby -# controller: -@person = Person.new(city_id: 2) -``` - -```erb -# view: -<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> -``` - -注意,第三个参数,选项数组,和传入 `options_for_select` 方法的参数一样。这种帮助方法的一个好处是,无需关心如何预先选中正确的城市,只要用户设置了所在城市,Rails 就会读取 `@person.city_id` 的值,为你代劳。 - -和其他帮助方法一样,如果要在绑定到 `@person` 对象上的表单构造器上使用 `select` 方法,相应的句法为: - -```erb -# select on a form builder -<%= f.select(:city_id, ...) %> -``` - -`select` 帮助方法还可接受一个代码块: - -```erb -<%= f.select(:city_id) do %> - <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%> - <%= content_tag(:option, c.first, value: c.last) %> - <% end %> -<% end %> -``` - -WARNING: 如果使用 `select` 方法(或类似的帮助方法,例如 `collection_select` 和 `select_tag`)处理 `belongs_to` 关联,必须传入外键名(在上例中是 `city_id`),而不是关联名。如果传入的是 `city` 而不是 `city_id`,把 `params` 传给 `Person.new` 或 `update` 方法时,会抛出异常:` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)`。这个要求还可以这么理解,表单帮助方法只能编辑模型的属性。此外还要知道,允许用户直接编辑外键具有潜在地安全隐患。 - -### 根据任意对象组成的集合创建 `option` 标签 - -使用 `options_for_select` 方法生成 `option` 标签必须使用数组指定各选项的文本和值。如果有个 `City` 模型,想根据模型实例组成的集合生成 `option` 标签应该怎么做呢?一种方法是遍历集合,创建一个嵌套数组: - -```erb -<% cities_array = City.all.map { |city| [city.name, city.id] } %> -<%= options_for_select(cities_array) %> -``` - -这种方法完全可行,但 Rails 提供了一个更简洁的帮助方法:`options_from_collection_for_select`。这个方法接受一个由任意对象组成的集合,以及另外两个参数:获取选项文本和值使用的方法。 - -```erb -<%= options_from_collection_for_select(City.all, :id, :name) %> -``` - -从这个帮助方法的名字中可以看出,它只生成 `option` 标签。如果想生成可使用的选择列表,和 `options_for_select` 方法一样要结合 `select_tag` 方法一起使用。`select` 方法集成了 `select_tag` 和 `options_for_select` 两个方法,类似地,处理集合时,可以使用 `collection_select` 方法,它集成了 `select_tag` 和 `options_from_collection_for_select` 两个方法。 - -```erb -<%= collection_select(:person, :city_id, City.all, :id, :name) %> -``` - -`options_from_collection_for_select` 对 `collection_select` 来说,就像 `options_for_select` 与 `select` 的关系一样。 - -NOTE: 传入 `options_for_select` 方法的子数组第一个元素是选项文本,第二个元素是选项的值,但传入 `options_from_collection_for_select` 方法的第一个参数是获取选项值的方法,第二个才是获取选项文本的方法。 - -### 时区和国家选择列表 - -要想在 Rails 程序中实现时区相关的功能,就得询问用户其所在的时区。设定时区时可以使用 `collection_select` 方法根据预先定义的时区对象生成一个选择列表,也可以直接使用 `time_zone_select` 帮助方法: - -```erb -<%= time_zone_select(:person, :time_zone) %> -``` - -如果想定制时区列表,可使用 `time_zone_options_for_select` 帮助方法。这两个方法可接受的参数请查阅 API 文档。 - -以前 Rails 还内置了 `country_select` 帮助方法,用于创建国家选择列表,但现在已经被提取出来做成了 [country_select](https://github.com/stefanpenner/country_select) gem。使用这个 gem 时要注意,是否包含某个国家还存在争议(正因为此,Rails 才不想内置)。 - -使用日期和时间表单帮助方法 ----------------------- - -你可以选择不使用生成 HTML5 日期和时间输入框的帮助方法,而使用生成日期和时间选择列表的帮助方法。生成日期和时间选择列表的帮助方法和其他表单帮助方法有两个重要的不同点: - -* 日期和时间不在单个 `input` 元素中输入,而是每个时间单位都有各自的元素,因此在 `params` 中就没有单个值能表示完整的日期和时间; -* 其他帮助方法通过 `_tag` 后缀区分是独立的帮助方法还是操作模型对象的帮助方法。对日期和时间帮助方法来说,`select_date`、`select_time` 和 `select_datetime` 是独立的帮助方法,`date_select`、`time_select` 和 `datetime_select` 是相应的操作模型对象的帮助方法。 - -这两类帮助方法都会为每个时间单位(年,月,日等)生成各自的选择列表。 - -### 独立的帮助方法 - -`select_*` 这类帮助方法的第一个参数是 `Date`、`Time` 或 `DateTime` 类的实例,并选中指定的日期时间。如果不指定,就使用当前日期时间。例如: - -```erb -<%= select_date Date.today, prefix: :start_date %> -``` - -生成的 HTML 如下(为了行文简洁,省略了各选项): - -```html - - - -``` - -上面各控件会组成 `params[:start_date]`,其中包含名为 `:year`、`:month` 和 `:day` 的键。如果想获取 `Time` 或 `Date` 对象,要读取各时间单位的值,然后传入适当的构造方法中,例如: - -```ruby -Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) -``` - -`:prefix` 选项的作用是指定从 `params` 中获取各时间组成部分的键名。在上例中,`:prefix` 选项的值是 `start_date`。如果不指定这个选项,就是用默认值 `date`。 - -### 处理模型对象的帮助方法 - -`select_date` 方法在更新或创建 Active Record 对象的表单中有点力不从心,因为 Active Record 期望 `params` 中的每个元素都对应一个属性。用于处理模型对象的日期和时间帮助方法会提交一个名字特殊的参数,Active Record 看到这个参数时就知道必须和其他参数结合起来传递给字段类型对应的构造方法。例如: - -```erb -<%= date_select :person, :birth_date %> -``` - -生成的 HTML 如下(为了行文简洁,省略了各选项): - -```html - - - -``` - -创建的 `params` Hash 如下: - -```ruby -{'person' => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} -``` - -传递给 `Person.new`(或 `update`)方法时,Active Record 知道这些参数应该结合在一起组成 `birth_date` 属性,使用括号中的信息决定传给 `Date.civil` 等方法的顺序。 - -### 通用选项 - -这两种帮助方法都使用同一组核心函数生成各选择列表,因此使用的选项基本一样。默认情况下,Rails 生成的年份列表包含本年前后五年。如果这个范围不能满足需求,可以使用 `:start_year` 和 `:end_year` 选项指定。更详细的可用选项列表请参阅 [API 文档](http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html)。 - -基本原则是,使用 `date_select` 方法处理模型对象,其他情况都使用 `select_date` 方法,例如在搜索表单中根据日期过滤搜索结果。 - -NOTE: 很多时候内置的日期选择列表不太智能,不能协助用户处理日期和星期几之间的对应关系。 - -### 单个时间单位选择列表 - -有时只需显示日期中的一部分,例如年份或月份。为此,Rails 提供了一系列帮助方法,分别用于创建各时间单位的选择列表:`select_year`,`select_month`,`select_day`,`select_hour`,`select_minute`,`select_second`。各帮助方法的作用一目了然。默认情况下,这些帮助方法创建的选择列表 `name` 属性都跟时间单位的名称一样,例如,`select_year` 方法创建的 `select` 元素 `name` 属性值为 `year`,`select_month` 方法创建的 `select` 元素 `name` 属性值为 `month`,不过也可使用 `:field_name` 选项指定其他值。`:prefix` 选项的作用与在 `select_date` 和 `select_time` 方法中一样,且默认值也一样。 - -这些帮助方法的第一个参数指定选中哪个值,可以是 `Date`、`Time` 或 `DateTime` 类的实例(会从实例中获取对应的值),也可以是数字。例如: - -```erb -<%= select_year(2009) %> -<%= select_year(Time.now) %> -``` - -如果今年是 2009 年,那么上述两种用法生成的 HTML 是一样的。用户选择的值可以通过 `params[:date][:year]` 获取。 - -上传文件 --------- - -程序中一个常见的任务是上传某种文件,可以是用户的照片,或者 CSV 文件包含要处理的数据。处理文件上传功能时有一点要特别注意,表单的编码必须设为 `"multipart/form-data"`。如果使用 `form_for` 生成上传文件的表单,Rails 会自动加入这个编码。如果使用 `form_tag` 就得自己设置,如下例所示。 - -下面这两个表单都能用于上传文件: - -```erb -<%= form_tag({action: :upload}, multipart: true) do %> - <%= file_field_tag 'picture' %> -<% end %> - -<%= form_for @person do |f| %> - <%= f.file_field :picture %> -<% end %> -``` - -像往常一样,Rails 提供了两种帮助方法:独立的 `file_field_tag` 方法和处理模型的 `file_field` 方法。这两个方法和其他帮助方法唯一的区别是不能为文件选择框指定默认值,因为这样做没有意义。正如你所期望的,`file_field_tag` 方法上传的文件在 `params[:picture]` 中,`file_field` 方法上传的文件在 `params[:person][:picture]` 中。 - -### 上传了什么 - -存在 `params` Hash 中的对象其实是 `IO` 的子类,根据文件大小,可能是 `StringIO` 或者是存储在临时文件中的 `File` 实例。不管是哪个类,这个对象都有 `original_filename` 属性,其值为文件在用户电脑中的文件名;还有个 `content_type` 属性,其值为上传文件的 MIME 类型。下面这段代码把上传的文件保存在 `#{Rails.root}/public/uploads` 文件夹中,文件名和原始文件名一样(假设使用前面的表单上传)。 - -```ruby -def upload - uploaded_io = params[:person][:picture] - File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file| - file.write(uploaded_io.read) - end -end -``` - -文件上传完毕后可以做很多操作,例如把文件存储在某个地方(服务器的硬盘,Amazon S3 等);把文件和模型关联起来;缩放图片,生成缩略图。这些复杂的操作已经超出了本文范畴。有很多代码库可以协助完成这些操作,其中两个广为人知的是 [CarrierWave](https://github.com/jnicklas/carrierwave) 和 [Paperclip](http://www.thoughtbot.com/projects/paperclip)。 - -NOTE: 如果用户没有选择文件,相应的参数为空字符串。 - -### 使用 Ajax 上传文件 - -异步上传文件和其他类型的表单不一样,仅在 `form_for` 方法中加入 `remote: true` 选项是不够的。在 Ajax 表单中,使用浏览器中的 JavaScript 进行序列化,但是 JavaScript 无法读取硬盘中的文件,因此文件无法上传。常见的解决方法是使用一个隐藏的 `iframe` 作为表单提交的目标。 - -定制表单构造器 -------------- - -前面说过,`form_for` 和 `fields_for` 方法拽入的对象是 `FormBuilder` 或其子类的实例。表单构造器中封装了用于显示单个对象表单元素的信息。你可以使用常规的方式使用各帮助方法,也可以继承 `FormBuilder` 类,添加其他的帮助方法。例如: - -```erb -<%= form_for @person do |f| %> - <%= text_field_with_label f, :first_name %> -<% end %> -``` - -可以写成: - -```erb -<%= form_for @person, builder: LabellingFormBuilder do |f| %> - <%= f.text_field :first_name %> -<% end %> -``` - -在此之前需要定义 `LabellingFormBuilder` 类,如下所示: - -```ruby -class LabellingFormBuilder < ActionView::Helpers::FormBuilder - def text_field(attribute, options={}) - label(attribute) + super - end -end -``` - -如果经常这么使用,可以定义 `labeled_form_for` 帮助方法,自动启用 `builder: LabellingFormBuilder` 选项。 - -所用的表单构造器还会决定执行下面这个渲染操作时会发生什么: - -```erb -<%= render partial: f %> -``` - -如果 `f` 是 `FormBuilder` 类的实例,上述代码会渲染局部视图 `form`,并把传入局部视图的对象设为表单构造器。如果表单构造器是 `LabellingFormBuilder` 类的实例,则会渲染局部视图 `labelling_form`。 - -理解参数命名约定 --------------- - -从前几节可以看出,表单提交的数据可以直接保存在 `params` Hash 中,或者嵌套在子 Hash 中。例如,在 `Person` 模型对应控制器的 `create` 动作中,`params[:person]` 一般是一个 Hash,保存创建 `Person` 实例的所有属性。`params` Hash 中也可以保存数组,或由 Hash 组成的数组,等等。 - -HTML 表单基本上不能处理任何结构化数据,提交的只是由普通的字符串组成的键值对。在程序中使用的数组参数和 Hash 参数是通过 Rails 的参数命名约定生成的。 - -TIP: 如果想快速试验本节中的示例,可以在控制台中直接调用 Rack 的参数解析器。例如: -T> -```ruby -TIP: Rack::Utils.parse_query "name=fred&phone=0123456789" -TIP: # => {"name"=>"fred", "phone"=>"0123456789"} -TIP: ``` - -### 基本结构 - -数组和 Hash 是两种基本结构。获取 Hash 中值的方法和 `params` 一样。如果表单中包含以下控件: - -```html - -``` - -得到的 `params` 值为: - -```erb -{'person' => {'name' => 'Henry'}} -``` - -在控制器中可以使用 `params[:person][:name]` 获取提交的值。 - -Hash 可以随意嵌套,不限制层级,例如: - -```html - -``` - -得到的 `params` 值为: - -```ruby -{'person' => {'address' => {'city' => 'New York'}}} -``` - -一般情况下 Rails 会忽略重复的参数名。如果参数名中包含空的方括号(`[]`),Rails 会将其组建成一个数组。如果想让用户输入多个电话号码,在表单中可以这么做: - -```html - - - -``` - -得到的 `params[:person][:phone_number]` 就是一个数组。 - -### 结合在一起使用 - -上述命名约定可以结合起来使用,让 `params` 的某个元素值为数组(如前例),或者由 Hash 组成的数组。例如,使用下面的表单控件可以填写多个地址: - -```html - - - -``` - -得到的 `params[:addresses]` 值是一个由 Hash 组成的数组,Hash 中的键包括 `line1`、`line2` 和 `city`。如果 Rails 发现输入框的 `name` 属性值已经存在于当前 Hash 中,就会新建一个 Hash。 - -不过有个限制,虽然 Hash 可以嵌套任意层级,但数组只能嵌套一层。如果需要嵌套多层数组,可以使用 Hash 实现。例如,如果想创建一个包含模型对象的数组,可以创建一个 Hash,以模型对象的 ID、数组索引或其他参数为键。 - -WARNING: 数组类型参数不能很好的在 `check_box` 帮助方法中使用。根据 HTML 规范,未选中的复选框不应该提交值。但是不管是否选中都提交值往往更便于处理。为此 `check_box` 方法额外创建了一个同名的隐藏 `input` 元素。如果没有选中复选框,只会提交隐藏 `input` 元素的值,如果选中则同时提交两个值,但复选框的值优先级更高。处理数组参数时重复提交相同的参数会让 Rails 迷惑,因为对 Rails 来说,见到重复的 `input` 值,就会创建一个新数组元素。所以更推荐使用 `check_box_tag` 方法,或者用 Hash 代替数组。 - -### 使用表单帮助方法 - -前面几节并没有使用 Rails 提供的表单帮助方法。你可以自己创建 `input` 元素的 `name` 属性,然后直接将其传递给 `text_field_tag` 等帮助方法。但是 Rails 提供了更高级的支持。本节介绍 `form_for` 和 `fields_for` 方法的 `name` 参数以及 `:index` 选项。 - -你可能会想编写一个表单,其中有很多字段,用于编辑某人的所有地址。例如: - -```erb -<%= form_for @person do |person_form| %> - <%= person_form.text_field :name %> - <% @person.addresses.each do |address| %> - <%= person_form.fields_for address, index: address.id do |address_form|%> - <%= address_form.text_field :city %> - <% end %> - <% end %> -<% end %> -``` - -假设这个人有两个地址,ID 分别为 23 和 45。那么上述代码生成的 HTML 如下: - -```html - - - - -
-``` - -得到的 `params` Hash 如下: - -```ruby -{'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}} -``` - -Rails 之所以知道这些输入框中的值是 `person` Hash 的一部分,是因为我们在第一个表单构造器上调用了 `fields_for` 方法。指定 `:index` 选项的目的是告诉 Rails,其中的输入框 `name` 属性值不是 `person[address][city]`,而要在 `address` 和 `city` 索引之间插入 `:index` 选项对应的值(放入方括号中)。这么做很有用,因为便于分辨要修改的 `Address` 记录是哪个。`:index` 选项的值可以是具有其他意义的数字、字符串,甚至是 `nil`(此时会新建一个数组参数)。 - -如果想创建更复杂的嵌套,可以指定 `name` 属性的第一部分(前例中的 `person[address]`): - -```erb -<%= fields_for 'person[address][primary]', address, index: address do |address_form| %> - <%= address_form.text_field :city %> -<% end %> -``` - -生成的 HTML 如下: - -```html - -``` - -一般来说,最终得到的 `name` 属性值是 `fields_for` 或 `form_for` 方法的第一个参数加 `:index` 选项的值再加属性名。`:index` 选项也可直接传给 `text_field` 等帮助方法,但在表单构造器中指定可以避免代码重复。 - -为了简化句法,还可以不使用 `:index` 选项,直接在第一个参数后面加上 `[]`。这么做和指定 `index: address` 选项的作用一样,因此下面这段代码 - -```erb -<%= fields_for 'person[address][primary][]', address do |address_form| %> - <%= address_form.text_field :city %> -<% end %> -``` - -生成的 HTML 和前面一样。 - - -处理外部资源的表单 ----------------- - -如果想把数据提交到外部资源,还是可以使用 Rails 提供的表单帮助方法。但有时需要为这些资源创建 `authenticity_token`。做法是把 `authenticity_token: 'your_external_token'` 作为选项传递给 `form_tag` 方法: - -```erb -<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %> - Form contents -<% end %> -``` - -提交到外部资源的表单,其中可包含的字段有时受 API 的限制,例如支付网关。所有可能不用生成隐藏的 `authenticity_token` 字段,此时把 `:authenticity_token` 选项设为 `false` 即可: - -```erb -<%= form_tag 'http://farfar.away/form', authenticity_token: false) do %> - Form contents -<% end %> -``` - -以上技术也可用在 `form_for` 方法中: - -```erb -<%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %> - Form contents -<% end %> -``` - -如果不想生成 `authenticity_token` 字段,可以这么做: - -```erb -<%= form_for @invoice, url: external_url, authenticity_token: false do |f| %> - Form contents -<% end %> -``` - -编写复杂的表单 ------------- - -很多程序已经复杂到在一个表单中编辑一个对象已经无法满足需求了。例如,创建 `Person` 对象时还想让用户在同一个表单中创建多个地址(家庭地址,工作地址,等等)。以后编辑这个 `Person` 时,还想让用户根据需要添加、删除或修改地址。 - -### 设置模型 - -Active Record 为此种需求在模型中提供了支持,通过 `accepts_nested_attributes_for` 方法实现: - -```ruby -class Person < ActiveRecord::Base - has_many :addresses - accepts_nested_attributes_for :addresses -end - -class Address < ActiveRecord::Base - belongs_to :person -end -``` - -这段代码会在 `Person` 对象上创建 `addresses_attributes=` 方法,用于创建、更新和删除地址(可选操作)。 - -### 嵌套表单 - -使用下面的表单可以创建 `Person` 对象及其地址: - -```erb -<%= form_for @person do |f| %> - Addresses: -
    - <%= f.fields_for :addresses do |addresses_form| %> -
  • - <%= addresses_form.label :kind %> - <%= addresses_form.text_field :kind %> - - <%= addresses_form.label :street %> - <%= addresses_form.text_field :street %> - ... -
  • - <% end %> -
-<% end %> -``` - -如果关联支持嵌套属性,`fields_for` 方法会为关联中的每个元素执行一遍代码块。如果没有地址,就不执行代码块。一般的作法是在控制器中构建一个或多个空的子属性,这样至少会有一组字段显示出来。下面的例子会在新建 `Person` 对象的表单中显示两组地址字段。 - -```ruby -def new - @person = Person.new - 2.times { @person.addresses.build} -end -``` - -`fields_for` 方法拽入一个表单构造器,参数的名字就是 `accepts_nested_attributes_for` 方法期望的。例如,如果用户填写了两个地址,提交的参数如下: - -```ruby -{ - 'person' => { - 'name' => 'John Doe', - 'addresses_attributes' => { - '0' => { - 'kind' => 'Home', - 'street' => '221b Baker Street' - }, - '1' => { - 'kind' => 'Office', - 'street' => '31 Spooner Street' - } - } - } -} -``` - -`:addresses_attributes` Hash 的键是什么不重要,但至少不能相同。 - -如果关联的对象已经存在于数据库中,`fields_for` 方法会自动生成一个隐藏字段,`value` 属性的值为记录的 `id`。把 `include_id: false` 选项传递给 `fields_for` 方法可以禁止生成这个隐藏字段。如果自动生成的字段位置不对,导致 HTML 无法通过验证,或者在 ORM 关系中子对象不存在 `id` 字段,就可以禁止自动生成这个隐藏字段。 - -### 控制器端 - -像往常一样,参数传递给模型之前,在控制器中要[过滤参数](action_controller_overview.html#strong-parameters): - -```ruby -def create - @person = Person.new(person_params) - # ... -end - -private - def person_params - params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street]) - end -``` - -### 删除对象 - -如果允许用户删除关联的对象,可以把 `allow_destroy: true` 选项传递给 `accepts_nested_attributes_for` 方法: - -```ruby -class Person < ActiveRecord::Base - has_many :addresses - accepts_nested_attributes_for :addresses, allow_destroy: true -end -``` - -如果属性组成的 Hash 中包含 `_destroy` 键,且其值为 `1` 或 `true`,就会删除对象。下面这个表单允许用户删除地址: - -```erb -<%= form_for @person do |f| %> - Addresses: -
    - <%= f.fields_for :addresses do |addresses_form| %> -
  • - <%= addresses_form.check_box :_destroy%> - <%= addresses_form.label :kind %> - <%= addresses_form.text_field :kind %> - ... -
  • - <% end %> -
-<% end %> -``` - -别忘了修改控制器中的参数白名单,允许使用 `_destroy`: - -```ruby -def person_params - params.require(:person). - permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy]) -end -``` - -### 避免创建空记录 - -如果用户没有填写某些字段,最好将其忽略。此功能可以通过 `accepts_nested_attributes_for` 方法的 `:reject_if` 选项实现,其值为 Proc 对象。这个 Proc 对象会在通过表单提交的每一个属性 Hash 上调用。如果返回值为 `false`,Active Record 就不会为这个 Hash 构建关联对象。下面的示例代码只有当 `kind` 属性存在时才尝试构建地址对象: - -```ruby -class Person < ActiveRecord::Base - has_many :addresses - accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?} -end -``` - -为了方便,可以把 `reject_if` 选项的值设为 `:all_blank`,此时创建的 Proc 会拒绝为 `_destroy` 之外其他属性都为空的 Hash 构建对象。 - -### 按需添加字段 - -我们往往不想事先显示多组字段,而是当用户点击“添加新地址”按钮后再显示。Rails 并没有内建这种功能。生成新的字段时要确保关联数组的键是唯一的,一般可在 JavaScript 中使用当前时间。 diff --git a/source/zh-CN/generators.md b/source/zh-CN/generators.md deleted file mode 100644 index 69db110..0000000 --- a/source/zh-CN/generators.md +++ /dev/null @@ -1,669 +0,0 @@ -个性化Rails生成器与模板 -===================================================== - -Rails 生成器是提高你工作效率的有力工具。通过本章节的学习,你可以了解如何创建和个性化生成器。 - -通过学习本章节,你将学到: - -* 如何在你的Rails应用中辨别哪些生成器是可用的; -* 如何使用模板创建一个生成器; -* Rails应用在调用生成器之前如何找到他们; -* 如何通过创建一个生成器来定制你的 scaffold ; -* 如何通过改变生成器模板定制你的scaffold ; -* 如何使用回调复用生成器; -* 如何创建一个应用模板; - --------------------------------------------------------------------------------- - -简单介绍 -------------- - -当使用`rails` 命令创建一个应用的时候,实际上使用的是一个Rails生成器,创建应用之后,你可以使用`rails generate`命令获取当前可用的生成器列表: - - -```bash -$ rails new myapp -$ cd myapp -$ bin/rails generate -``` - -你将会看到和Rails相关的生成器列表,如果想了解这些生成器的详情,可以做如下操作: - -```bash -$ bin/rails generate helper --help -``` - - 创建你的第一个生成器 ------------------------------ - -从Rails 3.0开始,生成器都是基于[Thor](https://github.com/erikhuda/thor)构建的。Thor提供了强力的解析和操作文件的功能。比如,我们想让生成器在`config/initializers`目录下创建一个名为`initializer.rb`的文件: - -第一步可以通过`lib/generators/initializer_generator.rb`中的代码创建一个文件: - -```ruby -class InitializerGenerator < Rails::Generators::Base - def create_initializer_file - create_file "config/initializers/initializer.rb", "# Add initialization content here" - end -end -``` - -提示: `Thor::Actions`提供了`create_file`方法。关于`create_file`方法的详情可以参考[Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html) - -我们创建的生成器非常简单: 它继承自`Rails::Generators::Base`,只包含一个方法。当一个生成器被调用时,每个在生成器内部定义的方法都会顺序执行一次。最终,我们会根据程序执行环境调用`create_file`方法,在目标文件目录下创建一个文件。 如果你很熟悉Rails应用模板API,那么你在看生成器API时,也会轻车熟路,没什么障碍。 - - -为了调用我们刚才创建的生成器,我们只需要做如下操作: - -```bash -$ bin/rails generate initializer -``` - -我们可以通过如下代码,了解我们刚才创建的生成器的相关信息: -```bash -$ bin/rails generate initializer --help -``` - -Rails可以对一个命名空间化的生成器自动生成一个很好的描述信息。比如 `ActiveRecord::Generators::ModelGenerator`。一般而言,我们可以通过2中方式生成相关的描述。第一种是在生成器内部调用`desc`方法: - - -```ruby -class InitializerGenerator < Rails::Generators::Base - desc "This generator creates an initializer file at config/initializers" - def create_initializer_file - create_file "config/initializers/initializer.rb", "# Add initialization content here" - end -end -``` - -现在我们可以通过`--help`选项看到刚创建的生成器的描述信息。第二种是在生成器同名的目录下创建一个名为`USAGE`的文件存放和生成器相关的描述信息。 - -用生成器创建生成器 ------------------------------------ - -生成器本身拥有一个生成器: - -```bash -$ bin/rails generate generator initializer - create lib/generators/initializer - create lib/generators/initializer/initializer_generator.rb - create lib/generators/initializer/USAGE - create lib/generators/initializer/templates -``` - -这个生成器实际上只创建了这些: - -```ruby -class InitializerGenerator < Rails::Generators::NamedBase - source_root File.expand_path("../templates", __FILE__) -end -``` - -首先,我们注意到生成器是继承自`Rails::Generators::NamedBase`而非`Rails::Generators::Base`, -这意味着,我们的生成器在被调用时,至少要接收一个参数,即初始化器的名字。这样我们才能通过代码中的变量`name`来访问它。 - -我们可以通过查看生成器的描述信息来证实(别忘了删除旧的生成器文件): - -```bash -$ bin/rails generate initializer --help -Usage: - rails generate initializer NAME [options] -``` - -我们可以看到刚才创建的生成器有一个名为`source_root`的类方法。这个方法会指定生成器模板文件的存放路径,一般情况下,会放在 `lib/generators/initializer/templates`目录下。 - -为了了解生成器模板的作用,我们在`lib/generators/initializer/templates/initializer.rb`创建该文件,并添加如下内容: - -```ruby -# Add initialization content here -``` - -现在,我们来为生成器添加一个拷贝方法,将模板文件拷贝到指定目录: - -```ruby -class InitializerGenerator < Rails::Generators::NamedBase - source_root File.expand_path("../templates", __FILE__) - - def copy_initializer_file - copy_file "initializer.rb", "config/initializers/#{file_name}.rb" - end -end -``` - -接下来,使用刚才创建的生成器: - -```bash -$ bin/rails generate initializer core_extensions -``` - -我们可以看到通过生成器的模板在`config/initializers/core_extensions.rb`创建了一个名为core_extensions的初始化器。这说明 `copy_file` 方法从指定文件下拷贝了一个文件到目标文件夹。因为我们是继承自`Rails::Generators::NamedBase`的,所以会自动生成`file_name`方法 。 - -这个方法将在本章节的[final section](#generator-methods)实现完整功能。 - -生成器查找 ------------------ - -当你运行 `rails generate initializer core_extensions` 命令时,Rails会做如下搜索: - -```bash -rails/generators/initializer/initializer_generator.rb -generators/initializer/initializer_generator.rb -rails/generators/initializer_generator.rb -generators/initializer_generator.rb -``` - -如果没有找到,你将会看到一个错误信息。 - -提示: 上面的例子把文件放在Rails应用的`lib`文件夹下,是因为该文件夹路径属于`$LOAD_PATH`。 - -个性化你的工作流  -------------------------- - -Rails自带的生成器为工作流的个性化提供了支持。它们可以在`config/application.rb`中进行配置: - -```ruby -config.generators do |g| - g.orm :active_record - g.template_engine :erb - g.test_framework :test_unit, fixture: true -end -``` - -在个性化我们的工作流之前,我们先看看scaffold工具会做些什么:  - -```bash -$ bin/rails generate scaffold User name:string - invoke active_record - create db/migrate/20130924151154_create_users.rb - create app/models/user.rb - invoke test_unit - create test/models/user_test.rb - create test/fixtures/users.yml - invoke resource_route - route resources :users - invoke scaffold_controller - create app/controllers/users_controller.rb - invoke erb - create app/views/users - create app/views/users/index.html.erb - create app/views/users/edit.html.erb - create app/views/users/show.html.erb - create app/views/users/new.html.erb - create app/views/users/_form.html.erb - invoke test_unit - create test/controllers/users_controller_test.rb - invoke helper - create app/helpers/users_helper.rb - invoke test_unit - create test/helpers/users_helper_test.rb - invoke jbuilder - create app/views/users/index.json.jbuilder - create app/views/users/show.json.jbuilder - invoke assets - invoke coffee - create app/assets/javascripts/users.js.coffee - invoke scss - create app/assets/stylesheets/users.css.scss - invoke scss - create app/assets/stylesheets/scaffolds.css.scss -``` - -通过上面的内容,我们可以很容易理解Rails3.0以上版本的生成器是如何工作的。scaffold生成器几乎不生成文件。它只是调用其他生成器去做。这样的话,我们可以很方便的添加/替换/删除这些被调用的生成器。比如说,scaffold生成器调用了scaffold_controller生成器(调用了erb,test_unit和helper生成器),它们每个生成器都有一个单独的响应方法,这样就很容易实现代码复用。 - -如果我们希望scaffold 在生成工作流时不必生成样式表,脚本文件和测试固件等文件,那么我们可以进行如下配置: - -```ruby -config.generators do |g| - g.orm :active_record - g.template_engine :erb - g.test_framework :test_unit, fixture: false - g.stylesheets false - g.javascripts false -end -``` - -如果我们使用scaffold生成器创建另外一个资源时,就会发现样式表,脚本文件和测试固件的文件都不再创建了。如果你想更深入的进行定制,比如使用DataMapper和RSpec 替换Active Record和TestUnit -,那么只需要把相关的gem文件引入,并配置你的生成器。 - -为了证明这一点,我们将创建一个新的helper生成器,简单的添加一些实例变量访问器。首先,我们创建一个带Rails命名空间的的生成器,因为这样为Rails方便搜索提供了支持: - -```bash -$ bin/rails generate generator rails/my_helper - create lib/generators/rails/my_helper - create lib/generators/rails/my_helper/my_helper_generator.rb - create lib/generators/rails/my_helper/USAGE - create lib/generators/rails/my_helper/templates -``` - -现在,我们可以删除`templates`和`source_root`文件了,因为我们将不会用到它们,接下来我们在生成器中添加如下代码: - -```ruby -# lib/generators/rails/my_helper/my_helper_generator.rb -class Rails::MyHelperGenerator < Rails::Generators::NamedBase - def create_helper_file - create_file "app/helpers/#{file_name}_helper.rb", <<-FILE -module #{class_name}Helper - attr_reader :#{plural_name}, :#{plural_name.singularize} -end - FILE - end -end -``` - -我们可以使用修改过的生成器为products提供一个helper文件: - -```bash -$ bin/rails generate my_helper products - create app/helpers/products_helper.rb -``` - -这将会在 `app/helpers`目录下生成一个对应的文件: - -```ruby -module ProductsHelper - attr_reader :products, :product -end -``` - -这就是我们希望看到的。现在,我们可以修改`config/application.rb`,告诉scaffold使用我们的helper 生成器: - -```ruby -config.generators do |g| - g.orm :active_record - g.template_engine :erb - g.test_framework :test_unit, fixture: false - g.stylesheets false - g.javascripts false - g.helper :my_helper -end -``` - -你将在生成动作列表中看到上述方法的调用: - -```bash -$ bin/rails generate scaffold Article body:text - [...] - invoke my_helper - create app/helpers/articles_helper.rb -``` - -我们注意到新的helper生成器替换了Rails默认的调用。但有一件事情却忽略了,如何为新的生成器提供测试呢?我们可以复用原有的helpers测试生成器。 - -从Rails 3.0开始,简单的实现上述功能依赖于钩子的概念。我们新的helper方法不需要拘泥于特定的测试框架,它可以简单的提供一个钩子,测试框架只需要实现这个钩子并与之一致即可。 - -为此,我们需要对生成器做如下修改: - -```ruby -# lib/generators/rails/my_helper/my_helper_generator.rb -class Rails::MyHelperGenerator < Rails::Generators::NamedBase - def create_helper_file - create_file "app/helpers/#{file_name}_helper.rb", <<-FILE -module #{class_name}Helper - attr_reader :#{plural_name}, :#{plural_name.singularize} -end - FILE - end - - hook_for :test_framework -end -``` - -现在,当helper生成器被调用时,与之匹配的测试框架是TestUnit,那么这将会调用`Rails::TestUnitGenerator`和 `TestUnit::MyHelperGenerator`。如果他们都没有定义,我们可以告诉生成器调用`TestUnit::Generators::HelperGenerator`来替代。对于一个Rails生成器来说,我们只需要添加如下代码: - -```ruby -# Search for :helper instead of :my_helper -hook_for :test_framework, as: :helper -``` - -现在,你再次运行scaffold生成器生成Rails应用时,它就会生成相关的测试了。 - -通过修改生成器模板个性化工作流 ----------------------------------------------------------- - -上一章节中,我们只是简单的在helper生成器中添加了一行代码,没有添加额外的功能。有一种简便的方法可以实现它,那就是替换模版中已经存在的生成器。比如`Rails::Generators::HelperGenerator`。 - - -从Rails 3.0开始,生成器不只是在源目录中查找模版,它们也会搜索其他路径。其中一个就是`lib/templates`,如果我们想定制`Rails::Generators::HelperGenerator`,那么我们可以在`lib/templates/rails/helper`中添加一个名为`helper.rb`的文件,文件内容包含如下代码: - -```erb -module <%= class_name %>Helper - attr_reader :<%= plural_name %>, :<%= plural_name.singularize %> -end -``` - -将 `config/application.rb`中重复的内容删除: - -```ruby -config.generators do |g| - g.orm :active_record - g.template_engine :erb - g.test_framework :test_unit, fixture: false - g.stylesheets false - g.javascripts false -end -``` - -现在生成另外一个Rails应用时,你会发现得到的结果几乎一致。这是一个很有用的功能,如果你只想修改`edit.html.erb`, `index.html.erb`等文件的布局,那么可以在`lib/templates/erb/scaffold`中进行配置。 - -让生成器支持备选功能 ---------------------------- - -最后将要介绍的生成器特性对插件生成器特别有用。举个例子,如果你想给TestUnit添加一个名为 [shoulda](https://github.com/thoughtbot/shoulda)的特性,TestUnit已经实现了所有Rails要求的生成器功能,shoulda想重用其中的部分功能,shoulda不需要重新实现这些生成器,可以告诉Rails使用`TestUnit`的生成器,如果在`Shoulda`的命名空间中没找到的话。 - -我们可以通过修改`config/application.rb`的内容,很方便的实现这个功能: - -```ruby -config.generators do |g| - g.orm :active_record - g.template_engine :erb - g.test_framework :shoulda, fixture: false - g.stylesheets false - g.javascripts false - - # Add a fallback! - g.fallbacks[:shoulda] = :test_unit -end -``` - -现在,如果你使用scaffold 创建一个Comment 资源,那么你将看到shoulda生成器被调用了,但最后调用的是TestUnit的生成器方法: - -```bash -$ bin/rails generate scaffold Comment body:text - invoke active_record - create db/migrate/20130924143118_create_comments.rb - create app/models/comment.rb - invoke shoulda - create test/models/comment_test.rb - create test/fixtures/comments.yml - invoke resource_route - route resources :comments - invoke scaffold_controller - create app/controllers/comments_controller.rb - invoke erb - create app/views/comments - create app/views/comments/index.html.erb - create app/views/comments/edit.html.erb - create app/views/comments/show.html.erb - create app/views/comments/new.html.erb - create app/views/comments/_form.html.erb - invoke shoulda - create test/controllers/comments_controller_test.rb - invoke my_helper - create app/helpers/comments_helper.rb - invoke shoulda - create test/helpers/comments_helper_test.rb - invoke jbuilder - create app/views/comments/index.json.jbuilder - create app/views/comments/show.json.jbuilder - invoke assets - invoke coffee - create app/assets/javascripts/comments.js.coffee - invoke scss -``` - -备选功能支持你的生成器拥有单独的响应,可以实现代码复用,减少重复代码。 - - -应用模版 ---------------------- - -现在你已经了解如何在一个应用中使用生成器,那么你知道生成器还可以生成应用吗? 这种生成器一般是由"template"来实现的。接下来我们会简要介绍模版API,进一步了解可以参考[Rails Application Templates guide](rails_application_templates.html)。 - -```ruby -gem "rspec-rails", group: "test" -gem "cucumber-rails", group: "test" - -if yes?("Would you like to install Devise?") - gem "devise" - generate "devise:install" - model_name = ask("What would you like the user model to be called? [user]") - model_name = "user" if model_name.blank? - generate "devise", model_name -end -``` - -上述模版在`Gemfile`声明了 `rspec-rails` 和 `cucumber-rails`两个gem包属于`test`组,之后会发送一个问题给使用者,是否希望安装Devise?如果用户同意安装,那么模版会将Devise添加到`Gemfile`文件中,并运行 `devise:install`命令,之后根据用户输入的模块名,指定`devise`所属模块。 - -假如你想使用一个名为`template.rb`的模版文件,我们可以通过在执行 `rails new`命令时,加上 `-m` 选项来改变输出信息: - -```bash -$ rails new thud -m template.rb -``` - -上述命令将会生成`Thud` 应用,并使用模版生成输出信息。 - -模版文件不一定要存储在本地文件中, `-m`选项也支持在线模版: - -```bash -$ rails new thud -m https://gist.github.com/radar/722911/raw/ -``` - -本文最后的章节没有介绍如何生成大家都熟知的模版,而是介绍在开发模版过程中会用到的方法。同样这些方法也可以通过生成器来调用。 - -生成器方法 ------------------ - -下面要介绍的方法对生成器和模版来说都是可用的。 - -提示: Thor中未介绍的方法可以通过访问[Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html)做进一步了解。 - -### `gem` - -声明一个gem在Rails应用中的依赖项。 - -```ruby -gem "rspec", group: "test", version: "2.1.0" -gem "devise", "1.1.5" -``` - -可用的选项如下: - -* `:group` - 在`Gemfile`中声明所安装的gem包所在的分组。 -* `:version` - 声明gem的版本信息,你也可以在该方法的第二个参数中声明。 -* `:git` - gem包相关的git地址 - -可以在该方法参数列表的最后添加额外的信息: - -```ruby -gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" -``` - -上述代码将在`Gemfile`中添加如下内容: - -```ruby -gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" -``` - -### `gem_group` - -将gem包安装到指定组中: - -```ruby -gem_group :development, :test do - gem "rspec-rails" -end -``` - -### `add_source` - -为`Gemfile`文件添加指定数据源: - -```ruby -add_source "http://gems.github.com/" -``` - -### `inject_into_file` - -在文件中插入一段代码: - -```ruby -inject_into_file 'name_of_file.rb', after: "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY' - puts "Hello World" -RUBY -end -``` - -### `gsub_file` - -替换文件中的文本: - -```ruby -gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code' -``` - -使用正则表达式可以更准确的匹配信息。同时可以分别使用`append_file`和 `prepend_file`方法从文件的开始处或末尾处匹配信息。 - -### `application` - -在`config/application.rb`文件中的application类定义之后添加一行信息。 - -```ruby -application "config.asset_host = 'http://example.com/'" -``` - -这个方法也可以写成一个代码块的方式: - -```ruby -application do - "config.asset_host = 'http://example.com/'" -end -``` - -可用的选项如下: - -* `:env` -为配置文件指定运行环境,如果你希望写成代码块的方式,可以这么做: - -```ruby -application(nil, env: "development") do - "config.asset_host = 'http://localhost:3000/'" -end -``` - -### `git` - -运行指定的git命令: - -```ruby -git :init -git add: "." -git commit: "-m First commit!" -git add: "onefile.rb", rm: "badfile.cxx" -``` - -哈希值可以作为git命令的参数来使用,上述代码中指定了多个git命令,但并不能保证这些命令按顺序执行。 - - -### `vendor` - -查找`vendor`文件加下指定文件是否包含指定内容: - -```ruby -vendor "sekrit.rb", '#top secret stuff' -``` - -这个方法也可以写成一个代码块 : - -```ruby -vendor "seeds.rb" do - "puts 'in your app, seeding your database'" -end -``` - -### `lib` - -查找`lib`文件加下指定文件是否包含指定内容: - -```ruby -lib "special.rb", "p Rails.root" -``` - -这个方法也可以写成一个代码块 : - -```ruby -lib "super_special.rb" do - puts "Super special!" -end -``` - -### `rakefile` - -在Rails应用的 `lib/tasks`文件夹下创建一个Rake文件。 - -```ruby -rakefile "test.rake", "hello there" -``` - -这个方法也可以写成一个代码块 : - -```ruby -rakefile "test.rake" do - %Q{ - task rock: :environment do - puts "Rockin'" - end - } -end -``` - -### `initializer` - -在Rails应用的`config/initializers` 目录下创建一个初始化器: - -```ruby -initializer "begin.rb", "puts 'this is the beginning'" -``` - -这个方法也可以写成一个代码块,并返回一个字符串: - -```ruby -initializer "begin.rb" do - "puts 'this is the beginning'" -end -``` - -### `generate` - -运行指定的生成器,第一个参数是生成器名字,其余的直接传给生成器: - -```ruby -generate "scaffold", "forums title:string description:text" -``` - - -### `rake` - -运行指定的Rake任务: - - -```ruby -rake "db:migrate" -``` - -可用是选项如下: - -* `:env` - 声明rake任务的执行环境。 -* `:sudo` - 是否使用`sudo`命令运行rake任务,默认不使用。 - -### `capify!` - -在Rails应用的根目录下使用Capistrano运行`capify`命令,生成和Rails应用相关的Capistrano配置文件。 - -```ruby -capify! -``` - -### `route` - -在`config/routes.rb` 文件中添加文本: - -```ruby -route "resources :people" -``` - -### `readme` - -输出模版的`source_path`相关的内容,通常是一个README文件。 - -```ruby -readme "README" -``` diff --git a/source/zh-CN/getting_started.md b/source/zh-CN/getting_started.md deleted file mode 100644 index 0c07897..0000000 --- a/source/zh-CN/getting_started.md +++ /dev/null @@ -1,1457 +0,0 @@ -Rails 入门 -========== - -本文介绍如何开始使用 Ruby on Rails。 - -读完本文,你将学到: - -* 如何安装 Rails,新建 Rails 程序,如何连接数据库; -* Rails 程序的基本文件结构; -* MVC(模型,视图,控制器)和 REST 架构的基本原理; -* 如何快速生成 Rails 程序骨架; - --------------------------------------------------------------------------------- - -前提条件 -------- - -本文针对想从零开始开发 Rails 程序的初学者,不需要预先具备任何的 Rails 使用经验。不过,为了能顺利阅读,还是需要事先安装好一些软件: - -* [Ruby](https://www.ruby-lang.org/en/downloads) 1.9.3 及以上版本 -* 包管理工具 [RubyGems](https://rubygems.org),随 Ruby 1.9+ 安装。想深入了解 RubyGems,请阅读 [RubyGems 指南](http://guides.rubygems.org) -* [SQLite3](https://www.sqlite.org) 数据库 - -Rails 是使用 Ruby 语言开发的网页程序框架。如果之前没接触过 Ruby,学习 Rails 可要深下一番功夫。网上有很多资源可以学习 Ruby: - -* [Ruby 语言官方网站](https://www.ruby-lang.org/zh_cn/documentation/) -* [reSRC 列出的免费编程书籍](http://resrc.io/list/10/list-of-free-programming-books/#ruby) - -记住,某些资源虽然很好,但是针对 Ruby 1.8,甚至 1.6 编写的,所以没有介绍一些 Rails 日常开发会用到的句法。 - -Rails 是什么? -------------- - -Rails 是使用 Ruby 语言编写的网页程序开发框架,目的是为开发者提供常用组件,简化网页程序的开发。只需编写较少的代码,就能实现其他编程语言或框架难以企及的功能。经验丰富的 Rails 程序员会发现,Rails 让程序开发变得更有乐趣。 - -Rails 有自己的一套规则,认为问题总有最好的解决方法,而且建议使用最好的方法,有些情况下甚至不推荐使用其他替代方案。学会如何按照 Rails 的思维开发,能极大提高开发效率。如果坚持在 Rails 开发中使用其他语言中的旧思想,尝试使用别处学来的编程模式,开发过程就不那么有趣了。 - -Rails 哲学包含两大指导思想: - -* **不要自我重复(DRY):** DRY 是软件开发中的一个原则,“系统中的每个功能都要具有单一、准确、可信的实现。”。不重复表述同一件事,写出的代码才能更易维护,更具扩展性,也更不容易出问题。 -* **多约定,少配置:** Rails 为网页程序的大多数需求都提供了最好的解决方法,而且默认使用这些约定,不用在长长的配置文件中设置每个细节。 - -新建 Rails 程序 --------------- - -阅读本文时,最佳方式是跟着一步一步操作,如果错过某段代码或某个步骤,程序就可能出错,所以请一步一步跟着做。 - -本文会新建一个名为 `blog` 的 Rails 程序,这是一个非常简单的博客。在开始开发程序之前,要确保已经安装了 Rails。 - -TIP: 文中的示例代码使用 `$` 表示命令行提示符,你的提示符可能修改过,所以会不一样。在 Windows 中,提示符可能是 `c:\source_code>`。 - -### 安装 Rails - -打开命令行:在 Mac OS X 中打开 Terminal.app,在 Windows 中选择“运行”,然后输入“cmd.exe”。下文中所有以 `$` 开头的代码,都要在命令行中运行。先确认是否安装了 Ruby 最新版: - -TIP: 有很多工具可以帮助你快速在系统中安装 Ruby 和 Ruby on Rails。Windows 用户可以使用 [Rails Installer](http://railsinstaller.org),Mac OS X 用户可以使用 [Tokaido](https://github.com/tokaido/tokaidoapp)。 - -```bash -$ ruby -v -ruby 2.1.2p95 -``` - -如果你还没安装 Ruby,请访问 [ruby-lang.org](https://www.ruby-lang.org/en/downloads/),找到针对所用系统的安装方法。 - -很多类 Unix 系统都自带了版本尚新的 SQLite3。Windows 等其他操作系统的用户可以在 [SQLite3 的网站](https://www.sqlite.org)上找到安装说明。然后,确认是否在 PATH 中: - -```bash -$ sqlite3 --version -``` - -命令行应该回显版本才对。 - -安装 Rails,请使用 RubyGems 提供的 `gem install` 命令: - -```bash -$ gem install rails -``` - -要检查所有软件是否都正确安装了,可以执行下面的命令: - -```bash -$ rails --version -``` - -如果显示的结果类似“Rails 4.2.0”,那么就可以继续往下读了。 - -### 创建 Blog 程序 - -Rails 提供了多个被称为“生成器”的脚本,可以简化开发,生成某项操作需要的所有文件。其中一个是新程序生成器,生成一个 Rails 程序骨架,不用自己一个一个新建文件。 - -打开终端,进入有写权限的文件夹,执行以下命令生成一个新程序: - -```bash -$ rails new blog -``` - -这个命令会在文件夹 `blog` 中新建一个 Rails 程序,然后执行 `bundle install` 命令安装 `Gemfile` 中列出的 gem。 - -TIP: 执行 `rails new -h` 可以查看新程序生成器的所有命令行选项。 - -生成 `blog` 程序后,进入该文件夹: - -```bash -$ cd blog -``` - -`blog` 文件夹中有很多自动生成的文件和文件夹,组成一个 Rails 程序。本文大部分时间都花在 `app` 文件夹上。下面简单介绍默认生成的文件和文件夹的作用: - -| 文件/文件夹 | 作用 | -| ----------- | ------- | -|app/|存放程序的控制器、模型、视图、帮助方法、邮件和静态资源文件。本文主要关注的是这个文件夹。| -|bin/|存放运行程序的 `rails` 脚本,以及其他用来部署或运行程序的脚本。| -|config/|设置程序的路由,数据库等。详情参阅“[设置 Rails 程序](/configuring.html)”一文。| -|config.ru|基于 Rack 服务器的程序设置,用来启动程序。| -|db/|存放当前数据库的模式,以及数据库迁移文件。| -|Gemfile, Gemfile.lock|这两个文件用来指定程序所需的 gem 依赖件,用于 Bundler gem。关于 Bundler 的详细介绍,请访问 [Bundler 官网](http://bundler.io)。| -|lib/|程序的扩展模块。| -|log/|程序的日志文件。| -|public/|唯一对外开放的文件夹,存放静态文件和编译后的资源文件。| -|Rakefile|保存并加载可在命令行中执行的任务。任务在 Rails 的各组件中定义。如果想添加自己的任务,不要修改这个文件,把任务保存在 `lib/tasks` 文件夹中。| -|README.rdoc|程序的简单说明。你应该修改这个文件,告诉其他人这个程序的作用,如何安装等。| -|test/|单元测试,固件等测试用文件。详情参阅“[测试 Rails 程序](/testing.html)”一文。| -|tmp/|临时文件,例如缓存,PID,会话文件。| -|vendor/|存放第三方代码。经常用来放第三方 gem。| - -Hello, Rails! -------------- - -首先,我们来添加一些文字,在页面中显示。为了能访问网页,要启动程序服务器。 - -### 启动服务器 - -现在,新建的 Rails 程序已经可以正常运行。要访问网站,需要在开发电脑上启动服务器。请在 `blog` 文件夹中执行下面的命令: - -```bash -$ rails server -``` - -TIP: 把 CoffeeScript 编译成 JavaScript 需要 JavaScript 运行时,如果没有运行时,会报错,提示没有 `execjs`。Mac OS X 和 Windows 一般都提供了 JavaScript 运行时。Rails 生成的 `Gemfile` 中,安装 `therubyracer` gem 的代码被注释掉了,如果需要使用这个 gem,请把前面的注释去掉。在 JRuby 中推荐使用 `therubyracer`。在 JRuby 中生成的 `Gemfile` 已经包含了这个 gem。所有支持的运行时参见 [ExecJS](https://github.com/sstephenson/execjs#readme)。 - -上述命令会启动 WEBrick,这是 Ruby 内置的服务器。要查看程序,请打开一个浏览器窗口,访问 。应该会看到默认的 Rails 信息页面: - -![欢迎使用页面](images/getting_started/rails_welcome.png) - -TIP: 要想停止服务器,请在命令行中按 Ctrl+C 键。服务器成功停止后回重新看到命令行提示符。在大多数类 Unix 系统中,包括 Mac OS X,命令行提示符是 `$` 符号。在开发模式中,一般情况下无需重启服务器,修改文件后,服务器会自动重新加载。 - -“欢迎使用”页面是新建 Rails 程序后的“冒烟测试”:确保程序设置正确,能顺利运行。你可以点击“About your application's environment”链接查看程序所处环境的信息。 - -### 显示“Hello, Rails!” - -要在 Rails 中显示“Hello, Rails!”,需要新建一个控制器和视图。 - -控制器用来接受向程序发起的请求。路由决定哪个控制器会接受到这个请求。一般情况下,每个控制器都有多个路由,对应不同的动作。动作用来提供视图中需要的数据。 - -视图的作用是,以人类能看懂的格式显示数据。有一点要特别注意,数据是在控制器中获取的,而不是在视图中。视图只是把数据显示出来。默认情况下,视图使用 eRuby(嵌入式 Ruby)语言编写,经由 Rails 解析后,再发送给用户。 - -控制器可用控制器生成器创建,你要告诉生成器,我想要个名为“welcome”的控制器和一个名为“index”的动作,如下所示: - -```bash -$ rails generate controller welcome index -``` - -运行上述命令后,Rails 会生成很多文件,以及一个路由。 - -```bash -create app/controllers/welcome_controller.rb - route get 'welcome/index' -invoke erb -create app/views/welcome -create app/views/welcome/index.html.erb -invoke test_unit -create test/controllers/welcome_controller_test.rb -invoke helper -create app/helpers/welcome_helper.rb -invoke assets -invoke coffee -create app/assets/javascripts/welcome.js.coffee -invoke scss -create app/assets/stylesheets/welcome.css.scss -``` - -在这些文件中,最重要的当然是控制器,位于 `app/controllers/welcome_controller.rb`,以及视图,位于 `app/views/welcome/index.html.erb`。 - -使用文本编辑器打开 `app/views/welcome/index.html.erb` 文件,删除全部内容,写入下面这行代码: - -```html -

Hello, Rails!

-``` - -### 设置程序的首页 - -我们已经创建了控制器和视图,现在要告诉 Rails 在哪个地址上显示“Hello, Rails!”。这里,我们希望访问根地址 时显示。但是现在显示的还是欢迎页面。 - -我们要告诉 Rails 真正的首页是什么。 - -在编辑器中打开 `config/routes.rb` 文件。 - -```ruby -Rails.application.routes.draw do - get 'welcome/index' - - # The priority is based upon order of creation: - # first created -> highest priority. - # - # You can have the root of your site routed with "root" - # root 'welcome#index' - # - # ... -``` - -这是程序的路由文件,使用特殊的 DSL(domain-specific language,领域专属语言)编写,告知 Rails 请求应该发往哪个控制器和动作。文件中有很多注释,举例说明如何定义路由。其中有一行说明了如何指定控制器和动作设置网站的根路由。找到以 `root` 开头的代码行,去掉注释,变成这样: - -```ruby -root 'welcome#index' -``` - -`root 'welcome#index'` 告知 Rails,访问程序的根路径时,交给 `welcome` 控制器中的 `index` 动作处理。`get 'welcome/index'` 告知 Rails,访问 时,交给 `welcome` 控制器中的 `index` 动作处理。`get 'welcome/index'` 是运行 `rails generate controller welcome index` 时生成的。 - -如果生成控制器时停止了服务器,请再次启动(`rails server`),然后在浏览器中访问 。你会看到之前写入 `app/views/welcome/index.html.erb` 文件的“Hello, Rails!”,说明新定义的路由把根目录交给 `WelcomeController` 的 `index` 动作处理了,而且也正确的渲染了视图。 - -TIP: 关于路由的详细介绍,请阅读“[Rails 路由全解](/routing.html)”一文。 - -开始使用 -------- - -前文已经介绍如何创建控制器、动作和视图,下面我们来创建一些更实质的功能。 - -在博客程序中,我们要创建一个新“资源”。资源是指一系列类似的对象,比如文章,人和动物。 - -资源可以被创建、读取、更新和删除,这些操作简称 CRUD。 - -Rails 提供了一个 `resources` 方法,可以声明一个符合 REST 架构的资源。创建文章资源后,`config/routes.rb` 文件的内容如下: - -```ruby -Rails.application.routes.draw do - - resources :articles - - root 'welcome#index' -end -``` - -执行 `rake routes` 任务,会看到定义了所有标准的 REST 动作。输出结果中各列的意义稍后会说明,现在只要留意 `article` 的单复数形式,这在 Rails 中有特殊的含义。 - -```bash -$ bin/rake routes - Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index -``` - -下一节,我们会加入新建文章和查看文章的功能。这两个操作分别对应于 CRUD 的 C 和 R,即创建和读取。新建文章的表单如下所示: - -![新建文章表单](images/getting_started/new_article.png) - -表单看起来很简陋,不过没关系,后文会加入更多的样式。 - -### 挖地基 - -首先,程序中要有个页面用来新建文章。一个比较好的选择是 `/articles/new`。这个路由前面已经定义了,可以访问。打开 ,会看到如下的路由错误: - -![路由错误,常量 ArticlesController 未初始化](images/getting_started/routing_error_no_controller.png) - -产生这个错误的原因是,没有定义用来处理该请求的控制器。解决这个问题的方法很简单,执行下面的命令创建名为 `ArticlesController` 的控制器即可: - -```bash -$ bin/rails g controller articles -``` - -打开刚生成的 `app/controllers/articles_controller.rb` 文件,会看到一个几乎没什么内容的控制器: - -```ruby -class ArticlesController < ApplicationController -end -``` - -控制器就是一个类,继承自 `ApplicationController`。在这个类中定义的方法就是控制器的动作。动作的作用是处理文章的 CRUD 操作。 - -NOTE: 在 Ruby 中,方法分为 `public`、`private` 和 `protected` 三种,只有 `public` 方法才能作为控制器的动作。详情参阅 [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/) 一书。 - -现在刷新 ,会看到一个新错误: - -![ArticlesController 控制器不知如何处理 new 动作](images/getting_started/unknown_action_new_for_articles.png) - -这个错误的意思是,在刚生成的 `ArticlesController` 控制器中找不到 `new` 动作。因为在生成控制器时,除非指定要哪些动作,否则不会生成,控制器是空的。 - -手动创建动作只需在控制器中定义一个新方法。打开 `app/controllers/articles_controller.rb` 文件,在 `ArticlesController` 类中,定义 `new` 方法,如下所示: - -```ruby -class ArticlesController < ApplicationController - def new - end -end -``` - -在 `ArticlesController` 中定义 `new` 方法后,再刷新 ,看到的还是个错误: - -![找不到 articles/new 所用模板](images/getting_started/template_is_missing_articles_new.png) - -产生这个错误的原因是,Rails 希望这样的常规动作有对应的视图,用来显示内容。没有视图可用,Rails 就报错了。 - -在上图中,最后一行被截断了,我们来看一下完整的信息: - -``` -Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" -``` - -这行信息还挺长,我们来看一下到底是什么意思。 - -第一部分说明找不到哪个模板,这里,丢失的是 `articles/new` 模板。Rails 首先会寻找这个模板,如果找不到,再找名为 `application/new` 的模板。之所以这么找,是因为 `ArticlesController` 继承自 `ApplicationController`。 - -后面一部分是个 Hash。`:locale` 表示要找哪国语言模板,默认是英语(`"en"`)。`:format` 表示响应使用的模板格式,默认为 `:html`,所以 Rails 要寻找一个 HTML 模板。`:handlers` 表示用来处理模板的程序,HTML 模板一般使用 `:erb`,XML 模板使用 `:builder`,`:coffee` 用来把 CoffeeScript 转换成 JavaScript。 - -最后一部分说明 Rails 在哪里寻找模板。在这个简单的程序里,模板都存放在一个地方,复杂的程序可能存放在多个位置。 - -让这个程序正常运行,最简单的一种模板是 `app/views/articles/new.html.erb`。模板文件的扩展名是关键所在:第一个扩展名是模板的类型,第二个扩展名是模板的处理程序。Rails 会尝试在 `app/views` 文件夹中寻找名为 `articles/new` 的模板。这个模板的类型只能是 `html`,处理程序可以是 `erb`、`builder` 或 `coffee`。因为我们要编写一个 HTML 表单,所以使用 `erb`。所以这个模板文件应该命名为 `articles/new.html.erb`,还要放在 `app/views` 文件夹中。 - -新建文件 `app/views/articles/new.html.erb`,写入如下代码: - -```html -

New Article

-``` - -再次刷新 ,可以看到页面中显示了一个标头。现在路由、控制器、动作和视图都能正常运行了。接下来要编写新建文章的表单了。 - -### 首个表单 - -要在模板中编写表单,可以使用“表单构造器”。Rails 中常用的表单构造器是 `form_for`。在 `app/views/articles/new.html.erb` 文件中加入以下代码: - -```erb -<%= form_for :article do |f| %> -

- <%= f.label :title %>
- <%= f.text_field :title %> -

- -

- <%= f.label :text %>
- <%= f.text_area :text %> -

- -

- <%= f.submit %> -

-<% end %> -``` - -现在刷新页面,会看到上述代码生成的表单。在 Rails 中编写表单就是这么简单! - -调用 `form_for` 方法时,要指定一个对象。在上面的表单中,指定的是 `:article`。这个对象告诉 `form_for`,这个表单是用来处理哪个资源的。在 `form_for` 方法的块中,`FormBuilder` 对象(用 `f` 表示)创建了两个标签和两个文本字段,一个用于文章标题,一个用于文章内容。最后,在 `f` 对象上调用 `submit` 方法,创建一个提交按钮。 - -不过这个表单还有个问题。如果查看这个页面的源码,会发现表单 `action` 属性的值是 `/articles/new`。这就是问题所在,因为其指向的地址就是现在这个页面,而这个页面是用来显示新建文章表单的。 - -要想转到其他地址,就要使用其他的地址。这个问题可使用 `form_for` 方法的 `:url` 选项解决。在 Rails 中,用来处理新建资源表单提交数据的动作是 `create`,所以表单应该转向这个动作。 - -修改 `app/views/articles/new.html.erb` 文件中的 `form_for`,改成这样: - -```erb -<%= form_for :article, url: articles_path do |f| %> -``` - -这里,我们把 `:url` 选项的值设为 `articles_path` 帮助方法。要想知道这个方法有什么作用,我们要回过头再看一下 `rake routes` 的输出: - -```bash -$ bin/rake routes - Prefix Verb URI Pattern Controller#Action - articles GET /articles(.:format) articles#index - POST /articles(.:format) articles#create - new_article GET /articles/new(.:format) articles#new -edit_article GET /articles/:id/edit(.:format) articles#edit - article GET /articles/:id(.:format) articles#show - PATCH /articles/:id(.:format) articles#update - PUT /articles/:id(.:format) articles#update - DELETE /articles/:id(.:format) articles#destroy - root GET / welcome#index -``` - -`articles_path` 帮助方法告诉 Rails,对应的地址是 `/articles`,默认情况下,这个表单会向这个路由发起 `POST` 请求。这个路由对应于 `ArticlesController` 控制器的 `create` 动作。 - -表单写好了,路由也定义了,现在可以填写表单,然后点击提交按钮新建文章了。请实际操作一下。提交表单后,会看到一个熟悉的错误: - -![ArticlesController 控制器不知如何处理 create 动作](images/getting_started/unknown_action_create_for_articles.png) - -解决这个错误,要在 `ArticlesController` 控制器中定义 `create` 动作。 - -### 创建文章 - -要解决前一节出现的错误,可以在 `ArticlesController` 类中定义 `create` 方法。在 `app/controllers/articles_controller.rb` 文件中 `new` 方法后面添加以下代码: - -```ruby -class ArticlesController < ApplicationController - def new - end - - def create - end -end -``` - -然后再次提交表单,会看到另一个熟悉的错误:找不到模板。现在暂且不管这个错误。`create` 动作的作用是把新文章保存到数据库中。 - -提交表单后,其中的字段以参数的形式传递给 Rails。这些参数可以在控制器的动作中使用,完成指定的操作。要想查看这些参数的内容,可以把 `create` 动作改成: - -```ruby -def create - render plain: params[:article].inspect -end -``` - -`render` 方法接受一个简单的 Hash 为参数,这个 Hash 的键是 `plain`,对应的值为 `params[:article].inspect`。`params` 方法表示通过表单提交的参数,返回 `ActiveSupport::HashWithIndifferentAccess` 对象,可以使用字符串或者 Symbol 获取键对应的值。现在,我们只关注通过表单提交的参数。 - -如果现在再次提交表单,不会再看到找不到模板错误,而是会看到类似下面的文字: - -```ruby -{"title"=>"First article!", "text"=>"This is my first article."} -``` - -`create` 动作把表单提交的参数显示出来了。不过这么做没什么用,看到了参数又怎样,什么都没发生。 - -### 创建 Article 模型 - -在 Rails 中,模型的名字使用单数,对应的数据表名使用复数。Rails 提供了一个生成器用来创建模型,大多数 Rails 开发者创建模型时都会使用。创建模型,请在终端里执行下面的命令: - -```bash -$ bin/rails generate model Article title:string text:text -``` - -这个命令告知 Rails,我们要创建 `Article` 模型,以及一个字符串属性 `title` 和文本属性 `text`。这两个属性会自动添加到 `articles` 数据表中,映射到 `Article` 模型。 - -执行这个命令后,Rails 会生成一堆文件。现在我们只关注 `app/models/article.rb` 和 `db/migrate/20140120191729_create_articles.rb`(你得到的文件名可能有点不一样)这两个文件。后者用来创建数据库结构,下一节会详细说明。 - -TIP: Active Record 很智能,能自动把数据表中的字段映射到模型的属性上。所以无需在 Rails 的模型中声明属性,因为 Active Record 会自动映射。 - -### 运行迁移 - -如前文所述,`rails generate model` 命令会在 `db/migrate` 文件夹中生成一个数据库迁移文件。迁移是一个 Ruby 类,能简化创建和修改数据库结构的操作。Rails 使用 rake 任务运行迁移,修改数据库结构后还能撤销操作。迁移的文件名中有个时间戳,这样能保证迁移按照创建的时间顺序运行。 - -`db/migrate/20140120191729_create_articles.rb`(还记得吗,你的迁移文件名可能有点不一样)文件的内容如下所示: - -```ruby -class CreateArticles < ActiveRecord::Migration - def change - create_table :articles do |t| - t.string :title - t.text :text - - t.timestamps - end - end -end -``` - -在这个迁移中定义了一个名为 `change` 的方法,在运行迁移时执行。`change` 方法中定义的操作都是可逆的,Rails 知道如何撤销这次迁移操作。运行迁移后,会创建 `articles` 表,以及一个字符串字段和文本字段。同时还会创建两个时间戳字段,用来跟踪记录的创建时间和更新时间。 - -TIP: 关于迁移的详细说明,请参阅“[Active Record 数据库迁移](active_record_migrations.html)”一文。 - -然后,使用 rake 命令运行迁移: - -```bash -$ bin/rake db:migrate -``` - -Rails 会执行迁移操作,告诉你创建了 `articles` 表。 - -```bash -== CreateArticles: migrating ================================================== --- create_table(:articles) - -> 0.0019s -== CreateArticles: migrated (0.0020s) ========================================= -``` - -NOTE: 因为默认情况下,程序运行在开发环境中,所以相关的操作应用于 `config/database.yml` 文件中 `development` 区域设置的数据库上。如果想在其他环境中运行迁移,必须在命令中指明:`rake db:migrate RAILS_ENV=production`。 - -### 在控制器中保存数据 - -再回到 `ArticlesController` 控制器,我们要修改 `create` 动作,使用 `Article` 模型把数据保存到数据库中。打开 `app/controllers/articles_controller.rb` 文件,把 `create` 动作修改成这样: - -```ruby -def create - @article = Article.new(params[:article]) - - @article.save - redirect_to @article -end -``` - -在 Rails 中,每个模型可以使用各自的属性初始化,自动映射到数据库字段上。`create` 动作中的第一行就是这个目的(还记得吗,`params[:article]` 就是我们要获取的属性)。`@article.save` 的作用是把模型保存到数据库中。保存完后转向 `show` 动作。稍后再编写 `show` 动作。 - -TIP: 后文会看到,`@article.save` 返回一个布尔值,表示保存是否成功。 - -再次访问 ,填写表单,还差一步就能创建文章了,会看到一个错误页面: - -![新建文章时禁止使用属性](images/getting_started/forbidden_attributes_for_new_article.png) - -Rails 提供了很多安全防范措施保证程序的安全,你所看到的错误就是因为违反了其中一个措施。这个防范措施叫做“健壮参数”,我们要明确地告知 Rails 哪些参数可在控制器中使用。这里,我们想使用 `title` 和 `text` 参数。请把 `create` 动作修改成: - -```ruby -def create - @article = Article.new(article_params) - - @article.save - redirect_to @article -end - -private - def article_params - params.require(:article).permit(:title, :text) - end -``` - -看到 `permit` 方法了吗?这个方法允许在动作中使用 `title` 和 `text` 属性。 - -TIP: 注意,`article_params` 是私有方法。这种用法可以防止攻击者把修改后的属性传递给模型。关于健壮参数的更多介绍,请阅读[这篇文章](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/)。 - -### 显示文章 - -现在再次提交表单,Rails 会提示找不到 `show` 动作。这个提示没多大用,我们还是先添加 `show` 动作吧。 - -我们在 `rake routes` 的输出中看到,`show` 动作的路由是: - -``` -article GET /articles/:id(.:format) articles#show -``` - -`:id` 的意思是,路由期望接收一个名为 `id` 的参数,在这个例子中,就是文章的 ID。 - -和前面一样,我们要在 `app/controllers/articles_controller.rb` 文件中添加 `show` 动作,以及相应的视图文件。 - -```ruby -def show - @article = Article.find(params[:id]) -end -``` - -有几点要注意。我们调用 `Article.find` 方法查找想查看的文章,传入的参数 `params[:id]` 会从请求中获取 `:id` 参数。我们还把文章对象存储在一个实例变量中(以 `@` 开头的变量),只有这样,变量才能在视图中使用。 - -然后,新建 `app/views/articles/show.html.erb` 文件,写入下面的代码: - -```erb -

- Title: - <%= @article.title %> -

- -

- Text: - <%= @article.text %> -

-``` - -做了以上修改后,就能真正的新建文章了。访问 ,自己试试。 - -![显示文章](images/getting_started/show_action_for_articles.png) - -### 列出所有文章 - -我们还要列出所有文章,对应的路由是: - -``` -articles GET /articles(.:format) articles#index -``` - -在 `app/controllers/articles_controller.rb` 文件中,为 `ArticlesController` 控制器添加 `index` 动作: - -```ruby -def index - @articles = Article.all -end -``` - -然后编写这个动作的视图,保存为 `app/views/articles/index.html.erb`: - -```erb -

Listing articles

- - - - - - - - <% @articles.each do |article| %> - - - - - <% end %> -
TitleText
<%= article.title %><%= article.text %>
-``` - -现在访问 ,会看到已经发布的文章列表。 - -### 添加链接 - -至此,我们可以新建、显示、列出文章了。下面我们添加一些链接,指向这些页面。 - -打开 `app/views/welcome/index.html.erb` 文件,改成这样: - -```erb -

Hello, Rails!

-<%= link_to 'My Blog', controller: 'articles' %> -``` - -`link_to` 是 Rails 内置的视图帮助方法之一,根据提供的文本和地址创建超链接。这上面这段代码中,地址是文章列表页面。 - -接下来添加到其他页面的链接。先在 `app/views/articles/index.html.erb` 中添加“New Article”链接,放在 `` 标签之前: - -```erb -<%= link_to 'New article', new_article_path %> -``` - -点击这个链接后,会转向新建文章的表单页面。 - -然后在 `app/views/articles/new.html.erb` 中添加一个链接,位于表单下面,返回到 `index` 动作: - -```erb -<%= form_for :article do |f| %> - ... -<% end %> - -<%= link_to 'Back', articles_path %> -``` - -最后,在 `app/views/articles/show.html.erb` 模板中添加一个链接,返回 `index` 动作,这样用户查看某篇文章后就可以返回文章列表页面了: - -```erb -

- Title: - <%= @article.title %> -

- -

- Text: - <%= @article.text %> -

- -<%= link_to 'Back', articles_path %> -``` - -TIP: 如果要链接到同一个控制器中的动作,不用指定 `:controller` 选项,因为默认情况下使用的就是当前控制器。 - -TIP: 在开发模式下(默认),每次请求 Rails 都会重新加载程序,因此修改之后无需重启服务器。 - -### 添加数据验证 - -模型文件,比如 `app/models/article.rb`,可以简单到只有这两行代码: - -```ruby -class Article < ActiveRecord::Base -end -``` - -文件中没有多少代码,不过请注意,`Article` 类继承自 `ActiveRecord::Base`。Active Record 提供了很多功能,包括:基本的数据库 CRUD 操作,数据验证,复杂的搜索功能,以及多个模型之间的关联。 - -Rails 为模型提供了很多方法,用来验证传入的数据。打开 `app/models/article.rb` 文件,修改成: - -```ruby -class Article < ActiveRecord::Base - validates :title, presence: true, - length: { minimum: 5 } -end -``` - -添加的这段代码可以确保每篇文章都有一个标题,而且至少有五个字符。在模型中可以验证数据是否满足多种条件,包括:字段是否存在、是否唯一,数据类型,以及关联对象是否存在。“[Active Record 数据验证](/active_record_validations.html)”一文会详细介绍数据验证。 - -添加数据验证后,如果把不满足验证条件的文章传递给 `@article.save`,会返回 `false`。打开 `app/controllers/articles_controller.rb` 文件,会发现,我们还没在 `create` 动作中检查 `@article.save` 的返回结果。如果保存失败,应该再次显示表单。为了实现这种功能,请打开 `app/controllers/articles_controller.rb` 文件,把 `new` 和 `create` 动作改成: - -```ruby -def new - @article = Article.new -end - -def create - @article = Article.new(article_params) - - if @article.save - redirect_to @article - else - render 'new' - end -end - -private - def article_params - params.require(:article).permit(:title, :text) - end -``` - -在 `new` 动作中添加了一个实例变量 `@article`。稍后你会知道为什么要这么做。 - -注意,在 `create` 动作中,如果保存失败,调用的是 `render` 方法而不是 `redirect_to` 方法。用 `render` 方法才能在保存失败后把 `@article` 对象传给 `new` 动作的视图。渲染操作和表单提交在同一次请求中完成;而 `redirect_to` 会让浏览器发起一次新请求。 - -刷新 ,提交一个没有标题的文章,Rails 会退回这个页面,但这种处理方法没多少用,你要告诉用户哪儿出错了。为了实现这种功能,请在 `app/views/articles/new.html.erb` 文件中检测错误消息: - -```erb -<%= form_for :article, url: articles_path do |f| %> - <% if @article.errors.any? %> -
-

<%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved:

-
    - <% @article.errors.full_messages.each do |msg| %> -
  • <%= msg %>
  • - <% end %> -
-
- <% end %> -

- <%= f.label :title %>
- <%= f.text_field :title %> -

- -

- <%= f.label :text %>
- <%= f.text_area :text %> -

- -

- <%= f.submit %> -

-<% end %> - -<%= link_to 'Back', articles_path %> -``` - -我们添加了很多代码,使用 `@article.errors.any?` 检查是否有错误,如果有错误,使用 `@article.errors.full_messages` 显示错误。 - -`pluralize` 是 Rails 提供的帮助方法,接受一个数字和字符串作为参数。如果数字比 1 大,字符串会被转换成复数形式。 - -在 `new` 动作中加入 `@article = Article.new` 的原因是,如果不这么做,在视图中 `@article` 的值就是 `nil`,调用 `@article.errors.any?` 时会发生错误。 - -TIP: Rails 会自动把出错的表单字段包含在一个 `div` 中,并为其添加了一个 class:`field_with_errors`。我们可以定义一些样式,凸显出错的字段。 - -再次访问 ,尝试发布一篇没有标题的文章,会看到一个很有用的错误提示。 - -![出错的表单](images/getting_started/form_with_errors.png) - -### 更新文章 - -我们已经说明了 CRUD 中的 CR 两种操作。下面进入 U 部分,更新文章。 - -首先,要在 `ArticlesController` 中添加 `edit` 动作: - -```ruby -def edit - @article = Article.find(params[:id]) -end -``` - -视图中要添加一个类似新建文章的表单。新建 `app/views/articles/edit.html.erb` 文件,写入下面的代码: - -```erb -

Editing article

- -<%= form_for :article, url: article_path(@article), method: :patch do |f| %> - <% if @article.errors.any? %> -
-

<%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved:

-
    - <% @article.errors.full_messages.each do |msg| %> -
  • <%= msg %>
  • - <% end %> -
-
- <% end %> -

- <%= f.label :title %>
- <%= f.text_field :title %> -

- -

- <%= f.label :text %>
- <%= f.text_area :text %> -

- -

- <%= f.submit %> -

-<% end %> - -<%= link_to 'Back', articles_path %> -``` - -这里的表单指向 `update` 动作,现在还没定义,稍后会添加。 - -`method: :patch` 选项告诉 Rails,提交这个表单时使用 `PATCH` 方法发送请求。根据 REST 架构,更新资源时要使用 HTTP `PATCH` 方法。 - -`form_for` 的第一个参数可以是对象,例如 `@article`,把对象中的字段填入表单。如果传入一个和实例变量(`@article`)同名的 Symbol(`:article`),效果也是一样。上面的代码使用的就是 Symbol。详情参见 [form_for 的文档](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for)。 - -然后,要在 `app/controllers/articles_controller.rb` 中添加 `update` 动作: - -```ruby -def update - @article = Article.find(params[:id]) - - if @article.update(article_params) - redirect_to @article - else - render 'edit' - end -end - -private - def article_params - params.require(:article).permit(:title, :text) - end -``` - -新定义的 `update` 方法用来处理对现有文章的更新操作,接收一个 Hash,包含想要修改的属性。和之前一样,如果更新文章出错了,要再次显示表单。 - -上面的代码再次使用了前面为 `create` 动作定义的 `article_params` 方法。 - -TIP: 不用把所有的属性都提供给 `update` 动作。例如,如果使用 `@article.update(title: 'A new title')`,Rails 只会更新 `title` 属性,不修改其他属性。 - -最后,我们想在文章列表页面,在每篇文章后面都加上一个链接,指向 `edit` 动作。打开 `app/views/articles/index.html.erb` 文件,在“Show”链接后面添加“Edit”链接: - -```erb -
- - - - - - -<% @articles.each do |article| %> - - - - - - -<% end %> -
TitleText
<%= article.title %><%= article.text %><%= link_to 'Show', article_path(article) %><%= link_to 'Edit', edit_article_path(article) %>
-``` - -我们还要在 `app/views/articles/show.html.erb` 模板的底部加上“Edit”链接: - -```erb -... - -<%= link_to 'Back', articles_path %> -| <%= link_to 'Edit', edit_article_path(@article) %> -``` - -下图是文章列表页面现在的样子: - -![在文章列表页面显示了编辑链接](images/getting_started/index_action_with_edit_link.png) - -### 使用局部视图去掉视图中的重复代码 - -编辑文章页面和新建文章页面很相似,显示表单的代码是相同的。下面使用局部视图去掉两个视图中的重复代码。按照约定,局部视图的文件名以下划线开头。 - -TIP: 关于局部视图的详细介绍参阅“[Layouts and Rendering in Rails](/layouts_and_rendering.html)”一文。 - -新建 `app/views/articles/_form.html.erb` 文件,写入以下代码: - -```erb -<%= form_for @article do |f| %> - <% if @article.errors.any? %> -
-

<%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved:

-
    - <% @article.errors.full_messages.each do |msg| %> -
  • <%= msg %>
  • - <% end %> -
-
- <% end %> -

- <%= f.label :title %>
- <%= f.text_field :title %> -

- -

- <%= f.label :text %>
- <%= f.text_area :text %> -

- -

- <%= f.submit %> -

-<% end %> -``` - -除了第一行 `form_for` 的用法变了之外,其他代码都和之前一样。之所以能在两个动作中共用一个 `form_for`,是因为 `@article` 是一个资源,对应于符合 REST 架构的路由,Rails 能自动分辨使用哪个地址和请求方法。 - -关于这种 `form_for` 用法的详细说明,请查阅 [API 文档](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style)。 - -下面来修改 `app/views/articles/new.html.erb` 视图,使用新建的局部视图,把其中的代码全删掉,替换成: - -```erb -

New article

- -<%= render 'form' %> - -<%= link_to 'Back', articles_path %> -``` - -然后按照同样地方法修改 `app/views/articles/edit.html.erb` 视图: - -```erb -

Edit article

- -<%= render 'form' %> - -<%= link_to 'Back', articles_path %> -``` - -### 删除文章 - -现在介绍 CRUD 中的 D,从数据库中删除文章。按照 REST 架构的约定,删除文章的路由是: - -```ruby -DELETE /articles/:id(.:format) articles#destroy -``` - -删除资源时使用 DELETE 请求。如果还使用 GET 请求,可以构建如下所示的恶意地址: - -```html -look at this cat! -``` - -删除资源使用 DELETE 方法,路由会把请求发往 `app/controllers/articles_controller.rb` 中的 `destroy` 动作。`destroy` 动作现在还不存在,下面来添加: - -```ruby -def destroy - @article = Article.find(params[:id]) - @article.destroy - - redirect_to articles_path -end -``` - -想把记录从数据库删除,可以在 Active Record 对象上调用 `destroy` 方法。注意,我们无需为这个动作编写视图,因为它会转向 `index` 动作。 - -最后,在 `index` 动作的模板(`app/views/articles/index.html.erb`)中加上“Destroy”链接: - -```erb -

Listing Articles

-<%= link_to 'New article', new_article_path %> - - - - - - - -<% @articles.each do |article| %> - - - - - - - -<% end %> -
TitleText
<%= article.title %><%= article.text %><%= link_to 'Show', article_path(article) %><%= link_to 'Edit', edit_article_path(article) %><%= link_to 'Destroy', article_path(article), - method: :delete, data: { confirm: 'Are you sure?' } %>
-``` - -生成“Destroy”链接的 `link_to` 用法有点不一样,第二个参数是具名路由,随后还传入了几个参数。`:method` 和 `:'data-confirm'` 选项设置链接的 HTML5 属性,点击链接后,首先会显示一个对话框,然后发起 DELETE 请求。这两个操作通过 `jquery_ujs` 这个 JavaScript 脚本实现。生成程序骨架时,会自动把 `jquery_ujs` 加入程序的布局中(`app/views/layouts/application.html.erb`)。没有这个脚本,就不会显示确认对话框。 - -![确认对话框](images/getting_started/confirm_dialog.png) - -恭喜,现在你可以新建、显示、列出、更新、删除文章了。 - -TIP: 一般情况下,Rails 建议使用资源对象,而不手动设置路由。关于路由的详细介绍参阅“[Rails 路由全解](/routing.html)”一文。 - -添加第二个模型 ------------- - -接下来要在程序中添加第二个模型,用来处理文章的评论。 - -### 生成模型 - -下面要用到的生成器,和之前生成 `Article` 模型的一样。我们要创建一个 `Comment` 模型,表示文章的评论。在终端执行下面的命令: - -```bash -$ rails generate model Comment commenter:string body:text article:references -``` - -这个命令生成四个文件: - -| 文件 | 作用 | -|----------------------------------------------|----------------------------------------------------| -| db/migrate/20140120201010_create_comments.rb | 生成 comments 表所用的迁移文件(你得到的文件名稍有不同) | -| app/models/comment.rb | Comment 模型文件 | -| test/models/comment_test.rb | Comment 模型的测试文件 | -| test/fixtures/comments.yml | 测试时使用的固件 | - -首先来看一下 `app/models/comment.rb` 文件: - -```ruby -class Comment < ActiveRecord::Base - belongs_to :article -end -``` - -文件的内容和前面的 `Article` 模型差不多,不过多了一行代码:`belongs_to :article`。这行代码用来建立 Active Record 关联。下文会简单介绍关联。 - -除了模型文件,Rails 还生成了一个迁移文件,用来创建对应的数据表: - -```ruby -class CreateComments < ActiveRecord::Migration - def change - create_table :comments do |t| - t.string :commenter - t.text :body - - # this line adds an integer column called `article_id`. - t.references :article, index: true - - t.timestamps - end - end -end -``` - -`t.references` 这行代码为两个模型的关联创建一个外键字段,同时还为这个字段创建了索引。下面运行这个迁移: - -```bash -$ rake db:migrate -``` - -Rails 相当智能,只会执行还没有运行的迁移,在命令行中会看到以下输出: - -```bash -== CreateComments: migrating ================================================= --- create_table(:comments) - -> 0.0115s -== CreateComments: migrated (0.0119s) ======================================== -``` - -### 模型关联 - -使用 Active Record 关联可以轻易的建立两个模型之间的关系。评论和文章之间的关联是这样的: - -* 评论属于一篇文章 -* 一篇文章有多个评论 - -这种关系和 Rails 用来声明关联的句法具有相同的逻辑。我们已经看过 `Comment` 模型中那行代码,声明评论属于文章: - -```ruby -class Comment < ActiveRecord::Base - belongs_to :article -end -``` - -我们要编辑 `app/models/article.rb` 文件,加入这层关系的另一端: - -```ruby -class Article < ActiveRecord::Base - has_many :comments - validates :title, presence: true, - length: { minimum: 5 } -end -``` - -这两行声明能自动完成很多操作。例如,如果实例变量 `@article` 是一个文章对象,可以使用 `@article.comments` 取回一个数组,其元素是这篇文章的评论。 - -TIP: 关于 Active Record 关联的详细介绍,参阅“[Active Record 关联](/association_basics.html)”一文。 - -### 添加评论的路由 - -和 `article` 控制器一样,添加路由后 Rails 才知道在哪个地址上查看评论。打开 `config/routes.rb` 文件,按照下面的方式修改: - -```ruby -resources :articles do - resources :comments -end -``` - -我们把 `comments` 放在 `articles` 中,这叫做嵌套资源,表明了文章和评论间的层级关系。 - -TIP: 关于路由的详细介绍,参阅“[Rails 路由全解](/routing.html)”一文。 - -### 生成控制器 - -有了模型,下面要创建控制器了,还是使用前面用过的生成器: - -```bash -$ rails generate controller Comments -``` - -这个命令生成六个文件和一个空文件夹: - -| 文件/文件夹 | 作用 | -| -------------------------------------------- | ---------------------------------------- | -| app/controllers/comments_controller.rb | Comments 控制器文件 | -| app/views/comments/ | 控制器的视图存放在这个文件夹里 | -| test/controllers/comments_controller_test.rb | 控制器测试文件 | -| app/helpers/comments_helper.rb | 视图帮助方法文件 | -| test/helpers/comments_helper_test.rb | 帮助方法测试文件 | -| app/assets/javascripts/comment.js.coffee | 控制器的 CoffeeScript 文件 | -| app/assets/stylesheets/comment.css.scss | 控制器的样式表文件 | - -在任何一个博客中,读者读完文章后就可以发布评论。评论发布后,会转向文章显示页面,查看自己的评论是否显示出来了。所以,`CommentsController` 中要定义新建评论的和删除垃圾评论的方法。 - -首先,修改显示文章的模板(`app/views/articles/show.html.erb`),允许读者发布评论: - -```erb -

- Title: - <%= @article.title %> -

- -

- Text: - <%= @article.text %> -

- -

Add a comment:

-<%= form_for([@article, @article.comments.build]) do |f| %> -

- <%= f.label :commenter %>
- <%= f.text_field :commenter %> -

-

- <%= f.label :body %>
- <%= f.text_area :body %> -

-

- <%= f.submit %> -

-<% end %> - -<%= link_to 'Back', articles_path %> -| <%= link_to 'Edit', edit_article_path(@article) %> -``` - -上面的代码在显示文章的页面添加了一个表单,调用 `CommentsController` 控制器的 `create` 动作发布评论。`form_for` 的参数是个数组,构建嵌套路由,例如 `/articles/1/comments`。 - -下面在 `app/controllers/comments_controller.rb` 文件中定义 `create` 方法: - -```ruby -class CommentsController < ApplicationController - def create - @article = Article.find(params[:article_id]) - @comment = @article.comments.create(comment_params) - redirect_to article_path(@article) - end - - private - def comment_params - params.require(:comment).permit(:commenter, :body) - end -end -``` - -这里使用的代码要比文章的控制器复杂得多,因为设置了嵌套关系,必须这么做评论功能才能使用。发布评论时要知道这个评论属于哪篇文章,所以要在 `Article` 模型上调用 `find` 方法查找文章对象。 - -而且,这段代码还充分利用了关联关系生成的方法。我们在 `@article.comments` 上调用 `create` 方法,创建并保存评论。这么做能自动把评论和文章联系起来,让这个评论属于这篇文章。 - -添加评论后,调用 `article_path(@article)` 帮助方法,转向原来的文章页面。前面说过,这个帮助函数调用 `ArticlesController` 的 `show` 动作,渲染 `show.html.erb` 模板。我们要在这个模板中显示评论,所以要修改一下 `app/views/articles/show.html.erb`: - -```erb -

- Title: - <%= @article.title %> -

- -

- Text: - <%= @article.text %> -

- -

Comments

-<% @article.comments.each do |comment| %> -

- Commenter: - <%= comment.commenter %> -

- -

- Comment: - <%= comment.body %> -

-<% end %> - -

Add a comment:

-<%= form_for([@article, @article.comments.build]) do |f| %> -

- <%= f.label :commenter %>
- <%= f.text_field :commenter %> -

-

- <%= f.label :body %>
- <%= f.text_area :body %> -

-

- <%= f.submit %> -

-<% end %> - -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> -``` - -现在,可以为文章添加评论了,成功添加后,评论会在正确的位置显示。 - -![文章的评论](images/getting_started/article_with_comments.png) - -重构 ----- - -现在博客的文章和评论都能正常使用了。看一下 `app/views/articles/show.html.erb` 模板,内容太多。下面使用局部视图重构。 - -### 渲染局部视图中的集合 - -首先,把显示文章评论的代码抽出来,写入局部视图中。新建 `app/views/comments/_comment.html.erb` 文件,写入下面的代码: - -```erb -

- Commenter: - <%= comment.commenter %> -

- -

- Comment: - <%= comment.body %> -

-``` - -然后把 `app/views/articles/show.html.erb` 修改成: - -```erb -

- Title: - <%= @article.title %> -

- -

- Text: - <%= @article.text %> -

- -

Comments

-<%= render @article.comments %> - -

Add a comment:

-<%= form_for([@article, @article.comments.build]) do |f| %> -

- <%= f.label :commenter %>
- <%= f.text_field :commenter %> -

-

- <%= f.label :body %>
- <%= f.text_area :body %> -

-

- <%= f.submit %> -

-<% end %> - -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> -``` - -这个视图会使用局部视图 `app/views/comments/_comment.html.erb` 渲染 `@article.comments` 集合中的每个评论。`render` 方法会遍历 `@article.comments` 集合,把每个评论赋值给一个和局部视图同名的本地变量,在这个例子中本地变量是 `comment`,这个本地变量可以在局部视图中使用。 - -### 渲染局部视图中的表单 - -我们把添加评论的代码也移到局部视图中。新建 `app/views/comments/_form.html.erb` 文件,写入: - -```erb -<%= form_for([@article, @article.comments.build]) do |f| %> -

- <%= f.label :commenter %>
- <%= f.text_field :commenter %> -

-

- <%= f.label :body %>
- <%= f.text_area :body %> -

-

- <%= f.submit %> -

-<% end %> -``` - -然后把 `app/views/articles/show.html.erb` 改成: - -```erb -

- Title: - <%= @article.title %> -

- -

- Text: - <%= @article.text %> -

- -

Comments

-<%= render @article.comments %> - -

Add a comment:

-<%= render "comments/form" %> - -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> -``` - -第二个 `render` 方法的参数就是要渲染的局部视图,即 `comments/form`。Rails 很智能,能解析其中的斜线,知道要渲染 `app/views/comments` 文件夹中的 `_form.html.erb` 模板。 - -`@article` 变量在所有局部视图中都可使用,因为它是实例变量。 - -删除评论 -------- - -博客还有一个重要的功能是删除垃圾评论。为了实现这个功能,要在视图中添加一个链接,并在 `CommentsController` 中定义 `destroy` 动作。 - -先在 `app/views/comments/_comment.html.erb` 局部视图中加入删除评论的链接: - -```erb -

- Commenter: - <%= comment.commenter %> -

- -

- Comment: - <%= comment.body %> -

- -

- <%= link_to 'Destroy Comment', [comment.article, comment], - method: :delete, - data: { confirm: 'Are you sure?' } %> -

-``` - -点击“Destroy Comment”链接后,会向 `CommentsController` 控制器发起 `DELETE /articles/:article_id/comments/:id` 请求。我们可以从这个请求中找到要删除的评论。下面在控制器中加入 `destroy` 动作(`app/controllers/comments_controller.rb`): - -```ruby -class CommentsController < ApplicationController - def create - @article = Article.find(params[:article_id]) - @comment = @article.comments.create(comment_params) - redirect_to article_path(@article) - end - - def destroy - @article = Article.find(params[:article_id]) - @comment = @article.comments.find(params[:id]) - @comment.destroy - redirect_to article_path(@article) - end - - private - def comment_params - params.require(:comment).permit(:commenter, :body) - end -end -``` - -`destroy` 动作先查找当前文章,然后在 `@article.comments` 集合中找到对应的评论,将其从数据库中删掉,最后转向显示文章的页面。 - -### 删除关联对象 - -如果删除一篇文章,也要删除文章中的评论,不然这些评论会占用数据库空间。在 Rails 中可以在关联中指定 `dependent` 选项达到这一目的。把 `Article` 模型(`app/models/article.rb`)修改成: - -```ruby -class Article < ActiveRecord::Base - has_many :comments, dependent: :destroy - validates :title, presence: true, - length: { minimum: 5 } -end -``` - -安全 ----- - -### 基本认证 - -如果把这个博客程序放在网上,所有人都能添加、编辑、删除文章和评论。 - -Rails 提供了一种简单的 HTTP 身份认证机制可以避免出现这种情况。 - -在 `ArticlesController` 中,我们要用一种方法禁止未通过认证的用户访问其中几个动作。我们需要的是 `http_basic_authenticate_with` 方法,通过这个方法的认证后才能访问所请求的动作。 - -要使用这个身份认证机制,需要在 `ArticlesController` 控制器的顶部调用 `http_basic_authenticate_with` 方法。除了 `index` 和 `show` 动作,访问其他动作都要通过认证,所以在 `app/controllers/articles_controller.rb` 中,要这么做: - -```ruby -class ArticlesController < ApplicationController - - http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] - - def index - @articles = Article.all - end - - # snipped for brevity -``` - -同时,我们还希望只有通过认证的用户才能删除评论。修改 `CommentsController` 控制器(`app/controllers/comments_controller.rb`): - -```ruby -class CommentsController < ApplicationController - - http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy - - def create - @article = Article.find(params[:article_id]) - ... - end - - # snipped for brevity -``` - -现在,如果想新建文章,会看到一个 HTTP 基本认证对话框。 - -![HTTP 基本认证对话框](images/getting_started/challenge.png) - -其他的身份认证方法也可以在 Rails 程序中使用。其中两个比较流行的是 [Devise](https://github.com/plataformatec/devise) 引擎和 [Authlogic](https://github.com/binarylogic/authlogic) gem。 - -### 其他安全注意事项 - -安全,尤其是在网页程序中,是个很宽泛和值得深入研究的领域。Rails 程序的安全措施,在“[Ruby on Rails 安全指南](/security.html)”中有更深入的说明。 - -接下来做什么 ------------ - -至此,我们开发了第一个 Rails 程序,请尽情地修改、试验。在开发过程中难免会需要帮助,如果使用 Rails 时需要协助,可以使用这些资源: - -* [Ruby on Rails 指南](http://guides.ruby-china.org/index.html) -* [Ruby on Rails 教程](http://railstutorial-china.org) -* [Ruby on Rails 邮件列表](http://groups.google.com/group/rubyonrails-talk) -* irc.freenode.net 上的 [#rubyonrails](irc://irc.freenode.net/#rubyonrails) 频道 - -Rails 本身也提供了帮助文档,可以使用下面的 rake 任务生成: - -* 运行 `rake doc:guides`,会在程序的 `doc/guides` 文件夹中生成一份 Rails 指南。在浏览器中打开 `doc/guides/index.html` 可以查看这份指南。 -* 运行 `rake doc:rails`,会在程序的 `doc/api` 文件夹中生成一份完整的 API 文档。在浏览器中打开 `doc/api/index.html` 可以查看 API 文档。 - -TIP: 使用 `doc:guides` 任务在本地生成 Rails 指南,要安装 RedCloth gem。在 `Gemfile` 中加入这个 gem,然后执行 `bundle install` 命令即可。 - -常见问题 -------- - -使用 Rails 时,最好使用 UTF-8 编码存储所有外部数据。如果没使用 UTF-8 编码,Ruby 的代码库和 Rails 一般都能将其转换成 UTF-8,但不一定总能成功,所以最好还是确保所有的外部数据都使用 UTF-8 编码。 - -如果编码出错,常见的征兆是浏览器中显示很多黑色方块和问号。还有一种常见的符号是“ü”,包含在“ü”中。Rails 内部采用很多方法尽量避免出现这种问题。如果你使用的外部数据编码不是 UTF-8,有时会出现这些问题,Rails 无法自动纠正。 - -非 UTF-8 编码的数据经常来源于: - -* 你的文本编辑器:大多数文本编辑器(例如 TextMate)默认使用 UTF-8 编码保存文件。如果你的编辑器没使用 UTF-8 编码,有可能是你在模板中输入了特殊字符(例如 é),在浏览器中显示为方块和问号。这种问题也会出现在国际化文件中。默认不使用 UTF-8 保存文件的编辑器(例如 Dreamweaver 的某些版本)都会提供一种方法,把默认编码设为 UTF-8。记得要修改。 -* 你的数据库:默认情况下,Rails 会把从数据库中取出的数据转换成 UTF-8 格式。如果数据库内部不使用 UTF-8 编码,就无法保存用户输入的所有字符。例如,数据库内部使用 Latin-1 编码,用户输入俄语、希伯来语或日语字符时,存进数据库时就会永远丢失。如果可能,在数据库中尽量使用 UTF-8 编码。 diff --git a/source/zh-CN/i18n.md b/source/zh-CN/i18n.md deleted file mode 100644 index 70a9732..0000000 --- a/source/zh-CN/i18n.md +++ /dev/null @@ -1,1043 +0,0 @@ -Rails 国际化 API -================ - -Rails 内建支持 Ruby I18n(internationalization 的简写)。Ruby I18n 这个 gem 用法简单,是个扩展性强的框架,可以把程序翻译成英语之外的其他语言,或者为程序提供多种语言支持。 - -“国际化”是指把程序中的字符串或者其他需要本地化的内容(例如,日期和货币的格式)提取出来的过程。“本地化”是指把提取出来的内容翻译成本地语言或者本地所用格式的过程。 - -所以在 Rails 程序国际化的过程中要做这些事情: - -* 确保程序支持 i18n; -* 告诉 Rails 在哪里寻找本地语言包; -* 告诉 Rails 怎么设置、保存和切换本地语言包; - -在本地化程序的过程中或许要做以下三件事: - -* 修改或提供 Rails 默认使用的本地语言包,例如,日期和时间的格式、月份名、Active Record 模型名等; -* 把程序中的字符串提取出来,保存到键值对中,例如 Flash 消息、视图中的纯文本等; -* 在某个地方保存语言包; - -本文会详细介绍 I18n API,并提供一个教程,演示如何从头开始国际化一个 Rails 程序。 - -读完本文,你将学到: - -* Ruby on Rails 是如何处理 i18n 的; -* 如何在 REST 架构的程序中正确使用 i18n; -* 如何使用 i18n 翻译 ActiveRecord 错误和 ActionMailer E-mail; -* 协助翻译程序的工具; - --------------------------------------------------------------------------------- - -NOTE: Ruby I18n 框架提供了 Rails 程序国际化和本地化所需的各种功能。不过,还可以使用各种插件和扩展,添加额外的功能。详情参见 [Ruby I18n 的维基](http://ruby-i18n.org/wiki)。 - -Ruby on Rails 是如何处理 i18n 的 ------------------------------- - -国际化是个很复杂的问题。自然语言千差万别(例如复数变形规则),很难提供一种工具解决所有问题。因此,Rails I18n API 只关注: - -* 默认支持和英语类似的语言; -* 让支持其他语言变得简单; - -Rails 框架中的每个静态字符串(例如,Active Record 数据验证消息,日期和时间的格式)都支持国际化,因此本地化时只要重写默认值即可。 - -### Ruby I18n 的整体架构 - -Ruby I18n 分成两部分: - -* 公开的 API:这是一个 Ruby 模块,定义了库中可用的公开方法; -* 一个默认的后台(特意取名为“Simple”),实现这些方法; - -普通用户只要使用这些公开方法即可,但了解后台的功能也有助于使用 i18n API。 - -NOTE: 默认提供的“Simple”后台可以用其他强大的后台替代(推荐这么做),例如把翻译后的数据存储在关系型数据库中,或 GetText 语言包中。详情参见下文的“[使用其他后台](#using-different-backends)”一节。 - -### 公开 API - -I18n API 最重要的方法是: - -```ruby -translate # Lookup text translations -localize # Localize Date and Time objects to local formats -``` - -这两个方法都有别名,分别为 `#t` 和 `#l`。因此可以这么用: - -```ruby -I18n.t 'store.title' -I18n.l Time.now -``` - -I18n API 同时还提供了针对下述属性的读取和设值方法: - -```ruby -load_path # Announce your custom translation files -locale # Get and set the current locale -default_locale # Get and set the default locale -exception_handler # Use a different exception_handler -backend # Use a different backend -``` - -下一节起,我们要从零开始国际化一个简单的 Rails 程序。 - -为国际化做准备 ------------- - -为程序提供 I18n 支持只需简单几步。 - -### 配置 I18n 模块 - -按照“多约定,少配置”原则,Rails 会为程序提供一些合理的默认值。如果想使用其他设置,可以很容易的改写默认值。 - -Rails 会自动把 `config/locales` 文件夹中所有 `.rb` 和 `.yml` 文件加入**译文加载路径**。 - -默认提供的 `en.yml` 文件中包含一些简单的翻译文本: - -```ruby -en: - hello: "Hello world" -``` - -上面这段代码的意思是,在 `:en` 语言中,`hello` 键映射到字符串 `"Hello world"` 上。Rails 中的每个字符串的国际化都使用这种方式,比如说 Active Model [数据验证消息](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml)以及[日期和时间格式](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml)。在默认的后台中,可以使用 YAML 或标准的 Ruby Hash 存储翻译数据。 - -I18n 库使用的默认语言是**英语**,所以如果没设为其他语言,就会用 `:en` 查找翻译数据。 - -NOTE: 经过[讨论](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)之后,i18n 库决定为语言名称使用一种**务实的方案**,只说明所用语言(例如,`:en`,`:pl`),不区分地区(例如,`:en-US`,`:en-GB`)。地区经常用来区分同一语言在不同地区的分支或者方言。很多国际化程序只使用语言名称,例如 `:cs`、`:th` 和 `:es`(分别为捷克语,泰语和西班牙语)。不过,同一语种在不同地区可能有重要差别。例如,在 `:en-US` 中,货币符号是“$”,但在 `:en-GB` 中是“£”。在 Rails 中使用区分地区的语言设置也是可行的,只要在 `:en-GB` 中使用完整的“English - United Kingdom”即可。很多 [Rails I18n 插件](http://rails-i18n.org/wiki),例如 [Globalize3](https://github.com/globalize/globalize),都可以实现。 - -**译文加载路径**(`I18n.load_path`)是一个 Ruby 数组,由译文文件的路径组成,Rails 程序会自动加载这些文件。你可以使用任何一个文件夹,任何一种文件命名方式。 - -NOTE: 首次加载查找译文时,后台会惰性加载这些译文。这么做即使已经声明过,也可以更换所用后台。 - -`application.rb` 文件中的默认内容有介绍如何从其他文件夹中添加本地数据,以及如何设置默认使用的语言。去掉相关代码行前面的注释,修改即可。 - -```ruby -# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. -# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] -# config.i18n.default_locale = :de -``` - -### (选做)更改 I18n 库的设置 - -如果基于某些原因不想使用 `application.rb` 文件中的设置,我们来介绍一下手动设置的方法。 - -告知 I18n 库在哪里寻找译文文件,可以在程序的任何地方指定加载路径。但要保证这个设置要在加载译文之前执行。我们可能还要修改默认使用的语言。要完成这两个设置,最简单的方法是把下面的代码放到一个初始化脚本中: - -```ruby -# in config/initializers/locale.rb - -# tell the I18n library where to find your translations -I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')] - -# set default locale to something other than :en -I18n.default_locale = :pt -``` - -### 设置并传入所用语言 - -如果想把 Rails 程序翻译成英语(默认语言)之外的其他语言,可以在 `application.rb` 文件或初始化脚本中设置 `I18n.default_locale` 选项。这个设置对所有请求都有效。 - -不过,或许你希望为程序提供多种语言支持。此时,需要在请求中指定所用语言。 - -WARNING: 你可能想过把用户选择适用的语言保存在会话或 cookie 中,请**千万别**这么做。所用语言应该是透明的,写在 URL 中。这样做不会破坏用户对网页内容的预想,如果把 URL 发给一个朋友,他应该看到和我相同的内容。这种方式在 [REST 架构](http://en.wikipedia.org/wiki/Representational_State_Transfer)中很容易实现。不过也有不适用 REST 架构的情况,后文会说明。 - -设置所用语言的方法很简单,只需在 `ApplicationController` 的 `before_action` 中作如下设定即可: - -```ruby -before_action :set_locale - -def set_locale - I18n.locale = params[:locale] || I18n.default_locale -end -``` - -使用这种方法要把语言作为 URL 查询参数传入,例如 `http://example.com/books?locale=pt`(这是 Google 使用的方法)。`http://localhost:3000?locale=pt` 会加载葡萄牙语,`http://localhost:3000?locale=de` 会加载德语,以此类推。如果想手动在 URL 中指定语言再刷新页面,可以跳过后面几小节,直接阅读“[国际化程序](#internationalizing-your-application)”一节。 - -当然了,你可能并不想手动在每个 URL 中指定语言,或者想使用其他形式的 URL,例如 `http://example.com/pt/books` 或 `http://example.com/en/books`。下面分别介绍其他各种设置语言的方法。 - -### 使用不同的域名加载不同的语言 - -设置所用语言可以通过不同的域名实现。例如,`www.example.com` 加载英语内容,`www.example.es` 加载西班牙语内容。这里使用的是不同的顶级域名。这么做有多个好处: - -* 所用语言在 URL 中很明显; -* 用户很容易得知所查看内容使用的语言; -* 在 Rails 中可以轻松实现; -* 搜索引擎似乎喜欢把不同语言的内容放在不同但相互关联的域名上; - -在 `ApplicationController` 中加入如下代码可以实现这种处理方式: - -```ruby -before_action :set_locale - -def set_locale - I18n.locale = extract_locale_from_tld || I18n.default_locale -end - -# Get locale from top-level domain or return nil if such locale is not available -# You have to put something like: -# 127.0.0.1 application.com -# 127.0.0.1 application.it -# 127.0.0.1 application.pl -# in your /etc/hosts file to try this out locally -def extract_locale_from_tld - parsed_locale = request.host.split('.').last - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil -end -``` - -类似地,还可以使用不同的二级域名提供不同语言的内容: - -```ruby -# Get locale code from request subdomain (like http://it.application.local:3000) -# You have to put something like: -# 127.0.0.1 gr.application.local -# in your /etc/hosts file to try this out locally -def extract_locale_from_subdomain - parsed_locale = request.subdomains.first - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil -end -``` - -如果程序中需要切换语言的连接,可以这么写: - -```ruby -link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}") -``` - -上述代码假设 `APP_CONFIG[:deutsch_website_url]` 的值为 `http://www.application.de`。 - -这种方法虽有种种好处,但你或许不想在不同的域名上提供不同语言的内容。最好的实现方式肯定是在 URL 的参数中加上语言代码。 - -### 在 URL 参数中设置所用语言 - - -The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case. - -This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though. - -Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(/service/locale: I18n.locale))`) would be tedious and probably impossible, of course. - -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). - -We can include something like this in our `ApplicationController` then: - -```ruby -# app/controllers/application_controller.rb -def default_url_options(options={}) - logger.debug "default_url_options is passed options: #{options.inspect}\n" - { locale: I18n.locale } -end -``` - -Every helper method dependent on `url_for` (e.g. helpers for named routes like `root_path` or `root_url`, resource routes like `books_path` or `books_url`, etc.) will now **automatically include the locale in the query string**, like this: `http://localhost:3001/?locale=ja`. - -You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this. - -You probably want URLs to look like this: `www.example.com/en/books` (which loads the English locale) and `www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scoping`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html) option in this way: - -```ruby -# config/routes.rb -scope "/:locale" do - resources :books -end -``` - -Now, when you call the `books_path` method you should get `"/en/books"` (for the default locale). An URL like `http://localhost:3001/nl/books` should load the Dutch locale, then, and following calls to `books_path` should return `"/nl/books"` (because the locale changed). - -If you don't want to force the use of a locale in your routes you can use an optional path scope (denoted by the parentheses) like so: - -```ruby -# config/routes.rb -scope "(:locale)", locale: /en|nl/ do - resources :books -end -``` - -With this approach you will not get a `Routing Error` when accessing your resources such as `http://localhost:3001/books` without a locale. This is useful for when you want to use the default locale when one is not specified. - -Of course, you need to take special care of the root URL (usually "homepage" or "dashboard") of your application. An URL like `http://localhost:3001/nl` will not work automatically, because the `root to: "books#index"` declaration in your `routes.rb` doesn't take locale into account. (And rightly so: there's only one "root" URL.) - -You would probably need to map URLs like these: - -```ruby -# config/routes.rb -get '/:locale' => 'dashboard#index' -``` - -Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.) - -NOTE: Have a look at two plugins which simplify work with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master) and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master). - -### Setting the Locale from the Client Supplied Information - -In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above. - -#### Using `Accept-Language` - -One source of client supplied information would be an `Accept-Language` HTTP header. People may [set this in their browser](http://www.w3.org/International/questions/qa-lang-priorities) or other clients (such as _curl_). - -A trivial implementation of using an `Accept-Language` header would be: - -```ruby -def set_locale - logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}" - I18n.locale = extract_locale_from_accept_language_header - logger.debug "* Locale set to '#{I18n.locale}'" -end - -private - def extract_locale_from_accept_language_header - request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first - end -``` - -Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). - -#### Using GeoIP (or Similar) Database - -Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. - -#### User Profile - -You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. - -Internationalizing your Application ------------------------------------ - -OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff. - -Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide necessary translations for these abstracts. - -You most probably have something like this in one of your applications: - -```ruby -# config/routes.rb -Yourapp::Application.routes.draw do - root to: "home#index" -end -``` - -```ruby -# app/controllers/application_controller.rb -class ApplicationController < ActionController::Base - before_action :set_locale - - def set_locale - I18n.locale = params[:locale] || I18n.default_locale - end -end -``` - -```ruby -# app/controllers/home_controller.rb -class HomeController < ApplicationController - def index - flash[:notice] = "Hello Flash" - end -end -``` - -```erb -# app/views/home/index.html.erb -

Hello World

-

<%= flash[:notice] %>

-``` - -![rails i18n demo untranslated](images/i18n/demo_untranslated.png) - -### Adding Translations - -Obviously there are **two strings that are localized to English**. In order to internationalize this code, **replace these strings** with calls to Rails' `#t` helper with a key that makes sense for the translation: - -```ruby -# app/controllers/home_controller.rb -class HomeController < ApplicationController - def index - flash[:notice] = t(:hello_flash) - end -end -``` - -```erb -# app/views/home/index.html.erb -

<%=t :hello_world %>

-

<%= flash[:notice] %>

-``` - -When you now render this view, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing. - -![rails i18n demo translation missing](images/i18n/demo_translation_missing.png) - -NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do not need to spell out `I18n.t` all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a ``. - -So let's add the missing translations into the dictionary files (i.e. do the "localization" part): - -```ruby -# config/locales/en.yml -en: - hello_world: Hello world! - hello_flash: Hello flash! - -# config/locales/pirate.yml -pirate: - hello_world: Ahoy World - hello_flash: Ahoy Flash -``` - -There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows: - -![rails i18n demo translated to English](images/i18n/demo_translated_en.png) - -And when you change the URL to pass the pirate locale (`http://localhost:3000?locale=pirate`), you'll get: - -![rails i18n demo translated to pirate](images/i18n/demo_translated_pirate.png) - -NOTE: You need to restart the server when you add new locale files. - -You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.) - -### Passing variables to translations - -You can use variables in the translation messages and pass their values from the view. - -```erb -# app/views/home/index.html.erb -<%=t 'greet_username', user: "Bill", message: "Goodbye" %> -``` - -```yaml -# config/locales/en.yml -en: - greet_username: "%{message}, %{user}!" -``` - -### Adding Date/Time Formats - -OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used. - -```erb -# app/views/home/index.html.erb -

<%=t :hello_world %>

-

<%= flash[:notice] %>

<%= l Time.now, format: :short %>

-``` - -And in our pirate translations file let's add a time format (it's already there in Rails' defaults for English): - -```ruby -# config/locales/pirate.yml -pirate: - time: - formats: - short: "arrrround %H'ish" -``` - -So that would give you: - -![rails i18n demo localized time to pirate](images/i18n/demo_localized_pirate.png) - -TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at GitHub](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use. - -### Inflection Rules For Other Locales - -Rails allows you to define inflection rules (such as rules for singularization and pluralization) for locales other than English. In `config/initializers/inflections.rb`, you can define these rules for multiple locales. The initializer contains a default example for specifying additional rules for English; follow that format for other locales as you see fit. - -### Localized Views - -Let's say you have a _BooksController_ in your application. Your _index_ action renders content in `app/views/books/index.html.erb` template. When you put a _localized variant_ of this template: `index.es.html.erb` in the same directory, Rails will render content in this template, when the locale is set to `:es`. When the locale is set to the default locale, the generic `index.html.erb` view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in `public`, etc.) - -You can make use of this feature, e.g. when working with a large amount of static content, which would be clumsy to put inside YAML or Ruby dictionaries. Bear in mind, though, that any change you would like to do later to the template must be propagated to all of them. - -### Organization of Locale Files - -When you are using the default SimpleStore shipped with the i18n library, dictionaries are stored in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you. - -For example, your `config/locales` directory could look like this: - -``` -|-defaults -|---es.rb -|---en.rb -|-models -|---book -|-----es.rb -|-----en.rb -|-views -|---defaults -|-----es.rb -|-----en.rb -|---books -|-----es.rb -|-----en.rb -|---users -|-----es.rb -|-----en.rb -|---navigation -|-----es.rb -|-----en.rb -``` - -This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (e.g. date and time formats). Other stores for the i18n library could provide different means of such separation. - -NOTE: The default locale loading mechanism in Rails does not load locale files in nested dictionaries, like we have here. So, for this to work, we must explicitly tell Rails to look further: - -```ruby - # config/application.rb - config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] - -``` - -Do check the [Rails i18n Wiki](http://rails-i18n.org/wiki) for list of tools available for managing translations. - -Overview of the I18n API Features ---------------------------------- - -You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. - -These chapters will show examples using both the `I18n.translate` method as well as the [`translate` view helper method](http://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate) (noting the additional feature provide by the view helper method). - -Covered are features like these: - -* looking up translations -* interpolating data into translations -* pluralizing translations -* using safe HTML translations (view helper method only) -* localizing dates, numbers, currency, etc. - -### Looking up Translations - -#### Basic Lookup, Scopes and Nested Keys - -Translations are looked up by keys which can be both Symbols or Strings, so these calls are equivalent: - -```ruby -I18n.t :message -I18n.t 'message' -``` - -The `translate` method also takes a `:scope` option which can contain one or more additional keys that will be used to specify a "namespace" or scope for a translation key: - -```ruby -I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] -``` - -This looks up the `:record_invalid` message in the Active Record error messages. - -Additionally, both the key and scopes can be specified as dot-separated keys as in: - -```ruby -I18n.translate "activerecord.errors.messages.record_invalid" -``` - -Thus the following calls are equivalent: - -```ruby -I18n.t 'activerecord.errors.messages.record_invalid' -I18n.t 'errors.messages.record_invalid', scope: :active_record -I18n.t :record_invalid, scope: 'activerecord.errors.messages' -I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] -``` - -#### Defaults - -When a `:default` option is given, its value will be returned if the translation is missing: - -```ruby -I18n.t :missing, default: 'Not here' -# => 'Not here' -``` - -If the `:default` value is a Symbol, it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned. - -E.g., the following first tries to translate the key `:missing` and then the key `:also_missing.` As both do not yield a result, the string "Not here" will be returned: - -```ruby -I18n.t :missing, default: [:also_missing, 'Not here'] -# => 'Not here' -``` - -#### Bulk and Namespace Lookup - -To look up multiple translations at once, an array of keys can be passed: - -```ruby -I18n.t [:odd, :even], scope: 'errors.messages' -# => ["must be odd", "must be even"] -``` - -Also, a key can translate to a (potentially nested) hash of grouped translations. E.g., one can receive _all_ Active Record error messages as a Hash with: - -```ruby -I18n.t 'activerecord.errors.messages' -# => {:inclusion=>"is not included in the list", :exclusion=> ... } -``` - -#### "Lazy" Lookup - -Rails implements a convenient way to look up the locale inside _views_. When you have the following dictionary: - -```yaml -es: - books: - index: - title: "Título" -``` - -you can look up the `books.index.title` value **inside** `app/views/books/index.html.erb` template like this (note the dot): - -```erb -<%= t '.title' %> -``` - -NOTE: Automatic translation scoping by partial is only available from the `translate` view helper method. - -### Interpolation - -In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature. - -All options besides `:default` and `:scope` that are passed to `#translate` will be interpolated to the translation: - -```ruby -I18n.backend.store_translations :en, thanks: 'Thanks %{name}!' -I18n.translate :thanks, name: 'Jeremy' -# => 'Thanks Jeremy!' -``` - -If a translation uses `:default` or `:scope` as an interpolation variable, an `I18n::ReservedInterpolationKey` exception is raised. If a translation expects an interpolation variable, but this has not been passed to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised. - -### Pluralization - -In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature. - -The `:count` interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR: - -```ruby -I18n.backend.store_translations :en, inbox: { - one: 'one message', - other: '%{count} messages' -} -I18n.translate :inbox, count: 2 -# => '2 messages' - -I18n.translate :inbox, count: 1 -# => 'one message' -``` - -The algorithm for pluralizations in `:en` is as simple as: - -```ruby -entry[count == 1 ? 0 : 1] -``` - -I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero). - -If the lookup for the key does not return a Hash suitable for pluralization, an `18n::InvalidPluralizationData` exception is raised. - -### Setting and Passing a Locale - -The locale can be either set pseudo-globally to `I18n.locale` (which uses `Thread.current` like, e.g., `Time.zone`) or can be passed as an option to `#translate` and `#localize`. - -If no locale is passed, `I18n.locale` is used: - -```ruby -I18n.locale = :de -I18n.t :foo -I18n.l Time.now -``` - -Explicitly passing a locale: - -```ruby -I18n.t :foo, locale: :de -I18n.l Time.now, locale: :de -``` - -The `I18n.locale` defaults to `I18n.default_locale` which defaults to :`en`. The default locale can be set like this: - -```ruby -I18n.default_locale = :de -``` - -### Using Safe HTML Translations - -Keys with a '_html' suffix and keys named 'html' are marked as HTML safe. When you use them in views the HTML will not be escaped. - -```yaml -# config/locales/en.yml -en: - welcome: welcome! - hello_html: hello! - title: - html: title! -``` - -```erb -# app/views/home/index.html.erb -
<%= t('welcome') %>
-
<%= raw t('welcome') %>
-
<%= t('hello_html') %>
-
<%= t('title.html') %>
-``` - -NOTE: Automatic conversion to HTML safe translate text is only available from the `translate` view helper method. - -![i18n demo html safe](images/i18n/demo_html_safe.png) - -How to Store your Custom Translations -------------------------------------- - -The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] - -For example a Ruby Hash providing translations can look like this: - -```ruby -{ - pt: { - foo: { - bar: "baz" - } - } -} -``` - -The equivalent YAML file would look like this: - -```ruby -pt: - foo: - bar: baz -``` - -As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". - -Here is a "real" example from the Active Support `en.yml` translations YAML file: - -```ruby -en: - date: - formats: - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" -``` - -So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: - -```ruby -I18n.t 'date.formats.short' -I18n.t 'formats.short', scope: :date -I18n.t :short, scope: 'date.formats' -I18n.t :short, scope: [:date, :formats] -``` - -Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. - -### Translations for Active Record Models - -You can use the methods `Model.model_name.human` and `Model.human_attribute_name(attribute)` to transparently look up translations for your model and attribute names. - -For example when you add the following translations: - -```ruby -en: - activerecord: - models: - user: Dude - attributes: - user: - login: "Handle" - # will translate User attribute "login" as "Handle" -``` - -Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("login")` will return "Handle". - -You can also set a plural form for model names, adding as following: - -```ruby -en: - activerecord: - models: - user: - one: Dude - other: Dudes -``` - -Then `User.model_name.human(count: 2)` will return "Dudes". With `count: 1` or without params will return "Dude". - -#### Error Message Scopes - -Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account. - -This gives you quite powerful means to flexibly adjust your messages to your application's needs. - -Consider a User model with a validation for the name attribute like this: - -```ruby -class User < ActiveRecord::Base - validates :name, presence: true -end -``` - -The key for the error message in this case is `:blank`. Active Record will look up this key in the namespaces: - -```ruby -activerecord.errors.models.[model_name].attributes.[attribute_name] -activerecord.errors.models.[model_name] -activerecord.errors.messages -errors.attributes.[attribute_name] -errors.messages -``` - -Thus, in our example it will try the following keys in this order and return the first result: - -```ruby -activerecord.errors.models.user.attributes.name.blank -activerecord.errors.models.user.blank -activerecord.errors.messages.blank -errors.attributes.name.blank -errors.messages.blank -``` - -When your models are additionally using inheritance then the messages are looked up in the inheritance chain. - -For example, you might have an Admin model inheriting from User: - -```ruby -class Admin < User - validates :name, presence: true -end -``` - -Then Active Record will look for messages in this order: - -```ruby -activerecord.errors.models.admin.attributes.name.blank -activerecord.errors.models.admin.blank -activerecord.errors.models.user.attributes.name.blank -activerecord.errors.models.user.blank -activerecord.errors.messages.blank -errors.attributes.name.blank -errors.messages.blank -``` - -This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models, or default scopes. - -#### Error Message Interpolation - -The translated model name, translated attribute name, and value are always available for interpolation. - -So, for example, instead of the default error message `"cannot be blank"` you could use the attribute name like this : `"Please fill in your %{attribute}"`. - -* `count`, where available, can be used for pluralization if present: - -| validation | with option | message | interpolation | -| ------------ | ------------------------- | ------------------------- | ------------- | -| confirmation | - | :confirmation | - | -| acceptance | - | :accepted | - | -| presence | - | :blank | - | -| absence | - | :present | - | -| length | :within, :in | :too_short | count | -| length | :within, :in | :too_long | count | -| length | :is | :wrong_length | count | -| length | :minimum | :too_short | count | -| length | :maximum | :too_long | count | -| uniqueness | - | :taken | - | -| format | - | :invalid | - | -| inclusion | - | :inclusion | - | -| exclusion | - | :exclusion | - | -| associated | - | :invalid | - | -| numericality | - | :not_a_number | - | -| numericality | :greater_than | :greater_than | count | -| numericality | :greater_than_or_equal_to | :greater_than_or_equal_to | count | -| numericality | :equal_to | :equal_to | count | -| numericality | :less_than | :less_than | count | -| numericality | :less_than_or_equal_to | :less_than_or_equal_to | count | -| numericality | :only_integer | :not_an_integer | - | -| numericality | :odd | :odd | - | -| numericality | :even | :even | - | - -#### Translations for the Active Record `error_messages_for` Helper - -If you are using the Active Record `error_messages_for` helper, you will want to add -translations for it. - -Rails ships with the following translations: - -```yaml -en: - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - body: "There were problems with the following fields:" -``` - -NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form) -gem by adding this line to your Gemfile: `gem 'dynamic_form'`. - -### Translations for Action Mailer E-Mail Subjects - -If you don't pass a subject to the `mail` method, Action Mailer will try to find -it in your translations. The performed lookup will use the pattern -`..subject` to construct the key. - -```ruby -# user_mailer.rb -class UserMailer < ActionMailer::Base - def welcome(user) - #... - end -end -``` - -```yaml -en: - user_mailer: - welcome: - subject: "Welcome to Rails Guides!" -``` - -### Overview of Other Built-In Methods that Provide I18n Support - -Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview. - -#### Action View Helper Methods - -* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L4) translations. - -* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L39) scope if applicable. - -* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L37) scope. - -#### Active Model Methods - -* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L36) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". - -* `ActiveModel::Errors#generate_message` (which is used by Active Model validations but may also be used manually) uses `model_name.human` and `human_attribute_name` (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". - -* `ActiveModel::Errors#full_messages` prepends the attribute name to the error message using a separator that will be looked up from [errors.format](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml#L4) (and which defaults to `"%{attribute} %{message}"`). - -#### Active Support Methods - -* `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope. - -Customize your I18n Setup -------------------------- - -### Using Different Backends - -For several reasons the Simple backend shipped with Active Support only does the "simplest thing that could possibly work" _for Ruby on Rails_[^3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but cannot dynamically store them to any format. - -That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend: - -```ruby -I18n.backend = Globalize::Backend::Static.new -``` - -You can also use the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends. For example, you could use the Active Record backend and fall back to the (default) Simple backend: - -```ruby -I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend) -``` - -### Using Different Exception Handlers - -The I18n API defines the following exceptions that will be raised by backends when the corresponding unexpected conditions occur: - -```ruby -MissingTranslationData # no translation was found for the requested key -InvalidLocale # the locale set to I18n.locale is invalid (e.g. nil) -InvalidPluralizationData # a count option was passed but the translation data is not suitable for pluralization -MissingInterpolationArgument # the translation expects an interpolation argument that has not been passed -ReservedInterpolationKey # the translation contains a reserved interpolation variable name (i.e. one of: scope, default) -UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path -``` - -The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for `MissingTranslationData` exceptions. When a `MissingTranslationData` exception has been caught, it will return the exception's error message string containing the missing key/scope. - -The reason for this is that during development you'd usually want your views to still render even though a translation is missing. - -In other contexts you might want to change this behavior, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module or a class with `#call` method: - -```ruby -module I18n - class JustRaiseExceptionHandler < ExceptionHandler - def call(exception, locale, key, options) - if exception.is_a?(MissingTranslation) - raise exception.to_exception - else - super - end - end - end -end - -I18n.exception_handler = I18n::JustRaiseExceptionHandler.new -``` - -This would re-raise only the `MissingTranslationData` exception, passing all other input to the default exception handler. - -However, if you are using `I18n::Backend::Pluralization` this handler will also raise `I18n::MissingTranslationData: translation missing: en.i18n.plural.rule` exception that should normally be ignored to fall back to the default pluralization rule for English locale. To avoid this you may use additional check for translation key: - -```ruby -if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule' - raise exception.to_exception -else - super -end -``` - -Another example where the default behavior is less desirable is the Rails TranslationHelper which provides the method `#t` (as well as `#translate`). When a `MissingTranslationData` exception occurs in this context, the helper wraps the message into a span with the CSS class `translation_missing`. - -To do so, the helper forces `I18n#translate` to raise exceptions no matter what exception handler is defined by setting the `:raise` option: - -```ruby -I18n.t :foo, raise: true # always re-raises exceptions from the backend -``` - -Conclusion ----------- - -At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. - -If you find anything missing or wrong in this guide, please file a ticket on our [issue tracker](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview). If you want to discuss certain portions or have questions, please sign up to our [mailing list](http://groups.google.com/group/rails-i18n). - - -Contributing to Rails I18n --------------------------- - -I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. - -Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n!)) - -If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://github.com/guides/pull-requests). - - -Resources ---------- - -* [rails-i18n.org](http://rails-i18n.org) - Homepage of the rails-i18n project. You can find lots of useful resources on the [wiki](http://rails-i18n.org/wiki). -* [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. -* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. -* [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. -* [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project. -* [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem. - - -Authors -------- - -* [Sven Fuchs](http://www.workingwithrails.com/person/9963-sven-fuchs) (initial author) -* [Karel Minařík](http://www.workingwithrails.com/person/7476-karel-mina-k) - -If you found this guide useful, please consider recommending its authors on [workingwithrails](http://www.workingwithrails.com). - - -Footnotes ---------- - -[^1]: Or, to quote [Wikipedia](http://en.wikipedia.org/wiki/Internationalization_and_localization): _"Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting software for a specific region or language by adding locale-specific components and translating text."_ - -[^2]: Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files. - -[^3]: One of these reasons is that we don't want to imply any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions. diff --git a/source/zh-CN/index.html.erb b/source/zh-CN/index.html.erb deleted file mode 100644 index 9119c2e..0000000 --- a/source/zh-CN/index.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -<% content_for :page_title do %> -Ruby on Rails 指南 -<% end %> - -<% content_for :header_section do %> -<%= render 'welcome' %> -<% end %> - -<% content_for :index_section do %> -
-
-
由此图案标记的指南代表原文正在撰写中,或是尚待翻译。正在撰写中的原文不会收录在上方的指南目录里。可能包含不完整的资讯与错误。
-
-
-<% end %> - -<% documents_by_section.each do |section| %> -

<%= section['name'] %>

-
- <% section['documents'].each do |document| %> - <%= guide(document['name'], document['url'], work_in_progress: document['work_in_progress'], needs_translation: document['needs_translation']) do %> -

<%= document['description'] %>

- <% end %> - <% end %> -
-<% end %> diff --git a/source/zh-CN/initialization.md b/source/zh-CN/initialization.md deleted file mode 100644 index e445911..0000000 --- a/source/zh-CN/initialization.md +++ /dev/null @@ -1,635 +0,0 @@ -Rails 应用的初始化过程 -================================ - -本章节介绍了 Rails 4 应用启动的内部流程,适合有一定经验的Rails应用开发者阅读。 - -通过学习本章节,您会学到如下知识: - -* 如何使用 `rails server`; -* Rails应用初始化的时间序列; -* Rails应用启动过程都用到哪些文件; -* Rails::Server接口的定义和使用; - --------------------------------------------------------------------------------- - -本章节通过介绍一个基于Ruby on Rails框架默认配置的 Rails 4 应用程序启动过程中的方法调用,详细介绍了每个调用的细节。通过本章节,我们将了解当你执行`rails server`命令启动你的Rails应用时,背后究竟都发生了什么。 - - -提示:本章节中的路径如果没有特别说明都是指Rails应用程序下的路径。 - - -提示:如果你想浏览Rails的源代码[sourcecode](https://github.com/rails/rails),强烈建议您使用快捷键 `t`快速查找Github中的文件。 - -启 动 ! -------- - -我们现在准备启动和初始化一个Rails 应用。 一个Rails 应用经常是以运行命令 `rails console` 或者 `rails server` 开始的。 - -### `railties/bin/rails` - -Rails应用中的 `rails server`命令是Rails应用程序所在文件中的一个Ruby的可执行程序,该程序包含如下操作: - -```ruby -version = ">= 0" -load Gem.bin_path('railties', 'rails', version) -``` - -如果你在Rails 控制台中使用上述命令,你将会看到载入`railties/bin/rails`这个路径。作为 `railties/bin/rails.rb`的一部分,包含如下代码: - -```ruby -require "rails/cli" -``` - -模块`railties/lib/rails/cli` 会调用`Rails::AppRailsLoader.exec_app_rails`方法. - -### `railties/lib/rails/app_rails_loader.rb` - -`exec_app_rails`模块的主要功能是去执行你的Rails应用中`bin/rails`文件夹下的指令。如果当前文件夹下没有`bin/rails`文件,它会到父级目录去搜索,直到找到为止(Windows下应该会去搜索环境变量中的路径),在Rails应用程序目录下的任意位置(命令行模式下),都可以执行`rails`的命令。 - -因为`rails server`命令和下面的操作是等价的: - -```bash -$ exec ruby bin/rails server -``` - -### `bin/rails` - -文件`railties/bin/rails`包含如下代码: - -```ruby -#!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require_relative '../config/boot' -require 'rails/commands' -``` - -`APP_PATH`稍后会在`rails/commands`中用到。`config/boot`在这被引用是因为我们的Rails应用中需要`config/boot.rb`文件来载入Bundler,并初始化Bundler的配置。 - -### `config/boot.rb` - -`config/boot.rb` 包含如下代码: - -```ruby -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) - -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) -``` - -在一个标准的Rails应用中的`Gemfile`文件会配置它的所有依赖项。`config/boot.rb`文件会根据`ENV['BUNDLE_GEMFILE']`中的值来查找`Gemfile`文件的路径。如果`Gemfile`文件存在,那么`bundler/setup`操作会被执行,Bundler执行该操作是为了配置Gemfile依赖项的加载路径。 - -一个标准的Rails应用会包含若干Gem包,特别是下面这些: - -* actionmailer -* actionpack -* actionview -* activemodel -* activerecord -* activesupport -* arel -* builder -* bundler -* erubis -* i18n -* mail -* mime-types -* polyglot -* rack -* rack-cache -* rack-mount -* rack-test -* rails -* railties -* rake -* sqlite3 -* thor -* treetop -* tzinfo - -### `rails/commands.rb` - -一旦`config/boot.rb`执行完毕,接下来要引用的是`rails/commands`文件,这个文件于帮助解析别名。在本应用中,`ARGV` 数组包含的 `server`项会被匹配: - -```ruby -ARGV << '--help' if ARGV.empty? - -aliases = { - "g" => "generate", - "d" => "destroy", - "c" => "console", - "s" => "server", - "db" => "dbconsole", - "r" => "runner" -} - -command = ARGV.shift -command = aliases[command] || command - -require 'rails/commands/commands_tasks' - -Rails::CommandsTasks.new(ARGV).run_command!(command) -``` - -提示: 如你所见,一个空的ARGV数组将会让系统显示相关的帮助项。 - -如果我们使用`s`缩写代替 `server`,Rails系统会从`aliases`中查找匹配的命令。 - -### `rails/commands/command_tasks.rb` - -当你键入一个错误的rails命令,`run_command`函数会抛出一个错误信息。如果命令正确,一个与命令同名的方法会被调用。 - -```ruby -COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) - -def run_command!(command) - command = parse_command(command) - if COMMAND_WHITELIST.include?(command) - send(command) - else - write_error_message(command) - end -end -``` - -如果执行`server`命令,Rails将会继续执行下面的代码: - -```ruby -def set_application_directory! - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) -end - -def server - set_application_directory! - require_command!("server") - - Rails::Server.new.tap do |server| - # We need to require application after the server sets environment, - # otherwise the --environment option given to the server won't propagate. - require APP_PATH - Dir.chdir(Rails.application.root) - server.start - end -end - -def require_command!(command) - require "rails/commands/#{command}" -end -``` - -这个文件将会指向Rails的根目录(与`APP_PATH`中指向`config/application.rb`不同),但是如果没找到`config.ru`文件,接下来将需要`rails/commands/server`来创建`Rails::Server`类。 - -```ruby -require 'fileutils' -require 'optparse' -require 'action_dispatch' -require 'rails' - -module Rails - class Server < ::Rack::Server -``` - -`fileutils` 和 `optparse` 是Ruby标准库中帮助操作文件和解析选项的函数。 - -### `actionpack/lib/action_dispatch.rb` - -动作分发(Action Dispatch)是Rails框架中的路径组件。它增强了路径,会话和中间件的功能。 - -### `rails/commands/server.rb` - -这个文件中定义的`Rails::Server`类是继承自`Rack::Server`类的。当`Rails::Server.new`被调用时,会在 `rails/commands/server.rb`中调用一个`initialize`方法: - -```ruby -def initialize(*) - super - set_environment -end -``` - -首先,`super`会调用父类`Rack::Server`中的`initialize`方法。 - -### Rack: `lib/rack/server.rb` - -`Rack::Server`会为所有基于Rack的应用提供服务接口,现在它已经是Rails框架的一部分了。 - -`Rack::Server`中的`initialize` 方法会简单的设置一对变量: - -```ruby -def initialize(options = nil) - @options = options - @app = options[:app] if options && options[:app] -end -``` - -在这种情况下,`options` 的值是 `nil`,所以在这个方法中相当于什么都没做。 - -当`Rack::Server`中的`super`方法执行完毕后。我们回到`rails/commands/server.rb`,此时此刻,`Rails::Server`对象会调用 `set_environment` 方法,这个方法貌似看上去什么也没干: - -```ruby -def set_environment - ENV["RAILS_ENV"] ||= options[:environment] -end -``` - -事实上,`options`方法在这做了很多事情。`Rack::Server` 中的这个方法定义如下: - -```ruby -def options - @options ||= parse_options(ARGV) -end -``` - -接着`parse_options`方法部分代码如下: - -```ruby -def parse_options(args) - options = default_options - - # Don't evaluate CGI ISINDEX parameters. - # http://www.meb.uni-bonn.de/docs/cgi/cl.html - args.clear if ENV.include?("REQUEST_METHOD") - - options.merge! opt_parser.parse!(args) - options[:config] = ::File.expand_path(options[:config]) - ENV["RACK_ENV"] = options[:environment] - options -end -``` - -`default_options`方法的代码如下: - -```ruby -def default_options - environment = ENV['RACK_ENV'] || 'development' - default_host = environment == 'development' ? 'localhost' : '0.0.0.0' - - { - :environment => environment, - :pid => nil, - :Port => 9292, - :Host => default_host, - :AccessLog => [], - :config => "config.ru" - } -end -``` - -`ENV`中没有`REQUEST_METHOD`项,所以我们可以忽略这一行。接下来是已经在 `Rack::Server`被定义好的`opt_parser`方法: - -```ruby -def opt_parser - Options.new -end -``` - -**这个**方法已经在`Rack::Server`被定义过了,但是在`Rails::Server` 使用不同的参数进行了重载。他的 `parse!`方法如下: - -```ruby -def parse!(args) - args, options = args.dup, {} - - opt_parser = OptionParser.new do |opts| - opts.banner = "Usage: rails server [mongrel, thin, etc] [options]" - opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } - ... -``` - -这个方法为`options`建立一些配置选项,以便给Rails决定如何运行服务提供支持。`initialize`方法执行完毕后。我们将回到`rails/server`目录下,就是`APP_PATH`中的路径。 - -### `config/application` - -当`require APP_PATH`操作执行完毕后。`config/application.rb` 被载入了 (重新调用`bin/rails`中的`APP_PATH`), 在你的应用中,你可以根据需求对该文件进行配置。 - -### `Rails::Server#start` - -`config/application`载入后,`server.start`方法被调用了。这个方法定义如下: - -```ruby -def start - print_boot_information - trap(:INT) { exit } - create_tmp_directories - log_to_stdout if options[:log_stdout] - - super - ... -end - -private - - def print_boot_information - ... - puts "=> Run `rails server -h` for more startup options" - ... - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] - end - - def create_tmp_directories - %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make)) - end - end - - def log_to_stdout - wrapped_app # touch the app so the logger is set up - - console = ActiveSupport::Logger.new($stdout) - console.formatter = Rails.logger.formatter - console.level = Rails.logger.level - - Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) - end -``` - -这是Rails初始化过程中的第一次控制台输出。这个方法创建了一个`INT`中断信号,所以当你在服务端控制台按下`CTRL-C`键后,这将终止Server的运行。我们可以看到,它创建了`tmp/cache`,`tmp/pids`, `tmp/sessions`和`tmp/sockets`等目录。在创建和声明`ActiveSupport::Logger`之前,会调用 `wrapped_app`方法来创建一个Rake 应用程序。 - -`super`会调用`Rack::Server.start` 方法,该方法定义如下: - -```ruby -def start &blk - if options[:warn] - $-w = true - end - - if includes = options[:include] - $LOAD_PATH.unshift(*includes) - end - - if library = options[:require] - require library - end - - if options[:debug] - $DEBUG = true - require 'pp' - p options[:server] - pp wrapped_app - pp app - end - - check_pid! if options[:pid] - - # Touch the wrapped app, so that the config.ru is loaded before - # daemonization (i.e. before chdir, etc). - wrapped_app - - daemonize_app if options[:daemonize] - - write_pid if options[:pid] - - trap(:INT) do - if server.respond_to?(:shutdown) - server.shutdown - else - exit - end - end - - server.run wrapped_app, options, &blk -end -``` - -上述Rails 应用有趣的部分在最后一行,`server.run`方法。它再次调用了`wrapped_app`方法(温故而知新)。 - -```ruby -@wrapped_app ||= build_app app -``` - -这里的`app`方法定义如下: - -```ruby -def app - @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config -end -... -private - def build_app_and_options_from_config - if !::File.exist? options[:config] - abort "configuration #{options[:config]} not found" - end - - app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) - self.options.merge! options - app - end - - def build_app_from_string - Rack::Builder.new_from_string(self.options[:builder]) - end -``` - -`options[:config]`中的值默认会从 `config.ru` 中获取,包含如下代码: - -```ruby -# This file is used by Rack-based servers to start the application. - -require ::File.expand_path('../config/environment', __FILE__) -run <%= app_const %> -``` - - -`Rack::Builder.parse_file`方法会从`config.ru`中获取内容,包含如下代码: - -```ruby -app = new_from_string cfgfile, config - -... - -def self.new_from_string(builder_script, file="(rackup)") - eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app", - TOPLEVEL_BINDING, file, 0 -end -``` - -`Rack::Builder`中的`initialize`方法会创建一个新的`Rack::Builder`实例,这是Rails应用初始化过程中主要内容。接下来`config.ru`中的`require`项`config/environment.rb`会继续执行: - -```ruby -require ::File.expand_path('../config/environment', __FILE__) -``` - -### `config/environment.rb` - -这是`config.ru` (`rails server`)和信使(Passenger)都要用到的文件,是两者交流的媒介。之前的操作都是为了创建Rack和Rails。 - -这个文件是以引用 `config/application.rb`开始的: - -```ruby -require File.expand_path('../application', __FILE__) -``` - -### `config/application.rb` - -这个文件需要引用`config/boot.rb`: - -```ruby -require File.expand_path('../boot', __FILE__) -``` - -如果之前在`rails server`中没有引用上述的依赖项,那么它**将不会**和信使(Passenger)发生联系。 - -现在,有趣的部分要开始了! - -加载 Rails -------------- - -`config/application.rb`中的下一行是这样的: - -```ruby -require 'rails/all' -``` - -### `railties/lib/rails/all.rb` - -本文件中将引用和Rails框架相关的所有内容: - -```ruby -require "rails" - -%w( - active_record - action_controller - action_view - action_mailer - rails/test_unit - sprockets -).each do |framework| - begin - require "#{framework}/railtie" - rescue LoadError - end -end -``` - -这样Rails框架中的所有组件已经准备就绪了。我们将不会深入介绍这些框架的内部细节,不过强烈建议您去探索和发现她们。 - -现在,我们关心的模块比如Rails engines,I18n 和 Rails configuration 都已经准备就绪了。 - -### 回到 `config/environment.rb` - -`config/application.rb`为`Rails::Application`定义了Rails应用初始化之后所有需要用到的资源。当`config/application.rb` 加载了Rails和命名空间后,我们回到`config/environment.rb`,就是初始化完成的地方。比如我们的应用叫‘blog’,我们将在`rails/application.rb`中调用`Rails.application.initialize!`方法。 - -### `railties/lib/rails/application.rb` - -`initialize!`方法部分代码如下: - -```ruby -def initialize!(group=:default) #:nodoc: - raise "Application has been already initialized." if @initialized - run_initializers(group, self) - @initialized = true - self -end -``` - -如你所见,一个应用只能初始化一次。初始化器通过在`railties/lib/rails/initializable.rb`中的`run_initializers`方法运行: - -```ruby -def run_initializers(group=:default, *args) - return if instance_variable_defined?(:@ran) - initializers.tsort_each do |initializer| - initializer.run(*args) if initializer.belongs_to?(group) - end - @ran = true -end -``` - -`run_initializers`代码本身是有点投机取巧的,Rails在这里要做的是遍历所有的祖先,查找一个`initializers`方法,之后根据名字进行排序,并依次执行它们。举个例子,`Engine`类将调用自己和祖先中名为`initializers`的方法。 - -`Rails::Application` 类是在`railties/lib/rails/application.rb`定义的。定义了`bootstrap`, `railtie`和 `finisher`模块的初始化器。`bootstrap`的初始化器在应用被加载以前就预加载了。(类似初始化中的日志记录器),`finisher`的初始化器则是最后加载的。`railtie`初始化器被定义在`Rails::Application`中,执行是在`bootstrap`和 `finishers`之间。 - -这些完成后,我们将回到`Rack::Server` 。 - -### Rack: lib/rack/server.rb - -上次我们离开的时候,`app` 方法代码如下: - -```ruby -def app - @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config -end -... -private - def build_app_and_options_from_config - if !::File.exist? options[:config] - abort "configuration #{options[:config]} not found" - end - - app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) - self.options.merge! options - app - end - - def build_app_from_string - Rack::Builder.new_from_string(self.options[:builder]) - end -``` - -此时此刻,`app`是Rails 应用本身(中间件)。接下来就是Rack调用所有的依赖项了(提供支持的中间件): - -```ruby -def build_app(app) - middleware[options[:environment]].reverse_each do |middleware| - middleware = middleware.call(self) if middleware.respond_to?(:call) - next unless middleware - klass = middleware.shift - app = klass.new(app, *middleware) - end - app -end -``` - -必须牢记,`Server#start`最后一行中调用了`build_app`方法(被`wrapped_app`调用)了。接下来我们看看还剩下什么: - -```ruby -server.run wrapped_app, options, &blk -``` - -此时此刻,调用`server.run` 方法将依赖于你所用的Server类型 。比如,如果你的Server是Puma, 那么就会是下面这个结果: - -```ruby -... -DEFAULT_OPTIONS = { - :Host => '0.0.0.0', - :Port => 8080, - :Threads => '0:16', - :Verbose => false -} - -def self.run(app, options = {}) - options = DEFAULT_OPTIONS.merge(options) - - if options[:Verbose] - app = Rack::CommonLogger.new(app, STDOUT) - end - - if options[:environment] - ENV['RACK_ENV'] = options[:environment].to_s - end - - server = ::Puma::Server.new(app) - min, max = options[:Threads].split(':', 2) - - puts "Puma #{::Puma::Const::PUMA_VERSION} starting..." - puts "* Min threads: #{min}, max threads: #{max}" - puts "* Environment: #{ENV['RACK_ENV']}" - puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}" - - server.add_tcp_listener options[:Host], options[:Port] - server.min_threads = min - server.max_threads = max - yield server if block_given? - - begin - server.run.join - rescue Interrupt - puts "* Gracefully stopping, waiting for requests to finish" - server.stop(true) - puts "* Goodbye!" - end - -end -``` - -我们没有深入到服务端配置的细节,因为这是我们探索Rails应用初始化过程之旅的终点了。 - -高层次的阅读将有助于您提高编写代码的水平,成为Rail开发高手。如果你想要知道更多,那么去读Rails的源代码将是你的不二选择。 diff --git a/source/zh-CN/kindle/copyright.html.erb b/source/zh-CN/kindle/copyright.html.erb deleted file mode 100644 index bd51d87..0000000 --- a/source/zh-CN/kindle/copyright.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= render 'license' %> \ No newline at end of file diff --git a/source/zh-CN/kindle/layout.html.erb b/source/zh-CN/kindle/layout.html.erb deleted file mode 100644 index f0a2862..0000000 --- a/source/zh-CN/kindle/layout.html.erb +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - -<%= yield(:page_title) || 'Ruby on Rails Guides' %> - - - - - - - <% if content_for? :header_section %> - <%= yield :header_section %> -
- <% end %> - - <% if content_for? :index_section %> - <%= yield :index_section %> -
- <% end %> - - <%= yield.html_safe %> - - diff --git a/source/zh-CN/kindle/rails_guides.opf.erb b/source/zh-CN/kindle/rails_guides.opf.erb deleted file mode 100644 index 547abcb..0000000 --- a/source/zh-CN/kindle/rails_guides.opf.erb +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - Ruby on Rails Guides (<%= @version %>) - - en-us - Ruby on Rails - Ruby on Rails - Reference - <%= Time.now.strftime('%Y-%m-%d') %> - - These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. - - - - - - - - - <% documents_flat.each do |document| %> - - <% end %> - - <% %w{toc.html credits.html welcome.html copyright.html}.each do |url| %> - - <% end %> - - - - - - - - - - - - <% documents_flat.each do |document| %> - - <% end %> - - - - - - - diff --git a/source/zh-CN/kindle/toc.html.erb b/source/zh-CN/kindle/toc.html.erb deleted file mode 100644 index f310edd..0000000 --- a/source/zh-CN/kindle/toc.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -<% content_for :page_title do %> -Ruby on Rails Guides -<% end %> - -

Table of Contents

-
- -<% documents_by_section.each_with_index do |section, i| %> -

<%= "#{i + 1}." %> <%= section['name'] %>

-
    - <% section['documents'].each do |document| %> -
  • - <%= document['name'] %> - <% if document['work_in_progress']%>(WIP)<% end %> -
  • - <% end %> -
-<% end %> -
- -
diff --git a/source/zh-CN/kindle/toc.ncx.erb b/source/zh-CN/kindle/toc.ncx.erb deleted file mode 100644 index 2c6d8e3..0000000 --- a/source/zh-CN/kindle/toc.ncx.erb +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - -Ruby on Rails Guides -docrails - - - - Table of Contents - - - - - - Introduction - - - - - - Welcome - - - - - Credits - - - - Copyright & License - - - - - <% play_order = 4 %> - <% documents_by_section.each_with_index do |section, section_no| %> - - - <%= section['name'] %> - - - - <% section['documents'].each_with_index do |document, document_no| %> - - - <%= document['name'] %> - - - - <% end %> - - <% end %> - - - - diff --git a/source/zh-CN/kindle/welcome.html.erb b/source/zh-CN/kindle/welcome.html.erb deleted file mode 100644 index 676e5e4..0000000 --- a/source/zh-CN/kindle/welcome.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= render 'welcome' %> diff --git a/source/zh-CN/layout.html.erb b/source/zh-CN/layout.html.erb deleted file mode 100644 index 8c3ef8d..0000000 --- a/source/zh-CN/layout.html.erb +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - -<%= yield(:page_title) || 'Ruby on Rails 指南' %> - - - - - - - - - - - - <% if @edge %> -
- edge-badge -
- <% end %> -
-
- 更多内容 rubyonrails.org: - - 更多内容 - - -
-
- -
-
- -
-
- <%= yield :header_section %> - - <%= yield :index_section %> -
-
- -
-
-
- <%= yield.html_safe %> - -

反馈

-

- 欢迎帮忙改善指南质量。 -

-

- 如发现任何错误,欢迎修正。开始贡献前,可先行阅读<%= link_to '贡献指南:文档', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %>。 -

-

翻译如有错误,深感抱歉,欢迎 <%= link_to 'Fork', 'https://github.com/ruby-china/guides/fork' %> 修正,或至此处<%= link_to '回报', 'https://github.com/ruby-china/guides/issues/new' %>。

-

- 文章可能有未完成或过时的内容。请先检查 <%= link_to 'Edge Guides','http://edgeguides.rubyonrails.org/' %> 来确定问题在 master 是否已经修掉了。再上 master 补上缺少的文件。内容参考 <%= link_to 'Ruby on Rails 指南准则', 'ruby_on_rails_guides_guidelines.html' %>来了解行文风格。 -

-

最后,任何关于 Ruby on Rails 文档的讨论,欢迎到 <%= link_to 'rubyonrails-docs 邮件群组', 'http://groups.google.com/group/rubyonrails-docs' %>。 -

-
-
-
- -
- - - - - - - - - - - - - - diff --git a/source/zh-CN/layouts_and_rendering.md b/source/zh-CN/layouts_and_rendering.md deleted file mode 100644 index 7e0b644..0000000 --- a/source/zh-CN/layouts_and_rendering.md +++ /dev/null @@ -1,1193 +0,0 @@ -Rails 布局和视图渲染 -=================== - -本文介绍 Action Controller 和 Action View 中布局的基本功能。 - -读完本文,你将学到: - -* 如何使用 Rails 内建的各种渲染方法; -* 如何创建具有多个内容区域的布局; -* 如何使用局部视图去除重复; -* 如何使用嵌套布局(子模板); - --------------------------------------------------------------------------------- - -概览:各组件之间的协作 -------------------- - -本文关注 MVC 架构中控制器和视图之间的交互。你可能已经知道,控制器的作用是处理请求,但经常会把繁重的操作交给模型完成。返回响应时,控制器会把一些操作交给视图完成。本文要说明的就是控制器交给视图的操作是怎么完成的。 - -总的来说,这个过程涉及到响应中要发送什么内容,以及调用哪个方法创建响应。如果响应是个完整的视图,Rails 还要做些额外工作,把视图套入布局,有时还要渲染局部视图。后文会详细介绍整个过程。 - -创建响应 -------- - -从控制器的角度来看,创建 HTTP 响应有三种方法: - -* 调用 `render` 方法,向浏览器发送一个完整的响应; -* 调用 `redirect_to` 方法,向浏览器发送一个 HTTP 重定向状态码; -* 调用 `head` 方法,向浏览器发送只含报头的响应; - -### 渲染视图 - -你可能已经听说过 Rails 的开发原则之一是“多约定,少配置”。默认渲染视图的处理就是这一原则的完美体现。默认情况下,Rails 中的控制器会渲染路由对应的视图。例如,有如下的 `BooksController` 代码: - -```ruby -class BooksController < ApplicationController -end -``` - -在路由文件中有如下定义: - -```ruby -resources :books -``` - -而且有个名为 `app/views/books/index.html.erb` 的视图文件: - -```erb -

Books are coming soon!

-``` - -那么,访问 `/books` 时,Rails 会自动渲染视图 `app/views/books/index.html.erb`,网页中会看到显示有“Books are coming soon!”。 - -网页中显示这些文字没什么用,所以后续你可能会创建一个 `Book` 模型,然后在 `BooksController` 中添加 `index` 动作: - -```ruby -class BooksController < ApplicationController - def index - @books = Book.all - end -end -``` - -注意,基于“多约定,少配置”原则,在 `index` 动作末尾并没有指定要渲染视图,Rails 会自动在控制器的视图文件夹中寻找 `action_name.html.erb` 模板,然后渲染。在这个例子中,Rails 渲染的是 `app/views/books/index.html.erb` 文件。 - -如果要在视图中显示书籍的属性,可以使用 ERB 模板: - -```erb -

Listing Books

- - - - - - - - - - -<% @books.each do |book| %> - - - - - - - -<% end %> -
TitleSummary
<%= book.title %><%= book.content %><%= link_to "Show", book %><%= link_to "Edit", edit_book_path(book) %><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %>
- -
- -<%= link_to "New book", new_book_path %> -``` - -NOTE: 真正处理渲染过程的是 `ActionView::TemplateHandlers` 的子类。本文不做深入说明,但要知道,文件的扩展名决定了要使用哪个模板处理程序。从 Rails 2 开始,ERB 模板(含有嵌入式 Ruby 代码的 HTML)的标准扩展名是 `.erb`,Builder 模板(XML 生成器)的标准扩展名是 `.builder`。 - -### 使用 `render` 方法 - -大多数情况下,`ActionController::Base#render` 方法都能满足需求,而且还有多种定制方式,可以渲染 Rails 模板的默认视图、指定的模板、文件、行间代码或者什么也不渲染。渲染的内容格式可以是文本,JSON 或 XML。而且还可以设置响应的内容类型和 HTTP 状态码。 - -TIP: 如果不想使用浏览器直接查看调用 `render` 方法得到的结果,可以使用 `render_to_string` 方法。`render_to_string` 和 `render` 的用法完全一样,不过不会把响应发送给浏览器,而是直接返回字符串。 - -#### 什么都不渲染 - -或许 `render` 方法最简单的用法是什么也不渲染: - -```ruby -render nothing: true -``` - -如果使用 cURL 查看请求,会得到一些输出: - -```bash -$ curl -i 127.0.0.1:3000/books -HTTP/1.1 200 OK -Connection: close -Date: Sun, 24 Jan 2010 09:25:18 GMT -Transfer-Encoding: chunked -Content-Type: */*; charset=utf-8 -X-Runtime: 0.014297 -Set-Cookie: _blog_session=...snip...; path=/; HttpOnly -Cache-Control: no-cache - -$ -``` - -可以看到,响应的主体是空的(`Cache-Control` 之后没有数据),但请求本身是成功的,因为 Rails 把响应码设为了“200 OK”。调用 `render` 方法时可以设置 `:status` 选项修改状态码。这种用法可在 Ajax 请求中使用,因为此时只需告知浏览器请求已经完成。 - -TIP: 或许不应该使用 `render :nothing`,而要用后面介绍的 `head` 方法。`head` 方法用起来更灵活,而且只返回 HTTP 报头。 - -#### 渲染动作的视图 - -如果想渲染同个控制器中的其他模板,可以把视图的名字传递给 `render` 方法: - -```ruby -def update - @book = Book.find(params[:id]) - if @book.update(book_params) - redirect_to(@book) - else - render "edit" - end -end -``` - -如果更新失败,会渲染同个控制器中的 `edit.html.erb` 模板。 - -如果不想用字符串,还可使用 Symbol 指定要渲染的动作: - -```ruby -def update - @book = Book.find(params[:id]) - if @book.update(book_params) - redirect_to(@book) - else - render :edit - end -end -``` - -#### 渲染其他控制器中的动作模板 - -如果想渲染其他控制器中的模板该怎么做呢?还是使用 `render` 方法,指定模板的完整路径即可。例如,如果控制器 `AdminProductsController` 在 `app/controllers/admin` 文件夹中,可使用下面的方式渲染 `app/views/products` 文件夹中的模板: - -```ruby -render "products/show" -``` - -因为参数中有个斜线,所以 Rails 知道这个视图属于另一个控制器。如果想让代码的意图更明显,可以使用 `:template` 选项(Rails 2.2 及先前版本必须这么做): - -```ruby -render template: "products/show" -``` - -#### 渲染任意文件 - -`render` 方法还可渲染程序之外的视图(或许多个程序共用一套视图): - -```ruby -render "/u/apps/warehouse_app/current/app/views/products/show" -``` - -因为参数以斜线开头,所以 Rails 将其视为一个文件。如果想让代码的意图更明显,可以使用 `:file` 选项(Rails 2.2+ 必须这么做) - -```ruby -render file: "/u/apps/warehouse_app/current/app/views/products/show" -``` - -`:file` 选项的值是文件系统中的绝对路径。当然,你要对使用的文件拥有相应权限。 - -NOTE: 默认情况下,渲染文件时不会使用当前程序的布局。如果想让 Rails 把文件套入布局,要指定 `layout: true` 选项。 - -TIP: 如果在 Windows 中运行 Rails,就必须使用 `:file` 选项指定文件的路径,因为 Windows 中的文件名和 Unix 格式不一样。 - -#### 小结 - -上述三种渲染方式的作用其实是一样的。在 `BooksController` 控制器的 `update` 动作中,如果更新失败后想渲染 `views/books` 文件夹中的 `edit.html.erb` 模板,下面这些用法都能达到这个目的: - -```ruby -render :edit -render action: :edit -render "edit" -render "edit.html.erb" -render action: "edit" -render action: "edit.html.erb" -render "books/edit" -render "books/edit.html.erb" -render template: "books/edit" -render template: "books/edit.html.erb" -render "/path/to/rails/app/views/books/edit" -render "/path/to/rails/app/views/books/edit.html.erb" -render file: "/path/to/rails/app/views/books/edit" -render file: "/path/to/rails/app/views/books/edit.html.erb" -``` - -你可以根据自己的喜好决定使用哪种方式,总的原则是,使用符合代码意图的最简单方式。 - -#### 使用 `render` 方法的 `:inline` 选项 - -如果使用 `:inline` 选项指定了 ERB 代码,`render` 方法就不会渲染视图。如下所示的用法完全可行: - -```ruby -render inline: "<% products.each do |p| %>

<%= p.name %>

<% end %>" -``` - -WARNING: 但是很少这么做。在控制器中混用 ERB 代码违反了 MVC 架构原则,也让程序的其他开发者难以理解程序的逻辑思路。请使用单独的 ERB 视图。 - -默认情况下,行间渲染使用 ERB 模板。你可以使用 `:type` 选项指定使用其他处理程序: - -```ruby -render inline: "xml.p {'Horrid coding practice!'}", type: :builder -``` - -#### 渲染文本 - -调用 `render` 方法时指定 `:plain` 选项,可以把没有标记语言的纯文本发给浏览器: - -```ruby -render plain: "OK" -``` - -TIP: 渲染纯文本主要用于 Ajax 或无需使用 HTML 的网络服务。 - -NOTE: 默认情况下,使用 `:plain` 选项渲染纯文本,不会套用程序的布局。如果想使用布局,可以指定 `layout: true` 选项。 - -#### 渲染 HTML - -调用 `render` 方法时指定 `:html` 选项,可以把 HTML 字符串发给浏览器: - -```ruby -render html: "Not Found".html_safe -``` - -TIP: 这种方法可用来渲染 HTML 片段。如果标记很复杂,就要考虑使用模板文件了。 - -NOTE: 如果字符串对 HTML 不安全,会进行转义。 - -#### 渲染 JSON - -JSON 是一种 JavaScript 数据格式,很多 Ajax 库都用这种格式。Rails 内建支持把对象转换成 JSON,经渲染后再发送给浏览器。 - -```ruby -render json: @product -``` - -TIP: 在需要渲染的对象上无需调用 `to_json` 方法,如果使用了 `:json` 选项,`render` 方法会自动调用 `to_json`。 - -#### 渲染 XML - -Rails 也内建支持把对象转换成 XML,经渲染后再发回给调用者: - -```ruby -render xml: @product -``` - -TIP: 在需要渲染的对象上无需调用 `to_xml` 方法,如果使用了 `:xml` 选项,`render` 方法会自动调用 `to_xml`。 - -#### 渲染普通的 JavaScript - -Rails 能渲染普通的 JavaScript: - -```ruby -render js: "alert('Hello Rails');" -``` - -这种方法会把 MIME 设为 `text/javascript`,再把指定的字符串发给浏览器。 - -#### 渲染原始的主体 - -调用 `render` 方法时使用 `:body` 选项,可以不设置内容类型,把原始的内容发送给浏览器: - -```ruby -render body: "raw" -``` - -TIP: 只有不在意内容类型时才可使用这个选项。大多数时候,使用 `:plain` 或 `:html` 选项更合适。 - -NOTE: 如果没有修改,这种方式返回的内容类型是 `text/html`,因为这是 Action Dispatch 响应默认使用的内容类型。 - -#### `render` 方法的选项 - -`render` 方法一般可接受四个选项: - -* `:content_type` -* `:layout` -* `:location` -* `:status` - -##### `:content_type` 选项 - -默认情况下,Rails 渲染得到的结果内容类型为 `text/html`;如果使用 `:json` 选项,内容类型为 `application/json`;如果使用 `:xml` 选项,内容类型为 `application/xml`。如果需要修改内容类型,可使用 `:content_type` 选项 - -```ruby -render file: filename, content_type: "application/rss" -``` - -##### `:layout` 选项 - -`render` 方法的大多数选项渲染得到的结果都会作为当前布局的一部分显示。后文会详细介绍布局。 - -`:layout` 选项告知 Rails,在当前动作中使用指定的文件作为布局: - -```ruby -render layout: "special_layout" -``` - -也可以告知 Rails 不使用布局: - -```ruby -render layout: false -``` - -##### `:location` 选项 - -`:location` 选项可以设置 HTTP `Location` 报头: - -```ruby -render xml: photo, location: photo_url(https://github.com/photo) -``` - -##### `:status` 选项 - -Rails 会自动为生成的响应附加正确的 HTTP 状态码(大多数情况下是 `200 OK`)。使用 `:status` 选项可以修改状态码: - -```ruby -render status: 500 -render status: :forbidden -``` - -Rails 能理解数字状态码和对应的符号,如下所示: - -| 响应类别 | HTTP 状态码 | 符号 | -|---------------------|------------------|----------------------------------| -| **信息** | 100 | :continue | -| | 101 | :switching_protocols | -| | 102 | :processing | -| **成功** | 200 | :ok | -| | 201 | :created | -| | 202 | :accepted | -| | 203 | :non_authoritative_information | -| | 204 | :no_content | -| | 205 | :reset_content | -| | 206 | :partial_content | -| | 207 | :multi_status | -| | 208 | :already_reported | -| | 226 | :im_used | -| **重定向** | 300 | :multiple_choices | -| | 301 | :moved_permanently | -| | 302 | :found | -| | 303 | :see_other | -| | 304 | :not_modified | -| | 305 | :use_proxy | -| | 306 | :reserved | -| | 307 | :temporary_redirect | -| | 308 | :permanent_redirect | -| **客户端错误** | 400 | :bad_request | -| | 401 | :unauthorized | -| | 402 | :payment_required | -| | 403 | :forbidden | -| | 404 | :not_found | -| | 405 | :method_not_allowed | -| | 406 | :not_acceptable | -| | 407 | :proxy_authentication_required | -| | 408 | :request_timeout | -| | 409 | :conflict | -| | 410 | :gone | -| | 411 | :length_required | -| | 412 | :precondition_failed | -| | 413 | :request_entity_too_large | -| | 414 | :request_uri_too_long | -| | 415 | :unsupported_media_type | -| | 416 | :requested_range_not_satisfiable | -| | 417 | :expectation_failed | -| | 422 | :unprocessable_entity | -| | 423 | :locked | -| | 424 | :failed_dependency | -| | 426 | :upgrade_required | -| | 428 | :precondition_required | -| | 429 | :too_many_requests | -| | 431 | :request_header_fields_too_large | -| **服务器错误** | 500 | :internal_server_error | -| | 501 | :not_implemented | -| | 502 | :bad_gateway | -| | 503 | :service_unavailable | -| | 504 | :gateway_timeout | -| | 505 | :http_version_not_supported | -| | 506 | :variant_also_negotiates | -| | 507 | :insufficient_storage | -| | 508 | :loop_detected | -| | 510 | :not_extended | -| | 511 | :network_authentication_required | - -#### 查找布局 - -查找布局时,Rails 首先查看 `app/views/layouts` 文件夹中是否有和控制器同名的文件。例如,渲染 `PhotosController` 控制器中的动作会使用 `app/views/layouts/photos.html.erb`(或 `app/views/layouts/photos.builder`)。如果没找到针对控制器的布局,Rails 会使用 `app/views/layouts/application.html.erb` 或 `app/views/layouts/application.builder`。如果没有 `.erb` 布局,Rails 会使用 `.builder` 布局(如果文件存在)。Rails 还提供了多种方法用来指定单个控制器和动作使用的布局。 - -##### 指定控制器所用布局 - -在控制器中使用 `layout` 方法,可以改写默认使用的布局约定。例如: - -```ruby -class ProductsController < ApplicationController - layout "inventory" - #... -end -``` - -这么声明之后,`ProductsController` 渲染的所有视图都将使用 `app/views/layouts/inventory.html.erb` 文件作为布局。 - -要想指定整个程序使用的布局,可以在 `ApplicationController` 类中使用 `layout` 方法: - -```ruby -class ApplicationController < ActionController::Base - layout "main" - #... -end -``` - -这么声明之后,整个程序的视图都会使用 `app/views/layouts/main.html.erb` 文件作为布局。 - -##### 运行时选择布局 - -可以使用一个 Symbol,在处理请求时选择布局: - -```ruby -class ProductsController < ApplicationController - layout :products_layout - - def show - @product = Product.find(params[:id]) - end - - private - def products_layout - @current_user.special? ? "special" : "products" - end - -end -``` - -如果当前用户是特殊用户,会使用一个特殊布局渲染产品视图。 - -还可使用行间方法,例如 Proc,决定使用哪个布局。如果使用 Proc,其代码块可以访问 `controller` 实例,这样就能根据当前请求决定使用哪个布局: - -```ruby -class ProductsController < ApplicationController - layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" } -end -``` - -##### 条件布局 - -在控制器中指定布局时可以使用 `:only` 和 `:except` 选项。这两个选项的值可以是一个方法名或者一个方法名数组,这些方法都是控制器中的动作: - -```ruby -class ProductsController < ApplicationController - layout "product", except: [:index, :rss] -end -``` - -这么声明后,除了 `rss` 和 `index` 动作之外,其他动作都使用 `product` 布局渲染视图。 - -##### 布局继承 - -布局声明按层级顺序向下顺延,专用布局比通用布局优先级高。例如: - -* `application_controller.rb` - -```ruby -class ApplicationController < ActionController::Base - layout "main" -end -``` - -* `posts_controller.rb` - -```ruby -class PostsController < ApplicationController -end -``` - -* `special_posts_controller.rb` - -```ruby -class SpecialPostsController < PostsController - layout "special" -end -``` - -* `old_posts_controller.rb` - -```ruby -class OldPostsController < SpecialPostsController - layout false - - def show - @post = Post.find(params[:id]) - end - - def index - @old_posts = Post.older - render layout: "old" - end - # ... -end -``` - -在这个程序中: - -* 一般情况下,视图使用 `main` 布局渲染; -* `PostsController#index` 使用 `main` 布局; -* `SpecialPostsController#index` 使用 `special` 布局; -* `OldPostsController#show` 不用布局; -* `OldPostsController#index` 使用 `old` 布局; - -#### 避免双重渲染错误 - -大多数 Rails 开发者迟早都会看到一个错误消息:Can only render or redirect once per action(动作只能渲染或重定向一次)。这个提示很烦人,也很容易修正。出现这个错误的原因是,没有理解 `render` 的工作原理。 - -例如,下面的代码会导致这个错误: - -```ruby -def show - @book = Book.find(params[:id]) - if @book.special? - render action: "special_show" - end - render action: "regular_show" -end -``` - -如果 `@book.special?` 的结果是 `true`,Rails 开始渲染,把 `@book` 变量导入 `special_show` 视图中。但是,`show` 动作并不会就此停止运行,当 Rails 运行到动作的末尾时,会渲染 `regular_show` 视图,导致错误出现。解决的办法很简单,确保在一次代码运行路线中只调用一次 `render` 或 `redirect_to` 方法。有一个语句可以提供帮助,那就是 `and return`。下面的代码对上述代码做了修改: - -```ruby -def show - @book = Book.find(params[:id]) - if @book.special? - render action: "special_show" and return - end - render action: "regular_show" -end -``` - -千万别用 `&& return` 代替 `and return`,因为 Ruby 语言操作符优先级的关系,`&& return` 根本不起作用。 - -注意,`ActionController` 能检测到是否显式调用了 `render` 方法,所以下面这段代码不会出错: - -```ruby -def show - @book = Book.find(params[:id]) - if @book.special? - render action: "special_show" - end -end -``` - -如果 `@book.special?` 的结果是 `true`,会渲染 `special_show` 视图,否则就渲染默认的 `show` 模板。 - -### 使用 `redirect_to` 方法 - -响应 HTTP 请求的另一种方法是使用 `redirect_to`。如前所述,`render` 告诉 Rails 构建响应时使用哪个视图(以及其他静态资源)。`redirect_to` 做的事情则完全不同:告诉浏览器向另一个地址发起新请求。例如,在程序中的任何地方使用下面的代码都可以重定向到 `photos` 控制器的 `index` 动作: - -```ruby -redirect_to photos_url -``` - -`redirect_to` 方法的参数与 `link_to` 和 `url_for` 一样。有个特殊的重定向,返回到前一个页面: - -```ruby -redirect_to :back -``` - -#### 设置不同的重定向状态码 - -调用 `redirect_to` 方法时,Rails 会把 HTTP 状态码设为 302,即临时重定向。如果想使用其他的状态码,例如 301(永久重定向),可以设置 `:status` 选项: - -```ruby -redirect_to photos_path, status: 301 -``` - -和 `render` 方法的 `:status` 选项一样,`redirect_to` 方法的 `:status` 选项同样可使用数字状态码或符号。 - -#### `render` 和 `redirect_to` 的区别 - -有些经验不足的开发者会认为 `redirect_to` 方法是一种 `goto` 命令,把代码从一处转到别处。这么理解是**不对**的。执行到 `redirect_to` 方法时,代码会停止运行,等待浏览器发起新请求。你需要告诉浏览器下一个请求是什么,并返回 302 状态码。 - -下面通过实例说明。 - -```ruby -def index - @books = Book.all -end - -def show - @book = Book.find_by(id: params[:id]) - if @book.nil? - render action: "index" - end -end -``` - -在这段代码中,如果 `@book` 变量的值为 `nil` 很可能会出问题。记住,`render :action` 不会执行目标动作中的任何代码,因此不会创建 `index` 视图所需的 `@books` 变量。修正方法之一是不渲染,使用重定向: - -```ruby -def index - @books = Book.all -end - -def show - @book = Book.find_by(id: params[:id]) - if @book.nil? - redirect_to action: :index - end -end -``` - -这样修改之后,浏览器会向 `index` 动作发起新请求,执行 `index` 方法中的代码,一切都能正常运行。 - -这种方法有个缺点,增加了浏览器的工作量。浏览器通过 `/books/1` 向 `show` 动作发起请求,控制器做了查询,但没有找到对应的图书,所以返回 302 重定向响应,告诉浏览器访问 `/books/`。浏览器收到指令后,向控制器的 `index` 动作发起新请求,控制器从数据库中取出所有图书,渲染 `index` 模板,将其返回浏览器,在屏幕上显示所有图书。 - -在小型程序中,额外增加的时间不是个问题。如果响应时间很重要,这个问题就值得关注了。下面举个虚拟的例子演示如何解决这个问题: - -```ruby -def index - @books = Book.all -end - -def show - @book = Book.find_by(id: params[:id]) - if @book.nil? - @books = Book.all - flash.now[:alert] = "Your book was not found" - render "index" - end -end -``` - -在这段代码中,如果指定 ID 的图书不存在,会从模型中取出所有图书,赋值给 `@books` 实例变量,然后直接渲染 `index.html.erb` 模板,并显示一个 Flash 消息,告知用户出了什么问题。 - -### 使用 `head` 构建只返回报头的响应 - -`head` 方法可以只把报头发送给浏览器。还可使用意图更明确的 `render :nothing` 达到同样的目的。`head` 方法的参数是 HTTP 状态码的符号形式(参见[前文表格](#the-status-option)),选项是一个 Hash,指定报头名和对应的值。例如,可以只返回报错的报头: - -```ruby -head :bad_request -``` - -生成的报头如下: - -``` -HTTP/1.1 400 Bad Request -Connection: close -Date: Sun, 24 Jan 2010 12:15:53 GMT -Transfer-Encoding: chunked -Content-Type: text/html; charset=utf-8 -X-Runtime: 0.013483 -Set-Cookie: _blog_session=...snip...; path=/; HttpOnly -Cache-Control: no-cache -``` - -或者使用其他 HTTP 报头提供其他信息: - -```ruby -head :created, location: photo_path(@photo) -``` - -生成的报头如下: - -``` -HTTP/1.1 201 Created -Connection: close -Date: Sun, 24 Jan 2010 12:16:44 GMT -Transfer-Encoding: chunked -Location: /photos/1 -Content-Type: text/html; charset=utf-8 -X-Runtime: 0.083496 -Set-Cookie: _blog_session=...snip...; path=/; HttpOnly -Cache-Control: no-cache -``` - -布局结构 -------- - -Rails 渲染响应的视图时,会把视图和当前模板结合起来。查找当前模板的方法前文已经介绍过。在布局中可以使用三种工具把各部分合在一起组成完整的响应: - -* 静态资源标签 -* `yield` 和 `content_for` -* 局部视图 - -### 静态资源标签帮助方法 - -静态资源帮助方法用来生成链接到 Feed、JavaScript、样式表、图片、视频和音频的 HTML 代码。Rails 提供了六个静态资源标签帮助方法: - -* `auto_discovery_link_tag` -* `javascript_include_tag` -* `stylesheet_link_tag` -* `image_tag` -* `video_tag` -* `audio_tag` - -这六个帮助方法可以在布局或视图中使用,不过 `auto_discovery_link_tag`、`javascript_include_tag` 和 `stylesheet_link_tag` 最常出现在布局的 `` 中。 - -WARNING: 静态资源标签帮助方法不会检查指定位置是否存在静态资源,假定你知道自己在做什么,只负责生成对应的链接。 - -#### 使用 `auto_discovery_link_tag` 链接到 Feed - -`auto_discovery_link_tag` 帮助方法生成的 HTML,大多数浏览器和 Feed 阅读器都能用来自动识别 RSS 或 Atom Feed。`auto_discovery_link_tag` 接受的参数包括链接的类型(`:rss` 或 `:atom`),传递给 `url_for` 的 Hash 选项,以及该标签使用的 Hash 选项: - -```erb -<%= auto_discovery_link_tag(:rss, {action: "feed"}, - {title: "RSS Feed"}) %> -``` - -`auto_discovery_link_tag` 的标签选项有三个: - -* `:rel`:指定链接 `rel` 属性的值,默认值为 `"alternate"`; -* `:type`:指定 MIME 类型,不过 Rails 会自动生成正确的 MIME 类型; -* `:title`:指定链接的标题,默认值是 `:type` 参数值的全大写形式,例如 `"ATOM"` 或 `"RSS"`; - -#### 使用 `javascript_include_tag` 链接 JavaScript 文件 - -`javascript_include_tag` 帮助方法为指定的每个资源生成 HTML `script` 标签。 - -如果启用了 [Asset Pipeline](asset_pipeline.html),这个帮助方法生成的链接指向 `/assets/javascripts/` 而不是 Rails 旧版中使用的 `public/javascripts`。链接的地址由 Asset Pipeline 伺服。 - -Rails 程序或引擎中的 JavaScript 文件可存放在三个位置:`app/assets`,`lib/assets` 或 `vendor/assets`。详细说明参见 Asset Pipeline 中的“[静态资源的组织方式](asset_pipeline.html#asset-organization)”一节。 - -文件的地址可使用相对文档根目录的完整路径,或者是 URL。例如,如果想链接到 `app/assets`、`lib/assets` 或 `vendor/assets` 文件夹中名为 `javascripts` 的子文件夹中的文件,可以这么做: - -```erb -<%= javascript_include_tag "main" %> -``` - -Rails 生成的 `script` 标签如下: - -```html - -``` - -对这个静态资源的请求由 Sprockets gem 伺服。 - -同时引入 `app/assets/javascripts/main.js` 和 `app/assets/javascripts/columns.js` 可以这么做: - -```erb -<%= javascript_include_tag "main", "columns" %> -``` - -引入 `app/assets/javascripts/main.js` 和 `app/assets/javascripts/photos/columns.js`: - -```erb -<%= javascript_include_tag "main", "/photos/columns" %> -``` - -引入 `http://example.com/main.js`: - -```erb -<%= javascript_include_tag "http://example.com/main.js" %> -``` - -#### 使用 `stylesheet_link_tag` 链接 CSS 文件 - -`stylesheet_link_tag` 帮助方法为指定的每个资源生成 HTML `` 标签。 - -如果启用了 Asset Pipeline,这个帮助方法生成的链接指向 `/assets/stylesheets/`,由 Sprockets gem 伺服。样式表文件可以存放在三个位置:`app/assets`,`lib/assets` 或 `vendor/assets`。 - -文件的地址可使用相对文档根目录的完整路径,或者是 URL。例如,如果想链接到 `app/assets`、`lib/assets` 或 `vendor/assets` 文件夹中名为 `stylesheets` 的子文件夹中的文件,可以这么做: - -```erb -<%= stylesheet_link_tag "main" %> -``` - -引入 `app/assets/stylesheets/main.css` 和 `app/assets/stylesheets/columns.css`: - -```erb -<%= stylesheet_link_tag "main", "columns" %> -``` - -引入 `app/assets/stylesheets/main.css` 和 `app/assets/stylesheets/photos/columns.css`: - -```erb -<%= stylesheet_link_tag "main", "photos/columns" %> -``` - -引入 `http://example.com/main.css`: - -```erb -<%= stylesheet_link_tag "http://example.com/main.css" %> -``` - -默认情况下,`stylesheet_link_tag` 创建的链接属性为 `media="screen" rel="stylesheet"`。指定相应的选项(`:media`,`:rel`)可以重写默认值: - -```erb -<%= stylesheet_link_tag "main_print", media: "print" %> -``` - -#### 使用 `image_tag` 链接图片 - -`image_tag` 帮助方法为指定的文件生成 HTML `` 标签。默认情况下,文件存放在 `public/images` 文件夹中。 - -WARNING: 注意,必须指定图片的扩展名。 - -```erb -<%= image_tag "header.png" %> -``` - -可以指定图片的路径: - -```erb -<%= image_tag "icons/delete.gif" %> -``` - -可以使用 Hash 指定额外的 HTML 属性: - -```erb -<%= image_tag "icons/delete.gif", {height: 45} %> -``` - -可以指定一个Alt属性,在关闭图片的浏览器中显示。如果没指定Alt属性,Rails 会使用图片的文件名,去掉扩展名,并把首字母变成大写。例如,下面两个标签会生成相同的代码: - -```erb -<%= image_tag "home.gif" %> -<%= image_tag "home.gif", alt: "Home" %> -``` - -还可指定图片的大小,格式为“{width}x{height}”: - -```erb -<%= image_tag "home.gif", size: "50x20" %> -``` - -除了上述特殊的选项外,还可在最后一个参数中指定标准的 HTML 属性,例如 `:class`、`:id` 或 `:name`: - -```erb -<%= image_tag "home.gif", alt: "Go Home", - id: "HomeImage", - class: "nav_bar" %> -``` - -#### 使用 `video_tag` 链接视频 - -`video_tag` 帮助方法为指定的文件生成 HTML5 `