infoq_InfoQ书摘:面向Java开发人员的Rails

infoq

Ruby和Rails为Web开发带来了很多好主意。 关于Ruby和Rails的书籍很多,但是Java开发人员的Rails带来了独特的视角。 我们不是从头开始,而是建立在各地Java开发人员共享知识的基础上。

以Java知识为基础,使我们可以进行比较。 在整本书中,我们将Rails示例与Java Web框架示例进行了比较,以便您可以快速看到相似之处,并特别注意它们之间的区别。

例如,考虑一种管理保存简单模型对象的控制器方法。 大多数Java框架与Rails之间的最大区别在于,Java框架在每个控制器方法上都显式创建了许多对象模型。 相比之下,在Rails中,许多Web对象模型都是隐式的。 在Rails中,除非需要,否则您不会看到它。

摘录1:控制器保存方法

这是一种Struts动作方法,可以保存或更新一个人:

代码/ appfuse_people / src / web / com / relevancellc / people / webapp / action / PersonAction.java
public ActionForward save(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
ActionMessages messages =new ActionMessages();
PersonForm personForm = (PersonForm) form;
boolean isNew = ("".equals(personForm.getId()));
PersonManager mgr = (PersonManager) getBean("personManager");
Person person = (Person) convert(personForm);
mgr.savePerson(person);
if(isNew) {
messages.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("person.added"));
saveMessages(request.getSession(), messages);
return mapping.findForward("mainMenu");
} else {
messages.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("person.updated"));
saveMessages(request, messages);
return mapping.findForward("viewPeople");
}
}

让我们从考虑用户编辑成功的快乐案例开始。 该代码中的大部分与之前的示例相似; 新的部分是添加状态消息。 在第5行中,我们创建一个ActionMessages实例来保存状态消息,在第12--14和17--19行中,我们将ActionMessages保存到请求中,以便可以在视图中呈现它们。

这是更新的Rails版本:

代码/人员/应用程序/控制器/people_controller.rb
def update
@person = Person.find(params[:id])
if @person.update_attributes(params[:person])
flash[:notice] = 'Person was successfully updated.'
redirect_to :action =>'show', :id => @person
else
render :action =>'edit'
end
end

实际的更新发生在第3行。update_attributes是一个ActiveRecord方法,可以一次设置多个属性。 就像其表亲createsave一样update_attributes自动执行验证。 由于params [:person]哈希包含输入表单中的所有名称/值对,因此对update_attributes的单个调用即可完成更新@person实例所需的一切。

与Struts更新一样,Rails版本的更新会设置状态消息。 在第4行中,消息“人员已成功更新”。 被添加到称为flash的特殊对象中。 闪存旨在处理通常在更新之后进行重定向的事实。 因此,将状态保存到成员变量中并没有好处-重定向后,状态变量将丢失。 而是可以保存到会话中,但是您必须记住以后要从会话中删除状态消息。 这正是Flash的功能:将对象保存到会话中,然后在下一次重定向后自动删除状态消息。

闪光灯是一个聪明的把戏。 不幸的是,通常放入闪存中的数据根本不够灵巧。 开箱即用,Rails不支持国际化,状态消息直接存储为字符串(通常为英语)。 与Struts应用程序进行对比,该应用程序存储诸如“ person.added”之类的键。 该视图以后可以使用这些键来查找适当本地化的字符串。 缺乏国际化支持是Rails中缺少的主要内容之一。 如果您的应用程序需要国际化,则必须自己滚动或使用第三方库。

成功执行更新操作后,控制器应重定向到执行读取操作的URL。 这使得用户不太可能将进行更新的URL标记为书签,这将在以后导致奇怪的结果。 一些可能的选择是刚编辑的对象的显示视图,相似对象的列表视图或顶级视图。 Struts版本通过调用findForward进行重定向:

return mapping.findForward("mainMenu");

要验证此转发是否进行重定向,可以查阅struts.xml配置文件。 一切看起来都不错:

<global-forwards>
<forward name="mainMenu" path="/mainMenu.html" redirect="true"/>
<!-- etc. -->
</global-forwards>

在Struts使用findForward进行渲染和重定向的地方,Rails有两个单独的方法。 保存后,控制器发出显式重定向:

redirect_to :action => 'show', :id => @person

请注意,重定向是根据操作和参数命名的。 Rails向后运行其路由表,以将操作和参数转换回URL。 使用默认路由时,此URL为/ people / show /(some_int)。

既然您已经看到了成功的更新,我们将展示更新失败的情况。 Struts和Rails都提供了验证用户输入的机制。 在Struts中,Validator对象根据XML文件中的声明性设置自动验证表单bean。 验证与表单关联。 要指定名字是必需的,可以使用如下所示的XML:

代码/appfuse_people/snippets/person_form.xml
<form name="personForm">
<field property="firstName" depends="required">
<arg0 key="personForm.firstName"/>
</field>
<!-- other fields -->
</form>

单独的验证语言的初衷是关注点分离。 有时将相关问题放在一起比较方便。 无需手动编写validation.xml文件,而是在Person模型类中使用XDoclet注释生成验证:

代码/appfuse_people/src/dao/com/relevancellc/people/model/Person.java
/**
* @hibernate.property column="first_name" length="50"
* @struts.validator type="required"
*/
public String getFirstName() {
return firstName;
}

在Ant构建步骤中, struts.validator批注会在validation.xml文件中生成相应的行。 (在Java 5和更高版本中,注释提供了更简单,更集成的注释机制。)

在Rails中,没有单独的Form Bean,并且验证直接在Person模型类上声明。 您已经在第4.5节“ 验证数据值”中看到了这一点。

代码/人员/应用程序/模型/person.rb
class Person < ActiveRecord::Base
validates_presence_of :first_name, :last_name
end

Struts版本和Rails版本都以相同的方式处理验证错误:再次渲染页面,并显示错误消息,标记需要更正的表单字段。 在Struts中,此重定向在验证器中处理。 诸如PersonForm之类的表单bean扩展了Struts类org.apache.struts.validator.ValidatorFormValidatorForm类提供了一个validate方法。 Struts框架自动调用验证 ,如果任何项目通过验证失败,将再次呈现表单页面。

Rails方法更为明确。 当您在ActiveRecord模型上调用saveupdate_attributes时,布尔值false可能表示验证失败。 如果发生这种情况,可以使用render再次渲染edit操作:

代码/人员/代码段/update_fails.rb
if @person.update_attributes(params[:person])
# ...success case elided...
else
render :action => 'edit'
end

验证错误存储在@person对象的errors属性中,因此您无需执行其他任何操作即可将错误传递给表单视图。 第6.5节“ 构建HTML表单 ”描述了如何在视图中呈现验证。

通过比较上面部分中类似的Ruby代码和Java代码部分,可以通过示例快速了解Ruby语法。 这是有用的,但也很危险,因为Ruby是一种非常不同的语言。 (如果您曾经阅读过用C口音编写的Java代码,那么您将确切地知道这里存在的危险!)在Rails中有效的一个重要部分是对Ruby语言有透彻的了解。 因此,我们在纯Ruby上花了本书的两章。 下一个摘录显示了Ruby语言最重要,最强大的优势之一:对核心类进行修改的能力。

摘录2:扩展核心类

程序员经常需要将方法添加到语言运行时本身的一部分中。 在这种情况下,通常不能选择对类进行子类化,因为该方法需要基类本身的实例可用。 例如,Java和Ruby都没有一种方法来判断String是空的,换句话说,是null,空还是空白。 空白测试方法很有用,因为许多应用程序都希望以相同的方式处理所有空白输入。 对于Java和Ruby,开源社区都提供了测试空白的方法。 这是来自Apache Commons Lang的isBlank()的Java实现:

代码/语言/IsBlank.java
public class StringUtils {
public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if ((Character.isWhitespace(str.charAt(i)) == false)) {
return false;
}
}
return true;
}
}

由于不能将方法添加到核心类中,因此Commons Lang使用标准的Java习惯用法,将扩展方法作为静态方法收集在另一个类中。 isBlank()的实现位于StringUtils类中。

isBlank()的调用者在每个调用之前都添加了助手类名称StringUtils

代码/java_xt/src/TestStringUtils.java
import junit.framework.TestCase;
import org.apache.commons.lang.StringUtils;

public class TestStringUtils extends TestCase {
public void testIsBlank() {
assertTrue(StringUtils.isBlank(" "));
assertTrue(StringUtils.isBlank(""));
assertTrue(StringUtils.isBlank(null));
assertFalse(StringUtils.isBlank("x"));
}
}

Ruby类是开放的 ---您可以随时对其进行修改。 那么,Ruby的方法是添加空白吗?String ,就像Rails那样:

代码/rails/activesupport/lib/active_support/core_ext/blank.rb
class String
def blank?
empty? || strip.empty?
end
end

这里有一些空白的电话吗?

代码/rails_xt/test/examples/blank_test.rb
require File.dirname(__FILE__) + '/../test_helper'

class BlankTest < Test::Unit::TestCase
def test_blank
assert "".blank?
assert " ".blank?
assert nil.blank?
assert !"x".blank?
end
end

那空值呢?

Java版本的isBlank()使用辅助类StringUtils是第二个原因。 即使可以将String方法isBlank()挂在String上 ,在Java中,您也不想这样做。 调用isBlank()需要为null String返回false 。 在Java中,对null调用任何方法都将导致NullPointerException 。 通过测试静态StringUtils方法的第一个参数,可以避免尝试编写String方法(毫无意义地) 将此方法null进行比较的陷阱。 为什么Ruby方法也不能这样工作?

Ruby nil是一个对象

Ruby相当于Java null的值为nil 。 但是, nil是实际对象。 您可以像其他任何对象一样在nil上调用方法。 对于手头的任务而言,更重要的是,您可以像其他任何对象一样向nil 添加方法:以下代码会导致nil.blank? 返回true

代码/rails/activesupport/lib/active_support/core_ext/blank.rb
class NilClass #:nodoc:
def blank?
true
end
end

Rails提供了空白的合理定义 对于其他几个对象也是如此: truefalse ,空数组或哈希,数字类型,甚至是Object类。

有数十种语言和数百种Web框架。 真正使Java与众不同的是整个生态系统。 当我们开始一个新的Java项目时,我们知道我们遇到的任何问题都有很大的机会已经在开源Java世界中得到解决。 在考虑诸如Rails之类的任何新框架时,了解周围的生态系统非常重要。 是否有好的IDE? 自动化测试和持续集成又如何呢? 是否有用于每个常见编程任务的标准库?

Java具有最大的生态系统,因此您可以预期会遇到挫折感而转移到任何其他语言。 但是我们发现Ruby生态系统比我们预期的要丰富得多。 在下一个摘录中,我们将研究Ruby对自动测试的支持,Rails扩展了它对Web控制器的测试。

摘录3:Rails扩展测试:: Unit

开始使用Rails的Test :: Unit扩展的最简单方法是查看使用Rails支架免费获得的测试:

$ script/generate scaffold Person

在第1.2节“ Rails应用中的15分钟内”中检查了大多数脚手架代码。 在这里,我们将集中于一个生成的文件,即功能测试test / functional / people_controller_test.rb 。 我们将逐行将PeopleControllerTest类拆开。 首先,测试包括固定装置:

代码/rails_xt/test/functional/people_controller_test.rb
fixtures :people, :users

脚手架生成器假定PeopleController与人打交道 ,并相应地设置固定装置。 除了最琐碎的应用程序之外,所有其他应用程序都将发现控制器有时会与多个模型类进行交互。 发生这种情况时,只需将更多其他模型添加到灯具生产线。 例如:

fixtures :people, :widgets, :thingamabobs, :sheep

接下来是设置方法:

def setup
@controller = PeopleController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end

几乎所有功能测试都模拟一个(或多个)Web请求/响应周期。 因此,为每个测试实例化@request@response变量。

现在进行真正的测试。 支架生成一个索引页面,该页面仅呈现模型内容的列表视图。 这是索引页的测试:

def test_index
get :index
assert_response :success
assert_template 'list'
end

首先, get()方法在控制器上模拟HTTP GET。 此处看到的单参数版本指定了Rails动作名称。 然后,Rails断言assert_response:success断言该响应是成功的,即HTTP状态200。Rails断言assert_template'list'断言该响应是从列表模板呈现的。

作为Java程序员,我们很想问:“对象在哪里?” 也许test_index()应该看起来更像以下带有显式对象的代码:

# hypothetical, with explicit objects
@controller.get :index
assert_equal :success, @response.status
assert_equal 'list', @response.template

前面的两个示例在功能上是等效的。 区别是风格之一。 在Java中,我们倾向于将对象明确化。 在Ruby中,尤其是在Rails中,我们更喜欢在可能的情况下将“显而易见的”事物隐含。 尝试大声阅读两个版本,以更好地了解两者之间的差异。

接下来,脚手架测试列表操作:

def test_list
get :list
assert_response :success
assert_template 'list'
assert_not_nil assigns(:people)
end

大部分代码都是test_index()所熟悉的。 新颖的部分如下:

assert_not_nil assigns(:people)

Assigns变量很特殊。 如果在控制器中创建实例变量,则该变量将神奇地可用于您的视图模板。 魔术实际上很简单:Rails使用反射将控制器变量复制到集合中,然后将其复制回到视图实例中。 该集合被命名为Assigns ,因此先前的断言可以读为“断言控制器创建了一个名为people的非空变量”。

接下来,脚手架测试表演动作:

def test_show
get :show, :id => 1
assert_response :success
assert_template 'show'
assert_not_nil assigns(:person)
assert assigns(:person).valid?
end

该测试看起来有些不同,因为show方法要求特定的人显示。 Rails的默认行为是通过向URL添加id来标识特定的模型实例,因此对get()的调用包括第二个参数来传递人的id

get :show, :id => 1

get()的一般形式可以处理请求的任何可能的上下文:

get(action=nil, parameters=nil, session=nil, flash=nil)

我们如何确定一个ID为1的人存在? 查看灯具文件test / fixtures / people.yml

代码/rails_xt/test/fixtures/people.yml
first:
id: 1
first_name: Stuart
last_name: Halloway

test_show()中的另一个新颖之处是有效的?()测试:

assert assigns(:person).valid?

这只是ActiveRecord的标准验证支持,已在第4.5节“ 验证数据值”中进行了讨论。 当您向Person类添加验证方法时,对valid?()的调用将自动变得更智能。

脚手架的test_new()没有引入任何新概念,因此我们将其跳过。 接下来是test_create()

代码/rails_xt/test/functional/people_controller_test.rb
def test_create
num_people = Person.count
post :create, :person => {}
assert_response :redirect
assert_redirected_to :action => 'list'
assert_equal num_people + 1, Person.count
end

这提出了几个新想法。 与到目前为止讨论的方法不同, create实际上会更改数据库。 这对我们的测试有几个含义。 首先,该测试调用post()而不是get() ,因为create()操作不是幂等的。 1第二,我们要测试数据库是否以适当的方式进行了更改。 下一行:

num_people = Person.count

捕获create()操作之前的人数,以及以下行:

assert_equal num_people + 1, Person.count

验证恰好创建了一个人。 (如果需要,您可以在此处执行更严格的测试,并确保新人员匹配传入的参数。)

诸如create()之类的变异操作的第三个含义是,我们不应期望:success响应。 而是,成功的更新将重定向到show操作。 以下几行:

assert_response :redirect
assert_redirected_to :action => 'list'

验证create()是否正确重定向。

其余的支架方法( test_edit()test_update()test_destroy() )没有引入任何新的测试概念,尽管您可能希望阅读它们以巩固对支架的理解。

为什么支架在POST后重定向

POST之后重定向,使用户很难意外地两次提交相同的更新。 (您可能已经在编写不良的Web应用程序中看到了两次更新问题。一个症状是浏览器警告“您将要重新提交包含POST数据的URL。确定吗?”)

Rails应用程序通常不会遭受两次更新问题的困扰,因为将一个相当好的解决方案(重定向)引入了支架。

Rails是一个强大的Web框架,建立在出色的语言(Ruby)之上,并被坚实的生态系统所围绕。 对于许多Web框架也可以这样说。 您为什么要在Rails上度过有限的时间? 因为Rails程序员正在快速完成工作 。 Rails程序员对开发人员的生产力提出了(并证实了)一些惊人的主张。 他们也玩得很开心。

Java程序员是否应该对这一新贵感到震惊? 绝对不。 Java程序员处于利用Ruby on Rails的独特位置。 Java开发人员的Rails将解释如何入门。


1幂等运算可以执行任意次,其效果仅是执行一次即可。 幂等操作对代理和缓存非常友好,因为不时进行额外的操作不会造成危害(带宽浪费除外)。 等幂运算具有其自己的HTTP动词(GET)。

版权所有©2006,实用书架。

翻译自: https://www.infoq.com/articles/rails-for-java-excerpt/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

infoq

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值