Skip to content

Instantly share code, notes, and snippets.

@ivanyv
Last active November 14, 2016 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ivanyv/236cf2b7562723663c86be99be492589 to your computer and use it in GitHub Desktop.
Save ivanyv/236cf2b7562723663c86be99be492589 to your computer and use it in GitHub Desktop.
Exploring a monolith Rails architecture based on "domains" (which I call "modules")

Architecture

Modules

The app is organized into "modules" (not Ruby modules), like "Core", "Admin", "Directory", etc.

Each module contains models, controllers, and every other related class.

When I say "modules", I'm actually talking about domains, so why call them modules? I'm actually torn on this as I like how "modules" fits much better. But I also know it can be confusing. On the other hand, the only part where the word "modules" actually appears in code is in the directory name (app/modules/<module_name>).

Models

CRUD Service Objects

  • base_model.rb
  • base_model/create.rb
  • base_model/find.rb - R in CRUD
  • base_model/update.rb
  • base_model/delete.rb

If there are several ways a record can be created, updated, etc., we can have classes such as:

  • base_model/create/guest.rb
  • base_model/update/by_admin.rb

Example Usage:

  • BaseModel::Create.(attributes)
  • BaseModel::Update::ByAdmin.(attributes)

Some devs would frown at using the shorthand for .call, but I feel it really fits here with the "action-like" class naming.

Forms

Examples:

  • base_model/form.rb
  • base_model/form/create.rb
  • base_model/form/update.rb
  • base_model/form/signup.rb

Example Usage:

class BaseModelsController < ApplicationController
  def new
    @form = BaseModel::Form.new(create_base_model_params)
    # or @form = BaseModel::Form::Create.new(create_base_model_params)
    assert_policy! @form, :new?
  end
  
  def create
    # Using custom method to pass in the user:
    @form = BaseModel::Form::Create.for(current_user, create_base_model_params)
    assert_policy! @form, :create?
    if @form.valid?
      # Create class should handle form object automatically
      BaseModel::Create.(@form)
      redirect_to '...'
    end
  end
  
  def edit
    @form = BaseModel::Form::Update.find(params[:id])
    assert_policy! @form, :edit?
  end
  
  def update
    @form = BaseModel::Form::Update.(params[:id], update_base_model_params)
    assert_policy! @form, :update?
    if @form.valid?
      # Update class should handle form object automatically through its attributes
      BaseModel::Update.(@form)
      redirect_to '...'
    end
  end
  
  private
  
  def create_base_model_params
    params.require(:base_model).permit(:a, :b)
  end

  def update_base_model_params
    create_base_model_params
  end
end

Decorators

Should handle decorating a Form object.

- BaseModel::Decorator.new(@form).tap do |base_model|

Also handle service objects:

@base_model = BaseModel::Find.(params[:id])
BaseModel::Decorator.new(@base_model)
# Collections:
@base_models = BaseModel::Find.recent()
BaseModel::Decorator.collection(@base_models) # Decorates each record

Query Objects

Store/run complex SQL queries

  • base_model/query/search.rb
  • base_model/query/geo.rb

Example Usage:

@base_models = BaseModel::Query::Geo.close_to(lat: -89, lng: 20, radius: 20.km)
# Distance would already been stored by previous call:
@base_models.by_distance.page(1).per(20)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment