classPost<ActiveRecord::Basehas_many:commentsendclassComments<ActiveRecord::Basebelongs_to:postend# routes.rbresources:postsdoresources:commentsend使用命名空间路由来群组相关的行为。namespace:admindo# Directs /admin/products/* to Admin::ProductsController# (app/controllers/admin/products_controller.rb)resources:productsend不要使用合法的疯狂路由。这种路由会让每个控制器的动作透过GET请求存取。# very badmatch':controller(/:action(/:id(.:format)))'
控制器
让你的控制器保持苗条 ― 它们应该只替视图层取出数据且不包含任何业务逻辑(所有业务逻辑应理所当然地放在模型里)。
每个控制器的行动应当(理想上)只调用一个除了初始的 find 或 new 方法
控制器与视图之间共享不超过两个实例变数
# 差classPersonvalidates:email,format:{with:/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i}end# 好classEmailValidator<ActiveModel::EachValidatordefvalidate_each(record,attribute,value)record.errors[attribute]<<(options[:message]||'is not a valid email')unlessvalue=~/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/iendendclassPersonvalidates:email,email:trueend
所有惯用的验证器应放在一个共享的 gem 。
自由地使用命名的作用域。
当一个由 lambda 及参数定义的作用域变得过于复杂时,更好的方式是建一个作为同样用途的类别方法,并返回 ActiveRecord::Relation 对象。
注意 update_attribute 方法的行为。它不运行模型验证(不同于 update_attributes )并且可能把模型状态给搞砸。
使用用户友好的网址。在网址显示具描述性的模型属性,而不只是 id 。
有不止一种方法可以达成:
覆写模型的 to_param 方法。这是 Rails 用来给对象建构网址的方法。缺省的实作会以字串形式返回该 id 的记录。它可被另一个人类可读的属性覆写。
class Person
def to_param
"#{id} #{name}".parameterize
end
end
为了要转换成对网址友好 (URL-friendly)的数值,字串应当调用 parameterize 。 对象的 id 要放在开头,以便给 ActiveRecord 的 find 方法查找。
使用此 friendly_id gem。它允许藉由某些具描述性的模型属性,而不是用 id 来创建人类可读的网址。
class Person
extend FriendlyId
friendly_id :name, use: :slugged
end
查看 gem 文档 获得更多关于使用的信息。
# 过去的方式classAddNameToPerson<ActiveRecord::Migrationdefupadd_column:persons,:name,:stringenddefdownremove_column:person,:nameendend# 新的偏好方式classAddNameToPerson<ActiveRecord::Migrationdefchangeadd_column:persons,:name,:stringendend视图不要直接从视图调用模型层。不要在视图构造复杂的格式,把它们输出到视图helper的一个方法或是模型。使用partial模版与布局来减少重复的代码。加入clientsidevalidation至惯用的validators。要做的步骤有:声明一个由ClientSideValidations::Middleware::Base而来的自定validatormoduleClientSideValidations::MiddlewareclassEmail<Basedefresponseifrequest.params[:email]=~/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/iself.status=200elseself.status=404endsuperendendend建立一个新文件public/javascripts/rails.validations.custom.js.coffee并在你的application.js.coffee文件加入一个它的参照:# app/assets/javascripts/application.js.coffee#= require rails.validations.custom添加你的用户端validator:#public/javascripts/rails.validations.custom.js.coffeeclientSideValidations.validators.remote['email']=(element,options)->if$.ajax({url:'/validators/email.json',data:{email:element.val()},async:false}).status==404returnoptions.message||'invalid e-mail format'国际化视图、模型与控制器里不应使用语言相关设置与字串。这些文字应搬到在config/locales下的语言文件里。当ActiveRecord模型的标签需要被翻译时,使用activerecord作用域:en:activerecord:models:user:Memberattributes:user:name:"Full name"然后User.model_name.human会返回"Member",而User.human_attribute_name("name")会返回"Full name"。这些属性的翻译会被视图作为标签使用。把在视图使用的文字与ActiveRecord的属性翻译分开。把给模型使用的语言文件放在名为models的文件夹,给视图使用的文字放在名为views的文件夹。当使用额外目录的语言文件组织完成时,为了要载入这些目录,要在application.rb文件里描述这些目录。# config/application.rbconfig.i18n.load_path+=Dir[Rails.root.join('config','locales','**','*.{rb,yml}')]把共享的本土化选项,像是日期或货币格式,放在locales的根目录下。使用精简形式的I18n方法:I18n.t来取代I18n.translate以及使用I18n.l取代I18n.localize.使用"懒惰"查询视图中使用的文字。假设我们有以下结构:en:users:show:title:"User details page"users.show.title的数值能这样被app/views/users/show.html.haml查询:=t'.title'在控制器与模型使用点分隔的键,来取代指定:scope选项。点分隔的调用更容易阅读及追踪层级。# 这样子调用使用I18n.t'activerecord.errors.messages.record_invalid'# 而不是这样I18n.t:record_invalid,:scope=>[:activerecord,:errors,:messages]关于Railsi18n更详细的信息可以在这里找到RailsGuides。Assets利用这个assetspipeline来管理应用的结构。保留app/assets给自定的样式表,javascripts,or图片.第三方代码如:jQuery或bootstrap应放置在vendor/assets。当可能的时候,使用gem化的assets版本。(如:jquery-rails).Mailers把mails命名为SomethingMailer。没有Mailer字根,不能立即显现哪个是一个mailer,以及哪个视图与它有关。提供HTML与纯文本视图模版。在你的开发环境启用信件失败发送错误。这些错误缺省是被停用的。# config/environments/development.rbconfig.action_mailer.raise_delivery_errors=true在开发模式使用smtp.gmail.com设置SMTP服务器(当然了,除非你自己有本地SMTP服务器)。# config/environments/development.rbconfig.action_mailer.smtp_settings={address:'smtp.gmail.com',# 更多设置}提供缺省的配置给主机名。# config/environments/development.rbconfig.action_mailer.default_url_options={host:"#{local_ip}:3000"}# config/environments/production.rbconfig.action_mailer.default_url_options={host:'your_site.com'}# 在你的 mailer 类default_url_options[:host]='your_site.com'如果你需要在你的网站使用一个email链结,总是使用_url方法,而不是_path方法。_url方法包含了主机名,而_path方法没有。# 错误Youcanalwaysfindmoreinfoaboutthiscourse=link_to'here',url_for(course_path(@course))# 正确Youcanalwaysfindmoreinfoaboutthiscourse=link_to'here',url_for(course_url(@course))正确地显示寄与收件人地址的格式。使用下列格式:# 在你的 mailer 类别defaultfrom:'Your Name <info@your_site.com>'确定测试环境的email发送方法设置为test:# config/environments/test.rbconfig.action_mailer.delivery_method=:test开发与生产环境的发送方法应为smtp:# config/environments/development.rb, config/environments/production.rbconfig.action_mailer.delivery_method=:smtp当发送HTMLemail时,所有样式应为行内样式,由于某些用户有关于外部样式的问题。某种程度上这使得更难管理及造成代码重用。有两个相似的gem可以转换样式,以及将它们放在对应的html标签里:premailer-rails3和roadie。应避免页面产生响应时寄送email。若多个email寄送时,造成了页面载入延迟,以及请求可能逾时。使用delayed_jobgem的帮助来克服在背景处理寄送email的问题。Bundler把只给开发环境或测试环境的gem适当地分组放在Gemfile文件中。在你的项目中只使用公认的gem。如果你考虑引入某些显为人所知的gem,你应该先仔细复查一下它的源代码。关于多个开发者使用不同操作系统的项目,操作系统相关的gem缺省会产生一个经常变动的Gemfile.lock。在Gemfile文件里,所有与OSX相关的gem放在darwin群组,而所有Linux相关的gem放在linux群组:# Gemfilegroup:darwindogem'rb-fsevent'gem'growl'endgroup:linuxdogem'rb-inotify'end要在对的环境获得合适的gem,添加以下代码至config/application.rb:platform=RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_symBundler.require(platform)不要把Gemfile.lock文件从版本控制里移除。这不是随机产生的文件-它确保你所有的组员执行bundleinstall时,获得相同版本的gem。无价的Gems一个最重要的编程理念是"不要重造轮子!"。若你遇到一个特定问题,你应该要在你开始前,看一下是否有存在的解决方案。下面是一些在很多Rails项目中"无价的"gem列表(全部兼容Rails3.1):active_admin-有了ActiveAdmin,创建Rails应用的管理介面就像儿戏。你会有一个很好的仪表盘,图形化CRUD介面以及更多东西。非常灵活且可客制。capybara-Capybara指在简化整合测试Rack应用的过程,像是Rails、Sinatra或Merb。Capybara模拟了真实用户使用web应用的互动。它与你测试在运行的驱动无关,并原生搭载Rack::Test及Selenium支持。透过外部gem支持HtmlUnit、WebKit及env.js。与RSpec&Cucumber一起使用工作良好。carrierwave-Rails最后一个文件上传解决方案。支持上传档案(及很多其它的酷玩意儿的)的本地储存与云储存。图片后处理与ImageMagick整合得非常好。client_side_validations-一个美妙的gem,替你从现有的服务器端模型验证自动产生Javascript用户端验证。高度推荐!compass-rails-一个优秀的gem,添加了某些css框架的支持。包括了sassmixin的蒐集,让你减少css文件的代码并帮你解决浏览器兼容问题。cucumber-rails-Cucumber是一个由Ruby所写,开发功能测试的顶级工具。cucumber-rails提供了Cucumber的Rails整合。devise-Devise是Rails应用的一个完整解决方案。多数情况偏好使用devise来开始你的客制验证方案。fabrication-一个很好的假数据产生器(编辑者的选择)。factory_girl-另一个fabrication的选择。一个成熟的假数据产生器。Fabrication的精神领袖先驱。faker-实用的gem来产生仿造的数据(名字、地址,等等)。feedzirra-非常快速及灵活的RSS/Atom种子解析器。friendly_id-透过使用某些具描述性的模型属性,而不是使用id,允许你创建人类可读的网址。guard-极佳的gem监控文件变化及任务的调用。搭载了很多实用的扩充。远优于autotest与watchr。haml-rails-haml-rails提供了Haml的Rails整合。haml-Haml是一个简洁的模型语言,被很多人认为(包括我)远优于Erb。kaminari-很棒的分页解决方案。machinist-假数据不好玩,Machinist才好玩。rspec-rails-RSpec是Test::MiniTest的取代者。我不高度推荐RSpec。rspec-rails提供了RSpec的Rails整合。simple_form-一旦用过simple_form(或formatastic),你就不想听到关于Rails缺省的表单。它是一个创造表单很棒的DSL。simplecov-rcov-为了SimpleCov打造的RCovformatter。若你想使用SimpleCov搭配Hudson持续整合服务器,很有用。simplecov-代码覆盖率工具。不像RCov,完全兼容Ruby1.9。产生精美的报告。必须用!slim-Slim是一个简洁的模版语言,被视为是远远优于HAML(Erb就更不用说了)的语言。唯一会阻止我大规模地使用它的是,主流IDE及编辑器的支持不好。它的效能是非凡的。spork-一个给测试框架(RSpec或现今Cucumber)用的DRb服务器,每次运行前确保分支出一个乾净的测试状态。简单的说,预载很多测试环境的结果是大幅降低你的测试启动时间,绝对必须用!sunspot-基于SOLR的全文检索引擎。这不是完整的清单,以及其它的gem也可以在之后加进来。以上清单上的所有gems皆经测试,处于活跃开发阶段,有社群以及代码的质量很高。缺陷的Gems这是一个有问题的或被别的gem取代的gem清单。你应该在你的项目里避免使用它们。rmagick-这个gem因大量消耗内存而声名狼藉。使用minimagick来取代。autotest-自动测试的老解决方案。远不如guard及watchr。rcov-代码覆盖率工具,不兼容Ruby1.9。使用SimpleCov来取代。therubyracer-极度不鼓励在生产模式使用这个gem,它消耗大量的内存。我会推荐使用Mustang来取代。这仍是一个完善中的清单。请告诉我受人欢迎但有缺陷的gems。管理进程若你的项目依赖各种外部的进程使用foreman来管理它们。测试Rails应用也许BDD方法是实作一个新功能最好的方法。你从开始写一些高阶的测试(通常使用Cucumber),然后使用这些测试来驱使你实作功能。一开始你给功能的视图写测试,并使用这些测试来创建相关的视图。之后,你创建丢给视图数据的控制器测试来实现控制器。最后你实作模型的测试以及模型自身。Cucumber用@wip(工作进行中)标签标记你未完成的场景。这些场景不纳入考虑,且不标记为测试失败。当完成一个未完成场景且功能测试通过时,为了把此场景加至测试套件里,应该移除@wip标签。配置你的缺省配置文件,排除掉标记为@javascript的场景。它们使用浏览器来测试,推荐停用它们来增加一般场景的执行速度。替标记著@javascript的场景配置另一个配置文件。配置文件可在cucumber.yml文件里配置。# 配置文件的定义:profile_name:--tags@tag_name带指令运行一个配置文件:cucumber-pprofile_name若使用fabrication来替换假数据(fixtures),使用预定义的fabricationsteps。不要使用旧版的web_steps.rb步骤定义!最新版Cucumber已移除websteps,使用它们导致冗赘的场景,而且它并没有正确地反映出应用的领域。当检查一元素的可视文字时,检查元素的文字而不是检查id。这样可以查出i18n的问题。给同种类对象创建不同的功能特色:# 差Feature:Articles# ... 特色实作 ...# 好Feature:ArticleEditing# ... 特色实作 ...Feature:ArticlePublishing# ... 特色实作 ...Feature:ArticleSearch# ... 特色实作 ...每一个特色有三个主要成分:TitleNarrative-简短说明这个特色关于什么。Acceptancecriteria-每个由独立步骤组成的一套场景。最常见的格式称为Connextra格式。Inorderto[benefit]...A[stakeholder]...Wantsto[feature]...这是最常见但不是要求的格式,叙述可以是依赖功能复杂度的任何文字。自由地使用场景概述使你的场景备作它用(keepyourscenariosDRY)。ScenarioOutline:Usercannotregisterwithinvalide-mailWhenItrytoregisterwithanemail"<email>"ThenIshouldseetheerrormessage"<error>"Examples:|email|error|||Thee-mailisrequired||invalidemail|isnotavalide-mail|场景的步骤放在step_definitions目录下的.rb文件。步骤文件命名惯例为[description]_steps.rb。步骤根据不同的标准放在不同的文件里。每一个功能可能有一个步骤文件(home_page_steps.rb)。也可能给每个特定对象的功能,建一个步骤文件(articles_steps.rb)。使用多行步骤参数来避免重复场景:UserprofileGivenIamloggedinasauser"John Doe"withane-mail"user@test.com"WhenIgotomyprofileThenIshouldseethefollowinginformation:|Firstname|John||Lastname|Doe||E-mail|user@test.com|# 步骤:Then/^I should see the following information:$/do|table|table.raw.eachdo|field,value|find_field(field).value.should=~/#{value}/endend使用复合步骤使场景备作它用(KeepyourscenariosDRY)# ...WhenIsubscribefornewsfromthecategory"Technical News"# ...# the step:When/^I subscribe for news from the category "([^"]*)"$/do|category|steps%Q{ When I go to the news categories page And I select the category #{category} And I click the button "Subscribe for this category" And I confirm the subscription }end总是使用Capybara否定匹配来取代正面情况搭配should_not,它们会在给定的超时时重试匹配,允许你测试ajax动作。见Capybara的读我文件获得更多说明。RSpec一个例子仅用一个期望值。# 差describeArticlesControllerdo#...describe'GET new'doit'assigns new article and renders the new article template'doget:newassigns[:article].shouldbe_a_newArticleresponse.shouldrender_template:newendend# ...end# 好describeArticlesControllerdo#...describe'GET new'doit'assigns a new article'doget:newassigns[:article].shouldbe_a_newArticleendit'renders the new article template'doget:newresponse.shouldrender_template:newendendend大量使用descibe及context。如下地替describe区块命名:非方法使用"description"实例方法使用井字号"#method"类别方法使用点".method"classArticledefsummary#...enddefself.latest#...endend# the spec...describeArticledescribe'#summary'#...enddescribe'.latest'#...endend使用fabricators来创建测试对象。大量使用mocks与stubs。# mocking 一个模型article=mock_model(Article)# stubbing a methodArticle.stub(:find).with(article.id).and_return(article)当mocking一个模型时,使用as_null_object方法。它告诉输出仅监听我们预期的讯息,并忽略其它的讯息。article=mock_model(Article).as_null_object使用let区块而不是before(:all)区块替spec例子创建数据。let区块会被懒惰求值。# 使用这个:let(:article){Fabricate(:article)}# ... 而不是这个:before(:each){@article=Fabricate(:article)}当可能时,使用subject。describeArticledosubject{Fabricate(:article)}it'is not published on creation'dosubject.should_notbe_publishedendend如果可能的话,使用specify。它是it的同义词,但在没docstring的情况下可读性更高。# 差describeArticledobefore{@article=Fabricate(:article)}it'is not published on creation'do@article.should_notbe_publishedendend# 好describeArticledolet(:article){Fabricate(:article)}specify{article.should_notbe_published}end当可能时,使用its。# 差describeArticledosubject{Fabricate(:article)}it'has the current date as creation date'dosubject.creation_date.should==Date.todayendend# 好describeArticledosubject{Fabricate(:article)}its(:creation_date){should==Date.today}end视图视图测试的目录结构要与app/views之中的相符。举例来说,在app/views/users视图被放在spec/views/users。视图测试的命名惯例是添加_spec.rb至视图名字之后,举例来说,视图_form.html.haml有一个对应的测试叫做_form.html.haml_spec.rb。每个视图测试文件都需要spec_helper.rb。外部描述区块使用不含app/views部分的视图路径。render方法没有传入参数时,是这么使用的。
ruby
# spec/views/articles/new.html.haml_spec.rb
require 'spec_helper'
describe 'articles/new.html.haml' do
# ...
end
# spec/views/articles/edit.html.haml_spec.rb
describe 'articles/edit.html.haml' do
it 'renders the form for a new article creation' do
assign(
:article,
mock_model(Article).as_new_record.as_null_object
)
render
rendered.should have_selector('form',
method: 'post',
action: articles_path
) do |form|
form.should have_selector('input', type: 'submit')
end
end
# app/helpers/articles_helper.rb
class ArticlesHelper
def formatted_date(date)
# ...
end
end
# app/views/articles/show.html.haml
= "Published at: #{formatted_date(@article.published_at)}"
# spec/views/articles/show.html.haml_spec.rb
describe 'articles/show.html.html' do
it 'displays the formatted date of article publishing'
article = mock_model(Article, published_at: Date.new(2012, 01, 01))
assign(:article, article)
template.stub(:formatted_date).with(article.published_at).and_return '01.01.2012'
render
rendered.should have_content('Published at: 01.01.2012')
end
end
# 常用的控制器 spec 示例
# spec/controllers/articles_controller_spec.rb
# 我们只对控制器应执行的动作感兴趣
# 所以我们 mock 模型及 stub 它的方法
# 并且专注在控制器该做的事情上
describe ArticlesController do
# The model will be used in the specs for all methods of the controller
let(:article) { mock_model(Article) }
describe 'POST create' do
before { Article.stub(:new).and_return(article) }
it 'creates a new article with the given attributes' do
Article.should_receive(:new).with(title: 'The New Article Title').and_return(article)
post :create, message: { title: 'The New Article Title' }
end
it 'saves the article' do
article.should_receive(:save)
post :create
end
it 'redirects to the Articles index' do
article.stub(:save)
post :create
response.should redirect_to(action: 'index')
end
end
end
describe ArticlesController do
let(:article) { mock_model(Article) }
describe 'POST create' do
before { Article.stub(:new).and_return(article) }
it 'creates a new article with the given attributes' do
Article.should_receive(:new).with(title: 'The New Article Title').and_return(article)
post :create, article: { title: 'The New Article Title' }
end
it 'saves the article' do
article.should_receive(:save)
post :create
end
context 'when the article saves successfully' do
before { article.stub(:save).and_return(true) }
it 'sets a flash[:notice] message' do
post :create
flash[:notice].should eq('The article was saved successfully.')
end
it 'redirects to the Articles index' do
post :create
response.should redirect_to(action: 'index')
end
end
context 'when the article fails to save' do
before { article.stub(:save).and_return(false) }
it 'assigns @article' do
post :create
assigns[:article].should be_eql(article)
end
it 're-renders the "new" template' do
post :create
response.should render_template('new')
end
end
end
end
describe Article
let(:article) { Fabricate(:article) }
end
加入一个例子确保捏造的模型是可行的。 Add an example ensuring that the fabricated model is valid.
describe Article
it 'is valid with valid attributes' do
article.should be_valid
end
end
# 差
describe '#title'
it 'is required' do
article.title = nil
article.should_not be_valid
end
end
# 偏好
describe '#title'
it 'is required' do
article.title = nil
article.should have(1).error_on(:title)
end
end
describe Article
describe '#title'
it 'is unique' do
another_article = Fabricate.build(:article, title: article.title)
article.should have(1).error_on(:title)
end
end
end
# rspec/uploaders/person_avatar_uploader_spec.rb
require 'spec_helper'
require 'carrierwave/test/matchers'
describe PersonAvatarUploader do
include CarrierWave::Test::Matchers
# 在执行例子前启用图片处理
before(:all) do
UserAvatarUploader.enable_processing = true
end
# 创建一个新的 uploader。模型被模仿为不依赖建模时的上传及调整图片。
before(:each) do
@uploader = PersonAvatarUploader.new(mock_model(Person).as_null_object)
@uploader.store!(File.open(path_to_file))
end
# 执行完例子时停用图片处理
after(:all) do
UserAvatarUploader.enable_processing = false
end
# 测试图片是否不比给定的维度长
context 'the default version' do
it 'scales down an image to be no larger than 256 by 256 pixels' do
@uploader.should be_no_larger_than(256, 256)
end
end
# 测试图片是否有确切的维度
context 'the thumb version' do
it 'scales down an image to be exactly 64 by 64 pixels' do
@uploader.thumb.should have_dimensions(64, 64)
end
end
end
```
原文转自: http://www.cnblogs.com/hedgehog-ZDH/archive/2012/11/16/2773749.html