# Chatwoot コードリーディングガイドライン

## はじめに

このガイドラインは、Chatwootのコードベースを効率的に理解するための手引きです。
Ruby/Rails、JavaScript/Vue.jsに精通していないエンジニアでも、段階的に学習できるよう構成されています。

**対象読者:**
- プロジェクトに新規参画するエンジニア
- 他言語からの経験者
- コードレビューを行う担当者

---

## 1. 言語基礎

> このセクションでは、Chatwootで使用されるRuby/RailsおよびJavaScript/Vue.jsの基本構文と概念を解説します。

### 1.1 プログラム構造

Chatwootは**Ruby on Rails**（バックエンド）と**Vue.js 3**（フロントエンド）を組み合わせたフルスタックアプリケーションです。

**Ruby（バックエンド）の基本構造:**
```ruby
# ファイル: app/models/conversation.rb:54-77
class Conversation < ApplicationRecord
  include Labelable
  include LlmFormattable
  include AssignmentHandler

  validates :account_id, presence: true
  validates :inbox_id, presence: true
  validates :contact_id, presence: true

  enum status: { open: 0, resolved: 1, pending: 2, snoozed: 3 }
  enum priority: { low: 0, medium: 1, high: 2, urgent: 3 }

  belongs_to :account
  belongs_to :inbox
  belongs_to :assignee, class_name: 'User', optional: true
  # ...
end
```

**Vue.js（フロントエンド）の基本構造:**
```vue
<!-- ファイル: app/javascript/dashboard/components/ChatList.vue:1-14 -->
<script setup>
import {
  ref,
  unref,
  provide,
  computed,
  watch,
  onMounted,
  defineEmits,
} from 'vue';
import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router';
// ...
</script>
```

### 1.2 データ型と変数

**Ruby:**
```ruby
# ファイル: app/models/conversation.rb:75-76
enum status: { open: 0, resolved: 1, pending: 2, snoozed: 3 }
enum priority: { low: 0, medium: 1, high: 2, urgent: 3 }

# JSONB型のカスタム属性
# ファイル: app/models/conversation.rb:70-71
validates :additional_attributes, jsonb_attributes_length: true
validates :custom_attributes, jsonb_attributes_length: true
```

**JavaScript:**
```javascript
// ファイル: app/javascript/dashboard/components/ChatList.vue:98-105
const activeAssigneeTab = ref(wootConstants.ASSIGNEE_TYPE.ME);
const activeStatus = ref(wootConstants.STATUS_TYPE.OPEN);
const activeSortBy = ref(wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC);
const showAdvancedFilters = ref(false);
const chatsOnView = ref([]);
const foldersQuery = ref({});
```

### 1.3 制御構造

**Ruby:**
```ruby
# ファイル: app/controllers/api/v1/accounts/conversations_controller.rb:78-89
def toggle_status
  if pending_to_open_by_bot?
    @conversation.bot_handoff!
  elsif params[:status].present?
    set_conversation_status
    @status = @conversation.save!
  else
    @status = @conversation.toggle_status
  end
  assign_conversation if should_assign_conversation?
end
```

**JavaScript:**
```javascript
// ファイル: app/javascript/dashboard/api/ApiClient.js:28-39
baseUrl() {
  let url = this.apiVersion;

  if (this.options.enterprise) {
    url = `/enterprise${url}`;
  }

  if (this.options.accountScoped && this.accountIdFromRoute) {
    url = `${url}/accounts/${this.accountIdFromRoute}`;
  }

  return url;
}
```

### 1.4 関数/メソッド定義

**Ruby:**
```ruby
# ファイル: app/services/conversations/filter_service.rb:9-24
def perform
  validate_query_operator
  @conversations = query_builder(@filters['conversations'])
  mine_count, unassigned_count, all_count, = set_count_for_all_conversations
  assigned_count = all_count - unassigned_count

  {
    conversations: conversations,
    count: {
      mine_count: mine_count,
      assigned_count: assigned_count,
      unassigned_count: unassigned_count,
      all_count: all_count
    }
  }
end
```

**JavaScript - Composable関数:**
```javascript
// ファイル: app/javascript/dashboard/composables/useAccount.js:9-65
export function useAccount() {
  const route = useRoute();
  const store = useStore();
  const getAccountFn = useMapGetter('accounts/getAccount');

  const accountId = computed(() => {
    return Number(route.params.accountId);
  });
  const currentAccount = computed(() => getAccountFn.value(accountId.value));

  const accountScopedUrl = url => {
    return `/app/accounts/${accountId.value}/${url}`;
  };

  return {
    accountId,
    route,
    currentAccount,
    accountScopedUrl,
    // ...
  };
}
```

### 1.5 モジュール/インポート

**Ruby（モジュール include/prepend）:**
```ruby
# ファイル: app/models/conversation.rb:54-64
class Conversation < ApplicationRecord
  include Labelable
  include LlmFormattable
  include AssignmentHandler
  include AutoAssignmentHandler
  include ActivityMessageHandler
  include UrlHelper
  include SortHandler
  include PushDataHelper
  include ConversationMuteHelpers
  # ...
end

# Enterprise拡張パターン（ファイル末尾）
# ファイル: app/models/conversation.rb:345-347
Conversation.include_mod_with('Audit::Conversation')
Conversation.include_mod_with('Concerns::Conversation')
Conversation.prepend_mod_with('Conversation')
```

**JavaScript（ES Modules）:**
```javascript
// ファイル: app/javascript/dashboard/components/ChatList.vue:23-35
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller';
import ChatListHeader from './ChatListHeader.vue';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import ConversationFilter from 'next/filter/ConversationFilter.vue';
import { useUISettings } from 'dashboard/composables/useUISettings';
import { useAlert } from 'dashboard/composables';
```

---

## 2. プロジェクト固有の概念

> このセクションでは、Chatwoot特有の概念を解説します。

### 2.1 フレームワーク固有の概念

#### Rails APIコントローラーパターン
```ruby
# ファイル: app/controllers/api/v1/accounts/conversations_controller.rb:1-8
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
  include Events::Types
  include DateRangeHelper
  include HmacConcern

  before_action :conversation, except: [:index, :meta, :search, :create, :filter]
  before_action :inbox, :contact, :contact_inbox, only: [:create]
  # ...
end
```

#### Vue 3 Composition API
Chatwootでは`<script setup>`構文を使用したComposition APIが標準です。

```vue
<!-- ファイル: app/javascript/dashboard/components/ChatList.vue:1-50 -->
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRoute, useRouter } from 'vue-router';

const props = defineProps({
  conversationInbox: { type: [String, Number], default: 0 },
  teamId: { type: [String, Number], default: 0 },
  label: { type: String, default: '' },
});

const emit = defineEmits(['conversationLoad']);
</script>
```

### 2.2 プロジェクト独自のパターン

#### Enterprise拡張パターン（prepend_mod_with / include_mod_with）

Chatwootには**Enterprise Edition**のオーバーレイ機構があります。OSS版のクラスをEnterpriseで拡張する場合、以下のパターンを使用します。

```ruby
# ファイル: app/controllers/application_controller.rb:28
ApplicationController.include_mod_with('Concerns::ApplicationControllerConcern')

# ファイル: app/models/conversation.rb:345-347
Conversation.include_mod_with('Audit::Conversation')
Conversation.include_mod_with('Concerns::Conversation')
Conversation.prepend_mod_with('Conversation')

# ファイル: app/controllers/api/v1/accounts/conversations_controller.rb:233
Api::V1::Accounts::ConversationsController.prepend_mod_with('Api::V1::Accounts::ConversationsController')
```

#### イベント駆動アーキテクチャ（Pub/Sub with Wisper）

```ruby
# ファイル: app/dispatchers/async_dispatcher.rb:1-26
class AsyncDispatcher < BaseDispatcher
  def dispatch(event_name, timestamp, data)
    EventDispatcherJob.perform_later(event_name, timestamp, data)
  end

  def listeners
    [
      AutomationRuleListener.instance,
      CampaignListener.instance,
      CsatSurveyListener.instance,
      HookListener.instance,
      InstallationWebhookListener.instance,
      NotificationListener.instance,
      ParticipationListener.instance,
      ReportingEventListener.instance,
      WebhookListener.instance
    ]
  end
end
```

#### Builderパターン

```ruby
# ファイル: app/builders/contact_inbox_builder.rb:4-10
class ContactInboxBuilder
  pattr_initialize [:contact, :inbox, :source_id, { hmac_verified: false }]

  def perform
    @source_id ||= generate_source_id
    create_contact_inbox if source_id.present?
  end
end
```

#### Serviceオブジェクトパターン

```ruby
# ファイル: app/services/conversations/filter_service.rb:1-52
class Conversations::FilterService < FilterService
  ATTRIBUTE_MODEL = 'conversation_attribute'.freeze

  def initialize(params, user, account)
    @account = account
    super(params, user)
  end

  def perform
    validate_query_operator
    @conversations = query_builder(@filters['conversations'])
    # ...
  end

  def base_relation
    # ...
  end
end
```

---

## 3. 命名規則

> このセクションでは、プロジェクト全体で使用される命名規則を解説します。

### 3.1 ファイル・ディレクトリ命名

| パターン | 意味 | 例 |
|---------|------|-----|
| `snake_case.rb` | Rubyファイル | `conversation.rb`, `filter_service.rb` |
| `PascalCase.vue` | Vueコンポーネント | `ChatList.vue`, `ConversationItem.vue` |
| `camelCase.js` | JavaScriptファイル | `useAccount.js`, `filterQueryGenerator.js` |
| `*_controller.rb` | Railsコントローラー | `conversations_controller.rb` |
| `*_service.rb` | サービスクラス | `filter_service.rb` |
| `*_job.rb` | バックグラウンドジョブ | `send_reply_job.rb` |
| `*_policy.rb` | 認可ポリシー | `conversation_policy.rb` |
| `*_builder.rb` | ビルダークラス | `contact_inbox_builder.rb` |
| `*_listener.rb` | イベントリスナー | `hook_listener.rb` |
| `components-next/` | 新しいコンポーネント（移行中） | メッセージバブル等 |

### 3.2 クラス・関数・変数命名

| プレフィックス/サフィックス | 意味 | 例 |
|---------------------------|------|-----|
| `use*` | Vue Composable関数 | `useAccount`, `useUISettings` |
| `*Service` | ビジネスロジック実行クラス | `FilterService`, `AssignmentService` |
| `*Builder` | オブジェクト構築クラス | `ContactInboxBuilder`, `ConversationBuilder` |
| `*Job` | Sidekiqバックグラウンドジョブ | `SendReplyJob`, `HookJob` |
| `*Policy` | Pundit認可ポリシー | `ConversationPolicy` |
| `*Listener` | イベントリスナー | `HookListener`, `WebhookListener` |
| `*Dispatcher` | イベント発行 | `AsyncDispatcher` |
| `*Api` | フロントエンドAPIクライアント | `ConversationApi` |
| `Channel::*` | チャンネル（受信経路）モデル | `Channel::WebWidget`, `Channel::Whatsapp` |
| `*Concern` | Railsの共有モジュール | `HmacConcern` |

### 3.3 プログラム分類一覧

| 分類 | ディレクトリ | 説明 |
|-----|------------|------|
| Model | `app/models/` | ActiveRecordモデル |
| Controller | `app/controllers/` | HTTPリクエスト処理 |
| Service | `app/services/` | ビジネスロジック |
| Job | `app/jobs/` | バックグラウンド処理 |
| Policy | `app/policies/` | 認可ロジック |
| Builder | `app/builders/` | オブジェクト生成 |
| Listener | `app/listeners/` | イベント処理 |
| Dispatcher | `app/dispatchers/` | イベント発行 |
| Finder | `app/finders/` | 検索ロジック |
| Presenter | `app/presenters/` | 表示用データ整形 |
| Mailer | `app/mailers/` | メール送信 |
| Vue Component | `app/javascript/dashboard/components/` | UIコンポーネント |
| Vue Store | `app/javascript/dashboard/store/` | Vuex状態管理 |
| Vue Composable | `app/javascript/dashboard/composables/` | ロジック再利用 |
| Vue API | `app/javascript/dashboard/api/` | APIクライアント |

---

## 4. ディレクトリ構造

> このセクションでは、プロジェクトのディレクトリ構造を解説します。

```
chatwoot-develop/
├── app/
│   ├── controllers/           # Railsコントローラー
│   │   ├── api/v1/accounts/   # アカウントスコープAPI
│   │   ├── concerns/          # 共有コントローラーロジック
│   │   └── webhooks/          # Webhook受信
│   ├── models/                # ActiveRecordモデル
│   │   ├── channel/           # チャンネルモデル
│   │   └── concerns/          # 共有モデルロジック
│   ├── services/              # ビジネスロジック
│   │   ├── conversations/     # 会話関連サービス
│   │   ├── messages/          # メッセージ関連サービス
│   │   └── whatsapp/          # WhatsApp連携
│   ├── jobs/                  # Sidekiqジョブ
│   ├── policies/              # Pundit認可ポリシー
│   ├── builders/              # オブジェクトビルダー
│   ├── listeners/             # イベントリスナー
│   ├── dispatchers/           # イベントディスパッチャー
│   ├── finders/               # 検索ロジック
│   ├── javascript/            # フロントエンドソース
│   │   ├── dashboard/         # メインダッシュボード
│   │   │   ├── api/           # APIクライアント
│   │   │   ├── components/    # Vueコンポーネント
│   │   │   ├── components-next/ # 新コンポーネント
│   │   │   ├── composables/   # Composition API関数
│   │   │   ├── store/         # Vuex状態管理
│   │   │   └── routes/        # Vue Router
│   │   ├── widget/            # チャットウィジェット
│   │   └── portal/            # ヘルプセンター
│   └── views/                 # ERBテンプレート
├── config/
│   ├── routes.rb              # ルーティング定義
│   ├── initializers/          # 初期化スクリプト
│   └── locales/               # i18nファイル
├── db/
│   └── migrate/               # マイグレーション
├── enterprise/                # Enterprise Edition
│   ├── app/                   # Enterprise専用コード
│   ├── config/                # Enterprise設定
│   └── lib/                   # Enterprise ライブラリ
├── lib/
│   ├── custom_exceptions/     # カスタム例外
│   ├── integrations/          # 外部連携
│   └── tasks/                 # Rakeタスク
├── spec/                      # RSpecテスト
└── swagger/                   # API仕様
```

### 各ディレクトリの役割

| ディレクトリ | 役割 | 主要ファイル |
|-------------|------|-------------|
| `app/controllers/api/v1/accounts/` | アカウントスコープのAPI処理 | `conversations_controller.rb` |
| `app/models/` | ドメインモデル定義 | `conversation.rb`, `message.rb` |
| `app/services/` | ビジネスロジック実行 | `filter_service.rb` |
| `app/jobs/` | 非同期バックグラウンド処理 | `send_reply_job.rb` |
| `app/listeners/` | イベント駆動処理 | `hook_listener.rb` |
| `app/javascript/dashboard/` | ダッシュボードUI | `ChatList.vue` |
| `enterprise/app/` | Enterprise専用機能 | 課金、監査等 |
| `config/routes.rb` | URLルーティング | 全APIエンドポイント定義 |

---

## 5. アーキテクチャ

> このセクションでは、プロジェクトのアーキテクチャパターンを解説します。

### 5.1 全体アーキテクチャ

Chatwootは**モノリシック Rails + Vue.js SPA**アーキテクチャを採用しています。

```
┌─────────────────────────────────────────────────────────────────┐
│                      フロントエンド                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │  Dashboard   │  │   Widget     │  │    Portal    │          │
│  │  (Vue 3)     │  │   (Vue 3)    │  │   (Vue 3)    │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Rails API Layer                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │   API v1     │  │  Platform    │  │   Public     │          │
│  │ Controllers  │  │    API       │  │    API       │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
│                              │                                  │
│  ┌──────────────────────────────────────────────────┐          │
│  │              Authorization (Pundit)              │          │
│  └──────────────────────────────────────────────────┘          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Business Logic Layer                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │ Services │  │ Builders │  │ Finders  │  │ Actions  │       │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘       │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Domain Layer                               │
│  ┌────────────────────────────────────────────────────┐        │
│  │                   ActiveRecord Models               │        │
│  │  Account, Conversation, Message, Contact, Inbox... │        │
│  └────────────────────────────────────────────────────┘        │
└─────────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────────┐
│                    Event-Driven Layer                           │
│  ┌──────────────┐           ┌──────────────┐                   │
│  │ Dispatchers  │ ────────► │  Listeners   │                   │
│  │   (Wisper)   │           │              │                   │
│  └──────────────┘           └──────────────┘                   │
│                                    │                            │
│                                    ▼                            │
│  ┌──────────────────────────────────────────────────┐          │
│  │              Background Jobs (Sidekiq)           │          │
│  └──────────────────────────────────────────────────┘          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    External Integrations                        │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐       │
│  │WhatsApp│ │Facebook│ │Telegram│ │ Slack  │ │ Email  │       │
│  └────────┘ └────────┘ └────────┘ └────────┘ └────────┘       │
└─────────────────────────────────────────────────────────────────┘
```

### 5.2 レイヤー構成

| レイヤー | 責務 | 代表的なファイル |
|---------|------|-----------------|
| Presentation | HTTPリクエスト/レスポンス処理 | `conversations_controller.rb` |
| Authorization | アクセス制御 | `conversation_policy.rb` |
| Business Logic | ビジネスルール実行 | `filter_service.rb` |
| Domain | データモデル・永続化 | `conversation.rb` |
| Event | 非同期イベント処理 | `async_dispatcher.rb`, `hook_listener.rb` |
| Integration | 外部サービス連携 | `send_on_whatsapp_service.rb` |

### 5.3 データフロー

**メッセージ送信フロー例:**

```
[User Input]
     │
     ▼
[Vue Component] ─────► [API Client] ─────► [HTTP Request]
                                                 │
                                                 ▼
                                    [Rails Controller]
                                          │
                                          ▼
                                    [Service Object]
                                          │
                                          ▼
                                    [Model (Message)]
                                          │
                                          ▼
                                    [Dispatcher]
                                          │
                                          ▼
                                    [Listener]
                                          │
                                          ▼
                                    [SendReplyJob]
                                          │
                                          ▼
                              [Channel Service (e.g., WhatsApp)]
```

---

## 6. 主要コンポーネント

> このセクションでは、主要なコンポーネントとその連携を解説します。

### 6.1 エントリーポイント

**Rails（バックエンド）:**
```ruby
# ファイル: config/routes.rb:40-159（抜粋）
namespace :api, defaults: { format: 'json' } do
  namespace :v1 do
    resources :accounts, only: [:create, :show, :update] do
      scope module: :accounts do
        resources :conversations, only: [:index, :create, :show, :update, :destroy] do
          # ...
        end
      end
    end
  end
end
```

**Vue（フロントエンド）:**
```javascript
// ファイル: app/javascript/dashboard/App.vue（エントリーポイント）
// ルーティング: app/javascript/dashboard/routes/
```

### 6.2 ビジネスロジック

ビジネスロジックは**Service Object**パターンで分離されています。

```ruby
# ファイル: app/controllers/api/v1/accounts/conversations_controller.rb:50-59
def filter
  result = ::Conversations::FilterService.new(params.permit!, current_user, current_account).perform
  @conversations = result[:conversations]
  @conversations_count = result[:count]
rescue CustomExceptions::CustomFilter::InvalidAttribute,
       CustomExceptions::CustomFilter::InvalidOperator,
       CustomExceptions::CustomFilter::InvalidQueryOperator,
       CustomExceptions::CustomFilter::InvalidValue => e
  render_could_not_create_error(e.message)
end
```

### 6.3 データアクセス

ActiveRecordモデルがデータアクセス層を担当します。

```ruby
# ファイル: app/models/conversation.rb:78-91（スコープ定義）
scope :unassigned, -> { where(assignee_id: nil) }
scope :assigned, -> { where.not(assignee_id: nil) }
scope :assigned_to, ->(agent) { where(assignee_id: agent.id) }
scope :unattended, -> { where(first_reply_created_at: nil).or(where.not(waiting_since: nil)) }
scope :resolvable_not_waiting, lambda { |auto_resolve_after|
  return none if auto_resolve_after.to_i.zero?
  open.where('last_activity_at < ? AND waiting_since IS NULL', Time.now.utc - auto_resolve_after.minutes)
}
```

### 6.4 ユーティリティ/共通機能

**フロントエンド APIクライアント:**
```javascript
// ファイル: app/javascript/dashboard/api/ApiClient.js:5-61
class ApiClient {
  constructor(resource, options = {}) {
    this.apiVersion = `/api/${options.apiVersion || DEFAULT_API_VERSION}`;
    this.options = options;
    this.resource = resource;
  }

  get url() {
    return `${this.baseUrl()}/${this.resource}`;
  }

  get() {
    return axios.get(this.url);
  }

  create(data) {
    return axios.post(this.url, data);
  }
  // ...
}
```

---

## 7. よく使われるパターン

> このセクションでは、コード内で頻出するパターンを解説します。

### パターン一覧

| パターン | 説明 | 出現頻度 | 代表的なファイル |
|---------|------|---------|-----------------|
| Service Object | ビジネスロジックの分離 | 高 | `app/services/**/*.rb` |
| Builder | オブジェクト構築 | 高 | `app/builders/**/*.rb` |
| Pub/Sub (Wisper) | イベント駆動 | 高 | `app/dispatchers/`, `app/listeners/` |
| Policy (Pundit) | 認可 | 高 | `app/policies/**/*.rb` |
| Composable | Vue ロジック再利用 | 高 | `app/javascript/dashboard/composables/` |
| Channel Strategy | チャンネル別処理の分岐 | 中 | `app/jobs/send_reply_job.rb` |

### 各パターンの詳細

#### パターン1: Service Object

**目的:** コントローラーからビジネスロジックを分離し、単一責任を保つ

**実装例:**
```ruby
// ファイル: app/services/conversations/filter_service.rb:1-52
class Conversations::FilterService < FilterService
  ATTRIBUTE_MODEL = 'conversation_attribute'.freeze

  def initialize(params, user, account)
    @account = account
    super(params, user)
  end

  def perform
    validate_query_operator
    @conversations = query_builder(@filters['conversations'])
    # ...
  end
end
```

**解説:** `perform`メソッドをエントリーポイントとし、初期化時に必要な依存を受け取ります。

#### パターン2: Channel Strategy（戦略パターン）

**目的:** 異なるチャンネル（WhatsApp, Telegram等）への処理を統一的に扱う

**実装例:**
```ruby
// ファイル: app/jobs/send_reply_job.rb:4-16
CHANNEL_SERVICES = {
  'Channel::TwitterProfile' => ::Twitter::SendOnTwitterService,
  'Channel::TwilioSms' => ::Twilio::SendOnTwilioService,
  'Channel::Line' => ::Line::SendOnLineService,
  'Channel::Telegram' => ::Telegram::SendOnTelegramService,
  'Channel::Whatsapp' => ::Whatsapp::SendOnWhatsappService,
  'Channel::Sms' => ::Sms::SendOnSmsService,
  'Channel::Instagram' => ::Instagram::SendOnInstagramService,
  'Channel::Tiktok' => ::Tiktok::SendOnTiktokService,
  'Channel::Email' => ::Email::SendOnEmailService,
  'Channel::WebWidget' => ::Messages::SendEmailNotificationService,
  'Channel::Api' => ::Messages::SendEmailNotificationService
}.freeze

def perform(message_id)
  message = Message.find(message_id)
  channel_name = message.conversation.inbox.channel.class.to_s
  service_class = CHANNEL_SERVICES[channel_name]
  return unless service_class
  service_class.new(message: message).perform
end
```

**解説:** チャンネル名をキーにしたハッシュで処理クラスをマッピングし、動的にサービスを呼び出します。

#### パターン3: Vue Composable

**目的:** Vue 3のロジック再利用

**実装例:**
```javascript
// ファイル: app/javascript/dashboard/composables/useAccount.js:9-65
export function useAccount() {
  const route = useRoute();
  const store = useStore();
  const getAccountFn = useMapGetter('accounts/getAccount');

  const accountId = computed(() => {
    return Number(route.params.accountId);
  });

  const accountScopedUrl = url => {
    return `/app/accounts/${accountId.value}/${url}`;
  };

  return {
    accountId,
    currentAccount,
    accountScopedUrl,
    // ...
  };
}
```

**解説:** `use*`プレフィックスで命名し、リアクティブな値と関数をオブジェクトとして返します。

---

## 8. 業務フロー追跡の実践例

> このセクションでは、実際の業務フローをコードで追跡する方法を解説します。

### 8.1 フロー追跡の基本手順

1. エントリーポイントを特定（ルーティング → コントローラー）
2. 処理の流れを追跡（コントローラー → サービス → モデル）
3. イベント発行を確認（ディスパッチャー → リスナー）
4. 非同期処理を追跡（ジョブ → 外部サービス）

### 8.2 フロー追跡の実例

#### 例1: 会話のステータス変更フロー

**概要:** エージェントが会話を「解決済み」に変更する際の処理フロー

**処理フロー:**
```
[API Request] → [Controller] → [Model] → [Dispatcher] → [Listeners] → [Jobs]
```

**詳細な追跡:**

1. **ルーティング** (`config/routes.rb:148`)
   ```ruby
   post :toggle_status
   ```

2. **コントローラー** (`app/controllers/api/v1/accounts/conversations_controller.rb:78-89`)
   ```ruby
   def toggle_status
     if pending_to_open_by_bot?
       @conversation.bot_handoff!
     elsif params[:status].present?
       set_conversation_status
       @status = @conversation.save!
     else
       @status = @conversation.toggle_status
     end
     assign_conversation if should_assign_conversation?
   end
   ```

3. **モデル** (`app/models/conversation.rb:149-154`)
   ```ruby
   def toggle_status
     self.status = open? ? :resolved : :open
     self.status = :open if pending? || snoozed?
     save
   end
   ```

4. **コールバック** (`app/models/conversation.rb:299-308`)
   ```ruby
   def notify_status_change
     {
       CONVERSATION_OPENED => -> { saved_change_to_status? && open? },
       CONVERSATION_RESOLVED => -> { saved_change_to_status? && resolved? },
       CONVERSATION_STATUS_CHANGED => -> { saved_change_to_status? },
       # ...
     }.each do |event, condition|
       condition.call && dispatcher_dispatch(event, status_change)
     end
   end
   ```

5. **ディスパッチャー** (`app/dispatchers/async_dispatcher.rb:2-4`)
   ```ruby
   def dispatch(event_name, timestamp, data)
     EventDispatcherJob.perform_later(event_name, timestamp, data)
   end
   ```

6. **リスナー例** (`app/listeners/hook_listener.rb:29-35`)
   ```ruby
   def conversation_resolved(event)
     conversation = extract_conversation_and_account(event)[0]
     return unless conversation.status == 'resolved'
     execute_account_hooks(event, conversation.account, conversation: conversation)
   end
   ```

### 8.3 フロー追跡チェックリスト

- [ ] エントリーポイントを特定したか（`config/routes.rb`を確認）
- [ ] 呼び出し関係を把握したか（コントローラー → サービス → モデル）
- [ ] イベント発行を確認したか（`dispatcher_dispatch`メソッド）
- [ ] 関連リスナーを確認したか（`app/listeners/`）
- [ ] バックグラウンドジョブを確認したか（`app/jobs/`）
- [ ] Enterprise拡張を確認したか（`prepend_mod_with`/`include_mod_with`）

---

## 9. 設計書の参照順序

> このセクションでは、プロジェクト理解のための設計書参照順序を案内します。

### 9.1 目的別ロードマップ

#### 全体像を把握したい場合
1. `README.md` - プロジェクト概要
2. `CLAUDE.md` (または `AGENTS.md`) - 開発ガイドライン
3. `config/routes.rb` - API構造の全体像
4. `app/models/` - ドメインモデルの理解

#### 特定機能を理解したい場合
1. `config/routes.rb` - 該当エンドポイントの特定
2. `app/controllers/` - 該当コントローラーの処理
3. `app/services/` - ビジネスロジック
4. `app/models/` - データ構造
5. `app/listeners/` - イベント処理

#### 改修作業を行う場合
1. 該当機能のコントローラー/サービス
2. 関連モデルとその関連（`belongs_to`, `has_many`）
3. `enterprise/app/` - Enterprise版の拡張有無を確認
4. `spec/` - 関連テストケース

### 9.2 ドキュメント一覧

| ドキュメント | 概要 | 参照タイミング |
|-------------|------|---------------|
| `README.md` | プロジェクト概要・セットアップ | 初回参画時 |
| `CLAUDE.md` | 開発ガイドライン・規約 | コーディング時 |
| `swagger/` | API仕様 | API実装・連携時 |
| `config/routes.rb` | ルーティング定義 | 機能追跡時 |

---

## 10. トラブルシューティング

> このセクションでは、コードリーディング時によくある問題と解決法を解説します。

### よくある疑問と回答

#### Q: `prepend_mod_with`や`include_mod_with`とは何ですか？
A: Enterprise Edition拡張のための仕組みです。`enterprise/app/`ディレクトリ配下に同名ファイルがある場合、そのモジュールがOSS版のクラスに組み込まれます。

#### Q: イベント処理がどこで行われているか分かりません
A: `app/dispatchers/async_dispatcher.rb`の`listeners`メソッドでリスナー一覧を確認できます。各リスナーは`app/listeners/`ディレクトリにあります。

#### Q: Vueコンポーネントで使われている`useMapGetter`とは？
A: Vuexストアのgetterをcomposable形式で使用するためのユーティリティです。`app/javascript/dashboard/composables/store.js`に定義されています。

#### Q: チャンネル（WhatsApp, Telegram等）の処理はどこにありますか？
A: 各チャンネルは以下の場所に実装されています：
- モデル: `app/models/channel/`
- サービス: `app/services/{channel_name}/`（例: `app/services/whatsapp/`）
- ジョブ: `app/jobs/send_reply_job.rb`（振り分け処理）

### デバッグのヒント

1. **Railsログの確認:** `log/development.log`でSQLクエリや処理フローを確認
2. **Sidekiqジョブの確認:** `/super_admin/monitoring/sidekiq`でジョブ状況を確認
3. **イベントの追跡:** `dispatcher_dispatch`メソッドの呼び出し箇所を検索
4. **Enterprise拡張の確認:** ファイル末尾の`prepend_mod_with`/`include_mod_with`をチェック

---

## 付録

### A. 用語集

| 用語 | 説明 |
|-----|------|
| Account | 組織単位。複数のユーザー、会話、受信トレイを持つ |
| Inbox | メッセージの受信経路（チャンネル）。WhatsApp, Email等 |
| Channel | Inboxの種類を定義するモデル。`Channel::Whatsapp`等 |
| Conversation | 顧客とエージェント間の会話スレッド |
| Contact | 顧客情報 |
| Agent | 対応担当者（ユーザー） |
| Assignee | 会話の担当者 |
| Webhook | 外部サービスへのイベント通知 |
| Hook | 外部連携（Slack, Dialogflow等）の設定 |
| Campaign | プロアクティブなメッセージキャンペーン |
| Automation Rule | 自動化ルール |
| Macro | 定型アクションの一括実行 |
| Portal | ヘルプセンター（ナレッジベース） |
| Article | ヘルプセンターの記事 |

### B. ファイル一覧

| ファイル/ディレクトリ | 説明 | 主な内容 |
|---------------------|------|---------|
| `app/controllers/api/v1/` | API v1コントローラー | RESTful API処理 |
| `app/models/conversation.rb` | 会話モデル | 会話の状態管理、関連定義 |
| `app/models/message.rb` | メッセージモデル | メッセージ処理 |
| `app/services/` | サービスクラス群 | ビジネスロジック |
| `app/jobs/` | バックグラウンドジョブ | 非同期処理 |
| `app/listeners/` | イベントリスナー | イベント駆動処理 |
| `app/javascript/dashboard/` | ダッシュボードUI | Vue 3アプリケーション |
| `config/routes.rb` | ルーティング | 全APIエンドポイント定義 |
| `enterprise/` | Enterprise版 | 有料機能、拡張 |

### C. 参考資料

- [Ruby on Rails Guides](https://guides.rubyonrails.org/)
- [Vue.js 3 Documentation](https://vuejs.org/guide/)
- [Vuex Documentation](https://vuex.vuejs.org/)
- [Pundit Authorization](https://github.com/varvet/pundit)
- [Wisper Pub/Sub](https://github.com/krisleech/wisper)
- [Sidekiq Background Jobs](https://github.com/mperham/sidekiq)
