Slide 1

Slide 1 text

Sane User Interfaces for Ruby on Rails @this_ahmed

Slide 2

Slide 2 text

Developer Happiness

Slide 3

Slide 3 text

Rails Way non-Rails UI Solutions

Slide 4

Slide 4 text

Action View Powerful & Easy to use Rails Way non-Rails

Slide 5

Slide 5 text

Growing Pains

Slide 6

Slide 6 text

<%= system('gem install rails') %>

Slide 7

Slide 7 text

Action View • Global Scope • Hard to maintain • Hard to reason about • Hard to test

Slide 8

Slide 8 text

Are we missing an object?

Slide 9

Slide 9 text

Action View Decorator

Slide 10

Slide 10 text

class CommentDecorator < SimpleDelegator def gravatar end def timestamp end def author_name end end CommentDecorator.new(comment)

Slide 11

Slide 11 text

Fat Decorator

Slide 12

Slide 12 text

Comment Decorator Comment Box Reaction Buttons Comment Form Comment Thread Embeds Components

Slide 13

Slide 13 text

class ViewComponent include ActiveModel::Model attr_accessor :context def render context.render( partial: "components/#{template_path}", locals: { component: self } ) end private def template_path self.class.to_s.underscore end end

Slide 14

Slide 14 text

# object at app/view_components/comment_box.rb # template at app/views/components/_comment_box.html.erb class CommentBox < ViewComponent end class CommentThread < ViewComponent end class CommentForm < ViewComponent end

Slide 15

Slide 15 text

def render_component(component, props) component .new({ context: self }.merge(props)) .render end <%= render_component(CommentForm, {}) %> Helper

Slide 16

Slide 16 text

View Components • Single Responsibility • Easier to reason about • Easier to test • Not great for interactivity (need JavaScript)

Slide 17

Slide 17 text

Action View Decorator Components Sprinkle JavaScript

Slide 18

Slide 18 text

Action View Decorator Components Front-end MVC Rails Way non-Rails

Slide 19

Slide 19 text

Duplicate effort • Routing • Data Layer (models, JSON, etc.) • Authentication / Authorization • Deploy multiple apps • CORS, proxying worth it sometimes

Slide 20

Slide 20 text

JavaScript Fatigue

Slide 21

Slide 21 text

Emerging Themes …

Slide 22

Slide 22 text

Web Components Components

Slide 23

Slide 23 text

Modern Tooling

Slide 24

Slide 24 text

Testing & Quality

Slide 25

Slide 25 text

Server-Side Rendering • Static Content • Search Engine Crawlers: SEO • First render performance

Slide 26

Slide 26 text

Action View Decorator Components JS Components Modern JS Tools Server Rendering Rails Way non-Rails

Slide 27

Slide 27 text

Pragmatic Middle

Slide 28

Slide 28 text

Action View Turbolinks, Caching, etc. Server Render by Default

Slide 29

Slide 29 text

Modern JS Tools Action View Proper Front-End Tools

Slide 30

Slide 30 text

JS Components Modern JS Tools Action View Components for Complex UIs

Slide 31

Slide 31 text

Example: copy to clipboard

Slide 32

Slide 32 text

# Install Node Version Manager: # https://github.com/creationix/nvm $ nvm install --lts $ nvm use --lts $ brew install yarn --without-node

Slide 33

Slide 33 text

$ cd my-rails-app $ yarn init # creates package.json skip for Rails 5.1+

Slide 34

Slide 34 text

# config/initializers/assets.rb Rails.application.config.assets.paths << Rails.root.join('node_modules') # gitignore /node_modules/* skip for Rails 5.1+

Slide 35

Slide 35 text

$ yarn add primer-tooltips

Slide 36

Slide 36 text

/*package.json*/ { "name": "my-rails-app", "private": true, "dependencies": { "primer-tooltips": “^1.4.1" }, "engines": { "node": “>=8.9.0”, "yarn": “^1.3.2" } }

Slide 37

Slide 37 text

app/assets/stylesheets/application.css *= require primer-tooltips/index The file is located here: node_modules/primer-tooltips/index.scss

Slide 38

Slide 38 text

Copy

Slide 39

Slide 39 text

how do we add copy behaviour?

Slide 40

Slide 40 text

Convert to React Component

Slide 41

Slide 41 text

# Rails 5.1+ $ rails new myapp --webpack=react # Rails version < 5.1 gem ‘webpacker' $ bin/rails webpacker:install $ bin/rails webpacker:install:react

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

Entry app/javascript/packs/application.js

Slide 44

Slide 44 text

require dependencies

Slide 45

Slide 45 text

<%= javascript_pack_tag 'application' %>
 <%= asset_pack_path 'images/one.png' %>

Slide 46

Slide 46 text

• Converts latest JavaScript to a target client • For example: target browsers which have >1% usage

Slide 47

Slide 47 text

// app/javascript/packs/application.js document.addEventListener("turbolinks:load", reactLoader); document.addEventListener("turbolinks:before- render", reactUnloader);

Slide 48

Slide 48 text

$ yarn add clipboard

Slide 49

Slide 49 text

// app/javascript/clipboard_button/index.jsx import React from 'react' import ReactDOM from 'react-dom' import Clipboard from "clipboard"; class ClipboardButton extends React.Component { ... } export default ClipboardButton

Slide 50

Slide 50 text

Register Component in ERB def react_component(component_name, props) tag.div data: { component_name: component_name, react_props: props.to_json } end <%= react_component('ClipboardButton', { content: 'copy this!' }) %>

Slide 51

Slide 51 text

// app/javascript/utils/react_loader.js import ClipboardButton from “../clipboard_button/ index"; const RegisteredComponentTypes = { ClipboardButton: ClipboardButton }; Register Component in JS

Slide 52

Slide 52 text

// app/javascript/utils/react_loader.js const containers = document.querySelectorAll("[data-component- name]"); containers.forEach(function(el) { const componentName = el.getAttribute("data-component-name"); const type = RegisteredComponentTypes[componentName]; mountComponent(type, el); });

Slide 53

Slide 53 text

// app/javascript/utils/react_loader.js const extractProps = el => JSON.parse(el.getAttribute("data-react-props")); const mountComponent = function(ComponentType, node) { const props = extractProps(node); const element = ; ReactDOM.render(element, node); };

Slide 54

Slide 54 text

// app/javascript/utils/react_unloader.js const components = document.querySelectorAll("[data- component-name]"); components.forEach(function(node) { ReactDOM.unmountComponentAtNode(node); });

Slide 55

Slide 55 text

JS Components Modern JS Tools Action View Options…

Slide 56

Slide 56 text

Sane User Interfaces for Ruby on Rails @this_ahmed