Skip to content

Latest commit

 

History

History
274 lines (188 loc) · 9.96 KB

09.md

File metadata and controls

274 lines (188 loc) · 9.96 KB

九、Ruby on Rails 后端集成

在本章中,我们将探讨在将 Angular.js 与 Ruby on Rails 框架相结合时如何解决常见问题。本章中使用的示例基于管理联系人列表的示例应用程序。

消耗

问题

您希望使用在您的 Rails 应用程序中实现的 JSON REST 应用编程接口。

溶液

使用$resource服务是一个很好的开始,对于 Rails 开发人员来说,可以通过根据 Rails 操作配置方法来调整它,使其感觉更自然:

    app.factory("Contact", function($resource) {
    return $resource("/api/contacts/:id", { id: "@id" },
    {
    'create': { method: 'POST' },
    'index': { method: 'GET', isArray: true },
    'show': { method: 'GET', isArray: false },
    'update': { method: 'PUT' },
    'destroy': { method: 'DELETE' }
    }
    );
    });

我们现在可以使用Contact.index()获取联系人列表,使用Contact.show(id)获取单个联系人。这些动作可以直接映射到您的 Rails 后端中的ContactsController动作:

    class ContactsController < ApplicationController
    respond_to :json

    def index
    @contacts = Contact.all
    respond_with @contacts
    end

    def show
    @contact = Contact.find(params[:id])
    respond_with @contact
    end

    ...
    end

Rails 动作控制器使用一个Contact活动记录模型,该模型具有常用的联系属性,如名字、姓氏、年龄等。通过指定respond_to :json,控制器只响应 JSON 资源格式;我们可以使用respond_with自动将Contact模型转换为 JSON 响应。

路由定义使用 Rails 默认资源路由和api作用域将 API 请求与其他请求分开:

    Contacts::Application.routes.draw do
    scope "api" do
    resources :contacts
    end
    end

这将为 HTTP GET 方法生成路径,例如api/contactsapi/contacts/:id

你可以在 GitHub 上找到完整的例子。

讨论

如果你想跟上 Ruby on Rails 的速度,我建议你看看 Rails Guides ,这将帮助你理解所有的部分是如何组合在一起的。

使用真实性令牌的 Rails 安全性

上面的示例代码工作得很好,直到我们对资源使用 HTTP 方法POSTPUT,DELETE。作为一种安全机制,Rails 期望真实性令牌来防止跨站点请求伪造(CSRF) 攻击。我们需要用令牌提交一个额外的 HTTP 头X-CSRF-Token。它由 Rails 方便地呈现在 HTML meta标签csrf-token中。使用 jQuery,我们可以获取该元标签定义并适当地配置$httpProvider:

    var app = angular.module("Contacts", ["ngResource"]);
    app.config(function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] =
    $('meta[name=csrf-token]').attr('content');
    });

Rails JSON 响应格式

如果您使用的是 3.1 之前的 Rails 版本,您会注意到 JSON 响应将使用contact命名空间作为模型属性,这破坏了您的 Angular.js 代码。要禁用此行为,您可以相应地配置您的 Rails 应用程序:

    ActiveRecord::Base.include_root_in_json = false

Ruby 和 JavaScript 世界之间仍然存在不一致。例如,在 Ruby 中,我们使用带下划线的属性名(display_name),而在 JavaScript 中,我们使用 camelCase (displayName)。

有一个定制的$resource实现, angularjs-rails-resource ,可用于简化对 rails 资源的消耗。它使用转换器和接收器来重命名属性字段,并为您处理根包装行为。

实现客户端路由

问题

您希望将客户端路由与 Ruby on Rails 后端结合使用。

溶液

对后端的每个请求都应该首先呈现完整的页面,以便加载我们的 Angular 应用程序。只有到那时,客户端渲染才会接管。让我们先来看看这条“通用”路线的路线定义:

    Contacts::Application.routes.draw do
    root :to => "layouts#index"
    match "*path" => "layouts#index"
    end

它使用路由全局化来匹配所有 URL,并定义一个根 URL。两者都将由布局控制器处理,其唯一目的是呈现初始布局:

    class LayoutsController < ApplicationController
    def index
    render "layouts/application"
    end
    end

实际的布局模板定义了我们的ng-view指令,并且驻留在app/views/layouts/application.html中——这里没有什么新东西。所以,让我们跳到app.js.erb中的角度路线定义:

    var app = angular.module("Contacts", ["ngResource"]);

    app.config(function($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);
    $routeProvider
    .when("/contacts",
    { templateUrl: "<%= asset_path('contacts/index.html') %> ",
    controller: "ContactsIndexCtrl" })
    .when("/contacts/new",
    { templateUrl: "<%= asset_path('contacts/edit.html') %> ",
    controller: "ContactsEditCtrl" })
    .when("/contacts/:id",
    { templateUrl: "<%= asset_path('contacts/show.html') %> ",
    controller: "ContactsShowCtrl" })
    .when("/contacts/:id/edit",
    { templateUrl: "<%= asset_path('contacts/edit.html') %> ",
    controller: "ContactsEditCtrl" })
    .otherwise({ redirectTo: "/contacts" });
    });

我们将$locationProvider设置为使用 HTML 5 模式,并定义我们的客户端路线,用于列出、显示、编辑和创建新联系人。

你可以在 GitHub 上找到完整的例子。

讨论

让我们再来看看路线的定义。首先,文件名以erb结尾,因为它在 JavaScript 文件中使用了 ERB 标签,这是由 Rails 资产管道提供的。asset_path方法用于检索网页组件的网址,因为它会根据环境而变化。在生产中,文件名包含 MD5 校验和,实际的 ERB 输出将从/assets/contacts/index.html变为/assets/contacts/index-7ce113b9081a20d93a4a86e1aacce05f.html。如果您的 Rails 应用程序被配置为使用资产主机,路径实际上是绝对的。

验证服务器端表单

问题

您希望使用 Rails 提供的服务器端 REST API 来验证表单。

溶液

Rails 已经提供了现成的模型验证支持。让我们从联系人活动记录模型开始:

    class Contact < ActiveRecord::Base
    attr_accessible :age, :firstname, :lastname

    validates :age, :numericality => {
    :only_integer => true, :less_than_or_equal_to => 50 }
    end

它定义了对age属性的验证。它必须是整数,并且小于或等于 50 年。

ContactsController,中,我们可以用它来确保 REST API 返回正确的错误消息。举个例子,让我们来看看create的动作:

    class ContactsController < ApplicationController
    respond_to :json

    def create
    @contact = Contact.new(params[:contact])
    if @contact.save
    render json: @contact, status: :created, location: @contact
    else
    render json: @contact.errors, status: :unprocessable_entity
    end
    end

    end

成功时,它将使用 JSON 表示呈现联系模型,失败时,它将返回转换为 JSON 的所有验证错误。让我们看一个 JSON 响应的例子:

    { "age": ["must be less than or equal to 50"] }

它是一个哈希,每个属性都有一个条目,带有验证错误。该值是一个字符串数组,因为可能同时存在多个错误。

让我们进入应用程序的客户端。Angular.js 触点$resource调用创建函数并传递失败回调函数:

    Contact.create($scope.contact, success, failure);

    function failure(response) {
    _.each(response.data, function(errors, key) {
    _.each(errors, function(e) {
    $scope.form[key].$dirty = true;
    $scope.form[key].$setValidity(e, false);
    });
    });
    }

请注意,活动记录属性可以定义多个验证。这就是为什么failure函数遍历每个验证条目和每个错误,并使用$setValidity$dirty将表单字段标记为无效。

现在,我们已经准备好向用户展示一些反馈,使用表单一章中已经讨论过的相同方法:

    <div class="control-group" ng-class="errorClass('age')">
    <label class="control-label" for="age">Age</label>
    <div class="controls">
    <input ng-model="contact.age" type="text" name="age"
    placeholder="Age" required>
    <span class="help-block"
    ng-show="form.age.$invalid && form.age.$dirty">
    {{errorMessage('age')}}
    </span>
    </div>
    </div>

如果表单字段无效且脏,则errorClass函数添加error CSS 类。这将以红色呈现标签、输入字段和帮助块:

    $scope.errorClass = function(name) {
    var s = $scope.form[name];
    return s.$invalid && s.$dirty ? "error" : "";
    };

errorMessage将打印更详细的错误信息,并在同一控制器中定义:

    $scope.errorMessage = function(name) {
    result = [];
    _.each($scope.form[name].$error, function(key, value) {
    result.push(value);
    });
    return result.join(", ");
    };

它遍历每个错误消息,并从中创建一个逗号分隔的字符串。

你可以在 GitHub 上找到完整的例子。

讨论

最后,errorMessage的处理当然相当原始。用户会期待本地化的故障消息,而不是这种技术演示。Rails 国际化指南描述了如何在 Rails 中翻译验证错误消息,并且可能对客户端代码的进一步使用有所帮助。