BibleHighlight 모델 + 마이그레이션 + CRUD API

ID: 43b6fc59-8283-4dbd-a9b0-c4f55487bd0a

높음 완료

## 목표
BibleHighlight 모델 생성 + CRUD API 컨트롤러 구현

## 1단계: 모델 + 마이그레이션

### 마이그레이션
```ruby
create_table :bible_highlights, id: :string do |t|
t.string :user_id, null: false
t.string :book_abbrev, null: false # gen, exo, lev...
t.integer :chapter, null: false
t.integer :verse, null: false
t.string :color, default: "yellow", null: false # yellow, green, blue, pink, purple
t.text :note
t.timestamps
end
add_index :bible_highlights, [:user_id, :book_abbrev, :chapter, :verse], unique: true, name: "idx_bible_highlights_unique"
add_index :bible_highlights, :user_id
add_foreign_key :bible_highlights, :users
```

### 모델 (app/models/bible_highlight.rb)
- belongs_to :user
- validates :book_abbrev, :chapter, :verse, presence: true
- validates :color, inclusion: { in: %w[yellow green blue pink purple] }
- validates :verse, uniqueness: { scope: [:user_id, :book_abbrev, :chapter] }
- validates :chapter, :verse, numericality: { greater_than: 0 }

### User 모델 업데이트
- has_many :bible_highlights, dependent: :destroy

### Fixture (test/fixtures/bible_highlights.yml)
```yaml
genesis_1_1:
id: "bh-genesis-1-1"
user: daniel
book_abbrev: "gen"
chapter: 1
verse: 1
color: "yellow"
note: "태초에 하나님이 천지를 창조하시니라"

psalm_23_1:
id: "bh-psalm-23-1"
user: daniel
book_abbrev: "psa"
chapter: 23
verse: 1
color: "green"
```

### 모델 테스트
- valid with required attributes
- invalid without book_abbrev/chapter/verse
- invalid with wrong color
- unique constraint (same user+book+chapter+verse)
- chapter/verse must be positive integers

## 2단계: CRUD API

### 라우트 (config/routes.rb)
```ruby
namespace :api do
namespace :bible do
resources :highlights, only: [:index, :create, :destroy]
end
end
```

### 컨트롤러 (app/controllers/api/bible/highlights_controller.rb)
```ruby
class Api::Bible::HighlightsController < ApplicationController
before_action :authenticate_user!

def index
highlights = current_user.bible_highlights
.where(book_abbrev: params[:book], chapter: params[:chapter])
render json: highlights.map { |h|
{ id: h.id, verse: h.verse, color: h.color, note: h.note }
}
end

def create
highlight = current_user.bible_highlights.find_or_initialize_by(
book_abbrev: highlight_params[:book_abbrev],
chapter: highlight_params[:chapter],
verse: highlight_params[:verse]
)
highlight.assign_attributes(highlight_params)
if highlight.save
render json: { id: highlight.id, verse: highlight.verse, color: highlight.color, note: highlight.note }, status: :created
else
render json: { errors: highlight.errors.full_messages }, status: :unprocessable_entity
end
end

def destroy
highlight = current_user.bible_highlights.find(params[:id])
highlight.destroy
head :no_content
rescue ActiveRecord::RecordNotFound
head :not_found
end

private

def highlight_params
params.require(:highlight).permit(:book_abbrev, :chapter, :verse, :color, :note)
end
end
```

### 컨트롤러 테스트
- GET index: 특정 book+chapter 하이라이트 조회
- GET index: 다른 사용자 하이라이트 안 보임
- POST create: 새 하이라이트 생성 → 201
- POST create: 같은 위치 재생성 → upsert (색상 변경)
- POST create: 잘못된 파라미터 → 422
- DELETE destroy: 본인 하이라이트 삭제 → 204
- DELETE destroy: 다른 사용자 하이라이트 삭제 → 404
- 미인증 요청 → 리다이렉트

## 주의사항
- UUID PK: ApplicationRecord의 set_uuid 패턴 사용 (id: :string)
- SQLite: parallelize(workers: 1) 필수
- 마이그레이션 후 `bin/rails db:migrate` 실행
- structure.sql 갱신: `bin/rails db:schema:dump`
- 기존 테스트 전체 통과 확인

첨부 이미지

이미지 추가 (Ctrl+V로 붙여넣기 또는 클릭)

JPEG, PNG, GIF, WebP / 최대 10MB

담당자: highlight-backend
생성일: 2026년 03월 02일 12:22

활동 로그

  • 팀리드 상태 변경: 진행 중 → 완료

    2026년 03월 03일 04:57:50

  • H
    highlight-backend 티켓 클레임 완료

    2026년 03월 02일 12:31:45