Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

AngularJS 및 Rails 4 시작하기

AngularJS를 시작하는 것은 어렵지 않습니다. 설명서는 현존하는 최고 중 일부이며 튜토리얼은 충분히 간단합니다.

하지만 기술을 결합하기 시작하면 상황이 복잡해집니다.

JavaScript 대신 CoffeeScript를 사용하는 경우 사전 처리 문제와 명백한 구문 차이를 고려해야 합니다. 이것들은 그 자체로 사소한 문제이지만 Ruby on Rails, Jasmine 및 Karma를 혼합하면 어떻게 될까요? 놀라울 정도로 까다로워집니다.

이것이 바로 이 튜토리얼에서 사용할 스택입니다. 우리가 처벌에 열광해서가 아니라 실제 세계에서 볼 수 있는 설정이기 때문입니다.

이 튜토리얼은 사용자가 Rails에 익숙하지만 AngularJS에 익숙하지 않다고 가정합니다.

기본 Rails 앱 만들기

관련된 기술 레이어가 너무 많기 때문에 거의 아무 작업도 수행하지 않는 간단한 응용 프로그램을 빌드하겠습니다. 레스토랑에 대한 CRUD 기능을 설정할 것입니다. 실제로는 CR 부분일 뿐입니다. -UD는 독자를 위한 연습으로 남겨둡니다.;-)

애플리케이션을 레스토랑이라고 부를 것입니다. .

여기서는 PostgreSQL과 RSpec을 사용하고 있지만 DBMS와 서버 측 테스팅 프레임워크는 중요하지 않습니다. 원하는 것을 사용할 수 있습니다.

초기 설정

먼저 프로젝트 생성:

$ rails new restauranteur --database=postgresql --skip-test-unit

Pow를 사용하는 경우 프로젝트를 Pow에 추가하세요:

$ ln -s /Users/jasonswett/projects/restauranteur ~/.pow/restauranteur

PostgreSQL 데이터베이스 사용자 생성:

$ createuser -P -s -e restauranteur

Gemfile에 RSpec 추가:

# Gemfile
gem "rspec-rails", "~> 2.14.0"

RSpec 설치:

$ bundle install
$ rails g rspec:install

데이터베이스 생성:

$ rake db:create

레스토랑 모델 만들기

이제 프로젝트와 데이터베이스가 생성되었으므로 첫 번째 리소스를 생성해 보겠습니다. 레스토랑 리소스에는 이름이라는 속성 하나만 있습니다. , 이는 문자열입니다.

$ rails generate scaffold restaurant name:string

이제 OCD에 대해 이야기하기 위해 레스토랑 이름이 고유한지 확인하겠습니다.

# db/migrate/[timestamp]_create_restaurants.rb

class CreateRestaurants < ActiveRecord::Migration
  def change
    create_table :restaurants do |t|
      t.string :name

      t.timestamps
    end

    # Add the following line
    add_index :restaurants, :name, unique: true
  end
end

마이그레이션 실행:

$ rake db:migrate

잘못된 레스토랑을 만들 수 없는지 확인하기 위해 몇 가지 사양을 추가해 보겠습니다. 고유한 실패는 원시 오류를 제공합니다.

require 'spec_helper'

describe Restaurant do
  before do
    @restaurant = Restaurant.new(name: "Momofuku")
  end

  subject { @restaurant }

  it { should respond_to(:name) }
  it { should be_valid }

  describe "when name is not present" do
    before { @restaurant.name = " " }
    it { should_not be_valid }
  end

  describe "when name is already taken" do
    before do
      restaurant_with_same_name = @restaurant.dup
      restaurant_with_same_name.name = @restaurant.name.upcase
      restaurant_with_same_name.save
    end

    it { should_not be_valid }
  end
end

다음 유효성 검사기를 추가하면 사양이 통과됩니다.

class Restaurant < ActiveRecord::Base 
  validates :name, presence: true, uniqueness: { case_sensitive: false }
end

이제 계속 진행할 수 있습니다.

AngularJS를 혼합

한 번에 모든 것을 쏟아붓기보다는 먼저 AngularJS-Rails 애플리케이션의 가장 간단한 "Hello, world" 버전을 시연한 다음 그 위에 레스토랑 CRUD 기능을 구축하고 싶습니다.

"Hello, world" 페이지가 특정 Rails 리소스에 연결되어야 하거나 연결되어야 할 이유가 없습니다. 이러한 이유로 StaticPagesController AngularJS 홈페이지를 제공합니다.

컨트롤러 생성

$ rails generate controller static_pages index

현재 루트 경로는 "Welcome to Rails" 페이지입니다. index로 설정하자 새로운 StaticPagesController 작업 :

# config/routes.rb

Restauranteur::Application.routes.draw do
  # Add the following line
  root 'static_pages#index'
end

앵귤러 다운로드

  1. 나중에 테스트가 제대로 작동하려면 angular-mocks.js라는 파일이 필요합니다. . Angular 문서 어디에도 이에 대한 언급이 없다고 생각하지만 반드시 필요합니다.
  2. AngularJS 튜토리얼의 문서에는 최신 버전이 나열되어 있지만 제 기억이 맞다면 angular.js 간의 호환성에 문제가 있었습니다. 및 angular-mocks.js 최신 버전의 경우. 버전 1.1.5가 함께 작동한다는 것을 알고 있으므로 최신 안정 버전은 아니지만 여기에 나열하는 버전입니다. 물론 시간이 지나면 호환성 상황이 개선될 것입니다.

angular.js 다운로드 및 angular-mocks.js code.angularjs.org에서 파일을 app/assets/javascripts로 이동합니다. .

$ wget https://code.angularjs.org/1.1.5/angular.js \
https://code.angularjs.org/1.1.5/angular-mocks.js
$ mv angular* app/assets/javascripts

자산 파이프라인에 추가

이제 우리는 애플리케이션에 AngularJS 파일이 필요하도록 지시하고 이에 의존하는 다른 파일보다 먼저 로드되도록 하고 싶습니다. (우리는 RequireJS와 같은 것을 사용하여 이러한 종속성을 관리할 수 있으며 아마도 프로덕션 제품에서 수행할 것입니다. 그러나 이 자습서의 목적을 위해 기술 스택을 가능한 한 얇게 유지하고 싶습니다.)

참고: Angular와 Turbolink는 서로 충돌할 수 있으므로 여기에서 비활성화합니다.

// app/assets/javascripts/application.js

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs

// Add the following two lines
//= require angular
//= require main

//= require_tree .

레이아웃 설정

페이지에 Angular 앱이 있음을 나타내는 ng-app 및 ng-view를 추가합니다. 또한 Turbolinks에 대한 언급이 제거되었음을 알 수 있습니다.

  <%= yield %>

각도 컨트롤러 만들기

먼저 컨트롤러를 위한 디렉토리를 생성해 보겠습니다. 원하는 이름을 지정할 수 있습니다.

$ mkdir -p app/assets/javascripts/angular/controllers

이제 컨트롤러 파일 자체를 생성해 보겠습니다. 이 컨트롤러를 "홈 컨트롤러"라고 부르고 Angular의 규칙은 컨트롤러 파일 이름에 Ctrl을 추가하는 것입니다. . 따라서 파일 이름은 app/assets/javascripts/angular/controllers/HomeCtrl.js.coffee가 됩니다. :

# app/assets/javascripts/angular/controllers/HomeCtrl.js.coffee

@restauranteur.controller 'HomeCtrl', ['$scope', ($scope) ->
  # Notice how this controller body is empty
]

각도 경로 추가

이제 HomeCtrl 우리의 "기본 페이지"가 ​​됩니다. 여기 app/assets/javascripts/main.js.coffee에서 라우팅을 정의하고 있습니다. , 하지만 파일 이름은 중요하지 않다고 생각합니다.

# app/assets/javascripts/main.js.coffee

# This line is related to our Angular app, not to our
# HomeCtrl specifically. This is basically how we tell
# Angular about the existence of our application.
@restauranteur = angular.module('restauranteur', [])

# This routing directive tells Angular about the default
# route for our application. The term "otherwise" here
# might seem somewhat awkward, but it will make more
# sense as we add more routes to our application.
@restauranteur.config(['$routeProvider', ($routeProvider) ->
  $routeProvider.
    otherwise({
      templateUrl: '../templates/home.html',
      controller: 'HomeCtrl'
    }) 
])

Angular 템플릿 추가

Angular 템플릿을 보관할 장소도 필요합니다. 내 것을 public/templates에 넣기로 결정했습니다. . 다시 말하지만 원하는 곳에 배치할 수 있습니다.

mkdir public/templates

public/templates/home.html 파일을 생성하면 임의의 콘텐츠가 있는 경우 브라우저에서 볼 수 있어야 합니다.


This is the home page.

이제 https://restauranteur.dev/로 이동하면 (또는 https://localhost:3000/ Pow를 사용하지 않는 경우) home.html의 내용이 표시되어야 합니다. .

데이터 바인딩의 예

일종의 흥미롭지 만 그다지 인상적이지 않을 것입니다. 실제로 유선으로 무언가를 보내 보겠습니다. app/assets/angular/controllers/HomeCtrl.js.coffee 수정 이렇게:

# app/assets/angular/controllers/HomeCtrl.js.coffee 

@restauranteur.controller 'HomeCtrl', ['$scope', ($scope) -> 
  $scope.foo = 'bar'
]

이것은 @foo = "bar"라고 말하는 것과 유사합니다. Rails 컨트롤러에서 foo를 연결할 수 있습니다. 다음과 같은 이중 중괄호 구문을 사용하여 템플릿에 삽입합니다.

Value of "foo": {{foo}}  

이번에는 실제로 하기

우리는 이미 간단한 hello world 앱을 구축했습니다. 완전한 CRUD 애플리케이션을 만드는 것은 그다지 어렵지 않습니다.

데이터베이스 시드

데이터베이스의 일부 레스토랑으로 시작하면 레스토랑 CRUD와 함께 작업하는 것이 조금 더 의미가 있습니다. 사용할 수 있는 시드 파일입니다.

# db/seeds.rb

Restaurant.create([
  { name: "The French Laundry" },
  { name: "Chez Panisse" },
  { name: "Bouchon" },
  { name: "Noma" },
  { name: "Taco Bell" },
])
rake db:seed

식당 색인 페이지 만들기

먼저 레스토랑용 템플릿 폴더를 생성해 보겠습니다.

mkdir public/templates/restaurants

우리가 만들 첫 번째 템플릿은 색인 페이지입니다.

[index](/#)

  * {{ restaurant.name }}

이 것들이 무엇을 의미하는지 잠시 후에 설명하겠습니다. 먼저 컨트롤러를 생성해 보겠습니다.

# app/assets/javascripts/angular/controllers/RestaurantIndexCtrl.js.coffee

@restauranteur.controller 'RestaurantIndexCtrl', ['$scope', '$location', '$http', ($scope, $location, $http) ->
  $scope.restaurants = []
  $http.get('./restaurants.json').success((data) ->
    $scope.restaurants = data
  )
]

마지막으로 라우팅 구성을 조정합니다.

# app/assets/javascripts/main.js.coffee

@restauranteur = angular.module('restauranteur', [])

@restauranteur.config(['$routeProvider', ($routeProvider) ->
  $routeProvider.
    when('/restaurants', {
      templateUrl: '../templates/restaurants/index.html',
      controller: 'RestaurantIndexCtrl'
    }).
    otherwise({
      templateUrl: '../templates/home.html',
      controller: 'HomeCtrl'
    })
])

이제 마지막으로 URI /#/restaurants로 이동할 수 있습니다. 레스토랑 목록을 볼 수 있어야 합니다. 계속 진행하기 전에 테스트를 추가해 보겠습니다.

첫 번째 테스트 추가

JS 테스트 폴더 추가:

mkdir spec/javascripts

쓰기 테스트:

# spec/javascripts/controllers_spec.js.coffee

describe "Restauranteur controllers", ->
  beforeEach module("restauranteur")

  describe "RestaurantIndexCtrl", ->
    it "should set restaurants to an empty array", inject(($controller) ->
      scope = {}
      ctrl = $controller("RestaurantIndexCtrl",
        $scope: scope
      )
      expect(scope.restaurants.length).toBe 0
    )

구성 추가:

// spec/javascripts/restauranteur.conf.js

module.exports = function(config) {
  config.set({
    basePath: '../..',

    frameworks: ['jasmine'],

    autoWatch: true,

    preprocessors: {
      '**/*.coffee': 'coffee'
    }, 

    files: [
      'app/assets/javascripts/angular.js',
      'app/assets/javascripts/angular-mocks.js',
      'app/assets/javascripts/main.js.coffee',
      'app/assets/javascripts/angular/controllers/RestaurantIndexCtrl.js.coffee',
      'app/assets/javascripts/angular/*',
      'spec/javascripts/*_spec.js.coffee'
    ]  
  });
};

Karma를 설치하고 서버를 시작하십시오:

sudo npm install -g karma
sudo npm install -g karma-ng-scenario
karma start spec/javascripts/restauranteur.conf.js

https://localhost:9876/에 접속하면 , 테스트가 실행되고 성공할 것입니다. 테스트 실패를 확인하려면 expect(scope.restaurants.length).toBe 0을 변경하세요. expect(scope.restaurants.length).toBe 1 테스트를 다시 실행하십시오.

방금 추가한 이 테스트의 의미는 분명히 의심스럽습니다. 하지만 여기에서 제 의도는 Angular 코드를 테스트 도구로 가져오는 방법을 알아내는 작업을 절약하는 것입니다. CoffeeScript 전처리기 및 angular-mocks.js와 같은 특정 항목이 있습니다. 완전히 명확하지 않은 포함이며 올바르게 이해하는 데 몇 시간이 걸렸습니다.

레스토랑 페이지 구축

이제 레스토랑 색인 템플릿을 임시로 조정해 보겠습니다.

  * {{restaurant.name}} ({{restaurant.id}})

이제 /#/restaurants를 다시 방문하는 경우 , 레스토랑 중 어느 곳도 ID가 없음을 알 수 있습니다. 왜 비어 있습니까?

Rails 4에서 스캐폴딩을 생성하면 .jbuilder 파일:

$ ls -1 app/views/restaurants/*.jbuilder
app/views/restaurants/index.json.jbuilder
app/views/restaurants/show.json.jbuilder

app/views/restaurants/index.json.jbuilder를 열면 , 다음과 같이 표시됩니다.

# app/views/restaurants/index.json.jbuilder

json.array!(@restaurants) do |restaurant|
  json.extract! restaurant, :name
  json.url restaurant_url(restaurant, format: :json)
end

보시다시피 :name이 포함되어 있습니다. :id는 아니지만 . 추가해 보겠습니다.

# app/views/restaurants/index.json.jbuilder

json.array!(@restaurants) do |restaurant|
  json.extract! restaurant, :id, :name
  json.url restaurant_url(restaurant, format: :json)
end

파일을 저장하고 /#/restaurants를 새로고침하면 , ID가 표시되어야 합니다.

이제 템플릿을 원래대로 변경해 보겠습니다.

[index](/#)

  * {{ restaurant.name }}

viewRestaurant() 그러나 실제로 viewRestaurant() . 지금 해보자:

# app/assets/javascripts/angular/controllers/RestaurantIndexCtrl.js.coffee

@restauranteur.controller 'RestaurantIndexCtrl', ['$scope', '$location', '$http', ($scope, $location, $http) ->
  $scope.restaurants = []
  $http.get('./restaurants.json').success((data) ->
    $scope.restaurants = data
  )

  # Add the following lines
  $scope.viewRestaurant = (id) ->
    $location.url "/restaurants/#{id}"
]

Rails의 규칙은 resource_name/:id "show" 페이지에 매핑하고 여기에서 수행할 작업입니다. 쇼 템플릿, 경로 및 컨트롤러를 생성해 보겠습니다.

# {{restaurant.name}}
# app/assets/javascripts/main.js.coffee

@restauranteur = angular.module('restauranteur', [])

@restauranteur.config(['$routeProvider', ($routeProvider) ->
  $routeProvider.
    when('/restaurants', {
      templateUrl: '../templates/restaurants/index.html',
      controller: 'RestaurantIndexCtrl'
    }).
    when('/restaurants/:id', {
      templateUrl: '../templates/restaurants/show.html',
      controller: 'RestaurantShowCtrl'
    }).
    otherwise({
      templateUrl: '../templates/home.html',
      controller: 'HomeCtrl'
    })
])
# app/assets/javascripts/angular/controllers/RestaurantShowCtrl.js.coffee

@restauranteur.controller 'RestaurantShowCtrl', ['$scope', '$http', '$routeParams', ($scope, $http, $routeParams) ->
  $http.get("./restaurants/#{$routeParams.id}.json").success((data) ->
    $scope.restaurant = data
  )
]

이제 /#/restaurants를 새로 고치면 레스토랑을 클릭하면 해당 레스토랑의 쇼 페이지에서 자신을 찾을 수 있습니다. 예!

지금은 여기까지입니다

특별히 인상적인 결과를 보지 못했을 수도 있지만 AngularJS를 Rails 4에 연결하는 데 시간을 절약할 수 있기를 바랍니다. 다음으로 CRUD 기능을 보다 DRY하게 만드는 데 도움이 되는 inngResource를 살펴보는 것이 좋습니다.

더 자세히 알고 싶으십니까?

Rails 4.0 시리즈에서 AngularJS 앱 부트스트래핑으로 AngularJS 및 Rails를 시작하는 데 도움을 준 Adam Anderson의 훌륭한 게시물을 확인하세요. 그의 튜토리얼도 살펴보고 싶지만 이 튜토리얼은 모든 세부 사항을 _정말_숟가락으로 먹여서 잡초에 갇힐 가능성을 최소화한다는 점에서 다릅니다.