active-interaction-coder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ActiveInteraction Coder

ActiveInteraction 代码实现指南

Typed business operations as the structured alternative to service objects.
类型化业务操作:作为服务对象的结构化替代方案。

Why ActiveInteraction Over Service Objects

为什么选择ActiveInteraction而非服务对象

Service ObjectsActiveInteraction
No standard interfaceConsistent
.run
/
.run!
Manual type checkingBuilt-in type declarations
Manual validationStandard Rails validations
Hard to composeNative composition
Verbose boilerplateClean, self-documenting
服务对象ActiveInteraction
无标准接口统一的
.run
/
.run!
接口
手动类型检查内置类型声明
手动验证标准Rails验证
难以组合原生支持组合
冗余样板代码简洁、自文档化

Setup

设置

ruby
undefined
ruby
undefined

Gemfile

Gemfile

gem "active_interaction", "~> 5.3"
undefined
gem "active_interaction", "~> 5.3"
undefined

Simple Interaction

简单交互示例

ruby
undefined
ruby
undefined

app/interactions/users/create.rb

app/interactions/users/create.rb

module Users class Create < ActiveInteraction::Base # Typed inputs string :email string :name string :password, default: nil
# Validations
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true

# Main logic
def execute
  user = User.create!(
    email: email,
    name: name,
    password: password || SecureRandom.alphanumeric(32)
  )

  UserMailer.welcome(user).deliver_later
  user  # Return value becomes outcome.result
end
end end
undefined
module Users class Create < ActiveInteraction::Base # Typed inputs string :email string :name string :password, default: nil
# Validations
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true

# Main logic
def execute
  user = User.create!(
    email: email,
    name: name,
    password: password || SecureRandom.alphanumeric(32)
  )

  UserMailer.welcome(user).deliver_later
  user  # Return value becomes outcome.result
end
end end
undefined

Running Interactions

执行交互

ruby
undefined
ruby
undefined

In controller

In controller

def create outcome = Users::Create.run( email: params[:email], name: params[:name] )
if outcome.valid? redirect_to outcome.result, notice: "User created" else @errors = outcome.errors render :new, status: :unprocessable_entity end end
def create outcome = Users::Create.run( email: params[:email], name: params[:name] )
if outcome.valid? redirect_to outcome.result, notice: "User created" else @errors = outcome.errors render :new, status: :unprocessable_entity end end

With bang method (raises on error)

With bang method (raises on error)

user = Users::Create.run!(email: "user@example.com", name: "John")
undefined
user = Users::Create.run!(email: "user@example.com", name: "John")
undefined

Input Types

输入类型

ruby
class MyInteraction < ActiveInteraction::Base
  # Primitives
  string :name
  integer :age
  float :price
  boolean :active
  symbol :status

  # Date/Time
  date :birthday
  time :created_at
  date_time :scheduled_at

  # Complex types
  array :tags
  hash :metadata

  # Model instances
  object :user, class: User

  # Typed arrays
  array :emails, default: [] do
    string
  end

  # Optional with default
  string :optional_field, default: nil
  integer :count, default: 0
end
ruby
class MyInteraction < ActiveInteraction::Base
  # Primitives
  string :name
  integer :age
  float :price
  boolean :active
  symbol :status

  # Date/Time
  date :birthday
  time :created_at
  date_time :scheduled_at

  # Complex types
  array :tags
  hash :metadata

  # Model instances
  object :user, class: User

  # Typed arrays
  array :emails, default: [] do
    string
  end

  # Optional with default
  string :optional_field, default: nil
  integer :count, default: 0
end

Composing Interactions

组合交互

ruby
module Users
  class Register < ActiveInteraction::Base
    string :email, :name, :password

    def execute
      # Compose calls another interaction
      user = compose(Users::Create,
        email: email,
        name: name,
        password: password
      )

      # Errors automatically merged if nested fails
      compose(Users::SendWelcomeEmail, user: user)
      user
    end
  end
end
ruby
module Users
  class Register < ActiveInteraction::Base
    string :email, :name, :password

    def execute
      # Compose calls another interaction
      user = compose(Users::Create,
        email: email,
        name: name,
        password: password
      )

      # Errors automatically merged if nested fails
      compose(Users::SendWelcomeEmail, user: user)
      user
    end
  end
end

Controller Pattern

控制器模式

ruby
class ArticlesController < ApplicationController
  def create
    outcome = Articles::Create.run(
      title: params[:article][:title],
      body: params[:article][:body],
      author: current_user
    )

    if outcome.valid?
      redirect_to article_path(outcome.result), notice: "Article created"
    else
      @article = Article.new(article_params)
      @article.errors.merge!(outcome.errors)
      render :new, status: :unprocessable_entity
    end
  end
end
ruby
class ArticlesController < ApplicationController
  def create
    outcome = Articles::Create.run(
      title: params[:article][:title],
      body: params[:article][:body],
      author: current_user
    )

    if outcome.valid?
      redirect_to article_path(outcome.result), notice: "Article created"
    else
      @article = Article.new(article_params)
      @article.errors.merge!(outcome.errors)
      render :new, status: :unprocessable_entity
    end
  end
end

Testing Interactions

测试交互

ruby
RSpec.describe Users::Create do
  let(:valid_params) { { email: "user@example.com", name: "John" } }

  it "creates user with valid inputs" do
    expect { described_class.run(valid_params) }
      .to change(User, :count).by(1)
  end

  it "returns valid outcome" do
    outcome = described_class.run(valid_params)
    expect(outcome).to be_valid
    expect(outcome.result).to be_a(User)
  end

  it "validates email format" do
    outcome = described_class.run(valid_params.merge(email: "invalid"))
    expect(outcome).not_to be_valid
    expect(outcome.errors[:email]).to be_present
  end
end
ruby
RSpec.describe Users::Create do
  let(:valid_params) { { email: "user@example.com", name: "John" } }

  it "creates user with valid inputs" do
    expect { described_class.run(valid_params) }
      .to change(User, :count).by(1)
  end

  it "returns valid outcome" do
    outcome = described_class.run(valid_params)
    expect(outcome).to be_valid
    expect(outcome.result).to be_a(User)
  end

  it "validates email format" do
    outcome = described_class.run(valid_params.merge(email: "invalid"))
    expect(outcome).not_to be_valid
    expect(outcome.errors[:email]).to be_present
  end
end

Advanced Patterns

高级模式

For composition, error handling, and custom types see:
  • references/active-interaction.md
关于组合、错误处理和自定义类型,请参阅:
  • references/active-interaction.md