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

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

Jul 15, 2015

Download

Software

Daniel Spector
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Crossing the Bridge: Connecting Rails and your Front-end Framework

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

FrameworkFramework

@danielspecs@danielspecs

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

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

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

Instead we want thisInstead we want this

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

A GameplanA GameplanUnderstand the tradeoffs you'll makeDeeply integrate your framework with RailsShare data in a consistent and maintainable way

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

Daniel SpectorDaniel SpectorSoftware Engineer at Lifebooker

@danielspecs

spector.io

Flatiron School

Rails/Javascript/Swift/Clojure

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

What are youWhat are yougetting yourselfgetting yourself

into?into?

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

Javascript. Wat.Javascript. Wat.> [] + {}

=> [object Object]

> {} + []

=> 0

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

Courtesy of Alex Matchneer

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

Always thinkAlways thinkabout theabout the

bigger picturebigger picture

You will encounter a lot ofYou will encounter a lot oftradeoffs.tradeoffs.

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

Some of the fun thatSome of the fun thatawaits...awaits...

Duplicated modelsDuplicated modelsSeparate codebasesSeparate codebasesComplexityComplexity

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

But myBut myclients/customers/formerclients/customers/formercat's owner demands it!cat's owner demands it!

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

Er, no.Er, no.

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

What do people want?What do people want?

Maintainable,Maintainable,sustainable,sustainable,performantperformantapplicationsapplications

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

But now that you'veBut now that you'vebeen warned...been warned...

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

Holy s**t canHoly s**t canyou make someyou make some

awesomeawesomeapplications.applications.

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

RecapRecap

Never lose sight of the ultimate goalNever lose sight of the ultimate goal

Understand the tradeoffs that willUnderstand the tradeoffs that willcomecome

There may be a solutionThere may be a solution

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

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

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

What we're going to be building:What we're going to be building:

TodoMVC on RailsTodoMVC on Rails

Scaffolding out the same application in each ofScaffolding out the same application in each ofthe frameworks makes it easy to referencethe frameworks makes it easy to reference

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

The Frameworks:The Frameworks:

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

Now let's have a lookNow let's have a lookat Angularat Angular

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

Developed by GoogleDeveloped by Google

Two-way data bindingTwo-way data binding

Dependency InjectionDependency Injection

But... Angular 2But... Angular 2

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

First, let's get ourFirst, let's get ourRails project API readyRails project API ready

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

# 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) endend

# config/routes.rb

namespace :api, :defaults => {:format => :json} do resources :todos, only: [:index, :create]end

# app/models/todo.rb

class Todo < ActiveRecord::Baseend

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

There's no official AngularThere's no official Angularintegration with Rails... integration with Rails...

So that's a perfect opportunity toSo that's a perfect opportunity to

try out Bower.try out Bower.

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

Created by Twitter

One centralized location for packages

Can be integrated with Rails via the bower-rails gem

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

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

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

How can we manageHow can we manageour client-side dataour client-side datato make it easy toto make it easy to

work with?work with?

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

ngResource is an optional libraryngResource is an optional libraryto map basic CRUD actions toto map basic CRUD actions to

specific method calls.specific method calls.

Let's scaffold out a basicLet's scaffold out a basicAngular and see how we canAngular and see how we can

integrate ngResourceintegrate ngResource

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

// 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.rbconfig.assets.paths << "#{Rails.root}/app/assets/templates"

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

Now we can set up our factory toNow we can set up our factory tohold our resource and pass it tohold our resource and pass it toour controller and our templateour controller and our template

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

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

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

// app/assets/templates/index.html

<p>Hello RailsConf!</p><ul> <li ng-repeat="t in todos"> {{t.name}} </li></ul>

<form ng-submit="addTodo()"> <input placeholder="I need to..." ng-model="newTodo" autofocus></form>

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

RecapRecap

1. Data binding in Angular is powerfulData binding in Angular is powerful2. ngResource makes requests easyngResource makes requests easy3. Multiple API calls to initialize application canMultiple API calls to initialize application can

get trickyget tricky

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

Ember! Yeah!Ember! Yeah!

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

Created by Tom Dale andCreated by Tom Dale andYehuda KatzYehuda KatzMade for large, ambitiousMade for large, ambitiousapplicationsapplicationsFavors convention overFavors convention overconfigurationconfigurationEmber Data is absolutelyEmber Data is absolutelywonderfulwonderful

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

Ember-CLIEmber-CLIThe new standard for developing Ember apps

Integrates with Rails via the ember-cli-rails gem

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

What we'll beWhat we'll beworking withworking with

class User < ActiveRecord::Base has_many :todosend

class Todo < ActiveRecord::Base belongs_to :userend

class EmberController < ApplicationController def preload @todos = current_user.todos endend

Rails.application.routes.draw do root 'ember#preload'end

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

# Gemfile

gem "active_model_serializers"gem "ember-cli-rails"

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

$ rails g serializer todo create app/serializers/todo_serializer.rb

class TodoSerializer < ActiveModel::Serializer embed :ids, include: true attributes :id, :nameend

{ "todos": [ { "id": 1, "name": "Milk" }, { "id": 2, "name": "Coffee" }, { "id": 3, "name": "Cupcakes" } ]}

Create a new serializer, set it up to workCreate a new serializer, set it up to workwith Emberwith Ember

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

Now that we're all set up, whatNow that we're all set up, whatare we trying to accomplish?are we trying to accomplish?

Instead of using JSON calls, weInstead of using JSON calls, wewant to preload Emberwant to preload Ember

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

Why?Why?Minimize round trips to the serverMinimize round trips to the server

Bootstrapping the app means a quickerBootstrapping the app means a quicker

experience for our usersexperience for our users

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

# 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) endend

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

We'll pass this to Ember via theWe'll pass this to Ember via thewindow.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

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

Let's get setup withLet's get setup withour client-side codeour client-side code

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

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

$ ember new frontend --skip-gitversion: 0.2.3installing 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...

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

$ ember g resource todosversion: 0.2.3installing create app/models/todo.jsinstalling create tests/unit/models/todo-test.jsinstalling create app/routes/todos.js create app/templates/todos.hbsinstalling create tests/unit/routes/todos-test.js

$ ember g adapter applicationversion: 0.2.3installing create app/adapters/application.jsinstalling create tests/unit/adapters/application-test.js

$ ember g serializer applicationversion: 0.2.3installing create app/serializers/application.jsinstalling create tests/unit/serializers/application-test.js

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

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

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

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

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

Ember will initializeEmber will initializeEmber Data objectsEmber Data objectsfor us, inferring thefor us, inferring the

correct type from thecorrect type from theroot of the JSONroot of the JSON

responseresponse

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

Now we can use our route to find the dataand 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

<h2>Todo:</h2><ul> {{#each todo in model}} <li>{{todo.name}}</li> {{/each}}</ul>

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

RecapRecap

1. Don't fight Ember. Use conventionsDon't fight Ember. Use conventionslike AMSlike AMS

2. Preloading is extremely powerfulPreloading is extremely powerful3. Avoiding spinners and loading screens

means a great experience

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

So let's talkSo let's talkabout React.about React.

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

Developed by Facebook

One-way data binding

Virtual DOM

Isomorphic Javascript

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

No initial API call, noNo initial API call, nopreloading, renderpreloading, renderstraight from thestraight from the

server.server.

http://bensmithett.com/server-rendered-react-components-in-rails/

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

# 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) endend

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

Use the react-rails gemUse the react-rails gem

# Gemfile

gem "react-rails"

$ rails g react:install

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

# app/views/todos/index.html.erb

<%= react_component('Todos', {:load => @load.to_json}, {:prerender => true}) %>

Really nice viewReally nice viewhelpershelpers

The magic lives in {:prerender => true}

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

React is builtReact is builtaround componentsaround components

Each component should have one isolatedresponsibility.

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

# 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 ( <div> <ul> <TodosList todos={this.state.todos} /> </ul>

<TodoForm form={this.state.form} onNewTodo={this.newTodo} /> </div> ); }});

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

TodosList ComponentTodosList Component// app/assets/javascripts/components/_todos_list.js.jsx

var TodosList = React.createClass({ render: function () { var allTodos = this.props.todos.map(function (todo) { return <Todo name={todo.name} /> });

return ( <div> { allTodos } </div> ) }});

// app/assets/javascripts/components/_todo.js.jsx

var Todo = React.createClass({ render: function (){ return ( <div> <li>{this.props.name}</li> </div> ) }});

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

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 ( <form ref="form"action={this.props.form.action} method="post" onSubmit={this.handleSubmit}> <input type="hidden" name={this.props.form.csrf_param } value={this.props.form.csrf_token}/> <input ref="name" name="todo[name]" placeholder="I need to do..." /> <button type="submit">New Todo</button> </form> ) }});

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

RecapRecap1. Each component should have only oneEach component should have only one

responsibilityresponsibility2. Prerender on the server for SEO, usability andPrerender on the server for SEO, usability and

other benefitsother benefits3. UJS will mount your component and take careUJS will mount your component and take care

of the handoffof the handoff

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

IsomorphicIsomorphicJavascript is theJavascript is the

future.future.React

Ember 2.0 with FastBootAngular 2?

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

Where we've come from andWhere we've come from andwhere we are goingwhere we are going

1. Constructing API's that serve JSON to the clientConstructing API's that serve JSON to the client

2. Preload your data on startup to avoid spinners andPreload your data on startup to avoid spinners andloading screensloading screens

3. Server-side rendering for SEO, startup time and a greatServer-side rendering for SEO, startup time and a greatuser experienceuser experience

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

Thanks!Thanks!

Would love to answer any questions

Please feel free to tweet at me or get intouch in any other way.

@danielspecs

spector.io