Description
You can now easily split your class definitions so that you have an isomorphic (i.e. runs on the server and client) definition, and a server-only definition.
This is especially useful for Server Operations and Active Record models.
Here is a sample directory structure, containing an application component, a model called Person, and an operation called PalindromeChecker. We also have a model called Orderbut it is only visible on the server. More on that later.
app -
hyperstack -
components -
app.rb
models -
application_record.rb
person.rb
operations
palindrome_checker.rb
models -
person.rb
order.rb
operations
palindrome_checker.rb
Note that the person.rb
and palindrome_checker.rb
files exist in two places: one in the hyperstack
directory, and the other in the outer app directory.
Files in the hyperstack
models
, operations
and shared
directories will be executed on both the client and the server. This is how a method can be understood by both the client and the server.
Files in the outer app subdirectory are only recognized by the Rails server. So any information in those files, will not be seen on the client.
As a side note files in the hyperstack
/components
, /libs
, and in fact, any other directories except /models
, /operations
, and /shared
directories are only seen on the client. So there three kinds of files: Server-only (those in the outer app
directories), server and client (those in hyperstack
/models
, /operations
, and /shared
directories, and client-only (any other hyperstac
k subdirectories)
Here is how we can use this difference to nicely structure our code that is sharing information between the server and client. We can put class definitions that are only relevant to the server in the outer app
directories, and put class definitions that are used by both the client and server in the hyperstack
directories.
So for example here are the two PalindromeChecker files:
# app/hyperstack/operations/palindrome_checker.rb
class PalindromeChecker < Hyperstack::ServerOp
param :string
end
# app/operations/palindrome_checker.rb
class PalindromeChecker < Hyperstack::ServerOp
param :acting_user, nils: true
param :string
step { params.string = params.string.gsub(/\s+/, '').downcase }
step { params.string.reverse == params.string }
end
All the client-side needs to know is that PalindromeChecker
is a subclass of Hyperstack::ServerOp
, and that it accepts a single param - a string to be checked. This definition is also available to the server, and will ensure that the type signature matches between the isomorphic (client and server) definition and the server-only definition.
The server-side file has all the implementation details including the acting_user declaration which can be used to validate who can be calling this operation. (In this case, we don't require an acting_user
hence nils are allowed)
Let's look at an Active Record example:
# app/hyperstack/models/person.rb
class Person < ApplicationRecord
def full_name
"#{first_name} #{last_name}"
end
scope :children, ->() { where("birthday > ?", Time.now - 21.years) }
scope :adults, ->() { where("birthday <= ?", Time.now - 21.years) }
end
# app/hyperstack/models/person.rb
class Person < ApplicationRecord
has_many :orders
end
Here we have a helper method and some scopes defined in the isomorphic definition: These are available to both the client and the server.
However the server-side file also has relationships with another model, and because these are defined on the server-side they are not visible to the client.
In order for this to work, you do need to add a small snippet of code to your initializers/hyperstack.rb
file which trains Rails to look in both directories for any constant definition. This code will be released into Hyperstack soon, but in the meantime feel free to use this snippet and clean up your classes:
# Add to your `initializers/hyperstack.rb file
module ActiveSupport
module Dependencies
HYPERSTACK_DIR = 'hyperstack'
class << self
alias original_require_or_load require_or_load
# before requiring_or_loading a file, first check if
# we have the same file in the server side directory
# and add that as a dependency
def require_or_load(file_name, const_path = nil)
add_server_side_dependency(file_name)
original_require_or_load(file_name, const_path)
end
# search the filename path from the end towards the beginning
# for the HYPERSTACK_DIR directory. If found, remove it from
# the filename, and if a ruby file exists at that location then
# add it as a dependency
def add_server_side_dependency(file_name)
path = File.expand_path(file_name.chomp('.rb'))
.split(File::SEPARATOR).reverse
hs_index = path.find_index(HYPERSTACK_DIR)
return unless hs_index # no hyperstack directory here
new_path = (path[0..hs_index - 1] + path[hs_index + 1..-1]).reverse
load_path = new_path.join(File::SEPARATOR)
return unless File.exist? "#{load_path}.rb"
require_dependency load_path
end
end
end
end