← Back to news

Rethinking Modularity in Ruby Applications

noteflakes.com|7 points|0 comments|by ciconia|Jun 19, 2026

A New Perspective on Ruby Application Modularity

The author recently introduced Syntropy, a fresh Ruby web framework (which actually powers the site where this was originally posted). The core philosophy of Syntropy centers on file-based routing, meaning the organization and naming of route handler files (controllers) directly mirror the application's URL namespace.

Below is a deeper dive into how Syntropy handles module loading compared to traditional Ruby patterns.


The Status Quo: Code Organization in Ruby on Rails

For those in the Rails ecosystem, the standard for code organization is driven by the Zeitwerk gem. This system relies on auto-loading, where the directory structure serves as a map for the application's namespace.

How it Works

In the Rails model, classes and modules are treated as global entities. Because Zeitwerk handles the loading, developers don't need to manually require dependencies; any constant referenced in the code is fetched automatically.

The Trade-offs of the Rails Approach:

  • The Upside:
    • Manual require statements are eliminated.
    • Dependencies are handled implicitly.
    • Development is streamlined via automatic reloading of modified files.
  • The Downside:
    • Rigid Structure: You are locked into a "one class per file" rule.
    • Naming Constraints: Class names must strictly match their file paths. If you move a file, you must rename the class.
    • Global Scope Risks: Since everything is global, it is easy to accidentally reference or mutate a singleton object's state, leading to unpredictable bugs.

"When an application reaches a high level of complexity, explicitly defining the relationships between different parts of the source code becomes an essential tool for understanding the system's architecture."


A Shift in Philosophy

The author argues that relying on global constants for services—such as database pools, mailers, or configuration hashes—is a liability. Instead, they advocate for:

  1. Explicit Dependencies: Knowing exactly where a piece of logic comes from.
  2. Flexible Interfaces: Using not just classes, but also procs and lambdas to define interfaces.

By removing the reliance on global constants, implementing Dependency Injection becomes trivial, which significantly enhances the ease of testing.

Mathematically, we can view the routing logic as a function where the file path maps to a URI: f(file_path)URL_endpointf(\text{file\_path}) \rightarrow \text{URL\_endpoint}


The Syntropy Approach

Syntropy implements the idea that the folder hierarchy is the URL structure.

Routing Map

Here is how a typical blog application would be structured:

File PathResulting URLNote
app/index.rb/Home page
app/about.rb/aboutAbout page
app/posts/index.rb/postsList of posts
app/posts/new.rb/posts/newCreate post
app/posts/[id]/index.rb/posts/[id]Single post view
app/posts/[id]/edit.rb/posts/[id]/editEdit post
app/_lib/storage.rbInternalNot exposed to router
app/_layout/default.rbInternalNot exposed to router

Note: Any file or directory prefixed with an underscore (_) is treated as internal and is ignored by the router.

Dependency Management

In Syntropy, dependencies are never implicit. To use another module, you must use the import keyword.

1. The Controller

Controllers are loaded on-demand when a request hits the matching URL.

# app/posts/index.rb
@storage = import '/_lib/storage'
@layout = import '/_layout/default'

# Define the template using the imported layout
@template = @layout.render(posts: :posts)

# Export the controller as a lambda
export ->(request) {
  posts = @storage.get_posts
  @template.render(posts: posts)
}

2. The Layout Module

Modules can export any object. In this case, a Papercraft template is exported.

# app/_layout/default.rb
export template {
  html {
    head { # Meta tags and styles }
    body { render_children }
  }
}

3. The Storage Module

A module can also export itself as a singleton for utility access.

# app/_lib/storage.rb
export self

def get_posts
  # Logic to fetch posts from DB
end

Visualizing the Flow

The following diagram illustrates how a request flows through Syntropy's explicit dependency chain:

Summary of Goals

  • Eliminate implicit global dependencies.
  • Map file system directly to URL namespace.
  • Allow flexible exports (lambdas, singletons, templates).
  • Simplify testing via explicit imports.

Modularity Concept Figure 1: Conceptual shift from global namespaces to explicit module imports.