Skip to content

automatically add dependencies for server side files. #361

Closed
@catmando

Description

@catmando

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 hyperstack 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds docEverything is working, but documentation is needed.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions