注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

风之云的足迹

点击这里添加博客描述

 
 
 

日志

 
 

【转载】Rails中Rake任务详解  

2010-08-15 23:42:31|  分类: Rails |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
原文地址:http://jonny131.javaeye.com/blog/138787
原文地址:http://hi.baidu.com/kenrome/blog/item/8caf71f0f6ffc7c07831aac9.html


作为一个Rails开发者,你可能熟练得运用“rake”运行你的测试,或者你利用“rake db:migrate”运行你的数据迁移任务(migration)。但是,你是否真正明白在那些Rake任务的背后发生了什么吗?你是否意识到,你可以 编写属于你的任务,或者创建好用的属于你的Rake库文件?

下面是我运用Rake任务的一些例子:

  • 创建一个成员列表,并给他们发送电子邮件。
  • 进行每天的数据计算和报告。
  • 清除过期和重新创建缓存。
  • 数据库备份和subversion repository。
  • 运行任何类型的数据操作脚本。
  • 为即将开始我们的谈话倒酒(玩笑)

在这篇文章中,我们将理解为什么Rake被发明,以及它如何协助我们的Rails应用程序。最终你将能够编写属于你的任务。

内容列表

  1. 回顾:为什么是make?
  2. 如何获取Rake?
  3. Rake如何工作?
  4. 如何表达rake依赖?
  5. 如何为Rake任务写文档?
  6. Rake命名空间
  7. 如何写有用的Ruby任务?
  8. 如何为我的Rails应用写Rake任务?
  9. 可以在任务中访问Rails模型(Model)吗?
  10. 哪里可以找到更多的例子?

回顾:为什么是make?

为了理解我们为什么拥有Rake,我们首先需要注目一下Rake的祖父Make。
和我回忆一下过去,在解释性语言和iPhones诞生之前,每一段代码都需要编译。

那时候如果你下载大型程序,它们总是伴随着大量源代码和一个shell 脚本。 这个脚本包含你的计算机为了编译/链接/创建应用程序,需要的每一行代码。你必须运行“install_me.sh”(shell脚本),每一行代码将运 行(编译每一个源文件),然后编译出可执行文件。

对大多数人来说,此过程工作正常,除非你是开发程序的所有人中极其不幸运的一个。每一次你对源代码做了很小改动,并且 想测试它,你必须重新运行shell脚本来重新编译所有代码。显然,如果是大型程序这将消耗大量时间。

  1. Make 识别那些文件/资源在最后一次编译被改动。利用这个信息,Make在第二次执行的时候,只编译被改动过的源文件。这动 态地减少了重新编译大型程序的时间。
  2. Make也包含依赖跟踪(dependency tracking),所以你可以告诉编译器,源文件A需要源文件B来正确编译,源文件B依赖源文件C来正确编译。因此,如果Make编 译源文件A而源文件B尚未编译,那么,系统将首先编译源文件B。

也可以这样解释,“Make”是一个简单的可执行程序,像“dir”或“ls”一 样。为了让Make如何知道编译一个程序,必须创建一个“makefile”文件,它描述所有的源文件和依赖关系。

经过多年的发展,其它编程语言甚至开始运用Make。事实上,许多Ruby编程人员在rake出来之前一直运用它。
“但是,Ruby不需要被编译,所以为什么Ruby程序员使用它?”你如此叫道。

是的,Ruby是一个解释性的语言,我们不需要编译我们的代码,那么为什么Ruby程序员使用Make文件呢?

好吧,有两个主要原因:

  1. 创建任务—在每一个大型程序最 后,你都要编写命令行下可以运行的脚本。或许你想清除缓存,运行一个维护任务, 或者迁移数据库。除吧创建10个单独的shell脚本(或者一个大型的复杂脚本),你可以创建单个“Makefile”文件,在其中你可以通过任务组织所 有的东西。然后任务可以通过键入“make stupid”来运行。
  2. 依赖任务跟踪—当你开始编写维护 任务的库的时候,你开始注意到某些任务可能部分重复。例如,任务 “migrate”和任务“schema:dump”都需要连接一个数据库。我可以创建叫“connect_to_database”一个任务,让任务 “migrate”和任务“schema:dump”都依赖于“connect_to_database”下次我运行“migrate”,在 “migrate”运行之前“connect_to_database”将被运行。

如何获取Rake?

不少年以前Jim Weirich 工作在一个使用Make的Java项目。当使用他的Makefile,他意识到,在他的Makefile中,如果他能够编写小的Ruby代码片段,事情会 变得异常方便。所以他创造了rake。上个月在Railsconf会议,我们幸运的见到了Jim,他的确是个不错的人。

Jim加入了创建任务功能,进行任务依赖跟踪,甚至加入同样时间戳识别。(只构建最后一次编译被改动的源文件)。显然,最后一个特性不是我们在 Ruby中经常用到的。因为我们不需要编译。
我一直惊奇“Jim Weirich”所做的一切,现在你也知道了!Jim从来没有特意的编写Make,我猜想他只是不得以写出来的。

Rake如何工作?

起初,我想给这部分加个“如何因Rake变得挥霍”的标题,但是,不是很直觉得那么 做。

假设我想喝醉,需要那几个步骤?

  1. 买酒
  2. 喝酒
  3. 喝醉

如果我想运用Rake来调用以上的任务,可以创建一个包含以下内容的 “Rakefile”文件:

1
2
3
4
5
6
7
8
9
10
11
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
task :mixDrink do
puts "Mixed Fuzzy Navel"
end
 
task :getSmashed do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

然后我在rake文件的同一个目录下运行每一个任务,有一点如下所示:

相当酷!不管怎样,从依赖的立场,我可以任何顺序运行任务。虽然有时候,我希望在 “mixDrink”或者“purchaseAlcohol”之前,我可以“getSmashed”,这不是简单人力所能及。

如何表达rake依赖?

1
2
3
4
5
6
7
8
9
10
11
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
task :mixDrink => :purchaseAlcohol do
puts "Mixed Fuzzy Navel"
end
 
task :getSmashed => :mixDrink do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

所以,现在我说“为了mixDrink,我必须先purchaseAlcohol”,“为了 getSmashed 我必须mixDrink”。也许如你希望,依赖堆叠,因此:

正如你所看到的,现在当我将要“getSmashed”,依赖的任务 “purchaseAlcohol”和“mixDrink”被调用。

经过不久,你可能被诱惑不断膨胀你的酒瘾,因此而扩充你的Rakefile文件。你也许会是你的朋 友染上酒瘾。正像一个真正的软件项目,当你的团队增加成员的时候,你需要保证你有良好的文档。问题成了:

如何为rake任务写文档?

Rake提供了一个简洁的方法来为task编写文档,下面是用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
desc "This task will purchase your Vodka"
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
desc "This task will mix a good cocktail"
task :mixDrink => :purchaseAlcohol do
puts "Mixed Fuzzy Navel"
end
 
desc "This task will drink one too many"
task :getSmashed => :mixDrink do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

正如你所看到的,每一个任务有一个“desc”。这允许我们和朋友键入“rake -T”或者“rake –tasks”

很简单吧?

Rake命名空间

一旦你成为一个酒徒,并且使用大量的Rake任务,你可能需要一个好一点的办法对他们加以分类。这是为 什么使用命名空间的原因。如果我在上面的例子使用命名空间,它看起来将是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace :alcoholic do
desc "This task will purchase your Vodka"
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
desc "This task will mix a good cocktail"
task :mixDrink => :purchaseAlcohol do
puts "Mixed Fuzzy Navel"
end
 
desc "This task will drink one too many"
task :getSmashed => :mixDrink do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end
end

命名空间允许你根据分类为任务分组,是的,你可以在一个Rakefile中拥有多个命名空间。现在,如 果我执行“rake –tasks”,我将看到:

所以,现在运行以上任务,显然你将运行“rake alcoholic:getSmashed”

如何写有用的Ruby任务?

好了,你尽管写ruby代码就可以了。不是开玩笑。最近,我需要一个创建几个目录的脚本,所以我最终编写了 一个Rake任务,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
desc "Create blank directories if they don't already exist"
task(:create_directories) do
 
# The folders I need to create
shared_folders = ["icons","images","groups"]
 
for folder in shared_folders
 
# Check to see if it exists
if File.exists?(folder)
puts "#{folder} exists"
else
puts "#{folder} doesn't exist so we're creating"
Dir.mkdir "#{folder}"
end
 
end
end

默认情况下,rake可以访问File Utils里 的任何东西,但是你可以包含任何额外的Ruby中的任何东西。

如何为我的Rails应用写Rake任务?

Rails应用程序附带着许多预先存在的rake任务,你可以进入应用程序目录下键入“rake –tasks”来显示这些任务。如果你还没有尝试,马上尝试,我会等待….

为你的Rails应用创建新的rake任务,你需要打开/lib/tasks目录(目录必须已经存 在)。在这个目录中,如果你创建属于你的 Rakefile,并且命名为“something.rake”,任务将自动被接受。任务将被增加到应用程序rake任务的列表中,你可以从应用程序的根 目录运行他们。让我们吧上面的例子,应用到我们的rails应用程序。

utils.tasks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace :utils do
desc "Create blank directories if they don't already exist"
task(:create_directories) do
 
# The folders I need to create
shared_folders = ["icons","images","groups"]
 
for folder in shared_folders
 
# Check to see if it exists
if File.exists?("#{RAILS_ROOT}/public/#{folder}")
puts "#{RAILS_ROOT}/public/#{folder} exists"
else
puts "#{RAILS_ROOT}/public/#{folder} doesn't exist so we're creating"
Dir.mkdir "#{RAILS_ROOT}/public/#{folder}"
end
 
end
end
end

注意,在代码片断中,我是如何使用#{RAILS_ROOT}来得到全路径的。如果我现在我应用程序的 根目录下,运行“rake –tasks”,我将看到这个新功能将和其它rails rake任务混合在一起。

1
2
3
4
5
6
...
rake tmp:pids:clear # Clears all files in tmp/pids
rake tmp:sessions:clear # Clears all files in tmp/sessions
rake tmp:sockets:clear # Clears all files in tmp/sockets
rake utils:create_directories # Create blank directories if they don't already exist
...

很酷!现在,此处它变得比较有用。

可以在任务中访问Rails模型(Model)吗?

当然!事实上这正是我们使用rake的主要用途:编 写偶尔需要手动运行的任务,或者定时调度以自动运行的任务(使用cronjobs。 正如我在文章开始说的,我使用Rake任务来做下面的事情:

  • 创建一个成员列表,并给他们发送电子邮件。
  • 进行每天的数据计算和报告。
  • 清除过期和重新创建缓存。
  • 数据库备份和subversion repository。
  • 运行任何类型的数据操作脚本。

非常有用,而且简单。下面是一个查找订购关系即将过期用户、并发送电子邮件的的rake任务。

utils.tasks

1
2
3
4
5
6
7
8
9
10
namespace :utils do
desc "Finds soon to expire subscriptions and emails users"
task(:send_expire_soon_emails => :environment) do
# Find users to email
for user in User.members_soon_to_expire
puts "Emailing #{user.name}"
UserNotifier.deliver_expire_soon_notification(user)
end
end
end

正如你看到的,只有一个步骤访问你的模型(model),“=> :environment”:

  1. task(:send_expire_soon_emails => :environment) do

在你的开发数据库上,运行这个任务,我将运行“rake utils:send_expire_soon_emails”。如果我想在产品数据库上运行,我将运行“rake RAILS_ENV=production utils:send_expire_soon_emails”。

如果我想每天午夜,在产品数据库上运行此任务,我将编写一个如下cronjob:

0 0 * * * cd /var/www/apps/rails_app/ 
&& /usr/local/bin/rake RAILS_ENV=production utils:send_expire_soon_emails

相当简便!

哪里可以找到更多的例子?

既然你已经知道如何开始编写rake任务,我想我应该给你一些资源。改善你编程最好的方法 是阅读他人的代码,一些人们已经些好的rake任务。

如果你找到了其它比较好的,发贴子给我们。

追加

作为追加,我几分钟以前受到Jim的电子邮件,解释我如何可以简化我的目录创建脚本,相当酷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# This is needed because the existing version of 
# directory in Rake is slightly broken, but Jim says
# it'll be fixed in the next version.
alias :original_directory :directory
def directory(dir)
original_directory dir
Rake::Task[dir]
end
 
# Do the directory creation
namespace :utils do
task :create_directories => [
directory('public/icons'),
directory('public/images'),
directory('public/groups'),
]
end






Rake简介

RakeMakeAnt

Rake的意思是Ruby Make,一个用ruby开 发的代码构建工具。Rake的英 文意思是耙子,一种很朴实的劳动工具。真的是很贴切,Rake正是一个功能强大、勤勤恳恳的劳动工具。

但是,为什么Ruby需要Rake

Ruby代码不需要编译,为什么需要Rake?其实,与其说Rake是一个代码构建工具,不如说Rake是一个任务管理工具,通过Rake我们可以得到两个好处:

  1. 以任务的方式创建 和运行脚本

当然,你可以用脚本来创建每一个你希望自动运行的任务。但是,对于大型的应用来说,你几乎 总是需要为数据库迁移(比如Railsdb:migrate任务)、清空缓存、或者代码维护等等编写脚本。对于每一项任务,你可能都需要写若干脚 本,这会让你的管理变得复杂。那么,把它们用任务的方式整理到一起,会让管理变得轻松很多。

  1. 追踪和管理任务之 间的依赖

Rake还提供了轻松管理任务之间依赖的方式。比如,“migrate”任务和“schemadump”任务都依赖于 “connect_to_database”任务,那么在“migrate”任务调用之前,“connect_to_database”任务都会被执行。

在哪里可以获得Rake

Rake的主页是在http://rake.rubyforge.org/,在这里你可以获得Rake的简单介绍,API以及一些有用文档的链接。可以在http://rubyforge.org/frs/?group_id=50获得最新版的Rake,在作者写作时,最新版本是0.7.3

Rake脚本编写

一个简单脚本

Rake的脚本相当简单,下面用一个例子进行说明。假设你是一个勤劳的家庭型程序员,在周末你打算 为你的家人做一些贡献。所以你为自己制定了三个任务:买菜、做饭和洗衣服。打开你的文本编辑器,创建一个名叫rakefile的文件(Rake会在当前路径下寻找名叫RakefilerakefileRakeFile.rbrakefile.rbrake文件),并输入如下内容:

desc "任务1 -- 买菜"
task :purchaseVegetables do
puts "
到沃尔玛去买菜。"
end

desc "
任务2 -- 做饭"
task :cook do
puts "
做一顿香喷喷的饭菜。"
end

desc "
任务3 -- 洗衣服"
task :laundry do
puts "
把所有衣服扔进洗衣机。"
end


打开命令行工具,进入这个文件所在目录,然后运行下面的命令,大致应该类似如下结果:

D:\work\ruby_works\ruby_book>rake purchaseVegetables
(in D:/work/ruby_works/ruby_book)
到沃尔玛去买菜。

D:\work\ruby_works\ruby_book>rake cook
(in D:/work/ruby_works/ruby_book)
做一顿香喷喷的饭菜。

D:\work\ruby_works\ruby_book>rake laundry
(in D:/work/ruby_works/ruby_book)
把所有衣服扔进洗衣机。


分析

很简单,也很易读,对吧。这个文件一共定义了3个任务,descRake定义的方法,表示对下面定义任务的描述。这个描述会在使用Rake --tasks(或者Rake -T,为懒人准备的快捷方式)命令时输出在屏幕上。

D:\work\ruby_works\ruby_book>rake --tasks
(in D:/work/ruby_works/ruby_book)
rake cook #
任务2 -- 做饭
rake laundry #
任务3 -- 洗衣服
rake purchaseVegetables #
任务1 -- 买菜


下面的语句定义了purchaseVegetables这个任务,taskRake最重要的方法。它的方法定义是:task(args, &block)。任务体是一个block,本例中只是简单输出你所要做的工作。需要注意的是代码

puts "到沃尔玛去买菜。"

完全是一个普通的Ruby语句,putsRuby中进行输出的一般性方法,可以看出,Rake任务可以完全使用Ruby的能力,这使得它非常强大。

加入依赖关系

很显然,在我们定义的任务中,做饭是依赖于买菜的(我相信大多数程序员在周末的冰箱里除了可乐没有别的)。那 么,我们需要在我们的任务定义中加入这个依赖关系,修改后的文件如下:

desc "任务1 -- 买菜"
task :purchaseVegetables do
puts "
到沃尔玛去买菜。"
end

desc "
任务2 -- 做饭"
task :cook => :purchaseVegetables do
puts "
做一顿香喷喷的饭菜。"
end

desc "
任务3 -- 洗衣服"
task :laundry do
puts "
把所有衣服扔进洗衣机。"
end


再次运行做饭任务,你会得到如下结果:

D:\work\ruby_works\ruby_book>rake cook
(in D:/work/ruby_works/ruby_book)
到沃尔玛去买菜。
做一顿香喷喷的饭菜。


是的,你当然需要先买菜,谁让你是一个冰箱空空如野的程序员呢。

命名空间

跟任何编程语言类似,当你的rake文件很多时,当你有很多任务的时候,你需要关注它们的命名冲突问题,命名空间(namespace)就是一个自然的解决方案。你可以为上面的三个任务定义一个叫做home的命名空间。

namespace :home do
desc "
任务1 -- 买菜"
task :purchaseVegetables do
puts "
到沃尔玛去买菜。"
end
……
end


再次运行rake --tasks,你会得到如下的结果:

D:\work\ruby_works\ruby_book >rake --tasks
(in D:/work/ruby_works/ruby_book)
rake home:cook #
任务2 -- 做饭
rake home:laundry #
任务3 -- 洗衣服
rake home:purchaseVegetables #
任务1 -- 买菜


你现在需要使用rake home:cook才能启动做饭这个任务了。当然,你可以在你的rakefile中使用多个命名空间,对任务进行分类。

在一个任务中调用另外一个任务

当任务众多的时候,你很可能需要在一个任务中调用另外一个任务,假设我们把今天所有要做的工作定义为一个任务:today。在这个任务中,有两个任务需要被调用,一个是做饭,一个是洗衣服。当然,由于做饭依赖 于买菜,我们还是需要买菜的(这一步是逃不过去的,呵呵)。在文件的顶部定义一个today的任务:

desc "今天的任务"
task :today do
Rake::Task["home:cook"].invoke
Rake::Task["home:laundry"].invoke
end

namespace :home do
……
end


可以看出,调用其它任务的方式很简单,只需要调用Rake::Task["task_name"].invoke 方法就可以了。在命令行中启动rake today,可以得到:

D:\work\ruby_works\ruby_book >rake today
(in D:/work/ruby_works/ruby_book)
到沃尔玛去买菜。
做一顿香喷喷的饭菜。
把所有衣服扔进洗衣机。


默认任务

可以为Rake增 加一个默认任务,这样可以简单地用Rake命 令来触发这个默认任务,在上面的rakefile中, 我们可以用如下方式把“today”任 务作为默认任务。

task :default => [:today]

然后调用直接在命令行中调用rake,可以得到跟调用rake today同样的输出结果。

这就是我们简单的一个Rake任务定义,下面是完整的修改后的rakefile

task :default => [:today]

desc "
今天的任务"
task :today do
Rake::Task["home:cook"].invoke
Rake::Task["home:laundry"].invoke
end

namespace :home do

desc "
任务1 -- 买菜"
task :purchaseVegetables do
puts "
到沃尔玛去买菜。"
end

desc "
任务2 -- 做饭"
task :cook => :purchaseVegetables do
puts "
做一顿香喷喷的饭菜。"
end

desc "
任务3 -- 洗衣服"
task :laundry do
puts "
把所有衣服扔进洗衣机。"
end
end


Rails中的Rake任务

Rails预定义了大量的Rake任务,在Rails应用的开发过程中,你想必已经在大量使用它们了。在Rails中,所有的Rake任务都放在rails目录的lib/tasks目录下(在作者的环境下是c:\ruby\lib\ruby\gems\1.8\gems\rails-1.1.4\lib\tasks\),所有的rake任务都以.rake作为后缀名,这些以.rake结尾的文件会被自动加载到你的环境中。你可以到一个已有的Rails工程根目录下键入rake --tasks,可以看到很多的rake任务已经为你整装待发了。

Rails中, 最常使用的Rake任务之一是进 行数据库的迁移(migration)。 数据库迁移程序允许你使用Ruby脚 本来定义数据库模式,而db:migrate就 是进行这个工作的rake任务。 下面我们来分析这个rake任 务。

db:migrate任务

db:migrate任务存放在lib/tasks/databases.rake文件中。这个文件中定义了所有与数据库操作相关的任务,我们仅仅抽出db:migrate的定义:

namespace :db do
desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
task :migrate => :environment do
ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
……
end



分析

首先是命名空间的声明,migrate任务的命名空间是db。这也就是我们用db:migrate来引用它的原因。

下面是一个描述,说明该任务的功能是把定义在db/migrate目录下(相对于你的Rails应用程序的根目录)的迁移脚本迁移到数据库中,如果不指定VERSION的话,默认是最新版本,否则可以恢复到一个指定的版本。

接着是任务的定义,该任务依赖于enviroment任务,这个任务在misc.rake中定义,用来加载Rails的环境,它的定义相当简单:

task :environment do
require(File.join(RAILS_ROOT, 'config', 'environment'))
end


用来加载config/environment.rb文件,该文件会加载Rails工作所需要加载的环境。由于加载了这个环境,所以ActiveRecord对象现在可以使用,下面就是调用ActiveRecord::Migrator.migrate方法对每个db/migrate/下的脚本文件进行迁移。

最后会调用db:schema:dump任务,该任务的主要作用是产生db/schema.rb文件。该文件用来记录不同版本的数据库模式。这个任务的定义就在db:migrate任务下面不远的地方,有兴趣的读者可以自行进行分析。
  评论这张
 
阅读(2335)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018