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

Sane User Interfaces for Ruby on Rails

Ahmed Omran
November 15, 2017

Sane User Interfaces for Ruby on Rails

Ruby on Rails is a great experience on the backend. Unfortunately, the user interface is a sad story. Your Rails views are the riskiest part of your application; they are hard to maintain, hard to reason about and hard to test. Some turn to single-page applications, but they can be expensive and time-consuming to build. What if we took full advantage of the best of rails and combined it with small testable UI components using modern tools and techniques.

Ahmed Omran

November 15, 2017
Tweet

More Decks by Ahmed Omran

Other Decks in Programming

Transcript

  1. Sane User Interfaces for Ruby on Rails @this_ahmed

  2. Developer Happiness

  3. Rails Way non-Rails UI Solutions

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

  5. Growing Pains

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

  7. Action View • Global Scope • Hard to maintain •

    Hard to reason about • Hard to test
  8. Are we missing an object?

  9. Action View Decorator

  10. class CommentDecorator < SimpleDelegator def gravatar end def timestamp end

    def author_name end end CommentDecorator.new(comment)
  11. Fat Decorator

  12. Comment Decorator Comment Box Reaction Buttons Comment Form Comment Thread

    Embeds Components
  13. 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
  14. # 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
  15. def render_component(component, props) component .new({ context: self }.merge(props)) .render end

    <%= render_component(CommentForm, {}) %> Helper
  16. View Components • Single Responsibility • Easier to reason about

    • Easier to test • Not great for interactivity (need JavaScript)
  17. Action View Decorator Components Sprinkle JavaScript

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

  19. Duplicate effort • Routing • Data Layer (models, JSON, etc.)

    • Authentication / Authorization • Deploy multiple apps • CORS, proxying worth it sometimes
  20. JavaScript Fatigue

  21. Emerging Themes …

  22. Web Components Components

  23. Modern Tooling

  24. Testing & Quality

  25. Server-Side Rendering • Static Content • Search Engine Crawlers: SEO

    • First render performance
  26. Action View Decorator Components JS Components Modern JS Tools Server

    Rendering Rails Way non-Rails
  27. Pragmatic Middle

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

  29. Modern JS Tools Action View Proper Front-End Tools

  30. JS Components Modern JS Tools Action View Components for Complex

    UIs
  31. Example: copy to clipboard

  32. # Install Node Version Manager: # https://github.com/creationix/nvm $ nvm install

    --lts $ nvm use --lts $ brew install yarn --without-node
  33. $ cd my-rails-app $ yarn init # creates package.json skip

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

    Rails 5.1+
  35. $ yarn add primer-tooltips

  36. /*package.json*/ { "name": "my-rails-app", "private": true, "dependencies": { "primer-tooltips": “^1.4.1"

    }, "engines": { "node": “>=8.9.0”, "yarn": “^1.3.2" } }
  37. app/assets/stylesheets/application.css *= require primer-tooltips/index The file is located here: node_modules/primer-tooltips/index.scss

  38. <button class="tooltipped tooltipped-w" aria-label="Copy to Clipboard!"> Copy </button>

  39. how do we add copy behaviour?

  40. Convert to React Component

  41. # Rails 5.1+ $ rails new myapp --webpack=react # Rails

    version < 5.1 gem ‘webpacker' $ bin/rails webpacker:install $ bin/rails webpacker:install:react
  42. None
  43. Entry app/javascript/packs/application.js

  44. require dependencies

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

  46. • Converts latest JavaScript to a target client • For

    example: target browsers which have >1% usage
  47. // app/javascript/packs/application.js document.addEventListener("turbolinks:load", reactLoader); document.addEventListener("turbolinks:before- render", reactUnloader);

  48. $ yarn add clipboard

  49. // 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
  50. 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!' }) %>
  51. // app/javascript/utils/react_loader.js import ClipboardButton from “../clipboard_button/ index"; const RegisteredComponentTypes =

    { ClipboardButton: ClipboardButton }; Register Component in JS
  52. // 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); });
  53. // 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 = <ComponentType {...props} />; ReactDOM.render(element, node); };
  54. // app/javascript/utils/react_unloader.js const components = document.querySelectorAll("[data- component-name]"); components.forEach(function(node) { ReactDOM.unmountComponentAtNode(node);

    });
  55. JS Components Modern JS Tools Action View Options…

  56. Sane User Interfaces for Ruby on Rails @this_ahmed