Backend/RubyOnRails

[๋ฒˆ์—ญ] Ruby on Rails Model Patterns and Anti-patterns (Ruby On Rails์˜ ๋ชจ๋ธ์˜ ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด)

Seyun(Marco) 2024. 1. 4. 09:29
728x90

๐Ÿ’ก ์›๋ณธ๊ธ€ : https://blog.appsignal.com/2020/11/18/rails-model-patterns-and-anti-patterns.html

 

Ruby on Rails Model Patterns and Anti-patterns | AppSignal Blog

If you're struggling with models, this blog post is for you. We will quickly go through the process of putting your models on a diet and finish strongly with some things to avoid when writing migrations.

blog.appsignal.com

Ruby on Rails ํŒจํ„ด ๋ฐ ์•ˆํ‹ฐํŒจํ„ด ์‹œ๋ฆฌ์ฆˆ์˜ . ๋‘๋ฒˆ์งธ ๊ฒŒ์‹œ๋ฌผ์— ์˜ค์‹ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ์ง€๋‚œ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€(๋ฒˆ์—ญ๋ณธ)์—์„œ ์šฐ๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด์ด ๋ฌด์—‡์ธ์ง€ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋˜ํ•œ Rails ์„ธ๊ณ„์—์„œ ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด์— ๋Œ€ํ•ด์„œ๋„ ์–ธ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒŒ์‹œ๊ธ€์—์„œ๋Š” ๋ช‡ ๊ฐ€์ง€ Rails ๋ชจ๋ธ ์•ˆํ‹ฐ ํŒจํ„ด๊ณผ ํŒจํ„ด์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ๋ชจ๋ธ ๋ฌธ์ œ๋กœ ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ๋‹ค๋ฉด ์ด ๊ฒŒ์‹œ๊ธ€์ด ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชจ๋ธ์„ ๋‹ค์ด์–ดํŠธ๋ฅผ ํ•˜๋Š” ๊ณผ์ •์„ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰ํ•˜๊ณ  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ž‘์„ฑ ์‹œ ํ”ผํ•ด์•ผ ํ•  ๋ช‡ ๊ฐ€์ง€ ์‚ฌํ•ญ์œผ๋กœ ๋งˆ๋ฌด๋ฆฌ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ๋„์–ด๋“ค์–ด ๋ด…์‹œ๋‹ค.

์ง€๋ฐฉ ๊ณผ์ฒด์ค‘ ๋ชจ๋ธ

Rails ์›น์‚ฌ์ดํŠธ์ด๋“  API์ด๋“  Rails ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ๋•Œ ์‚ฌ๋žŒ๋“ค์€ ๋Œ€๋ถ€๋ถ„์˜ ๋กœ์ง์„ ๋ชจ๋ธ์— ์ €์žฅํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๋‚œ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์šฐ๋ฆฌ๋Š” ๋งŽ์€ ์ผ์„ ํ•˜๋Š” Song ํด๋ž˜์Šค๋ฅผ ์˜ˆ์‹œ๋ฅผ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด ๋ชจ๋ธ๊ณผ ๊ฐ™์ด ๋ชจ๋ธ์— ๋งŽ์€ ์ผ์„ ํ•˜๋ฉด ๊ทธ๊ฒƒ์€ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™(SRP)์— ์œ„๋ฐ˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

ํ•œ๋ฒˆ ์‚ดํŽด๋ด…์‹œ๋‹ค.

class Song < ApplicationRecord
  belongs_to :album
  belongs_to :artist
  belongs_to :publisher

  has_one :text
  has_many :downloads

  validates :artist_id, presence: true
  validates :publisher_id, presence: true

  after_update :alert_artist_followers
  after_update :alert_publisher

  def alert_artist_followers
    return if unreleased?

    artist.followers.each { |follower| follower.notify(self) }
  end

  def alert_publisher
    PublisherMailer.song_email(publisher, self).deliver_now
  end

  def includes_profanities?
    text.scan_for_profanities.any?
  end

  def user_downloaded?(user)
    user.library.has_song?(self)
  end

  def find_published_from_artist_with_albums
    ...
  end

  def find_published_with_albums
    ...
  end

  def to_wav
    ...
  end

  def to_mp3
    ...
  end

  def to_flac
    ...
  end
end

์ด ๋ชจ๋ธ์— ๋ฌธ์ œ์ ์€ ๋…ธ๋ž˜์™€ ๊ด€๋ จ๋œ ๋‹ค์–‘ํ•œ ๋กœ์ง์˜ ์“ฐ๋ ˆ๊ธฐ์žฅ์ด ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฉ”์†Œ๋“œ๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ํ•˜๋‚˜์”ฉ ์ฒœ์ฒœํžˆ ์ถ”๊ฐ€๋˜๋ฉด์„œ ์Œ“์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋ชจ๋ธ ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ๋” ์ž‘์€ ๋ชจ๋“ˆ๋กœ ๋ถ„ํ• ํ•  ๊ฒƒ์„ ์ œ์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‹จ์ˆœํžˆ ์ฝ”๋“œ๋ฅผ ํ•œ ๊ณณ์—์„œ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์˜ฎ๊ธฐ๋Š” . ๊ฒƒ๋ฟ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ด๋™ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ๋” ์ž˜๊ตฌ์„ฑํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๋Š” Fat Modal์„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ ์‚ฌ๋žŒ๋“ค์€ Rails์˜ concerns๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋ธ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ฐœ๊ฒฌํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์ด์ „์— ์ด ์ฃผ์ œ์— ๋Œ€ํ•ด ๊ธ€์„ ์“ด ์ ์ด ์žˆ๋Š”๋ฐ, ์–ด๋–ค ์‚ฌ๋žŒ๋“ค์€ ์ข‹์•„ํ•˜๊ธฐ๋„ ํ•˜๊ณ , ์–ด๋–ค ์‚ฌ๋žŒ๋“ค์€ ์ข‹์•„ํ•˜์ง€ ์•Š๊ธฐ๋„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์–ด์จŒ๋“ , concerns์˜ ๊ฐœ๋…์€ ๋ชจ๋“ˆ๊ณผ ์œ ์‚ฌํ•˜์ฃ . concerns๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํžˆ ์–ด๋””์—๋‚˜ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆ๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ๋ฟ์ด๋ผ๋Š” ์ ์„ ์ธ์‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋˜ ๋‹ค๋ฅธ ๋Œ€์•ˆ์€ ์†Œ๊ทœ๋ชจ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“  ๋‹ค์Œ์— ํ•„์š”ํ•  ๋•Œ ๋งˆ๋‹ค ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ณ€ํ™˜ ์ฝ”๋“œ๋ฅผ ๋ณ„๋„์˜ ํด๋ž˜์Šค๋กœ ์ถ”์ถœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class SongConverter
  attr_reader :song

  def initialize(song)
    @song = song
  end

  def to_wav
    ...
  end

  def to_mp3
    ...
  end

  def to_flac
    ...
  end
end

class Song
  ...

  def converter
    SongConverter.new(self)
  end

  ...
end

์šฐ๋ฆฌ๋Š” ์ด์ œ ๋‹ค๋ฅธ ํฌ๋งท์˜ songs๋ฅผ ๋‹ค๋ฅธ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ชฉ์ ์„ ๊ฐ€์ง„ Songconverter ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณ€ํ™˜์— ๋Œ€ํ•œ ์ž์ฒด ํ…Œ์ŠคํŠธ์™€ ํ–ฅํ›„์— ๋กœ์ง์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋…ธ๋ž˜๋ฅผ MP3๋กœ ๋ณ€ํ™˜ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@song.converter.to_mp3

๋‚˜์•„๊ฒ ์ด๊ฒŒ module์ด๋‚˜ concern์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ๋ณด๋‹ค ๋” ๋ช…ํ™•ํ•ด ๋ณด์ž…๋‹ˆ๋‹ค. ์–ด์ฉŒ๋ฉด ์ƒ์†๋ณด๋‹ค ์กฐํ•ฉ์„ ๋” ์„ ํ˜ธํ•˜๊ธฐ ๋•Œ๋ฌธ์ผ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋” ์ง๊ด€์ ์ด๊ณ  ์ฝ๊ธฐ ์‰ฝ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์–ด๋Š ๊ธธ๋กœ ๊ฐˆ ๊ฒƒ์ธ์ง€์— ๊ฒฐ์ •์„ ํ•˜๊ธฐ ์ „์— ๋‘ ๊ฐ€์ง€ ์‚ฌ๋ก€๋ฅผ ๋ชจ๋‘ ๊ฒ€ํ† ํ•ด๋ณด์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋˜ํ•œ ์›ํ•˜๋ฉด ๋‘˜๋‹ค ์„ ํƒํ• . ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ฌด๋„ ๋‹น์‹ ์„ ๋ง‰์„ . ์ˆœ์—†์Šต๋‹ˆ๋‹ค.

SQL Pasta Parmesan

์‹ค์ƒํ™œ์—์„œ ๋ง›์ž‡๋Š” ํŒŒ์Šคํƒ€๋ฅผ ์ข‹์•„ํ•˜์ง€ ์•Š๋Š” ์‚ฌ๋žŒ์ด ์žˆ์„๊นŒ์š”? ๋ฐ˜๋ฉด์— ์ฝ”๋“œ ํŒŒ์Šคํƒ€์— ๊ด€ํ•ดํ•ด์„œ๋Š” ์ด์•ผ๊ธฐ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๊ฑฐ์˜ ์•„๋ฌด๋„ ์ข‹์•„ํ•˜์ง€ ์•Š์ฃ . ๊ทธ๋Ÿด ๋งŒํ•œ ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Rails๋ชจ๋ธ์—์„œ ActiveRecord ์‚ฌ์šฉ์ด ์ŠคํŒŒ๊ฒŒํ‹ฐ์ฒ˜๋Ÿผ ์—‰ํ‚ค๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ์ฝ”๋“œ๋ฒ ์ด์Šค ์ „์ฒด๋ฅผ ๋’ค๋ฎ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ ์ด ๋ฌธ์ œ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

๊ธด ์ฟผ๋ฆฌ๊ฐ€ ์ŠคํŒŒ๊ฒŒํ‹ฐ์ฒ˜๋Ÿผ ๊ผฌ์ด๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๋ช‡๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ์–ด๋””์—๋‚˜ ์žˆ์„ . ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Song ๋ชจ๋ธ๋กœ ๋Œ์•„๊ฐ€๋ด…์‹œ๋‹ค. ํŠนํžˆ, ๋ชจ๋ธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ํ• ๋•Œ๋ฅผ ์ƒ๊ฐํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

class SongReportService
  def gather_songs_from_artist(artist_id)
    songs = Song.where(status: :published)
                .where(artist_id: artist_id)
                .order(:title)

    ...
  end
end

class SongController < ApplicationController
  def index
    @songs = Song.where(status: :published)
                 .order(:release_date)

    ...
  end
end

class SongRefreshJob < ApplicationJob
  def perform
    songs = Song.where(status: :published)

    ...
  end
end

์œ„ ์˜ˆ์‹œ์—์„œ Song ๋ชจ๋ธ์ด ์ฟผ๋ฆฌ๋˜๋Š” ์„ธ ๊ฐ€์ง€ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋…ธ๋ž˜์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ๋ณด๊ณ ์— ์‚ฌ์šฉ๋˜๋Š” SongReporterService์—์„œ๋Š” ํŠน์ • ์•„ํ‹ฐ์ŠคํŠธ์˜ ๋…ธ๋ž˜๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ์— SongController์—์„œ ๊ฒŒ์‹œ๋œ ๋…ธ๋ž˜๋ฅผ ๊ฐ€์ ธ์™€ ์ถœ์‹œ ๋‚ ์งœ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ SongRefreshJob์—์„œ๋Š” ๊ฒŒ์‹œ๋œ ๋…ธ๋ž˜๋งŒ ๊ฐ€์ ธ์™€์„œ Job์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ •๋„๋ฉด ๊ดœ์ฐฎ์ง€๋งŒ, ๊ฐ‘์ž๊ธฐ ์ƒํƒœ ์ด๋ฆ„์„ released๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๋…ธ๋ž˜๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ๋ชจ๋“  ๋ฐœ์ƒ ๋ถ€๋ถ„์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ . ์œ„์ฝ”๋“œ๋Š” DRY ์›์น™์„ ๋”ฐ๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค๋งํ•˜์ง€๋งˆ์„ธ์š”. ๋‹คํ–‰ํžˆ ์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Rails์˜ scope๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ์ฝ”๋“œ๋ฅผ DRYํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. scope๋Š” ์—ฐ๊ด€ ๋ฐ ๊ฐ์ฒด์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์ฝ๊ธฐ ์‰ฝ๊ณ  ๋ณ€๊ฒฝํ•˜๊ธฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์–ด์ฉŒ๋ฉด ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€ scope๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด joins, where ๋“ฑ๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ activeRecord ๋ฉ”์„œ๋“œ๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. scope๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณด์ด๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

class Song < ApplicationRecord
  ...

  scope :published, ->            { where(published: true) }
  scope :by_artist, ->(artist_id) { where(artist_id: artist_id) }
  scope :sorted_by_title,         { order(:title) }
  scope :sorted_by_release_date,  { order(:release_date) }

  ...
end

class SongReportService
  def gather_songs_from_artist(artist_id)
    songs = Song.published.by_artist(artist_id).sorted_by_title

    ...
  end
end

class SongController < ApplicationController
  def index
    @songs = Song.published.sorted_by_release_date

    ...
  end
end

class SongRefreshJob < ApplicationJob
  def perform
    songs = Song.published

    ...
  end
end

์œ„์™€ ๊ฐ™์ด ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž˜๋ผ๋‚ด์„œ ๋ชจ๋ธ์— ๋„ฃ์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฒƒ์ด ํ•ญ์ƒ ์ตœ์„ ์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ํŠนํžˆ God ๊ฐ์ฒด๋‚˜ Fat Model์ด๋ผ๊ณ  ์ง„๋‹จ๋ฐ›์€ ๊ฒฝ์šฐ์—๋Š” ๋”์šฑ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ๋ชจ๋ธ์— ์ ์  ๋” ๋งŽ์€ ๋ฉ”์„œ๋“œ์™€ ์ฑ…์ž„์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ์ƒ๊ฐ์ด ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ œ ์กฐ์–ธ์€ ๋ฒ”์œ„ ์‚ฌ์šฉ์„ ์ตœ์†Œํ•œ์œผ๋กœ ์œ ์ง€ํ•˜๊ณ  ๊ฑฐ๊ธฐ์— ์ผ๋ฐ˜์ ์ธ ์ฟผ๋ฆฌ๋งŒ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๊ฒฝ, **where(published: true)**๋Š” ์–ด๋””์—์„œ๋‚˜ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์™„๋ฒฝํ•œ scope๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ SQL ๊ด€๋ จ ์ฝ”๋“œ์˜ ๊ฒฝ์šฐ Repository ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒŒ ๋ฌด์—‡์ธ์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Repository Pattern

์ด๋ฒˆ์— ์šฐ๋ฆฌ๊ฐ€ ๋ณด์—ฌ๋“œ๋ฆด ๊ฒƒ์€ Domain-Driven Design ์ฑ…์— ์ •์˜๋œ 1:1 Repository ํŒจํ„ด์ž…๋‹ˆ๋‹ค. Rails Repository ํŒจํ„ด์˜ ๊ธฐ๋ณธ์ ์ธ ์ •์˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋…ผ๋ฆฌ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋…ผ๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ Active Record ๋Œ€์‹  ์›์‹œ SQL ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ, ์ •๋ง ํ•„์š”ํ•˜์ง€ ์•Š์€ ํ•œ ์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๊ฐ€ ํ• ์ˆ˜ ์žˆ๋Š” ์ผ์€ SongRepository๋ฅผ ๋งŒ๋“ค๊ณ  ๊ฑฐ๊ธฐ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ์ง์„ ๋„ฃ๋Š”๊ฒƒ์ž…๋‹ˆ๋‹ค.

class SongRepository
  class << self
    def find(id)
      Song.find(id)
    rescue ActiveRecord::RecordNotFound => e
      raise RecordNotFoundError, e
    end

    def destroy(id)
      find(id).destroy
    end

    def recently_published_by_artist(artist_id)
      Song.where(published: true)
          .where(artist_id: artist_id)
          .order(:release_date)
    end
  end
end

class SongReportService
  def gather_songs_from_artist(artist_id)
    songs = SongRepository.recently_published_by_artist(artist_id)

    ...
  end
end

class SongController < ApplicationController
  def destroy
    ...

    SongRepository.destroy(params[:id])

    ...
  end
end

์šฐ๋ฆฌ๊ฐ€ ์—ฌ๊ธฐ์„œ ํ•œ ์ผ์€ ์ฟผ๋ฆฌ ๋กœ์ง์„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•œ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•œ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ชจ๋ธ์€ ๋” ์ด์ƒ scope์™€ ๋กœ์ง๊ณผ ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ปจํ‹€๋กค๋Ÿฌ์™€ ๋ชจ๋ธ์ด ์–‡์•„์„œ ๋ชจ๋‘๊ฐ€ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค. ์˜ฌ๋ฐ”๋ฅธ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋‚˜์š”?? ๊ธ€์Ž„์š”. ์—ฌ์ „ํžˆ ActiveRecord๊ฐ€ ๋ชจ๋“  ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” find๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋‹ค์Œ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

SELECT "songs".* FROM "songs" WHERE "songs"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

์ตœ์‹ ์˜ ๋ฐฉ๋ฒ•์€ ์ด ๋ชจ๋“  ๊ฒƒ์„ SongRepository ๋‚ด๋ถ€์— ์ •์˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•ž์„œ ๋งํ–ˆ๋“ฏ์ด ์ €๋Š” ์ด ๋ฐฉ๋ฒ•์„ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ตณ์ด ํ•„์š”ํ•˜์ง€ ์•Š๊ณ , ๋ชจ๋“  ๊ถŒํ•œ์„ ๊ฐ–๊ณ  ์‹ถ์–ดํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ActiveRecord ๋Œ€์‹  raw SQL์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ActiveRecord์—์„œ ์‰ฝ๊ฒŒ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ณต์žกํ•œ SQL ํŠธ๋ฆญ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

raw SQL๊ณผ ActiveRecord์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐ๋ฅผ ํ•˜๋‹ค ๋ณด๋‹ˆ ํ•œ ๊ฐ€์ง€ ๋” ์–ธ๊ธ‰ํ•ด์•ผ ํ•  ์ฃผ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ๊ทธ๊ฒƒ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์ด์ œ ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Migrations — ๋ˆ„๊ฐ€ ์‹ ๊ฒฝ์“ฐ๋‚˜์š”?

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ž‘์„ฑํ•  ๋•Œ ์ข…์ข… ๋“ฃ๋Š” ๋ง์ด ‘๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋Š” ๋‚˜๋จธ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๋งŒํผ ํ›Œ๋ฅญํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.’๋Š” ์ด์•ผ๊ธฐ ์ž…๋‹ˆ๋‹ค. ์ €๋Š” ์ด ์ฃผ์žฅ์— ์ „ํ˜€ ๊ณต๊ฐํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ‘ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๊ณ  ์žŠํ˜€์ง€๋Š” ๊ฒƒ’์ด๋ผ๋Š” ๋ณ€๋ช…์œผ๋กœ ๋ƒ„์ƒˆ ๋‚˜๋Š” ์ฝ”๋“œ๋ฅผ ์‰ฝ๊ฒŒ ๋„ฃ์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.

ํ˜„์‹ค์€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋” ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ทธ๋“ค์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด ๋‹ค๋ฅธ ๋ถ€๋ถ„๋“ค๊ณผ์˜ ์ƒํ˜ธ ์ž‘์šฉ์— ๋Œ€ํ•ด ์ž˜ ๋ชจ๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—‰ํ„ฐ๋ฆฌ ์ผํšŒ์„ฑ ์ฝ”๋“œ๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์— ์‚ฝ์ž…ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ๊ฐ€ ์†์ƒ๋˜๊ฑฐ๋‚˜ ์ด์ƒํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์œผ๋กœ ์ธํ•ด ๋ˆ„๊ตฐ๊ฐ€์˜ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ๋ช‡ ์‹œ๊ฐ„ ๋™์•ˆ ๋ง์น  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์•ˆํ‹ฐํŒจํ„ด์ธ์ง€๋Š” ํ™•์‹คํ•˜์ง€๋Š” ์•Š์ง€๋งŒ, ๋ฐ˜๋“œ์‹œ ์œ ๋…ํ•ด์•ผ ํ•˜๋Š” ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”? ํ”„๋กœ์ ํŠธ ์ฐธ์—ฌ์ž ๋ชจ๋‘์—๊ฒŒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋” ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ํŒ ๋ชฉ๋ก์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ•ญ์ƒ Down ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์–ธ์ œ ์–ด๋–ค๊ฒƒ์ด ๋กค๋ฐฑ๋ ์ง€ ์•Œ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋˜๋Œ๋ฆด์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ActiveRecord::IrreversibleMigration ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์„ธ์š”:

def down
  raise ActiveRecord::IrreversibleMigration
end

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—์„œ ActiveRecord๋ฅผ ํ”ผํ•˜์„ธ์š”.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ์‹œ์ ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ๋ฅผ ์ œ์™ธํ•œ ์™ธ๋ถ€ ์ข…์†์„ฑ์„ ์ตœ์†Œํ™” ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ActiveRecord validation์ด ์—†์–ด์„œ ์ƒํ™ฉ์„ ๋ง์น (๋˜๋Š” ์–ด์ฉŒ๋ฉด ๊ตฌํ•ด์ค„) ์ˆ˜๋„ ์—†์Šต๋‹ˆ๋‹ค. ์ˆœ์ˆ˜ํ•œ SQL๋งŒ ๋‚จ์•˜์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์•„ํ‹ฐ์ŠคํŠธ์˜ ๋ชจ๋“  ๋…ธ๋ž˜๋ฅผ ๊ฒŒ์‹œํ•˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

class UpdateArtistsSongsToPublished < ActiveRecord::Migration[6.0]
  def up
    execute <<-SQL
      UPDATE songs
      SET published = true
      WHERE artist_id = 46
    SQL
  end

  def down
    execute <<-SQL
      UPDATE songs
      SET published = false
      WHERE artist_id = 46
    SQL
  end
end

๋งŒ์•ฝ Song ๋ชจ๋ธ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋‚ด๋ถ€์—์„œ ๋ชจ๋ธ์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ app/models ์— ์žˆ๋Š” ์‹ค์ œ ActiveRecord ๋ชจ๋ธ์ด ๋ณ€๊ฒฝ๋˜์–ด๋„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฒƒ์ด ์™„๋ฒฝํ•œ ํ•ด๊ฒฐ์ฑ…์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ๋‹ค์Œ ์ฃผ์ œ๋กœ ๋„˜์–ด๊ฐ‘์‹œ๋‹ค.

๋ฐ์ดํ„ฐ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ์Šคํ‚ค๋งˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋ถ„๋ฆฌํ•ด๋ผ.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์— ๋Œ€ํ•œ Rails ๊ฐ€์ด๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ ๋‚ด์šฉ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ Active Record์˜ ๊ธฐ๋Šฅ์œผ๋กœ, ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ๋ฐœ์ „์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. raw SQL๋กœ ์Šคํ‚ค๋งˆ ์ˆ˜์ •์„ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ , ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด Ruby DSL์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ธฐ์ˆ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ€์ด๋“œ์˜ ์š”์•ฝ ๋‚ด์šฉ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ํŽธ์ง‘ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ์–ธ๊ธ‰์€ ์—†๊ณ , ์˜ค์ง ๊ตฌ์กฐ์— ๋Œ€ํ•ด์„œ๋งŒ ์–ธ๊ธ‰๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‘ ๋ฒˆ์งธ ํฌ์ธํŠธ์—์„œ song์„ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ฐ˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•œ ๊ฒƒ์€ ์™„์ „ํžˆ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์—์„œ ์ •๊ธฐ์ ์œผ๋กœ ์ด์™€ ์œ ์‚ฌํ•œ ์ž‘์—…์„ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ [data_migrate gem](<https://github.com/ilyakatz/data-migrate>) ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”. ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ์Šคํ‚ค๋งˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋ถ„๋ฆฌํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด gem์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด์ „ ์˜ˆ์ œ๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

bin/rails generate data_migration update_artists_songs_to_published

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

class UpdateArtistsSongsToPublished < ActiveRecord::Migration[7.0]
  def up
    execute <<-SQL
      UPDATE songs
      SET published = true
      WHERE artist_id = 46
    SQL
  end

  def down
    execute <<-SQL
      UPDATE songs
      SET published = false
      WHERE artist_id = 46
    SQL
  end
end

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด db/migrate ๋””๋ ‰ํ„ฐ๋ฆฌ ๋‚ด๋ถ€์˜ ๋ชจ๋“  ์Šคํ‚ค๋งˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ db/data ์—์„  ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์œ ์ง€๋˜๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰ ์ƒ๊ฐ

Rails์—์„œ ๋ชจ๋ธ์„ ๋‹ค๋ฃจ๊ณ  ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์€ ๋Š์ž„์—†๋Š” ๋„์ „์ž…๋‹ˆ๋‹ค. ์ด ๋ธ”๋กœ๊ทธ ๊ธ€์—์„œ ํ”ํžˆ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•จ์ •๊ณผ ํ•ด๊ฒฐ์ฑ…์„ ์‚ดํŽด๋ณด์…จ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ ๋ชจ๋ธ ๋ฐ˜ ํŒจํ„ด๊ณผ ํŒจํ„ด ๋ชฉ๋ก์€ ์™„๋ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค๋งŒ, ์ตœ๊ทผ์— ๋ฐœ๊ฒฌํ•œ ๊ฐ€์žฅ ์ฃผ๋ชฉํ•  ๋งŒํ•œ ๊ฒƒ๋“ค์„ ์†Œ๊ฐœํ–ˆ์Šต๋‹ˆ๋‹ค.

๋” ๋งŽ์€ Rails ํŒจํ„ด๊ณผ ๋ฐ˜ ํŒจํ„ด์— ๊ด€์‹ฌ์ด ์žˆ๋‹ค๋ฉด ์‹œ๋ฆฌ์ฆˆ์˜ ๋‹ค์Œ ์—ํ”ผ์†Œ๋“œ๋ฅผ ๊ธฐ๋Œ€ํ•ด ์ฃผ์„ธ์š”. ๋‹ค๊ฐ€์˜ค๋Š” ๊ธ€์—์„œ๋Š” Rails MVC์˜ ๋ทฐ ๋ฐ ์ปจํŠธ๋กค๋Ÿฌ ๋ถ€๋ถ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ์ฑ…์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์‹œ๊ฐ„๊นŒ์ง€, ๊ฑด๋ฐฐ!

PS. Ruby Magic ๊ฒŒ์‹œ๋ฌผ์ด ๋ณด๋„๋˜๋Š” ๋Œ€๋กœ ์ฝ๊ณ  ์‹ถ์œผ์‹œ๋ฉด Ruby Magic ๋‰ด์Šค๋ ˆํ„ฐ๋ฅผ ๊ตฌ๋…ํ•˜์‹œ๊ณ  ๋‹จ ํ•˜๋‚˜์˜ ๊ฒŒ์‹œ๋ฌผ๋„ ๋†“์น˜์ง€ ๋งˆ์„ธ์š”!

728x90
728x90