35 |
--------------------------------------------------------------------------------
/app/controllers/articles_controller.rb:
--------------------------------------------------------------------------------
1 | class V1::ArticlesController < ApplicationController
2 | before_action :set_article, only: [:show, :update, :destroy]
3 |
4 | # GET /articles
5 | # GET /articles.json
6 | def index
7 | @articles = Article.all
8 |
9 | render json: @articles
10 | end
11 |
12 | # GET /articles/1
13 | # GET /articles/1.json
14 | def show
15 | render json: @article
16 | end
17 |
18 | # POST /articles
19 | # POST /articles.json
20 | def create
21 | @article = Article.new(article_params)
22 |
23 | if @article.save
24 | render json: @article, status: :created, location: @article
25 | else
26 | render json: @article.errors, status: :unprocessable_entity
27 | end
28 | end
29 |
30 | # PATCH/PUT /articles/1
31 | # PATCH/PUT /articles/1.json
32 | def update
33 | @article = Article.find(params[:id])
34 |
35 | if @article.update(article_params)
36 | head :no_content
37 | else
38 | render json: @article.errors, status: :unprocessable_entity
39 | end
40 | end
41 |
42 | # DELETE /articles/1
43 | # DELETE /articles/1.json
44 | def destroy
45 | @article.destroy
46 |
47 | head :no_content
48 | end
49 |
50 | private
51 |
52 | def set_article
53 | @article = Article.find(params[:id])
54 | end
55 |
56 | def article_params
57 | params.require(:article).permit(:title, :body)
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/app/controllers/v1/articles_controller.rb:
--------------------------------------------------------------------------------
1 | class V1::ArticlesController < ApplicationController
2 | before_action :set_article, only: [:show, :update, :destroy]
3 |
4 | # GET /articles
5 | # GET /articles.json
6 | def index
7 | @articles = Article.all
8 |
9 | render json: @articles
10 | end
11 |
12 | # GET /articles/1
13 | # GET /articles/1.json
14 | def show
15 | render json: @article
16 | end
17 |
18 | # POST /articles
19 | # POST /articles.json
20 | def create
21 | @article = Article.new(article_params)
22 |
23 | if @article.save
24 | render json: @article, status: :created, location: @article
25 | else
26 | render json: @article.errors, status: :unprocessable_entity
27 | end
28 | end
29 |
30 | # PATCH/PUT /articles/1
31 | # PATCH/PUT /articles/1.json
32 | def update
33 | @article = Article.find(params[:id])
34 |
35 | if @article.update(article_params)
36 | head :no_content
37 | else
38 | render json: @article.errors, status: :unprocessable_entity
39 | end
40 | end
41 |
42 | # DELETE /articles/1
43 | # DELETE /articles/1.json
44 | def destroy
45 | @article.destroy
46 |
47 | head :no_content
48 | end
49 |
50 | private
51 |
52 | def set_article
53 | @article = Article.find(params[:id])
54 | end
55 |
56 | def article_params
57 | params.require(:article).permit(:title, :body)
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/client/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | angularRails
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/client/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-gulp-angular": {
3 | "version": "0.12.1",
4 | "props": {
5 | "angularVersion": "~1.4.0",
6 | "angularModules": [
7 | {
8 | "key": "animate",
9 | "module": "ngAnimate"
10 | },
11 | {
12 | "key": "cookies",
13 | "module": "ngCookies"
14 | },
15 | {
16 | "key": "touch",
17 | "module": "ngTouch"
18 | },
19 | {
20 | "key": "sanitize",
21 | "module": "ngSanitize"
22 | }
23 | ],
24 | "jQuery": {
25 | "key": "jquery2"
26 | },
27 | "resource": {
28 | "key": "angular-resource",
29 | "module": "ngResource"
30 | },
31 | "router": {
32 | "key": "ui-router",
33 | "module": "ui.router"
34 | },
35 | "ui": {
36 | "key": "bootstrap",
37 | "module": null
38 | },
39 | "bootstrapComponents": {
40 | "key": "ui-bootstrap",
41 | "module": "ui.bootstrap"
42 | },
43 | "cssPreprocessor": {
44 | "key": "none",
45 | "extension": "css"
46 | },
47 | "jsPreprocessor": {
48 | "key": "none",
49 | "extension": "js",
50 | "srcExtension": "js"
51 | },
52 | "htmlPreprocessor": {
53 | "key": "none",
54 | "extension": "html"
55 | },
56 | "foundationComponents": {
57 | "name": null,
58 | "version": null,
59 | "key": null,
60 | "module": null
61 | },
62 | "paths": {
63 | "src": "src",
64 | "dist": "dist",
65 | "e2e": "e2e",
66 | "tmp": ".tmp"
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31 | # yet still be able to expire them through the digest params.
32 | config.assets.digest = true
33 |
34 | # Adds additional error checking when serving assets at runtime.
35 | # Checks for improperly declared sprockets dependencies.
36 | # Raises helpful error messages.
37 | config.assets.raise_runtime_errors = true
38 |
39 | # Raises error for missing translations
40 | # config.action_view.raise_on_missing_translations = true
41 | end
42 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static file server for tests with Cache-Control for performance.
16 | config.serve_static_files = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Randomize the order test cases are executed.
35 | config.active_support.test_order = :random
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angularRails",
3 | "version": "0.0.1",
4 | "dependencies": {},
5 | "scripts": {
6 | "test": "gulp test",
7 | "postinstall": "cd client && bower install --config.interactive=false && gulp build"
8 | },
9 | "dependencies": {
10 | "browser-sync": "~2.7.12",
11 | "browser-sync-spa": "~1.0.2",
12 | "bower": "^1.4.1",
13 | "chalk": "~1.0.0",
14 | "concat-stream": "~1.5.0",
15 | "del": "~1.2.0",
16 | "gulp": "~3.9.0",
17 | "gulp-angular-filesort": "~1.1.1",
18 | "gulp-angular-templatecache": "~1.6.0",
19 | "gulp-autoprefixer": "~2.3.1",
20 | "gulp-csso": "~1.0.0",
21 | "gulp-filter": "~2.0.2",
22 | "gulp-flatten": "~0.0.4",
23 | "gulp-inject": "~1.3.1",
24 | "gulp-jshint": "~1.11.0",
25 | "gulp-load-plugins": "~0.10.0",
26 | "gulp-minify-html": "~1.0.3",
27 | "gulp-ng-annotate": "~1.0.0",
28 | "gulp-protractor": "~1.0.0",
29 | "gulp-rename": "~1.2.2",
30 | "gulp-replace": "~0.5.3",
31 | "gulp-rev": "~5.0.0",
32 | "gulp-rev-replace": "~0.4.2",
33 | "gulp-size": "~1.2.1",
34 | "gulp-sourcemaps": "~1.5.2",
35 | "gulp-uglify": "~1.2.0",
36 | "gulp-useref": "~1.2.0",
37 | "gulp-util": "~3.0.5",
38 | "http-proxy-middleware": "^0.2.0",
39 | "jshint-stylish": "~2.0.0",
40 | "karma": "~0.12.36",
41 | "karma-angular-filesort": "~0.1.0",
42 | "karma-jasmine": "~0.3.5",
43 | "karma-ng-html2js-preprocessor": "~0.1.2",
44 | "karma-phantomjs-launcher": "~0.2.0",
45 | "lodash": "~3.9.3",
46 | "main-bower-files": "~2.8.0",
47 | "merge-stream": "~0.1.7",
48 | "require-dir": "~0.3.0",
49 | "uglify-save-license": "~0.4.1",
50 | "wiredep": "~2.2.2",
51 | "wrench": "~1.5.8"
52 | },
53 | "engines": {
54 | "node": "0.12.7",
55 | "npm": "2.12.1"
56 | },
57 | "cacheDirectories": [
58 | "node_modules",
59 | "./client/bower_components"
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/client/src/app/components/malarkey/malarkey.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular
5 | .module('angularRails')
6 | .directive('acmeMalarkey', acmeMalarkey);
7 |
8 | /** @ngInject */
9 | function acmeMalarkey(malarkey) {
10 | var directive = {
11 | restrict: 'E',
12 | scope: {
13 | extraValues: '=',
14 | },
15 | template: ' ',
16 | link: linkFunc,
17 | controller: MalarkeyController,
18 | controllerAs: 'vm'
19 | };
20 |
21 | return directive;
22 |
23 | function linkFunc(scope, el, attr, vm) {
24 | var watcher;
25 | var typist = malarkey(el[0], {
26 | typeSpeed: 40,
27 | deleteSpeed: 40,
28 | pauseDelay: 800,
29 | loop: true,
30 | postfix: ' '
31 | });
32 |
33 | el.addClass('acme-malarkey');
34 |
35 | angular.forEach(scope.extraValues, function(value) {
36 | typist.type(value).pause().delete();
37 | });
38 |
39 | watcher = scope.$watch('vm.contributors', function() {
40 | angular.forEach(vm.contributors, function(contributor) {
41 | typist.type(contributor.login).pause().delete();
42 | });
43 | });
44 |
45 | scope.$on('$destroy', function () {
46 | watcher();
47 | });
48 | }
49 |
50 | /** @ngInject */
51 | function MalarkeyController($log, githubContributor) {
52 | var vm = this;
53 |
54 | vm.contributors = [];
55 |
56 | activate();
57 |
58 | function activate() {
59 | return getContributors().then(function() {
60 | $log.info('Activated Contributors View');
61 | });
62 | }
63 |
64 | function getContributors() {
65 | return githubContributor.getContributors(10).then(function(data) {
66 | vm.contributors = data;
67 |
68 | return vm.contributors;
69 | });
70 | }
71 | }
72 |
73 | }
74 |
75 | })();
76 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | scope '/api' do
4 | namespace :v1, defaults: { format: :json } do
5 |
6 | resources :articles, except: [:new, :edit]
7 |
8 | end
9 | end
10 |
11 | # The priority is based upon order of creation: first created -> highest priority.
12 | # See how all your routes lay out with "rake routes".
13 |
14 | # You can have the root of your site routed with "root"
15 | # root 'welcome#index'
16 |
17 | # Example of regular route:
18 | # get 'products/:id' => 'catalog#view'
19 |
20 | # Example of named route that can be invoked with purchase_url(id: product.id)
21 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
22 |
23 | # Example resource route (maps HTTP verbs to controller actions automatically):
24 | # resources :products
25 |
26 | # Example resource route with options:
27 | # resources :products do
28 | # member do
29 | # get 'short'
30 | # post 'toggle'
31 | # end
32 | #
33 | # collection do
34 | # get 'sold'
35 | # end
36 | # end
37 |
38 | # Example resource route with sub-resources:
39 | # resources :products do
40 | # resources :comments, :sales
41 | # resource :seller
42 | # end
43 |
44 | # Example resource route with more complex sub-resources:
45 | # resources :products do
46 | # resources :comments
47 | # resources :sales do
48 | # get 'recent', on: :collection
49 | # end
50 | # end
51 |
52 | # Example resource route with concerns:
53 | # concern :toggleable do
54 | # post 'toggle'
55 | # end
56 | # resources :posts, concerns: :toggleable
57 | # resources :photos, concerns: :toggleable
58 |
59 | # Example resource route within a namespace:
60 | # namespace :admin do
61 | # # Directs /admin/products/* to Admin::ProductsController
62 | # # (app/controllers/admin/products_controller.rb)
63 | # resources :products
64 | # end
65 | end
66 |
--------------------------------------------------------------------------------
/client/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var conf = require('./gulp/conf');
5 |
6 | var _ = require('lodash');
7 | var wiredep = require('wiredep');
8 |
9 | function listFiles() {
10 | var wiredepOptions = _.extend({}, conf.wiredep, {
11 | dependencies: true,
12 | devDependencies: true
13 | });
14 |
15 | return wiredep(wiredepOptions).js
16 | .concat([
17 | path.join(conf.paths.src, '/app/**/*.module.js'),
18 | path.join(conf.paths.src, '/app/**/*.js'),
19 | path.join(conf.paths.src, '/**/*.spec.js'),
20 | path.join(conf.paths.src, '/**/*.mock.js'),
21 | path.join(conf.paths.src, '/**/*.html')
22 | ]);
23 | }
24 |
25 | module.exports = function(config) {
26 |
27 | var configuration = {
28 | files: listFiles(),
29 |
30 | singleRun: true,
31 |
32 | autoWatch: false,
33 |
34 | frameworks: ['jasmine', 'angular-filesort'],
35 |
36 | angularFilesort: {
37 | whitelist: [path.join(conf.paths.src, '/**/!(*.html|*.spec|*.mock).js')]
38 | },
39 |
40 | ngHtml2JsPreprocessor: {
41 | stripPrefix: 'src/',
42 | moduleName: 'angularRails'
43 | },
44 |
45 | browsers : ['PhantomJS'],
46 |
47 | plugins : [
48 | 'karma-phantomjs-launcher',
49 | 'karma-angular-filesort',
50 | 'karma-jasmine',
51 | 'karma-ng-html2js-preprocessor'
52 | ],
53 |
54 | preprocessors: {
55 | 'src/**/*.html': ['ng-html2js']
56 | }
57 | };
58 |
59 | // This block is needed to execute Chrome on Travis
60 | // If you ever plan to use Chrome and Travis, you can keep it
61 | // If not, you can safely remove it
62 | // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076
63 | if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) {
64 | configuration.customLaunchers = {
65 | 'chrome-travis-ci': {
66 | base: 'Chrome',
67 | flags: ['--no-sandbox']
68 | }
69 | };
70 | configuration.browsers = ['chrome-travis-ci'];
71 | }
72 |
73 | config.set(configuration);
74 | };
75 |
--------------------------------------------------------------------------------
/client/src/app/components/webDevTec/webDevTec.service.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular
5 | .module('angularRails')
6 | .service('webDevTec', webDevTec);
7 |
8 | /** @ngInject */
9 | function webDevTec() {
10 | var data = [
11 | {
12 | 'title': 'AngularJS',
13 | 'url': 'https://angularjs.org/',
14 | 'description': 'HTML enhanced for web apps!',
15 | 'logo': 'angular.png'
16 | },
17 | {
18 | 'title': 'BrowserSync',
19 | 'url': 'http://browsersync.io/',
20 | 'description': 'Time-saving synchronised browser testing.',
21 | 'logo': 'browsersync.png'
22 | },
23 | {
24 | 'title': 'GulpJS',
25 | 'url': 'http://gulpjs.com/',
26 | 'description': 'The streaming build system.',
27 | 'logo': 'gulp.png'
28 | },
29 | {
30 | 'title': 'Jasmine',
31 | 'url': 'http://jasmine.github.io/',
32 | 'description': 'Behavior-Driven JavaScript.',
33 | 'logo': 'jasmine.png'
34 | },
35 | {
36 | 'title': 'Karma',
37 | 'url': 'http://karma-runner.github.io/',
38 | 'description': 'Spectacular Test Runner for JavaScript.',
39 | 'logo': 'karma.png'
40 | },
41 | {
42 | 'title': 'Protractor',
43 | 'url': 'https://github.com/angular/protractor',
44 | 'description': 'End to end test framework for AngularJS applications built on top of WebDriverJS.',
45 | 'logo': 'protractor.png'
46 | },
47 | {
48 | 'title': 'Bootstrap',
49 | 'url': 'http://getbootstrap.com/',
50 | 'description': 'Bootstrap is the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web.',
51 | 'logo': 'bootstrap.png'
52 | },
53 | {
54 | 'title': 'Angular UI Bootstrap',
55 | 'url': 'http://angular-ui.github.io/bootstrap/',
56 | 'description': 'Bootstrap components written in pure AngularJS by the AngularUI Team.',
57 | 'logo': 'ui-bootstrap.png'
58 | }
59 | ];
60 |
61 | this.getTec = getTec;
62 |
63 | function getTec() {
64 | return data;
65 | }
66 | }
67 |
68 | })();
69 |
--------------------------------------------------------------------------------
/client/gulp/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 | var browserSyncSpa = require('browser-sync-spa');
9 |
10 | var util = require('util');
11 |
12 | var proxyMiddleware = require('http-proxy-middleware');
13 | var exec = require('child_process').exec;
14 |
15 | function browserSyncInit(baseDir, browser) {
16 | browser = browser === undefined ? 'default' : browser;
17 |
18 | var routes = null;
19 | if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
20 | routes = {
21 | '/bower_components': 'bower_components'
22 | };
23 | }
24 |
25 | var server = {
26 | baseDir: baseDir,
27 | routes: routes,
28 | middleware: [
29 | proxyMiddleware('/api', { target: 'http://localhost:3000' })
30 | ]
31 | };
32 |
33 | /*
34 | * You can add a proxy to your backend by uncommenting the line bellow.
35 | * You just have to configure a context which will we redirected and the target url.
36 | * Example: $http.get('/users') requests will be automatically proxified.
37 | *
38 | * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.0.5/README.md
39 | */
40 | // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', proxyHost: 'jsonplaceholder.typicode.com'});
41 |
42 | browserSync.instance = browserSync.init({
43 | port: 9000,
44 | startPath: '/',
45 | server: server,
46 | browser: browser
47 | });
48 | }
49 |
50 | browserSync.use(browserSyncSpa({
51 | selector: '[ng-app]'// Only needed for angular apps
52 | }));
53 |
54 | gulp.task('serve', ['watch'], function () {
55 | browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
56 | });
57 |
58 | gulp.task('serve:dist', ['build'], function () {
59 | browserSyncInit(conf.paths.dist);
60 | });
61 |
62 | gulp.task('serve:e2e', ['inject'], function () {
63 | browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
64 | });
65 |
66 | gulp.task('serve:e2e-dist', ['build'], function () {
67 | browserSyncInit(conf.paths.dist, []);
68 | });
69 |
70 | gulp.task('rails', function() {
71 | exec("../bin/rails s");
72 | });
73 |
74 | gulp.task('serve:full-stack', ['rails', 'serve']);
75 |
--------------------------------------------------------------------------------
/client/gulp/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var $ = require('gulp-load-plugins')({
8 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
9 | });
10 |
11 | gulp.task('partials', function () {
12 | return gulp.src([
13 | path.join(conf.paths.src, '/app/**/*.html'),
14 | path.join(conf.paths.tmp, '/serve/app/**/*.html')
15 | ])
16 | .pipe($.minifyHtml({
17 | empty: true,
18 | spare: true,
19 | quotes: true
20 | }))
21 | .pipe($.angularTemplatecache('templateCacheHtml.js', {
22 | module: 'angularRails',
23 | root: 'app'
24 | }))
25 | .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
26 | });
27 |
28 | gulp.task('html', ['inject', 'partials'], function () {
29 | var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false });
30 | var partialsInjectOptions = {
31 | starttag: '',
32 | ignorePath: path.join(conf.paths.tmp, '/partials'),
33 | addRootSlash: false
34 | };
35 |
36 | var htmlFilter = $.filter('*.html');
37 | var jsFilter = $.filter('**/*.js');
38 | var cssFilter = $.filter('**/*.css');
39 | var assets;
40 |
41 | return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
42 | .pipe($.inject(partialsInjectFile, partialsInjectOptions))
43 | .pipe(assets = $.useref.assets())
44 | .pipe($.rev())
45 | .pipe(jsFilter)
46 | .pipe($.ngAnnotate())
47 | .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
48 | .pipe(jsFilter.restore())
49 | .pipe(cssFilter)
50 | .pipe($.csso())
51 | .pipe(cssFilter.restore())
52 | .pipe(assets.restore())
53 | .pipe($.useref())
54 | .pipe($.revReplace())
55 | .pipe(htmlFilter)
56 | .pipe($.minifyHtml({
57 | empty: true,
58 | spare: true,
59 | quotes: true,
60 | conditionals: true
61 | }))
62 | .pipe(htmlFilter.restore())
63 | .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
64 | .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
65 | });
66 |
67 | // Only applies for fonts from bower dependencies
68 | // Custom fonts are handled by the "other" task
69 | gulp.task('fonts', function () {
70 | return gulp.src($.mainBowerFiles())
71 | .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
72 | .pipe($.flatten())
73 | .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/')));
74 | });
75 |
76 | gulp.task('other', function () {
77 | var fileFilter = $.filter(function (file) {
78 | return file.stat.isFile();
79 | });
80 |
81 | return gulp.src([
82 | path.join(conf.paths.src, '/**/*'),
83 | path.join('!' + conf.paths.src, '/**/*.{html,css,js}')
84 | ])
85 | .pipe(fileFilter)
86 | .pipe(gulp.dest(path.join(conf.paths.dist, '/')));
87 | });
88 |
89 | gulp.task('clean', function (done) {
90 | $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')], { force: true }, done);
91 | });
92 |
93 | gulp.task('build', ['html', 'fonts', 'other']);
94 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PostgreSQL. Versions 8.2 and up are supported.
2 | #
3 | # Install the pg driver:
4 | # gem install pg
5 | # On OS X with Homebrew:
6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7 | # On OS X with MacPorts:
8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9 | # On Windows:
10 | # gem install pg
11 | # Choose the win32 build.
12 | # Install PostgreSQL and put its /bin directory on your path.
13 | #
14 | # Configure Using Gemfile
15 | # gem 'pg'
16 | #
17 | default: &default
18 | adapter: postgresql
19 | encoding: unicode
20 | # For details on connection pooling, see rails configuration guide
21 | # http://guides.rubyonrails.org/configuring.html#database-pooling
22 | pool: 5
23 |
24 | development:
25 | <<: *default
26 | database: railsAngular_development
27 |
28 | # The specified database role being used to connect to postgres.
29 | # To create additional roles in postgres see `$ createuser --help`.
30 | # When left blank, postgres will use the default role. This is
31 | # the same name as the operating system user that initialized the database.
32 | #username: railsAngular
33 |
34 | # The password associated with the postgres role (username).
35 | #password:
36 |
37 | # Connect on a TCP socket. Omitted by default since the client uses a
38 | # domain socket that doesn't need configuration. Windows does not have
39 | # domain sockets, so uncomment these lines.
40 | #host: localhost
41 |
42 | # The TCP port the server listens on. Defaults to 5432.
43 | # If your server runs on a different port number, change accordingly.
44 | #port: 5432
45 |
46 | # Schema search path. The server defaults to $user,public
47 | #schema_search_path: myapp,sharedapp,public
48 |
49 | # Minimum log levels, in increasing order:
50 | # debug5, debug4, debug3, debug2, debug1,
51 | # log, notice, warning, error, fatal, and panic
52 | # Defaults to warning.
53 | #min_messages: notice
54 |
55 | # Warning: The database defined as "test" will be erased and
56 | # re-generated from your development database when you run "rake".
57 | # Do not set this db to the same as development or production.
58 | test:
59 | <<: *default
60 | database: railsAngular_test
61 |
62 | # As with config/secrets.yml, you never want to store sensitive information,
63 | # like your database password, in your source code. If your source code is
64 | # ever seen by anyone, they now have access to your database.
65 | #
66 | # Instead, provide the password as a unix environment variable when you boot
67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
68 | # for a full rundown on how to provide these environment variables in a
69 | # production deployment.
70 | #
71 | # On Heroku and other platform providers, you may have a full connection URL
72 | # available as an environment variable. For example:
73 | #
74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
75 | #
76 | # You can use this database configuration with:
77 | #
78 | # production:
79 | # url: <%= ENV['DATABASE_URL'] %>
80 | #
81 | production:
82 | <<: *default
83 | database: railsAngular_production
84 | username: railsAngular
85 | password: <%= ENV['RAILSANGULAR_DATABASE_PASSWORD'] %>
86 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (4.2.3)
5 | actionpack (= 4.2.3)
6 | actionview (= 4.2.3)
7 | activejob (= 4.2.3)
8 | mail (~> 2.5, >= 2.5.4)
9 | rails-dom-testing (~> 1.0, >= 1.0.5)
10 | actionpack (4.2.3)
11 | actionview (= 4.2.3)
12 | activesupport (= 4.2.3)
13 | rack (~> 1.6)
14 | rack-test (~> 0.6.2)
15 | rails-dom-testing (~> 1.0, >= 1.0.5)
16 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
17 | actionview (4.2.3)
18 | activesupport (= 4.2.3)
19 | builder (~> 3.1)
20 | erubis (~> 2.7.0)
21 | rails-dom-testing (~> 1.0, >= 1.0.5)
22 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
23 | activejob (4.2.3)
24 | activesupport (= 4.2.3)
25 | globalid (>= 0.3.0)
26 | activemodel (4.2.3)
27 | activesupport (= 4.2.3)
28 | builder (~> 3.1)
29 | activerecord (4.2.3)
30 | activemodel (= 4.2.3)
31 | activesupport (= 4.2.3)
32 | arel (~> 6.0)
33 | activesupport (4.2.3)
34 | i18n (~> 0.7)
35 | json (~> 1.7, >= 1.7.7)
36 | minitest (~> 5.1)
37 | thread_safe (~> 0.3, >= 0.3.4)
38 | tzinfo (~> 1.1)
39 | arel (6.0.2)
40 | builder (3.2.2)
41 | erubis (2.7.0)
42 | globalid (0.3.5)
43 | activesupport (>= 4.1.0)
44 | i18n (0.7.0)
45 | json (1.8.3)
46 | loofah (2.0.2)
47 | nokogiri (>= 1.5.9)
48 | mail (2.6.3)
49 | mime-types (>= 1.16, < 3)
50 | mime-types (2.6.1)
51 | mini_portile (0.6.2)
52 | minitest (5.7.0)
53 | nokogiri (1.6.6.2)
54 | mini_portile (~> 0.6.0)
55 | pg (0.18.2)
56 | rack (1.6.4)
57 | rack-test (0.6.3)
58 | rack (>= 1.0)
59 | rails (4.2.3)
60 | actionmailer (= 4.2.3)
61 | actionpack (= 4.2.3)
62 | actionview (= 4.2.3)
63 | activejob (= 4.2.3)
64 | activemodel (= 4.2.3)
65 | activerecord (= 4.2.3)
66 | activesupport (= 4.2.3)
67 | bundler (>= 1.3.0, < 2.0)
68 | railties (= 4.2.3)
69 | sprockets-rails
70 | rails-api (0.4.0)
71 | actionpack (>= 3.2.11)
72 | railties (>= 3.2.11)
73 | rails-deprecated_sanitizer (1.0.3)
74 | activesupport (>= 4.2.0.alpha)
75 | rails-dom-testing (1.0.6)
76 | activesupport (>= 4.2.0.beta, < 5.0)
77 | nokogiri (~> 1.6.0)
78 | rails-deprecated_sanitizer (>= 1.0.1)
79 | rails-html-sanitizer (1.0.2)
80 | loofah (~> 2.0)
81 | rails_12factor (0.0.3)
82 | rails_serve_static_assets
83 | rails_stdout_logging
84 | rails_serve_static_assets (0.0.4)
85 | rails_stdout_logging (0.0.3)
86 | railties (4.2.3)
87 | actionpack (= 4.2.3)
88 | activesupport (= 4.2.3)
89 | rake (>= 0.8.7)
90 | thor (>= 0.18.1, < 2.0)
91 | rake (10.4.2)
92 | spring (1.3.6)
93 | sprockets (3.2.0)
94 | rack (~> 1.0)
95 | sprockets-rails (2.3.2)
96 | actionpack (>= 3.0)
97 | activesupport (>= 3.0)
98 | sprockets (>= 2.8, < 4.0)
99 | thor (0.19.1)
100 | thread_safe (0.3.5)
101 | tzinfo (1.2.2)
102 | thread_safe (~> 0.1)
103 |
104 | PLATFORMS
105 | ruby
106 |
107 | DEPENDENCIES
108 | pg
109 | rails (= 4.2.3)
110 | rails-api
111 | rails_12factor
112 | spring
113 |
114 | BUNDLED WITH
115 | 1.10.5
116 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like
20 | # NGINX, varnish or squid.
21 | # config.action_dispatch.rack_cache = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress JavaScripts and CSS.
28 | config.assets.js_compressor = :uglifier
29 | # config.assets.css_compressor = :sass
30 |
31 | # Do not fallback to assets pipeline if a precompiled asset is missed.
32 | config.assets.compile = false
33 |
34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets,
35 | # yet still be able to expire them through the digest params.
36 | config.assets.digest = true
37 |
38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
39 |
40 | # Specifies the header that your server uses for sending files.
41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | # config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | # config.log_tags = [ :subdomain, :uuid ]
53 |
54 | # Use a different logger for distributed setups.
55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
61 | # config.action_controller.asset_host = 'http://assets.example.com'
62 |
63 | # Ignore bad email addresses and do not raise email delivery errors.
64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65 | # config.action_mailer.raise_delivery_errors = false
66 |
67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68 | # the I18n.default_locale when a translation cannot be found).
69 | config.i18n.fallbacks = true
70 |
71 | # Send deprecation notices to registered listeners.
72 | config.active_support.deprecation = :notify
73 |
74 | # Use default logging formatter so that PID and timestamp are not suppressed.
75 | config.log_formatter = ::Logger::Formatter.new
76 |
77 | # Do not dump schema after migrations.
78 | config.active_record.dump_schema_after_migration = false
79 | end
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | The purpose of this guide is to teach how to create an Angular app that uses a Rails::API Backend. We'll build the client-side app using Gulp and deploy to Heroku with the source all contained in the same repository. This is a great stack that utilizes the robustness of Rails as a JSON API and the agility of Angular as a client-side application.
2 |
3 | I've grown very fond of using Rails as a single-purpose JSON API for it's great authentication (devise) and testing capability (RSpec, Capybara). Angular is great at consuming RESTful APIs using the official [ngResource](https://docs.angularjs.org/api/ngResource) module.
4 |
5 | ## Prerequisites
6 |
7 | * RVM / A Stable Ruby Version
8 | * Yeoman, Gulp, Bower `npm install -g yo gulp bower`
9 |
10 | ## Bootstrap the API
11 |
12 | We'll be using the [Rails::API gem](https://github.com/rails-api/rails-api) for this application as we'll be using Rails as a RESTFul, JSON API. Since we're using Angular as the client application we don't need much of the middleware or asset management Rails provides by default. Rails::API slims-down the backend and gives us exactly what we need.
13 |
14 | $ gem install rails-api
15 |
16 | Generate a new Rails::API app and run bundler.
17 |
18 | ```
19 | $ rails-api new railsAngular --database=postgresql
20 |
21 | $ cd railsAngular
22 |
23 | $ bundle install
24 | ```
25 |
26 | #### Scaffold a resource and test API
27 |
28 | Scaffold a resource that we'll wire up to Angular and consume. This can be anything you want but I recommend including at least a few fields to test with.
29 |
30 | ```
31 | $ bin/rails g scaffold articles title:string body:text
32 | ```
33 |
34 | #### API Scope and Namespace
35 |
36 | Modify routes to use a scope of /api and a namespace of V1. We'll also make it default to JSON.
37 |
38 | ```
39 | scope '/api' do
40 | namespace :v1, defaults: { format: :json } do
41 |
42 | resources :articles, except: [:new, :edit]
43 |
44 | end
45 | end
46 | ```
47 |
48 | Update the controller to use the new V1 namespace.
49 |
50 | ```
51 | $ mkdir app/controllers/v1
52 |
53 | $ mv app/controllers/articles_controller.rb app/controllers/v1/articles_controller.rb
54 | ```
55 |
56 | In app/controllers/v1/articles_controller.rb, update
57 |
58 | ```
59 | class ArticlesController
60 | ```
61 |
62 | to:
63 |
64 | ```
65 | class V1::ArticlesController
66 | ```
67 |
68 | #### Setup Database
69 |
70 | I recommend creating a few seeds by adding to db/seeds.rb. This will help us when we test the end point to request the resources and we'll eventually see these through our UI.
71 |
72 | ```
73 | $ echo "Article.create(title: 'Test Article', body: 'A test article. Cool!')" >> db/seeds.rb
74 |
75 | $ rake db:create
76 |
77 | $ rake db:migrate
78 |
79 | $ rake db:seed
80 | ```
81 |
82 | #### Test the Configuration
83 |
84 | Test the new resource by navigating to articles GET #index.
85 |
86 | ```
87 | $ bin/rails s
88 | ```
89 |
90 | http://localhost:3000/api/v1/articles
91 |
92 | You should see nice JSON output of the Article resources.
93 |
94 | ## Deploy to Heroku
95 |
96 | Refer to the Heroku documentation about getting started with a Rails application. We install the rails_12factor gem for production, initialize the git repo, and then create the Heroku app. After we deploy to Heroku, we need to setup the Rails database.
97 |
98 | https://devcenter.heroku.com/articles/getting-started-with-rails4
99 |
100 | ```
101 | $ echo "gem 'rails_12factor', group: :production" >> Gemfile
102 |
103 | $ bundle install
104 |
105 | $ git init
106 |
107 | $ git add .
108 |
109 | $ git commit -m "init"
110 |
111 | $ heroku create
112 |
113 | $ git push heroku master
114 |
115 | $ heroku run rake db:migrate && rake db:seed
116 |
117 | $ heroku open
118 | ```
119 |
120 | Open up your Heroku app in a browser and again test the resource you created. The output should be exactly as we see it when we deploy locally.
121 |
122 | https://fast-forest-6196.herokuapp.com/api/v1/articles
123 |
124 | ## Create Angular App
125 |
126 | We're going to put all of the source of our front-end Angular application in a client directory. We'll install the generator-gulp-angular, make the directory, and bootstrap the application.
127 |
128 | ```
129 | $ npm install -g generator-gulp-angular
130 |
131 | $ mkdir client && cd $_
132 |
133 | $ yo gulp-angular railsAngular
134 | ```
135 |
136 | I selected most of the default values. You can customize the options as you see fit.
137 |
138 | * Angular 1.4.0
139 | * all default Angular modules
140 | * jQuery 2.x
141 | * ngResource
142 | * UI Router
143 | * Bootstrap
144 | * Angular UI Bootstrap
145 | * Base CSS
146 | * Standard Javascript
147 | * Standard HTML
148 |
149 | #### Setup local development environment
150 |
151 | For the local dev environment, I followed this tutorial:
152 |
153 | http://www.angularonrails.com/how-to-wire-up-ruby-on-rails-and-angularjs-as-a-single-page-application-gulp-version/
154 |
155 | Next, we're going to edit client/gulp/server.js to do several things.
156 |
157 | 1. Configure [proxy middleware](https://www.npmjs.com/package/http-proxy-middleware#star) with a context of '/api' that targets the Rails API application.
158 | 2. Set browserSync to run the client application on port 9000 since the Rails server uses 3000 by default.
159 | 3. Add Gulp tasks to start rails and to start the entire full-stack application.
160 |
161 | ```
162 | ...
163 |
164 | var proxyMiddleware = require('http-proxy-middleware');
165 | + var exec = require('child_process').exec;
166 |
167 | ...
168 |
169 | var server = {
170 | baseDir: baseDir,
171 | routes: routes,
172 | + middleware: [
173 | + proxyMiddleware('/api', { target: 'http://localhost:3000' })
174 | + ]
175 | };
176 |
177 | /*
178 | * You can add a proxy to your backend by uncommenting the line bellow.
179 | * You just have to configure a context which will we redirected and the target url.
180 | * Example: $http.get('/users') requests will be automatically proxified.
181 | *
182 | * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.0.5/README.md
183 | */
184 | // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', proxyHost: 'jsonplaceholder.typicode.com'});
185 |
186 | browserSync.instance = browserSync.init({
187 | + port: 9000,
188 | startPath: '/',
189 | server: server,
190 | browser: browser
191 | });
192 |
193 | ...
194 |
195 | + gulp.task('rails', function() {
196 | + exec("../bin/rails s");
197 | + });
198 |
199 | + gulp.task('serve:full-stack', ['rails', 'serve']);
200 | ```
201 |
202 | Now we can run our new serve:full-stack task from the client directory and test that our client app loads and that the API is accessible from the proxy.
203 |
204 | ```
205 | $ gulp serve:full-stack
206 | ```
207 |
208 | Let's test the 3 changes we made:
209 |
210 | 1. The front-end server should come up on port 9000 instead of 3000.
211 | 2. Test the proxy by navigating to http://localhost:9000/api/v1/articles. You should see a response from the API.
212 | 3. Rails is running on port 3000.
213 |
214 | ## Acessing the API from Client
215 |
216 | Edit client/app/src/index.route.js and add an 'articles' state:
217 |
218 | ```
219 | ...
220 | .state('articles', {
221 | url: '/articles',
222 | templateUrl: 'app/components/articles/articles.html',
223 | controller: 'ArticlesController'
224 | });
225 | ...
226 | ```
227 |
228 | I like to create "Vertical Modules" where all of the source realted to a component is contained in the same directory.
229 |
230 | ```
231 | $ mkdir client/src/app/components/articles
232 | ```
233 |
234 | Create a factory that will consume our API resource. By default, Angular resource does not have a method for updating so we'll create that. See the [official documentation for Angular $resource](https://docs.angularjs.org/api/ngResource/service/$resource#creating-a-custom-put-request) for more information.
235 |
236 | ```
237 | $ touch client/src/app/components/articles/articles.factory.js
238 | ```
239 |
240 | ```
241 | 'use strict';
242 |
243 | angular.module('angularRails')
244 | .factory('Articles', function ($resource) {
245 | return $resource('api/v1/articles/:articleId', {
246 | articleId: '@id'
247 | }, {
248 | update: {
249 | method: 'PUT'
250 | }
251 | });
252 | });
253 |
254 | ```
255 |
256 | Now we'll create a basic view that will iterate over all of the article resources and output their basic attributes.
257 |
258 | ```
259 | touch src/app/components/articles/articles.html
260 | ```
261 |
262 | ```
263 |
264 |
265 |
266 |
267 |
268 |
269 |
Articles
270 |
271 |
272 |
{{ article.title }}
273 |
{{ article.body }}
274 |
275 |
276 |
277 |
278 | ```
279 |
280 | We'll create a basic controller that uses the factory to query all of the Article resources.
281 |
282 | ```
283 | $ touch src/app/components/articles/articles.controller.js
284 | ```
285 |
286 | ```
287 | angular.module('angularRails')
288 | .controller('ArticlesController', function ($scope, Articles) {
289 |
290 | Articles.query(function (res) {
291 | $scope.articles = res;
292 | });
293 |
294 | });
295 | ```
296 |
297 | #### Test the UI
298 |
299 | Run gulp:full-stack again and navigate to your newly created state on the frontend (http://localhost:9000/#/articles). Your Angular application should be correctly consuming and display the Article resources.
300 |
301 | 
302 |
303 | ## Deploy the entire stack to Heroku
304 |
305 | Let's deploy our awesome full-stack application! We want Gulp to build the production files in a directory called 'public'. Rails, by default, will load these files at the root route.
306 |
307 | Edit the gulpfile conf file at client/gulp/conf.js and change
308 |
309 | ```
310 | exports.paths = {
311 | src: 'src',
312 | dist: 'dist',
313 | tmp: '.tmp',
314 | e2e: 'e2e'
315 | };
316 | ```
317 |
318 | to:
319 |
320 | ```
321 | exports.paths = {
322 | src: 'src',
323 | dist: '../public',
324 | tmp: '.tmp',
325 | e2e: 'e2e'
326 | };
327 | ```
328 |
329 | Set the force option to true for the clean task in client/gulp/build.js. This is required since the build directory is a level up from the client directory and a warning is thrown when we clean the directory for building.
330 |
331 | ```
332 | gulp.task('clean', function (done) {
333 | $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')], { force: true }, done);
334 | });
335 | ```
336 |
337 | Now we can build the application and you should see the built application in public.
338 |
339 | ```
340 | $ gulp build
341 | ```
342 |
343 | #### Configure Multiple Heroku Buildpacks
344 |
345 | The following section refers to the [official Heroku documentation on using multiple buildpacks](https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app).
346 |
347 | Add the ruby and node heroku buildpacks:
348 |
349 | ```
350 | $ heroku buildpacks:set https://github.com/heroku/heroku-buildpack-ruby
351 | $ heroku buildpacks:add --index 1 https://github.com/heroku/heroku-buildpack-nodejs
352 | ```
353 |
354 | Verify that you have the correct buildpack configuration. Note that you must have both buildpacks set and in the correct order. This ensures that it will install and build our client app but use the Rails app as the webserver.
355 |
356 | ```
357 | $ heroku buildpacks
358 | ➜ railsAngular git:(master) heroku buildpacks
359 | === angular-rails-gulp-tutorial Buildpack URLs
360 | 1. https://github.com/heroku/heroku-buildpack-nodejs
361 | 2. https://github.com/heroku/heroku-buildpack-ruby
362 | ```
363 |
364 | Set the node environment to production.
365 |
366 | ```
367 | $ heroku config:set NODE_ENV=production
368 | ```
369 |
370 | Create a Procfile in root of project to run the WEBrick webserver
371 |
372 | ```
373 | $ bin/rails s
374 | ```
375 |
376 | Deploy!
377 |
378 | ```
379 | $ git add -A
380 |
381 | $ git commit -m "testing deploy"
382 |
383 | $ git push heroku
384 | ```
385 |
386 | Open your deployed Heroku application and verify everything works!
387 |
388 | ```
389 | $ heroku open
390 | ```
391 |
392 | Navigate to articles page. It should work exactly as you have it locally but with built client assets.
393 |
394 | https://angular-rails-gulp-tutorial.herokuapp.com/#/articles
395 |
396 | 
397 |
398 | ## Debugging
399 |
400 | If you're having issues with the deploy I recommend tailing the Heroku logs while you deploy. Additionally, I recommend installing papertrail. Papertrail allows you to search the logs and offers many features that can be useful as your application grows.
401 |
402 | ```
403 | $ heroku logs --tail
404 |
405 | $ heroku addons:create papertrail
406 |
407 | $ heroku addons:open papertrail
408 | ```
409 |
410 | 
411 |
412 | ## Where to go from here?
413 |
414 | Start building your application using the lightweight Rails API framework and the ever-popular Angular! This is a great stack that utilizes the robustness of Rails as a JSON API and the agility of Angular as a client-side application. There are many things you can do to improve your application: setup a test infrastructure, configure Puma as a webserver, and configure a logging application (I recommend papertrail as it works well with Heroku)
415 |
416 | Have fun!
417 |
--------------------------------------------------------------------------------