使用RailsAPI和Angular来构建一个酷炫的Web应用程序【环境设置版】

前几天在公司内举办了一个非常快速掌握Rails的研修营。由于之前只稍微接触过Rails,所以我勇敢地尝试了一些我之前未曾见过的API模式,并记录下来了备忘。

环境

Ruby:2.2.4
Rails:5.0.0.1
Angular:1.5.8(因为Angular2一直是rc版本)
Webpack:1.13.2
AngularMaterial:1.1.1

我也放了源代码,供参考。

我们将以Rails的环境已经准备完毕为前提,继续推进对话。

1. Rails应用程序接口 (RailsAPI)

【参考】使用Rails建立的仅用于API的应用程序

因为所有重要的事情都已经在RailsGuides(用日语)详细介绍了,所以在这里我只会简要概述。

为了创建一个专用于API的应用程序,只需在常规的rails new命令后面加上–api选项。
如果要使用数据库,为了避免后续调整设置麻烦,我们也应该加上-d选项。

$rails new rails-api-angular --api

出现的目录结构是这样的

undefined

与常规的Rails应用程序相比

    • app/helpers

 

    app/frontend

减少了

    app/jobs

数量在增加。

应用控制器(application_controller.rb)从::Base变为::API。

class ApplicationController < ActionController::API
end

Gemfile看起来整洁

source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
end

group :development do
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

2. Angular (角度)

这次我们在创建的应用程序下面还创建了Angular的环境,并采用了将其打包到公共目录下使用webpack的方式。

使用WebPack将JavaScript轻松分离并与Rails良好地结合
在Rails 4.2中放弃Sprockets,转而使用webpack进行迁移

安装Angular和webpack

首先准备 package.json(不用担心它可能包含了一些不必要的东西,因为它是从一个现有的项目中带来的)。

{
  "name": "rails-api-angular",
  "version": "1.0.0",
  "description": "rails-api-angular",
  "scripts": {
    "dev": "webpack-dev-server --hot --inline --port 3500 --progress --profile --colors",
    "build": "NODE_ENV=production webpack -p --progress --profile --colors "
  },
  "dependencies": {
    "angular": "1.5.8",
    "angular-animate": "1.5.8",
    "angular-aria": "1.5.8",
    "angular-cookies": "1.5.8",
    "angular-material": "1.1.1",
    "angular-material-icons": "0.7.1",
    "angular-messages": "1.5.8",
    "angular-resource": "1.5.8",
    "angular-route": "1.5.8",
    "angular-sanitize": "1.5.8",
    "clean-webpack-plugin": "^0.1.9",
    "css-loader": "^0.23.1",
    "es6-promise": "~3.2.1",
    "eslint-plugin-jsx-a11y": "^1.5.5",
    "eslint-plugin-react": "^5.2.2",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.8.5",
    "font-awesome": "^4.6.3",
    "font-awesome-sass-loader": "^1.0.1",
    "imports-loader": "^0.6.5",
    "jquery": "^2.2.4",
    "json-loader": "^0.5.4",
    "node-sass": "^3.7.0",
    "sass-loader": "^3.2.0",
    "style-loader": "^0.13.1",
    "ts-loader": "^0.8.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.13.2",
    "webpack-manifest-plugin": "^1.0.1"
  },
  "devDependencies": {
    "es5-shim": "^4.5.7",
    "eslint": "^2.9.0",
    "eslint-config-airbnb": "^9.0.1",
    "eslint-import-resolver-webpack": "^0.2.4",
    "eslint-plugin-import": "^1.7.0",
    "typescript": "^1.8.10",
    "webpack-dev-middleware": "^1.6.1",
    "webpack-dev-server": "^1.14.1",
    "webpack-hot-middleware": "^2.10.0"
  }
}

安装

$npm install

2-2. 创建Angular项目

我們將建立一個必要最低限的 Angular 專案。

$mkdir app/angular
$cd app/angular/
$mkdir javascripts
$touch javascripts/application.ts
$mkdir stylesheets
$touch stylesheets/application.sass

我会准备Typescript的类型定义。
这次我们会继续使用我们经常使用的dtsm,但是无论使用哪个类型定义管理工具,大体上都差不多,所以你可以使用你喜欢的工具。

$dtsm init

我要修改已经创建好的dtsm.json文件。

{
  "repos": [
    {
      "url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
      "ref": "master"
    }
  ],
  "path": "typings",
  "bundle": "typings/bundle.d.ts",
  "link": {
    "npm": {
      "include": true
    }
  },
  "dependencies": {
    "node/node.d.ts": {
      "ref": "17795ae18fc214be862fe578ad48c28fecfef8a6"
    },
    "angularjs/angular.d.ts": {
      "ref": "17795ae18fc214be862fe578ad48c28fecfef8a6"
    },
    "angular-material/angular-material.d.ts": {
      "ref": "17795ae18fc214be862fe578ad48c28fecfef8a6"
    },
    "angular-cookie/angular-cookie.d.ts": {
      "ref": "17795ae18fc214be862fe578ad48c28fecfef8a6"
    },
    "angularjs/angular-animate.d.ts": {
      "ref": "0c5c7a2d2bd0ce7dcab963a8402a9042749ca2da"
    }
  }
}

安装

$dtsm install

在javascripts/application.ts中调用angular和其他一些内容,并使用stylesheets/application.sass。

/// <reference path="./typings/bundle.d.ts"/>

// angular
import angular = require('angular')
require('angular-material')
require('angular-cookies')
require('angular-resource')
require('angular-sanitize')
require('angular-route')
require('angular-animate')
require('angular-material-icons')
require('es5-shim')

let app = angular.module('App', [
    'ngMaterial',
    'ngCookies',
    'ngResource',
    'ngSanitize',
    'ngRoute',
    'ngMdIcons',
    'ngAnimate'
]);

//stylesheets
require('../stylesheets/application')

//angular-materialのTheme
app.config(($mdThemingProvider) => {
    $mdThemingProvider.theme('default')
                      .primaryPalette('grey', {
                          'default': '100'
                      })
                      .accentPalette('pink', {
                          'default': '700'
                      });
});

//Controllerも一緒に定義しちゃいました
export default class AppCtrl {
    private title: string = 'Hello RailsAPI × Angular!'
    static $inject = ['$rootScope', '$scope', '$cookies', '$window', '$timeout', '$location']
    constructor(
        private rootScope: ng.IRootScopeService,
        private scope: ng.IScope,
        private cookies: any,
        private window: ng.IWindowService,
        private timeout: ng.ITimeoutService,
        private location: ng.ILocationService
    ) {
    }
}

app.controller('AppCtrl', AppCtrl)

在stylesheets/application.sass中导入angular-material。

@import '~angular-material/angular-material.css'

body
  background-color: whitesmoke

.header
  md-toolbar
    color: #D81B60
    button
      height: 100%
      margin: 0px

// angularの評価式が一瞬見えちゃう問題対応
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak
  display: none !important

2-3. webpack配置

在项目的根目录下创建webpack.config.js文件。

const DEBUG = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === undefined;
const webpack = require('webpack');
const path = require('path');

/**
 * Require webpack plugins
 */
const ManifestPlugin = require('webpack-manifest-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

/**
 * Environment settings
 */
const devtool = DEBUG ? '#inline-source-map' : '#eval';
const fileName = DEBUG ? '[name]' : '[name]-[hash]';
const publicPath = DEBUG ? 'http://localhost:3500/assets/' : '/assets/';

/**
 *  Entries
 */
const entries = {
    application: ['./app/angular/javascripts/application.ts']
}

/**
 * Add plugins
 */
const plugins = [
    new ExtractTextPlugin(fileName + '.css')
]

if (DEBUG) {
    plugins.push(new webpack.NoErrorsPlugin());
} else {
    plugins.push(new ManifestPlugin({fileName: 'webpack-manifest.json'}));
    plugins.push(new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}));
    plugins.push(new CleanWebpackPlugin(['assets'], {
        root: __dirname + '/public',
        verbose: true,
        dry: false
    }));
}

module.exports = {
    entry: entries,
    output: {
        path: __dirname + '/public/assets',
        filename: fileName + '.js',
        publicPath: publicPath
    },
    devtool: devtool,
    plugins: plugins,
    module: {
        loaders: [
            {
                test: /\.ts$/,
                loader: 'ts',
                exclude: [/node_modules/]
            },
            {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract('style-loader', 'css-loader?minimize')
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract('style-loader', 'css-loader!sass-loader?minimize')
            },
            {
                test: /\.sass$/,
                loader: ExtractTextPlugin.extract('style-loader', 'css-loader!sass-loader?minimize')
            },
            {
                test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url-loader?mimetype=image/svg+xml'
            },
            {
                test: /\.woff(\d+)?(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url-loader?mimetype=application/font-woff'
            },
            {
                test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url-loader?mimetype=application/font-woff'
            },
            {
                test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url-loader?mimetype=application/font-woff'
            },
            {
                test: /\.(jpg|png|gif)$/,
                loader: DEBUG ? 'file-loader?name=[name].[ext]' : 'file-loader?name=[name]-[hash].[ext]'
            }
        ]
    },
    resolve: {
        root: path.resolve(__dirname, 'app', 'angular'),
        extensions: ['', '.js', '.ts', '.css', '.scss', '.sass'],
    },
    devServer: {
        headers: {
            "Access-Control-Allow-Origin": "http://localhost:3000",
            "Access-Control-Allow-Credentials": "true"
        }
    }
}

用这个方法,在开发过程中可以启动服务器并进行监控,非常方便。
尝试运行之前在之前的package.json文件中偷偷定义的命令。

$npm run dev

然后就出现了这个错误。

ERROR in ./app/angular/javascripts/application.ts
Module build failed: TypeError: Path must be a string. Received undefined
    at assertPath (path.js:7:11)
    at Object.dirname (path.js:1324:5)
    at ensureTypeScriptInstance (/Users/nozakishohei/WorkSpace/repository/sample/rails-api-angular/node_modules/ts-loader/index.js:156:103)
    at Object.loader (/Users/nozakishohei/WorkSpace/repository/sample/rails-api-angular/node_modules/ts-loader/index.js:403:14)
 @ multi application

原来如此。ts-loader要求必须有tsconfig.json文件!

所以我会为你做这个

$touch app/angular/javascripts/tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "sourceMap": true
  },
  "exclude": [
    "node_modules"
  ]
}

只需再次运行npm run dev,就可以成功。然后只需要从屏幕上调用即可。

创建图像

我一直在标题中使用“RailsAPI”这个词,但现在我要打破规定(设置根目录,并创建一个页面)

只设置了3-1根目录(只有一个界面,请原谅)。

在routes.rb文件中设置根路由

我已经找不到别的想法,所以将其命名为root#index,但你可以给它取一个你喜欢的名字。

Rails.application.routes.draw do
    root 'root#index'
end

制造控制器

class RootController < ActionController::Base
    def index
    end
end

制作一个看起来像那样的顶部界面

<html ng-app="App">
<head>
  <meta charset="UTF-8">
  <meta name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>rails-api-angular</title>
</head>
<body ng-controller="AppCtrl as appCtrl" ng-cloak>
  <header class="header md-whiteframe-3dp">
    <md-toolbar md-scroll-shrink>
      <div class="md-toolbar-tools">
        <h3>
          <span>rails-api-angular</span>
        </h3>
        <span flex></span>
        <md-button class="md-accent">Sign up</md-button>
        <md-button class="md-accent">Log in</md-button>
      </div>
    </md-toolbar>
  </header>

  <div layout="row" layout-align="center center">
    <h1>{{appCtrl.title}}</h1>
  </div>

<%= stylesheet_link_tag webpack_asset_path('application.css') %>
<%= javascript_include_tag webpack_asset_path('application.js')%>
</body>
</html>

添加一个辅助函数 webpack_asset_path,用于区分读取 webpack 资源。

module RootHelper
    def webpack_asset_path(path)
        if Rails.env.development?
            return "http://localhost:3500/assets/#{path}"
        end

        host = Rails.application.config.action_controller.asset_host
        manifest = Rails.application.config.assets.webpack_manifest
        path = manifest[path] if manifest && manifest[path].present?
        "#{host}/assets/#{path}"
    end
end

我终于启动了Rails,并尝试访问localhost:3000。

$rails s

来了!

Kobito.oUViNg.png
广告
将在 10 秒后关闭
bannerAds