返回 Skill 列表
extension
分类: 开发与工程无需 API Key

rails-service-patterns

Rails服务对象模式和业务逻辑组织。当使用服务对象、提取业务逻辑、实现命令/查询模式或组织app/services时自动调用。在遇到“服务对象”、“服务”、“业务逻辑”、“工作流”、“编排”、“命令模式”、“查询对象”、“表单对象”、“交互器”、“结果对象”这些术语时触发。

person作者: jakexiaohubgithub

Rails Service Object Patterns

Analyze and recommend patterns for extracting and organizing business logic in Rails applications.

Resolve the axis first

This skill forks on Axis A — logic placement. Before recommending where logic goes:

  1. If the axes are already resolved this session, use them.
  2. Otherwise resolve via rails-stack-profiles (Axis A only — native or extracted).
  3. Then read the matching extraction guide:

If the axis cannot be resolved, default to native and state the assumption.

Everything else in this file is invariant — it holds regardless of axis.

Quick Reference

| Pattern | Use When | Entry Point | |---------|----------|-------------| | Basic Service | Single operation with transaction | CreateOrder.new(...).call | | Result Object | Caller needs success/failure + data | Result.new(success?: true, data:) | | Form Object | Multi-model form submissions | RegistrationForm.new(params).save | | Query Object | Complex reusable queries | UserSearchQuery.new(scope).call | | Policy Object | Authorization decisions | PostPolicy.new(user, post).update? |

Supporting Documentation

Core Principles

  1. VerbNoun naming: CreateOrder, SendInvitation -- never OrderService or UserManager
  2. One public method: Expose only call (or perform)
  3. Explicit return values: Use Result objects, never exceptions for expected flow control
  4. Axis-aware extraction: Whether a given operation becomes a service at all depends on Axis A. Read the matching extraction guide above before recommending a service.

Result Object Pattern

Use Struct.new(keyword_init: true) for lightweight results. Never raise exceptions for expected failures (validation, auth, payment decline).

class AuthenticateUser
  Result = Struct.new(:success?, :user, :error, keyword_init: true)

  def initialize(email:, password:)
    @email = email
    @password = password
  end

  def call
    user = User.find_by(email: @email)
    if user&.authenticate(@password)
      Result.new(success?: true, user: user)
    else
      Result.new(success?: false, error: "Invalid credentials")
    end
  end
end

See patterns.md for the enhanced monad-like ServiceResult with on_success/on_failure chaining.

Anti-Patterns

| Anti-Pattern | Problem | Fix | |-------------|---------|-----| | God service (100+ lines) | Does too much | Split into composable services | | Raising exceptions for flow control | Expensive, hard to handle | Use Result objects | | Deep service-calls-service chains | Hidden coupling | Orchestrate from controller or coordinator | | self.call class method pattern | No instance state, limits DI | Use instance methods with constructor DI | | No return value | Caller can't react to failures | Always return Result or meaningful value | | Service modifying passed-in objects | Surprising side effects | Return new objects or be explicit | | VerbNoun naming violation (UserService) | Unclear responsibility, attracts god service | One service = one operation = one verb |

Output Format

When analyzing or creating services, provide:

  1. Service file with VerbNoun naming, placed per the axis extraction guide
  2. Result struct if callers need success/failure status
  3. Controller integration showing how to call and handle results
  4. Test outline covering happy path, failure cases, and edge cases
  5. Error handling strategy (Result objects for expected failures, exceptions for unexpected)