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
앵귤러 다운로드
- 나중에 테스트가 제대로 작동하려면
angular-mocks.js
라는 파일이 필요합니다. . Angular 문서 어디에도 이에 대한 언급이 없다고 생각하지만 반드시 필요합니다. - 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의 훌륭한 게시물을 확인하세요. 그의 튜토리얼도 살펴보고 싶지만 이 튜토리얼은 모든 세부 사항을 _정말_숟가락으로 먹여서 잡초에 갇힐 가능성을 최소화한다는 점에서 다릅니다.