File Structure
Hyperstack adds the following files and directories to your Rails application:
1
/app/hyperstack
2
/app/operations
3
/app/policies
4
/config/initializers/hyperstack.rb
5
/app/javascript/packs/client_and_server.js
6
/app/javascript/packs/client_only.js
Copied!
In addition there are configuration settings in existing Rails files that are explained in the next section. Below we cover the purpose of each these files, and their contents.

The /app/hyperstack/ Directory

Here lives all your Hyperstack code that will run on the client. Some of the subdirectories are isomorphic meaning the code is shared between the client and the server, other directories are client only.
Within the hyperstack directory there can be the following sub-directories:
  • components (client-only) is where your components live.
    Following Rails conventions a component with a class of Bar::None::FooManchu should be in a file named components/bar/none/foo_manchu.rb
  • models (isomorphic) is where ActiveRecord models are shared with the client. More on this below.
  • operations (isomorphic) is where Hyperstack Operations will live.
  • shared (isomorphic) is where you can put shared code that are not models or operations.
  • Any other subdirectory (such as libs and client-ops) will be considered client-only.

Sharing Models and Operations

Files in the hyperstack /models and /operations directories are loaded on the client and the server. So when you place a model's class definition in the hyperstack/models directory the class is available on the client.
Assuming:
1
# app/hyperstack/models/todo.rb
2
class Todo < ApplicationRecord
3
...
4
end
Copied!
Then
1
Todo.count # will return the same value on the client and the server
Copied!
See the Policy section below for how access to the actual data is controlled. Remember a Model describes some data, but the actual data is stored in the database, and protected by Policies.
Likewise Operations placed in the /operations directory can be run on the client or the server, or in the case of a ServerOp the operation can be invoked on the client, but will run on the server.
Hyperstack sets things up so that Rails will first look in the hyperstack /models and /operations directories, and then in the server only app/models and app/operations directories. So if you don't want some model shared you can just leave it in the normal app directory.

Splitting Class Definitions

There are cases where you would like split a class definition into its shared and server-only aspects. For example there may be code in a model that cannot be sensibly run on the client. Hyperstack augments the Rails dependency lookup mechanism so that when a file is found in a hyperstack directory we will also load any matching file in the normal app directory.
This works because Ruby classes are open, so that you can define a class (or module) in multiple places.

Server Side Operations

Operations are Hyperstack's way of providing Service Objects: classes that perform some operation not strictly belonging to a single model, and often involving other services such as remote APIs. The idea of Operations comes from the Trailblazer Framework.
As such Operations can be useful strictly on the server side, and so can be added to the app/operations directory.
Server side operations can also be remotely run from the client. Such operations are defined as subclasses of Hyperstack::ServerOp.
The right way to define a ServerOp is to place its basic definition including its parameter signature in the hyperstack/operations directory, and then placing the rest of the operation's definition in the app/operations directory.

Policies

Hyperstack uses Policies to define access rights to your models. Policies are placed in the app/policies directory. For example the policies for the Todo model would defined by the TodoPolicy class located at app/policies/todo_policy.rb Details on policies can be found Policy section of this document..

Example Directory Structure

1
└── app/
2
├── models/
3
│ └── user.rb # private section of User model
4
├── operations/
5
│ └── email_the_owner.rb # server code
6
├── hyperstack/
7
│ ├── components/
8
│ │ ├── app.rb
9
│ │ ├── edit_todo.rb
10
│ │ ├── footer.rb
11
│ │ ├── header.rb
12
│ │ ├── show_todo.rb
13
│ │ └── todo_index.rb
14
│ ├── models/
15
│ │ ├── application_record.rb # usually no need to split this
16
│ │ ├── todo.rb # note all of Todo definition is public
17
│ │ └── user.rb # user has a public and private section
18
│ └── operations/
19
│ └── email_the_owner.rb # serverop interface only
20
└── policies/
21
├── todo_policy.rb
22
└── user_policy.rb
Copied!
These directories are where most of your work will be done during Hyperstack development.

What about Controllers and Views?

Hyperstack works alongside Rails controllers and views. In a clean-sheet Hyperstack app you never need to create a controller or a view. On the other hand if you have existing code or aspects of your project that you feel would work better using a traditional MVC approach everything will work fine. You can also merge the two worlds: Hyperstack includes two helpers that allow you to mount components either from a controller or from within a view.

The Hyperstack Initializer

The Hyperstack configuration can be controlled via the config/initializers/hyperstack.rb initializer file. Using the installer will set up a reasonable set of of options, which you can tweak as needed.
Here is a summary of the various configuration settings:
1
# config/initializers/hyperstack.rb
2
3
# server_side_auto_require will patch the ActiveSupport Dependencies module
4
# so that you can define classes and modules with files in both the
5
# app/hyperstack/xxx and app/xxx directories.
6
7
require "hyperstack/server_side_auto_require.rb"
8
9
# By default the generators will generate new components as subclasses of
10
# HyperComponent. You can change this using the component_base_class setting.
11
12
Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent'
13
14
# prerendering is default :off, you should wait until your
15
# application is relatively well debugged before turning on.
16
17
Hyperstack.prerendering = :off # or :on
18
19
# The transport setting controls how push (websocket) communications are
20
# implemented. The default is :none, but will be set to :action_cable if you
21
# install hyper-model.
22
23
# Other possibilities are :action_cable, :pusher (see www.pusher.com)
24
# or :simple_poller which is sometimes handy during system debug.
25
26
Hyperstack.transport = :action_cable # :pusher, :simple_poller or :none
27
28
# hotloader settings:
29
# sets the port hotloader will listen on. Note this must match the value used
30
# to start the hotloader typically in the foreman Procfile.
31
Hyperstack.hotloader_port = 25222
32
# seconds between pings over the hotloader websocket. Normally not needed.
33
Hyperstack.hotloader_ping = nil
34
# hotloader will automatically reload callbacks when effected classes are
35
# reloaded. Not recommended to change this.
36
Hyperstack.hotloader_ignore_callback_mapping = false
37
38
# Transport settings
39
# seconds before timeout when sending messages between the rails console and
40
# the server.
41
Hyperstack.send_to_server_timeout = 10
42
43
# Transport specific options
44
Hyperstack.opts, {
45
# pusher specific options
46
app_id: 'your pusher app id',
47
key: 'your pusher key',
48
secret: 'your pusher secret',
49
cluster: 'mt1', # pusher cluster defaults to mt1
50
encrypted: true, # encrypt pusher comms, defaults to true
51
refresh_channels_every: 2.minutes, # how often to check which channels are alive
52
53
# simple poller specific options
54
expire_polled_connection_in: 5.minutes, # when to kill simple poller connections
55
seconds_between_poll: 5.seconds, # how fast to poll when using simple poller
56
expire_new_connection_in: 10.seconds, # how long to keep initial sessions alive
57
}
58
59
# Namespace used to keep hyperstack communication separate from other websockets
60
Hyperstack.channel_prefix = 'synchromesh'
61
62
# If there a JS console available should websocket comms be logged?
63
Hyperstack.client_logging = true
64
65
# Automatically create a (possibly temporary) websocket connection as each
66
# browser session starts. Usually this is needed for further authentication and
67
# should be left as true
68
Hyperstack.connect_session = true
69
70
# Where to store the connection tables. Default is :active_record but you
71
# can also specify redis. If specifying redis the redis url defaults to
72
# redis://127.0.0.1:6379
73
Hyperstack.connection = [adapter: :active_record] # or
74
# [adapter: :redis, redis_url: 'redis://127.0.0.1:6379]
75
76
# The import directive loads optional portions of the various hyperstack gems.
77
# Here are the common imports typically included:
78
79
Hyperstack.import 'hyperstack/hotloader', client_only: true if Rails.env.development?
80
81
# and these are typically not imported:
82
83
# React source is normally brought in through webpacker
84
# Hyperstack.import 'react/react-source-browser'
85
86
# add this line if you need jQuery AND ARE NOT USING WEBPACK
87
# Hyperstack.import 'hyperstack/component/jquery', client_only: true
88
89
# The following are less common settings which you should never have to change:
90
Hyperstack.prerendering_files = ['hyperstack-prerender-loader.js']
91
Hyperstack.public_model_directories = ['app/hyperstack/models']
92
93
94
# change definition of on_error to control how errors such as validation
95
# exceptions are reported on the server
96
module Hyperstack
97
def self.on_error(operation, err, params, formatted_error_message)
98
::Rails.logger.debug(
99
"#{formatted_error_message}\n\n" +
100
Pastel.new.red(
101
'To further investigate you may want to add a debugging '\
102
'breakpoint to the on_error method in config/initializers/hyperstack.rb'
103
)
104
)
105
end
106
end if Rails.env.development?
Copied!

Hyperstack Packs

Rails webpacker organizes javascript into packs. Hyperstack will look for and load one of two packs depending on if you are prerendering or not.
The default content of these packs are as follows:
1
//app/javascript/packs/client_and_server.js
2
// these packages will be loaded both during prerendering and on the client
3
React = require('react'); // react-js library
4
createReactClass = require('create-react-class'); // backwards compatibility with ECMA5
5
History = require('history'); // react-router history library
6
ReactRouter = require('react-router'); // react-router js library
7
ReactRouterDOM = require('react-router-dom'); // react-router DOM interface
8
ReactRailsUJS = require('react_ujs'); // interface to react-rails
9
// to add additional NPM packages run `yarn add [email protected]`
10
// then add the require here.
Copied!
1
//app/javascript/packs/client_only.js
2
// add any requires for packages that will run client side only
3
ReactDOM = require('react-dom'); // react-js client side code
4
jQuery = require('jquery'); // remove if you don't need jQuery
5
// to add additional NPM packages call run yarn add [email protected]
6
// then add the require here.
Copied!