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

More Decks by Ahmed Omran

Other Decks in Programming


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

    Hard to reason about • Hard to test
  2. class CommentDecorator < SimpleDelegator def gravatar end def timestamp end

    def author_name end end CommentDecorator.new(comment)
  3. 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
  4. # 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
  5. View Components • Single Responsibility • Easier to reason about

    • Easier to test • Not great for interactivity (need JavaScript)
  6. Duplicate effort • Routing • Data Layer (models, JSON, etc.)

    • Authentication / Authorization • Deploy multiple apps • CORS, proxying worth it sometimes
  7. # Install Node Version Manager: # https://github.com/creationix/nvm $ nvm install

    --lts $ nvm use --lts $ brew install yarn --without-node
  8. # Rails 5.1+ $ rails new myapp --webpack=react # Rails

    version < 5.1 gem ‘webpacker' $ bin/rails webpacker:install $ bin/rails webpacker:install:react
  9. • Converts latest JavaScript to a target client • For

    example: target browsers which have >1% usage
  10. // 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
  11. 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!' }) %>
  12. // 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); });
  13. // 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); };