Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Crossing the Bridge: Connecting Rails and your Front-end Framework

Crossing the Bridge: Connecting Rails and your Front-end Framework

Presented at Railsconf 2015 by Daniel Spector, @danielspecs.

Crossing the Bridge explores tools, patterns and best practices to connect your Javascript MVC framework to Rails in the most seamless way possible. The talk progresses from demonstrating the standard API request cycle to preloading data to your client-side framework to rendering your javascript on the server. It explores Isomorphic Javascript and ways of implementing it with Rails.

Daniel Spector

April 22, 2015
Tweet

Other Decks in Programming

Transcript

  1. Crossing the Bridge:
    Crossing the Bridge:
    Connecting Rails and your Front-end
    Connecting Rails and your Front-end
    Framework
    Framework
    @danielspecs
    @danielspecs

    View Slide

  2. This is what we're trying to avoid
    This is what we're trying to avoid

    View Slide

  3. Instead we want this
    Instead we want this

    View Slide

  4. A Gameplan
    A Gameplan
    Understand the tradeoffs you'll make
    Deeply integrate your framework with Rails
    Share data in a consistent and maintainable way

    View Slide

  5. Daniel Spector
    Daniel Spector
    Software Engineer at Lifebooker
    @danielspecs
    spector.io
    Flatiron School
    Rails/Javascript/Swift/Clojure

    View Slide

  6. What are you
    What are you
    getting yourself
    getting yourself
    into?
    into?

    View Slide

  7. Javascript. Wat.
    Javascript. Wat.
    > [] + {}
    => [object Object]
    > {} + []
    => 0

    View Slide

  8. Courtesy of Alex Matchneer

    View Slide

  9. Always think
    Always think
    about the
    about the
    bigger picture
    bigger picture
    You will encounter a lot of
    You will encounter a lot of
    tradeoffs.
    tradeoffs.

    View Slide

  10. Some of the fun that
    Some of the fun that
    awaits...
    awaits...
    Duplicated models
    Duplicated models
    Separate codebases
    Separate codebases
    Complexity
    Complexity

    View Slide

  11. But my
    But my
    clients/customers/former
    clients/customers/former
    cat's owner demands it!
    cat's owner demands it!

    View Slide

  12. Er, no.
    Er, no.

    View Slide

  13. What do people want?
    What do people want?
    Maintainable,
    Maintainable,
    sustainable,
    sustainable,
    performant
    performant
    applications
    applications

    View Slide

  14. But now that you've
    But now that you've
    been warned...
    been warned...

    View Slide

  15. Holy s**t can
    Holy s**t can
    you make some
    you make some
    awesome
    awesome
    applications.
    applications.

    View Slide

  16. Recap
    Recap
    Never lose sight of the ultimate goal
    Never lose sight of the ultimate goal
    Understand the tradeoffs that will
    Understand the tradeoffs that will
    come
    come
    There may be a solution
    There may be a solution

    View Slide

  17. Now, let's dive in
    Now, let's dive in

    View Slide

  18. What we're going to be building:
    What we're going to be building:
    TodoMVC on Rails
    TodoMVC on Rails
    Scaffolding out the same application in each of
    Scaffolding out the same application in each of
    the frameworks makes it easy to reference
    the frameworks makes it easy to reference

    View Slide

  19. The Frameworks:
    The Frameworks:

    View Slide

  20. Now let's have a look
    Now let's have a look
    at Angular
    at Angular

    View Slide

  21. Developed by Google
    Developed by Google
    Two-way data binding
    Two-way data binding
    Dependency Injection
    Dependency Injection
    But... Angular 2
    But... Angular 2

    View Slide

  22. First, let's get our
    First, let's get our
    Rails project API ready
    Rails project API ready

    View Slide

  23. # app/controllers/api/todos_controller.rb
    class Api::TodosController < ApplicationController
    respond_to :json
    def index
    @todos = Todo.all
    render json: @todos
    end
    def create
    @todo = Todo.create(todo_params)
    render json: @todo
    end
    private
    def todo_params
    params.require(:todo).permit(:item)
    end
    end
    # config/routes.rb
    namespace :api, :defaults => {:format => :json} do
    resources :todos, only: [:index, :create]
    end
    # app/models/todo.rb
    class Todo < ActiveRecord::Base
    end

    View Slide

  24. There's no official Angular
    There's no official Angular
    integration with Rails...
    integration with Rails...
    So that's a perfect opportunity to
    So that's a perfect opportunity to
    try out Bower.
    try out Bower.

    View Slide

  25. Created by Twitter
    One centralized location for packages
    Can be integrated with Rails via the bower-rails gem

    View Slide

  26. $ npm install -g bower
    # Gemfile
    gem "bower-rails", "~> 0.9.2"
    $ rails g bower_rails:initialize
    # Bowerfile
    # Puts to ./vendor/assets/bower_components
    asset "angular"
    asset "angular-resource"
    asset "angular-route"

    View Slide

  27. How can we manage
    How can we manage
    our client-side data
    our client-side data
    to make it easy to
    to make it easy to
    work with?
    work with?

    View Slide

  28. ngResource is an optional library
    ngResource is an optional library
    to map basic CRUD actions to
    to map basic CRUD actions to
    specific method calls.
    specific method calls.
    Let's scaffold out a basic
    Let's scaffold out a basic
    Angular and see how we can
    Angular and see how we can
    integrate ngResource
    integrate ngResource

    View Slide

  29. // app/assets/main.js
    // This is the main entry point for our application.
    var Todo = angular
    .module('todo', ['ngResource', 'ngRoute'])
    .config(['$routeProvider', '$httpProvider', function($routeProvider, $httpProvider) {
    // We need to add this for Rails CSRF token protection
    $httpProvider.defaults.headers.common['X-CSRF-Token'] =
    $('meta[name=csrf-token]').attr('content');
    // Right now we have one route but we could have as many as we want
    $routeProvider
    .when("/",
    {templateUrl: "../assets/index.html",
    controller: "TodoCtrl"})
    }]);
    # config/application.rb
    config.assets.paths << "#{Rails.root}/app/assets/templates"

    View Slide

  30. Now we can set up our factory to
    Now we can set up our factory to
    hold our resource and pass it to
    hold our resource and pass it to
    our controller and our template
    our controller and our template

    View Slide

  31. // app/assets/factories/todoFactory.js
    Todo.factory("Todo", function($resource){
    return $resource("/api/todos/:id", { id: "@id" });
    });
    // app/assets/controllers/todoCtrl.js
    Todo.controller("TodoCtrl", function($scope, Todo){
    $scope.todos = Todo.query();
    $scope.addTodo = function() {
    $scope.data = new Todo();
    $scope.data.name = $scope.newTodo.trim();
    Todo.save($scope.data, function(){
    $scope.todos = Todo.query();
    $scope.newTodo = '';
    })
    }
    });

    View Slide

  32. // app/assets/templates/index.html
    Hello RailsConf!


    {{t.name}}





    View Slide

  33. Recap
    Recap
    1. Data binding in Angular is powerful
    Data binding in Angular is powerful
    2. ngResource makes requests easy
    ngResource makes requests easy
    3. Multiple API calls to initialize application can
    Multiple API calls to initialize application can
    get tricky
    get tricky

    View Slide

  34. Ember! Yeah!
    Ember! Yeah!

    View Slide

  35. Created by Tom Dale and
    Created by Tom Dale and
    Yehuda Katz
    Yehuda Katz
    Made for large, ambitious
    Made for large, ambitious
    applications
    applications
    Favors convention over
    Favors convention over
    configuration
    configuration
    Ember Data is absolutely
    Ember Data is absolutely
    wonderful
    wonderful

    View Slide

  36. Ember-CLI
    Ember-CLI
    The new standard for developing Ember apps
    Integrates with Rails via the ember-cli-rails gem

    View Slide

  37. What we'll be
    What we'll be
    working with
    working with
    class User < ActiveRecord::Base
    has_many :todos
    end
    class Todo < ActiveRecord::Base
    belongs_to :user
    end
    class EmberController < ApplicationController
    def preload
    @todos = current_user.todos
    end
    end
    Rails.application.routes.draw do
    root 'ember#preload'
    end

    View Slide

  38. # Gemfile
    gem "active_model_serializers"
    gem "ember-cli-rails"

    View Slide

  39. $ rails g serializer todo
    create app/serializers/todo_serializer.rb
    class TodoSerializer < ActiveModel::Serializer
    embed :ids, include: true
    attributes :id, :name
    end
    {
    "todos": [
    {
    "id": 1,
    "name": "Milk"
    },
    {
    "id": 2,
    "name": "Coffee"
    },
    {
    "id": 3,
    "name": "Cupcakes"
    }
    ]
    }
    Create a new serializer, set it up to work
    Create a new serializer, set it up to work
    with Ember
    with Ember

    View Slide

  40. Now that we're all set up, what
    Now that we're all set up, what
    are we trying to accomplish?
    are we trying to accomplish?
    Instead of using JSON calls, we
    Instead of using JSON calls, we
    want to preload Ember
    want to preload Ember

    View Slide

  41. Why?
    Why?
    Minimize round trips to the server
    Minimize round trips to the server
    Bootstrapping the app means a quicker
    Bootstrapping the app means a quicker
    experience for our users
    experience for our users

    View Slide

  42. # app/controllers/ember_controller.rb
    class EmberController < ApplicationController
    def preload
    @todos = current_user.todos
    preload! @todos, serializer: TodoSerializer
    end
    def preload!(data, opts = {})
    @preload ||= []
    data = prepare_data(data, opts)
    @preload << data unless data.nil?
    end
    def prepare_data(data, opts = {})
    data = data.to_a if data.respond_to? :to_ary
    data = [data] unless data.is_a? Array
    return if data.empty?
    options[:root] ||= data.first.class.to_s.underscore.pluralize
    options[:each_serializer] = options[:serializer] if options[:serializer]
    ActiveModel::ArraySerializer.new(data, options)
    end
    end

    View Slide

  43. We'll pass this to Ember via the
    We'll pass this to Ember via the
    window.
    window.
    # app/views/layouts/application.html.haml
    = stylesheet_link_tag :frontend
    :javascript
    window.preloadEmberData = #{(@preload || []).to_json};
    = include_ember_script_tags :frontend
    %body
    = yield
    github.com/hummingbird-me/hummingbird

    View Slide

  44. Let's get setup with
    Let's get setup with
    our client-side code
    our client-side code

    View Slide

  45. $ rails g ember-cli:init
    create config/initializers/ember.rb
    # config/initializer/ember.rb
    EmberCLI.configure do |config|
    config.app :frontend, path: Rails.root.join('frontend').to_s
    end
    $ ember new frontend --skip-git
    version: 0.2.3
    installing
    create .bowerrc
    create .editorconfig
    create .ember-cli
    create .jshintrc
    create .travis.yml
    create Brocfile.js
    create README.md
    create app/app.js
    create app/components/.gitkeep
    ...

    View Slide

  46. $ ember g resource todos
    version: 0.2.3
    installing
    create app/models/todo.js
    installing
    create tests/unit/models/todo-test.js
    installing
    create app/routes/todos.js
    create app/templates/todos.hbs
    installing
    create tests/unit/routes/todos-test.js
    $ ember g adapter application
    version: 0.2.3
    installing
    create app/adapters/application.js
    installing
    create tests/unit/adapters/application-test.js
    $ ember g serializer application
    version: 0.2.3
    installing
    create app/serializers/application.js
    installing
    create tests/unit/serializers/application-test.js

    View Slide

  47. // frontend/app/models/todo.js
    import DS from 'ember-data';
    var Todo = DS.Model.extend({
    name: DS.attr('string')
    });
    export default Todo;
    // frontend/app/adapters/application.js
    import DS from 'ember-data';
    export default DS.ActiveModelAdapter.extend({
    });

    View Slide

  48. // frontend/app/initializers/preload.js
    export function initialize(container) {
    if (window.preloadEmberData) {
    var store = container.lookup('store:main');
    window.preloadEmberData.forEach(function(item) {
    store.pushPayload(item);
    });
    }
    }
    export default {
    name: 'preload',
    after: 'store',
    initialize: initialize
    };

    View Slide

  49. Ember will initialize
    Ember will initialize
    Ember Data objects
    Ember Data objects
    for us, inferring the
    for us, inferring the
    correct type from the
    correct type from the
    root of the JSON
    root of the JSON
    response
    response

    View Slide

  50. Now we can use our route to find the data
    and render it via a template
    // frontend/app/router.js
    export default Router.map(function() {
    this.resource('todos', { path: '/' }, function() {});
    });
    // frontend/app/routes/todos/index.js
    export default Ember.Route.extend({
    model: function() {
    return this.store.all('todo')
    }
    });
    // frontend/app/templates/todos/index.hbs
    Todo:

    {{#each todo in model}}
    {{todo.name}}
    {{/each}}

    View Slide

  51. Recap
    Recap
    1. Don't fight Ember. Use conventions
    Don't fight Ember. Use conventions
    like AMS
    like AMS
    2. Preloading is extremely powerful
    Preloading is extremely powerful
    3. Avoiding spinners and loading screens
    means a great experience

    View Slide

  52. So let's talk
    So let's talk
    about React.
    about React.

    View Slide

  53. Developed by Facebook
    One-way data binding
    Virtual DOM
    Isomorphic Javascript

    View Slide

  54. No initial API call, no
    No initial API call, no
    preloading, render
    preloading, render
    straight from the
    straight from the
    server.
    server.
    http://bensmithett.com/server-rendered-react-components-in-rails/

    View Slide

  55. # app/controllers/todos_controller.rb
    class TodosController < ApplicationController
    def index
    @load = {
    :todos => current_user.todos,
    :form => {
    :action => todos_path,
    :csrf_param => request_forgery_protection_token,
    :csrf_token => form_authenticity_token
    }
    }
    end
    def create
    @todo = Todo.create(todo_params)
    render json: @todo
    end
    def todo_params
    params.require(:todo).permit(:name)
    end
    end

    View Slide

  56. Use the react-rails gem
    Use the react-rails gem
    # Gemfile
    gem "react-rails"
    $ rails g react:install

    View Slide

  57. # app/views/todos/index.html.erb
    <%= react_component('Todos',
    {:load => @load.to_json},
    {:prerender => true}) %>
    Really nice view
    Really nice view
    helpers
    helpers
    The magic lives in {:prerender => true}

    View Slide

  58. React is built
    React is built
    around components
    around components
    Each component should have one isolated
    responsibility.

    View Slide

  59. # app/assets/javascripts/components/_todos.js.jsx
    var Todos = React.createClass({
    getInitialState: function () {
    return JSON.parse(this.props.load);
    },
    newTodo: function ( formData, action ) {
    $.ajax({
    data: formData,
    url: action,
    type: "POST",
    dataType: "json",
    success: function (data) {
    this.setState({todos: this.state.todos.concat([data]});
    }.bind(this)
    });
    },
    render: function () {
    return (






    );
    }
    });

    View Slide

  60. TodosList Component
    TodosList Component
    // app/assets/javascripts/components/_todos_list.js.jsx
    var TodosList = React.createClass({
    render: function () {
    var allTodos = this.props.todos.map(function (todo) {
    return
    });
    return (

    { allTodos }

    )
    }
    });
    // app/assets/javascripts/components/_todo.js.jsx
    var Todo = React.createClass({
    render: function (){
    return (

    {this.props.name}

    )
    }
    });

    View Slide

  61. And now the form...
    And now the form...
    // app/assets/javascripts/components/_todo_form.js.jsx
    var TodoForm = React.createClass({
    handleSubmit: function (e) {
    e.preventDefault();
    var formData = $(this.refs.form.getDOMNode()).serialize();
    this.props.onNewTodo(formData, this.props.form.action);
    this.refs.name.getDOMNode().value = "";
    },
    render: function () {
    return (



    New Todo

    )
    }
    });

    View Slide

  62. Recap
    Recap
    1. Each component should have only one
    Each component should have only one
    responsibility
    responsibility
    2. Prerender on the server for SEO, usability and
    Prerender on the server for SEO, usability and
    other benefits
    other benefits
    3. UJS will mount your component and take care
    UJS will mount your component and take care
    of the handoff
    of the handoff

    View Slide

  63. Isomorphic
    Isomorphic
    Javascript is the
    Javascript is the
    future.
    future.
    React
    Ember 2.0 with FastBoot
    Angular 2?

    View Slide

  64. Where we've come from and
    Where we've come from and
    where we are going
    where we are going
    1. Constructing API's that serve JSON to the client
    Constructing API's that serve JSON to the client
    2. Preload your data on startup to avoid spinners and
    Preload your data on startup to avoid spinners and
    loading screens
    loading screens
    3. Server-side rendering for SEO, startup time and a great
    Server-side rendering for SEO, startup time and a great
    user experience
    user experience

    View Slide

  65. Thanks!
    Thanks!
    Would love to answer any questions
    Please feel free to tweet at me or get in
    touch in any other way.
    @danielspecs
    spector.io

    View Slide