nginx缓存cache的5种方案

1. 客户端浏览器上的缓存(非Cookie, Cookie中的内容为: 键和值均为string类型的键值对)

我们可以通过在Http回应中增加特定的头部说明来指定浏览器的缓存策略; 添加头部说明的手段既可以通过页面指令声明设置, 也可以通过编程方式设置.

 

对于图片、Javascript脚本、CSS等资源, 可以在IIS管理器中, 右击图片等资源, 选择”属性” –> HttpHeaders后, 勾选Enable Content Expiration并设置时间即可. 一种值得推荐的手段是, 将需要缓存的资源分类, 如: image/dynamic/、image/static/, 这样我们可以再文件夹上, 选择属性中的HttpHeaders进行设置. 否则, 针对每一个静态资源设置HttpHeaders将是件非常痛苦的事情. 此外, 还可以采用一款名为CacheRight的工具可以提供缓存资源的统一配置.

 

查看或设置浏览器缓存位置:  IE –> Internet选项 –> 常规 –> 临时Internet文件 –> 设置

 

Html文件的Head中的缓存设置:

<meta http-equiv=”pragma” content=”no-cache” />

<meta http-equiv=”Cache-Control” content=”no-cache” />

<meta http-equiv=”expires” content=”Wed, 26 Feb 1997 08:21:57 GMT” />

浏览器中关于Cache的3属性:

Cache-Control:

设置相对过期时间, max-age指明以秒为单位的缓存时间. 若对静态资源只缓存一次, 可以设置max-age的值为315360000000 (一万年).

Http协议的cache-control的常见取值及其组合释义:

no-cache: 数据内容不能被缓存, 每次请求都重新访问服务器, 若有max-age, 则缓存期间不访问服务器.

no-store: 不仅不能缓存, 连暂存也不可以(即: 临时文件夹中不能暂存该资源)

private(默认): 只能在浏览器中缓存, 只有在第一次请求的时候才访问服务器, 若有max-age, 则缓存期间不访问服务器.

public: 可以被任何缓存区缓存, 如: 浏览器、服务器、代理服务器等

max-age: 相对过期时间, 即以秒为单位的缓存时间.

no-cache, private: 打开新窗口时候重新访问服务器, 若设置max-age, 则缓存期间不访问服务器.

private, 正数的max-age: 后退时候不会访问服务器

no-cache, 正数的max-age: 后退时会访问服务器

点击刷新: 无论如何都会访问服务器.

Expires:

设置以分钟为单位的绝对过期时间, 优先级比Cache-Control低, 同时设置Expires和Cache-Control则后者生效.

Last-Modified:

该资源的最后修改时间, 在浏览器下一次请求资源时, 浏览器将先发送一个请求到服务器上, 并附上If-Unmodified-Since头来说明浏览器所缓存资源的最后修改时间, 如果服务器发现没有修改, 则直接返回304(Not Modified)回应信息给浏览器(内容很少), 如果服务器对比时间发现修改了, 则照常返回所请求的资源.

 

注意:

Last-Modified属性通常和Expires或Cache-Control属性配合使用, 因为即使浏览器设置缓存, 当用户点击”刷新”按钮时, 浏览器会忽略缓存继续向服务器发送请求, 这时Last-Modified将能够很好的减小回应开销.

 

ETag将返回给浏览器一个资源ID, 如果有了新版本则正常发送并附上新ID, 否则返回304, 但是在服务器集群情况下, 每个服务器将返回不同的ID, 因此不建议使用ETag.

 

以上描述的客户端浏览器缓存是指存储位置在客户端浏览器, 但是对客户端浏览器缓存的实际设置工作是在服务器上的资源中完成的. 虽然刚才我们介绍了有关于客户端浏览器缓存的属性, 但是实际上对这些属性的设置工作都需要在服务器的资源中做设置. 我们有两种操作手段对浏览器缓存进行设置, 一个是通过页面指令声明来设置, 另外一个是通过编程方式来设置.

 

浏览器缓存的设置手段:

第一: 通过页面指令声明来设置HTTP的缓存

页面指令<%@ OutputCache Location=”Any” Duration=”10” VaryByParam=”ProductId” VaryByHeader=”Accept-Language”%>中的Location用来设置缓存的位置, 该属性常见的值为:

Any(默认): 输出缓存可以位于任何地点, 对应于HttpCacheability.Public. 如: 客户端浏览器、代理服务器或服务器本身.

Client: 只能位于发出请求的客户端浏览器, 对应于HttpCacheability.Private.

Downstream: 输出缓存可以位于除服务器本身的其他任何地方, 如: 客户端浏览器、代理服务器.

Server: 输出缓存位于Web服务器本身, 对应于HttpCacheability.Server

ServerAndClient: 输出缓存只能位于服务器本身或客户端浏览器, 对应于HttpCacheability.Private和HttpCacheability.Server

None: 禁用输出缓存, 对应于HttpCacheability.NoCache.

VaryByParam属性: 根据请求参数的不同而缓存不同的版本. 多个值用分号(;)分隔, *号表示为任意参数或参数组合缓存不同版本, “none”表示只缓存一个版本.

VaryByHeader属性: 根据请求头来缓存不同的版本, 如同一页面的不同语言版本.

VaryByCustom属性: 根据自定义参数来缓存不同的版本, 如: VaryByCunstom=”browser”是系统已实现的, 根据浏览器名称和版本号缓存不同的版本. 也可以, 根据自定义参数来缓存, 如: VaryByCustom=”happy”, 此时系统不知道如何解释happy, 因此需要在Global.asax或IHttpModule实现类中重写GetVaryByCustomString()方法, 来完成处理逻辑.

VaryByControl属性: 根据用户控件中的服务器控件ID来缓存不同版本.

更高级的方式, 是通过配置文件来设置HTTP的缓存.

页面指令为<%@ OutputCache CacheProfile=”cacheconfig”%>, 其中cacheconfig是配置文件中的缓存配置节中CacheProfile的名称.

View Code

<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name=”cacheconfig” duration=”10″ varyByParam=”none”/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>

nginx缓存cache的5种方案

1、传统缓存之一(404)

这个办法是把nginx的404错误定向到后端,然后用proxy_store把后端返回的页面保存。

配置:

location / {
root /home/html/;#主目录
expires 1d;#网页的过期时间
error_page 404 =200 /fetch$request_uri;#404定向到/fetch目录下
}

location /fetch/ {#404定向到这里
internal;#指明这个目录不能在外部直接访问到
expires 1d;#网页的过期时间
alias /home/html/;#虚拟目录文件系统地址要和locaion /一致,proxy_store会将文件保存到这目录下
proxy_pass ;#后端upstream地址,/fetch同时是一个代理
proxy_set_header Accept-Encoding ”;#让后端不要返回压缩(gzip或deflate)的内容,保存压缩后的内容会引发乱子。
proxy_store on;#指定nginx将代理返回的文件保存
proxy_temp_path /home/tmp;#临时目录,这个目录要和/home/html在同一个硬盘分区内
}

使用的时候还有要注意是nginx要有权限往/home/tmp和/home/html下有写入文件的权限,在linux下nginx一般会配置成nobody用户运行,这样这两个目录就要chown nobody,设成nobody用户专用,当然也可以chmod 777,不过所有有经验的系统管理员都会建议不要随便使用777。

2、传统缓存之二(!-e)

原理和404跳转基本一致,但更简洁一些:

location / {
root /home/html/;
proxy_store on;
proxy_set_header Accept-Encoding ”;
proxy_temp_path /home/tmp;
if ( !-f $request_filename )
{
        proxy_pass ;
}
}

可以看到这个配置比404节约了不少代码,它是用!-f来判断请求的文件在文件系统上存不存在,不存在就proxy_pass到后端,返回同样是用proxy_store保存。

两种传统缓存都有着基本一样的优点和缺点:

缺点1:不支持带参数的动态链接,比如read.php?id=1,因为nginx只保存文件名,所以这个链接只在文件系统下保存为read.php,这样用户访问read.php?id=2时会返回不正确的结果。同时不支持这种形式的首页和二级目录,因为nginx非常老实,会将这样的请求照链接写入文件系统,而这个链接显然是一个目录,所以保存失败。这些情况都需要写rewrite才能正确保存。
缺点2:nginx内部没有缓存过期和清理的任何机制,这些缓存的文件会永久性地保存在机器上,如果要缓存的东西非常多,那就会撑暴整个硬盘空间。为此可以使用一个shell脚本定期清理,同时可以撰写php等动态程序来做实时更新。
缺点3:只能缓存200状态码,因此后端返回301/302/404等状态码都不会缓存,假如恰好有一个访问量很大的伪静态链接被删除,那就会不停穿透导致后端承载不小压力。
缺点4:nginx不会自动选择内存或硬盘作为存储介质,一切由配置决定,当然在当前的操作系统里都会有操作系统级的文件缓存机制,所以存在硬盘上也不需要过分担心大并发读取造成的io性能问题。

nginx传统缓存的缺点也是它和squid等缓存软件的不同之特色,所以也可看作其优点。在生产应用中它常常用作和squid的搭档,squid对于带?的链接往往无法阻挡,而nginx能将其访问拦住,例如:?和在squid上会被当做两个链接,所以会造成两次穿透;而nginx只会保存一次,无论链接变成还是,均不能透过nginx缓存,从而有效地保护了后端主机。

nginx会非常老实地将链接形式保存到文件系统中,这样对于一个链接,可以很方便地查阅它在缓存机器上的缓存状态和内容,也可以很方便地和别的文件管理器如rsync等配合使用,它完完全全就是一个文件系统结构。

这两种传统缓存都可以在linux下将文件保存到/dev/shm里,一般我也是这么做的,这样可以利用系统内存来做缓存,利用内存的话,清理过期内容速度就会快得多。使用/dev/shm/时除了要把tmp目录也指向到/dev/shm这个分区外,如果有大量小文件和目录,还要修改一下这个内存分区的inode数量和最大容量:

mount -o size=2500M -o nr_inodes=480000 -o noatime,nodiratime -o remount /dev/shm

上面的命令在一台有3G内存的机器上使用,因为/dev/shm默认最大内存是系统内存的一半就是1500M,这条命令将其调大成2500M,同时shm系统inode数量默认情况下可能是不够用的,但有趣的是它可以随意调节,这里调节为480000保守了点,但也基本够用了。

3、基于memcached的缓存

nginx对memcached有所支持,但是功能并不是特别之强,性能上还是非常之优秀。

location /mem/ {
    if ( $uri ~ “^/mem/([0-9A-Za-z_]*)$” )
    {
     set $memcached_key “$1″;
     memcached_pass     192.168.1.2:11211;
    }
    expires 70;
}

这个配置会将指明到memcached的abc这个key去取数据。

nginx目前没有写入memcached的任何机制,所以要往memcached里写入数据得用后台的动态语言完成,可以利用404定向到后端去写入数据。

4、基于第三方插件ncache

ncache是新浪兄弟开发的一个不错的项目,它利用nginx和memcached实现了一部分类似squid缓存的功能,我并没有使用这个插件的经验,可以参考:

 

5、nginx新开发的proxy_cache功能

从nginx-0.7.44版开始,nginx支持了类似squid较为正规的cache功能,目前还处于开发阶段,支持相当有限,这个缓存是把链接用md5编码hash后保存,所以它可以支持任意链接,同时也支持404/301/302这样的非200状态。

配置:

首先配置一个cache空间:

proxy_cache_path /path/to/cache levels=1:2 keys_zone=NAME:10m inactive=5m max_size=2m clean_time=1m;

#注意这个配置是在server标签外,levels指定该缓存空间有两层hash目录,第一层目录是1个字母,第二层为2个字母,保存的文件名就会类似/path/to/cache/c/29/b7f54b2df7773722d382f4809d65029c;keys_zone为这个空间起个名字,10m指空间大小为10MB;inactive的5m指缓存默认时长5分钟;max_size的2m是指单个文件超过2m的就不缓存;clean_time指定一分钟清理一次缓存。

location / {
    proxy_pass ;

    proxy_cache NAME;#使用NAME这个keys_zone

    proxy_cache_valid 200 302 1h;#200和302状态码保存1小时
    proxy_cache_valid 301 1d;#301状态码保存一天
    proxy_cache_valid any 1m;#其它的保存一分钟
}

ps:支持cache的0.7.44到0.7.51这几个版本的稳定性均有问题,访问有些链接会出现错误,所以这几个版本最好不要在生产环境中使用。nginx-0.7下目前所知较为稳定的版本是0.7.39。稳定版0.6.36版也是近期更新,如果在配置里没有使用到0.7的一些新标签新功能,也可以使用0.6.36版。

Posted in Uncategorized

Rubyonrails Guide to Active Record Associations

Active Record

Active Record(中文名:活动记录)是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。Active Record 和Row Gateway (行记录入口)十分相似,但前者是领域模型,后者是一种数据源模式。关系型数据库往往通过外键来表述实体关系,Active Record 在数据源层面上也将这种关系映射为对象的关联和聚集。

Active Record 适合非常简单的领域需求,尤其在领域模型和数据库模型十分相似的情况下。如果遇到更加复杂的领域模型结构(例如用到继承、策略的领域模型),往往需要使用分离数据源的领域模型,结合Data Mapper (数据映射器)使用。

Active Record 驱动框架一般兼有ORM 框架的功能,但ActivActive Recorde Record 不是简单的ORM,正如和Row Gateway 的区别。著名的例子是全栈(Full Stack)Web 开发框架Ruby on Rails ,其默认使用一个纯Ruby 写成的Active Record 框架来驱动MVC 中的模型层。

对象关系映射(ORM)提供了概念性的、易于理解的模型化数据的方法。ORM方法论基于三个核心原则:简单:以最基本的形式建模数据。传达性:数据库结构被任何人都能理解的语言文档化。精确性:基于数据模型创建正确标准化了的结构。

在Martin Fowler 的《企业应用架构模式》一书中曾详细叙述了本模式。

以下是著名的Active Record 驱动框架:

SQLObject(Python)

Ruby on Rails ActiveRecord (Ruby)

Yii Framework ActiveRecord (PHP)

Castle ActiveRecord (.NET)

Migrations

Migrations are a convenient way for you to alter移动your database in a structured and organized manner.Migrations是一种很便捷的方法让你能够以一种结构化的和有组织的方式来迁移你的数据库。You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them.你可以手动编辑SQL片段,而且你有责任把这些告诉其他的开发人员,因为他们需要开发和使用它们。You’d also have to keep track of which changes need to be run against the production machines next time you deploy.你也可以跟踪对你部署的代码在接下来的production机器(将会)发生的变化。

Active Record tracks which migrations have already been run so all you have to do is update your source and run rake db:migrate.Active Record跟踪并迁移你已经运行过的(代码和数据),而你只需要在更新了你的源代码的时候执行rake db:migrateActive Record will work out which migrations should be run.Active Recor将会计算出那些迁移需要被执行。It will also update your db/schema.rb file to match the structure of your database.它还会更新你的db/schema.rb文件使其于你的数据库结构相匹配。

Rails使用的是Active Record 框架来处理数据迁移,这里笔者把ActiveRecord框架放在一个地方学习了,如需了解Migration部分需要直接阅读Migration部分。

 

Active Record Validations and Callbacks 活动记录验证和回调

This guide teaches you how to hook勾子into the life cycle of your Active Record objects.这个教程指导你怎样挂接到你的Active Record objects的生存周期。You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.你将会学习到在将数据对象存入数据库之前怎样验证它们的状态,以及在对象生存周期的一些点上怎样执行定制操作。

Rails使用的是Active Record 框架来处理验证和回调,这里笔者把ActiveRecord框架放在一个地方学习了,如需了解Migration部分需要直接阅读ValidationsandCallbacks部分。

A Guide to Active Record Associations

This guide covers the association features of Active Record. By referring to this guide, you will be able to:本教程涵盖了Active Record的关系部分的特性。(通过)这个教程提及的,你将能够:

  • Declare associations between Active Record models 在Active Record的models中声明(它们的)关系
  • Understand the various types of Active Record associations 明白各种类型的Active Record关系
  • Use the methods added to your models by creating associations 通过添加方法到models(的形式)来创建关系

1 Why Associations?为什么(会有)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:为什么在models之间需要关系?因为它们使得在你的代码中大多数操作更加简单和容易。例如,思考一个简单的Rails应用程序其中包含一个customers的models和一个orders的模型(生产者和消费者模型)。每个消费者可以拥有多个生产者。没有associations(的话),模型声明看起来像这样:

classCustomer<ActiveRecord::Base

end

classOrder<ActiveRecord::Base

end

Now,suppose we wanted to add a new order for an existing customer.We’d need to do something like this:现在假设我们想为一个存在的customer添加一个新的order。我们需要做如下事情:

@order=Order.create(:order_date=>Time.now,

:customer_id=>@customer.id)

Or consider deleting a customer, and ensuring that all of its orders get deleted as well:或者考虑删除一个customer,那么确保它的所有的orders也被删除。

@orders = Order.where(:customer_id => @customer.id)

@orders.each do |order|

order.destroy

end

@customer.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:通过Active Record associations,我们可以精简这样的或者其它(类似的)操作通过声明的方式告诉Rails在两个models之间有一个连接。这里修订代码来设定customers和orders:

class Customer < ActiveRecord::Base

has_many :orders, :dependent => :destroy

end

 

class Order < ActiveRecord::Base

belongs_to :customer

end

With this change, creating a new order for a particular customer is easier:通过这样的修改,创建一个新的order给一个特定的customer更加容易:

@order = @customer.orders.create(:order_date => Time.now)

Deleting a customer and all of its orders is much easier:删除一个customer和它的所有的orders也容易了许多:

@customer.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.想要学习更多不同的类型的associations,阅读guide接下来的部分。这些(内容)伴随这一些在(使用)associations工作发现的tips和tricks,然后是一些Rails的associations的一些方法和options的一些完整的参考。

2 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,一个association是一个在两个ActiveRecord模型之间的连接。Associations(通过)使用宏方式的调用实施,以至于你可以(以)声明的方式添加features到你的models。例如申明一个modelbelongs_to另一个(模型)。Rails支持六种类型的associations

  • belongs_to
  • has_one
  • has_many
  • has_many :through
  • has_one :through
  • has_and_belongs_to_many

In the remainder of this guide,youll learn how to declare and use the various forms of associations. Butfirst,a quick introduction to the situations where each association type is appropriate.在这个guide的其他的部分,你将会学习到怎样申明和使用各种的形式的associations。但是首先,做一个快速的说明:每种类型的association在什么情况下出现。

2.1 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:一个belongs_to关系设立一个one-to-one连接到另一个model,通过这样声明model的每一个实例belongs to另一个模型的实例。例如,如果你的应用程序包含customersorders,并且每个order能够关联到仅仅一个customer,你可以这样的方式申明order模型:

class Order < ActiveRecord::Base

belongs_to :customer

end

2.2 The has_one Association

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:

一个has_one关系也设定一个one-to-one连接到另一个model,但是有某些不同的语义(和结果)。这个关系指出了(声明的)模型的每一个实例包含或拥有一个其他模型的实例。例如你的应用程序中的每一个supplier仅仅只有一个account,你可以这样声明supplier模型:

class Supplier < ActiveRecord::Base

has_one :account

end

 

2.3 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:

一个has_many关系指定了一个one-to-many连接到另一个model。你会经常发现这个关系在belongs_to关系的另一个端。这个关系指明了(声明)模型的每一个实例有零个或多个其它模型的实例。例如,在一个应用程序中包含customersorderscustomer模型如下声明:

class Customer < ActiveRecord::Base

has_many :orders

end

The name of the other model is pluralized多元化when declaring a has_many association.

 

2.4 The has_many :through Association

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:

一个has_many :through关系通常用于设定一个many-to-many连接到另一个模型。这个关系指定申明的模型可以通过第三个模型(中间模型)匹配零个或多个另一个模型的实例。例如,想想一个医疗实践(例子)其中病人约定时间去看医生。它们的有关关系声明可以如下:

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

The collection of join models can be managed via通过the API. For example, if you assign加入模型的集合可以通过API管理。例如,如果你指定

physician.patients = patients

new join models are created for newly associated关联的objects, and if some are gone their rows are deleted.

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:

has_many :through关系也有利于设定快捷方式通过嵌套has_many关系。

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

With :through => :sections specified, Rails will now understand:因为:through => :sections被指定,Rails现在将会明白如下(语句):

@document.paragraphs

2.5 The has_one :through Association

A has_one :through association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding through a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this:

一个has_one :through关系设定一个one-to-one连接到另一个模型。这个关系指明声明的模型可以匹配另个模型的一个实例通过第三个模型(中间模型)。例如,如果每个supplier(供应商)有一个account,并且每个account关联一个account history,那么customer模型将会像这样:

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

 

2.6 The has_and_belongs_to_many Association

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:

一个has_and_belongs_to_many关系创建一个直接的many-to-many连接到另一个模型,没有干扰模型。例如,如果你的应用程序包含assembliesparts,其中每个assembly拥有多个parts并且每个part发生在多个集会中,你可以以这样的方式声明模型:

class Assembly < ActiveRecord::Base

has_and_belongs_to_many :parts

end

 

class Part < ActiveRecord::Base

has_and_belongs_to_many :assemblies

end

 

2.7 Choosing Between belongs_to and has_one

If you want to set up a 1–1 relationship between two models, you’ll need to add belongs_to to one, and has_one to the other. How do you know which is which?

如果你想在两个模型间设定一个1-1的关系,你将需要添加belongs_to到一个(模型中),以及has_one到另一个(模型中)。你是怎么知道谁是谁的呢?

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 yoursthat 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:

class Supplier < ActiveRecord::Base

has_one :account

end

 

class Account < ActiveRecord::Base

belongs_to :supplier

end

The corresponding migration might look like this:

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

Using t.integer :supplier_id makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using t.references :supplier instead.使用t.integer :supplier_id使得外建命名明显和精确。在当前版本的Rails,你可以抽象掉这个实施细节通过使用t.references :supplier替代。

2.8 Choosing Between has_many :through and has_and_belongs_to_many

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:

Rails提供了两种不同的方式来声明一个模型间的many-to-many关系。比较简单的方式使用has_and_belongs_to_many,它允许你直接的(创建)关系:

class Assembly < ActiveRecord::Base

has_and_belongs_to_many :parts

end

 

class Part < ActiveRecord::Base

has_and_belongs_to_many :assemblies

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:

第二种方式是声明一个many-to-many关系使用has_many :through,它可以使得关系间接的,同加入一个模型:

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

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).最简单的使用规则是,如果你需要使用关系模型作为依赖实体来工作你应该设定一个has_many :through关系

You should use has_many:through if you need validations, callbacks, or extra attributes on the join model.

2.9 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:例如,你可能有一个picture模型属于一个employee或者一个product模型。这是它们如何被定义的:

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

You can think of a polymorphic belongs_to declaration as setting up an interface that any other model can use. From an instance of the Employee model, you can retrieve a collection of pictures: @employee.pictures.

你可以认为一个多元belongs_to声明相当于设定一个其它任何模型可以使用的接口。来源于Employee模型的实例,你可以检索一个pictures的集合:@employee.pictures

Similarly, you can retrieve @product.pictures.相近的,你可以检索@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:如果你有一个Picture模型的实例,你可以得到它的父模型通过@picture.imageable。要使这些工作,你需要声明一个外键字段和一个什么多元(关系)的接口的字段:

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

This migration can be simplified by using the t.references form:这个migration可以通过t.references形式简洁的(声明):

class CreatePictures < ActiveRecord::Migration

def change

create_table :pictures do |t|

t.string :name

t.references :imageable, :polymorphic => true

t.timestamps

end

end

end

2.10 Self Joins

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:在数据库设计中,有时你会发现一个模型具有一个(关联)到它自己的关系例如,你可能希望保存所有的employees在单个数据库模型中,但是能够跟踪关系比如在manager和subordinates之间。这个情况可以设定self-joining模型:

class Employee < ActiveRecord::Base

has_many :subordinates, :class_name => "Employee"

belongs_to :manager, :class_name => "Employee",

:foreign_key => "manager_id"

end

With this setup, you can retrieve @employee.subordinates and @employee.manager.通过这个设定,你可以检索@employee.subordinates and @employee.manager

3 Tips提示, Tricks窍门, and Warnings警告

Here are a few things you should know to make efficient use of Active Record associations in your Rails applications:这里有一些事情你应该知道(它)使得在你的Rails应用程序使用Active Record关系变得高效:

  • Controllingcaching控制缓存
  • Avoidingnamecollisions避免名称碰撞
  • Updating the schema 更新结构
  • Controlling association scope 控制关系范围

3.1 Controlling Caching

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:所有的关系方法都创建在缓存周围,这使的最近的查询,可用于进一步的操作的结果。

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

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:但是如果你想重载cache,因为数据可能被应用程序的其他部分改变?仅仅通过添加投入额到关系调用:

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

3.2 Avoiding Name Collisions避免名称碰撞

You are not free to use just any name for your associations. Because creating an association adds a method with that name to the model, it is a bad idea to give an association a name that is already used for an instance method of ActiveRecord::Base. The association method would override the base method and break things. For instance, attributes or connection are bad names for associations.

你不能不限制的给你的关系使用任何名字。因为创建一个关系添加一个方法和你命名的名字到模型,给一个关系取一个在ActiveRecord::Base实例方法中已经使用了的方法不是个好主意。这个关系方法将会覆盖原有方法和打断事情。比如实例,attributes or connection是关系的坏名称。

3.3 Updating the Schema

Associations are extremely useful, but they are not magic. You are responsible for maintaining your database schema to match your associations. In practice, this means two things, depending on what sort of associations you are creating. For belongs_to associations you need to create foreign keys, and for has_and_belongs_to_many associations you need to create the appropriate join table.

关系相当有用,但是它们不是魔法。你有责任注意你的数据库结构和你的关系的匹配。在实际中,这意味这两件事,依赖于你创建的何种关系。对于belongs_to关系你需要创建外键,对应has_and_belongs_to_many关系你需要创建适当的(关系)加入表中。

3.3.1 Creating Foreign Keys for belongs_to Associations

When you declare a belongs_to association, you need to create foreign keys as appropriate. For example, consider this model:当你声明一个belongs_to关系,你需要创建一个外键(作为合适的方式来更新数据结构)。例如,思考如下模型:

class Order < ActiveRecord::Base

belongs_to :customer

end

This declaration needs to be backed up by the proper foreign key declaration on the orders table:这个声明需要通过在生产者表单声明适当的外键备份。

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

If you create an association some time after you build the underlying底层model, you need to remember to create an add_column migration to provide the necessary foreign key.有时你创建的关系在你创建的底层模型之后,你需要记住创建一个add_column migration提供必须的外键。

 

 

3.3.2 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.

如果你创建了一个has_and_belongs_to_many关系,你需要准确的创建joining表单。除非join表单使用:join_table选项准确的指定,(否则)Active Record就会使用类名提供的的词汇创建表单名称。因此一个在customerorder模型之间join(的表单)将会默认插入表名customers_orders因为co前面在提供的词汇中。

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:

class Assembly < ActiveRecord::Base

has_and_belongs_to_many :parts

end

 

class Part < ActiveRecord::Base

has_and_belongs_to_many :assemblies

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:

class CreateAssemblyPartJoinTable < ActiveRecord::Migration

def change

create_table :assemblies_parts, :id => false do |t|

t.integer :assembly_id

t.integer :part_id

end

end

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.

3.4 Controlling Association Scope控制关系范围

By default, associations look for objects only within the current module’s scope. This can be important when you declare Active Record models within a module. For example:默认情况,(通过)关系查找对象仅仅在当前模型范围中。这很重要点你在一个module中声明Active Record models例如:

module MyApplication

module Business

class Supplier < ActiveRecord::Base

has_one :account

end

 

class Account < ActiveRecord::Base

belongs_to :supplier

end

end

end

This will work fine, because both the Supplier and the Account class are defined within the same scope. But the following will not work, because Supplier and Account are defined in different scopes:

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

To associate a model with a model in a different namespace, you must specify the complete class name in your association declaration:

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

4 Detailed Association Reference具体关系参考

The following sections give the details of each type of association, including the methods that they add and the options that you can use when declaring an association.接下来的部分给出了每种关系的具体的介绍,包含当你声明一个关系时可以使用的(这些关系)添加的方法和选项。

4.1.1 Methods Added by belongs_to

When you declare a belongs_to association, the declaring class automatically gains受益four methods related to the association:

  • association(force_reload = false)
  • association=(associate)
  • build_association(attributes = {})
  • create_association(attributes = {})

In all of these methods, association is replaced with the symbol passed as the first argument to belongs_to. For example, given the declaration:

class Order < ActiveRecord::Base

belongs_to :customer

end

Each instance of the order model生产者模型will have these methods:

customer

customer=

build_customer

create_customer

 

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新建前缀.

4.1.1.1 association(force_reload = false)

The association method returns the associated object, if any. If no associated object is found, it returns nil.

@customer = @order.customer

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.

4.1.1.2 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 objects foreign key to the same value.

@order.customer = @customer

4.1.1.3 build_association(attributes = {})

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.

@customer = @order.build_customer(:customer_number => 123,

:customer_name => “John Doe”)

4.1.1.4 create_association(attributes = {})

The create_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. In addition, the associated object will be saved (assuming that it passes any validations).

@customer = @order.create_customer(:customer_number => 123,

:customer_name => “John Doe”)

4.1.2 Options for belongs_to

In many situations, you can use the default behavior of belongs_to without any customization. But despite尽管Rails’ emphasis强调of convention公约over customization, you can alter that behavior in a number of ways. This section covers the options that you can pass when you create a belongs_to association. For example, an association with several options might look like this:

class Order < ActiveRecord::Base

belongs_to :customer, :counter_cache => true,

:conditions => “active = 1″

end

The belongs_to association supports these options:

  • :autosave
  • :class_name
  • :conditions 条件
  • :counter_cache
  • :dependent
  • :foreign_key
  • :include
  • :polymorphic 多元
  • :readonly
  • :select
  • :touch
  • :validate
4.1.2.1 :autosave

If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.

4.1.2.2 :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:

4.1.2.3 :conditions

The :conditions option lets you specify the conditions that the associated object must meet (in the syntax used by an SQL WHERE clause).

class Order < ActiveRecord::Base

belongs_to :customer, :conditions => “active = 1″

end

4.1.2.4 :counter_cache

The :counter_cache option can be used to make finding the number of belonging objects more efficient. Consider these models:

class Order < ActiveRecord::Base

belongs_to :customer

end

class Customer < ActiveRecord::Base

has_many :orders

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:

class Order < ActiveRecord::Base

belongs_to :customer, :counter_cache => true

end

class Customer < ActiveRecord::Base

has_many :orders

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:

class Order < ActiveRecord::Base

belongs_to :customer, :counter_cache => :count_of_orders

end

class Customer < ActiveRecord::Base

has_many :orders

end

Counter cache columns are added to the containing model’s list of read-only attributes through attr_readonly.

4.1.2.5 :dependent

If you set the :dependent option to :destroy, then deleting this object will call the destroy method on the associated object to delete that object. If you set the :dependent option to :delete, then deleting this object will delete the associated object without calling its destroy method.

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.对应多个的话用了dependent的话删除一个和他的外键的话其他关联就失效了

4.1.2.6 :foreign_key

By convention约定, Rails guesses 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:

class Order < ActiveRecord::Base

belongs_to :customer, :class_name => “Patron”,

:foreign_key => “patron_id”

end

In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.

4.1.2.7 :include

You can use the :include option to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:

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

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:如果你频繁的从line_item(@line_item.order.customer)直接检索,那么你可以在line_item关系中包含customers,使得你的代码变得更加有效率:

class LineItem < ActiveRecord::Base

belongs_to :order, :include => :customer

end

 

class Order < ActiveRecord::Base

belongs_to :customer

has_many :line_items

end

 

class Customer < ActiveRecord::Base

has_many :orders

end

There’s no need to use :include for immediate立即(直接)associations – that is, if you have Order belongs_to :customer, then the customer is eager-loaded automatically when it’s needed.

4.1.2.8 :polymorphic

Passing true to the :polymorphic option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail earlierinthisguide.

4.1.2.9 :readonly

If you set the :readonly option to true, then the associated object will be read-only when retrieved via the association.

4.1.2.10 :select

The :select option lets you override the SQL SELECT clause that is used to retrieve data about the associated object. By default, Rails retrieves all columns.

If you set the :select option on a belongs_to association, you should also set the foreign_key option to guarantee保证the correct results.

4.1.2.11 :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:

class Order < ActiveRecord::Base

belongs_to :customer, :touch => true

end

 

class Customer < ActiveRecord::Base

has_many :orders

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:

class Order < ActiveRecord::Base

belongs_to :customer, :touch => :orders_updated_at

end

4.1.2.12 :validate

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.

4.1.3 How To Know Whether There’s an Associated Object?怎样知道这里是否有Associated 对象?

To know whether there’s and associated object just check association.nil?:

if @order.customer.nil?

@msg = “No customer found for this order”

end

4.1.4 When are Objects Saved?

Assigning an object to a belongs_to association does not automatically save the object. It does not save the associated object either.

4.2 has_one Association Reference has_one关系参考

The has_one association creates a one-to-one match with another model. In database terms, this association says that the other class contains the foreign key. If this class contains the foreign key, then you should use belongs_to instead.

4.2.1 Methods Added by has_one

When you declare a has_one association, the declaring class automatically gains four methods related to the association:

  • association(force_reload = false)
  • association=(associate)
  • build_association(attributes = {})
  • create_association(attributes = {})

In all of these methods, association is replaced with the symbol passed as the first argument to has_one. For example, given the declaration:

class Supplier < ActiveRecord::Base

has_one :account

end

Each instance of the Supplier model will have these methods:每一个Supplier模型的实例将会具有如下方法:

account

account=

build_account

create_account

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.

4.2.1.1 association(force_reload = false)

The association method returns the associated object, if any. If no associated object is found, it returns nil.

@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.

4.2.1.2 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.

@supplier.account = @account

4.2.1.3 build_association(attributes = {})

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 its foreign key will be set, but the associated object will notyetbesaved.

@account = @supplier.build_account(:terms => “Net 30″)

4.2.1.4 create_association(attributes = {})

The create_association method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through its foreign key will be set. In addition此外, the associated object willbesaved(assumingthatitpassesanyvalidations).

@account = @supplier.create_account(:terms => “Net 30″)

4.2.2 Options for has_one

In many situations, you can use the default behavior of has_one without any customization. But despite Rails’ emphasis of convention over customization, you can alter that behavior in a number of ways. This section covers the options that you can pass when you create a has_one association. For example, an association with several options might look like this:

class Supplier < ActiveRecord::Base

has_one :account, :class_name => “Billing”, :dependent => :nullify

end

The has_one association supports these options:

  • :as
  • :autosave
  • :class_name
  • :conditions
  • :dependent
  • :foreign_key
  • :include
  • :order
  • :primary_key
  • :readonly
  • :select
  • :source
  • :source_type
  • :through
  • :validate
4.2.2.1 :as

Setting the :as option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail earlierinthisguide.

4.2.2.2 :autosave

If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.

4.2.2.3 :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 supplier has an account, but the actual name of the model containing accounts is Billing, you’d set things up this way:

class Supplier < ActiveRecord::Base

has_one :account, :class_name => “Billing”

end

4.2.2.4 :conditions

The :conditions option lets you specify the conditions that the associated object must meet (in the syntax used by an SQL WHERE clause).

class Supplier < ActiveRecord::Base

has_one :account, :conditions => “confirmed = 1″

end

4.2.2.5 :dependent

If you set the :dependent option to :destroy, then deleting this object will call the destroy method on the associated object to delete that object. If you set the :dependent option to :delete, then deleting this object will delete the associated object without calling its destroy method. If you set the :dependent option to :nullify, then deleting this object will set the foreign key in the association object to NULL.

4.2.2.6 :foreign_key

By convention, Rails guesses that the column used to hold the foreign key on the other model is the name of this model with the suffix _id added添加id后缀. The :foreign_key option lets you set the name of the foreign key directly:

class Supplier < ActiveRecord::Base

has_one :account, :foreign_key => “supp_id”

end

In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.

4.2.2.7 :include

You can use the :include option to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:

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

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:

class Supplier < ActiveRecord::Base

has_one :account, :include => :representative

end

 

class Account < ActiveRecord::Base

belongs_to :supplier

belongs_to :representative

end

 

class Representative < ActiveRecord::Base

has_many :accounts

end

4.2.2.8 :order

The :order option dictates the order in which associated objects will be received (in the syntax used by an SQL ORDER BY clause). Because a has_one association will only retrieve a single associated object, this option should not be needed.

4.2.2.9 :primary_key

By convention, Rails guesses that the column used to hold the primary key of this model is id. You can override this and explicitly specify the primary key with the :primary_key option.

4.2.2.10 :readonly

If you set the :readonly option to true, then the associated object will be read-only when retrieved via the association.

4.2.2.11 :select

The :select option lets you override the SQL SELECT clause that is used to retrieve data about the associated object. By default, Rails retrieves all columns.

.2.2.12 :source

The :source option specifies the source association name for a has_one :through association.

4.2.2.13 :source_type

The :source_type option specifies the source association type for a has_one :through association that proceeds through a polymorphic association.

4.2.2.14 :through

The :through option specifies a join model through which to perform the query. has_one :through associations were discussed in detail earlierinthisguide.

4.2.2.15 :validate

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.

4.2.3 How To Know Whether There’s an Associated Object?

To know whether there’s and associated object just check association.nil?:

if @supplier.account.nil?

@msg = “No account found for this supplier”

end

4.2.4 When are Objects Saved?

When you assign an object to a has_one association, that object is automatically saved (in order to update its foreign key). In addition, any object being replaced is also automatically saved, because its foreign key will change too.

If either of these saves fails due to validation errors, then the assignment statement returns false and the assignment itself is cancelled.

If the parent object (the one declaring the has_one association) is unsaved (that is, new_record? returns true) then the child objects are not saved. They will automatically when the parent object is saved.

If you want to assign an object to a has_one association without saving the object, use the association.build method.

4.3 has_many Association Reference

The has_many association creates a one-to-many relationship with another model. In database terms, this association says that the other class will have a foreign key that refers to instances of this class.

4.3.1 Methods Added by has_many

When you declare a has_many association, the declaring class automatically gains 13 methods related to the association:

collection(force_reload = false)

  • collection<<(object,)
  • collection.delete(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 = {})

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:

class Customer < ActiveRecord::Base

has_many :orders

end

Each instance of the customer model will have these methods:

 

orders(force_reload = false)

orders<<(object, …)

orders.delete(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 = {})

4.3.1.1 collection(force_reload = false)

The collection method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.

@orders = @customer.orders

4.3.1.2 collection<<(object,)

The collection<< method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model.

@customer.orders << @order1

4.3.1.3 collection.delete(object,)

The collection.delete method removes one or more objects from the collection by setting their foreign keys to NULL.

@customer.orders.delete(@order1)

Additionally, objects will be destroyed if they’re associated with :dependent => :destroy, and deleted if they’re associated with :dependent => :delete_all.

4.3.1.4 collection=objects

The collection= method makes the collection contain only the supplied objects, by adding and deleting as appropriate.

4.3.1.5 collection_singular_ids

The collection_singular_ids method returns an array of the ids of the objects in the collection.

@order_ids = @customer.order_ids

4.3.1.6 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适当.

4.3.1.7 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.

4.3.1.8 collection.empty?

The collection.empty? method returns true if the collection does not contain any associated objects.

<% if @customer.orders.empty? %>

No Orders Found

<% end %>

4.3.1.9 collection.size

The collection.size method returns the number of objects in the collection.

@order_count = @customer.orders.size

4.3.1.10 collection.find()

The collection.find method finds objects within the collection. It uses the same syntax and options as ActiveRecord::Base.find.

@open_orders = @customer.orders.where(:open => 1)

4.3.1.11 collection.where()

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.

@open_orders = @customer.orders.where(:open => true) # No query yet

@open_order = @open_orders.first # Now the database will be queried

4.3.1.12 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?.

4.3.1.13 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.

@order = @customer.orders.build(:order_date => Time.now,

:order_number => “A12345″)

4.3.1.14 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 the associated object will be saved (assuming that it passes any validations).

@order = @customer.orders.create(:order_date => Time.now,

:order_number => “A12345″)

4.3.2 Options for has_many

In many situations, you can use the default behavior for has_many without any customization. But you can alter that behavior in a number of ways. This section covers the options that you can pass when you create a has_many association. For example, an association with several options might look like this:

class Customer < ActiveRecord::Base

has_many :orders, :dependent => :delete_all, :validate => :false

end

The has_many association supports these options:

  • :as
  • :autosave
  • :class_name
  • :conditions
  • :counter_sql
  • :dependent
  • :extend
  • :finder_sql
  • :foreign_key
  • :group
  • :include
  • :limit
  • :offset
  • :order
  • :primary_key
  • :readonly
  • :select
  • :source
  • :source_type
  • :through
  • :uniq
  • :validate
4.3.2.1 :as

Setting the :as option indicates that this is a polymorphic association, as discussed earlierinthisguide.

4.3.2.2 :autosave

If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.

4.3.2.3 :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:

class Customer < ActiveRecord::Base

has_many :orders, :class_name => “Transaction”

end

4.3.2.4 :conditions

The :conditions option lets you specify the conditions that the associated object must meet (in the syntax used by an SQL WHERE clause).

class Customer < ActiveRecord::Base

has_many :confirmed_orders, :class_name => “Order”,

:conditions => “confirmed = 1″

end

You can also set conditions via a hash:

class Customer < ActiveRecord::Base

has_many :confirmed_orders, :class_name => “Order”,

:conditions => { :confirmed => true }

end

If you use a hash-style :conditions 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 need to evaluate conditions dynamically at runtime, use a proc:

4.3.2.5 :counter_sql

Normally Rails automatically generates the proper SQL to count the association members. With the :counter_sql option, you can specify a complete SQL statement to count them yourself.

If you specify :finder_sql but not :counter_sql, then the counter SQL will be generated by substituting SELECT COUNT(*) FROM for the SELECT ... FROM clause of your :finder_sql statement.

4.3.2.6 :dependent

If you set the :dependent option to :destroy(删除映射链和映射对象), then deleting this object will call the destroy method on the associated objects to delete those objects. If you set the :dependent option to :delete_all(只删除映射链), then deleting this object will delete the associated objects without calling their destroy method. If you set the :dependent option to :nullify, then deleting this object will set the foreign key in the associated objects to NULL.

This option is ignored when you use the :through option on the association.

4.3.2.7 :extend

The :extend option specifies a named module to extend the association proxy. Association extensions are discussed in detail laterinthisguide.

4.3.2.8 :finder_sql

Normally Rails automatically generates the proper SQL to fetch the association members. With the :finder_sql option, you can specify a complete SQL statement to fetch them yourself. If fetching objects requires complex multi-table SQL, this may be necessary.

4.3.2.9 :foreign_key

By convention, Rails guesses 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:

In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.

4.3.2.10 :group

The :group option supplies an attribute name to group the result set by, using a GROUP BY clause in the finder SQL.

class Customer < ActiveRecord::Base

has_many :line_items, :through => :orders, :group => “orders.id”

end

4.3.2.11 :include

You can use the :include option to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:

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

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:

class Customer < ActiveRecord::Base

has_many :orders, :include => :line_items

end

 

class Order < ActiveRecord::Base

belongs_to :customer

has_many :line_items

end

 

class LineItem < ActiveRecord::Base

belongs_to :order

end

4.3.2.12 :limit

The :limit option lets you restrict the total number of objects that will be fetched through an association.

class Customer < ActiveRecord::Base

has_many :recent_orders, :class_name => “Order”,

:order => “order_date DESC”, :limit => 100

end

4.3.2.13 :offset

The :offset option lets you specify the starting offset for fetching objects via an association. For example, if you set :offset => 11, it will skip the first 11 records.

4.3.2.14 :order

The :order option dictates the order in which associated objects will be received (in the syntax used by an SQL ORDER BY clause).

class Customer < ActiveRecord::Base

has_many :orders, :order => “date_confirmed DESC”

end

4.3.2.15 :primary_key

By convention, Rails guesses 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.

4.3.2.16 :readonly

If you set the :readonly option to true, then the associated objects will be read-only when retrieved via the association.

4.3.2.17 :select

The :select option lets you override the SQL SELECT clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns.

If you specify your own :select, be sure to include the primary key and foreign key columns of the associated model. If you do not, Rails will throw an error.

4.3.2.18 :source

The :source option specifies the source association name for a has_many :through association. You only need to use this option if the name of the source association cannot be automatically inferred from the association name.

4.3.2.19 :source_type

The :source_type option specifies the source association type for a has_many :through association that proceeds through a polymorphic association.

4.3.2.20 :through

The :through option specifies a join model through which to perform the query. has_many :through associations provide a way to implement many-to-many relationships, as discussed earlierinthisguide.

4.3.2.21 :uniq

Set the :uniq option to true to keep the collection free of duplicates. This is mostly useful together with the :through option.

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 # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]

Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]

In the above case there are two readings and person.posts brings out both of them even though these records are pointing to the same post.

Now let’s set :uniq to true:

class Person

has_many :readings

has_many :posts, :through => :readings, :uniq => true

end

 

person = Person.create(:name => ‘honda’)

post = Post.create(:name => ‘a1′)

person.posts << post

person.posts << post

person.posts.inspect # => [#<Post id: 7, name: "a1">]

Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]

In the above case there are still two readings. However person.posts shows only one post because the collection loads only unique records.

4.3.2.22 :validate

If you set the :validate option to false, then associated objects will not be validated whenever you save this object. By default, this is true: associated objects will be validated when this object is saved.

4.3.3 When are Objects Saved?

When you assign an object to a has_many association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.

If any of these saves fails due to validation errors, then the assignment statement returns false and the assignment itself is cancelled.

If the parent object (the one declaring the has_many association) is unsaved (that is, new_record? returns true) then the child objects are not saved when they are added. All unsaved members of the association will automatically be saved when the parent is saved.

If you want to assign an object to a has_many association without saving the object, use the collection.build method.

4.4 has_and_belongs_to_many Association Reference

The has_and_belongs_to_many association creates a many-to-many relationship with another model. In database terms, this associates two classes via an intermediate join table that includes foreign keys referring to each of the classes.

4.4.1 Methods Added by has_and_belongs_to_many

When you declare a has_and_belongs_to_many association, the declaring class automatically gains 13 methods related to the association:

  • collection(force_reload = false)
  • collection<<(object,)
  • collection.delete(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 = {})

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:

class Part < ActiveRecord::Base

has_and_belongs_to_many :assemblies

end

Each instance of the part model will have these methods:

assemblies(force_reload = false)

assemblies<<(object, …)

assemblies.delete(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 = {})

4.4.1.1 Additional Column Methods

If the join table for a has_and_belongs_to_many association has additional columns beyond the two foreign keys, these columns will be added as attributes to records retrieved via that association. Records returned with additional attributes will always be read-only, because Rails cannot save changes to those attributes.

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.

4.4.1.2 collection(force_reload = false)

The collection method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.

@assemblies = @part.assemblies

4.4.1.3 collection<<(object,)

The collection<< method adds one or more objects to the collection by creating records in the join table.

@part.assemblies << @assembly1

This method is aliased as collection.concat and collection.push.

4.4.1.4 collection.delete(object,)

The collection.delete method removes one or more objects from the collection by deleting records in the join table. This does not destroy the objects.

@part.assemblies.delete(@assembly1)

4.4.1.5 collection=objects

The collection= method makes the collection contain only the supplied objects, by adding and deleting as appropriate.

4.4.1.6 collection_singular_ids

The collection_singular_ids method returns an array of the ids of the objects in the collection.

@assembly_ids = @part.assembly_ids

4.4.1.7 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.

4.4.1.8 collection.clear

The collection.clear method removes every object from the collection by deleting the rows from the joining table. This does not destroy the associated objects.

4.4.1.9 collection.empty?

The collection.empty? method returns true if the collection does not contain any associated objects.

<% if @part.assemblies.empty? %>

This part is not used in any assemblies

<% end %>

4.4.1.10 collection.size

The collection.size method returns the number of objects in the collection.

@assembly_count = @part.assemblies.size

4.4.1.11 collection.find()

The collection.find method finds objects within the collection. It uses the same syntax and options as ActiveRecord::Base.find. It also adds the additional condition that the object must be in the collection.

@new_assemblies = @part.assemblies.all(

:conditions => ["created_at > ?", 2.days.ago])

Starting Rails 3, supplying options to ActiveRecord::Base.find method is discouraged. Use collection.where instead when you need to pass conditions.

4.4.1.12 collection.where()

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. It also adds the additional condition that the object must be in the collection.

@new_assemblies = @part.assemblies.where(“created_at > ?”, 2.days.ago)

4.4.1.13 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?.

4.4.1.14 collection.build(attributes = {})

The collection.build method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through the join table will be created, but the associated object will not yet be saved.

@assembly = @part.assemblies.build(

{:assembly_name => “Transmission housing”})

4.4.1.15 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 the join table will be created, and the associated object will be saved (assuming that it passes any validations).

@assembly = @part.assemblies.create(

{:assembly_name => “Transmission housing”})

4.4.2 Options for has_and_belongs_to_many

In many situations, you can use the default behavior for has_and_belongs_to_many without any customization. But you can alter that behavior in a number of ways. This section covers the options that you can pass when you create a has_and_belongs_to_many association. For example, an association with several options might look like this:

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies, :uniq => true,

:read_only => true

end

The has_and_belongs_to_many association supports these options:

  • :association_foreign_key
  • :autosave
  • :class_name
  • :conditions
  • :counter_sql
  • :delete_sql
  • :extend
  • :finder_sql
  • :foreign_key
  • :group
  • :include
  • :insert_sql
  • :join_table
  • :limit
  • :offset
  • :order
  • :readonly
  • :select
  • :uniq
  • :validate
4.4.2.1 :association_foreign_key

By convention, Rails guesses that the column in the join table used to hold the foreign key pointing to the other model is the name of that model with the suffix _id added. The :association_foreign_key option lets you set the name of the foreign key directly:

The :foreign_key and :association_foreign_key options are useful when setting up a many-to-many self-join. For example:

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

4.4.2.2 :autosave

If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.

4.4.2.3 :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 part has many assemblies, but the actual name of the model containing assemblies is Gadget, you’d set things up this way:

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies, :class_name => “Gadget”

end

4.4.2.4 :conditions

The :conditions option lets you specify the conditions that the associated object must meet (in the syntax used by an SQL WHERE clause).

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies,

:conditions => “factory = ‘Seattle'”

end

You can also set conditions via a hash:

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies,

:conditions => { :factory => ‘Seattle’ }

end

If you use a hash-style :conditions option, then record creation via this association will be automatically scoped using the hash. In this case, using @parts.assemblies.create or @parts.assemblies.build will create orders where the factory column has the value “Seattle”.

4.4.2.5 :counter_sql

Normally Rails automatically generates the proper SQL to count the association members. With the :counter_sql option, you can specify a complete SQL statement to count them yourself.

If you specify :finder_sql but not :counter_sql, then the counter SQL will be generated by substituting SELECT COUNT(*) FROM for the SELECT ... FROM clause of your :finder_sql statement.

4.4.2.6 :delete_sql

Normally Rails automatically generates the proper SQL to remove links between the associated classes. With the :delete_sql option, you can specify a complete SQL statement to delete them yourself.

4.4.2.7 :extend

The :extend option specifies a named module to extend the association proxy. Association extensions are discussed in detail laterinthisguide.

4.4.2.8 :finder_sql

Normally Rails automatically generates the proper SQL to fetch the association members. With the :finder_sql option, you can specify a complete SQL statement to fetch them yourself. If fetching objects requires complex multi-table SQL, this may be necessary.

4.4.2.9 :foreign_key

By convention, Rails guesses 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:

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

4.4.2.10 :group

The :group option supplies an attribute name to group the result set by, using a GROUP BY clause in the finder SQL.

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies, :group => “factory”

end

4.4.2.11 :include

You can use the :include option to specify second-order associations that should be eager-loaded when this association is used.

4.4.2.12 :insert_sql

Normally Rails automatically generates the proper SQL to create links between the associated classes. With the :insert_sql option, you can specify a complete SQL statement to insert them yourself.

4.4.2.13 :join_table

If the default name of the join table, based on lexical ordering, is not what you want, you can use the :join_table option to override the default.

4.4.2.14 :limit

The :limit option lets you restrict the total number of objects that will be fetched through an association.

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies, :order => “created_at DESC”,

:limit => 50

end

4.4.2.15 :offset

The :offset option lets you specify the starting offset for fetching objects via an association. For example, if you set :offset => 11, it will skip the first 11 records.

4.4.2.16 :order

The :order option dictates the order in which associated objects will be received (in the syntax used by an SQL ORDER BY clause).

class Parts < ActiveRecord::Base

has_and_belongs_to_many :assemblies, :order => “assembly_name ASC”

end

4.4.2.17 :readonly

If you set the :readonly option to true, then the associated objects will be read-only when retrieved via the association.

4.4.2.18 :select

The :select option lets you override the SQL SELECT clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns.

4.4.2.19 :uniq

Specify the :uniq => true option to remove duplicates from the collection.

4.4.2.20 :validate

If you set the :validate option to false, then associated objects will not be validated whenever you save this object. By default, this is true: associated objects will be validated when this object is saved.

4.4.3 When are Objects Saved?

When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.

If any of these saves fails due to validation errors, then the assignment statement returns false and the assignment itself is cancelled.

If the parent object (the one declaring the has_and_belongs_to_many association) is unsaved (that is, new_record? returns true) then the child objects are not saved when they are added. All unsaved members of the association will automatically be saved when the parent is saved.

If you want to assign an object to a has_and_belongs_to_many association without saving the object, use the collection.build method.

4.5 Association Callbacks

Normal callbacks hook into the life cycle of Active Record objects, allowing you to work with those objects at various points. For example, you can use a :before_save callback to cause something to happen just before an object is saved.

Association callbacks are similar to normal callbacks, but they are triggered by events in the life cycle of a collection. There are four available association callbacks:

  • before_add
  • after_add
  • before_remove
  • after_remove

You define association callbacks by adding options to the association declaration. For example:

class Customer < ActiveRecord::Base

has_many :orders, :before_add => :check_credit_limit

 

def check_credit_limit(order)

end

end

You can stack callbacks on a single event by passing them as an array:

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

If a before_add callback throws an exception, the object does not get added to the collection. Similarly, if a before_remove callback throws an exception, the object does not get removed from the collection.

4.6 Association Extensions

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:

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

If you have an extension that should be shared by many associations, you can use a named extension module. For example:

module FindRecentExtension

def find_recent

where(“created_at > ?”, 5.days.ago)

end

end

 

class Customer < ActiveRecord::Base

has_many :orders, :extend => FindRecentExtension

end

 

class Supplier < ActiveRecord::Base

has_many :deliveries, :extend => FindRecentExtension

end

To include more than one extension module in a single association, specify an array of modules:

class Customer < ActiveRecord::Base

has_many :orders,

:extend => [FindRecentExtension, FindActiveExtension]

end

Extensions can refer to the internals of the association proxy using these three accessors访问器:

  • proxy_owner returns the object that the association is a part of.
  • proxy_reflection returns the reflection object that describes the association.
  • proxy_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.

 

Now,supposewewantedtoaddaneworderforanexistingcustomer.We’dneedtodosomethinglikethis:现在假设我们想为一个存在的customer添加一个新的order。我们需要做如下事情:

标签: guide rails ruby
Posted in Uncategorized

Active Record Validations and Callbacks 活动记录验证和回调

Active Record Validations and Callbacks 活动记录验证和回调

This guide teaches you how to hook勾子into the life cycle of your Active Record objects.这个教程指导你怎样挂接到你的Active Record objects的生存周期。You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.你将会学习到在将数据对象存入数据库之前怎样验证它们的状态,以及在对象生存周期的一些点上怎样执行定制操作。

After reading this guide and trying out the presented concepts, we hope that you’ll be able to:在阅读了这个教程以及尝试介绍的概念,我们希望你能够:

  • Understand the life cycle of Active Record objects 理解 Active Record对象的生存周期
  • Use the built-in Active Record validation helpers 使用内建的 Active Record验证helpers
  • Create your own custom validation methods 创建属于你的定制验证方法
  • Work with the error messages generated by the validation process 在验证过程中使用错误消息创建器工作
  • Create callback methods that respond to events in the object life cycle 新建一个回调方法响应对象生存周期的事件
  • Create special classes that encapsulate封装common behavior for your callbacks 创建特殊的类来封装你回调的通常习惯(方法)
  • Create Observers观察员that respond to life cycle events outside of the original class 创建 Observers来响应原类以外的生存周期事件

1 The Object Life Cycle

During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this object life cycle so that you can control your application and its data.一个Rails应用程序的正常操作期间,对象可能被新建,更新,和销毁。Active Record提供挂载到这个对象生存周期,以便你可以控制你的应用程序和数据。

Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger触发logic before or after an alteration改造变动of an object’s state.验证允许你确保只有验证数据被存储到你的数据库。回调和观察员(监视器)允许你在变动一个对象的状态之前或之后触发逻辑。

2 Validations Overview验证概述

Before you dive into the detail of validations in Rails, you should understand a bit about how validations fit into the big picture.在你深入Rails 验证的详细说明之前,你应该明白一点就是关于怎样让验证适合于(Rails)这幅大画卷。

2.1 Why Use Validations?

Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address.验证用于确保只有通过验证的数据被保存入你的数据库。例如,在你的应用程序中确认每个用户提供了一个有效的Email地址和邮寄地址是非常重要的。

There are several ways to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations.在数据被存储到你的数据库中之前这里有几种方法来验证它,包括本地数据库约束,客户端验证,控制层级别的验证和模型层级别的验证。

Database constraints and/or stored procedures make the validation mechanisms database-dependent and can make testing and maintenance more difficult. However, if your database is used by other applications, it may be a good idea to use some constraints at the database level. Additionally, database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that can be difficult to implement otherwise.

  • Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user’s browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. 客户端验证是很有用的,但是一般情况下单独使用是靠不住的。如果他们使用JavaScript来实施(验证),它们可能被绕过如果用户的浏览器中JavaScript被关闭的话。然而,如果联合其他的技术,客户端验证是一种方便的方法来提供用户即使反馈(验证信息)如果他们使用你的的站点。
  • Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain. Whenever possible, it’s a good idea to keepyourcontrollersskinny, as it will make your application a pleasure to work with in the long run. 控制层的验证的使用是诱人的,但是通常变得笨重和难以测试和维护。只要有可能,它是一个保持你的控制层苗条好主意,它也使得你的应用程序愉快的长时间工作。
  • Model-level validations are the best way to ensure that only valid data is saved into your database. They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Rails makes them easy to use, provides built-in helpers for common needs, and allows you to create your own validation methods as well. 模板层的验证是最好的方式来确保只有有效的数据被保存进了你的数据库。它们与数据库无关,不能被终端用户绕过,并且方便测试和维护。Rails通过提供(基于)常规需要的内建的helpers,使得它们容易使用,并且同样允许你新建属于你的验证方法。

2.2 When Does Validation Happen?验证在什么时候发生?

There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the new method, that object does not belong to the database yet. Once you call save uponthat object it will be saved into the appropriate database table. Active Record uses the new_record? instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class:这里有两种Active Record对象:一些对应于你数据库中的一行以及一些不是。当你创建一个新鲜的对象,例如使用新的方法,这个对象还不属于数据库。一旦你调用save(函数)之后这个对象将会被适当的保存入数据表单。Active Record使用new_record?实例方法来决定一个对象是否已经被保存进了数据库。思考下面简单的Active Record类:

class Person < ActiveRecord::Base

end

We can see how it works by looking at some rails console output:我们通过观察一些rails的控制台输可以明白它是怎么工作的:

$rails console

>> p = Person.new(:name => "John Doe")

=> #<Person id: nil, name: "John Doe", created_at: nil, :updated_at: nil>

>> p.new_record?

=> true

>> p.save

=> true

>> p.new_record?

=> false

Creating and saving a new record will send an SQL INSERT operation to the database. Updating an existing record will send an SQL UPDATE operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the INSERT or UPDATE operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.创建和保存一个新的记录将会发送一个SQL INSERT操作到数据库。更新一个存在的记录将会发送一个SQL UPDATE操作。验证通常在这些命令被发送到数据库之前运行。如果任何验证失败,这个对象将会标记为非法并且Active Record将不会执行INSERTUPDATE操作。这有助于避免存储一个非法的数据到数据库。你可以在对象被创建,保存或更新的时候选择指定的验证执行。

There are many ways to change the state of an object in the database. Some methods will trigger触发validations, but some will not. This means that it’s possible to save an object in the database in an invalid state if you aren’t careful.这里有很多方法来改变对象在数据库中的状态。一些方法将会触发验证,但是一些却不会。这里的意思是如果你不仔细的话就可能以一种非法的状态保存一个对象到数据库。

The following methods trigger validations, and will save the object to the database only if the object is valid:下面的方法触发验证,并且如果对象是合法的话将会保存对象到数据库:

  • create
  • create!
  • save
  • save!
  • update
  • update_attributes
  • update_attributes!

The bang versions (e.g. save!) raise an exception if the record is invalid. The non-bang versions don’t: save and update_attributes return false, create and update just return the objects.有感叹号的形式(例如save!)在记录是非法的时候会唤起一个异常。没有感叹号形式的就不会:saveupdate_attributes返回false,createupdate只是返回这个对象

2.3 Skipping Validations忽略验证

The following methods skip validations, and will save the object to the database regardless无论of its validity. They should be used with caution.下面的方法会略过验证,并且将会保存对象到数据库而无论它的有效性。他们应该慎重使用。

  • decrement! 递减
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch 切换
  • update_all
  • update_attribute
  • update_column
  • 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.注意save也可以略过演奏如果通过使用:validate => false作为参数。这个技术也应该慎重使用。

  • save(:validate => false)

2.4 valid? and invalid?

  1. 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 and returns true if no errors were added to the object, and false otherwise.验证一个对象是否有效,Rails使用valid?方法。你也可以使用属于你的方法。valid?触发你的验证并且返回True如果没有错误被添加到对象,否则就返回false

class Person < ActiveRecord::Base

validates :name, :presence => true

end

 

Person.create(:name => "John Doe").valid? # => true

Person.create(:name => nil).valid? # => false

 

When Active Record is performing validations, any errors found can be accessed through the errors instance method. By definition an object is valid if this collection is empty after running validations.Active Record在执行验证的时候,任何发现的错误都可以通过errors实例方法来访问。通过定义一个对象是有效的如果这个集合在运行验证过后是空的。

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.注意当实例化一个新的对象的时候将不会报告错误即使假设它的验证技术(得出数据)是非有效的,因为在使用new的时候验证是没有运行的。

class Person < ActiveRecord::Base

validates :name, :presence => true

end

 

>> p = Person.new

=> #<Person id: nil, name: nil>

>> p.errors

=> {}

 

>> p.valid?

=> false

>> p.errors

=> {:name=>["can't be blank"]}

 

>> p = Person.create

=> #<Person id: nil, name: nil>

>> p.errors

=> {: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? is simply the inverse of valid?. invalid? triggers your validations and returns true if any errors were added to the object, and false otherwise. invalid?valid?简单的逆。invalid?触发你的验证如果有任何错误添加到对象中则返回true,否则返回false

2.5 errors[]

To verify whether or not a particular attribute of an object is valid, you can use errors[:attribute]. It returns an array of all the errors for :attribute. If there are no errors on the specified attribute, an empty array is returned.验证一个对象具体的属性是否有效,你可以使用errors[:attribute]。它返回有关:attribute所有错误的数组。如果指定的属性没有错误,将会返回一个空数组。

This method is only useful after validations have been run, because it only inspects the errors collection and does not trigger validations itself. It’s different from the ActiveRecord::Base#invalid? method explained above because 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.这个方法只有在验证被执行过后才有用,因为它仅仅检查错误集合却不会自己触发验证。它和ActiveRecord::Base#invalid?方法上面解释的不同因为它不会整个验证对象的有效性。它仅仅检查这个对象的个别属性是否有错误被找到。

class Person < ActiveRecord::Base

validates :name, :presence => true

end

 

>> Person.new.errors[:name].any? # => false

>> Person.create.errors[:name].any? # => true

We’ll cover validation errors in greater depth in the WorkingwithValidationErrors section. For now, let’s turn to the built-in validation helpers that Rails provides by default.我们将会非常深入的涵盖验证错误在WorkingwithValidationErrors。现在,让我们转入Rails默认提供的内建的验证helpers

 

3 Validation Helpers

Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers provide common validation rules. Every time a validation fails, an error message is added to the object’s errors collection, and this message is associated with the field being validated.Active Record提供许多预定义的你可以插入你的类中直接使用的验证helpers。这些helpers提供常规验证规则。每次验证失败,一个错误消息会被添加到对象的errors集合中,并且这些消息和被验证的field相关。

Each helper accepts an arbitrary number of attribute names, so with a single line of code you can add the same kind of validation to several attributes.每个helper接受一个任意数目的属性名字,因此使用一行代码你可以给一些属性添加相同类型验证。

All of them accept the :on and :message options, which define when the validation should be run and what message should be added to the errors collection if it fails, respectively个别的. The :on option takes one of the values :save (the default), :create or :update. There is a default error message for each one of the validation helpers. These messages are used when the :message option isn’t specified. Let’s take a look at each one of the available helpers.所有的(helpers)接受:on:message选项,用来对个别的(helpers)定义什么时候运行验证以及如果验证失败什么消息被添加到errors集合。:on选项获取:save (默认的), :create:update的值。这里每个验证helpers是默认错误消息。这些消息在:message选项没有被指定时使用。下面我们来看看每个可用的helpers

3.1 acceptance接受承认

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).这个是很特殊对于web应用程序的验证并且这个acceptance’ 不需要在你的数据库中的任何地方记录(如果没有一个它的fieldhelper将会仅仅创建一个虚拟属性)。

class Person < ActiveRecord::Base

validates :terms_of_service, :acceptance => true

end

The default error message for this helper is must be accepted.

It can receive an :accept option, which determines the value that will be considered acceptance. It defaults to1and can be easily changed.它可以接收一个:accept选项,用来决定哪个值认为是接受。默认是1并且可以很容易更改成其他的。

class Person < ActiveRecord::Base

validates :terms_of_service, :acceptance => { :accept => 'yes' }

end

 

3.2 validates_associated

You should use this helper when your model has associations with other models 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.

class Library < ActiveRecord::Base

has_many :books

validates_associated :books

end

This validation will work with all of the association types.

Dontusevalidates_associatedonbothendsofyourassociations.Theywouldcalleachotherinaninfinite无限loop.

The default error message for validates_associated is “is invalid”. Note that each associated object will contain its own errors collection; errors do not bubble up冒泡to the calling model.

 

3.3 confirmation确认

You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address 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.

你应该使用这个helper当你有两个内容完全相同的文本框需要回收。(验证输入)

class Person < ActiveRecord::Base

validates :email, :confirmation => true

end

In your view template you could use something like

<%= text_field :person, :email %>

<%= text_field :person, :email_confirmation %>

 

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):

class Person < ActiveRecord::Base

validates :email, :confirmation => true

validates :email_confirmation, :presence => true

end

The default error message for this helper is “doesnt match confirmation”.

3.4 exclusion排除

This helper validates that the attributes’ values are not included in a given set. In fact, this set can be any enumerable列举object.

class Account < ActiveRecord::Base

validates :subdomain, :exclusion => { :in => %w(www us ca jp),

:message => "Subdomain %{value} is reserved." }

end

The exclusion helper has an option :in that receives the set of values that will not be accepted for the validated attributes. The :in option has an alias called :within that you can use for the same purpose目的, if you’d like to. This example uses the :message option to show how you can include the attribute’s value.

The default error message is “is reserved”.

3.5 format格式(匹配)

This helper validates the attributes’ values by testing whether they match a given regular expression正则表达式, which is specified using the :with option.

class Product < ActiveRecord::Base

validates :legacy_code, :format => { :with => /A[a-zA-Z]+z/,

:message => "Only letters allowed" }

end

The default error message is “is invalid”.

3.6 inclusion列入

This helper validates that the attributes’ values are included in a given set. In fact, this set can be any enumerable object.

class Coffee < ActiveRecord::Base

validates :size, :inclusion => { :in => %w(small medium large),

:message => "%{value} is not a valid size" }

end

The inclusion helper has an option :in that receives the set of values that will be accepted. The :in option has an alias called :within that you can use for the same purpose, if you’d like to. The previous example uses the :message option to show how you can include the attribute’s value.

The default error message for this helper is “is not included in the list”.

3.7 length

This helper validates the length of the attributes’ values. It provides a variety of options, so you can specify length constraints in different ways:

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

The possible length constraint约束options are:

  • :minimum – The attribute cannot have less than the specified length.
  • :maximum – The attribute cannot have more than the specified length.
  • :in (or :within) – The attribute length must be included in a given interval. The value for this option must be a range.
  • :is – The attribute length must be equal to the given value.

The default error messages depend on the type of length validation being performed. Youcanpersonalizethesemessagesusingthe:wrong_length,:too_long,and:too_shortoptionsand%{count}asaplaceholderforthenumbercorrespondingtothelengthconstraintbeingused.You can still use the :message option to specify an error message.

class Person < ActiveRecord::Base

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:

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

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 validates_presence_of instead. When :in or :within have a lower limit of 1, you should either provide a personalized message or call presence priorto length.(当长度比1还小就为空了应该使用validates_presence_of来验证

The size helper is an alias for length.

3.8 numericality只能输入数字

This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by an integral or floating point number. To specify that only integral numbers are allowed set :only_integer to true.

If you set :only_integer to true, then it will use the

/A[+-]?d+Z/

regular expression to validate the attribute’s value. Otherwise, it will try to convert the value to a number using Float.

Notethattheregularexpressionaboveallowsatrailingnewlinecharacter.请注意,上面的正则表达式允许一个尾随的换行符

class Player < ActiveRecord::Base

validates :points, :numericality => true

validates :games_played, :numericality => { :only_integer => true }

end

Besides :only_integer, this helper also accepts the following options to add constraints to acceptable values:

  • :greater_than – Specifies the value must be greater than the supplied value. The default error message for this option is “must be greater than %{count}”. 大于
  • :greater_than_or_equal_to – Specifies the value must be greater than or equal to the supplied value. The default error message for this option is “must be greater than or equal to %{count}”.
  • :equal_to – Specifies the value must be equal to the supplied value. The 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}”.
  • :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 default error message for this option is “must be even”. 偶数

The default error message is “is not a number”.

3.9 presence存在

This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.

class Person < ActiveRecord::Base

validates :name, :login, :email, :presence => true

end

If you want to be sure that an association is present, you’ll need to test whether the foreign key used to map the association is present, and not the associated object itself.

class LineItem < ActiveRecord::Base

belongs_to :order

validates :order_id, :presence => true

end

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] }.

The default error message is “cant be empty”.

3.10 uniqueness独特性

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 in your database.

class Account < ActiveRecord::Base

validates :email, :uniqueness => true

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 are used to limit the uniqueness check:

class Holiday < ActiveRecord::Base

validates :name, :uniqueness => { :scope => :year,

:message => "should happen once per year" }

end

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.

class Person < ActiveRecord::Base

validates :name, :uniqueness => { :case_sensitive => false }

end

Note that some databases are configured to perform case-insensitive searches anyway.需要注意的是一些数据库配置为执行区分大小写的搜索。

The default error message is “has already been taken”.

3.11 validates_with通过指定类验证

This helper passes the record to a separate class for validation.

class Person < ActiveRecord::Base

validates_with GoodnessValidator

end

 

class GoodnessValidator < ActiveModel::Validator

def validate(record)

if record.first_name == “Evil”

record.errors[:base] << “This person is evil”

end

end

end

The validates_with helper takes a class, or a list of classes to use for validation. There is no default error message for validates_with. You must manually add errors to the record’s errors collection in the validator class.

To implement the validate method, you must have a record parameter defined, which is the record to be validated.

Like all other validations, validates_with takes the :if, :unless and :on options. If you pass any other options, it will send those options to the validator class as options:

class Person < ActiveRecord::Base

validates_with GoodnessValidator, :fields => [:first_name, :last_name]

end

 

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

3.12 validates_each

This helper validates attributes against a block. It doesn’t have a predefined validation function. You should create one using a block, and every attribute 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.

class Person < ActiveRecord::Base

validates_each :name, :surname do |model, attr, value|

model.errors.add(attr, ‘must start with upper case’) if value =~ /A[a-z]/

end

end

The block receives the model, the attribute’s name and the attribute’s value. You can do anything you like to check for valid data within the block. If your validation fails, you can add an error message to the model, thereforemaking it invalid.

4 Common Validation Options常规验证选项

These are common validation options:

4.1 :allow_nil

The :allow_nil option skips the validation when the value being validated is nil.

class Coffee < ActiveRecord::Base

validates :size, :inclusion => { :in => %w(small medium large),

:message => “%{value} is not a valid size” }, :allow_nil => true

end

:allow_nilisignoredbythepresencevalidator.

4.2 :allow_blank

The :allow_blank option is similar to the :allow_nil option. This option will let validation pass if the attribute’s value is blank?, like nil or an empty string for example.

class Topic < ActiveRecord::Base

validates :title, :length => { :is => 5 }, :allow_blank => true

end

 

Topic.create(“title” => “”).valid? # => true

Topic.create(“title” => nil).valid? # => true

:allow_blankisignoredbythepresencevalidator.

4.3 :message

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.

4.4 :on

The :on option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be run on save (both when you’re creating a new record and when you’re updating it). If you want to change it, you can use :on => :create to run the validation only when a new record is created or :on => :update to run the validation only when a record is updated.:on 选项让你指定什么时候产生验证。

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, :on => :save

end

5 Conditional Validation有附加条件的验证

Sometimes it will make sense感觉to validate an object just when a given predicate is satisfied. You can do that by using the :if and :unless options, which can take a symbol, a string or a Proc. You may use the :if option when you want to specify when the validation should happen. If you want to specify when the validation should not happen, then you may use the :unless option.

有时候验证一个对象的时候如果给你一个判定性的(语句)你会感觉到满足。你可以使用:if:unless选项达到这样的效果,它可以(判断)一个符号(标记),字符串或者是Proc

 

Proc Ruby block的面向对象的封装。

 

5.1 Using a Symbol with :if and :unless

You can associate the :if and :unless options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.

class Order < ActiveRecord::Base

validates :card_number, :presence => true, :if => :paid_with_card?

 

def paid_with_card?

payment_type == “card”

end

end

5.2 Using a String with :if and :unless

YoucanalsouseastringthatwillbeevaluatedusingevalandneedstocontainvalidRubycode.You should use this option only when the string represents a really short condition.

class Person < ActiveRecord::Base

validates :surname, :presence => true, :if => “name.nil?”

end

5.3 Using a Proc with :if and :unless

Finally, it’s possible to associate :if and :unless with a Proc object which will be called. Using a Proc object gives you the ability to write an inline condition instead of a separate单独method. This option is best suited for one-liners.

class Account < ActiveRecord::Base

validates :password, :confirmation => true,

:unless => Proc.new { |a| a.password.blank? }

end

5.4 Grouping conditional validations

Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using with_options.

class User < ActiveRecord::Base

with_options :if => :is_admin? do |admin|

admin.validates :password, :length => { :minimum => 10 }

admin.validates :email, :presence => true

end

end

All validations inside of with_options block will have automatically passed the condition :if => :is_admin?

6 Performing Custom Validations执行定制验证

When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.

6.1 Custom Validators定制验证器

Custom validators are classes that extend ActiveModel::Validator. These classes must implement a 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.

class MyValidator < ActiveModel::Validator

def validate(record)

if 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

The easiest way to add custom validators for validating individual attributes is with the convenient ActiveModel::EachValidator. In this case, the custom validator class must implement实施落实a validate_each method which takes three arguments: record, attribute and value which correspond对应to the instance, the attribute to be validated and the value of the attribute in the passed instance.

最简单的方法添加(需要)验证个别的属性到定制的validatoras是便捷的使用ActiveModel::EachValidator。在这个案例中,定制的validator类必须落实validate_each方法获得三个参数:与实例对应的record, attribute and value,(它们是)接下来的实例的属性和值

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

As shown in the example, you can also combine结合standard validations with your own custom validators.

 

6.2 Custom Methods定制(验证)方法

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 one or more of the validate, validate_on_create or validate_on_update class methods, passing in the symbols符号for the validation methods’ names.

你也可以创建方法验证你models的状态以及在被验证的时候添加消息到errors集合。你必须注册这些方法通过使用一个或多个这些方法:validate, validate_on_create or validate_on_update类方法加上验证方法的名字。

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.你可以添加一个或多个符号给(对应的)每一个类方法并且各自的验证将会按照与注册相同的顺序运行。

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.blank? and 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

Youcanevencreateyourownvalidationhelpersandreusetheminseveraldifferentmodels. For example, an application that manages surveys调查may find it useful to express that a certain某些field corresponds对应to a set of choices:

ActiveRecord::Base.class_eval do

def self.validates_as_choice(attr_name, n, options={})

validates attr_name, :inclusion => { {:in => 1..n}.merge(options) }

end

end

Simply reopen ActiveRecord::Base and define a class method like that. You’d typically put this code somewhere in config/initializers. You can use this helper like this:

class Movie < ActiveRecord::Base

validates_as_choice :rating, 5

end

7 Working with Validation Errors工作与Validation Errors

In addition to除了the valid? and invalid? methods covered earlier, Rails provides a number of methods for working with the errors collection and inquiring疑问about the validity of objects.

Thefollowingisalistofthemostcommonlyusedmethods.PleaserefertotheActiveRecord::Errorsdocumentationforalistofalltheavailablemethods.

7.1 errors

Returns an OrderedHash with all errors. Each key is the attribute name and the value is an array of strings with all errors.

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 = Person.new(:name => “John Doe”)

person.valid? # => true

person.errors # => []

7.2 errors[]

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.

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)"]

7.3 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. #这里只是添加的消息并没有验证

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 !@#%*()_-+="]

 

Another way to do this is using []= setter

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 !@#%*()_-+="]

7.4 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 the array and uses it as the error message.

class Person < ActiveRecord::Base

def a_method_used_for_validation_purposes

errors[:base] << “This person is invalid because …”

end

end

7.5 errors.clear

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 wont 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. Ifanyofthevalidationsfail,theerrorscollectionwillbefilledagain.

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)"]

7.6 errors.size

The size method returns the total number of error messages for the object.

class Person < ActiveRecord::Base

validates :name, :presence => true, :length => { :minimum => 3 }

end

 

person = Person.new

person.valid? # => false

person.errors.size # => 3

 

person = Person.new(:name => “Andrea”, :email => “andrea@example.com”)

person.valid? # => true

person.errors.size # => 0

8 Displaying Validation Errors in the View在视图中显示验证错误(信息)

Rails maintains an official plugin that provides helpers to display the error messages of your models in your view templates. You can install it as a plugin or as a Gem.

8.1 Installing as a plugin

$ rails plugin install git://github.com/joelmoss/dynamic_form.git

8.2 Installing as a Gem

Add this line in your Gemfile:

gem “dynamic_form”

Now you will have access to these two methods in your view templates

8.3 error_messages and error_messages_for

When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance.

class Product < ActiveRecord::Base

validates :description, :value, :presence => true

validates :value, :numericality => true, :allow_nil => true

end

<%= form_for(@product) do |f| %>

<%= f.error_messages %>

<p>

<%= f.label :description %><br />

<%= f.text_field :description %>

</p>

<p>

<%= f.label :value %><br />

<%= f.text_field :value %>

</p>

<p>

<%= f.submit “Create” %>

</p>

<% end %>

To get the idea, if you submit the form with empty fields you typically get this back, though styles are indeed missing by default:

You can also use the error_messages_for helper to display the error messages of a model assigned to a view template. It’s very similar to the previous example and will achieve exactly the same result.

<%= error_messages_for :product %>

The displayed text for each error message will always be formed by the capitalized name of the attribute that holds the error, followed by the error message itself.大写错误的属性名,后跟错误消息。

<%= f.error_messages :header_message => “Invalid product!”,

:message => “You’ll need to fix the following fields:”,

:header_tag => :h3 %>

Which results in the following content:

If you pass nil to any of these options, it will get rid of the respective section of the div.

8.4 Customizing the Error Messages CSS定制错误消息的CSS样式

The selectors to customize the style of error messages are:定制错误消息的选择器是:

  • field_with_errorsStyle for the form fields and labels with errors. Errorsform fieldslabels的样式
  • #errorExplanation – Style for the div element with the error messages. error消息的div元素样式
  • #errorExplanation h2 – Style for the header of the div element. div元素包含的标题样式
  • #errorExplanation p – Style for the paragraph that holds the message that appears right below the header of the div element.div元素中出现在标题下方段落的样式
  • #errorExplanation ul li – Style for the list items with individual error messages. 错误消息中个别的项目列表样式

Scaffolding脚手架for example generates app/assets/stylesheets/scaffold.css.scss, which later compiles to app/assets/stylesheets/scaffold.css and defines the red-based style you saw above.

The name of the class and the id can be changed with the :class and :id options, accepted by both helpers.

Scaffolding — 基架

  基于数据库架构生成网页模板的过程。在ASP .NET 中,动态数据使用基架来简化基于Web UI 的生成过程。用户可以通过这种UI 来查看和更新数据库。

8.5 Customizing the Error Messages HTML

By default, form fields with errors are displayed enclosed by a div element with the field_with_errors CSS class. However, it’s possible to override that.

The way form fields with errors are treated is defined by ActionView::Base.field_error_proc. This is a Proc that receives two parameters:

  • A string with the HTML tag
  • An instance of ActionView::Helpers::InstanceTag.

Here is a simple example where we change the Rails behavior to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a span element with a validation-error CSS class. There will be no div element enclosing the input element, so we get rid of that red border around the text field. You can use the validation-error CSS class to style it anyway you want.

ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|

if instance.error_message.kind_of?(Array)

%(#{html_tag}<span class=”validation-error”>&nbsp;

#{instance.error_message.join(‘,’)}</span>).html_safe

else

%(#{html_tag}<span class=”validation-error”>&nbsp;

#{instance.error_message}</span>).html_safe

end

end

This will result in something like the following:

9 Callbacks Overview回调概述

Callbacks are methods that get called at certain moments of an object’s life cycle. With callbacks it’s possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.

回调是一种在对象生存周期中的某些情况中(使得对象)被调用的方法。通过callbacks可以运行编写的代码无论Active Record对象是在数据库被创建,保存,删除,验证或者是导入。

9.1 Callback Registration

In order to use the available callbacks, you need to register them. You can do that by implementing实施them as ordinary一般methods, and then using a macro-style class method to register them as callbacks.

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

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 just one line.

class User < ActiveRecord::Base

validates :login, :email, :presence => true

 

before_create do |user|

user.name = user.login.capitalize if user.name.blank?

end

end

It’s considered考虑good practice to declare callback methods as being protected or private. If left public, they can be called from outside of the model and violate违反the principle of object encapsulation封装.(基于)周密的考虑声明callback方法为protected或者private。如果是public,他们可以被model外调用这样违反了封装对象的原则。

10 Available Callbacks

Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations:

10.1 Creating an Object

  • before_validation
  • after_validation
  • before_save
  • before_create
  • around_create
  • after_create
  • after_save

10.2 Updating an Object

  • before_validation
  • after_validation
  • before_save
  • before_update
  • around_update
  • after_update
  • after_save

10.3 Destroying an Object

  • before_destroy
  • after_destroy
  • around_destroy

after_save runs both on create and update, but always after the more specific callbacks after_create and after_update, no matter the order in which the macro calls were executed.after_save总是在after_create and after_update的后面不管他们的微调用如何。

10.4 after_initialize and after_find

The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override覆盖your Active Record initialize method.

 

The after_find callback will be called whenever Active Record loads a record from the database. after_find is called before after_initialize if both are defined.

 

 

The after_initialize and after_find callbacks have no before_* counterparts同行, but they can be registered just like the other Active Record callbacks.

 

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 id: nil>

 

>> User.first

You have found an object!

You have initialized an object!

=> #<User id: 1>

11 Running Callbacks

The following methods trigger callbacks:下面的方法触发回调:

  • create
  • create!
  • decrement! 递减
  • destroy
  • destroy_all
  • increment!
  • save
  • save!
  • save(false)
  • toggle!
  • update
  • update_attribute
  • update_attributes
  • update_attributes!
  • valid?

Additionally, the after_find callback is triggered by the following finder methods:此外,after_find会被下面的查找方法触发:

  • all
  • first
  • find
  • find_all_by_attribute
  • find_by_attribute
  • find_by_attribute!
  • last

The after_initialize callback is triggered every time a new object of the class is initialized.当类的一个新的对象被初始化的时候都会触发after_initialize回调。

12 Skipping Callbacks

Just as with validations, it’s also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential潜在implications影响may lead to invalid data.正如验证,它有可能忽略回调。这些方法需要注意使用,然而,由于重要的业务规则和应用程序逻辑可能保持回调。没有明白潜在的影响就绕过它们可能会导致非法数据。

  • decrement
  • decrement_counter
  • delete
  • delete_all
  • find_by_sql
  • increment
  • increment_counter
  • toggle
  • touch
  • update_column
  • update_all
  • update_counters

13 Halting Execution停止执行

As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model’s validations, the registered callbacks, and the database operation to be executed.

The whole callback chainis 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.

Raising an arbitrary exception may break code that expects save and friends not to fail like that.抛出任意异常可能会打断代码预计的save以及朋友们不想要的失败。The ActiveRecord::Rollback exception is thought precisely正是to tell Active Record a rollback is going on. That one is internally captured but not reraised.

14 Relational Callbacks Callbacks相关

Callbacks work through model relationships, and can even be defined by them. Let’s take an example where a user has many posts. In our example, a user’s posts should be destroyed if the user is destroyed. So, we’ll add an after_destroy callback to the User model by way of its relationship to the Post model.

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 id: 1>

>> user.posts.create!

=> #<Post id: 1, user_id: 1>

>> user.destroy

Post destroyed

=> #<User id: 1>

15 Conditional Callbacks有条件的回调

Like in validations, we can also make our callbacks conditional, calling them only when a given predicate is satisfied. You can do that by using the :if and :unless options, which can take a symbol, a string or a Proc. You may use the :if option when you want to specify when the callback should get called. If you want to specify when the callback should not be called, then you may use the :unless option.

15.1 Using :if and :unless with a Symbol

You can associate the :if and :unless options with a symbol corresponding to the name of a method that will get called right before the callback. When using the :if option, the callback won’t be executed if the method returns false; when using the :unless option, the callback won’t be executed if the method returns true. This is the most common option. Using this form of registration it’s also possible to register several different methods that should be called to check if the callback should be executed.

class Order < ActiveRecord::Base

before_save :normalize_card_number, :if => :paid_with_card?

end

15.2 Using :if and :unless with a String

You can also use a string that will be evaluated using eval and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.

class Order < ActiveRecord::Base

before_save :normalize_card_number, :if => “paid_with_card?”

end

15.3 Using :if and :unless with a Proc

Finally, it’s possible to associate :if and :unless with a Proc object. This option is best suited when writing short validation methods, usually one-liners.

class Order < ActiveRecord::Base

before_save :normalize_card_number,

:if => Proc.new { |order| order.paid_with_card? }

end

15.4 Multiple Conditions for Callbacks

When writing conditional callbacks, it’s possible to mix both :if and :unless in the same callback declaration.

class Comment < ActiveRecord::Base

after_create :send_email_to_author, :if => :author_wants_emails?,

:unless => Proc.new { |comment| comment.post.ignore_comments? }

end

16 Callback Classes

Sometimes the callback methods that you’ll write will be useful enough to be reused by other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them.

Heres an example where we create a class with an after_destroy callback for a PictureFile model.

class PictureFileCallbacks

def after_destroy(picture_file)

if File.exists?(picture_file.filepath)

File.delete(picture_file.filepath)

end

end

end

When declared inside a class the callback method will receive the model object as a parameter. We can now use it this way:

class PictureFile < ActiveRecord::Base

after_destroy PictureFileCallbacks.new

end

Note that we needed to instantiate a new PictureFileCallbacks object, since we declared our callback as an instance method. Sometimes it will make more sense to have it as a class method.

class PictureFileCallbacks

def self.after_destroy(picture_file)

if File.exists?(picture_file.filepath)

File.delete(picture_file.filepath)

end

end

end

If the callback method is declared this way, it won’t be necessary to instantiate a PictureFileCallbacks object.

class PictureFile < ActiveRecord::Base

after_destroy PictureFileCallbacks

end

You can declare as many callbacks as you want inside your callback classes.

17 Observers观测者

Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn’t directly related to its purpose, observers allow you to add the same functionality outside of a model. For example, it could be argued that a User model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isnt directly related to your model, you may want to consider creating an observer instead.

Observerscallbacks很相似,但是有着很大不同。鉴于callbacks能够影响model通过代码那与他的目的并没有直接关联,observers允许你在model外添加相同的功能。

17.1 Creating Observers

For example, imagine a User model where we want to send an email every time a new user is created. Because sending emails is not directly related to our model’s purpose, we could create an observer to contain this functionality.

$ rails generate observer User

 

class UserObserver < ActiveRecord::Observer

def after_create(model)

# code to send confirmation email…

end

end

 

As with callback classes, the observer’s methods receive the observed model as a parameter.

17.2 Registering Observers

Observers are conventionally placed inside of your app/models directory and registered in your application’s config/application.rb file. For example, the UserObserver above would be saved as app/models/user_observer.rb and registered in config/application.rb this way:

# Activate observers that should always be running

config.active_record.observers = :user_observer

As usual, settings in config/environments take precedence优先权over those in config/application.rb. So, if you prefer that an observer doesn’t run in all environments, you can simply register it in a specific environment instead.

17.3 Sharing Observers

By default, Rails will simply strip “Observer” from an observer’s name to find the model it should observe. However, observers can also be used to add behavior to more than one model, and so it’s possible to manually specify the models that our observer should observe.

class MailerObserver < ActiveRecord::Observer

observe :registration, :user

 

def after_create(model)

# code to send confirmation email…

end

end

In this example, the after_create method would be called whenever a Registration or User was created. Note that this new MailerObserver would also need to be registered in config/application.rb in order to take effect.

config.active_record.observers = :mailer_observer#与上面的类名相关

18 Transaction Callbacks

There are two additional callbacks that are triggered by the completion of a database transaction: after_commit and after_rollback. These callbacks are very similar to the after_save callback except that they don’t execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction.

Consider, for example, the previous example where the PictureFile model needs to delete a file after a record is destroyed. If anything raises an exception after the after_destroy callback is called and the transaction rolls back, the file will have been deleted and the model will be left in an inconsistent state. For example, suppose that picture_file_2 in the code below is not valid and the save! method raises an error.

PictureFile.transaction do

picture_file_1.destroy

picture_file_2.save!

end

By using the after_commit callback we can account for this case.

class PictureFile < ActiveRecord::Base

attr_accessor :delete_file

 

after_destroy do |picture_file|

picture_file.delete_file = picture_file.filepath

end

 

after_commit do |picture_file|

if picture_file.delete_file && File.exist?(picture_file.delete_file)

File.delete(picture_file.delete_file)

picture_file.delete_file = nil

end

end

end

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.

Posted in Uncategorized

Django 国际化实例及原理分析

当 Web 服务搭建好以后,可以接收来自全球不同国家用户访问。这样就要求开发人员调整软件,使之能适用于不同的语言,即国际化和本地化。国际化 — Internationalization,i 和 n 之间有 18 个字母,简称 I18N,。本地化 — localization, l 和 n 之间有 10 个字母,简称 L10N。国际化意味着 Web 产品有适用于任何地方的潜力,针对程序开发人员;本地化则是指使一个国际化的程序为了在某个特定地区使用而进行实际翻译的过程,针对翻译人员而言。

Django 提供了非常强大的翻译机制,开发者一旦理解它的实现,就能减少编码量,提高开发效率。

本文通过两个 Django 国际化实例循序渐进地介绍在 Django 环境里进行 Web 程序国际化和本地化开发的相关方法和知识。并结合 Django 国际化现有代码进行分析,向读者阐述 Django 国际化的原理与内部实现。学习本文,可以很好的掌握国际化 Django 程序的技术。

当 Web 服务搭建好以后,可以接收来自全球不同国家用户访问。这样就要求开发人员调整软件,使之能适用于不同的语言,即国际化和本地化。国际化 — Internationalization,i 和 n 之间有 18 个字母,简称 I18N,。本地化 — Localization, l 和 n 之间有 10 个字母,简称 L10N。国际化意味着 Web 产品有适用于任何地方的潜力,针对程序开发人员;本地化则是指使一个国际化的程序为了在某个特定地区使用而进行实际翻译的过程,针对翻译人员而言。

Django 提供了非常强大的翻译机制,开发者一旦理解它的实现,就能减少编码量,提高开发效率。

Django 国际化简介

Django 的开发和维护者对 Django 框架本身进行了完全国际化,我们可以在 ./Python2.5/site-packages/django/conf/locale/ 找到相关的语言文件。目前 Django-1.2.1 带着 52 个不同的本地化语言文件发行的,使用户能够方便的使用它现有的管理界面。

Django 国际化的本质就是开发者对需要翻译的字符串进行标记,并对字符串进行相应的翻译。当用户访问该 Web 时,Django 内部框架根据用户使用偏好进行 Web 呈现。

Django 国际化使用的翻译模块是使用 Python 自带的 gettext 标准模块。通过一个到 GNU gettext 消息目录库的接口,这个模块为 Python 程序提供了国际化 (I18N) 和本地化 (L10N)。

开发人员和翻译人员需要完成一下 3 个步骤:

1. 第一步:在 Python 代码和模板中嵌入待翻译的字符串,

2. 第二步:把那些字符串翻译成需要支持的语言,并进行相应的编译

3. 第三步:在 Django settings 文件中激活本地中间件,

下面我们将通过这个几个步骤,介绍两个实例,然后在每个实例后结合 Django 代码来向读者介绍 Django 国际化的原理。本文环境:CentOS release 5.3 (Final),Python 2.5.5 , gettext 版本为 gettext-0.14.6-4.el5,浏览器为 Fixfox 3.6.3., Django-1.2.1。请前往http://www.djangoproject.com/download/下载并安装 Django。

读者最好能边阅读本文,边在计算机上进行操作,达到最佳学习效果。因此,需要读者具备以下技能:

  1. 熟悉基本的 Linux 操作命令
  2. 熟悉 Python 语法,对编写 Django 应用程序所有了解。

本文附带相关源代码,直接下载并解压缩到配置好 Django 的环境中,就可以运行看到效果。

 

 

针对 Python 代码的国际化实例及原理分析

对 Django 中 Python 代码进行国际化主要用到 ugettext(),gettext_noop(),gettext_lazy() 和 ungettext() 等函数。在本 Django 应用程序实例中,主要 Python 代码都集中在 models.py 和 views.py。 我们将 ugettext() 使用对 views.py 中的相关字符串进行实例化。接着,我们将会结合 Django 代码对整个过程的原理实现做详细的介绍。

一个简单的 Python Web 程序

首先在 /home/jerry/ 目录下 , 创建名为 testsite 的 Project,请参考清单 1:
清单 1. 创建一名为 testsite 的 project

 [root@localhost jerry]# django-admin.py startproject testsite
 [root@localhost jerry]# cd testsite/
 [root@localhost testsite]# ls
 __init__.py  manage.py  settings.py  urls.py

 

接着,在 /home/jerry/testsite 目录下创建一个名为 test1 的 Django App, 请参看清单 2。
清单 2. 创建名为 test1 的 Django App

 [root@localhost testsite]# Python manage.py startapp test1
 [root@localhost testsite]# ls
 __init__.py  __init__.pyc  manage.py  settings.py  settings.pyc  test1  urls.py
 [root@localhost testsite]#cd test1

 

接着,在 test1 的 views.py 中添加代码获取今天周期几,具体代码参看清单 3。
清单 3. test1 的 views.py 中的代码

 [root@localhost test1]# vim views.py

 # Create your views here.
 from django.http import HttpResponse
 import time

 def test1_view(request):
 # 获得系统本地时间,返回的格式是 UTC 中的 struct_time 数据
        t  = time.localtime()
 # 第 6 个元素是 tm_wday , 范围为 [0,6], 星期一 is 0
        n  = t[6]
 # 星期一到星期日字符串
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday']
 # 返回一个 HttpResponse、,这段代码将用来返回服务器系统上是星期几。
        return HttpResponse(weekdays[n])

 

然后配置 URL, 在 url.py 增加用户访问 test1_view 的 url, 具体请参看清单 4。
清单 4. test1_view 的 url 配置

 [root@localhost testsite]# vim urls.py
 from testsite.test1 import views

 urlpatterns = patterns('',
    …
    (r'^test1_view$', views.test1_view),
 )

在 testsite 中,setting.py 中默认的语言设置为 en-us:

 LANGUAGE_CODE = 'en-us'

 

在更新 url.py 和 setting.py 后,在 /home/jerry/testsite 目录下启动服务,具体参看清单 5:
清单 5. 启动 testsite 服务

 root@localhost testsite]# Python manage.py runserver 0.0.0.0:8080

 

当启动服务没有错误提示,就可以在在浏览器中输入 Web 服务地址 : http://192.168.174.128:8080/test1_view,就会看到英文星期几,具体见图 1:
图 1. test1 未国际化时的显示结果
图 1. test1 未国际化时的显示结果 

对 Python 代码的国际化和本地化

接下来我们将对 views.py 中的字符串进行国际化和本地化。

指定待翻译的字符串

首先,代码中,使用函数 ugettext() 指定一个待翻译的字符串。另外,为了节约输入时间,代码中使用“from django.utils.translation import ugettext as _”即用短别名 _ 来引入这个函数。具体代码修改如下清单 6:
清单 6. 国际化 test1

 [root@localhost test1]# vim views.py

 # Create your views here.
 from django.http import HttpResponse
 from django.utils.translation import ugettext as _
 import time

 def test1_view(request):
 # 获得系统本地时间,返回的格式是 UTC 中的 struct_time 数据
        t  = time.localtime()
 # 第 6 个元素是 tm_wday , 范围为 [0,6], 星期一 is 0
        n  = t[6]
 # 星期一到星期日字符串,每个字符串用 _() 标识出来。
weekdays = [_('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'),
_('Friday'), _('Saturday'), _('Sunday')]
 # 返回一个 HttpResponse

        return HttpResponse(weekdays[n])

 

创建语言文件

接下来,先在 test1 App 目录下创建 locale 目录,并运行“django-admin.py makemessages -l zh_CN”产生 locale/zh_CN/LC_MESSAGES/django.po 文件 , 具体操作请参看清单 7.
清单 7. 创建语言文件

 [root@localhost test1]# mkdir locale
 [root@localhost test1]# ls
 __init__.py  __init__.pyc  locale  models.py  tests.py  views.py  views.pyc
 [root@localhost test1]# django-admin.py makemessages -l zh_CN

 

打开 locale/zh_CN/LC_MESSAGES/django.po 文件,其主要内容如清单 8:
清单 8. 更新 django.po 文件

 #: views.py:12
 msgid "Monday"
 msgstr "星期一"

 #: views.py:12
 msgid "Tuesday"
 msgstr "星期二"

 #: views.py:12
 msgid "Wednesday"
 msgstr "星期三"

 #: views.py:12
 msgid "Thursday"
 msgstr "星期四"

 #: views.py:12
 msgid "Friday"
 msgstr "星期五"

 #: views.py:12
 msgid "Saturday"
 msgstr "星期六"

 #: views.py:12
 msgid "Sunday"
 msgstr "星期天"

 

这个文件是一个纯文本文件,包含用于翻译的原始字符串和目标语言字符串。

# 为前缀的行起注释作用。msgid 是在源文件中出现的翻译字符串。msgstr 是相应语言的翻译结果。注意语句前后都有引号。 刚创建时 msgstr 是空字符串,需要翻译人员翻译。这个文件,用户可以手动按格式添加一些内容。

编译信息文件

创建信息文件之后,每次对其做了修改,都需要用 django-admin.py compilemessages 编译成“.mo”文件供 gettext 使用,具体操作请参看清单 9。
清单 9. 编译信息文件

 [root@localhost test1]# django-admin.py compilemessages

 

确认相关配置

首先需要确认 testsite 目录下 setting.py 的配置,主要需要核实 LANGUAGE_CODE,USE_I18N 和 MIDDLEWARE_CLASSES。主要配置请参看清单 10:
清单 10. setting.py 中的国际化相关配置

 LANGUAGE_CODE = 'en-us'
 USE_I18N = True
 MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
 )

 

请注意注意 MIDDLEWARE_CLASSES 中的’django.middleware.locale.LocaleMiddleware’, 需要放在’django.contrib.sessions.middleware.SessionMiddleware’ 后面。

Firefox 语言顺序的选择

工具 – 〉选项 – 〉内容,语言栏这一项选着,将汉语 / 中国 [zh-cn] 移到最上面。这样页面将会根据浏览器的配置,优先中文显示,具体请参看图 2。
图 2. Firefox 中文选择
图 2. Firefox 中文选择 

启动 test1 服务

 [root@localhost testsite]# Python manage.py runserver 0.0.0.0:8080

 

察看 test1 国际化效果

在 Firefox 中输入 服务地址 : http://192.168.174.128:8080/test1_view, 就能看到中文星期四几,具体效果请参看图 3。
图 3. test1 国际化效果
图 3. test1 国际化效果 

指定待翻译的字符串另外一种方式

另外,_() 的参数也可以是变量,views.py 中的相关行可修改成如清单 11:
清单 11. _() 的参数为变量

  …
weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
return HttpResponse(_(weekdays[n]))
  … ..

 

此时运行,重新创建语言文件 :

[root@localhost test1]# django-admin.py makemessages -l zh_CN

processing language zh_CN

若之前没有 django.po 文件,此时也不会产生。需要自己将 weekdays 里面的每个元素按格式添加到 locale/zh_CN/LC_MESSAGES/django.po 中,再运行

django-admin.py compilemessages

这样国际化也可以成功。

针对 Python 代码国际化原理分析

通过上面那个实例,可以了解到对 Python 代码国际化的全过程。接下来,我们结合 Django 代码来分析以下两个国际化的关键点的工作流程来理解 Python 代码的国际化的原理 :

1,配置文件 setting.py 中 USE_I18N = True

2, 指定翻译字符串的 4 个主要函数

django.utils.translation.ugettext()

django.utils.translation.gettext_noop()

django.utils.translation.gettext_lazy()

django.utils.translation.ungettext()

4 个主要函数的用途

其中:

django.utils.translation.ugettext()

指定一个翻译字符串,一般都用于 views.py

django.utils.translation.gettext_noop()

标记一个不需要立即翻译的字符串。 这个串会稍后从变量翻译。使用这种方法的环境是,有字符串必须以原始语言的形式存储(如储存在数据库中的字符串)而在最后需要被 翻译出来(如显示给用户时)。

django.utils.translation.gettext_lazy()

ugettext_lazy() 将字符串作为惰性参照存储,而不是实际翻译 , 一般会用于 models.py。 翻译工作将在字符串在字符串上下文中被用到时进行,比如在 Django 管理页面提交模板时。在 Django 模型中总是无一例外的使用惰性翻译。

django.utils.translation.ungettext()

函数包括三个参数: 单数形式的翻译字符串,复数形式的翻译字符串,和对象的个数(将以 count 变量传递给需要翻译的语言)。

主要函数实现原理和 USE_I18N 变量

在 /usr/local/lib/Python2.5/site-packages/django/utils/translation 中存在 __init__.py,trans_null.py 和 trans_real.py 3 个重要的文件。

__init__.py 表示该目录作为 Python 的一个包,在 Python 中可以为空。django.utils.translation 模块中的 __init__.py 包含了包级别的初始化代码。“__init__.py”文件定义了一个名为“__all__”的列表,这个列表就作为从包内导入 * 时要导入的所有模块的名字表。

具体代码如下 :

__all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',

'ngettext_lazy', 'string_concat', 'activate', 'deactivate',

'get_language', 'get_language_bidi', 'get_date_formats',

'get_partial_date_formats', 'check_for_language', 'to_locale',

'get_language_from_request', 'templatize', 'ugettext', 'ugettext_lazy',

'ungettext', 'deactivate_all']

并且在 __init__.py 中定义了我们上面提到的几个函数:

def gettext_noop(message):

return real_gettext_noop(message)

def gettext(message):

return real_gettext(message)

def ugettext(message):

return real_ugettext(message)

def ungettext(singular, plural, number):

return real_ungettext(singular, plural, number)

gettext_lazy = lazy(gettext, str)

注意 : lazy 是一种延迟计算,使用它表示一种对结果的承诺,但只有当真正需要时才会去计算。只要你不是真是需要,你得到的并不是真正的结果。

USE_I18N 变量的如何产生作用的?

/usr/local/lib/Python2.5/site-packages/django/utils/translation/__init__.py 将调用本文件内的 delayed_loader()函数。

在 delayed_loader()函数中会把这些 real_* 函数替换成 trans_real 和 trans_null 中的函数,如 real_gettext 被替换成 trans_real.gettext 或者 trans_null.gettext。如果在项目的根目录 settings.py 中设置了 USE_I18N = True,表示我们在应用程序中采用国际化,则所有的函数被替换成 trans_real.*,否则,不采用国际化,将被替换成 trans_null.*。这个替换做一次,并且一次将所有的函数都替换掉。

trans_null.py 文件:不打算翻译时,为了性能考虑。 settings.USE_I18N = False 时,使用这个模块中函数 , 此时,django.utils.translation.trans_real 中的函数将不作任何事情。

trans_real.py 文件 : settings.USE_I18N = True 时,将会使用这个模块中的函数。我们看看上面几个函数在 trans_real.py 中的实现 :

def gettext(message):

return do_translate(message, ‘gettext’)

def ugettext(message):

return do_translate(message, ‘ugettext’)

def gettext_noop(message):

……

标识这个字符串将要翻译,但现在没有翻译。它可被用于在全局变量中存储基本语言的字符串 ( 因为它们可能在外部被使用 ),稍微翻译。

……

return message

gettext 和 ugettext 都调用了 do_translate()函数。do_translate()函数使用了 Python 的 gettext 模块来处理带翻译的字符串。从而通过 Python 的 gettext 模块来完成国际化和本地化。

 

 

针对 template 代码的国际化实例及原理分析

一个较复杂的 Django 的国际化实例

创建名为 test2 的 Django App

同第一个实例在同一项目,在 testsite 目录里创建名为 test2 的 Django App.

 [root@localhost testsite]# Python manage.py startapp test2

 

创建 test2 的模版文件

在项目(Project)testsite 目录下创建 templates/test2/index.html 文件。具体操作及文件内容请参看清单 12。
清单 12. test2 的模版文件

 [root@localhost testsite]#mkdir – p templates/test2
 [root@localhost testsite]#cd  templates/test2
 [root@localhost test2]#vim index.html

 {% load i18n %}
 <html xmlns="http://www.w3.org/1999/xhtml"
 xml:lang="{{ LANGUAGE_CODE }}" lang="{{ LANGUAGE_CODE }}">

 <head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
    <title>Welcome to my site</title>
         <script  type="text/javascript">
               function selectdo(obj) {
                        str="/i18n/setlang/";
                        myform = document.getElementById('testform');
                        myform.method = "POST";
                        myform.action = str;
                        myform.submit();
                }
        </script>

 </head>
 <body>
        <form name="testform" id="testform" method='post'>
            <select id="language" name="language" onchange="selectdo(this)">
           <!--    <input name="next" type="hidden" value="{{request.path}}" />-->
                <option value="1" >{% trans "Languages" %}</option>
                 {% for lang in LANGUAGES %}
                <option value="{{ lang.0 }}" > {{ lang.1 }}</option>
                 {% endfor %}
              </select>
         </form>
         <p>{% trans "The first sentence is from the  template index.html" %}</p>
        {{ code }}
 </body>
 </html>

 

在代码中已经加入了国际化模版相关代码,下面 4 点需要注意:

1 . `{% load i18n %}`

使模版能够访问到访问到标签。

2 . `{% trans str %}`

标记翻译一个常量字符串或 可变内容

3 . 模版中的 select 的 name 值必须为”language”

4. 将 testform 的 action 重定向到 /i18n/setlang/,启用了 django.views.i18n.set_language 视图,它的作用是设置用户语言偏好并重定向返回到前一页面。

它们的工作原理,将在“针对 template 代码国际化原理分析”进行剖析。

设置模版路径

我们需要在 setting.py 里配置 template 的路径,具体请参看清单 13:
清单 13. 设置模版路径

 [root@localhost testsite] vim settings.py
 TEMPLATE_DIRS = (
      。。。。。。
     '/home/jerry/testsite/templates',
 )

 

请注意 : Template 不能放在 app 目录下,需要放在 project 下面。否则,国际化将会失败,页面仅显示英文。

增加 test2 App 的 views.py 代码

在 /home/jerry/testsite/test2/views.py 增加一下内容,具体请参看清单 14:
清单 14. test2 的 views.py 内容

 # Create your views here.
 from django.http import HttpResponse
 from django.shortcuts import render_to_response
 from django.template import RequestContext
 from django.utils.translation import ugettext_lazy as _

 def test2_view(request):
    code = _("The second sentence is from the Python code.");
    responseContext = {'lang':request.LANGUAGE_CODE,
                        'code':code,
                        }
    resp = render_to_response('test2/index.html', responseContext,
                                context_instance=RequestContext(request))
    return resp

 

更新 URL

修改 /home/jerry/testsite/urls.py,首先,我们需要从 testsite.test2.views 模块中导入所有函数。接着在 urlpatterns 中加入 test2_veiw 和 i18n 的 url,具体请参看清单 15。
清单 15. 更新 URL

 from django.conf.urls.defaults import *
 from testsite.test2.views import *
。。。。。。

 urlpatterns = patterns('',
    。。。。。。
    (r'^test2_view$', test2_view),
    (r'^i18n/', include('django.conf.urls.i18n')),
 )

 

更新 setting

修改 /home/jerry/testsite/settings.py,更新国际化 相关的设置 , 具体请参看清单 16。
清单 16. 更新国际化相关设置

 DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql'
 DATABASE_NAME = 'test' # Or path to database file if using sqlite3.
 DATABASE_USER = 'root' # Not used with sqlite3.
 DATABASE_PASSWORD = '1234'  # Not used with sqlite3.
 DATABASE_HOST = ''  # Set to empty string for localhost. Not used with sqlite3.
 DATABASE_PORT = ''  # Set to empty string for default. Not used with sqlite3.

 USE_I18N = True

 ugettext = lambda s: s

 LANGUAGES = (
    ('en-us', ugettext('English')),
    ('zh-CN', ugettext('Chinese')),
 )

 TEMPLATE_CONTEXT_PROCESSORS = (
 #     "django.core.context_processors.auth",
 #    "django.core.context_processors.debug",

    "django.core.context_processors.i18n",
 #    "django.core.context_processors.request",
 )

 MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
 )

 

这里需要注意,需要用数据库保存 session。在本实例中用到了 mysql test2 数据库中的 django_session 数据表。我们在 /home/jerry/testsite 目录下运行 Python manage.py syncdb 就能产生 django_session 了。具体见清单 17:
清单 17. 产生 django_session 数据表

 [root@localhost testsite]# Python manage.py syncdb

 

创建并更新语言文件

创建 django.po 文件后,需要将其中 msgid 所在行引号内的英语,都翻译中文,写在 msgstr 所在行的引号内。具体请参看清单 18。
清单 18. 创建并更新语言文件

 [root@localhost testsite]# django-admin.py makemessages -l zh_CN
 [root@localhost testsite]# vim locale/zh_CN/LC_MESSAGES/django.po
……
 #: settings.py:41
 msgid "English"
 msgstr "英语"

 #: settings.py:42
 msgid "Chinese"
 msgstr "中文"

 #: templates/test2/index.html:8
 msgid "Welcome to my site"
 msgstr "欢迎访问我的网站"

 #: templates/test2/index.html:24
 msgid "Languages"
 msgstr "语言"

 #: templates/test2/index.html:30
 msgid "The first sentence is from the  template index.html"
 msgstr "第一句话来自 intex.html 模版。"

 #: test2/views.py:8
 msgid "The second sentence is from the Python code."
 msgstr "第二句话来自 Python 代码。"

编译信息文件

 [root@localhost testsite]#  django-admin.py compilemessages

启动服务

 [root@localhost testsite]# Python manage.py runserver 0.0.0.0:8080

 

察看结果

在 firfox 中输入 服务地址 : http://192.168.174.128:8080/test2_view

应为 Firfox 浏览器的配置如图, 优先中文配置。所以,初始显示如图 4:
图 4. test2 中文显示效果
图 4. test2 中文显示效果 

当选择下拉框中的 English 时,页面将会迅速更新为图 5:
图 5. test2 英文显示效果
图 5. test2 英文显示效果 

针对 template 代码国际化原理分析

本小节将结合 Django 代码来分析在 template 代码国际化所用到的关键点的工作原理。

Load i18n 实现原理

模板中的 `{% load %}` 标签用于加载已有的模板。

在 /usr/local/lib/Python2.5/site-packages/django/templatetags/i18n.py 文件中定义了指定模版中翻译字符串的模板标签。在 django 的模板前加入 `{% load i18n %}`,在 i18n.py 源文件中的定义标签就可以在有 load 语句的模板中使用了。

模板标签的实现原理

Django 模板两种常用模板标签

`{% trans %}` 模板标签标记需要翻译的字符串;如果只需要标记字符串而以后再翻译,可以使用 noop 选项。

在 `{% trans %}` 中不允许使用模板中的变量,只能使用单引号或双引号中的字符串。如果翻译时需要用到变量(占位符),可以使用 `{% blocktrans %}`。

如果需要在 blocktrans 标签内绑定多个表达式,可以用 and 来分隔。

为了表示单复数相关的内容,需要在 `{% blocktrans %}` 和 `{% endblocktrans %}` 之间使用 `{% plural %}` 标签来指定单复数形式。

模版标签实现分析

{% trans %}

通过 /usr/local/lib/Python2.5/site-packages/django/templatetags/i18n.py 文件 do_translate()实现的。do_translate()处理了 `{% trans %}` 的三种格式:

 {% trans "this is a test" %}
 {% trans "this is a test" noop %}
 {% trans variable %}

 

do_translate() 调用了 TranslateNode(),最后通过 django.utils.translation.ugettext() 来处理的。具体实现可参看 Django 源代码。

`{% blocktrans %}` 和 `{% endblocktrans %}`

通过 /usr/local/lib/Python2.5/site-packages/django/templatetags/i18n.py 文件 do_block_translate()实现的。

用于翻译带参数的文本块

用法 :

{% blocktrans with foo|filter as bar and baz|filter as boo %}
This is {{ bar }} and {{ boo }}.
{% endblocktrans %}

 

另外,支持复数 ::

{% blocktrans count var|length as count %}
There is {{ count }} object.
{% plural %}
There are {{ count }} objects.
{% endblocktrans %}

 

do_block_translate()条用了 BlockTranslateNode(),其内在机制是,所有的块和内嵌翻译调用 django.utils.translation.ugettext() 和 django.utils.translation.ungettext() 即相应的 gettext 或 ngettext 。

在 i18n.py 中,用 register.tag() 注册 Tag 名字和对应的处理方法 :

register.tag(‘trans’, do_translate)

register.tag(‘blocktrans’, do_block_translate)

这样 i18n.py 中定义标签就可以在 template 中使用了。

激活 set_language 重定向视图的实现原理

django.views.i18n.set_language 视图的主要作用是设置用户语言偏好并重定向返回到前一页面。

对 template 代码国际化时,需要在项目中的 urls.py 添加 url 到 urlpatterns 中 :

 urlpatterns += patterns('',
    (r'^i18n/', include('django.conf.urls.i18n')),
 )

 

为什么呢?

我们继续深入 django.conf.urls.i18n 看看到底做什么处理。

在 /usr/local/lib/Python2.5/site-packages/django/conf/urls/i18n.py 中内容为 :

 from django.conf.urls.defaults import *

 urlpatterns = patterns('',
    (r'^setlang/$', 'django.views.i18n.set_language'),
 )

 

当用户在 url 附加上 /i18n/setlang/,就会重定向到 django.views.i18n.set_language()。

在 /usr/local/lib/Python2.5/site-packages/django/views/ i18n.py 中的 set_language() 函数的实现比较经典,如清单 19,供大家参考。
清单 19. 重定向视图函数 set_language

 def set_language(request):
    """
当在 session 或 cookie 中设置所选择的语言时,会重定向到指定的网址。
URL 和语言代码需要在 request 的参数中被指定。由于这个视图改变用户如何看到网站的其他部分,
它必须只能通过 POST request. 如果调用 GET request,
它将重定向到 request 的那页,但没有任何状态改变。
    """
    next = request.REQUEST.get('next', None)
    if not next:
        next = request.META.get('HTTP_REFERER', None)
    if not next:
        next = '/'
    response = http.HttpResponseRedirect(next)
    if request.method == 'POST':
        lang_code = request.POST.get('language', None)
        if lang_code and check_for_language(lang_code):
            if hasattr(request, 'session'):
                request.session['django_language'] = lang_code
            else:
                response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
 return response

 

从上面的代码,可以了解到保存了语言选择后,Django 根据以下算法来重定向页面:

(1)Django 在 POST 数据中寻找 next 参数。

(2)如果 next 参数不存在或为空,Django 尝试重定向页面为 HTML 头部信息中 Referer 的值。

(3)如果 Referer 也是空的,即该用户的浏览器并不发送 Referer 头信息,则页面将重定向到 / (页面根目录)。

这个视图是通过 POST 方法调用的,在请求中包含了 language 参数。所以在前面的模版的 select 的 name 和 id 必须为” language”。

如果 session 已启用,这个视图会将语言选择保存在用户的 session 中。 否则,它会以缺省名 django_language 在 cookie 中保存这个语言选择。( 这个名字可以通过 LANGUAGE_COOKIE_NAME 设置来改变 )。

 

 

小结

本文通过两个 Django 国际化实例向读者介绍了如何对 Django 程序中的 Python 和 template 代码进行国际化和本地化。并结合 Django 国际化现有代码框架进行分析,向读者阐述 Django 国际化的原理与内部实现。通过学习,可以很好的掌握国际化 Django 程序的技术。

 

 

 

下载

描述 名字 大小 下载方法
示例代码 testsite.zip 15KB HTTP

关于下载方法的信息

 

参考资料

学习

标签: django i18n
Posted in Uncategorized

Markdown Syntax Guide

Markdown Syntax Guide
The Allura code uses markdown syntax everywhere to allow you to create rich
text markup, and extends markdown in several ways to allow for quick linking
to other artifacts in your project.

Markdown was created to be easy to read, easy to write, and still readable in plain text format.

Links
Reference Links
Artifact Links
Text
Blockquotes
Preformatted Text
Lists
Tables
Headers
Horizontal Rules
Images
Escapes
More Headers
Code Highlighting
Includes
Neighborhood Notifications
Download Button
Project Screenshots
Thanks
Links
Most URLs will automatically be turned into links. To be explicit, just write it like this:

Output:

http://someurl

somebbob@example.com

To use text for the link, write it:

[like this](http://someurl)Output:

like this

You can add a *title* (which shows up under the cursor):

[like this](http://someurl “this title shows up when you hover”)Output:

like this

Reference Links
You can also put the [link URL][1] below the current paragraph
like [this][2].

[1]: http://url
[2]: http://another.url “A funky title”
Output:

You can also put the link URL below the current paragraph like this.

Here the text “link URL” gets linked to “http://url”, and the lines showing “[1]: http://url” won’t show anything.

Or you can use a [shortcut][] reference, which links the text “shortcut” to the link named “[shortcut]” on the next paragraph.

Or you can use a [shortcut][] reference, which links the text
“shortcut” to the link named “[shortcut]” on the next paragraph.

[shortcut]: http://goes/with/the/link/name/textOutput:

Or you can use a shortcut reference, which links the text “shortcut” to the link named “shortcut” on the next paragraph.

Artifact Links
Any existing forge resource can be linked with surrounding square brackets ie [MyPage] or [#123].

Links to resources in other tools can be explicitly referenced by adding a tool identifier prefix to the link. So for instance `[developerwiki:MyPage]` can refer to a wiki page in a `developerwiki` instance. You can also link to tickets with `[tickets:#123]` assuming there’s a 123 ticket in a Tracker instance mounted at `tickets`. The same is true for forums, or any of the other tools you have installed. You can even link to tickets in a subproject with `[subproject.tickets:#123]`.

[MyPage]
[developerwiki:MyPage]
[#123]
[tickets:#123]
No example output is available for this one because it only works on real artifacts. Try it in your project!

Text
Use * or _ to emphasize things:

*this is in italic* and _so is this_

**this is in bold** and __so is this__

***this is bold and italic*** and ___so is this___
Output:

this is in italic and so is this

this is in bold and so is this

this is bold and italic and so is this

You can strike through text using HTML like this:

this is strike through text
Output:

this is strike through text

Just write paragraphs like in a text file and they will display how
you would expect. A blank line separates paragraphs.

So this is a new paragraph. But any text on adjacent lines
will all end up
in the same paragraph.
Output:

Just write paragraphs like in a text file and they will display how
you would expect. A blank line separates paragraphs.

So this is a new paragraph. But any text on adjacent lines
will all end up
in the same paragraph.

Blockquotes
Use the > character in front of a line, just like in email

> Use it if you’re quoting a person, a song or whatever.

> You can use *italic* or lists inside them also.
And just like with other paragraphs,
all of these lines are still
part of the blockquote, even without the > character in front.

To end the blockquote, just put a blank line before the following
paragraph.
Output:

Use it if you’re quoting a person, a song or whatever.

You can use italic or lists inside them also. And just like with other paragraphs, all of these lines are still part of the blockquote, even without the > character in front.

To end the blockquote, just put a blank line before the following
paragraph.

Preformatted Text
If you want some text to show up exactly as you write it, without Markdown doing anything to it, just indent every line by at least 4 spaces (or 1 tab).

This line won’t *have any markdown* formatting applied.
I can even write HTML and it will show up as text.
This is great for showing program source code, or HTML or even
Markdown. this won’t show up as HTML but
exactly as you see it in this text file.

As a shortcut you can use backquotes to do the same thing while
inside a normal pargraph. `This won’t be *italic* or **bold**
at all.`
Output:

This line won’t *have any markdown* formatting applied.
I can even write HTML and it will show up as text.
This is great for showing program source code, or HTML or even
Markdown. this won’t show up as HTML but
exactly as you see it in this text file.
As a shortcut you can use backquotes to do the same thing while
inside a normal pargraph. This won’t be *italic* or **bold**
at all.

Lists
* an asterisk starts an unordered list
* and this is another item in the list
+ or you can also use the + character
– or the – character

To start an ordered list, write this:

1. this starts a list *with* numbers
+ this will show as number “2”
* this will show as number “3.”
9. any number, +, -, or * will keep the list going.
* just indent by 4 spaces (or tab) to make a sub-list
1. keep indenting for more sub lists
* here i’m back to the second level
Output:

an asterisk starts an unordered list
and this is another item in the list
or you can also use the + character
or the – character
To start an ordered list, write this:

this starts a list with numbers
this will show as number “2”
this will show as number “3.”
any number, +, -, or * will keep the list going.
just indent by 4 spaces (or tab) to make a sub-list
keep indenting for more sub lists
here i’m back to the second level
Tables
You can create tables using pipes and dashes like this:

First Header | Second Header
————- | ————-
Content Cell | Content Cell
Content Cell | Content Cell
Output:

First Header Second Header
Content Cell Content Cell
Content Cell Content Cell

You can use markdown syntax within table cells for formatting:

First Header | Second Header
————- | ————-
*Content Cell* | Content Cell
Content Cell | Content Cell
Output:

First Header Second Header
Content Cell Content Cell
Content Cell Content Cell

You can also create tables using HTML code.

Headers
Just put 1 or more dashes or equals signs (— or ===) below the title.

This is a huge header
==================

this is a smaller header
——————
Output:

This is a huge header
this is a smaller header
Horizontal Rule
Just put three or more *’s or -‘s on a line:

—————-
Output:

——————————————————————————–

Or, you can use single spaces between then, like this:

* * *
Output:

——————————————————————————–

or

– – – – – – –
Output:

——————————————————————————–

Make sure you have a blank line above the dashes, though, or else:

you will get a header

Output:

you will get a header
Images
To include an image, just put a “!” in front of a text link:

![alternate text](https://sourceforge.net/images/icon_linux.gif)
Output:

The “alternate text” will show up if the browser can’t load the image.

You can also use a title if you want, like this:

![tiny arrow](https://sourceforge.net/images/icon_linux.gif “tiny arrow”)
Output:

To reference an attached image, just use the img macro. You can add more attributes:

[[img src=attached-image.jpg alt=foobar]]
Output:

Escapes
What if you want to just show asterisks, not italics?

* this shows up in italics: *a happy day*
* this shows the asterisks: *a happy day*
Output:

this shows up in italics: a happy day
this shows the asterisks: *a happy day*
The backslashes will disappear and leave the asterisks.

You can do the same with any of the characters that have a special meaning
for Markdown.

HTML tags may need to be escaped. will be interpreted as a bold tag. Entity codes will be used. isn’t allowed and will be dropped, so you probably want to escape it:

this will be bold
you should escape <unknown> tags
< special entities work
&lt; if you want to escape it
Output:

this will be bold
you should escape tags
< special entities work
< if you want to escape it

Individual ampersands (&) and less-than signs (<) are fine, they will be shown as expected.

More Headers
More ways of doing headers:

# this is a huge header #
## this is a smaller header ##
### this is even smaller ###
#### more small ####
##### even smaller #####
###### smallest still: `

` header
Output:

this is a huge header
this is a smaller header
this is even smaller
more small
even smaller
smallest still:

header
You can use up to 6 # characters at the beginning of the line.

Code Highlighting
The Code highlighting used in the newforge is based on (http://www.freewisdom.org/projects/python-markdown/CodeHilite). It follows the same syntax as regular Markdown code blocks, except that there are two ways to tell the highlighter what language to use for the code block.

If the first line of the codeblock contains a shebang, the language is derived from that and line numbers are used.

#!/usr/bin/python
# Code goes here …
Output:

1
2 #!/usr/bin/python
# Code goes here …

If the first line contains a shebang, but the shebang line does not contain a path (a single / or even a space) or If the first line begins with three or more colons, the text following the colons identifies the language. In both cases, the first line is removed from the code block before processing.

:::python
# Code goes here …
Output:

# Code goes here …
You can also designate a code block by surrounding it with lines of tildes. The type of code highlighting to apply will be inferred based on the code within, or you can specify like above.

~~~~~~
My code
~~~~~~
Output:

My code
Includes
You can embed another wiki page directly:

[[include ref=SamplePage]]
No example output is available for this one because it only works on real wiki pages. Try it in your wiki!

Neighborhood Notifications
You can list updates from all projects in a neighborhood by tool type. Max_number (default is 5) and sort (default is pubdate) are optional:

[[neighborhood_feeds tool_name=Wiki max_number=10 sort=pubdate]]
Neighborhood Blog Posts
You can view blog posts from all projects in a neighborhood. Max_number (default is 5) and sort (default is timestamp) are optional:

[[neighborhood_blog_posts max_number=10 sort=timestamp]]
Project Blog Posts
You can view blog posts from all blogs in a project. Max_number (default is 5), mount point (leave empty to view posts from all blog tools in a project), and sort (default is timestamp) are optional:

[[project_blog_posts max_number=10 sort=timestamp mount_point=news]]
Download Button
You can display a download button that links to the best download available for the active project. Please note that if you use this macro and there is no download associated with your project, the button will not appear.

[[download_button]]
Project Screenshots
You can show all the screenshots for the current project as thumbnails that are linked to the full-size image.

[[project_screenshots]]
Thanks
Thanks to John Gruber and Aaron Swartz for creating Markdown.

This page is based on some examples from Greg Schueler, greg@vario.us

Posted in Uncategorized

guides rubyonrails Migrations (EN and CN)

Migrations

Migrations are a convenient way for you to alter移动your database in a structured and organized manner.Migrations是一种很便捷的方法让你能够以一种结构化的和有组织的方式来迁移你的数据库。You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them.你可以手动编辑SQL片段,而且你有责任把这些告诉其他的开发人员,因为他们需要开发和使用它们。You’d also have to keep track of which changes need to be run against the production machines next time you deploy.你也可以跟踪对你部署的代码在接下来的production机器(将会)发生的变化。

Active Record tracks which migrations have already been run so all you have to do is update your source and run rake db:migrate.Active Record跟踪并迁移你已经运行过的(代码和数据),而你只需要在更新了你的源代码的时候执行rake db:migrateActive Record will work out which migrations should be run.Active Recor将会计算出那些迁移需要被执行。It will also update your db/schema.rb file to match the structure of your database.它还会更新你的db/schema.rb文件使其于你的数据库结构相匹配。

Migrations also allow you to describe these transformations using Ruby.Migrations同样允许你使用Ruby来描述这些转换。The great thing about this is that (like most of Active Records functionality) it is database independent: you dont need to worry about the precise syntax of CREATE TABLE any more than you worry about variations on SELECT * (you can drop down to raw SQL for database specific features).值得高兴的事情是(就像大多数的Active Record's工厂)它是与数据独立的:你再也不需要担心准确的语法来CREATE TABLE也不需要担心SELECT *的变化。For example you could use SQLite3 in development, but MySQL in production.例如你可以使用SQLite3开发,但是在发布的产品中使用MySQL

Youll learn all about migrations including:下面你将了解到的migrations包括:

  • The generators you can use to create them 你可以使用generators来创建他们(数据库表单)
  • The methods Active Record provides to manipulate操纵your database Active Record提供方法来操纵你的数据库
  • The Rake tasks that manipulate them 使用Rake命令操作这些(迁移)
  • How they relate to schema.rb 它们是如何映射到schema.rb

1 Anatomy of a Migration

Before we dive into the details of a migration, here are a few examples of the sorts of things you can do:在深入migration的详细介绍之前,下面有一系列的例子你可以尝试一下:

class CreateProducts < ActiveRecord::Migration

def up

create_table :products do |t|

t.string :name

t.text :description

 

t.timestamps

end

end

 

def down

drop_table :products

end

end

This migration adds a table called products with a string column called name and a text column called description.这次migration添加了一个名叫products的表它有一个叫name的字符串的列和一个叫description的文本框的列。A primary key column called id will also be added, however since this is the default we do not need to ask for this.一个名叫id的主键列也被添加,然而因为这是默认操作的不需要我们刻意添加。The timestamp columns created_at and updated_at which Active Record populates automatically will also be added.还添加了timestamp字段,products表单会在created_atupdated_at的时候通过Active Record自动填充timestamp字段。Reversing this migration is as simple as dropping the table.撤销这次migration就像dropping这个表。

 

Migrations are not limited to changing the schema.Migrations不限制更改schemaYou can also use them to fix bad data in the database or populate new fields:你可以使用(schema)它们来修复坏的数据或者添加新的字段:

class AddReceiveNewsletterToUsers < ActiveRecord::Migration

def up

change_table :users do |t|

t.boolean :receive_newsletter, :default => false

end

User.update_all ["receive_newsletter = ?", true]

end

 

def down

remove_column :users, :receive_newsletter

end

end

Some caveats apply to using models in your migrations.一些在modelmigrations中的注意事项

This migration adds a receive_newsletter column to the users table.(上面)这个migration添加一个receive_newsletter字段到user表。We want it to default to false for new users, but existing users are considered to have already opted in, so we use the User model to set the flag to true for existing users.我们希望对于新用户默认设置receive_newsletter字段为fasle,但是存在的用户被认为已经有(自己的)选择,因此我们通过在User model中存在的用户设置为True的标识(来保留以后信息)。

Rails 3.1 makes migrations smarter by providing a new change method.Rails3.1通过提供一个新的change方法,使得migrations更加智能化。This method is preferred首选for writing constructive migrations (adding columns or tables).这个方法是用来做(数据库)结构迁移(添加或删除字段)的首选。The migration knows how to migrate your database and reverse it when the migration is rolled back without the need to write a separate down method.migration知道怎样迁移你的数据库以及不需要单独的编写down方法来处理回滚是的migration

class CreateProducts < ActiveRecord::Migration

def change

create_table :products do |t|

t.string :name

t.text :description

 

t.timestamps

end

end

end

1.1 Migrations are Classes Migrations是一个类

A migration is a subclass of ActiveRecord::Migration that implements two methods: up (perform the required transformations) and down (revert them).一个migration类是ActiveRecord::Migration的子类,它实现了两个方法:up(执行所请求的转换)和down(撤销所做的更改)。

Active Record provides methods that perform common data definition tasks in a database independent way (you’ll read about them in detail later):Active Record提供了了在数据库中执行常见数据定义的方法(你将会在后面看到详细的介绍)。

  • create_table
  • change_table
  • drop_table
  • add_column
  • change_column
  • rename_column
  • remove_column
  • add_index
  • remove_index

If you need to perform tasks specific to your database (for example create a foreignkey constraint约束) then the execute function allows you to execute arbitrary SQL.如果你在你的数据库中需要处理特殊的任务(例如创建一个foreign key约束)那么execute功能允许你执行任意的SQL(语句)。A migration is just a regular Ruby class so you’re not limited to these functions. migration仅仅是一个Ruby类,因此你不必仅仅局限于现有的这些功能。For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models).例如在添加了一个字段之后你可以添加代码来设置这个字段在存在记录中的值(如果在你的model中需要)。

On databases that support transactions处理办理with statements that change the schema (such as PostgreSQL or SQLite3), migrations are wrapped in a transaction.当数据库支持通过声明来改变数据库的结构(例如PostgreSQLSQLite3),会在包含在migration处理中(直接改变数据库的结构)。If the database does not support this (for example MySQL) then when a migration fails the parts of it that succeeded will not be rolled back. 如果数据库不支持这样的功能(比如MySQL)然后migration会有部分失败——成功添加的(数据)将不会回滚。You will have to unpick the changes that were made by hand.你必须手动的分开这些改变。

1.2 What’s in a Name (数据库文件)名称中的信息

Migrations are stored in files in db/migrate, one for each migration class.Mgirations以单个文件的形式被存放在文件夹db/migrateThe name of the file is of the form YYYYMMDDHHMMSS_create_products.rb, that is to say a UTC timestamp时间戳identifying确定the migration followed by an underscore下划线followed by the name of the migration.migration)文件是以form命名的,还看见了一个在migration完成之时的时间戳接着是下划线接着是migration的名字。The name of the migration class (CamelCased version) should match the latter part of the file name. migraiton类(使用驼峰命名法)的名字应该和migration文件的名称的最后部分相匹配。For example 20080906120000_create_products.rb should define CreateProducts and 20080906120001_add_details_to_products.rb should define AddDetailsToProducts. If you do feel the need to change the file name then you have to update the name of the class inside or Rails will complain about a missing class.如果你觉得需要改变migration文件的名字你必须同样修改文件里边migration类的名字,不然Rails会找不到migration类。

Internally Rails only uses the migration’s number (the timestamp) to identify them.Rails内部只使用migration编号(时间戳)来确定他们。Prior to Rails 2.1 the migration number started at 1 and was incremented each time a migration was generated.Rails2.1之前migration编号从1开始然后在每次migration被创建过后增加。With multiple developers it was easy for these to clash requiring you to rollback migrations and renumber them.随着开发人员的增多这样会使的很容易产生冲突这就需要你回滚migrations和重新编号他们。developers With Rails 2.1 this is largely avoided by using the creation time of the migration to identify them.Rails2.1的开发人员通过migraiton文件的创建时间指明每个文件在很大程度上避免了冲突(的发生)。You can revert to the old numbering scheme by adding the following line to config/application.rb.你可以还原带旧的版本通过在config/application.rb文件中添加如下行:

config.active_record.timestamped_migrations = false

The combination组合of timestamps and recording which migrations have been run allows Rails to handle common situations that occur with multiple developers.时间戳和migrations的名字的组合使得Rails可以处理多个开发人员的普遍情况。

For example Alice adds migrations 20080906120000 and 20080906123000 and Bob adds 20080906124500 and runs it.比如Alice 添加了migration 20080906120000 and 20080906123000以及Bob添加并运行了20080906124500Alice finishes her changes and checks in her migrations and Bob pulls down the latest changes. Alice完成了他的更改并提交在他的migrationss中,并且Bob pull down了最新的更改。Rails knows that it has not run Alices two migrations so rake db:migrate would run them (even though Bobs migration with a later timestamp has been run), and similarly migrating down would not run their down methods.

Of course this is no substitution for communication within the team.当然这些在团队交流中是不可避免的。For example, if Alices migration removed a table that Bobs migration assumed to exist, then trouble would certainly strike.例如如果Alicemigration中移除了一个表但是Bob'smigraion假设它还在,那么麻烦就来了。

1.3 Changing Migrations

Occasionally you will make a mistake when writing a migration.偶尔你在写入一个migration的时候犯个错误。If you have 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.如果你已经运行了migration接着你不可能再去编辑和运行这个(错误的)migraionRails认为(你已经)运行了migration因此在你运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.你必须回滚migation(例如rake db:rollback),编辑修改你的migraion然后运行正确的版本rake db:migrate

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 if the existing version of the migration has already been run on production machines.在一般情况下,编辑存在的migrations不是一个好主意:因为这样你会给你自己或者你的合作成员产生额外的工作,头疼的原因是如果存在的migration已经在production机器中运行。Instead you should write a new migration that performs the changes you require.作为替代你应该编写一个新的migration来执行你需要的更改。Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless.编辑一个刚生成的还没有提交到软代码控制migration(或者更一般的情况还没有传播出你的开发机器)相对危害较轻。

1.4 Supported Types支持的类型

Active Record supports the following types:Active Record支持如下类型:

  • :primary_key
  • :string
  • :text
  • :integer
  • :float
  • :decimal 10进制
  • :datetime
  • :timestamp
  • :time
  • :date
  • :binary
  • :boolean

These will be mapped onto an appropriate underlying底层database type, for example with MySQL :string is mapped to VARCHAR(255).这些将会被映射为合适底层数据库的类型,例如使用MySQL :string类型将会映射成VARCHAR(255)You can create columns of types not supported by Active Record when using the non-sexy syntax, for example你可以在创建Active Record不支持的字段,使用non-sexy语法,例如

create_table :products do |t|

t.column :name, 'polygon', :null => false

end

This may however hinder阻碍portability移植to other databases.不过这可能会阻碍移植到其它数据库。

2 Creating a Migration新建一个Migrateion

2.1 Creating a Model新建一个Model

The model and scaffold generators will create migrations appropriate for adding a new model.modelgenerators创建器会为添加的新的model创建合适的migrationsThis migration will already contain instructions for creating the relevant有关table.这个migration已经包含在有关创建的的表的说明中。If you tell Rails what columns you want then statements声明for adding those will also be created. For example, running如果你告诉Rails将你随后声明的字段添加到创建migrations中,例如,运行

$ rails generate model Product name:string description:text

will create a migration that looks like this rails)将会新建一个像这样的migraion

class CreateProducts < ActiveRecord::Migration

def change

create_table :products do |t|

t.string :name

t.text :description

 

t.timestamps

end

end

end

You can append as many column name/type pairs as you want.你可以随意增加字段或者类型对。By default t.timestamps (which creates the updated_at and created_at columns that are automatically populated by Active Record) will be added for you.默认的t.timestampsActive Record 会自动生成updated_atcreated_at字段)会子添加在你的表中。

2.2 Creating a Standalone Migration新建一个独立的Migration

If you are creating migrations for other purposes (for example to add a column to an existing table) then you can use the migration generator:如果你正在因为其他的目的新建migrations(例如添加一个字段到一个存在的表)你可以使用migration创建器:

$ rails generate migration AddPartNumberToProducts

This will create an empty but appropriately named migration:这里会新建一个空的但是合适的migration

class AddPartNumberToProducts < ActiveRecord::Migration

def change

end

end

If the migration name is of the form “AddXXXToYYY” or “RemoveXXXFromYYY” and is followed by a list of column names and types then a migration containing the appropriate add_column and remove_column statements will be created.如果migration命名为AddXXXToYYY”RemoveXXXFromYYY”格式并且后跟有一些字段名称或类型,那么一个migration包含合适的add_columnremove_column将会被新建。

$ rails generate migration AddPartNumberToProducts part_number:string

ps:如果已经创建了一个AddPartNumberToProducts那么运行rails generate migration AddPartNumberToProducts part_number:string -f进行覆盖新建

will generate将会生成:

class AddPartNumberToProducts < ActiveRecord::Migration

def change

add_column :products, :part_number, :string

end

end

Similarly,同样,

$ rails generate migration RemovePartNumberFromProducts part_number:string

generates生成

class RemovePartNumberFromProducts < ActiveRecord::Migration

def up

remove_column :products, :part_number

end

 

def down

add_column :products, :part_number, :string

end

end

You are not limited to one magically神奇generated column, for example你不限制于一次只输入一个字段,例如

$ rails generate migration AddDetailsToProducts part_number:string price:decimal

generates生成

class AddDetailsToProducts < ActiveRecord::Migration

def change

add_column :products, :part_number, :string

add_column :products, :price, :decimal

end

end

As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit.一如往常,使用migration创建器生成的migration只是一个起点,你可以添加或删除其中的字段直到你满意为止。

 

The generated migration file for destructive migrations will still be old-style using the up and down methods.在创建的migration文件中对migrations进行破坏性的操作仍然使用老式的updown方法。This is because Rails doesnt know the original data types defined when you made the original changes.这是因为当你对初始migration做了更改,Rails不知道原始的数据类型定义。

 

3 Writing a Migration

Once you have created your migration using one of the generators it’s time to get to work!一旦你已经使用一种创建器创建了你的migration,现在是时候开始工作了。

3.1 Creating a Table创建一个表

Migration method create_table will be one of your workhorses. A typical use would be Migration方法create_table将会是一个你的驮马。下面是他的典型形式

create_table :products do |t|

t.string :name

end

which creates a products table with a column called name (and as discussed below, an implicit id column).上面的代码会创建一个products表,其中包含了一个name字段(和下面讨论的一样,一个隐藏的id字段)

The object yielded to the block allows you to create columns on the table.这个migration类产生代码块让你创建表中的字段。There are two ways of doing this: The first (traditional) form looks like有两种方法创建字段:首先(传统的)方式看起来像这样

create_table :products do |t|

t.column :name, :string, :null => false

end

the second form, the so called “sexy” migration, drops the somewhat redundant column method.第二种方式,也称为sexy” migration,丢掉了有些冗余的column方法。Instead, the string, integer, etc. methods create a column of that type.作为替代,如string,interger等等方法创建相应类型的字段。Subsequent parameters are the same.其后的参赛也在同一个字段中。

create_table :products do |t|

t.string :name, :null => false

end

HABTM, hasAndBelongsToMany 这是Active Recored功能里面比较复杂的一个东西。简单的来说,就是你有一个帖子,你给这个帖子定义了多个tag但是实际上tag也是独立的,一个tag会包含很多个不同的帖子。

By default create_table will create a primary key called id.通过默认的create_table将会创建一个叫做id的主键。You can change the name of the primary key with the :primary_key option (dont forget to update the corresponding相应的model) or if you dont want a primary key at all (for example for a HABTM join table) you can pass :id => false.你可以更改主键的名字通过使用:primary_key选项(不要忘记更新其相应的model)或者你根本不想要主键(例如添加一个HABTM关系到表中)你可以通过:id => falseIf you need to pass database specific options you can place an SQL fragment in the :options option. For example如果你需要数据库的特殊选项你可以放置一个SQL片段在:options选项中。例如

create_table :products, :options => "ENGINE=BLACKHOLE" do |t|

t.string :name, :null => false

end

will append ENGINE=BLACKHOLE to the SQL statement used to create the table (when using MySQL the default is ENGINE=InnoDB).将会添加ENGINE=BLACKHOLESQL声明中用于创建表单(当使用MySQL默认的(ENGINE)是InnoDB)。

3.2 Changing Tables更改表单

A close cousin of create_table is change_table, used for changing existing tables. create_table的一个近亲是change_table,用于更改存在的表单。It is used in a similar fashion to create_table but the object yielded to the block knows more tricks.它使用的是与create_table类似的方法,但是其类产生的块能够知道更多的技巧。(如移除字段,重命名字段)For example例如

change_table :products do |t|

t.remove :description, :name

t.string :part_number

t.index :part_number

t.rename :upccode, :upc_code

end

removes the description and name columns, creates a part_number column and adds an index on it. Finally it renames the upccode column.移除description字段和name字段,创建一个part_number字段并且添加一个index(索引)给part_number。最后它重命名了:upccodeThis is the same as doing下面的代码也可以达到同样的效果

remove_column :products, :description

remove_column :products, :name

add_column :products, :part_number, :string

add_index :products, :part_number

rename_column :products, :upccode, :upc_code

 

You dont have to keep repeating the table name and it groups all the statements related to modifying one particular table.你不必重复表单名称和组相关的所有声明,(而只需要重复)修改表的一部分。The individual个别transformation转换names are also shorter, for example remove_column becomes just remove and add_index becomes just index.个别的转换方法的名称也更加简短了,例如remove_column变成removeadd_index变成index##这里是说的新旧两种方法比较

3.3 Special Helpers个别的Helpers

Active Record provides some shortcuts for common functionality.Active Record提供了一些一般功能的快捷操作。It is for example very common to add both the created_at and updated_at columns and so there is a method that does exactly that:例如很常见的通过created_atupdated_at添加字段等等,这里是一个方法的简要例子

create_table :products do |t|

t.timestamps

end

will create a new products table with those two columns (plus the id column) whereas.将会新建一个products表单,有两个字段(加上id字段)。

change_table :products do |t|

t.timestamps

end

adds those columns to an existing table.添加一些字段到存在的表中。

 

The other helper is called references (also available as belongs_to). In its simplest form it just adds some readability另一个helper被称为references(也可用belongs_to)。在其最简单的形式,它仅仅增加一些可读性。

create_table :products do |t|

t.references :category

end

will create a category_id column of the appropriate type.将会创建一个适当形式的category_id字段。Note that you pass the model name, not the column name. Active Record adds the _id for you. 注意那是你关联的model名称,不是字段名称。Active Record会给你添加_idIf you have polymorphic多态性belongs_to associations then references will add both of the columns required:

create_table :products do |t|

t.references :attachment, :polymorphic => {:default => 'Photo'}

end

will add an attachment_id column and a string attachment_type column with a default value ofPhoto.将会添加一个attachment_id 和一个默认值是Photo的字符串attachment_type字段。

The references helper does not actually create foreign key constraints for you. You will need to use execute for that or a plugin that adds foreignkeysupport.references helper没有真正创建外键约束给你。你需要使用execute或者一个plugin来添加外键支持。

If the helpers provided by Active Record arent enough you can use the execute function to execute arbitrary SQL.如果Active Record提供的helper不足以满足,你可以使用execute功能来执行任意SQL

For more details and examples of individual个别methods check the API documentation, in particular the documentation for ActiveRecord::ConnectionAdapters::SchemaStatements (which provides the methods available in the up and down methods), ActiveRecord::ConnectionAdapters::TableDefinition (which provides the methods available on the object yielded by create_table) and ActiveRecord::ConnectionAdapters::Table (which provides the methods available on the object yielded by change_table).

3.4 Writing Your change Method

The change method removes the need to write both up and down methods in those cases that Rails know how to revert the changes automatically. change(migration类方法)删除需要写进updown的方法,那样Rails知道怎样自动撤销更改。Currently, the change method supports only these migration definitions:目前,change方法支持如下的migration定义:

  • add_column
  • add_index
  • add_timestamps
  • create_table
  • remove_timestamps
  • rename_column
  • rename_index
  • rename_table

If you’re going to use other methods, you’ll have to write the up and down methods normally.如果你打算使用其他方法,你可以常规的写updown方法。

3.5 Writing Your down Method

The down method of your migration should revert the transformations done by the up method.迁移的down方法应该撤销up方法做的转换。In other words the database schema架构should be unchanged if you do an up followed by a down.换句话说数据库架构应该没有改变,如果你在up方法过后又执行down方法。For example if you create a table in the up method you should drop it in the down method. It is wise to do things in precisely the reverse order to in the up method.例如如果你创建使用up方法一个表你应该在down方法中去除它。For example

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 "ALTER TABLE products DROP FOREIGN KEY fk_products_categories"

drop_table :products

end

end

Sometimes your migration will do something which is just plain irreversible不可逆转的, for example it might destroy some data.有时候你的migration将会做一些不可逆转的事情。例如它可能会删除一些数据。In cases like those when you can’t reverse the migration you can raise ActiveRecord::IrreversibleMigration from your down method.在这样的情况中你不能撤销migration你可以在你的down方法中raise ActiveRecord::IrreversibleMigrationIf someone tries to revert your migration an error message will be displayed saying that it can’t be done.如果有人尝试撤销你的migration那么一个错误消息将会被显示it can’t be done”

4 Running Migrations

Rails provides a set of rake tasks to work with migrations which boils down to running certain sets of migrations. Rails提供给migrations一组rake任务,它大致归纳于运行若干的migrationsThe very first migration related rake task you use will probably be db:migrate.你使用的非常靠前的migration相应于rake的任务恰好是db:migrateIn its most basic form it just runs the up method for all the migrations that have not yet been run.其中它是最基本的形式,它仅仅运行migrations 还没被运行的up方法。If there are no such migrations it exits.如果没有这样的迁移它将会退出。

Note that running the db:migrate also invokes the db:schema:dump task, which will update your db/schema.rb file to match the structure of your database.注意运行db:migrate也调用了db:schema:dump任务,它将会更新你的db/schema.rb来和你的数据库匹配。

If you specify a target version, Active Record will run the required migrations (up or down) until it has reached the specified version.如果你特别(指定了)一个目标版本,Active Record将会运行所请求的migrationsupdown)直到它已经达成了(与)指定版本(的匹配)。The version is the numerical prefix on the migrations filename.版本号是migrations文件的数字前缀。For example to migrate to version 20080906120000 run例如迁移到20080906120000版本

$ rake db:migrate VERSION=20080906120000

If this is greater than the current version (i.e. it is migrating upwards) this will run the up method on all migrations up to and including 20080906120000, if migrating downwards this will run the down method on all the migrations down to, but not including, 20080906120000.如果执行的版本比当前版本更优(新)(i.e. it is migrating upwards)这将会执行包含20080906120000的所有版本的migrations up方法,反之则执行不包括20080906120000的版本之外的所有版本的migrations down方法。

4.1 Rolling Back

A common task is to rollback the last migration, for example if you made a mistake in it and wish to correct it.一个常用的任务来回滚最新的migration,例如如果你在其中犯了一个错误希望Rails能够改正它。Rather than tracking down the version number associated with the previous migration you can run

$ rake db:rollback

This will run the down method from the latest migration.这将会对最新的migration执行down方法。If you need to undo several migrations you can provide a STEP parameter:如果你需要撤销一系列的migrations你可以提供一个STEP 参数:

$ rake db:rollback STEP=3

will run the down method from the last 3 migrations.将会对最近的3次迁移执行down方法。

The rake db:migrate:redo task is a shortcut for doing a rollback and then migrating back up again. rake db:migrate:redo任务是一个回滚后再次返回的快捷操作。As with the db:rollback task you can use the STEP parameter if you need to go more than one version back, for example通过dbrollback 任务你可以使用STEP参数如果你需要撤销超过一个版本,例如

$ rake db:migrate:redo STEP=3

Neither of these Rake 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.(通过)上两次rake任务,你没有对db:migrate任何事情,如果你不需要指定明确的版本来迁移这样做都非常方便。

Lastly, the db:reset task will drop the database, recreate it and load the current schema into it.最后,

db:reset任务将会drop数据库,重建它并且在其中导入正确的架构。

This is not the same as running all the migrationssee the section on schema.rb.这和运行所有的migrations不一样——阅读schema.rb章节。

4.2 Being Specific开始指定的

If you need to run a specific migration up or down the db:migrate:up and db:migrate:down tasks will do that. 如果你需要对一个指定的版本migration执行updown,那么db:migrate:up and db:migrate:down任务会满足你。Just specify the appropriate version and the corresponding migration will have its up or down method invoked, for example指定适当的版本那么相应的migration就会调用它的updown方法,例如

$ rake db:migrate:up VERSION=20080906120000

will run the up method from the 20080906120000 migration.将会执行来自20080906120000版本的migrationup方法。These tasks check whether the migration has already run, so for example db:migrate:up VERSION=20080906120000 will do nothing if Active Record believes that 20080906120000 has already been run.这个任务会检查migration是否已经运行,如果Active Record认为已经被运行过了db:migrate:up VERSION=20080906120000将不会做任何事情。

4.3 Being Talkative开始唠叨

By default migrations tell you exactly what they’re doing and how long it took.通过默认的migrations会明确地告诉你他们做了什么以及花费了多长时间。A migration creating a table and adding an index might produce output like this一次迁移创建一个表并且添加一个index可能会产生这样的输出

20080906170109 CreateProducts: migrating

-- create_table(:products)

-> 0.0021s

-- add_index(:products, :name)

-> 0.0026s

20080906170109 CreateProducts: migrated (0.0059s)

Several methods are provided that allow you to control all this:一些方法(被证明)允许你像下面这样控制

  • suppress_messages takes a block as an argument and suppresses抑制any output generated by the block. suppress_messages获取一个block作为一个参数并且抑制常规输出到block
  • say takes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent缩进or not. say获取一个message参数并且输出它。第二个布尔参数可以指定是否缩进
  • say_with_time outputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected. say_with_time在文字旁边输出运行这个代码块花费了多少时间。如果block返回一个整数它假设这是受影响的代码行。

For example, this migration例如,这个migration

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

generates the following output一般情况会得到如下输出

== CreateProducts: migrating =================================================

— Created a table

-> and an index!

— Waiting for a while

-> 10.0108s

-> 250 rows

== CreateProducts: migrated (10.0171s) =======================================

If you just want Active Record to shut up then running rake db:migrate VERBOSE=false will suppress抑制all output.如果你希望让所有Active Record闭嘴,那么运行rake db:migrate VERBOSE=false会抑制所有的输出。

5 Using Models in Your Migrations在你的migration中使用Moldels

When creating or updating data in a migration it is often tempting to use one of your models新建或更新一个migration的数据它通常会使你的models更有诱惑力。After all they exist to provide easy access to the underlying底层data.毕竟他们的存在提供了对底层数据的访问。This can be done, but some caution注意should be observed观察.这是可行的,但是有些地方需要注意。

For example, problems occur when the model uses database columns which are (1) not currently in the database and (2) will be created by this or a subsequent migration.例如,问题焦点当models使用数据库字段,但是migration1)在数据库中没有这个字段而migration2)将会被它本身的migration文件或者随后的migration创建。

Consider this example, where Alice and Bob are working on the same code base which contains a Product model:思考这个例子,AliceBob工作在同样的代码中,其主要包含一个Product模型:

Bob goes on vacation. Bob在休假

Alice creates a migration for the products table which adds a new column and initializes it. She also adds a validation to the Product model for the new column. Alice创建一个products表单的migration,其添加一个新的字段并初始化它。她也添加一个关于新字段的验证到Product模型。

rails generate migration AddFlagToProduct flag:int#然后再添加一部分这样通过创建器的话model文件也一起创建了

# db/migrate/20100513121110_add_flag_to_product.rb

 

class AddFlagToProduct < ActiveRecord::Migration

def change

add_column :products, :flag, :int

Product.all.each { |f| f.update_attributes!(:flag => 'false') }#初始化置为false

end

end

 

# app/model/product.rb

 

class Product < ActiveRecord::Base

validates :flag, :presence => true

end

Alice adds a second migration which adds and initializes another column to the products table and also adds a validation to the Product model for the new column.Alice添加第二个migration,在这个migration中给Products表单添加并初始化了另一个字段,而且为Product model的新字段添加并初始化了验证。

# db/migrate/20100515121110_add_fuzz_to_product.rb

 

class AddFuzzToProduct < ActiveRecord::Migration

def change

add_column :products, :fuzz, :string

Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }

end

end

# app/model/product.rb

 

class Product < ActiveRecord::Base

validates :flag, :fuzz, :presence => true

end

Both migrations work for Alice.两个migrations都是Alice编写的。

Bob comes back from vacation假期and:Bob休假回来然后接着下面的工作

  1. updates the sourcewhich contains both migrations and the latests version of the Product model. 更新源代码——包含migrations和最新版本的Product模型。
  2. runs outstanding migrations with rake db:migrate, which includes the one that updates the Product model. 通过rake db:migrate执行突出(最新版本的migration)的migrations,其中包含了更新Product模型。

The migration crashes because when the model attempts尝试to save, it tries to validate the second added column, which is not in the database when the first migration runs.数据迁移冲突因为当model尝试保存时,它试图验证第二个添加的字段,然而它在第一次migration运行时不在数据库中。(没有弄明白呢感觉好像两个迁移是分开的才会出现这样的错误)

rake aborted!

An error has occurred, this and all later migrations canceled:

 

undefined method `fuzz' for #<Product:0x000001049b14a0>

A fix for this is to create a local model within the migration.解决这个错误是在migration创建一个本地的modelThis keeps rails from running the validations, so that the migrations run to completion.这样在rails运行中保持了验证,使得migrations完成了验证。

When using a faux model, it’s a good idea to call Product.reset_column_information to refresh the ActiveRecord cache for the Product model priorto updating data in the database.当使用一个人造的model,调用Product.reset_column_information来刷新在更新数据到数据库前的Product modelActiveRecord缓存是一个好主意。

If Alice had done this instead, there would have been no problem:如果Alice这样做了就不会有问题了

# db/migrate/20100513121110_add_flag_to_product.rb

 

class AddFlagToProduct < ActiveRecord::Migration

class Product < ActiveRecord::Base

end

 

def change

add_column :products, :flag, :int

Product.reset_column_information

Product.all.each { |f| f.update_attributes!(:flag => false) }

end

end

 

class AddFuzzToProduct < ActiveRecord::Migration

class Product < ActiveRecord::Base

end

def change

add_column :products, :fuzz, :string

Product.reset_column_information

Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }

end

end

 

6 Schema Dumping and You架构模式和你

6.1 What are Schema Files for?

Migrations, mighty as they may be, are not the authoritative source for your database schema. Migrations,很可能不是你的数据库schema的授权源。That role falls to either db/schema.rb or an SQL file which Active Record generates by examining the database.这一规则影响到db/schema.rbSQL文件它是Active Record创建器通过检查数据库生成的。They are not designed to be edited, they just represent the current state of the database.他们的设计(原意)是不被编辑的,他们仅仅是当前数据库状态的一个表现。

There is no need (and it is error prone) to deploy部署a new instance of an app by replaying the entire整个migration history. It is much simpler and faster to just load into the database a description of the current schema.这里没有必要(并且是错误倾向)去通过replaying整个migration历史来部署一个app的新的实例。更明智和更快捷的方式是load数据库的描述(也就是)当前schema

For example, this is how the test database is created: the current development database is dumped (either to db/schema.rb or db/development.sql) and then loaded into the test database.例如,这是测试数据库怎样被创建的:当前的开发数据库已经被转储(要么db/schema.rb 要么db/development.sql)然后被导入test数据库。

Schema files are also useful if you want a quick look at what attributes an Active Record object has. Schema文件在你希望快速的查看Active Record object有些什么属性的时候也很有帮助。This information is not in the model’s code and is frequently spread across several migrations but is all summed up in the schema file. 这些信息不是model中的代码并且频繁分布在多个migrations但是是所有架构文件的总结。The annotate_models plugin, which automatically adds (and updates) comments at the top of each model summarizing the schema, may also be of interest. annotate_models 插件,它能够自动在每个model汇总的顶部的添加(更新)comments,可能你会有兴趣。

6.2 Types of Schema Dumps架构转储的形式

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.有两种方式来转储架构。这是(相关)设置在config/application.rb通过config.active_record.schema_format来设置,其形式要么是:sql要么是:ruby.

If :ruby is selected then the schema is stored in db/schema.rb. If you look at this file youll find that it looks an awful可怕lot like one very big migration:如果:ruby被选中那么schema被存储在db/schema.rb。如果你查看这个文件你将会发现它看起来就像一个很大的migration

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

In many ways this is exactly what it is.在很多情况下它能够准确的(反应出schema)的信息。This file is created by inspecting the database and expressing its structure using create_table, add_index, and so on.这个文件被创建来检查数据库以及表达它的结构使用create_table, add_index等等。Because this is database independent it could be loaded into any database that Active Record supports.由于数据库的独立性它可以被导入到Active Record支持的任何数据库中。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 foreign key constraints, triggers or stored procedures.然而,有一个权衡:DB/ schema.rb不能表达数据库的具体项目,如外键约束,触发器或存储过程。While in a migration you can execute custom SQL statements, the schema dumper cannot reconstitute those statements from the database.虽然在一个migration你可以执行自定义的SQL表达式,schema储存器不能修复一些SQL的表达式(不能实现migrationrollback等功能)。If you are using features like this then you should set the schema format to :sql.如果你正在使用这样的特性接着你应该设置schema 格式为:sql

Instead of using Active Records schema dumper the databases structure will be dumped using a tool specific to that database (via the db:structure:dump Rake task) into db/#{Rails.env}_structure.sql. For example for PostgreSQL the pg_dump utility is used and for MySQL this file will contain the output of SHOW CREATE TABLE for the various tables. Loading this schema is simply a question of executing the SQL statements contained inside.

By definition this will be a perfect copy of the database’s structure but this will usually prevent loading the schema into a database other than the one used to create it.根据定义,这将是一个完美的复制数据库的结构,但通常会阻止加载到比其他用于创建一个数据库的架构。

6.3 Schema Dumps and Source Control

Because schema dumps are the authoritative source for your database schema, it is strongly recommended建议that you check them into source control.因为架构转储是你的数据库架构的授权源,强烈建议你检查他们到源控制。

7 Active Record and Referential参照Integrity完整

The Active Record way claims that intelligence belongs in your models, not in the database. As such, features such as triggers or foreign key constraints, which push some of that intelligence back into the database, are not heavily used.

Validations such as validates :foreign_key, :uniqueness => true are one way in which models can enforce data integrity. The :dependent option on associations allows models to automatically destroy child objects when the parent is destroyed. Like anything which operates at the application level these cannot guarantee referential integrity and so some people augment them with foreign key constraints.

Although Active Record does not provide any tools for working directly with such features, the execute method can be used to execute arbitrary SQL. There are also a number of plugins such as foreign_key_migrations which add foreign key support to Active Record (including support for dumping foreign keys in db/schema.rb).

 

Posted in Uncategorized

HTML4和HTML5之间的10个主要不同

HTML5是最新的HTML标准,或迟或早,所有的web程序员都会发现需要使用到这个最新的标准,而且,很多人都会感觉到,重新开发一个HTML5的网站,要比把一个网站从HTML4迁移到HTML5上容易的多,这是因为这两个版本之间有很大不同之处。

事实上,HTML5并没有对HTML4做什么重大的修改,它们很多东西都是相似的。

可是,其中有一些很重要的区别你需要知道。下面列出的就是一些HTML4和HTML5之间主要的不同之处(并不是全部,全部列出来是不可能的):

1. HTML5标准还在制定中
这头一个不同之处显而易见,但非常重要,我需要先从它开始。也许你已经注意到了关于HTML5很酷的言论到处都是,但是事实情况是,HTML5是一个还未完成的标准。HTML4已经有10岁了,但它仍是当前正式的标准的事实没有改变。

另一方面,HTML5仍处在早期阶段,以后的修改会不断的出现。你必须考虑到这些,因为你在网站上使用的这些新增加或修改的网页元素会每年都出现一些变化,你需要不停的更新升级你的网站,这可不是你希望的。这就是目前为止,你最好在产品里使用HTML4,只在实验里使用HTML5的原因。

2. 简化的语法
更简单的doctype声明是HTML5里众多新特征之一。现在你只需要写<!doctype html>,这就行了。HTML5的语法兼容HTML4和XHTML1,但不兼容SGML。

3. 一个替代Flash的新 <canvas> 标记
对于Web用户来说,Flash既是一个惊喜,也是一种痛苦。有很多的Web开发人员对HTML5对Flash产生的威胁很不满。但对于那些忍受着要花几年时间加载和运行的臃肿的Flash视频的人来说,用新的 <canvas> 标记生成视频的技术已经到来。

目前, <canvas> 标记并不能提供所有的Flash具有的功能,但假以时日,Flash必将从web上淘汰。我们拭目以待,因为很多人还并不认同这种观点。

4. 新的 <header> 和 <footer> 标记
HTML5的设计是要更好的描绘网站的解剖结构。这就是为什么这些<header> 和<footer> 等新标记的出现,它们是专门为标志网站的这些部分设计的。

在开发网站时,你不在需要用<div>标记来标注网页的这些部分。

5. 新的 <section> 和 <article> 标记
跟<header> 和 <footer>标记类似,HTML5中引入的新的<section> 和 <article> 标记可以让开发人员更好的标注页面上的这些区域。

据推测,除了让代码更有组织外,它也能改善SEO效果,能让搜索引擎更容易的分析你的页面。

6. 新的 <menu> 和 <figure> 标记
新的<menu>标记可以被用作普通的菜单,也可以用在工具条和右键菜单上,虽然这些东西在页面上并不常用。

类似的,新的 <figure> 标记是一种更专业的管理页面上文字和图像的方式。当然,你可以用样式表来控制文字和图像,但使用HTML5内置的这个标记更适合。

7. 新的 <audio> 和 <video> 标记
新的<audio> 和 <video> 标记可能是HTML5中增加的最有用处的两个东西了。正如标记名称,它们是用来嵌入音频和视频文件的。

除此之外还有一些新的多媒体的标记和属性,例如<track>,它是用来提供跟踪视频的文字信息的。有了这些标记,HTML5使Web2.0特征变得越来越友好。问题在于,在HTML5还未被广泛的接受之前,Web2.0还是老的Web2.0。

8. 全新的表单设计
新的 <form> 和 <forminput> 标记对原有的表单元素进行的全新的修改,它们有很多的新属性(以及一些修改)。如果你经常的开发表单,你应该花时间更详细的研究一下。

9. 不再使用 <b> 和 <font> 标记
对我个人来说,这是一个让我不太理解的改动。我并不认为去除 <b> 和 <font>标记会带来多大的好处。我知道,官方的指导说这些标记可以通过CCS来做更好的处理,但这样一来,为了在文章一两个地方出现的这种标记,你就需要在独立的css和文本两个地方来实现这一的功能,岂不笨拙。也许我们以后会习惯这种方法。

10. 不再使用 <frame>, <center>, <big> 标记
事实上,我已经记不清曾经何时用过这些标记了,所以,我并不为去除这些标记感到悲哀。相同的原因,有更好的标记能实现它们的功能——这很好,任何作废的标记从标准中剔除都是受欢迎的。

这10个HTML5和HTML4之间的不同只是整个新的规范中的一小部分。除了这些主要的变动外,我还可以略提一下一些次要的改动,比如修改了<ol> 标记的属性,让它能够倒排序,对<u>标记也做了修改。

所有这些次要的改动数量众多。而且新的修改也在不断的增加,因此,如果你想实时跟踪最新的动向,你需要经常的查看w3.org的HTML4 和 HTML5之间的不同这个页面。如果你很心急,想在你的工作中使用这些新的标记和属性,我劝告你最好只是做实验,原因已经说的很清楚了,这些新标记和新属性在将来也许会有很大的改变,所以,除非你不断的更新你的代码,它们很可能会过期失效。

尽管如今大多数流行的浏览器的最新版都支持HTML5,但有些新的(或修改的)标记和属性它们并不支持,所以你的网页在用户的屏幕上有可能前后显示的不一致。耐心等待,等待HTML5真正可以实用时候。目前还不是时候。

Posted in Uncategorized

日历js代码

a simple js calendar code:

code1:








3 4

Code2:








3 4
Posted in Uncategorized

苹果饥饿营销自造尴尬 “黄牛”堵门叫卖iPhone4s

iPhone4s已经在几十个国家和地区上市销售,并于本月11日正式登陆中国香港,但因为iPhone4s在内地上市时间迟迟未定,已经望穿秋水的“果粉”只能继续守候下去。然而想继续通过饥饿营销积累客户的苹果,不得不面对自己制造的“尴尬”。随着昨日大量港版iPhone4s抵京,大批黄牛来到苹果三里屯官方零售店外堵门叫卖。

  “iPhone4s要吗?港版无锁的!”在苹果三里屯零售店门口,一群手拿着苹果白色包装盒的黄牛,不断地招揽着过往行人,一发现有顾客从苹果店走出来,就立刻围上去。

  一位刘姓男子透露,他卖的iPhone4s是昨天刚从深圳发到北京的,黑色16G版的现在卖5850元,不讲价。虽然比起4150元的官方定价,这名黄牛一口气加了1700元,但他表示这个价格还是港版上市后降了的,以前澳版的iPhone4s要价都超过6500元。这名黄牛“备货”十分充足,身后的书包里装了近百台iPhone4s。

  隔着一道通明的玻璃墙,苹果店员自然对门外的黄牛一清二楚,除了在店门口立起一块不大的警示牌提醒消费者黄牛手中的iPhone4s“来源不明”外,苹果对黄牛们似乎无能为力。“iPhone4s的上市时间还没定,所以现在我们还没有相应的预订服务。”三里屯苹果旗舰店的一位负责人坦言,想从苹果官方渠道买到iPhone4s,可能还要等上很长一段时间。

  面对苹果没货,黄牛堵门的情况,不禁有“果粉”发出疑问,“为什么iPhone4s在国内迟迟不能上市”。对此,苹果中国的负责人解释称,iPhone4s进入国内需要经过多重审核,以取得工信部的入网许可证,所以上市时间迟迟未定。然而苹果这一解释无法服众,一位业内人士表示,工信部的入网许可并不难拿,一般企业提出申请后10个工作日就能通过相应审核,而iPhone4s从发布至今已经1个多月。

  “苹果之所以迟迟不定iPhone4s在中国内地的上市时间,无非还是想玩饥饿营销,吊起消费者的胃口。”这位业内人士直言,但如此营销造成黄牛堵门,不知苹果有何感想。

Posted in Uncategorized

Simple Django event calendar template tag

Simple Django event calendar template tag

blogserver / apps / blog / templatetags / event_tags.py


##### Here is the template tag code. It goes in a file named
# "event_tags.py" in a subdirectory of your app called "templatetags".
#####

from calendar import HTMLCalendar
from django import template
from datetime import *
from itertools import groupby
import pdb
from django.utils.html import conditional_escape as esc
from blogserver.apps.blog.models import Post
register = template.Library()

def do_event_calendar(parser, token):
    """
    The template tag's syntax is {% event_calendar year month event_list %}
    """

    try:
        #tag_name, year, month, event_list = token.split_contents()
        tag_name= token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError, "%r tag requires three arguments" % token.contents.split()[0]
    #return EventCalendarNode(year, month, event_list)
    return EventCalendarNode()

class EventCalendarNode(template.Node):
    """
    Process a particular node in the template. Fail silently.
    """

    def __init__(self,):
        try:
            lToday = datetime.now()
            #self.year = template.Variable(year)
            #self.month = template.Variable(month)
            self.year = lToday.year
            self.month = lToday.month
            my_events = Post.objects.filter(date_published__year=self.year, date_published__month=self.month)
            #self.event_list = template.Variable(event_list)
            self.event_list = my_events
        except ValueError:
            raise template.TemplateSyntaxError

    def render(self, context):
        try:
            # Get the variables from the context so the method is thread-safe.
            #my_event_list = self.event_list.resolve(context)
            #my_year = self.year.resolve(context)
            #my_month = self.month.resolve(context)
            my_event_list = self.event_list
            my_year = self.year
            #pdb.set_trace()
            my_month = self.month
            cal = EventCalendar(my_event_list)
            return cal.formatmonth(int(my_year), int(my_month))
        except ValueError:
            return
        except template.VariableDoesNotExist:
            return

class EventCalendar(HTMLCalendar):
    """
    Overload Python's calendar.HTMLCalendar to add the appropriate events to
    each day's table cell.
    """

    def __init__(self, events):
        super(EventCalendar, self).__init__()
        self.events = self.group_by_day(events)

    def formatday(self, day, weekday):
        if day != 0:
            cssclass = self.cssclasses[weekday]
            if date.today() == date(self.year, self.month, day):
                cssclass += ' today'
            if day in self.events:
                cssclass += ' filled'
                href="/%d/%02d/%02d/"%(self.year,self.month,day)
                return self.day_cell(cssclass, '<span class="dayNumber" title="click to view post"><a href=%s >%d</a></span>' % (href,day))
            return self.day_cell(cssclass, '<span class="dayNumberNoEvents">%d</span>' % (day))
        return self.day_cell('noday', '&nbsp;')

    def formatmonth(self, year, month):
        self.year, self.month = year, month
        return super(EventCalendar, self).formatmonth(year, month)

    def group_by_day(self, events):
        field = lambda event: event.date_published.day
        return dict(
            [(day, list(items)) for day, items in groupby(events, field)]
        )

    def day_cell(self, cssclass, body):
        return '<td class="%s">%s</td>' % (cssclass, body)

# Register the template tag so it is available to templates
register.tag("event_calendar", do_event_calendar)

 blogserver/ views.py

# Create your views here.
from django.shortcuts import render_to_response
import pdb
from datetime import *
from calendar import monthrange
from blogserver.apps.blog.models import Post
from django.template import RequestContext
from django.utils.html import conditional_escape as esc
from django.utils.safestring import mark_safe
from itertools import groupby
from calendar import HTMLCalendar, monthrange
##### Here’s code for the view to look up the event objects for to put in
# the context for the template. It goes in your app’s views.py file (or
# wherever you put your views).
#####
def named_month(month_number):
    “””
Return the name of the month, given the number.
“””
    return date(1900, month_number, 1).strftime(“%B”)
def this_month(request):
    “””
Show calendar of events this month.
“””
    today = datetime.now()
    return calendar(request, today.year, today.month)
def calendar(request, year, month, series_id=None):
    “””
Show calendar of events for a given month of a given year.
“series_id“
The event series to show. None shows all event series.
“””
    my_year = int(year)
    my_month = int(month)
    my_calendar_from_month = datetime(my_year, my_month, 1)
    my_calendar_to_month = datetime(my_year, my_month, monthrange(my_year, my_month)[1])
    my_events = Post.objects.filter(date_published__year=my_year, date_published__month=my_month)
    if series_id:
        my_events = my_events.filter(series=series_id)
    # Calculate values for the calendar controls. 1-indexed (Jan = 1)
    my_previous_year = my_year
    my_previous_month = my_month – 1
    if my_previous_month == 0:
        my_previous_year = my_year – 1
        my_previous_month = 12
    my_next_year = my_year
    my_next_month = my_month + 1
    if my_next_month == 13:
        my_next_year = my_year + 1
        my_next_month = 1
    my_year_after_this = my_year + 1
    my_year_before_this = my_year – 1
    #pdb.set_trace()
    return render_to_response(“blog/calendar.html”, { ‘events_list': my_events,
                                                        ‘month': my_month,
                                                        ‘month_name': named_month(my_month),
                                                        ‘year': my_year,
                                                        ‘previous_month': my_previous_month,
                                                        ‘previous_month_name': named_month(my_previous_month),
                                                        ‘previous_year': my_previous_year,
                                                        ‘next_month': my_next_month,
                                                        ‘next_month_name': named_month(my_next_month),
                                                        ‘next_year': my_next_year,
                                                        ‘year_before_this': my_year_before_this,
                                                        ‘year_after_this': my_year_after_this,
    }, context_instance=RequestContext(request))
def post_calendar(request):
  #pdb.set_trace()
# return render_to_response(“base.html”,)
  lToday = datetime.now()
  return calendar(request, lToday.year, lToday.month)
def base_page(request):
  #pdb.set_trace()
  return render_to_response(“base.html”,)
  #lToday = datetime.now()
  #return calendar(request, lToday.year, lToday.month)
##### Here’s code for the view to look up the event objects for to put in
# the context for the template. It goes in your app’s views.py file (or
# wherever you put your views).
#####
 blogserver / templates / blog/ calendar.html
at end do not forget to setting your url files
<style type="text/css">
/* Calendar */

#calendar table {
    width: 100%;
}

#calendar table tr th {
    text-align: center;
    font-size: 16px;
    background-color: #316497;
    color: #99ccff;
}

#calendar table tr td {
    width: 10%;
    border: 1px solid #555;
    vertical-align: top;
    height: 20px;
    padding: 2px;
}

#calendar td.noday {

}

#calendar td.filled {
    background-color: #99ccff;
}

#calendar td.today {
    border: 4px solid #316497;
}

#calendar .dayNumber {
    font-size: 16px !important;
    font-weight: bold;
}

#calendar a {
    font-size: 10px;
}

}
</style>
{% load event_tags %}

<div id="calendar">
{% event_calendar year month events_list %}
</div>
And I think the better way is bulit the calendar in css or js files and get event from the server
Posted in Uncategorized