Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

Hyperloop elevator pitch

Barrie Hadfield edited this page Jan 7, 2017 · 10 revisions

Hyperloop elevator pitch

Hyperloop is an Isomorphic Web Application Framework which empowers a developer to build a modern interactive web applications quickly. Hyperloop includes full access to the entire Rails ecosystem and all front end JavaScript libraries including React - all using one language - Ruby.

The goal of HyperLoop is to allow the developer to write code that is directed toward solving the user's needs in the most straight forward manner, without redundant code, unnecessary APIs, or artificial separation between client and server.

This guide will take you through all the main Hyperloop architectural layers.

Components

We have a Component based DSL which wraps React and is compiled to JavaScript using Opal. We also have access to the full universe of JavaScript libraries seamlessly from within our Ruby code. Our DSL looks like this:

class BookList < React::Component::Base
  render(UL) do
    10.times do |i|
      LI { "Number #{i}" }.on(:click) do
          Alert "You clicked me!"
      end 
    end
  end
end

Models

We have full access to the ActiveRecord models on the client or the server which means we can use the models directly within our Components without needing the abstraction of an API:

class BookList < React::Component::Base
  # Display each book in our catalog 
  render(UL) do
    Book.all.each do |book|
      LI { "Add #{book.name}" }.on(:click) do
           ...
        end
      end 
    end
  end
end

Changes made to Models on a client or server are automatically synchronized to all other authorized connected clients using ActionCable, pusher.com or polling. The synchronization is completely automatic and magical to behold.

Operations

We encapsulate our business logic into Operations so we do not need to overload our Models or Components and can write business logic code which will run on the client or server:

class BookList < React::Component::Base
  # Display each book in our catalog unless its already in the cart basket.
  # When the user clicks on a book, add it the Basket.
  render(UL) do
    Book.all.each do |book|
      LI { "Add #{book.name}" }.on(:click) do
        AddBookToBasket(book: book) do |outcome|
          alert "Failed to add the book" unless outcome.success?
        end
      end unless acting_user.basket.include? book
    end
  end
end

class AddBookToBasket < HyperLoop::Operation
  # Add a book to the basket and add to users watchlist
  param :book, type: Book

  def execute
    acting_user.basket << book 
    AddToActingUsersWatchList(book: params.book)
  end
end

Some Operations only make sense on the server so we have a concept of a Server Operation which will only execute on the server but is available to the client or server code. Hyperloop handles the mechanics of this seamlessly without the need for an API. This blending of client-server functionality is a key attribute of this architecture.

class AddToActingUsersWatchList < HyperLoop::ServerOperation
  # Add a book the the current acting_user's watch list, and
  # send an initial email about the book.

  # This Operation can only be run on the server because it is going
  # to sending mail, so it inherits from ServerOperation.

  # only clients with a logged in user can access this action.
  allow_operation { acting_user }

  param :book, type: Book

  def execute
    return if acting_user.watch_list.include? params.book
    WatchListMailer.new_book_email WatchList.create(user: acting_user,  book: params.book) 
  end
end

Authorization

Access to our Models and Operations is controlled by Policies that describe what the current acting user is authorized to do.

# the regulation can be defined directly in the operation class:
class UserSignup < HyperLoop::Operation
  allow_operation { acting_user } # must have a logged in user to do this operation
  # allow_operation { acting_user.admin? } # would mean only admins could do the operation
end

# alternatively the regulations can be grouped in a general policy class (i.e. ApplicationPolicy or OperationPolicy)
class OperationPolicy
  allow_operation(UserSignup, SomeOtherOperation) { acting_user }
end

# or even better
class OperationPolicy
  UserSignup.allow_operation { acting_user }
end

Routing

We also provide a DSL which wraps ReactRouter for full single page application routing and passing data between components:

route("/", mounts: App, index: Home) do
  route("about")
  route("books") do
    redirect('book/:id').to { | params | "/books/#{params[:id]}" }
  end
  route(mounts: Books) do
    route("books/:id")
  end
end

Bennifits

  • One language - Ruby everywhere - reduces complexity and lets developers build a solutions quickly
  • Client side execution for distributed processing and a rich interactive user experience
  • Full power of Rails, React and the entire JavaScript universe
  • Components, Models and Operations architecture encapsulate functionality for clean, predictable, testable code
Clone this wiki locally