Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Instead we want this Instead we want this

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

What are you What are you getting yourself getting yourself into? into?

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Courtesy of Alex Matchneer

Slide 9

Slide 9 text

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.

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Er, no. Er, no.

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Now, let's dive in Now, let's dive in

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

The Frameworks: The Frameworks:

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

# 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

Slide 24

Slide 24 text

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.

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

$ 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"

Slide 27

Slide 27 text

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?

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

// 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"

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

// 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 = ''; }) } });

Slide 32

Slide 32 text

// app/assets/templates/index.html

Hello RailsConf!

  • {{t.name}}

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Ember! Yeah! Ember! Yeah!

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

$ 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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

# 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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

$ 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 ...

Slide 46

Slide 46 text

$ 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

Slide 47

Slide 47 text

// 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({ });

Slide 48

Slide 48 text

// 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 };

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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}}

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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/

Slide 55

Slide 55 text

# 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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

# 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}

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

# 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 (
); } });

Slide 60

Slide 60 text

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}
  • ) } });

    Slide 61

    Slide 61 text

    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 ) } });

    Slide 62

    Slide 62 text

    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

    Slide 63

    Slide 63 text

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

    Slide 64

    Slide 64 text

    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

    Slide 65

    Slide 65 text

    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