Backend/RubyOnRails

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

Seyun(Marco) 2024. 1. 11. 01:05
728x90

๐Ÿ’ก ์›๋ณธ๊ธ€ : https://blog.appsignal.com/2021/02/10/ruby-on-rails-view-patterns-and-anti-patterns.html

 

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

Rails views are sometimes amazing and fast, and at other times, they can have all sorts of issues. If you want to increase confidence over how you handle your views, then this blog post is for you.

blog.appsignal.com

 

Ruby On Rails ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด ์‹œ๋ฆฌ์ฆˆ์˜ ์„ธ ๋ฒˆ์งธ ํŽธ์— ์˜ค์‹ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์šฐ๋ฆฌ๋Š” Rails ๋ชจ๋ธ๊ณผ ๊ด€๋ จํ•œ ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด๊ณผ ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ ๋‹ค๋ฃจ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” Rail์˜ view์™€ ๊ด€๋ จ๋œ ๋ช‡ ๊ฐ€์ง€ ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Rails View๋Š” ๋•Œ๋กœ๋Š” ์™„๋ฒฝํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ๋„ ํ•˜์ง€๋งŒ, ์˜จ๊ฐ– ์ข…๋ฅ˜์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. View๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์‹ ๊ฐ์„ ๋†’์ด๊ณ  ์‹ถ๊ฑฐ๋‚˜ ์ด ์ฃผ์ œ์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์ด ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ ๋ฐ”๋กœ ์‹œ์ž‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์•„์‹œ๋‹ค์‹œํ”ผ Rails ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ๊ตฌ์„ฑ์— ๋Œ€ํ•œ ๊ทœ์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Rails๋Š” ๋ชจ๋ธ-๋ทฐ-์ปจํŠธ๋กค๋Ÿฌ(MVC) ํŒจํ„ด์„ ์ค‘์š”์‹œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ชจํ† ๋Š” ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ View ์ฝ”๋“œ์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ๋งˆํฌ์—…(ERB ๋˜๋Š” Slim ํŒŒ์ผ), JavaScript ๋ฐ CSS ํŒŒ์ผ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์–ธ๋œป ๋ณด๊ธฐ์—๋Š” View ๋ ˆ์ด์–ด๊ฐ€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์‰ฝ๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์š”์ฆ˜์—๋Š” View ๋ ˆ์ด์–ด์— ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ์ˆ ์ด ํ˜ผ์žฌ๋˜์–ด ์žˆ๋‹ค๋Š” ์ ์„ ๋ช…์‹ฌํ•˜์„ธ์š”.

View์—๋Š” JavaScript, HTML, CSS๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ์„ธ๊ฐ€์ง€ ๊ธฐ์ˆ ์€ ํ˜ผ๋™๊ณผ ์ฝ”๋“œ์˜ ๋ฌด์งˆ์„œ๋ฅผ ์ดˆ๋ž˜ํ•˜์—ฌ ํฐ ์˜๋ฏธ๊ฐ€ ์—†๋Š” ๊ตฌํ˜„์œผ๋กœ ์ด์–ด์งˆ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ๋„ ์˜ค๋Š˜์€ Rails View ๊ณ„์ธต์˜ ๋ช‡ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Powerlifting Views

์ด ์‹ค์ˆ˜๋Š” ์ž์ฃผ ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ,, ๋ฐœ์ƒํ•˜๋ฉด ๋ˆˆ์— ๊ฑฐ์Šฌ๋ฆฌ๋Š” ์‹ค์ˆ˜์ž…๋‹ˆ๋‹ค. ๋•Œ๋•Œ๋กœ ์‚ฌ๋žŒ๋“ค์€ ๋„๋ฉ”์ธ ๋กœ์ง์ด๋‚˜ ์ฟผ๋ฆฌ๋ฅผ View ๋‚ด๋ถ€์— ์ง์ ‘ ๋„ฃ๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด View ๋ ˆ์ด์–ด๊ฐ€ ๋ฌด๊ฑฐ์šด ์ž‘์—…์ด๋‚˜ ํŒŒ์›Œ๋ฆฌํ”„ํŒ…(Powerlifting)์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํฅ๋ฏธ๋กœ์šด ์ ์€ Rails๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‹ค์ œ ์ด๋Ÿฐ ์ผ์ด ์‰ฝ๊ฒŒ ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์™€ ๊ด€๋ จํ•˜์—ฌ ‘์•ˆ์ „๋ง’์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— View ๋ ˆ์ด์–ด๊ฐ€ ์›ํ•˜๋Š” ๋ฌด์—‡์ด๋“  ๋‹ค ํ• ์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

MVCํŒจํ„ด์˜ View ๋ ˆ์ด์–ด์˜ ์ •์˜์— ๋”ฐ๋ผ ํ•ด๋‹น ๋ ˆ์ด์–ด์—๋Š” ํ”„๋ฆฌ์  ํ…Œ์ด์…˜(ํ‘œํ˜„)ํ•˜๋Š” ๋กœ์ง์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ ๋กœ์ง์ด๋‚˜ ๋ฐ์ดํ„ฐ ์ฟผ๋ฆฌ์—๋Š” ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. Rails์—๋Š” ๋ฃจ๋น„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ERB ํŒŒ์ผ(์ž„๋ฒ ๋””๋“œ ๋ฃจ๋น„)๊ฐ€ ์ œ๊ณต๋˜๋ฉฐ, ์ด ํŒŒ์ผ์€ HTML๋กœ ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค. index ํŽ˜์ด์ง€์— ๋…ธ๋ž˜๋ฅผ ๋‚˜์—ดํ•˜๋Š” ์›น์‚ฌ์ดํŠธ์˜ ์˜ˆ๋ฅผ ์ƒ๊ฐํ•ด๋ณด๋ฉด, View ๋กœ์ ์€ app/views/songs/index.html.erb์— ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํŒŒ์›Œ๋ฆฌํ”„ํŒ…(Powerlifting)์ด ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๊ณ  ๋ฌด์—‡์„ ํ•˜์ง€ ๋ง์•„์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ์„ค๋ช… ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:

# app/views/songs/index.html.erb

<div class="songs">
  <% Song.where(published: true).order(:title) do |song| %>
    <section id="song_<%= song.id %>">
      <span><%= song.title %></span>

      <span><%= song.description %></span>

      <a href="<%= song.download_url %>">Download</a>
    </section>
  <% end %>
</div>

์—ฌ๊ธฐ์„œ ํฌ๊ฒŒ ๋ณด์—ฌ์ง€๋Š” ์•ˆํ‹ฐํŒจํ„ด์€ ๋งˆํฌ์—…์—์„œ ๋ฐ”๋กœ Song์„ ๊ฐ€์ ธ์˜ค๋Š” “Song.where(published: true).order(:title)” ์ฝ”๋“œ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฑ…์ž„์€ Controller ๋˜๋Š” Service์— ์œ„์ž„ํ•ด์•ผ ํ•˜๋Š”๋ฐ ๊ฐ€๋” Controller์—์„œ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•œ ํ›„์— ๋‚˜์ค‘์— View์—์„œ ๋” ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ด…๋‹ˆ๋‹ค. ์ด๋Š” ์ž˜๋ชป๋œ ์„ค๊ณ„์ด๋ฉฐ ์ฟผ๋ฆฌ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋” ๋ถ€ํ•˜๋ฅผ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์›น์‚ฌ์ดํŠธ ์†๋„๊ฐ€ ๋Š๋ ค์ง‘๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹ ๋Œ€์‹ ์— Controller ์•ก์…˜์—์„œ “@Song”์˜ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋ฅผ ๋…ธ์ถœํ•˜๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งˆํฌ์—…์—์„œ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

class SongsController < ApplicationController
  ...

  def index
    @songs = Song.all.where(published: true).order(:title)
  end

  ...
end
# app/views/songs/index.html.erb

<div class="songs">
  <% @songs.each do |song| %>
    <section id="song_<%= song.id %>">
      <span><%= song.title %></span>

      <span><%= song.description %></span>

      <a href="<%= song.download_url %>">Download</a>
    </section>
  <% end %>
</div>

์ด ์˜ˆ์ œ๋Š” ์™„๋ฒฝํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. Controller ์ฝ”๋“œ๋ฅผ ๋” ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณ  SQL ํŒŒ์Šคํƒ€ ์ฝ”๋“œ๋ฅผ ํ”ผํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด์ „ ๋ธ”๋กœ๊ทธ(๋ฒˆ์—ญ๋ณธ)๋ฅผ ํ™•์ธํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋˜ํ•œ View ๋ ˆ์ด์–ด์—์„œ ๋กœ์ง์„ ์ œ์™ธํ•˜๋ฉด ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด๊ฒฐ์ฑ…์„ ๊ตฌ์ถ•ํ•˜๋ ค๋Š” ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.

Rails๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๊ธฐ

์—ฌ๊ธฐ์„œ๋Š” ์งง๊ฒŒ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋กœ์„œ Ruby On Rails๋Š” ํŠนํžˆ View ๋‚ด๋ถ€์— ๊น”๋”ํ•œ Helper๊ฐ€ ๋งŽ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ‹์ง„ ์ž‘์€ Helper๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด View ๋ ˆ์ด์–ด๋ฅผ ๋น ๋ฅด๊ณ  ์‰ฝ๊ฒŒ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Rails์˜ ์ดˆ๋ณด์ž๋ผ๋ฉด erbํŒŒ์ผ ๋‚ด์— ์ „์ฒด HTML์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:

# app/views/songs/new.html.erb

<form action="/songs" method="post">
  <div class="field">
    <label for="song_title">Title</label>
    <input type="text" name="song[title]" id="song_title">
  </div>

  <div class="field">
    <label for="song_description">Description</label>
    <textarea name="song[description]" id="song_description"></textarea>
  </div>

  <div class="field">
    <label for="song_download_url">Download URL</label>
    <textarea name="song[download_url]" id="song_download_url"></textarea>
  </div>

  <input type="submit" name="commit" value="Create Song">
</form>

์ด HTML์„ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜ ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ์šด ๋…ธ๋ž˜๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š” Form์„ ์–ป์„์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 
 

 

ํ•˜์ง€๋งŒ Rails๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, Rails๊ฐ€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜ HTML์„ ์ž‘์„ฑํ•  ํ•„์š”๋„ ์—†๊ณ  ์ž‘์„ฑํ•ด์„œ๋„ ์•ˆ๋ฉ๋‹ˆ๋‹ค. form_with view helper๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด HTML์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด ์ค๋‹ˆ๋‹ค. form_with์€ Rails 5.1์— ๋„์ž…๋˜์—ˆ์œผ๋ฉฐ ์ผ๋ถ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ต์ˆ™ํ•œ form_tag ๋ฐ form_for๋ฅผ ๋Œ€์ฒดํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. form_with์ด ์–ด๋–ป๊ฒŒ ์ถ”๊ฐ€ ์ฝ”๋“œ ์ž‘์„ฑ์„ ๋œ์–ด์ฃผ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

<%= form_with(model: song, local: true) do |form| %>
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :description %>
    <%= form.text_area :description %>
  </div>

  <div class="field">
    <%= form.label :download_url do %>
      Download URL
    <% end %>
    <%= form.text_area :download_url %>
  </div>

  <%= form.submit %>
<% end %>

form_with”์€ HTML์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„ CSRF ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ์ธ์ฆ ํ† ํฐ๋„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒฝ์šฐ์— ์ง€์ •๋œ ํ—ฌํผ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์œผ๋ฉฐ, ์ด๋Š” Rails ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์ž˜ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜ HTML Form์„ ์ œ์ถœํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์š”์ฒญ๊ณผ ํ•จ๊ป˜ ์ œ์ถœ๋œ ์œ ํšจํ•œ ํ† ํฐ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

CSRF(Cross Site Request Forgery)๋ž€?

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

 

form_with”, “label”, “text_area”, “submit” ํ—ฌํผ ์™ธ์—๋„ Rails์—๋Š” View ํ—ฌํผ๊ฐ€ ๋” ๋งŽ์ด ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ View ํ—ฌํผ๋Š” ์—ฌ๋Ÿฌ๋ถ„๋“ค์˜ ๊ฐœ๋ฐœ ์‚ถ์„ ๋” ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ์ฃผ๊ธฐ ์œ„ํ•ด ์กด์žฌํ•˜๋ฏ€๋กœ ๋” ์ž˜ ์•Œ์•„๋‘ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ฌ์Šคํƒ€์ค‘ ํ•˜๋‚˜๋Š” “link_to” ์ž…๋‹ˆ๋‹ค.

<%= link_to "Songs", songs_path %>

์•„๋ž˜์™€ ๊ฐ™์ด HTML์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

<a href="/songs">Songs</a>

๊ฐ ํ—ฌํผ์— ๋Œ€ํ•ด์„œ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๋‹ค ๋ณด๋ฉด ์ด๊ธ€์ด ๋„ˆ๋ฌด ๊ธธ์–ด์ง€๊ณ , ๋ชจ๋“  ํ—ฌํผ๋ฅผ ์‚ดํŽด๋ณด๋Š” ๊ฒƒ์ด ์˜ค๋Š˜์˜ ์ฃผ์ œ๋Š” ์•„๋‹ˆ๋ฏ€๋กœ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ถ๊ธˆํ•˜์‹œ๋‹ค๋ฉด **Rails Action View helpers guide**๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ํ•„์š”ํ•œ ํ—ฌํผ๋ฅผ ์„ ํƒํ•˜๋Š”๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

View ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์ •๋ฆฌํ•˜๊ธฐ

์™„๋ฒฝํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค. ์™„๋ฒฝํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€์—๋Š” if-else๋ฌธ์ด ์—†๊ณ  Controller์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ HTML ํƒœ๊ทธ ์‚ฌ์ด์— ๋„ฃ๋Š” ์ˆœ์ˆ˜ํ•œ ์ฝ”๋“œ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํ•ด์ปคํ†ค์ด๋‚˜ ๊ฟˆ์†์—์„œ๋‚˜ ์กด์žฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” View๋ฅผ ๋žœ๋”๋งํ•  ๋•Œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ถ„๊ธฐ์™€ ์กฐ๊ฑด์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€์˜ ์ผ๋ถ€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋กœ์ง์ด ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ง€๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”? ์ผ๋ฐ˜์ ์ธ ๋Œ€๋‹ต์€ ์ตœ์‹  JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณต์žกํ•œ ๊ฒƒ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๊ฒŒ์‹œ๋ฌผ์€ Rails View์— ๊ด€ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ ๊ทธ ์•ˆ์— ์žˆ๋Š” ์„ ํƒ์ง€๋“ค์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

After-Market (Custom) Helpers

Song ์•„๋ž˜์— CTA(call-to-action) ๋ฒ„ํŠผ์„ ํ‘œ์‹œํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•œ ๊ฐ€์ง€ ์ •์ฑ…์ด ์žˆ์Šต๋‹ˆ๋‹ค. Song์— ๋‹ค์šด๋กœ๋“œ URL์ด ์กด์žฌํ• ์ˆ˜๋„ ์žˆ๊ณ  ์•„๋‹์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

app/views/songs/show.html.erb

...

<div class="song-cta">
  <% if @song.download_url %>
    <%= link_to "Download", download_url %>
  <% else %>
    <%= link_to "Subscribe to artists updates",
                artist_updates_path(@song.artist) %>
  <% end %>
</div>

...

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

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์€ ๋ณ„๋„์˜ ํ—ฌํผ๋กœ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ๋„ Rails๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ํ—ฌํผ๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. “app/helpers”์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด “SongHelper”๋ฅผ ๋งŒ๋“ค์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

module SongsHelper
  def song_cta_link
    content_tag(:div, class: 'song-cta') do
      if @song.download_url
        link_to "Download", @song.download_url
      else
        link_to "Subscribe to artists updates",
                artist_updates_path(@song.artist)
      end
    end
  end
end

Song์˜ show ํŽ˜์ด์ง€๋ฅผ ์—ด์–ด๋„ ์—ฌ์ „ํžˆ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ์˜ˆ์ œ๋ฅผ ์กฐ๊ธˆ ๋” ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜์ธ “@Song”์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋…ธ๋ž˜๊ฐ€ nil์ธ ๊ณณ์—์„œ ์ด ํ—ฌํผ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค๋ฉด ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ• ์ˆ˜๊ฐ€ ์—†์„์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜ ํ˜•ํƒœ์˜ ์™ธ๋ถ€ ์ข…์†์„ฑ์„ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ—ฌํผ์—๊ฒŒ ์ธ์ˆ˜๋ฅผ ์ „๋‹ฌํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

module SongsHelper
  def song_cta_link(song)
    content_tag(:div, class: 'song-cta') do
      if song.download_url
        link_to "Download", song.download_url
      else
        link_to "Subscribe to artists updates",
                artist_updates_path(song.artist)
      end
    end
  end
end

view์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

app/views/songs/show.html.erb
...
<%= song_cta_link(@song) %>
...

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

Rails ์‚ฌ์šฉ์ž ํ—ฌํผ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•˜์ง€ ์•Š๋Š” ๋‹ค๋ฉด draper gem์„ ์‚ฌ์šฉํ•ด View ModelํŒจํ„ด์„ ์‚ฌ์šฉํ•ด๋ณผ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์ž์‹ ๋งŒ์˜ View Model ํŒจํ„ด์„ ์ง์ ‘ ๋งŒ๋“ค์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ทธ๋ ‡๊ฒŒ ๋ณต์žกํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์›น์•ฑ์„ ๋ง‰ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ํ—ฌํผ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฒœ์ฒœํžˆ ์‹œ์ž‘ํ•˜๊ณ  ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‹ค๋ฅธ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

DRY up Your Views

Rails๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด์„œ ์ •๋ง ๋งˆ์Œ์— ๋“ค์—ˆ๋˜ ์ ์€ ๋งˆํฌ์—…์„ ์‰ฝ๊ฒŒ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด์˜€์Šต๋‹ˆ๋‹ค. Rails๋Š” ์–ด๋””์—๋‚˜ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ ์กฐ๊ฐ์ธ partials๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์—ฌ๋Ÿฌ ๊ณณ์—์„œ Song์„ ๋žœ๋”๋งํ•˜๊ณ  ์žˆ๊ณ  ์—ฌ๋Ÿฌ ํŒŒ์ผ์— ๋™์ผํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ Song partial์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ๋…ธ๋ž˜๋ฅผ ํ‘œ์‹œํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

# app/views/songs/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @song.title %>
</p>

<p>
  <strong>Description:</strong>
  <%= @song.description %>
</p>

<%= song_cta_link %>

<%= link_to 'Edit', edit_song_path(@song) %> |
<%= link_to 'Back', songs_path %>

ํ•˜์ง€๋งŒ ๋™์ผํ•œ ๋งˆํฌ์—…์œผ๋กœ ๋‹ค๋ฅธ ํŽ˜์ด์ง€์—๋„ ํ‘œ์‹œํ•˜๊ณ  ์‹ถ์„์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด “app/views/songs/_song.html.erb”์™€ ๊ฐ™์ด ์–ธ๋”๋ฐ”(_) ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ ์ƒˆ ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด ๋ฉ๋‹ˆ๋‹ค.

# app/views/songs/_song.html.erb

<p>
  <strong>Title:</strong>
  <%= @song.title %>
</p>

<p>
  <strong>Description:</strong>
  <%= @song.description %>
</p>

<%= song_cta_link(@song) %>

๊ทธ๋ฆฌ๊ณ  ์–ด๋””์—์„œ๋‚˜ ์›ํ•˜๋Š” ๊ณณ์— Song partial์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

...
<%= render "song" %>
...

“_Song” partial์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ์ž๋™์œผ๋กœ ์กฐํšŒํ•œ ํ›„์— ๋žœ๋”๋ง์„ ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ •์˜ ํ—ฌํผ๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์ œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ partial์—์„œ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜์˜ “@song”์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค.

# app/views/songs/_song.html.erb
<p>
  <strong>Title:</strong>
  <%= song.title %>
</p>

<p>
  <strong>Description:</strong>
  <%= song.description %>
</p>

<%= song_cta_link(song) %>

๊ทธ๋Ÿฐ ๋‹ค์Œ song์˜ ๋ณ€์ˆ˜๋ฅผ partial์— ์ „๋‹ฌํ•˜์—ฌ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๊ณ  ๋‹ค๋ฅธ ๊ณณ์— ํฌํ•จ์‹œํ‚ค๊ธฐ์— ์ ํ•ฉํ•˜๋„๋ก ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

...
<%= render "song", song: @song %>
...

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

์ด ํฌ์ŠคํŒ…์€ ์—ฌ๊ธฐ๊นŒ์ง€์ž…๋‹ˆ๋‹ค. ์š”์•ฝํ•˜์ž๋ฉด Rails View ์˜์—ญ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ํŒจํ„ด๊ณผ ์•ˆํ‹ฐํŒจํ„ด์„ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๋ช‡ ๊ฐ€์ง€ ์š”์ ์ž…๋‹ˆ๋‹ค:

  • UI์—์„œ ๋ณต์žกํ•œ ๋กœ์ง ํ”ผํ•˜๊ธฐ(View๊ฐ€ ๋งŽ์€ Powerlifting์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋„๋ก ๋งŒ๋“ค๊ธฐ.)
  • Rails๊ฐ€ View ํ—ฌํผ์™€ ๊ด€๋ จ๋œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์„ธ์š”.
  • ์‚ฌ์šฉ์ž ์ง€์ • ํ—ฌํผ ๋ฐ partial ์ฝ”๋“œ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ์žฌ์‚ฌ์šฉ ํ•˜์„ธ์š”.
  • ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜์— ๋„ˆ๋ฌด ๋งŽ์ด ์˜์กดํ•˜์ง€ ๋งˆ์„ธ์š”.

๋‹ค์Œ ๊ฒŒ์‹œ๋ฌผ์—์„œ๋Š” ์ƒ๋‹นํžˆ ์ง€์ €๋ถ„ํ•ด์งˆ์ˆ˜ ์žˆ๋Š” Rails Controller ํŒจํ„ด๊ณผ ์•ˆํ‹ฐ ํŒจํ„ด์— ๋Œ€ํ•ด ๋‹ค๋ฃฐ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋Œ€ํ•ด์ฃผ์„ธ์š”.

๋‹ค์Œ ์‹œ๊ฐ„๊นŒ์ง€ ํ™”์ดํŒ…!

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

728x90
728x90