This tutorial is the third in a series of four:
- Chapter 1: Hyperloop Showcase
- Chapter 2: Hyperloop Showcase Template
- Chapter 3: Hyperloop Showcase with Heroku (Debug way)
- Chapter 4: Hyperloop Showcase with Heroku (Fast way)
For more information: HyperLoop Web site
This tutorial is a very detailed list of commands and file modifications to achieve a working deployment of the Hyperloop showcase on a Heroku Server.
Of course the Heroku deployment could be faster, but this tutorial allows programmers to check step by step where an error could happen.
The live Heroku demo deployment is here : https://damp-harbor-10403.herokuapp.com
The Hyperloop showcase has already been deployed successfully on a classic independent VPS (Rails 5.01, Capistrano and Passenger) : http://http://hyperloop-showcase.pixagency.com
heroku login
git clone https://github.com/heroku/ruby-getting-started.git
cd ruby-getting-started
heroku create
git push heroku master
heroku open
The initial Heroku app is coming with a Rails 4.x version. We are going to upgrade it to a Rails 5.0.1 version.
#Gemfile
source 'https://rubygems.org'
ruby '2.3.1'
gem 'rails', '~> 5.0.1'
gem 'puma', '3.7.0'
gem "puma_worker_killer"
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.2'
gem 'pg', '0.18.4'
gem 'jquery-rails'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'react-rails', '1.4.2'
gem 'hyper-rails', '0.4.1'
gem 'opal-rails', '0.9.1'
gem 'opal-browser', '0.2.0'
gem 'hyper-react', '0.11.0'
gem 'hyper-router', '2.4.0'
gem 'therubyracer', platforms: :ruby
gem 'opal_hot_reloader', git: 'https://github.com/fkchang/opal-hot-reloader.git'
gem 'foreman'
group :development, :test do
gem 'byebug', platform: :mri
end
group :development do
gem 'web-console', '>= 3.3.0'
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
#config/environments/development.rb
Rails.application.configure do
config.cache_classes = false
config.eager_load = false
config.consider_all_requests_local = true
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800'
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
config.active_support.deprecation = :log
config.active_record.migration_error = :page_load
config.assets.debug = true
config.assets.quiet = true
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
#config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.assets.js_compressor = :uglifier
config.assets.compile = true
config.log_level = :debug
config.log_tags = [ :request_id ]
config.action_mailer.perform_caching = false
config.i18n.fallbacks = true
config.active_support.deprecation = :notify
config.log_formatter = ::Logger::Formatter.new
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
config.active_record.dump_schema_after_migration = false
end
rm Gemfile.lock
bundle install
heroku local web
Browse to http://localhost:5000
#config/database.yml
development:
<<: *default
database: ruby-getting-started_development
username: your-username
password: your-password
bundle exec rake db:create db:migrate
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
//app/assets/javascripts/application.js
//= require 'components'
//= require 'react_ujs'
//= require 'jquery'
//= require 'jquery_ujs'
//= require 'turbolinks'
//= require_tree .
Opal.load('components');
#app/views/components.rb
require 'opal'
require 'hyper-react'
require 'react/react-source'
require 'reactrb/auto-import'
if React::IsomorphicHelpers.on_opal_client?
require 'opal-jquery'
require 'browser'
require 'browser/interval'
require 'browser/delay'
# add any additional requires that can ONLY run on client here
end
require_tree './components'
rails g hyperloop:component Home::Show
#config/routes.rb
root 'home#show'
#app/controllers/home_controller.rb
class HomeController < ApplicationController
def show
end
end
#app/views/home/show.html.erb
<%= react_component 'Home::Show', {}, { prerender: false } %>
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
#Gemfile
gem 'hyper-mesh', '0.5.3'
bundle update
#app/views/components.rb
require 'opal'
require 'reactrb/auto-import'
require 'react/react-source'
require 'hyper-react'
if React::IsomorphicHelpers.on_opal_client?
require 'opal-jquery'
require 'browser'
require 'browser/interval'
require 'browser/delay'
# add any additional requires that can ONLY run on client here
end
require 'hyper-mesh'
require 'models'
require_tree './components'
#config/application.rb
...
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.eager_load_paths += %W(#{config.root}/app/models/public)
config.autoload_paths += %W(#{config.root}/app/models/public)
config.assets.paths << ::Rails.root.join('app', 'models').to_s
end
...
#routes.rb
mount HyperMesh::Engine => '/rr'
root 'home#show'
#app/models/models.rb
require_tree './public' if RUBY_ENGINE == 'opal'
#app/models/public/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
#app/policies/application_policy.rb
# Policies regulate access to your public models
# The following policy will open up full access (but only in development)
# The policy system is very flexible and powerful. See the documentation
# for complete details.
class ApplicationPolicy
# Allow any session to connect:
always_allow_connection
# Send all attributes from all public models
regulate_all_broadcasts { |policy| policy.send_all }
# Allow all changes to public models
allow_change(to: :all, on: [:create, :update, :destroy]) { true }
end
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
npm init
npm install webpack --save-dev
npm install webpack -g
#/.gitignore
/node_modules
// webpack.config.js
var path = require("path");
module.exports = {
context: __dirname,
entry: {
client_only: "./webpack/client_only.js",
client_and_server: "./webpack/client_and_server.js"
},
output: {
path: path.join(__dirname, 'app', 'assets', 'javascripts', 'webpack'),
filename: "[name].js",
publicPath: "/webpack/"
},
module: {
loaders: [
// add any loaders here
]
},
resolve: {
modules: [
path.join(__dirname, "src"),
"node_modules"
]
},
};
// webpack/client_only.js
// any packages that depend specifically on the DOM go here
// for example the Webpack CSS loader generates code that will break prerendering
console.log('client_only.js loaded');
// webpack/client_and_server.js
// all other packages that you can run on both server (prerendering) and client go here
// most well behaved packages can be required here
console.log('client_and_server.js loaded');
webpack
// app/assets/javascripts/application.js
//= require 'components'
//= require 'react_ujs'
//= require 'jquery'
//= require 'jquery_ujs'
//= require 'webpack/client_only'
//= require 'turbolinks'
//= require_tree .
Opal.load('components');
#app/views/components.rb
require 'opal'
require 'hyper-react'
require 'webpack/client_and_server.js'
require 'reactrb/auto-import'
if React::IsomorphicHelpers.on_opal_client?
require 'opal-jquery'
require 'browser'
require 'browser/interval'
require 'browser/delay'
# add any additional requires that can ONLY run on client here
end
require 'hyper-mesh'
require 'models'
require_tree './components'
npm install react --save
npm install react-dom --save
// webpack/client_and_server.js
ReactDOM = require('react-dom')
React = require('react')
console.log('client_and_server.js loaded');
webpack
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
npm install react-player --save
//webpack/client_and_server.js
ReactDOM = require('react-dom')
React = require('react')
console.log('client_and_server.js loaded');
ReactPlayer = require('react-player')
webpack
#app/views/components/home/show.rb
def render
div do
ReactPlayer(url: 'https://www.youtube.com/embed/FzCsDVfPQqk',
playing: true
)
end
end
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
npm install bootstrap react-bootstrap --save
//webpack/client_and_server.js
ReactDOM = require('react-dom')
React = require('react')
console.log('client_and_server.js loaded');
ReactPlayer = require('react-player')
ReactBootstrap = require('react-bootstrap')
webpack
#app/views/components/home/show.rb
def render
ReactBootstrap::Button(bsStyle: 'success', bsSize: "small") do
'Success'
end.on(:click) do
alert('you clicked me!')
end
end
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
npm install css-loader file-loader style-loader url-loader --save-dev
// webpack.config.js
var path = require("path");
module.exports = {
context: __dirname,
entry: {
client_only: "./webpack/client_only.js",
client_and_server: "./webpack/client_and_server.js"
},
output: {
path: path.join(__dirname, 'app', 'assets', 'javascripts', 'webpack'),
filename: "[name].js",
publicPath: "/webpack/"
},
module: {
rules: [
{ test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader"
}
]
},
{ test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
},
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
},
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader'
},
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
}
]
},
resolve: {
modules: [
path.join(__dirname, "src"),
"node_modules"
]
},
};
//webpack/client_only.js
require('bootstrap/dist/css/bootstrap.css');
npm install bootstrap --save
webpack
#app/views/components/home/show.rb
module Components
module Home
class Show < React::Component::Base
def say_hello(i)
alert "Hello from number #{i}"
end
def render
div do
ReactBootstrap::Navbar(bsStyle: :inverse) do
ReactBootstrap::Nav() do
ReactBootstrap::NavbarBrand() do
a(href: '#') { 'Hyperloop Showcase' }
end
ReactBootstrap::NavDropdown(
eventKey: 1,
title: 'Things',
id: :drop_down
) do
(1..5).each do |n|
ReactBootstrap::MenuItem(href: '#',
key: n,
eventKey: "1.#{n}"
) do
"Number #{n}"
end.on(:click) { say_hello(n) }
end
end
end
end
end
end
end
end
end
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
npm install bootswatch
// webpack/client_only.js
// any packages that depend specifically on the DOM go here
// for example the Webpack CSS loader generates code that will break prerendering
console.log('client_only.js loaded');
require('bootstrap/dist/css/bootstrap.css');
require('bootswatch/superhero/bootstrap.min.css');
webpack
heroku local web
Browse http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
rails g model Planevent
# db/migrate/..create_planevents.rb
class CreatePlanevents < ActiveRecord::Migration[5.0]
def change
create_table :planevents do |t|
t.string :planeventtitle
t.text :description
t.timestamps
end
end
end
rails db:migrate
mv app/models/planevent.rb app/models/public
mv app/models/application_record.rb app/models/public
heroku run rake --trace db:migrate
# views/components/home/show.rb
module Components
module Home
class Show < React::Component::Base
def say_hello(i)
alert "Hello from number #{i}"
end
def render
div do
ReactBootstrap::Navbar(bsStyle: :inverse) do
ReactBootstrap::Nav() do
ReactBootstrap::NavbarBrand() do
a(href: '') { 'HyperLoop Showcase' }
end
ReactBootstrap::NavDropdown(
eventKey: 1,
title: 'Things',
id: :drop_down
) do
(1..5).each do |n|
ReactBootstrap::MenuItem(href: '#',
key: n,
eventKey: "1.#{n}"
) do
"Number #{n}"
end.on(:click) { say_hello(n) }
end
end
end
end
div.container do
PlaneventsList()
end
end
end
end
end
end
#app/views/components/home/planeventslist.rb
module Components
module Home
class PlaneventsList < React::Component::Base
define_state new_planevent: Hash.new { |h, k| h[k] = '' }
before_mount do
# note that this will lazy load posts
# and only the fields that are needed will be requested
@planevents = Planevent.all
@planevent_attributes = Hash[ 'planeventtitle' => 'Event Name', 'description' => 'Description']
end
def render
div.container do
div.row do
new_planevent
end
hr
div.row do
table_render
end
end
end
def table_render
div.col_md_12 do
br
table(class: "table table-hover") do
thead do
tr do
td.text_muted.small(width: '33%') { "NAME" }
td.text_muted.small(width: '33%') { "DESCRIPTION" }
td.text_muted.small(width: '33%') { "DATE" }
end
end
tbody do
@planevents.reverse.each do |planevent|
PlaneventsListItem(planevent: planevent)
end
end
end
end
end
def new_planevent
@planevent_attributes.each do |attribute, value|
ReactBootstrap::FormGroup() do
ReactBootstrap::ControlLabel() do
value
end
ReactBootstrap::FormControl(
value: state.new_planevent[attribute],
type: :text,
).on(:change) { |e|
state.new_planevent![attribute] = e.target.value
}
end
end
ReactBootstrap::Button(bsStyle: :primary) do
"Create an new event"
end.on(:click) { save_new_planevent }
end
def save_new_planevent
Planevent.create(state.new_planevent) do |result|
# note that save is a promise so this code will only run after the save
# yet react will move onto the code after this (before the save happens)
alert "unable to save" unless result == true
end
state.new_planevent.clear
end
end
class PlaneventsListItem < React::Component::Base
param :planevent
def render
tr do
td(width: '33%') { params.planevent.planeventtitle }
td(width: '33%') { params.planevent.description }
td(width: '33%') { params.planevent.created_at.to_s }
end
end
end
end
end
heroku local web
Browse to http://localhost:5000
git add .
git commit -m "Demo"
git push heroku master
heroku open
#config/initializers/hyper_mesh.rb
HyperMesh.configuration do |config|
config.transport = :simple_poller
end
heroku local web
Browse to http://localhost:5000 with 2 different browsers and add a new Event. Both browsers should update the view with this new Event.
git add .
git commit -m "Demo"
git push heroku master
heroku open