Skip to content

Latest commit

ย 

History

History
2271 lines (1624 loc) ยท 106 KB

File metadata and controls

2271 lines (1624 loc) ยท 106 KB

์ œ 13์žฅ ์œ ์ €์˜ Micropost

sample ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฝ”์–ด ๋ถ€๋ถ„์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•ด, ์ง€๊ธˆ๊นŒ์ง€ ์œ ์ €, ์„ธ์…˜, Account ์œ ํšจํ™”, ํŒจ์Šค์›Œ๋“œ ๋ฆฌ์…‹ ๋“ฑ 4๊ฐœ์˜ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•ด ํ•™์Šตํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ ๋“ค ์ค‘, "์œ ์ €" ๋ผ๊ณ  ํ•˜๋Š” ๋ฆฌ์†Œ์Šค๋งŒ์ด Active Record์— ์˜ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์˜ ํ…Œ์ด๋ธ”๊ณผ ์ด์–ด์ ธ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ค€๋น„๋ฅผ ๋งˆ์นœ ์ง€๊ธˆ, ์œ ์ €๊ฐ€ ์งง์€ ๋ฉ”์„ธ์ง€๋ฅผ ํˆฌ๊ณ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๋ฆฌ์†Œ์Šค "Micropost" ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ œ 2์žฅ ์—์„œ ๊ฐ„์ด์ ์ธ micropost ๋“ฑ๋ก form์„ ํ•ธ๋“ค๋งํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ๋Š” 2.3 ์—์„œ ํ•™์Šตํ•œ Micropost ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์ƒ์„ฑํ•˜๊ณ , User ๋ชจ๋ธ๊ณผ has_many , belongs_to ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€๋ จ์ง“๊ธฐ๋ฅผ ํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ Form๊ณผ ํ•ด๋‹น ๋ถ€ํ’ˆ์„ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (13.4์—์„œ ์ด๋ฏธ์ง€์˜ ์—…๋กœ๋“œ๋„ ๊ตฌํ˜„ํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.) ์ œ 14์žฅ์—์„œ๋Š” micropost์˜ ํ”ผ๋“œ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด ์œ ์ €๋ฅผ ํŒ”๋กœ์šฐํ•˜๋Š” ๊ฐœ๋…์„ ๋„์ž…ํ•˜๊ณ , Twitter์˜ ๋ฏธ๋‹ˆํด๋ก  ๋ฒ„์ „์„ ์™„์„ฑ์‹œ์ผœ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

13.1 Micropost Model

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

Git์œผ๋กœ ๋ฒ„์ „๊ด€๋ฆฌ์— ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ๋Š”, ์–ธ์ œ๋‚˜์ฒ˜๋Ÿผ ํ† ํ”ฝ๋ธŒ๋žœ์น˜๋ฅผ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค.

$ git checkout -b user-microposts

13.1.1 ๊ธฐ๋ณธ์ ์ธ ๋ชจ๋ธ

Micropost ๋ชจ๋ธ์€ micropost์˜ ๋‚ด์šฉ์„ ์ €์žฅํ•  content ์†์„ฑ๊ณผ ํŠน์ • ์œ ์ €์™€ micropost๋ฅผ ๊ด€๋ จ์ง“๋Š” user_id ์†์„ฑ์˜ 2๊ฐœ ๋งŒ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ์˜ Micropost์˜ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์œ„ ๋ชจ๋ธ์—์„œ๋Š” micropost์˜ ๋ฉ”์„ธ์ง€ ํ˜•ํƒœ๋ฅผ String์ด ์•„๋‹Œ Text ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด๊ฒƒ์€ ์–ด๋А์ •๋„์˜ ์–‘์˜ ํ…์ŠคํŠธ๋ฅผ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค. String ํ˜•ํƒœ๋กœ๋„ 255๋ฌธ์ž๊นŒ์ง€๋Š” ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, String ํ˜•์œผ๋กœ๋„ 13.1.2์—์„œ ๊ตฌํ˜„ํ•˜๋Š” 140๋ฌธ์ž ์ œํ•œ์„ ์ถฉ์กฑ์‹œํ‚ฌ ์ˆ˜๋Š” ์žˆ์œผ๋‚˜, Text ํ˜•์ด ์ข€ ๋” ๋งŽ์€ ๋‚ด์šฉ์˜ micropost๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 13.3.2 ์—์„œ๋Š” ์ž‘์„ฑ form์—์„œ String ์šฉ ํ…์ŠคํŠธ ํ•„๋“œ๊ฐ€ ์•„๋‹Œ, Text์šฉ ํ…์ŠคํŠธ ์—๋ฆฌ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ณด๋‹ค ๋” ์ž์—ฐ์Šค๋Ÿฌ์šด ์ž‘์„ฑ Form์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ Text ํ˜•์ด ์œ ์—ฐ์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด ๋‚˜์ค‘์— ๊ตญ์ œํ™”๋กœ ์ธํ•œ ๋ฒˆ์—ญ์„ ํ•  ๋•Œ, ์–ธ์–ด๋ณ„๋กœ ์ž‘์„ฑ๋‚ด์šฉ์˜ ๊ธธ์ด๋ฅผ ์กฐ์ ˆํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ Text ํ˜•์„ ์‚ฌ์šฉํ•˜์—ฌ๋„ ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ ํผํฌ๋จผ์Šค์˜ ์ฐจ์ด๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋“ค๋กœ ๋‹จ์ ๋ณด๋‹ค๋Š” ์žฅ์ ์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฒˆ์—๋Š” Text ํ˜•์„ ์ฑ„์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ œ 6์žฅ์—์„œ User ๋ชจ๋ธ์„ ์ƒ์„ฑํ–ˆ๋˜ ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, Rails์˜ generate model ์ปค๋งจ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Micropost ๋ชจ๋ธ์„ ์ƒ์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

$ rails generate model Micropost content:text user:references

์œ„ ์ปค๋งจ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์€ ๋ชจ๋ธ ํŒŒ์ผ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰ 6.1.2 ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ApplicationRecord ๋ฅผ ๊ณ„์Šนํ•œ ๋ชจ๋ธ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋‹จ, ์ด๋ฒˆ์— ์ƒ์„ฑ๋œ ๋ชจ๋ธ ๋‚ด๋ถ€์—๋Š” ์œ ์ €์™€ 1๋Œ€1์˜ ๊ด€๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” belongs_to ์˜ ์ฝ”๋“œ๋„ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์•„๊นŒ ์ „ ์ปค๋งจ๋“œ๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ์˜ user:reference ๋ผ๊ณ  ํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํฌํ•จํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์„ค๋ช…์— ๋Œ€ํ•ด์„œ๋Š” 13.1.3์—์„œ ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

# app/models/micropost.rb

class Micropost < ApplicationRecord
   belongs_to :user
end

์ด์ „ 6์žฅ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— users ํ…Œ์ด๋ธ”์„ ์ž‘์„ฑํ•  ๋•Œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ƒ์„ฑํ•œ ๊ฒƒ ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์ด generate ์ปค๋งจ๋“œ๋Š” microposts ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

User๋ชจ๋ธ๊ณผ์˜ ์ œ์ผ ํฐ ์ฐจ์ด์ ์€ references ํ˜•์„ ์ด์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์  ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ์ด์šฉํ•˜๋ฉด, ์ž๋™์ ์œผ๋กœ ์ธ๋ฑ์Šค์™€ ์™ธ๋ถ€ Key ์ฐธ์กฐ๋ฅผ ํ•˜๋Š” user_id ์ปฌ๋Ÿผ์ด ์ƒ์„ฑ๋˜์–ด User์™€ Microepost์„ ๊ด€๋ จ์ง“๋Š” ๋ฐ‘์ž‘์—…์„ ์ž๋™์œผ๋กœ ํ•ด์ค๋‹ˆ๋‹ค. User ๋ชจ๋ธ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, Micropost ๋ชจ๋ธ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์—๋„ t.timestamp ๋ผ๊ณ  ํ•˜๋Š” ํ–‰ (๋งค์ง์ปฌ๋Ÿผ)์ด ์ž๋™์ ์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ 6.1.1 ์—์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด created_at ๊ณผ updated_at ์ด๋ผ๊ณ ํ•˜๋Š” ์ปฌ๋Ÿผ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ created_at ์ปฌ๋Ÿผ์€ 13.1.4์˜ ๊ตฌํ˜„์„ ์ง„ํ–‰ํ•ด๋‚˜๊ฐ€๋ฉด์„œ ํ•„์š”ํ•œ ์ปฌ๋Ÿผ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

# db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration[5.0]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    add_index :microposts, [:user_id, :created_at]
  end
end

์—ฌ๊ธฐ์„œ ์œ„ ์ฝ”๋“œ์—์„œ user_id ์™€ created_at ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์— ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ ์ธํ•˜์—ฌ user_id ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ๋งˆ์ดํฌ๋กœ ํฌ์ŠคํŠธ๋ฅผ ์ƒ์„ฑ์‹œ๊ฐ„์˜ ์—ญ์ˆœ์œผ๋กœ ์–ป๊ธฐ ์‰ฌ์›Œ์ง€๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

add_index :micropost, [:user_id, :created_at]

๋˜ํ•œ user_id ์™€ created_id ๋‘˜ ๋‹ค ํ•˜๋‚˜์˜ ๋ฐฐ์—ด๋กœ ๋ฌถ๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ Active Record๋Š” ์–‘์ชฝ์˜ key๋ฅผ ๋™์‹œ์— ๋‹ค๋ฃจ๋Š” ๋ณตํ•ฉ key index๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. What is a multiple key index?

๊ทธ๋Ÿฌ๋ฉด ์œ„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ฐฑ์‹ ํ•ด๋ด…์‹œ๋‹ค.

$ rails db:migrate

์—ฐ์Šต
  1. Rails ์ฝ˜์†”์—์„œ Micropost.new ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ณ€์ˆ˜ micropost ์— ๋Œ€์ž…ํ•ด๋ณด์„ธ์š”. ๊ทธ ๋‹ค์Œ, user_id ์— ์ œ์ผ ์ฒซ ๋ฒˆ์งธ ์œ ์ € id๋ฅผ, content ์— "Lorem ipsum" ์„ ๊ฐ๊ฐ ๋Œ€์ž…ํ•ด๋ณด์„ธ์š”. ์ด ์‹œ์ ์—์„œ micropost ์˜ค๋ธŒ์ ํŠธ์˜ ๋งค์ง์ปฌ๋Ÿผ (created_at ๊ณผ updated_at) ์—๋Š” ๋ฌด์—‡์ด ๋“ค์–ด์žˆ์Šต๋‹ˆ๊นŒ?

  2. ์•ž์„œ ๋งŒ๋“  ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ micropost.user ์‹คํ–‰์‹œ์ผœ๋ด…์‹œ๋‹ค. ์–ด๋– ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์ถœ๋ ฅ๋˜๋‚˜์š”? ๋˜ํ•œ micropost.user.name ์„ ์‹คํ–‰ํ–ˆ์„ ๊ฒฝ์šฐ์—๋Š” ์–ด๋– ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฉ๋‹ˆ๊นŒ?

  3. ์•ž์„œ ๋งŒ๋“  micropost ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด๋ด…์‹œ๋‹ค. ์ด ์‹œ์ ์—์„œ ํ•œ ๋ฒˆ ๋” ๋งค์ง์ปฌ๋Ÿผ์˜ ๋‚ด์šฉ์„ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. ์ด๋ฒˆ์—๋Š” ์–ด๋– ํ•œ ๊ฐ’์ด ๋“ค์–ด์žˆ๋‚˜์š”?

13.1.2 Micropost ์˜ Validation

๊ธฐ๋ณธ์ ์ธ ๋ชจ๋ธ์€ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ๋Š” ์š”๊ตฌ๋˜๋Š” ์ œํ•œ์‚ฌํ•ญ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ, Validation์„ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค. Micropost ๋ชจ๋ธ์„ ์ƒ์„ฑํ–ˆ์„ ๋•Œ, micropost๋Š” ์ž‘์„ฑํ•œ ์œ ์ €์˜ id(user_id) ๋ฅผ ๋ฐ์ดํ„ฐ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๊ฒŒ๋” ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€์Šต์ ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ Active Record์™€์˜ ๊ด€๊ณ„๋งบ๊ธฐ ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์šฐ์„  Micropost ๋ชจ๋ธ ๋งŒ์„ (ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ๋กœ) ๋™์ž‘ํ•ด๋ณด๋„๋ก ํ•ฉ์‹œ๋‹ค.

Micropost ์˜ ์ดˆ๊ธฐ ํ…Œ์ŠคํŠธ๋Š” User๋ชจ๋ธ์˜ ์ดˆ๊ธฐ ํ…Œ์ŠคํŠธ์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ์šฐ์„ ์€ setup ๋‹จ๊ณ„์—์„œ, fixture์˜ sample ์œ ์ €์™€ ๊ด€๋ จ์žˆ๋Š” ์ƒˆ๋กœ์šด micropost๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ ์ž‘์„ฑํ•œ ๋งˆ์ดํฌ๋กœํฌ์ŠคํŠธ๊ฐ€ ์œ ํšจํ•œ์ง€ ์•„๋‹Œ์ง€๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ชจ๋“  micropost๋Š” ์œ ์ €์˜ id๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ๋งŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— user_id ์˜ ์กด์žฌ์„ฑ์˜ validation์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ์œผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

# test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase

  def setup
    @user = users(:michael)
    # ์ด ์ฝ”๋“œ๋Š” ๊ด€์Šต์ ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
    @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
  end

  test "should be valid" do
    assert @micropost.valid?
  end

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
end

setup ๋ฉ”์†Œ๋“œ์˜ ๋‚ด๋ถ€์˜ ์ฝ”๋ฉ˜ํŠธ์ฒ˜๋Ÿผ, micropost๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค๋งŒ ๊ด€์Šต์ ์œผ๋กœ๋Š” ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. (13.1.3์—์„œ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.)

์›๋ž˜๋ถ€ํ„ฐ ์กด์žฌํ•˜๋Š” User ๋ชจ๋ธ์˜ ํ…Œ์ŠคํŠธ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์œ„ ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ •์ƒ์ ์ธ ์ƒํƒœ์ธ์ง€ ์•„๋‹Œ์ง€๋ฅผ ํ…Œ์ŠคํŠธ (Sanity Test) ๋ฅผ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ์—์„œ๋Š” user_id ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€๋ฅผ (nil ์€ ์•„๋‹Œ์ง€) ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด, ์กด์žฌ์„ฑ์˜ validation์„ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค.

# app/models/micropost.rb
class Micropost < ActiveRecord::Base
  belongs_to :user
  validates :user_id, presence: true
end

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

์ด์ƒ์œผ๋กœ ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

$ rails test:models

๋‹ค์Œ์œผ๋กœ micropost์˜ content ์†์„ฑ์— ๋Œ€ํ•œ validation์„ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค. (2.3.2์—์„œ ์†Œ๊ฐœํ•ด๋“œ๋ฆฐ ๋ฐฉ๋ฒ•๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.) user_id ์†์„ฑ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, content ์†์„ฑ๋„ ์กด์žฌํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ micropost๊ฐ€ 140๋ฌธ์ž๋ณด๋‹ค ๊ธด ๋ฌธ์ž๊ฐ€ ๋˜์ง€ ์•Š๋„๋ก ์ œํ•œ์„ ๊ฑธ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (์ด๊ฒƒ์ด micropost๊ฐ€ micro๋ผ๊ณ  ์ด๋ฆ„์ด ๋ถ™์€ ์ด์œ ์ž…๋‹ˆ๋‹ค.)

6.2 ์—์„œ User๋ชจ๋ธ์— Validation์„ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์—์„œ micropost ๋ชจ๋ธ์˜ validation์„ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” User๋ชจ๋ธ ๋•Œ์™€ ๋น„์Šทํ•œ validation์„ ์ถ”๊ฐ€ํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

# test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase

  def setup
    @user = users(:michael)
    @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
  end

  test "should be valid" do
    assert @micropost.valid?
  end

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
#์ถ”๊ฐ€
  test "content should be present" do
    @micropost.content = "   "
    assert_not @micropost.valid?
  end

  test "content should be at most 140 characters" do
    @micropost.content = "a" * 141
    assert_not @micropost.valid?
  end
#์ถ”๊ฐ€  
end

6.2 ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์œ„ ์ฝ”๋“œ๋Š” micropost์˜ ๊ธธ์ด๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด ๋ฌธ์ž์—ด์˜ ๊ณ„์‚ฐ์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

$ rails console
>> "a" * 10
=> "aaaaaaaaaa"
>> "a" * 141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

์ด๊ฒƒ์— ๋Œ€์‘ํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์˜ ๊ตฌํ˜„์€, User์˜ name ์šฉ validation๋•Œ์™€ (6์žฅ) ๋˜‘๊ฐ™์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๊ฒฐ๊ณผ์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

์ด ์‹œ์ ์—์„œ, ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋Š” GREEN์ด ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

์—ฐ์Šต
  1. Rails ์ฝ˜์†”์„ ์‹คํ–‰์‹œํ‚ค๊ณ , user_id ์™€ content ๊ฐ€ ๋น„์–ด์žˆ๋Š” micropost ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. ์ด ์˜ค๋ธŒ์ ํŠธ์— ๋Œ€ํ•ด valid? ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด ์‹คํŒจํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ด๋ณด์„ธ์š”. ๋˜ํ•œ ์ƒ์„ฑ๋œ ์—๋Ÿฌ๋ฉ”์„ธ์ง€์— ์–ด๋– ํ•œ ๋‚ด์šฉ์ด ๊ธฐ์žฌ๋˜์–ด ์žˆ๋‚˜์š”?
  2. ์ฝ˜์†”์„ ์‹คํ–‰์‹œํ‚ค๊ณ  ์ด๋ฒˆ์—๋Š” user_id๊ฐ€ ๋น„์–ด์žˆ๊ณ , content ๊ฐ€ 141๋ฌธ์ž ์ด์ƒ์˜ micropost ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ด๋ณด์„ธ์š”. ์ด ์˜ค๋ธŒ์ ํŠธ์— ๋Œ€ํ•ด์„œ valid? ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด ์‹คํŒจํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ด๋ณด์„ธ์š”. ๋˜ํ•œ ์ƒ์„ฑ๋œ ์—๋Ÿฌ๋ฉ”์„ธ์ง€์—๋Š” ์–ด๋– ํ•œ ๋‚ด์šฉ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์„ธ์š”.

13.1.3 User/Micropost์˜ ๊ด€๊ณ„๋งบ๊ธฐ

Web ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์šฉ์˜ ๋ฐ์ดํ„ฐ๋ชจ๋ธ์„ ๊ตฌ์ถ•ํ•  ๋•Œ๋Š”, ๊ฐ๊ฐ์˜ ๋ชจ๋ธ ๊ฐ„์˜ ๊ด€๊ณ„์— ๋Œ€ํ•ด์„œ ์ถฉ๋ถ„ํžˆ ์ƒ๊ฐํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ฒฝ์šฐ์—๋Š” 2.3.3 ์—์„œ๋„ ํ•ด๋ณธ ์ ์ด ์žˆ๋“ฏ, ๊ฐ๊ฐ์˜ micropost๋Š” 1๋ช…์˜ ์œ ์ €์™€ ๊ด€๊ณ„๋ฅผ ๋งบ๊ฒŒ๋˜๊ณ , ๊ฐ๊ฐ์˜ ์œ ์ €๋Š” (์ž ์žฌ์ ์œผ๋กœ) ์—ฌ๋Ÿฌ๊ฐœ์˜ micropost์™€ ๊ด€๊ณ„ ๋งบ๊ฒŒ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ด€๊ณ„๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ด€๊ณ„๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์ผํ™˜์œผ๋กœ, micropost ๋ชจ๋ธ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ๊ฒŒ๋‹ค๊ฐ€ User๋ชจ๋ธ์— ๋ช‡๊ฐ€์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์ด๋ฒˆ ์„น์…˜์—์„œ ์ •์˜ํ•˜๋Š” belongs_to / has_many ๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์œ„ ๊ทธ๋ฆผ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ Rails์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์†์„ฑ๋“ค์€ ์•„๋ž˜์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ์•„๋‹Œ

Micropost.create
Micropost.create!
Micropost.new

์•„๋ž˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ์˜ํ•ด์ฃผ์„ธ์š”.

user.microposts.create
user.microposts.create!
user.microposts.build

์ด ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๊ด€๊ณ„๋ฅผ ๋งบ๊ณ  ์žˆ๋Š” ์œ ์ €๋ฅผ ํ†ตํ•ด micropost๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ด€์Šต์ ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.) ์‹ ๊ทœ micropost๊ฐ€ ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๊ฒฝ์šฐ, user_id ๋Š” ์ž๋™์ ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด, ์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์€

@user = users(:michael)  
# ์•„๋ž˜ ์ฝ”๋“œ๋Š” ๊ด€์Šต์ ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)

์œ„์™€ ๊ฐ™์€ ์ž‘์„ฑ๋ฐฉ๋ฒ•์ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋‹ค์‹œ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")

(new ๋ฉ”์†Œ๋“œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, build ๋ฉ”์†Œ๋“œ๋Š” ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฆฌํ„ดํ•˜๋Š”๋ฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” ๋ฐ˜์˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.) ํ•œ ๋ฒˆ์— ์˜ฌ๋ฐ”๋ฅธ ๊ด€๊ณ„๋ฅผ ์ •์˜ํ•ด ๋†“๋Š”๋‹ค๋ฉด, @micropost ๋ณ€์ˆ˜์˜ user_id ๋Š” ๊ด€๊ณ„๋ฅผ ๋งบ๊ณ ์žˆ๋Š” ์œ ์ €์˜ id๊ฐ€ ์ž๋™์ ์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

๋ฉ”์†Œ๋“œ ์šฉ๋„
micropost.user Micropost์™€ ๊ด€๊ณ„๋ฅผ ๋งบ๊ณ  ์žˆ๋Š” User ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.
user.microposts User์˜ Micropost ์ง‘ํ•ฉ์„ ๋ฆฌํ„ดํ•œ๋‹ค.
user.microposts.create(arg) user ๊ด€๊ณ„๋งบ์€ micropost๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
user.microposts.create!(arg) user์™€ ๊ด€๊ณ„๋งบ์€ micropost๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. (์‹คํŒจ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ)
user.microposts.build(arg) user ์™€ ๊ด€๊ณ„๋งบ์€ ์ƒˆ๋กœ์šด Micropost ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.
user.microposts.find_by(id: 1) user์™€ ๊ด€๊ณ„๋ฅผ ๋งบ๊ณ  ์žˆ๊ณ  id๊ฐ€ 1์ธ micropost๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.

@user.micropost.build ์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด, User๋ชจ๋ธ๊ณผ Micropost ๋ชจ๋ธ์„ ๊ฐ๊ฐ ์ˆ˜์ •ํ•˜๊ณ , ๊ด€๊ณ„๋ฅผ ๋งบ์„ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Micropost ๋ชจ๋ธ์—์„œ๋Š” belongs_to :user ๋ผ๊ณ  ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค๋งŒ, ์ด๊ฒƒ์€ ์•„๋ž˜ ์ฝ”๋“œ์˜ migration์— ์˜ํ•ด ์ž๋™์ ์œผ๋กœ ์ƒ์„ฑ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ํ•œ ํŽธ, User๋ชจ๋ธ์—์„œ๋Š” has_many :microposts ๋ผ๊ณ  ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๋Š” ์ž๋™์ ์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ด์•ผํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

# app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user # ์ˆ˜์ •
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
# app/models/user.rb
class User < ApplicationRecord
  has_many :microposts
  .
  .
  .
end

์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ด€๊ณ„๊ฐ€ ๋งบ์–ด์กŒ๋‹ค๋ฉด, setup ๋ฉ”์†Œ๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ๊ด€์Šต์ ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ micropost๋ฅผ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค.

# test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase

  def setup
    @user = users(:michael)
    @micropost = @user.microposts.build(content: "Lorem ipsum") #Update
  end

  test "should be valid" do
    assert @micropost.valid?
  end

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
  .
  .
  .
end

๋ฌผ๋ก  ์„ฌ์„ธํ•œ ๋ฆฌํŒฉํ† ๋ง์ด๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ๋Š” ์•„์ง ํ†ต๊ณผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

์—ฐ์Šต
  1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์žˆ๋Š” ์ œ์ผ ์ฒซ ๋ฒˆ์งธ ์œ ์ €๋ฅผ ๋ณ€์ˆ˜ user ์— ๋Œ€์ž…ํ•ด์ฃผ์„ธ์š”. ํ•ด๋‹น user ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ micropost = user.microposts.create(content: "Lorem ipsum") ์„ ์‹คํ–‰ํ•˜๋ฉด ์–ด๋– ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?
  2. ์•ž์„œ ์—ฐ์Šต๋ฌธ์ œ์—์„œ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์— ์ƒˆ๋กœ์šด micropost๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. user.microposts.find(micropost.id) ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ, ์ •๋ง๋กœ ์ถ”๊ฐ€๋˜์—ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. ๋˜ํ•œ ์•ž์„œ ์‹คํ–‰ํ•œ micropost.id ์˜ ๋ถ€๋ถ„์„ micropost ๋กœ ๋ฐ”๊พธ๋ฉด ์–ด๋– ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์ƒ๊ธฐ๋‚˜์š”?
  3. user == micropost.user ๋ฅผ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๋Š” ์–ด๋–ป๊ฒŒ ๋˜๋‚˜์š”? ๋˜ํ•œ user.microposts.first == micropost ๋ฅผ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๋Š” ์–ด๋–ป๊ฒŒ ๋˜๋‚˜์š”? ๊ฐ๊ฐ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

13.1.4 Micropost๋ฅผ ๊ฐœ์„ ํ•ด๋ณด์ž

์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” User์™€ Micropost์˜ ๊ด€๊ณ„๋งบ๊ธฐ๋ฅผ ๊ฐœ์„ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ์œ ์ €์˜ micropost๋ฅผ ํŠน์ • ์ˆœ์„œ๋กœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค๊ฑฐ๋‚˜, micropost๋ฅผ ์œ ์ €์—๊ฒŒ ์˜์กด์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ  ์œ ์ €๊ฐ€ ์‚ญ์ œ๋˜๋ฉด micropost๋„ ์ž๋™์ ์œผ๋กœ ์‚ญ์ œ๋˜๋„๋ก ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Default Scope

user.microposts ๋ฉ”์†Œ๋“œ๋Š” ๊ธฐ๋ณธ์œผ๋กœ ์กฐํšŒ์ˆœ์„œ์— ๋Œ€ํ•ด์„œ ์•„๋ฌด๊ฒƒ๋„ ๋ณด์ฆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค๋งŒ ๋ธ”๋กœ๊ทธ๋‚˜ Twitter์˜ ๊ด€์Šต์— ๋”ฐ๋ผ ์ž‘์„ฑ์‹œ๊ฐ„์˜ ์—ญ์ˆœ, ์ฆ‰ ์ œ์ผ ์ตœ์‹ ์˜ micropost๋ฅผ ์ œ์ผ ์ฒ˜์Œ์œผ๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด๋ด…์‹œ๋‹ค. ์ด๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” default scope ๋ผ๊ณ  ํ•˜๋Š” ํ…Œํฌ๋‹‰์„ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์˜ ํ…Œ์ŠคํŠธ๋Š” ๊ฒ‰์—์„œ ๋ณด์ด๊ธฐ๋งŒํ•˜๋Š” ๊ฒฐ๊ณผ๋กœ ์„ฑ๊ณตํ–ˆ๋‹ค๊ณ  ์ž๋งŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. "์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์˜ ๊ตฌํ˜„์€ ์‚ฌ์‹ค ์ž˜๋ชป๋˜์–ด์žˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•ด๋ฒ„๋ฆฌ๋Š”" ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ, ์—ฌ๊ธฐ์„œ๋Š” ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ๋กœ ์ง„ํ–‰ํ•ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ์šฐ์„  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์˜ ์ œ์ผ ์ฒซ ๋ฒˆ์งธ micropost๊ฐ€ fixture ๋‚ด์˜ micropost(most_recent) ์™€ ๊ฐ™์€ ๊ฒƒ์„ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด๋ด…์‹œ๋‹ค.

# test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase
  .
  .
  .
  test "order should be most recent first" do
    assert_equal microposts(:most_recent), Micropost.first
  end
end

์œ„ ์ฝ”๋“œ๋Š” micropost์šฉ์˜ fixture ํŒŒ์ผ๋กœ๋ถ€ํ„ฐ sample ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ fixture ํŒŒ์ผ๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

# test/fixtures/microposts.yml
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>

tau_manifesto:
  content: "Check out the @tauday site by @mhartl: http://tauday.com"
  created_at: <%= 3.years.ago %>

cat_video:
  content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
  created_at: <%= 2.hours.ago %>

most_recent:
  content: "Writing a short test"
  created_at: <%= Time.zone.now %>

์—ฌ๊ธฐ์„œ Embeded Ruby๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ created_at ์ปฌ๋Ÿผ์˜ ๊ฐ’์„ ์…‹ํŒ…ํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด ์ปฌ๋Ÿผ์€ Rails์— ์˜ํ•ด ์ž๋™์ ์œผ๋กœ ๊ฐฑ์‹ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ˆ˜๋™์œผ๋กœ ๊ฐฑ์‹ ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค๋งŒ, fixture ํŒŒ์ผ ์•ˆ์—์„œ๋Š” ๊ฐฑ์‹ ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜๋„์ ์œผ๋กœ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํŒŒ์ผ ๋‚ด์˜ ์ œ์ผ ์•„๋ž˜ sample ๋ฐ์ดํ„ฐ๋Š” ์ œ์ผ ๋‚˜์ค‘์— ์ƒ์„ฑ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์‹ ์˜ post ๊ฐ€ ๋˜๋„๋ก ์ˆ˜์ •ํ•˜๋Š” ๋А๋‚Œ์ž…๋‹ˆ๋‹ค. ๋งŽ์€ ์‹œ์Šคํ…œ์—์„œ fixture๋Š” ์œ„์—์„œ๋ถ€ํ„ฐ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ํŒŒ์ผ ๋‚ด์˜ ์ œ์ผ ์•„๋ž˜์— ์žˆ๋Š” sample ๋ฐ์ดํ„ฐ๊ฐ€ ์ œ์ผ ๋งˆ์ง€๋ง‰์— ์ƒ์„ฑ๋˜๋‚˜, ์ด๋Ÿฌํ•œ ๋™์ž‘์— ์˜์กดํ•œ ํ…Œ์ŠคํŠธ๋Š” ์ž‘์„ฑํ•  ํ•„์š”๋Š” ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์‹คํ–‰์ˆœ์„œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ์œ„ ๋‘๊ฐœ์˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ผ๋‹จ ์‹คํŒจํ•˜๊ฒŒ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test test/models/micropost_test.rb

๋‹ค์Œ์œผ๋กœ Rails ์˜ default_scope ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณต์‹œ์ผœ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์†Œ๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์—ˆ์„ ๋•Œ, ๊ธฐ๋ณธ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. ํŠน์ • ์ˆœ์„œ๋กœ ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” default_scope ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์— order ๋ฅผ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, created_at ์ปฌ๋Ÿผ์˜ ์ˆœ์„œ๋กœ ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•ฉ๋‹ˆ๋‹ค.

order(:created_at)

๋‹จ, ์•„์‰ฝ๊ฒŒ๋„ ๊ธฐ๋ณธ ์ˆœ์„œ๊ฐ€ ์˜ค๋ฆ„์ฐจ์ˆœ (ascending) ์œผ๋กœ ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋Œ€๋กœ๋Š” ์ž‘์€ ์ˆ˜๋ถ€ํ„ฐ ํฐ ์ˆ˜์˜ ์ˆœ์œผ๋กœ ์ •๋ ฌ๋˜์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. (์ œ์ผ ์˜ค๋ž˜๋œ ๋‚ด์šฉ์ด ์ œ์ผ ์ฒ˜์Œ์— ํ‘œ์‹œ๋˜์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.) ์ˆœ์„œ๋ฅผ ๋ฐ”๊พธ๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์กฐ๊ธˆ ๋‚ฎ์€ ๋ ˆ๋ฒจ์˜ ๊ธฐ์ˆ ์ด๊ธด ํ•ฉ๋‹ˆ๋‹ค๋งŒ ๋‹ค์Œ๊ณผ ๊ฐ™์ด SQL์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธธ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

order(created_at: :desc)

์ด ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Micropost ๋ชจ๋ธ์„ ์ˆ˜์ •ํ•œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) } #์ถ”๊ฐ€
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ์ƒˆ๋กญ๊ฒŒ ๋žŒ๋‹ค์‹ (Stabby lambda) ๋ผ๊ณ  ํ•˜๋Š” ๋ฐฉ์‹์ด ์“ฐ์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ Proc ์ด๋‚˜ lambda (ํ˜น์€ ๋ฌด๋ช…ํ•จ์ˆ˜) ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. -> ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋žŒ๋‹ค์‹์€, ๋ธ”๋ก(4.3.2) ์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ•˜๋ฉฐ Proc ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ค๋ธŒ์ ํŠธ๋Š” call ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ์„ ๋•Œ, ๋ธ”๋ก ๋‚ด์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฌธ๋ฒ•์€ ์ฝ˜์†”์—์„œ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

>> -> { puts "foo" }
=> #<Proc:0x007fab938d0108@(irb):1 (lambda)>
>> -> { puts "foo" }.call
foo
=> nil

(Proc์€ Ruby์—์„œ๋„ ์กฐ๊ธˆ ๋‚œ์ด๋„๊ฐ€ ๋†’์€ ์ฃผ์ œ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ง€๊ธˆ ๋‹น์žฅ ์ดํ•ดํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.)

์œ„ ๋ชจ๋ธ์˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์œผ๋กœ, ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋Š” ์„ฑ๊ณตํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

Dependent: destory

์ˆœ์„œ์— ๋Œ€ํ•ด์„œ๋Š” ์ผ๋‹จ ์—ฌ๊ธฐ์„œ ๋งˆ์น˜๋„๋กํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” micropost์— ์ œ 2์˜ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 10.4 ์—์„œ ์ž‘์„ฑํ–ˆ๋“ฏ์ด, ์‚ฌ์ดํŠธ ๊ด€๋ฆฌ์ž๋Š” ์œ ์ €๋ฅผ ํŒŒ๊ธฐ ํ•  ๊ถŒํ•œ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์œ ์ €๊ฐ€ ํŒŒ๊ธฐ๋˜์—ˆ์„ ๊ฒฝ์šฐ, ์œ ์ €์˜ micropost๋„ ๊ฐ™์ด ํŒŒ๊ธฐ๋˜์–ด์•ผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋™์ž‘์€ has_many ๋ฉ”์†Œ๋“œ์— ์˜ต์…˜์„ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy #Update
  .
  .
  .
end

dependent: :destory ๋ผ๊ณ  ํ•˜๋Š” ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด, ์œ ์ €๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์„ ๋•Œ ํ•ด๋‹น ์œ ์ €์™€ ๊ด€๊ณ„๋ฅผ ๋งบ์€ (ํ•ด๋‹น ์œ ์ €๊ฐ€ ์ž‘์„ฑํ•œ) micropost๋„ ๊ฐ™์ด ์‚ญ์ œ๋˜๊ฒŒ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๊ด€๋ฆฌ์ž๊ฐ€ ์‹œ์Šคํ…œ์œผ๋กœ๋ถ€ํ„ฐ ์œ ์ €๋ฅผ ์‚ญ์ œํ–ˆ์„ ๋•Œ, ์ž‘์„ฑ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์€ micropost๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋‚จ๊ฒจ์ง€๋Š” ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ์œ„ ์ฝ”๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€๋ฅผ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด User๋ชจ๋ธ์„ ๊ฒ€์ฆํ•ด๋ด…์‹œ๋‹ค. ์ด ํ…Œ์ŠคํŠธ์—์„œ๋Š” (id๋ฅผ ๊ด€๊ณ„์ง“๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด) ์œ ์ €๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ๊ณผ, ํ•ด๋‹น ์œ ์ €์™€ ๊ด€๊ณ„๋ฅผ ๋งบ๊ณ  ์žˆ๋Š” micropost๋ฅผ ์ƒ์„ฑํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ํ›„, ์œ ์ €๋ฅผ ์‚ญ์ œํ•˜์—ฌ micropost์˜ ๊ฐฏ์ˆ˜๊ฐ€ 1๊ฐœ ์ค„์–ด๋“  ๊ฒƒ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด์ „์— ์ž‘์„ฑํ•œ [delete] ๋งํฌ์˜ ํ†ตํ•ฉํ…Œ์ŠคํŠธ์™€ ๋น„๊ตํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ดํ•ดํ•˜๊ธฐ์— ๋„์›€์ด ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

# test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
  test "associated microposts should be destroyed" do
    @user.save
    @user.microposts.create!(content: "Lorem ipsum")
    assert_difference 'Micropost.count', -1 do
      @user.destroy
    end
  end
end

์œ„ ์ฝ”๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•œ๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

์—ฐ์Šต
  1. Micropost.first.created_at ์˜ ์‹คํ–‰๊ฒฐ๊ณผ์™€ Micropost.last.created_at ์˜ ์‹คํ–‰๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•ด๋ด…์‹œ๋‹ค.
  2. Micropost.first ๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ์ƒˆ์„ฑ๋˜๋Š” SQL์€ ์–ด๋– ํ•œ ํ˜•ํƒœ์ธ๊ฐ€์š”? ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Micropost.last ์˜ ๊ฒฝ์šฐ๋Š” ์–ด๋–ค ํ˜•ํƒœ์ธ๊ฐ€์š”? Hint : ๊ฐ๊ฐ์˜ ์ฝ”๋“œ๋ฅผ ์ฝ˜์†”์—์„œ ์‹คํ–‰ํ•ด ๋ณด์•˜์„ ๋•Œ ํ‘œ์‹œ๋˜๋Š” ๋ฌธ์ž์—ด์ด SQL๋ฌธ์ด ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.
  3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์˜ ์ œ์ผ ์ฒซ ๋ฒˆ์งธ ์œ ์ €๋ฅผ ๋ณ€์ˆ˜ user ์— ๋Œ€์ž…ํ•˜์„ธ์š”. ํ•ด๋‹น user ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ œ์ผ ์ฒ˜์Œ์œผ๋กœ ์ž‘์„ฑํ•œ micropost์˜ id๋Š” ๋ช‡์ธ๊ฐ€์š”? ๋‹ค์Œ์œผ๋กœ destory ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น user ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์‚ญ์ œํ•ด๋ณด์„ธ์š”. ์‚ญ์ œํ•˜๋ฉด, ํ•ด๋‹น user ์™€ ๊ด€๊ณ„๋งบ๊ณ  ์žˆ๋˜ micropost๋„ ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์„, Micropost.find ๋กœ ํ™•์ธํ•ด๋ณด์„ธ์š”.

13.2 Micropost๋ฅผ ํ‘œ์‹œํ•ด๋ณด์ž

Web ๊ฒฝ์œ ๋กœ Micropost๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํ˜„ ์‹œ์ ์—์„œ๋Š” ์—†์Šต๋‹ˆ๋‹ค๋งŒ, (13.3.2์—์„œ๋ถ€ํ„ฐ ์ž‘์„ฑํ•ด๋ด…๋‹ˆ๋‹ค.) Micropost๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ๊ณผ, ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” Twitter์™€ ๊ฐ™์€ ๋…๋ฆฝ์ ์ธ micropost์˜ index ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ณ , ์•„๋ž˜์™€ ๊ฐ™์ด ์œ ์ €์˜ show ํŽ˜์ด์ง€์— ์ง์ ‘ micropost๋ฅผ ํ‘œ์‹œ์‹œ์ผœ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์œ ์ €์˜ ํ”„๋กœํ•„์— micropost๋ฅผ ํ‘œ์‹œ์‹œํ‚ค๊ธฐ ์œ„ํ•ด, ๋งค์šฐ ๊ฐ„๋‹จํ•œ ERB ํ…œํ”Œ๋ ›์„ ์ฒ˜์Œ์œผ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ๋‹ค์Œ์œผ๋กœ๋Š” 10.3.2 ์˜ sample ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ํƒœ์Šคํฌ์— micropost์˜ sample์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ™”๋ฉด์— sample ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

13.2.1 Micropost์˜ ํ‘œ์‹œ

์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” ์œ ์ €์˜ ํ”„๋กœํ•„ ํ™”๋ฉด (show.html.erb) ์—์„œ ํ•ด๋‹น ์œ ์ €์˜ micropost๋ฅผ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜, ์ง€๊ธˆ๊นŒ์ง€ ์ž‘์„ฑํ•œ micropost์˜ ์ด ๊ฐฏ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด๋„, ์ด๋ฒˆ์— ํ•„์š”ํ•œ ์•„์ด๋””์–ด์˜ ๋Œ€๋ถ€๋ถ„์€ 10.3 ์—์„œ ๊ตฌํ˜„ํ•œ, ์œ ์ €๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ถ€๋ถ„๊ณผ ๋‹ฎ์•„ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฐ์Šต์—์„œ ์ด๋ฏธ micropost๋ฅผ ๋ช‡ ๊ฐœ ์ •๋„ ์ž‘์„ฑํ•œ ๊ฒฝ์šฐ์—๋Š”, ํ•œ ๋ฒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋ฆฌ์…‹ํ•˜๊ณ  sample ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ์ƒ์„ฑํ•ด๋†“์œผ์„ธ์š”.

$ rails db:migrate:reset
$ rails db:seed

์šฐ์„ , Micropost์˜ ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ทฐ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค. ๋˜ํ•œ ์ด๋ฒˆ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ทฐ ๋ฟ๋งŒ์ด๋ฉฐ, Micropost ์ปจํŠธ๋กค๋Ÿฌ๋Š” 13.3๋ถ€ํ„ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

$ rails generate controller Microposts

์ด๋ฒˆ ๋ชฉ์ ์€, ์œ ์ €๋ณ„๋กœ ๋ชจ๋“  Micropost๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. 10.3.5 ์—์„œ ๋ณธ ์ฝ”๋“œ๋Š”,

<ul class="users">
  <%= render @users %>
</ul>

_user.html.erb ํŒŒ์…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์ ์œผ๋กœ @users ๋ณ€์ˆ˜ ๋‚ด์˜ ๊ฐ๊ฐ์˜ ์œ ์ €๋ฅผ ์ถœ๋ ฅํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์„ ์ฐธ๊ณ ํ•˜์—ฌ _micropost.html.erb ํŒŒ์…œ์„ ์‚ฌ์šฉํ•˜์—ฌ micropost์˜ ์ปฌ๋ ‰์…˜์„ ํ‘œ์‹œํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

<ol class="microposts">
  <%= render @microposts %>
</ol>

์šฐ์„ ์€ ์ˆœ์„œ๊ฐ€ ์—†๋Š” ๋ฆฌ์ŠคํŠธ์ธ ul ํƒœ๊ทธ๊ฐ€ ์•„๋‹Œ, ์ˆœ์„œ๊ฐ€ ์žˆ๋Š” ๋ฆฌ์ŠคํŠธ์˜ ol ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด๊ฒƒ์€ micropost ๊ฐ€ ํŠน์ •ํ•œ ์ˆœ์„œ (์ตœ์‹ ์ˆœ > ์˜ค๋ž˜๋œ ์ˆœ) ์œผ๋กœ ์˜์กดํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ ๋Œ€์‘ํ•˜๋Š” ํŒŒ์…œ์€ ์•„๋ž˜์˜ ์ฝ”๋“œ์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<!-- app/views/microposts/_micropost.html.erb -->
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>

์—ฌ๊ธฐ์„œ๋Š” time_ago_in_words ๋ผ๊ณ  ํ•˜๋Š” ํ—ฌํผ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ฉ”์†Œ๋“œ ์ด๋ฆ„์ด ๋‚˜ํƒ€๋‚ด๋Š” ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค๋งŒ, "3๋ถ„ ์ „์— ์ž‘์„ฑ" ์ด๋ผ๋Š” ๋ฌธ์ž์—ด์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์ธ ํšจ๊ณผ์— ๋Œ€ํ•ด์„œ๋Š” 13.2.2์—์„œ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๊ฐ Micropost์— ๋Œ€ํ•˜์—ฌ CSS์˜ id๋ฅผ ํ• ๋‹นํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

<li id="micropost-<%= micropost.id %>">

์ด ๊ฒƒ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์ข‹๋‹ค๊ณ  ํ‰๊ฐ€๋˜๋Š” ๊ด€์Šต์œผ๋กœ, ์˜ˆ๋ฅผ ๋“ค์–ด ์•ž์œผ๋กœ Javascript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ micropost๋ฅผ ์กฐ์ž‘ํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ํ•œ ๋ฒˆ์— ๋ชจ๋“  micropost๊ฐ€ ํ‘œ์‹œ๋˜์–ด๋ฒ„๋ฆฌ๋Š” ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์— ๋Œ€ํ•ด ๋Œ€์ฒ˜ํ•ด๋ด…๋‹ˆ๋‹ค. 10.3.3 ์—์„œ๋Š” Pagination์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๋ฒˆ์—๋„ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด๋ด…๋‹ˆ๋‹ค. ์ €๋ฒˆ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ will_paginate ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

<%= will_paginate @microposts %>

์ด์ „ 10์žฅ์˜ ์œ ์ € ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด์˜ ์ฝ”๋“œ์™€ ๋น„๊ตํ•ด๋ณด๋ฉด ์กฐ๊ธˆ์€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์ด์ „์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹จ์ˆœํ•œ ์ฝ”๋“œ์˜€์Šต๋‹ˆ๋‹ค.

<%= will_paginate %>

์‚ฌ์‹ค์€ ์œ„ ์ฝ”๋“œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์ด๋„ ๋™์ž‘ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ will_paginate ๊ฐ€ Users ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ฌธ๋งฅ์—์„œ, @users ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๊ฐ€ ์กด์žฌํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ์ „์ œ ๋กœ ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋Š” 10.3.3 ์— ์„œ์ˆ ํ–ˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ, ActiveRecord::Relation ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ฒฝ์šฐ์—๋Š” Users ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ฌธ๋งฅ์œผ๋กœ๋ถ€ํ„ฐ, micropost๋ฅผ paginationํ•˜๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์—, (์ฆ‰ ๋ฌธ๋งฅ์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—), ๋ช…์‹œ์ ์œผ๋กœ @microposts ๋ณ€์ˆ˜๋ฅผ will_paginate ์— ๋„˜๊ธธ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด์™€ ๊ฐ™์ด ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋ฅผ Users ์ปจํŠธ๋กค๋Ÿฌ์˜ show ์•ก์…˜์—์„œ ์ •์˜ํ•ด์ฃผ์–ด์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page]) #์ถ”๊ฐ€
  end
  .
  .
  .
end

paginate ๋ฉ”์†Œ๋“œ์˜ ๋Œ€๋‹จํ•จ์— ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. micropost ์˜ ๊ด€๊ณ„๋ฅผ ๊ฒฝ์œ ํ•˜์—ฌ micropost ํ…Œ์ด๋ธ”์— ๋„๋‹ฌํ•˜๊ณ , ํ•„์š”ํ•œ micropost์˜ ํŽ˜์ด์ง€๋ฅผ ๋Œ์–ด๋‚ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰ ๊ณผ์ œ๋กœ๋Š”, micropost์˜ ์ž‘์„ฑ ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค๋งŒ, ์ด๊ฒƒ์€ count ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์˜ฌ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

user.microposts.count

paginate ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๊ด€๊ณ„๋ฅผ ํ†ตํ•˜์—ฌ count ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ์ ์€, count ๋ฉ”์†Œ๋“œ์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์˜ micropost๋ฅผ ์ „๋ถ€ ์ฝ์–ด๋“ค์ธ ํ›„, ๊ฒฐ๊ณผ์˜ ๋ฐฐ์—ด์— ๋Œ€ํ•ด length ๋ฅผ ํ˜ธ์ถœ, ํ•˜๋Š” ์ด๋Ÿฌํ•œ ๋ถˆํ•„์š”ํ•œ ์ฒ˜๋ฆฌ๋Š” ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌํ•œ ํ–‰์œ„๋ฅผ ํ•œ๋‹ค๋ฉด, micropost์˜ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด ๊ทธ์— ๋”ฐ๋ผ ํšจ์œจ์ด ๋–จ์–ด์ง€๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๊ณ , (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด์˜ ๊ณ„์‚ฐ์€ ๊ณ ๋„๋กœ ์ตœ์ ํ™”๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๊ฒŒ ๋Œ€์‹  ๊ณ„์‚ฐ์„ ํ•˜๊ฒŒ ํ•˜๊ณ , ํŠน์ • user_id ์— ๊ด€๊ณ„๋งบ์–ด์ ธ ์žˆ๋Š” micropost ์˜ ์ˆ˜๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. (๊ทธ๋Ÿผ์—๋„ count ๋ฉ”์†Œ๋“œ๊ฐ€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ bottle neck์ด ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค๋ฉด, ์ข€ ๋” ๋น ๋ฅธ counter cache ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.)

์ด๊ฒƒ์œผ๋กœ ๋ชจ๋“  ์š”์†Œ๋Š” ์ •๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœํ•„ ํ™”๋ฉด์— micropost๋ฅผ ํ‘œ์‹œํ•ด๋ด…์‹œ๋‹ค. (์ด ๋•Œ, 7์žฅ์—์„œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ if @user.microposts.any? ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์œ ์ €์˜ micropost๊ฐ€ ํ•˜๋‚˜๋„ ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๊ฒŒํ•˜๋Š” ์ ๋„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.)

<!-- app/views/users/show.html.erb -->
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
  <div class="col-md-8">
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

์—ฌ๊ธฐ์„œ ๊ฐœ์„ ํ•œ ์ƒˆ๋กœ์šด ํ”„๋กœํ•„ ํ™”๋ฉด์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. ...์•„๋ฌด๊ฒƒ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š๋Š” ์™ธ๋กœ์šด ํŽ˜์ด์ง€๋„ค์š”. micropost๊ฐ€ ํ•˜๋‚˜๋„ ์—†๋Š” ๊ฑด ์•„๋‹๊นŒ์š”? ๊ทธ๋Ÿผ micropost๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค.

์—ฐ์Šต
  1. 7.3.3 ์— ๊ฐ€๋ณ๊ฒŒ ์„ค๋ช…๋“œ๋ ธ๋“ฏ, ์ด๋ฒˆ ํ—ฌํผ ๋ฉ”์†Œ๋“œ๋กœ ์‚ฌ์šฉํ•œ time_ago_in_words ๋ฉ”์†Œ๋“œ๋Š” Rails ์ฝ˜์†”์˜ helper ์˜ค๋ธŒ์ ํŠธ๋กœ๋ถ€ํ„ฐ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด helper ์˜ค๋ธŒ์ ํŠธ์˜ time_ago_in_words ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ 3.weeks.ago ๋‚˜ 6.months.ago ๋ฅผ ์‹คํ–‰ํ•ด๋ด…์‹œ๋‹ค.
  2. helper.time_ago_in_words(1.year.ago) ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ์–ด๋– ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋ณด์—ฌ์ง€๋‚˜์š”?
  3. microposts ์˜ค๋ธŒ์ ํŠธ์˜ ํด๋ž˜์Šค๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? Hint : ์œ„ ๋‘ ๋ฒˆ์งธ ์ฝ”๋“œ์— ์žˆ๋“ฏ, ์šฐ์„  paginate ๋ฉ”์†Œ๋“œ (ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” page: nil) ๋กœ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์–ป๊ณ , ๊ทธ ๋‹ค์Œ class ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด๋ด…์‹œ๋‹ค.

13.2.2 Micropost์˜ Sample

13.2.1 ์˜ ์œ ์ € micropost์˜ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋Š” ์กฐ๊ธˆ์€ ๋‹จ์ˆœํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. 10.3.2 ์˜ Sample data ์ƒ์„ฑ Task์— micropost๋„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ด๋Ÿฌํ•œ ์กฐ๊ธˆ ํž˜๋น ์ง€๋Š” ์ƒํ™ฉ์„ ์ˆ˜์ •ํ•ด๋ด…์‹œ๋‹ค.

๋ชจ๋“  ์œ ์ €์˜ micropost๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ ํ•˜๋ฉด ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—, take ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ œ์ผ ์ฒ˜์Œ์˜ 6๋ช…๋งŒ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค.

User.order(:created_at).take(6)

(์ด ๋•Œ, order ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ์ œ์ผ ์ดˆ๋ฐ˜์— ์ƒ์„ฑ๋œ 6๋ช…์˜ ์œ ์ €๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•ด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

์ด 6๋ช…์˜ ์œ ์ €์— ๋Œ€ํ•ด์„œ๋Š” 1ํŽ˜์ด์ง€์˜ ํ‘œ์‹œํ•œ๊ณ„์ˆ˜ (30) ๋ฅผ ๋„˜๊ธฐ๊ธฐ ์œ„ํ•ด ๊ฐ๊ฐ 50์—ฌ๊ฐœ์˜ micropost๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ฐ ํˆฌ๊ณ  ๋‚ด์šฉ์— ๋Œ€ํ•ด์„œ๋Š” Faker gem์˜ Lorem.sentence๋ผ๊ณ  ํ•˜๋Š” ํŽธ๋ฆฌํ•œ ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝํ•œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. (์•„๋ž˜ ์ฝ”๋“œ์˜ ๋ฃจํ”„ ์ˆœ์„œ๋Š” ์œ„ํ™”๊ฐ์ด ์žˆ์„์ง€๋„ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๊ฒƒ์€ 14.3์—์„œ ์Šคํ…Œ์ดํ„ฐ์Šค ํ”ผ๋“œ(ํƒ€์ž„๋ผ์ธ) ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋„์›€์ด ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์œ ์ €๋ณ„๋กœ 50๊ฐœ๋ถ„์˜ micropost๋ฅผ ์ •๋ฆฌํ•˜์—ฌ ์ƒ์„ฑํ•ด๋ฒ„๋ฆฌ๋ฉด, ์Šคํ…Œ์ดํ„ฐ์Šค ํ”ผ๋“œ์— ํ‘œ์‹œ๋˜๋Š” ํˆฌ๊ณ ๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ ์œ ์ €๊ฐ€ ๋˜์–ด๋ฒ„๋ฆฌ๊ณ  ๋งˆ๋‹ˆ, ์‹œ๊ฐ์ ์œผ๋กœ๋„ ์ข‹์ง€ ์•Š์„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.)

# db/seeds.rb
.
.
.
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(5)
  users.each { |user| user.microposts.create!(content: content) }
end

์—ฌ๊ธฐ์„œ ์–ธ์ œ๋‚˜์ฒ˜๋Ÿผ ๊ฐœ๋ฐœํ™˜๊ฒฝ์šฉ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋‹ค์‹œ ํ•œ ๋ฒˆ sample data๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ rails db:migrate:reset
$ rails db:seed

์ž‘์„ฑ์ด ๋๋‚ฌ๋‹ค๋ฉด, Rails ์„œ๋ฒ„๋ฅผ ์žฌ๋ถ€ํŒ…ํ•ด๋ณด์„ธ์š”.

๊ฐ๊ฐ์˜ micropost๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์— ์˜ํ•ด, 13.2.1 ์˜ ๊น€๋น ์ง€๋Š” ์ž‘์—…์ด ๋“œ๋””์–ด ๋๋‚ฌ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์œ„ ํŽ˜์ด์ง€์—๋Š” micropost ๊ณ ์œ ์˜ ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์–ด์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด๋ด…์‹œ๋‹ค.

/* app/assets/stylesheets/custom.scss */
.
.
.
/* microposts */

.microposts {
  list-style: none;
  padding: 0;
  li {
    padding: 10px 0;
    border-top: 1px solid #e8e8e8;
  }
  .user {
    margin-top: 5em;
    padding-top: 0;
  }
  .content {
    display: block;
    margin-left: 60px;
    img {
      display: block;
      padding: 5px 0;
    }
  }
  .timestamp {
    color: $gray-light;
    display: block;
    margin-left: 60px;
  }
  .gravatar {
    float: left;
    margin-right: 10px;
    margin-top: 5px;
  }
}

aside {
  textarea {
    height: 100px;
    margin-bottom: 5px;
  }
}

span.picture {
  margin-top: 10px;
  input {
    border: 0;
  }
}

์•„๋ž˜ ์ฒซ ๋ฒˆ์งธ ์œ ์ €์˜ ํ”„๋กœํ•„ ํ™”๋ฉด์„, ์•„๋ž˜ ๋‘ ๋ฒˆ์งธ ํ™”๋ฉด์—์„œ๋Š”, 2๋ฒˆ์งธ ์œ ์ €์˜ ํ”„๋กœํ•„ ํ™”๋ฉด์„ ํ‘œ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰ ์Šคํฌ๋ฆฐ์ƒท์—์„œ๋Š” ์ œ์ผ ์ฒซ ๋ฒˆ์งธ ์œ ์ €์˜ 2๋ฒˆ์งธ ํŽ˜์ด์ง€์™€ ์ œ์ผ ๋ฐ‘์— ์žˆ๋Š” pagination์˜ ๋งํฌ๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ micropost์˜ ํ‘œ์‹œ์—๋Š” 3๊ฐœ์˜ ์–ด๋– ํ•œ ๊ฒฝ์šฐ๋ผ๋„, ์ƒ์„ฑ๋œ ํ›„๋กœ์˜ ๊ฒฝ๊ณผ์‹œ๊ฐ„ ("1๋ถ„์ „์— ํˆฌ๊ณ " ๋“ฑ)์ด ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด๊ฒƒ์€ ์ด์ „์— ์ž‘์„ฑํ•œ time_ago_in_words ๋ฉ”์†Œ๋“œ์— ์˜ํ•œ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์ˆ˜ ๋ถ„์„ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ๋‚˜์„œ ํŒจ์ด์ง€๋ฅผ ์ƒˆ๊ณ ๋กœ์นจํ•˜๋ฉด, ์ด ํ…์ŠคํŠธ๋Š” ์ž๋™์ ์œผ๋กœ ์ƒˆ๋กœ์šด ์‹œ๊ฐ„๋Œ€๋กœ ๊ฐฑ์‹ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์—ฐ์Šต
  1. (1..10).to_a.take(6) ๋ผ๊ณ  ํ•˜๋Š” ์ฝ”๋“œ์˜ ์‹คํ–‰๊ฒฐ๊ณผ๋ฅผ ์ถ”์ธกํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์ถ”์ธกํ•œ ๊ฒฐ๊ณผ๊ฐ’์ด ์ œ๋Œ€๋กœ ๋งž๋Š”์ง€ ์‹ค์ œ๋กœ ์ฝ˜์†”์—์„œ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.
  2. ๋ฐฉ๊ธˆ ์ „ ์—ฐ์Šต์—์„œ ์‚ฌ์šฉํ•œ to_a ๋ฉ”์†Œ๋“œ์˜ ๋ถ€๋ถ„์€ ์ •๋ง๋กœ ํ•„์š”ํ•œ ๋ถ€๋ถ„์ธ๊ฐ€์š”? ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.
  3. Faker ๋Š” Lorem ipsum ์ด์™ธ์—๋„ ๋งค์šฐ ๋‹ค์–‘ํ•œ ์‚ฌ๋ก€์— ๋Œ€์‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Faker์˜ ๋ฌธ์„œ ๋ฅผ ํ™•์ธํ•˜๋ฉด์„œ ํ™”๋ฉด์˜ ์ถœ๋ ฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ณ , ์‹ค์ œ๋กœ ๋Œ€ํ•™์ด๋ฆ„์ด๋‚˜ ์ „ํ™”๋ฒˆํ˜ธ ๋“ฑ์„ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ด๋ด…์‹œ๋‹ค.

13.2.3 Profile ํ™”๋ฉด์˜ Micropost๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž.

์ด์ œ ๋ง‰ account๋ฅผ ์œ ํšจํ™”ํ•œ ์œ ์ €๋Š” ํ”„๋กœํ•„ํ™”๋ฉด์— ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜๊ธฐ์—, ํ•ด๋‹น ํ”„๋กœํ•„ํ™”๋ฉด์ด ์ œ๋Œ€๋กœ ํ‘œ์‹œ๋˜๋Š”์ง€๋Š” ์œ ๋‹› ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” ํ”„๋กœํ•„ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” micropost์— ๋Œ€ํ•ด ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์šฐ์„  ํ”„๋กœํ•„ ํ™”๋ฉด์šฉ์˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค.

$ rails generate integration_test users_profile
      invoke  test_unit
      create    test/integration/users_profile_test.rb

ํ”„๋กœํ•„ํ™”๋ฉด์— ์žˆ๋Š” micropost๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์œ ์ €์™€ ๊ด€๊ณ„๊ฐ€ ๋งบ์–ด์ ธ ์žˆ๋Š” micropost์˜ ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Rails์˜ ๊ด€์Šต์— ๋”ฐ๋ผ, ๊ด€๋ จ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ fixture ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•˜๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>
  user: michael

user ์— michael ์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด, Rails๋Š” fixture ํŒŒ์ผ ๋‚ด์˜ ๋Œ€์‘ํ•˜๋Š” ์œ ์ €๋ฅผ ์ฐพ์•„๋‚ด์–ด (ํ˜น์€ ๋ฐœ๊ฒฌํ•œ๋‹ค๋ฉด,) micropost์™€ ๊ด€๊ณ„๋ฅผ ๋งบ์–ด์ค„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

  name: Michael Example
  email: michael@example.com
  .
  .
  .

๋˜ํ•œ micropost์˜ pagination์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด, micropost์šฉ์˜ fixture์— ๋ช‡๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๊ฒƒ์€ ์ด์ „ 10์žฅ์—์„œ ์œ ์ €๋ฅผ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, Embeded Ruby์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

<% 30.times do |n| %>
micropost_<%= n %>:
  content: <%= Faker::Lorem.sentence(5) %>
  created_at: <%= 42.days.ago %>
  user: michael
<% end %>

์œ„ ์ฝ”๋“œ๋ฅผ 1๊ฐœ๋กœ ์ •๋ฆฌํ•˜๋ฉด, micropost ์šฉ์˜ fixture ํŒŒ์ผ์€ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

# test/fixtures/microposts.yml
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>
  user: michael

tau_manifesto:
  content: "Check out the @tauday site by @mhartl: http://tauday.com"
  created_at: <%= 3.years.ago %>
  user: michael

cat_video:
  content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
  created_at: <%= 2.hours.ago %>
  user: michael

most_recent:
  content: "Writing a short test"
  created_at: <%= Time.zone.now %>
  user: michael

<% 30.times do |n| %>
micropost_<%= n %>:
  content: <%= Faker::Lorem.sentence(5) %>
  created_at: <%= 42.days.ago %>
  user: michael
<% end %>

ํ…Œ์ŠคํŠธ๋ฐ์ดํ„ฐ๋Š” ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๋ฒˆ ํ…Œ์ŠคํŠธ๋Š” ์กฐ๊ธˆ์€ ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ํ”„๋กœํ•„ ํ™”๋ฉด์— ์•ก์„ธ์Šค ํ•œ ๋‹ค์Œ, ํŽ˜์ด์ง€ ํƒ€์ดํ‹€๊ณผ ์œ ์ € ์ด๋ฆ„, Gravatar, micropost์˜ ํˆฌ๊ณ  ๊ฐœ์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ํŽ˜์ด์ง€ ๋ถ„ํ• ๋œ micropost ๋“ฑ์˜ ์ˆœ์œผ๋กœ ํ…Œ์ŠคํŠธํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. (Application ํ—ฌํผ๋ฅผ ์ฝ์–ด๋“ค์ธ ๊ฒƒ์œผ๋กœ, full_title ํ—ฌํผ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.)

# test/integration/users_profile_test.rb
require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:michael)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination'
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
end

์œ„ ์ฝ”๋“œ์—์„œ๋Š” micropost์˜ ํˆฌ๊ณ  ๊ฐœ์ˆ˜๋ฅผ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด์„œ, ์ œ 12์žฅ ์˜ ์—ฐ์Šต (12.3.3 ์˜ ์—ฐ์Šต๋ฌธ์ œ) ์—์„œ ์†Œ๊ฐœํ•œ response.body ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฆ„์„ ๋ณด๋ฉด ์˜คํ•ดํ•  ์ง€๋„ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค๋งŒ, response.body ์—๋Š” ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ์™„์ „ํ•œ HTML์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. (HTML์˜ body ํƒœ๊ทธ ๋ฟ๋งŒ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค.) ๋”ฐ๋ผ์„œ ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ์–ด๋”˜๊ฐ€์—๋Š” micropost์˜ ํˆฌ๊ณ  ๊ฐœ์ˆ˜๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฐพ์•„๋‚ด์–ด ๋งค์น˜ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

assert_match @user.microposts.count.to_s, response.body

์ด ๊ฒƒ์€ assert_select ๋ณด๋‹ค๋„ ์ข€ ๋” ์ถ”์ƒ์ ์ธ ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ assert_select ์—์„œ๋Š” ์–ด๋– ํ•œ HTML ํƒœ๊ทธ๋ฅผ ์ฐพ๋Š”์ง€ ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค๋งŒ, assert_match ๋ฉ”์†Œ๋“œ์—์„œ๋Š” ๊ทธ๋Ÿด ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ์ ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

๋˜ํ•œ assert_select ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ์ข€ ๋” ์ƒ์„ธํ•œ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.

assert_select 'h1>img.gravatar'

์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์œผ๋กœ h1 ํƒœ๊ทธ (์ œ์ผ ํฐ ํ‘œ์ œ ๊ธ€์”จ) ์˜ ์•ˆ์ชฝ ์— ์žˆ๋Š” gravatar ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง„ img ํƒœ๊ทธ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธก์˜ ์ฝ”๋“œ๋Š” ๊ตฌํ˜„ํ•ด๋†“์•˜์œผ๋ฏ€๋กœ, ์ด ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

์—ฐ์Šต
  1. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ์žˆ๋Š” 2๊ฐœ์˜ h1 ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธก์˜ ์ฝ”๋“œ๋ฅผ ์ฝ”๋ฉ˜ํŠธ์•„์›ƒํ•ด๋ด…์‹œ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.
  2. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ, will_paginate ๊ฐ€ 1๋ฒˆ๋งŒ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•ด๋ด…์‹œ๋‹ค. Hint : ์ด์ „ 5์žฅ์˜ 2๋ฒˆ์งธ ํ‘œ ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

13.3 Micropost๋ฅผ ์กฐ์ž‘ํ•ด๋ณด์ž.

๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง๊ณผ micropost ํ‘œ์‹œ ํ…œํ”Œ๋ฆฟ์˜ ์–‘์ชฝ์„ ์™„์„ฑ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ Web ๊ฒฝ์œ ๋กœ micropost๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ Interface๋ฅผ ์ž‘์„ฑํ•ด๋ด…์‹œ๋‹ค. ์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” Status feed (์ œ 14์žฅ์—์„œ ์™„์„ฑ์‹œํ‚ต๋‹ˆ๋‹ค.) ์˜ ์ œ์ผ ์ฒซ ๋ฒˆ์งธ ํžŒํŠธ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ๋Š” ์œ ์ €๊ฐ€ micropost๋ฅผ Web ๊ฒฝ์œ ๋กœ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด๋ด…์‹œ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€์˜ Rails๊ฐœ๋ฐœ์˜ ์—ฐ์Šต๊ณผ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด 1๊ตฐ๋ฐ ์žˆ์Šต๋‹ˆ๋‹ค. Micropost ๋ฆฌ์†Œ์Šค๋กœ์˜ Interface๋Š” ์ฃผ๋กœ ํ”„๋กœํ•„ ํŽ˜์ด์ง€์™€ Home ํŽ˜์ด์ง€์˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๊ฒฝ์œ ํ•˜์—ฌ ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, Micropost ์ปจํŠธ๋กค๋Ÿฌ๋Š” new ๋‚˜ edit ๊ณผ ๊ฐ™์€ ์•ก์…˜์€ ํ•„์š”ํ•˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰, create ๋‚˜ destroy ๋งŒ ์žˆ์œผ๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Microposts์˜ ๋ฆฌ์†Œ์Šค๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ, ์•„๋ž˜ ์ฝ”๋“œ๋Š” RESTfulํ•œ ๋ผ์šฐํŒ…์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ์ด ๋ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์‹ฌํ”Œํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์€ ์™„์„ฑ๋„๊ฐ€ ๋”์šฑ ๋†’์•„์กŒ๋‹ค๋Š” ์ฆ๊ฑฐ์ด๋ฉฐ, ํ‡ดํ™”๋˜์ง„ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ œ 2์žฅ ์—์„œ scaffold์— ๋งก๊ธฐ๊ธฐ๋งŒํ–ˆ๋˜ ์‹œ์ ˆ๋ถ€ํ„ฐ ์ง€๊ธˆ์— ์ด๋ฅด๊ธฐ๊นŒ์ง€ ๋จธ๋‚˜๋จผ ๊ธธ์ด์—ˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์ง€๊ธˆ์€ scaffold๊ฐ€ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ณต์žกํ•œ ์ฝ”๋“œ๋Š” ๊ฑฐ์˜ ํ•„์š”ํ•˜์ง€ ์•Š๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

# config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy] #new
end
HTTP Request URL Action Named root
POST /microposts create microposts_path
DELETE /microposts/1 destroy micropost_path(micropost)

13.3.1 Micropost์˜ ์ ‘๊ทผ์ œ์–ด

Microposts ๋ฆฌ์†Œ์Šค์˜ ๊ฐœ๋ฐœ์—์„œ๋Š”, Microposts ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€์˜ ์ ‘๊ทผ์ œ์–ด๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ด…์‹œ๋‹ค. ๊ด€๋ จ๋œ ์œ ์ €๋“ค์„ ํ†ตํ•˜์—ฌ micropost๋กœ ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์—, create ์•ก์…˜์ด๋‚˜ destory ์•ก์…˜์„ ์ด์šฉํ•˜๋Š” ์œ ์ €๋Š” ์ด๋ฏธ ๋กœ๊ทธ์ธ๋˜์–ด์žˆ๋Š” ์ƒํƒœ์ด์ง€ ์•Š์œผ๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ๋˜์–ด์žˆ๋Š”์ง€ ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ์—์„œ๋Š” Users ์ปจํŠธ๋กค๋Ÿฌ์šฉ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์˜ฌ๋ฐ”๋ฅธ Request๋ฅผ ๊ฐ ์•ก์…˜์œผ๋กœ ์‹คํ–‰์‹œ์ผœ, Micropost์˜ ์ˆ˜์— ๋ณ€ํ™”๊ฐ€ ์žˆ๋Š”์ง€ ์—†๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , Redirect๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# test/controllers/microposts_controller_test.rb
require 'test_helper'

class MicropostsControllerTest < ActionDispatch::IntegrationTest

  def setup
    @micropost = microposts(:orange)
  end

  test "should redirect create when not logged in" do
    assert_no_difference 'Micropost.count' do
      post micropost_path, params: { micropost: { content: "Lorem ipsum" } }
    end
    assert_redirected_to login_url
  end

  test "should redirect destroy when not logged in" do
    assert_no_difference 'Micropost.count' do
      delete micropost_path(@micropost)
    end
    assert_redirected_to login_url
  end
end

์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ํ…Œ์ŠคํŠธ์— ํ†ต๊ณผํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์•ฝ๊ฐ„์˜ application์—์„œ์˜ ์ฝ”๋“œ๋ฅผ Refactoringํ•ด์•ผํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. 10.2.1 ์—์„œ๋Š” before ํ•„ํ„ฐ์˜ logged_in_user ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ๋กœ๊ทธ์ธ์„ ์š”์ฒญํ–ˆ๋˜ ๊ฒƒ์„ ๋– ์˜ฌ๋ ค์ฃผ์„ธ์š”. ๊ทธ ๋•Œ์—๋Š” Users ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€์˜ ์ด ๋ฉ”์†Œ๋“œ๊ฐ€ ์กด์žฌํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์—, before ํ•„ํ„ฐ๋กœ ์ง€์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค๋งŒ, ์ด ๋ฉ”์†Œ๋“œ๋Š” microposts ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ๊ฐ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๊ณ„์Šนํ•˜๋Š” Application ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋‚ด๋ถ€์— ์ด ๋ฉ”์†Œ๋“œ๋ฅผ ์˜ฎ๊ฒจ๋ด…์‹œ๋‹ค. ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper

  private

    # ์œ ์ €์˜ ๋กœ๊ทธ์ธ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end

์ฝ”๋“œ๊ฐ€ ์ค‘๋ณตํ•˜์ง€ ์•Š๋„๋ก, Users์ปจํŠธ๋กค๋Ÿฌ๋ถ€ํ„ฐ logged_in_user ๋ฅผ ์‚ญ์ œํ•ฉ์‹œ๋‹ค.

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  .
  .
  .
  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # before filter

    # ์˜ฌ๋ฐ”๋ฅธ ์œ ์ €์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end

    # ๊ด€๋ฆฌ์ž์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end
end

application_controller.rb ์˜ ์ฝ”๋“œ์— ๋”ฐ๋ผ์„œ๋Š”, Microposts ์ปจํŠธ๋กค๋Ÿฌ๋กœ๋ถ€ํ„ฐ๋„ logged_in_user ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ create ์•ก์…˜์ด๋‚˜ destroy ์•ก์…˜์— ๋Œ€ํ•œ ์ ‘๊ทผ์ œ์–ด๋ฅผ, before ํ•„ํ„ฐ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

# app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
  end

  def destroy
  end
end

์ด ๊ฒƒ์œผ๋กœ ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

์—ฐ์Šต
  1. ์–ด์งธ์„œ Users ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€์— ์žˆ๋Š” logged_in_user ํ•„ํ„ฐ๋ฅผ ๋‚จ๊ฒจ๋‘”๋‹ค๋ฉด ์ข‹์ง€ ์•Š์€ ๊ฒƒ์ผ๊นŒ์š”? ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค.

13.3.2 Micropost๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž

์ œ 7์žฅ ์—์„œ๋Š” HTTP POST Request๋ฅผ Users ์ปจํŠธ๋กค๋Ÿฌ์˜ create ์•ก์…˜์œผ๋กœ ๋ฐœํ–‰ํ•˜๋Š” HTML form์„ ์ƒ์„ฑํ•˜์—ฌ ์œ ์ €์˜ sign up ์„ ๊ตฌํ˜„ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. Micropost ์ƒ์„ฑ์˜ ๊ตฌํ˜„๋„ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋œ ์ฐจ์ด์ ์€ ๋‹ค๋ฅธ micropost / new ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ์—, Home ํ™”๋ฉด (์ฆ‰, Root path) ์— Form์„ ์„ค์น˜ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ๋ชฉ์—…์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

๋งˆ์ง€๋ง‰์œผ๋กœ Home ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•˜์˜€์„ ๋•Œ, (5์žฅ) [Sign up now !] ๋ฒ„ํŠผ์ด ์ค‘์•™์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. Micropost ์ƒ์„ฑ form์€, ๋กœ๊ทธ์ธ๋˜์–ด์žˆ๋Š” ํŠน์ • ์œ ์ €๋งŒ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ด ์„น์…˜์—์„œ์˜ ๋ชฉํ‘œ์ค‘ ํ•˜๋‚˜๋กœ๋Š”, ์œ ์ €์˜ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์— ๋Œ€ํ•˜์—ฌ Home ํ™”๋ฉด์— ํ‘œ์‹œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์— ๋Œ€ํ•ด์„œ๋Š” ์•„๋ž˜ ๋‘ ๋ฒˆ์งธ ์ฝ”๋“œ์—์„œ ๊ตฌํ˜„ํ•ด๋ด…๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ Micropost์˜ create ์•ก์…˜์„ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค. ์ด ์•ก์…˜๋„ ์ด์ „ 7์žฅ์—์„œ ๊ตฌํ˜„ํ•œ ์œ ์ €์šฉ์˜ ์•ก์…˜๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ์ฐจ์ด์ ์œผ๋กœ๋Š” ์ƒˆ๋กœ์šด micropost๋ฅผ build ํ•˜๊ธฐ ์œ„ํ•ด User/Micropost ๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. micropost_params ์—์„œ Strong Parameters ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ ์ธํ•˜์—ฌ, micropost์˜ content ์†์„ฑ๋งŒ์ด Web ๊ฒฝ์œ ๋กœ ๋ณ€๊ฒฝ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋œ ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.

# app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end

Micropost ์ƒ์„ฑ Form์„ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด, ์‚ฌ์ดํŠธ ๋ฐฉ๋ฌธ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๊ณ  ์žˆ๋Š”์ง€์— ๋”ฐ๋ผ ๋ณ€ํ•˜๋Š” HTML์„ ์ œ๊ณตํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ด…์‹œ๋‹ค.

<!-- app/views/static_pages/home.html.erb -->
<!-- new -->
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>
<!-- new -->
  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails logo"),
              'http://rubyonrails.org/' %>
<% end %> <!-- new -->

if-else ๋ฌธ์˜ ๋ถ„๊ธฐ์—์„œ ์ฝ”๋“œ๋ฅผ ๋‚˜๋ˆ„๊ณ  ์žˆ๋Š” ์ ์ด ์กฐ๊ธˆ์€ ๋ฒˆ๊ฑฐ๋กญ๊ธด ํ•˜์ง€๋งŒ, ์ด ์ฝ”๋“œ์˜ Refactoring์€ ์—ฐ์Šต๋ฌธ์ œ๋กœ ๋‚ด๊ธฐ๋กœ ํ•ด๋ด…์‹œ๋‹ค.

์œ„ ์ฝ”๋“œ๋ฅผ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ช‡๊ฐ€์ง€์˜ Partial์„ ์ƒ์„ฑํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ์„  Home ํŽ˜์ด์ง€์˜ ์ƒˆ๋กœ์šด ์‚ฌ์ด๋“œ๋ฐ”๋ถ€ํ„ฐ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<!-- app/views/shared/_user_info.html.erb -->
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>

ํ”„๋กœํ•„ ์‚ฌ์ด๋“œ๋ฐ”์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์œ„ ์ฝ”๋“œ์˜ ์œ ์ € ์ •๋ณด์—๋„ ํ•ด๋‹น ์œ ์ €๊ฐ€ ํˆฌ๊ณ ํ•œ micropost์˜ ์ด ๊ฐฏ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ๋‹จ, ์กฐ๊ธˆ ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ํ”„๋กœํ•„ ์‚ฌ์ด๋“œ๋ฐ”์—์„œ๋Š” "Micropost" ์„ ๋ผ๋ฒจ๋กœ ํ‘œ์‹œํ•˜๊ณ , "Micropost(1)" ๋กœ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์€ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋ฒˆ ์ฒ˜๋Ÿผ "1 micropost" ๋ผ๊ณ  ํ‘œ์‹œํ•ด๋ฒ„๋ฆฌ๋ฉด ์˜์–ด์˜ ๋ฌธ๋ฒ•์ƒ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ 7.3.3 ์†Œ๊ฐœํ•œ pluralize ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ "1 micropost" ๋‚˜ "2 micrposts" ๋ผ๊ณ  ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ๋Š” micropost์ƒ์„ฑ form์„ ์ •์˜ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์œ ์ € ๋“ฑ๋ก form๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. (7์žฅ)

<!-- app/views/shared/_micropost_form.html.erb -->
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>

์œ„ ์ฝ”๋“œ์—์„œ์˜ form์ด ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, 2๊ฐ€์ง€์˜ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” (์ด์ „๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ) ๊ด€๊ณ„๋งบ๊ธฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด @micropost ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

@micropost = current_user.microposts.build

์ƒ์„ฑํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# app/controllers/static_pages_controller.rb 
# home ์•ก์…˜์— micropost๋ฅผ ์ธ์Šคํ„ด์Šค๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

class StaticPagesController < ApplicationController

  def home
    @micropost = current_user.microposts.build if logged_in?
  end

  def help
  end

  def about
  end

  def contact
  end
end

๋ฌผ๋ก  current_user ๋ฉ”์†Œ๋“œ๋Š” ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธํ•˜๊ณ  ์žˆ์„๋•Œ๋งŒ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ @micropost ๋ณ€์ˆ˜๋„ ๋กœ๊ทธ์ธํ•˜๊ณ  ์žˆ์„๋•Œ๋งŒ ์ •์˜๋  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์œ„ ๋‘๋ฒˆ์งธ ์ฝ”๋“œ๋ฅผ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ๋‹ค๋ฅธ ํ•˜๋‚˜์˜ ๋ณ€๊ฒฝ์ ์€, ์—๋Ÿฌ ๋ฉ”์„ธ์ง€์˜ partial์„ ์žฌ์ •์˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋Š”๋‹ค๋ฉด ์œ„ ๋‘๋ฒˆ์งธ ์ฝ”๋“œ๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

<%= render 'shared/error_messages, object: f.object %>

7์žฅ์—์„œ๋Š” ์—๋Ÿฌ ๋ฉ”์„ธ์ง€ partial์ด @user ๋ณ€์ˆ˜๋ฅผ ์ง์ ‘ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋˜ ๊ฒƒ์„ ๋– ์˜ฌ๋ ค์ฃผ์„ธ์š”. ์ด๋ฒˆ์—๋Š” ๊ทธ ๋Œ€์‹ ์—, @micropost ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌํ•œ ์ผ€์ด์Šค๋“ค์„ ์ •๋ฆฌํ•ด๋ณด๋ฉด, form ๋ณ€์ˆ˜ f ๋ฅผ, f.object ๋กœ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ๊ด€๊ณ„๋งบ์–ด์ ธ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ,

form_for(@user) do |f|

์œ„์™€ ๊ฐ™์ด f.object ๊ฐ€ @user ๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ์™€,

form_for(@micropost) do |f|

์œ„์™€ ๊ฐ™์ด f.object ๊ฐ€ @micropost ๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Partial์— ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋„˜๊ธฐ๊ธฐ ์œ„ํ•ด์„œ, ๊ฐ’์ด Object์ด๋ฉด์„œ Key๋ฅผ Partial์—์„œ์˜ ๋ณ€์ˆ˜๋ช…๊ณผ ๊ฐ™์€ ํ•ด์‹œ๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ ์œ„ ๋‘ ๋ฒˆ์งธ ์ฝ”๋“œ์˜ 2๋ฒˆ์งธ์ค„์˜ ์ฝ”๋“œ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋ฐ”๊ฟ”๋งํ•˜์ž๋ฉด, object: f.object ์™€ error_message partial ์•ˆ์—์„œ object ๋ผ๊ณ  ํ•˜๋Š” ๋ณ€์ˆ˜๋ช…์„ ์ƒ์„ฑํ•ด๋†“๋Š”๋‹ค๋ฉด, ์ด ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๊ฐฑ์‹ ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

<!-- app/views/shared/_error_messages.html.erb -->
<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

์ด ์‹œ์ ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด ํ…Œ์ŠคํŠธ๋Š” ์•„์ง ์‹คํŒจ์ผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

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

<!-- app/views/users/new.html.erb -->
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %> <!-- update -->
      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
<!-- app/views/users/edit.html.erb -->
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %><!-- update -->

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails">change</a>
    </div>
  </div>
</div>
<!-- app/views/password_resets/edit.html.erb -->
<% provide(:title, 'Reset password') %>
<h1>Password reset</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
      <%= render 'shared/error_messages', object: f.object %> <!-- update -->

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

์ด๊ฒƒ์œผ๋กœ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

๊ฒŒ๋‹ค๊ฐ€ ์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ ์ƒ์„ฑํ•œ ๋ชจ๋“  HTML์ด ์ ์ ˆํ•˜๊ฒŒ ํ‘œ์‹œ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์ตœ์ข…์  form์€ ์•„๋ž˜์™€ ์ฒซ ๋ฒˆ์งธ ์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋Ÿผ, ์ž‘์„ฑ ์—๋Ÿฌ๊ฐ€ ํ‘œ์‹œ๋œ form์€ ์•„๋ž˜ ๋‘ ๋ฒˆ์งธ ์Šคํฌ๋ฆฐ์ƒท์ฒ˜๋Ÿผ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์—ฐ์Šต
  1. HomeํŽ˜์ด์ง€๋ฅผ Refactoring ํ•˜์—ฌ if-else ๋ฌธ์˜ ๋ถ„๊ธฐ๋ฅผ ๊ฐ๊ฐ Partial๋กœ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค.

13.3.3 Feed์˜ ์›ํ˜•

micropost ์ž‘์„ฑ form์ด ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋˜์—ˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์ง€๊ธˆ ๋‹จ๊ณ„์—์„œ๋Š” ์ž‘์„ฑํ•œ ๋‚ด์šฉ์„ ๋ฐ”๋กœ ๋ณผ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์•„์ง Home ํŽ˜์ด์ง€์— micropost ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ถ€๋ถ„์ด ์•„์ง ๊ตฌํ˜„๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

micropost ์ž‘์„ฑ form์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ, ์˜ฌ๋ฐ”๋ฅธ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•œ ํ›„, ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜์—ฌ post๊ฐ€ ์ œ๋Œ€๋กœ ํ‘œ์‹œ๋˜๋ฉด ๊ทธ๊ฑธ๋กœ ๋‹คํ–‰์ž…๋‹ˆ๋‹ค๋งŒ, ์ด ๊ฒƒ์€ ์กฐ๊ธˆ ๊ท€์ฐฎ์€ ์ž‘์—…์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ชฉ์—…์ฒ˜๋Ÿผ, ์œ ์ € ์ž์‹ ์˜์˜ post๋ฅผ ํฌํ•จํ•œ, micropost์˜ feed๊ฐ€ ์—†์œผ๋ฉด ๋ถˆํŽธํ•ฉ๋‹ˆ๋‹ค. (์ œ 14์žฅ์—์„œ๋Š” ํ”ผ๋“œ๋ฅผ ๋ฒ”์šฉํ™”ํ•˜์—ฌ์—ฌ follow ํ•˜๊ณ  ์žˆ๋Š” ์œ ์ €๋“ค์˜ micropost๋„ feed์— ํ‘œ์‹œํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.)

๋ชจ๋“  ์œ ์ €๊ฐ€ feed๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ์—, feed ๋ฉ”์†Œ๋“œ๋Š” User ๋ชจ๋ธ์—์„œ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ž์—ฐ์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค. Feed์˜ ์›ํ˜•์€, ์šฐ์„  ํ˜„์žฌ ๋กœ๊ทธ์ธํ•˜๊ณ  ์žˆ๋Š” ์œ ์ €์˜ micropost๋ฅผ ์ „๋ถ€ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๋‹ค์Œ ์ฑ•ํ„ฐ์—์„œ ์™„์ „ํ•œ feed๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฒˆ์—๋Š” 11.3.3 ์—์„œ ์†Œ๊ฐœํ•œ where ๋ฉ”์†Œ๋“œ์—์„œ ์ด๊ฒƒ์„ ๊ตฌํ˜„ํ•ด๋ณผ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. Micropost ๋ชจ๋ธ์— ๋ณ€๊ฒฝ์ ์„ ๋”ํ•œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # 
  # ์™„์ „ํ•œ ๊ตฌํ˜„์€ ๋‹ค์Œ์žฅ์˜ "์œ ์ €๋ฅผ followํ•ด๋ณด์ž" ๋ฅผ ์ฐธ์กฐํ•ด์ฃผ์„ธ์š”.
  def feed
    Micropost.where("user_id = ?", id)
  end

    private
    .
    .
    .
end

๋‹ค์Œ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š” ๋ฌผ์Œํ‘œ๋Š”, ๋ณด์•ˆ์ƒ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ด์ค๋‹ˆ๋‹ค.

Microposts.where("user_id = ?", id)

์œ„ ๋ฌผ์Œํ‘œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ์ธํ•˜์—ฌ, SQL Query์— ๋Œ€์ž…ํ•˜๊ธฐ ์ „์— id ๊ฐ€ Escape๋˜์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์—, SQL์ธ์ ์…˜ (SQL Injection) ์ด๋ผ ๋ถˆ๋ฆฌ๋Š” ์‹ฌ๊ฐํ•œ ๋ณด์•ˆ์ƒ ๋ฌธ์ œ๋ฅผ ํšŒํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์˜ id ์†์„ฑ์€ ๋‹จ์ˆœํ•œ ์ •์ˆ˜ (์ฆ‰, self.id ๋Š” ์œ ์ €์˜ id) ์ด ๋•Œ๋ฌธ์— ์œ„ํ—˜ํ•˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค๋งŒ, SQL ๋ฌธ์— ๋ณ€์ˆ˜๋ฅผ ๋Œ€์ž…ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ํ•ญ์ƒ Escapeํ•˜๋Š” ์Šต๊ด€์„ ๋ชธ์— ์ตํ˜€์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์„ธ์‹ฌํ•œ ๋…์ž๋“ค์€ ์œ„ ์ฝ”๋“œ์—์„œ ๋ณธ์งˆ์ ์œผ๋กœ ๋‹ค์Œ ์ฝ”๋“œ์™€ ๊ฐ™์€ ๊ฒƒ์„ ์•Œ์•„์ฑ„๋ ธ์„ ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

def feed
  microposts
end

์œ„ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ์ผ๋ถ€๋Ÿฌ ์œ„ 2๋ฒˆ์งธ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์€, ์ œ 14์žฅ์—์„œ ํ•„์š”ํ•˜๊ฒŒ๋˜๋Š” ์™„์ „ํ•œ Status feed์—์„œ ์‘์šฉ์„ ํ•ด๋ณผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Sample application์— feed๊ธฐ๋Šฅ์„ ๋„์ž…ํ•˜๊ธฐ ์œ„ํ•ด, ๋กœ๊ทธ์ธ ์œ ์ €์˜ feed์šฉ์œผ๋กœ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜ @feed_items ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , Home ํŽ˜์ด์ง€์—์„œ๋Š” feed์šฉ์˜ Partial์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Home ํŽ˜์ด์ง€์— ๋ณ€๊ฒฝ์„ ๋”ํ•œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜ ์„ธ ๋ฒˆ์งธ app/views/static_pages/home.html.erb ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”. ์ด ๋•Œ, ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธํ•ด์žˆ๋Š”์ง€ ์–ด๋–ค์ง€ ํ™•์ธํ•œ ํ›„ if๋ฌธ์ด ๋ณ€ํ™”ํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ฆ‰, ์•„๋ž˜ ์ฝ”๋“œ

@micropost = current_user.microposts.build if logged_in?

๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€ํ•ฉ๋‹ˆ๋‹ค.

 if logged_in?
    @micropost  = current_user.microposts.build
    @feed_items = current_user.feed.paginate(page: params[:page])
  end
# app/controllers/static_pages_controller.rb
# home ์•ก์…˜์— feed์˜ ์ธ์Šคํ„ด์Šค๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
class StaticPagesController < ApplicationController

  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end

  def help
  end

  def about
  end

  def contact
  end
end
<!-- app/views/shared/_feed.html.erb -->
<!-- statusfeed์˜ partial -->
<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render @feed_items %>
  </ol>
  <%= will_paginate @feed_items %>
<% end %>

Status Feed์˜ Partial์€ Micropost์˜ partial๊ณผ๋Š” ๋‹ค๋ฅธ ์ ์— ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.

<%= render @feed_items %>

์ด ๋•Œ, @feed_item ์˜ ๊ฐ ์š”์†Œ๊ฐ€ Micropost ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, Rails๋Š” Micropost์˜ Partial์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ Rails๋Š” ๋Œ€์‘ํ•˜๋Š” ์ด๋ฆ„์˜ Partial์„ ๋„˜๊ฒจ์ง„ ๋ฆฌ์†Œ์Šค์˜ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด๋ถ€๋กœ๋ถ€ํ„ฐ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

app/views/microposts/_micropost.html.erb

๋‚จ์€ ๊ฒƒ์€, ์–ธ์ œ๋‚˜์ฒ˜๋Ÿผ feed partial์„ ํ‘œ์‹œํ•˜๋ฉด HomeํŽ˜์ด์ง€์— feed๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฐ๊ณผ๋Š” HomeํŽ˜์ด์ง€์˜ Feed๋กœ์จ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

<!-- app/views/static_pages/home.html.erb -->
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <!-- new -->
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
    <!-- new -->
  </div>

<% else %>
  .
  .
  .
<% end %>

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

# app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      @feed_items = [] #๋นˆ ๋ฐฐ์—ด
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
์—ฐ์Šต
  1. ์ƒˆ๋กญ๊ฒŒ ๊ตฌํ˜„ํ•œ micropost์˜ ์ƒ์„ฑ form์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ๋กœ micropost๋ฅผ ํˆฌ๊ณ ํ•ด๋ด…์‹œ๋‹ค. Rails ์„œ๋ฒ„์˜ ๋กœ๊ทธ ๋‚ด๋ถ€์— ์žˆ๋Š” INSERT ๋ฌธ์€ ์–ด๋– ํ•œ ๋‚ด์šฉ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋ณด๋‚ด๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.
  2. ์ฝ˜์†”์„ ์‹คํ–‰์‹œํ‚ค๊ณ , user ๋ณ€์ˆ˜์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์˜ ์ œ์ผ ์ฒซ ๋ฒˆ์งธ ์œ ์ €๋ฅผ ๋Œ€์ž…์‹œ์ผœ๋ด…์‹œ๋‹ค. ๊ทธ ๋‹ค์Œ, Micropost.where("user_id = ?", user.id) ์™€ user.microposts ๊ทธ๋ฆฌ๊ณ  user.feed ๋ฅผ ๊ฐ๊ฐ ์‹คํ–‰์‹œ์ผœ๋ณด๊ณ , ์‹คํ–‰๊ฒฐ๊ณผ๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ ๊ฒƒ์„ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. Hint : == ๋กœ ๋น„๊ตํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์€์ง€ ์•„๋‹Œ์ง€๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

13.3.4 Micropost๋ฅผ ์‚ญ์ œํ•ด๋ณด์ž

๋งˆ์ง€๋ง‰ ๊ธฐ๋Šฅ์œผ๋กœ๋Š” micropost resource์— post๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ด…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์œ ์ € ์‚ญ์ œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ (10.4.2) "delete" ๋งํฌ๋กœ ์‹คํ˜„ํ•ด๋ด…์‹œ๋‹ค. ์œ ์ €์˜ ์‚ญ์ œ๋Š” ๊ด€๋ฆฌ์ž ์œ ์ €๋งŒ ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•œ ๊ฒƒ์— ๋น„ํ•ด, ์ด๋ฒˆ์—๋Š” ์ž์‹ ์ด ์ž‘์„ฑํ•œ micropost์— ๋Œ€ํ•ด์„œ๋งŒ ์‚ญ์ œ๋งํฌ๋ฅผ ๋™์ž‘ํ•˜๋„๋ก ํ•ด๋ด…์‹œ๋‹ค.

์ œ์ผ ์ฒ˜์Œ์œผ๋กœ๋Š” micropost์˜ partial ์— ์‚ญ์ œ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…๋‹ˆ๋‹ค. ์ƒ์„ฑํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<!-- app/views/microposts/_micropost.html.erb -->
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <!-- add -->
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
    <!-- add -->
  </span>
</li>

๋‹ค์Œ์œผ๋กœ๋Š”, Microposts ์ปจํŠธ๋กค๋Ÿฌ์˜ destrory ์•ก์…˜์„ ์ •์˜ํ•ด๋ด…์‹œ๋‹ค. ์ด๊ฒƒ ๋˜ํ•œ ์œ ์ €์˜ ์‚ญ์ œ์™€ ๋Œ€๊ฐ• ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ํฐ ์ฐจ์ด๋กœ๋Š” admin_user ํ•„ํ„ฐ์—์„œ @user ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ micropost๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ ์–ด๋–ค ์œ ์ €๊ฐ€ ๋‹ค๋ฅธ ์œ ์ €์˜ micropost๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•ด๋„ ์ž๋™์ ์œผ๋กœ ์‹คํŒจํ•ด๋ฒ„๋ฆด ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” correct_user ํ•„ํ„ฐ ๋‚ด์—์„œ find ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ํ˜„์žฌ ์œ ์ €๊ฐ€ ์‚ญ์ œ๋Œ€์ƒ์˜ micropost๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์™„์„ฑ๋œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy #new
  .
  .
  .
   #new
  def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_to request.referrer || root_url
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
		#new
    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end

์ด ๋•Œ ์œ„ ์ฝ”๋“œ์˜ destory ๋ฉ”์†Œ๋“œ์—์„œ redirect๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.

request.referrer || root_url

์—ฌ๊ธฐ์„œ๋Š” request.referrer ๋ผ๊ณ  ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์†Œ๋“œ๋Š” Friendly forwarding์˜ request.url ๋ณ€์ˆ˜ (10.2.3) ์™€ ๋น„์Šทํ•˜๊ฒŒ, ๋ฐ”๋กœ ์ด์ „ URL์„ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. (์ด ๊ฒฝ์šฐ, HomeํŽ˜์ด์ง€๊ฐ€ ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.) ๋•Œ๋ฌธ์— micropost๊ฐ€ HomeํŽ˜์ด์ง€์—์„œ ์‚ญ์ œ๋œ ๊ฒฝ์šฐ์—๋„ ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋กœ๋ถ€ํ„ฐ ์‚ญ์ œ๋œ ๊ฒฝ์šฐ์—๋„, request.referrer ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ DELETE Request๊ฐ€ ์‹คํ–‰๋œ ํŽ˜์ด์ง€๋กœ ๋˜๋Œ์•„๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋‹ด์œผ๋กœ, ์ด์ „์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” URL์ด ์—†๋‹ค๊ณ  ํ•˜๋”๋ผ๋„, (์˜ˆ๋ฅผ๋“ค์–ด ํ…Œ์ŠคํŠธ์—์„œ๋Š” nil ์ด ๋ฆฌํ„ด๋˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.) ์œ„ ์ฝ”๋“œ์—์„œ || ์—ฐ์‚ฐ์ž์— ์˜ํ•˜์—ฌ root_url ์„ ๋””ํดํŠธ๋กœ ์„ค์ •ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค.

์œ„ ์ฝ”๋“œ๋กœ ์ธํ•˜์—ฌ ์œ„์—์„œ๋ถ€ํ„ฐ 2๋ฒˆ์žฌ micropost ๋ฅผ ์‚ญ์ œํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋™์ž‘ํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์—ฐ์Šต
  1. micropost๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ๊ทธ ๋‹ค์Œ ์ž‘์„ฑํ•œ micropost๋ฅผ ์‚ญ์ œํ•ด๋ด…์‹œ๋‹ค. ๋‹ค์Œ์œผ๋กœ Rails ์„œ๋ฒ„์˜ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•˜์—ฌ DELETE ๋ฌธ์˜ ๋‚ด์šฉ์„ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.
  2. redirect_to request.referrer || root_url์˜ ํ–‰์„ redirect_back(fallback_location: root_url) ์œผ๋กœ ๋ฐ”๊พธ์–ด๋„ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€, ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. (์ด ๋ฉ”์†Œ๋“œ๋Š” Rails 5๋ถ€ํ„ฐ ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.)

13.3.5 Feed ํ™”๋ฉด์˜ Micropost ๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž

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

์šฐ์„ ์€ micropost์šฉ์˜ fixture์— ์ œ๊ฐ๊ฐ์˜ ์œ ์ €์™€ ์—ฐ๊ฒฐ๋˜์–ด์ง„ micropost๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. (์ง€๊ธˆ์€ ์ด ์ค‘ 1๊ฐœ๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค๋งŒ ๋‚˜์ค‘์—๋Š” ๋‹ค๋ฅธ micropost๋„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.)

# test/fixtures/microposts.yml
.
.
.
ants:
  content: "Oh, is that what you want? Because that's how you get ants!"
  created_at: <%= 2.years.ago %>
  user: archer

zone:
  content: "Danger zone!"
  created_at: <%= 3.days.ago %>
  user: archer

tone:
  content: "I'm sorry. Your words made sense, but your sarcastic tone did not."
  created_at: <%= 10.minutes.ago %>
  user: lana

van:
  content: "Dude, this van's, like, rolling probable cause."
  created_at: <%= 4.hours.ago %>
  user: lana

๋‹ค์Œ์œผ๋กœ ์ž์‹  ์ด์™ธ์˜ ์œ ์ €์˜ micropost ๋Š” ์‚ญ์ œ๋ฅผ ํ•˜๋ คํ•˜๋ฉด ์ ์ ˆํ•˜๊ฒŒ redirectํ•˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ํ…Œ์ŠคํŠธ์—์„œ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

# test/controllers/microposts_controller_test.rb
require 'test_helper'

class MicropostsControllerTest < ActionDispatch::IntegrationTest

  def setup
    @micropost = microposts(:orange)
  end

  test "should redirect create when not logged in" do
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
    end
    assert_redirected_to login_url
  end

  test "should redirect destroy when not logged in" do
    assert_no_difference 'Micropost.count' do
      delete micropost_path(@micropost)
    end
    assert_redirected_to login_url
  end

  # new
  test "should redirect destroy for wrong micropost" do
    log_in_as(users(:michael))
    micropost = microposts(:ants)
    assert_no_difference 'Micropost.count' do
      delete micropost_path(micropost)
    end
    assert_redirected_to root_url
  end
end

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

$ rails generate integration_test microposts_interface
      invoke  test_unit
      create    test/integration/microposts_interface_test.rb

์•ž์˜ ์ˆœ์„œ๋Œ€๋กœ ์ž‘์„ฑํ•œ ํ†ตํ•ฉํ…Œ์ŠคํŠธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด์ „์— ์ž‘์„ฑํ•œ ์ฝ”๋“œ๊ฐ€ ์‚ด์ง ์„ž์—ฌ์žˆ๋Š” ์ ์„ ์ฃผ์˜ํ•ด์ฃผ์„ธ์š”.

# test/integration/microposts_interface_test.rb
require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    # ๋ฌดํšจํ•œ ์ž‘์„ฑ
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "" } }
    end
    assert_select 'div#error_explanation'
    # ์œ ํšจํ•œ ์ž‘์„ฑ
    content = "This micropost really ties the room together"
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost: { content: content } }
    end
    assert_redirected_to root_url
    follow_redirect!
    assert_match content, response.body
    # micropost๋ฅผ ์‚ญ์ œ
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # ๋‹ค๋ฅธ ์œ ์ €์˜ ํ”„๋กœํ•„๋กœ์˜ ์•ก์„ธ์Šค (์‚ญ์ œ๋งํฌ๊ฐ€ ์—†๋Š” ๊ฒƒ์„ ํ™•์ธ)
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end
end

์ด๋ฏธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฝ”๋“œ๋Š” ์ž‘์„ฑํ•ด๋†“์•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

$ rails test

์—ฐ์Šต
  1. ์œ„ ์ฝ”๋“œ์˜ 4๊ฐœ์˜ ์ฝ”๋ฉ˜ํŠธ์˜ ๊ฐ๊ฐ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ๋Œ€์‘ํ•˜๋Š” application ์˜ ์ฝ”๋“œ๋ฅผ ์ฝ”๋ฉ˜ํŠธ์•„์›ƒํ•˜๊ณ  ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ณ , ์›๋ž˜๋Œ€๋กœ ๋˜๋Œ๋ฆฐ ํ›„ ํ…Œ์ŠคํŠธ๊ฐ€ ๋‹ค์‹œ ํ†ต๊ณผ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

  2. ์‚ฌ์ด๋“œ ๋ฐ”์— ์žˆ๋Š” micropost์˜ ํ•ฉ๊ณ„ ์ž‘์„ฑ ๊ฐœ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ด…์‹œ๋‹ค. ์ด ๋•Œ ๋‹จ์ˆ˜ํ˜•(micropost) ์™€ ๋ณต์ˆ˜ํ˜• (microposts) ๊ฐ€ ์ œ๋Œ€๋กœ ํ‘œ์‹œ๋˜๋Š”์ง€๋„ ํ…Œ์ŠคํŠธํ•ด๋ด…์‹œ๋‹ค. Hint : ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค

    # test/integration/microposts_interface_test.rb
    require 'test_helper'
    
    class MicropostInterfaceTest < ActionDispatch::IntegrationTest
    
      def setup
        @user = users(:michael)
      end
      .
      .
      .
      # new
      test "micropost sidebar count" do
        log_in_as(@user)
        get root_path
        assert_match "#{FILL_IN} microposts", response.body
        # ์•„์ง micropost๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์œ ์ €
        other_user = users(:malory)
        log_in_as(other_user)
        get root_path
        assert_match "0 microposts", response.body
        other_user.microposts.create!(content: "A micropost")
        get root_path
        assert_match FILL_IN, response.body
      end
    end

13.4 Micropost์˜ image ์ฒจ๋ถ€

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

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” 2๊ฐœ์˜ ์‹œ๊ฐ์ ์ธ ์š”์†Œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•œ form, ๋˜ ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์ž‘์„ฑํ•œ ์ด๋ฏธ์ง€ ๊ทธ ์ž์ฒด์ž…๋‹ˆ๋‹ค. [Upload image] ๋ฒ„ํŠผ๊ณผ ์ด๋ฏธ์ง€๊ฐ€ ์ฒจ๋ถ€๋˜์–ด์žˆ๋Š” micropost์˜ ๋ชฉ์—…์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

13.4.1 ๊ธฐ๋ณธ์ ์ธ Image Upload

์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ๋‹ค๋ฃจ๊ฑฐ๋‚˜ ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ micropost ๋ชจ๋ธ๊ณผ ๊ด€๊ณ„๋งบ๊ธฐ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ, ์ด๋ฒˆ์—๋Š” CarrierWave ๋ผ๊ณ  ํ•˜๋Š” Image Uploader๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์šฐ์„  carrierwave gem ์„ Gemfile ์— ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค. ์ด ๋•Œ, ์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” mini_magick gem ๊ณผ fog gems ๋„ ๊ฐ™์ด ํฌํ•จ๋˜์–ด์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด gem๋“ค์€ Resizeํ•˜๊ฑฐ๋‚˜(13.4.3) ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. (13.4.4)

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2' #new
gem 'mini_magick',             '4.7.0' #new
gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'
.
.
.
group :production do
  gem 'pg',  '0.20.0'
  gem 'fog', '1.42' #new
  end
.
.
.

๋‹ค์Œ์œผ๋กœ๋Š” ์–ธ์ œ๋‚˜์ฒ˜๋Ÿผ bundle install ์„ ์‹คํ–‰ํ•ด๋ด…๋‹ˆ๋‹ค.

$ bundle install

CarrierWave๋ฅผ ๋„์ž…ํ•˜๋ฉด, Rails์˜ ์ œ๋„ค๋ ˆ์ดํ„ฐ๋กœ ์ด๋ฏธ์ง€ ์—…๋กœ๋”๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์ปค๋งจ๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ด…์‹œ๋‹ค. (์ด๋ฏธ์ง€๋ฅผ image๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ํ‰๋ฒ”ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, picture ๋ผ๊ณ  ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)

$ rails generate uploader Picture

CarrierWave๋กœ ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€๋Š”, Active Record ๋ชจ๋ธ์˜ ์†์„ฑ๊ณผ ๊ด€๊ณ„๋ฅผ ๋งบ์–ด์•ผํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๊ด€๊ณ„๋งบ์–ด์ง„ ์†์„ฑ์—๋Š” ์ด๋ฏธ์ง€์˜ ํŒŒ์ผ๋ช…์ด ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— Stringํ˜•์œผ๋กœ ํ•ด๋†“์Šต๋‹ˆ๋‹ค. ํ™•์žฅ๋œ micropost์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ picture ์†์„ฑ์„ Micropost๋ชจ๋ธ์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด, migration ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ , ๊ฐœ๋ฐœํ™˜๊ฒฝ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

$ rails generate migration add_picture_to_microposts picture:string
$ rails db:migrate

CarrierWave์— ์ด๋ฏธ์ง€์™€ ๊ด€๊ณ„์ง€์–ด๋†“์€ ๋ชจ๋ธ์„ ์ž…๋ ฅํ•˜๊ธฐ ์œ„ํ•ด์„ , mount_uploader ๋ผ๊ณ  ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์†Œ๋“œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์— ์†์„ฑ๋ช…์˜ ์‹ฌ๋ณผ๊ณผ ์ƒ์„ฑ๋˜์–ด์ง„ ์—…๋กœ๋”์˜ ํด๋ž˜์Šค์ด๋ฆ„์„ ์ทจํ•ฉ๋‹ˆ๋‹ค.

mount_uploader :picture, PictureUploader

(picture_uploader.rb ๋ผ๊ณ  ํ•˜๋Š” ํŒŒ์ผ์— PictureUploader ํด๋ž˜์Šค๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. 13.4.2์—์„œ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค๋งŒ, ์ง€๊ธˆ์€ ๋””ํดํŠธ์ธ ์ƒํƒœ๋กœ๋„ ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค. ) Micropost ๋ชจ๋ธ์— ์—…๋กœ๋”๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

# app/models/micropost.rb
# micropost๋ชจ๋ธ์— ์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader #new
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

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

Home ํŽ˜์ด์ง€์— ์—…๋กœ๋”๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” micropost์˜ form์— file_field ํƒœ๊ทธ๋ฅผ ํฌํ•จํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

<!-- app/views/shared/_micropost_form.html.erb -->
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    <%= f.file_field :picture %> <!-- new -->
  </span>
<% end %>

๋งˆ์ง€๋ง‰์œผ๋กœ๋Š” Web์œผ๋กœ๋ถ€ํ„ฐ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ํ—ˆ๊ฐ€ ๋ฆฌ์ŠคํŠธ์— picture ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด๋†“์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ํ•ด๋†“์œผ๋ฉด micropost_params ๋ฉ”์†Œ๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

# app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  private

    def micropost_params
      params.require(:micropost).permit(:content, :picture) # new
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end

ํ•œ ๋ฒˆ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด๋†“์œผ๋ฉด, Micropost partial์˜ image_tag ํ—ฌํผ๋กœ ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด๋ฏธ์ง€๊ฐ€ ์—†๋Š” (ํ…์ŠคํŠธ๋งŒ ์žˆ๋Š”) micropost์—์„œ๋Š” ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•˜์ง€์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด, picture? ๋ผ๊ณ  ํ•˜๋Š” ๋…ผ๋ฆฌ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”. ์ด ๋ฉ”์†Œ๋“œ๋Š” ์ด๋ฏธ์ง€์šฉ์˜ ์†์„ฑ๋ช…์— ๋”ฐ๋ผ, CarrierWave๊ฐ€ ์ž๋™์ ์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. ์ˆ˜๋™์œผ๋กœ ์ด๋ฏธ์ง€๊ฐ€ ํฌํ•จ๋œ micropost๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ฉด ์•„๋ž˜ ์บก์ณ์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค. ์—…๋กœ๋“œ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋Š” ์—ฐ์Šต๋ฌธ์ œ๋กœ ๋‚ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

<!-- app/views/microposts/_micropost.html.erb -->
<!-- micropost์˜ ์ด๋ฏธ์ง€ํ‘œ์‹œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. -->
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.picture.url if micropost.picture? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>

์—ฐ์Šต
  1. ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” micropost๋ฅผ ์ž‘์„ฑํ•ด๋ด…์‹œ๋‹ค. ํ˜น์‹œ๋‚˜ ๋„ˆ๋ฌด๋‚˜๋„ ํฐ ์ด๋ฏธ์ง€๋ฅผ ์ฒจ๋ถ€ํ•ด๋ฒ„๋ฆฌ์ง„ ์•Š์œผ์…จ๋‚˜์š”? (๊ฑฑ์ •ํ•˜์ง€๋งˆ์„ธ์š”. ์ด ๋ฌธ์ œ๋Š” ๋‹ค์Œ [13.4.3](#1343-image์˜- resize)์—์„œ ๋Œ€์‘ํ•ด๋ด…๋‹ˆ๋‹ค.)
  2. ์•„๋ž˜ ์ฝ”๋“œ์˜ ํ…œํ”Œ๋ฆฟ์„ ์ฐธ๊ณ ํ•˜์—ฌ, 13.4 ์—์„œ ๊ตฌํ˜„ํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋”๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด์„ธ์š”. ํ…Œ์ŠคํŠธ์˜ ์ค€๋น„๋กœ์„œ, ๋จผ์ € sample ์ด๋ฏธ์ง€๋ฅผ fixture๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”. (์ปค๋งจ๋“œ : cp app/assets/images/rails.png test/fixtures/) ์•„๋ž˜ ์ฝ”๋“œ์—์„œ ์ถ”๊ฐ€ํ•œ ํ…Œ์ŠคํŠธ์—์„œ๋Š”, Home ํŽ˜์ด์ง€์— ์žˆ๋Š” ํŒŒ์ผ ์—…๋กœ๋“œ์™€, ์ž‘์„ฑ์— ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ์ด๋ฏธ์ง€๊ฐ€ ํ‘œ์‹œ๋˜๋Š”์ง€๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ํ…Œ์ŠคํŠธ ๋‚ด๋ถ€์— ์žˆ๋Š” fixture_file_upload ๋ผ๊ณ  ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋Š”, fixture์—์„œ ์ •์˜๋œ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋Š” ํŠน๋ณ„ํ•œ ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. Hint : picture ์†์„ฑ์ด ์œ ํšจํ•œ์ง€ ์•„๋‹Œ์ง€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, 11.3.3 ์—์„œ ์†Œ๊ฐœํ•œ assigns ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”. ์ด ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž‘์„ฑ ์„ฑ๊ณตํ•œ ํ›„, create ์•ก์…˜ ๋‚ด์˜ micropost์— ์•ก์„ธ์Šคํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
# test/integration/microposts_interface_test.rb
# ์˜์ƒ์—…๋กœ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ํ…œํ”Œ๋ฆฟ
require 'test_helper'

class MicropostInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    assert_select 'input[type=FILL_IN]'
    # ๋ฌดํšจํ•œ ์†ก์‹ 
    post microposts_path, params: { micropost: { content: "" } }
    assert_select 'div#error_explanation'
    # ์œ ํšจํ•œ ์†ก์‹ 
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost:
                                      { content: content,
                                        picture: FILL_IN } }
    end
    assert FILL_IN.picture?
    follow_redirect!
    assert_match content, response.body
    # ์ž‘์„ฑํ•œ ๊ธ€์„ ์‚ญ์ œํ•œ๋‹ค.
    assert_select 'a', 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # ๋‹ค๋ฅธ ์œ ์ €์˜ ํ”„๋กœํ•„์— ์•ก์„ธ์Šคํ•ด๋ณธ๋‹ค.
    get user_path(users(:archer))
    assert_select 'a', { text: 'delete', count: 0 }
  end
  .
  .
  .
end

13.4.2 Image์˜ ๊ฒ€์ฆ

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

์ œ์ผ ์ฒซ validation์—์„œ๋Š” ์œ ํšจํ•œ ์ด๋ฏธ์ง€์˜ ์ข…๋ฅ˜๋ฅผ ์ œํ•œํ•ด๋ด…์‹œ๋‹ค. ์ด๊ฒƒ์€ CarrierWave์˜ ์—…๋กœ๋” ์•ˆ์— ํžŒํŠธ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์—…๋กœ๋”์˜ ์•ˆ์— ์ฃผ์„์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ฃผ์„์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ๋ฅผ ์›๋ž˜๋Œ€๋กœ ๋˜๋Œ๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ, ์ด๋ฏธ์ง€์˜ ํŒŒ์ผ๋ช…์œผ๋กœ๋ถ€ํ„ฐ ์œ ํšจํ•œ ํ™•์žฅ์ž (PNG/GIF/JPEG ๋“ฑ)์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

# app/uploaders/picture_uploader.rb
# Image format validation
class PictureUploader < CarrierWave::Uploader::Base
  storage :file

  # UploadํŒŒ์ผ์˜ ์ €์žฅํด๋”๋Š” ๋ฎ์–ด์“ฐ๊ธฐ ๊ฐ€๋Šฅ
  # ์•„๋ž˜๋Š” ๊ธฐ๋ณธ ์ €์žฅ ํด๋”
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # ์—…๋กœ๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•œ ํ™•์žฅ์ž์˜ ๋ฆฌ์ŠคํŠธ 
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

2๋ฒˆ์งธ valiadtion์—์„œ๋Š” ์ด๋ฏธ์ง€์˜ ์šฉ๋Ÿ‰์„ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ Micropost ๋ชจ๋ธ์— ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฐฉ๊ธˆ ์ „ validation๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ, ํŒŒ์ผ ์šฉ๋Ÿ‰์— ๋Œ€ํ•œ validation์€ Rails์˜ ๊ธฐ์กด ์˜ต์…˜ (presence๋‚˜ length ๋“ฑ) ์—๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฒˆ์—๋Š” ์ˆ˜๋™์œผ๋กœ picture_size ๋ผ๊ณ ํ•˜๋Š” ๋…์ž์ ์ธ validation์„ ์ •์˜ํ•ด๋ด…์‹œ๋‹ค. ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋…์ž์ ์ธ validation์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ง€๊ธˆ๊นŒ์ง€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋˜ validates ๋ฉ”์†Œ๋“œ๊ฐ€ ์•„๋‹Œ validate ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ ์„ ์ฃผ๋ชฉํ•ด์ฃผ์„ธ์š”.

# app/models/micropost.rb
# Add imageSize Validation
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
  validate  :picture_size #new

  private

    # ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ์šฉ๋Ÿ‰์„ validationํ•œ๋‹ค. 
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end

์ด validate ๋ฉ”์†Œ๋“œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‹ฌ๋ณผ (:picture_size) ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค. ์ด ์‹ฌ๋ณผ๋ช…์— ๋Œ€์‘ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ํ˜ธ์ถœ๋œ picture_size ๋ฉ”์†Œ๋“œ์—์„œ๋Š” 5MB๋ฅผ ์ƒํ•œ์œผ๋กœ ํ•˜์—ฌ (๋ฌธ๋ฒ•์€ ์ปฌ๋Ÿผ 9.1 ์„ ์ฐธ์กฐ) ํ•ด๋‹น ์šฉ๋Ÿ‰์„ ๋„˜์€ ๊ฒฝ์šฐ์—๋Š” ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•œ ์—๋Ÿฌ๋ฉ”์„ธ์ง€๋ฅผ errors ์ปฌ๋ ‰์…˜์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. (errors ์— ๋Œ€ํ•ด์„œ๋Š” 6.2.2 ์—์„œ ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค.)

์œ„ ๋‘ ๊ฐœ์˜ ์ฝ”๋“œ์—์„œ ์ •์˜ํ•œ ์ด๋ฏธ์ง€์˜ validation์„ view์—์„œ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด, ํด๋ผ์ด์–ธํŠธ ์ธก์— 2๊ฐœ์˜ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค. ์ผ๋‹จ์€ format์˜ validation์„ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” file_field ํƒœ๊ทธ์— accept ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>

์ด ๋•Œ acceptํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ๋Š” ์œ„ ์ฒซ ๋ฒˆ์งธ ์ฝ”๋“œ์—์„œ ํ—ˆ๊ฐ€ํ•œ ํŒŒ์ผ ํ˜•์‹์„, MIME ํƒ€์ž… ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ๋Š” ๋งค์šฐ ํฐ ํŒŒ์ผ ์‚ฌ์ด์ฆˆ์— ๋Œ€ํ•ด ๊ฒฝ๊ณ ๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•ด, ๊ฐ„๋‹จํ•œ JavaScript (์ •ํ™•ํžˆ๋Š” jQuery) ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋„ˆ๋ฌด ๊ธด ์—…๋กœ๋“œ ์‹œ๊ฐ„์„ ๋ง‰์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$('#micropost_picture').bind('change', function() {
  var size_in_megabytes = this.files[0].size/1024/1024;
  if (size_in_megabytes > 5) {
    alert('Maximum file size is 5MB. Please choose a smaller file.');
  }
});

jQuery๋Š” ๋ณธ ํŠœํ† ๋ฆฌ์–ผ์˜ ์ฃผ์ œ๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์„ธํ•œ ์„ค๋ช…์€ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค๋งŒ, ์œ„ ์ฝ”๋“œ์—์„œ๋Š” (ํ•ด์‹œ๋งˆํฌ # ์œผ๋กœ๋ถ€ํ„ฐ ์•Œ ์ˆ˜ ์žˆ๋“ฏ) CSS id micropost_picture ๋ฅผ ํฌํ•จํ•œ ์š”์†Œ๋ฅผ ์ฐพ์•„๋‚ด์–ด, ํ•ด๋‹น ์š”์†Œ๋ฅผ ๊ฐ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด id๋ฅผ ๊ฐ€์ง„ ์š”์†Œ๋Š” micropost form์„ ์ง€์นญํ•ฉ๋‹ˆ๋‹ค. (๋˜ํ•œ ๋ธŒ๋ผ์šฐ์ € ์ƒ์—์„œ๋Š” ํ™”๋ฉด์„ ์˜ค๋ฅธ์ชฝ ํด๋ฆญํ•˜์—ฌ inspector๋กœ ์š”์†Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.) ์ฆ‰, ์ด CSS id๋ฅผ ๊ฐ€์ง„ ์š”์†Œ๊ฐ€ ๋ณ€ํ™”ํ•˜์˜€์„๋•Œ, ์ด jQuery ํ•จ์ˆ˜๊ฐ€ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ํŒŒ์ผ ์‚ฌ์ด์ฆˆ๊ฐ€ ๋„ˆ๋ฌด ํฐ ๊ฒฝ์šฐ์—๋Š” alert ๋ฉ”์†Œ๋“œ๋กœ ๊ฒฝ๊ณ ๋ฅผ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ถ”๊ฐ€์ ์ธ ์ฒดํฌ๊ธฐ๋Šฅ์„ ์ •๋ฆฌํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

<!-- app/views/shared/_micropost_form.html.erb -->

<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
  </span>
<% end %>

<script type="text/javascript">
  $('#micropost_picture').bind('change', function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert('Maximum file size is 5MB. Please choose a smaller file.');
    }
  });
</script>

์ด๋ฏธ ๋ˆˆ์น˜์ฑˆ ๋…์ž๋ถ„๋“ค๋„ ์žˆ์„ ์ˆ˜๋„ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค๋งŒ, ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ์—์„œ๋Š” ํฐ ํŒŒ์ผ์˜ ์—…๋กœ๋“œ๋ฅผ ์™„์ „ํ•˜๊ฒŒ ๋ง‰์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์œ ์ €๋Š” alert๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์—…๋กœ๋“œ๋ฅผ ๊ฐ•ํ–‰ํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ณธ ํŠœํ† ๋ฆฌ์–ผ์ด jQuery ํŠœํ† ๋ฆฌ์–ผ์ด์—ˆ๋‹ค๋ฉด ์ด ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ์ฑ…์„ ์ƒ๋ƒฅํ•˜๊ฒŒ ์„ค๋ช…ํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค๋งŒ, ๋ณธ ํŠœํ† ๋ฆฌ์–ผ์€ Rails ํŠœํ† ๋ฆฌ์–ผ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒˆ์—๋Š” "์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ์œผ๋กœ๋Š” ์•„์ง ๊ตฌํ˜„์ด ๋ถˆ์™„์ „ํ•˜๋‹ค" ๋ผ๋Š” ์ ์„ ์•Œ์•„๋†“๋Š”๋‹ค๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๋งŒ์•ฝ ์†ก์‹  form์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์„ฑ์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹คํ•˜์—ฌ๋„, ๋ธŒ๋ผ์šฐ์ €์˜ inspector ๊ธฐ๋Šฅ์œผ๋กœ JavaScript๋ฅผ ๋œฏ์–ด๊ณ ์นœ๋‹ค๊ฑฐ๋‚˜ curl ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ POST ๋ฆฌํ€˜์ŠคํŠธ๋ฅผ ์†ก์‹ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋Œ€์‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—๋„ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด, ์„œ๋ฒ„์ธก validation์€ ์ค‘์š”ํ•œ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์—ฐ์Šต
  1. 5MB์ด์ƒ์˜ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๋“ฑ๋กํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์–ด๋–ป๊ฒŒ ๋ฉ๋‹ˆ๊นŒ?
  2. ๋ฌดํšจํ•œ ํ™•์žฅ์ž์˜ ํŒŒ์ผ์„ ๋“ฑ๋กํ•˜๊ณ ์žํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์–ด๋–ป๊ฒŒ ๋ฉ๋‹ˆ๊นŒ?

13.4.3 Image์˜ Resize

ํŒŒ์ผ์‚ฌ์ด์ฆˆ์— ๋Œ€ํ•œ Validation (13.4.2) ์€ ์ œ๋Œ€๋กœ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ (๊ฐ€๋กœ์„ธ๋กœ์˜ ๊ธธ์ด)์— ๋Œ€ํ•œ ์ œํ•œ์€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํฐ ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ๊ฐ€ ์—…๋กœ๋“œ ๋˜์–ด์ง€๋ฉด ์•„๋ž˜ ์บก์ณ์™€๊ฐ™์ด ๋ ˆ์ด์•„์›ƒ์ด ๊นจ์ ธ๋ฒ„๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ ๋Š” ํ•ด๋„, ์œ ์ €์—๊ฒŒ ์œ ์ €๊ฐ€ ๊ฐ€์ง„ ์ด๋ฏธ์ง€์˜ ์‚ฌ์ด์ฆˆ๋ฅผ ์ˆ˜์ •ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํŽธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์ „์— ์‚ฌ์ด์ฆˆ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” (Resize) ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•ด๋ด…์‹œ๋‹ค.

์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ฆˆํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ImageMagick ๋ผ๊ณ  ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๊ฒƒ์„ ๊ฐœ๋ฐœํ™˜๊ฒฝ์— ์„ค์น˜ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (13.4.4์—์„œ๋„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค๋งŒ, ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์ด Heroku๋ผ๋ฉด, ์ด๋ฏธ ๋ฐฐํฌํ™˜๊ฒฝ์— ImageMagick์„ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ์ค€๋น„๋˜์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.) Cloud IDE์˜ ๊ฒฝ์šฐ, ๋‹ค์Œ ์ปค๋งจ๋“œ๋กœ ์ด ํ”„๋กœ๊ทธ๋žจ์„ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

sudo yum install -y ImageMagick

(ํ˜น์—ฌ๋‚˜ ๋กœ์ปฌํ™˜๊ฒฝ์—์„œ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š”, ๊ฐ๊ฐ์˜ ํ™˜๊ฒฝ์— ๋งž์ถ”์–ด ImageMagick๋ฅผ ์„ค์น˜ํ•˜๋Š” ์ˆœ์„œ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Mac์ด๋ผ๋ฉด, Homebrew๋ฅผ ๋„์ž…ํ•˜์—ฌ brew install imagemagick ์ปค๋งจ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์„ค์น˜๊ฐ€ ์ œ๋Œ€๋กœ ์•ˆ๋œ๋‹ค๋ฉด, ์ปฌ๋Ÿผ1.1๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.)

๋‹ค์Œ์œผ๋กœ, MiniMagick ์ด๋ผ๊ณ  ํ•˜๋Š”, ImageMagick๊ณผ Ruby๋ฅผ ์ด์–ด์ฃผ๋Š” gem์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ฆˆํ•ด๋ด…์‹œ๋‹ค. MiniMagick์˜ Document ๋ฅผ ํ™•์ธํ•˜๋ฉด, ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฆฌ์‚ฌ์ด์ฆˆํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๋ฒˆ์—๋Š” resize_to_limit: [400, 400] ๋ผ๊ณ  ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด๋ด…๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ๊ฐ€๋กœ์„ธ๋กœ ์–ด๋А์ชฝ์ด๋˜ 400px๋ฅผ ์ดˆ๊ณผํ•  ๊ฒฝ์šฐ, ์ ์ ˆํ•œ ์‚ฌ์ด์ฆˆ๋กœ ์ถ•์†Œํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค. (๋‹จ, ์ž‘์€ ์ด๋ฏธ์ง€๋ผ๊ณ  ํ•ด๋„ ํ™•๋Œ€ํ•˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค.) ์—ฌ๋‹ด์œผ๋กœ, CarrierWave์˜ MiniMagick ํ•ญ๋ชฉ ์„ ํ™•์ธํ•ด๋ณด๋ฉด, ๋„ˆ๋ฌด๋‚˜๋„ ์ž‘์€ ์ด๋ฏธ์ง€๋ฅผ ์–ต์ง€๋กœ ๋Š˜๋ฆฌ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๋ฒˆ์—๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์ข…์ ์ธ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ ์ปค๋‹ค๋ž€ ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ๋ผ๋„ ์ ์ ˆํ•˜๊ฒŒ ๋ฆฌ์‚ฌ์ด์ฆˆ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

# app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick #new
  process resize_to_limit: [400, 400] #new

  storage :file

  # ์—…๋กœ๋“œํŒŒ์ผ์˜ ์ €์žฅํด๋”๋Š” ๋ฎ์–ด์“ฐ๊ธฐ ๊ฐ€๋Šฅ
  # ์•„๋ž˜๋Š” ๊ธฐ๋ณธ ์ €์žฅ ํด๋”
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # ์—…๋กœ๋“œ๊ฐ€๋Šฅํ•œ ํ™•์žฅ์ž ๋ฆฌ์ŠคํŠธ
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

์—ฐ์Šต
  1. ํ•ด์ƒ๋„๊ฐ€ ๋†’์€ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ , ๋ฆฌ์‚ฌ์ด์ฆˆ ๋˜์–ด์ง€๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. ์ด๋ฏธ์ง€๊ฐ€ ์ง์‚ฌ๊ฐํ˜• ์ผ ๊ฒฝ์šฐ, ๋ฆฌ์‚ฌ์ด์ฆˆ๋Š” ์ œ๋Œ€๋กœ ์ž˜ ๋ฉ๋‹ˆ๊นŒ?
  2. ์ด๋ฏธ ์ด์ „์— ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ, ์ด ์‹œ์ ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์—๋Ÿฌ๋ฉ”์„ธ์ง€๊ฐ€ ํ‘œ์‹œ๋  ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์ด ์—๋Ÿฌ๋ฅผ ์—†์• ๋ด…์‹œ๋‹ค. Hint : ์•„๋ž˜ ์„ค์ • ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์‹œ์—๋Š” CarrierWave์— ์ด๋ฏธ์ง€์˜ ๋ฆฌ์‚ฌ์ด์ฆˆ๋ฅผ ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š๋„๋ก ํ•ด๋ด…์‹œ๋‹ค.
# config/initializers/skip_image_resizing.rb
if Rails.env.test?
  CarrierWave.configure do |config|
    config.enable_processing = false
  end
end

13.4.4 ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ์˜ Image Upload

(์ด ์„น์…˜์€ Skip ํ•˜์—ฌ๋„ ๋ฉ๋‹ˆ๋‹ค. ์ œ๋Œ€๋กœ ๋˜์ง€์•Š๋Š”๋‹ค๋ฉด Skipํ•ด๋„ ๋ฌธ์ œ์—†์Šต๋‹ˆ๋‹ค.)

13.4.3 ์—์„œ ๊ตฌํ˜„ํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋”๋Š” ๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ ๋™์ž‘์‹œํ‚ค๊ธฐ์—๋Š” ๋ฌธ์ œ์—†์Šต๋‹ˆ๋‹ค๋งŒ, ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—๋Š” ์ ์ ˆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ picture_uploader.rb์˜ storage :file ์ด๋ผ๊ณ ํ•˜๋Š” ์ฝ”๋“œ์— ์˜ํ•ด์„œ, ๋กœ์ปฌ ํŒŒ์ผ์‹œ์Šคํ…œ์— ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋„๋ก ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ๋Š” ํŒŒ์ผ ์‹œ์Šคํ…œ์ด ์•„๋‹Œ Cloud Storage ์„œ๋น„์Šค์— ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋„๋ก ํ•ด๋ด…์‹œ๋‹ค.

์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ cloud storage์— ์ €์žฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ fog gem ์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  # new
  if Rails.env.production?
    storage :fog
  else
    storage :file
  end

  # ์—…๋กœ๋“œ ํŒŒ์ผ์˜ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ๋Š” ๋ฎ์–ด์“ฐ๊ธฐ ๊ฐ€๋Šฅ
  # ์•„๋ž˜๋Š” ๊ธฐ๋ณธ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # ์—…๋กœ๋“œ ๊ฐ€๋Šฅํ•œ ํ™•์žฅ์ž์˜ ๋ฆฌ์ŠคํŠธ 
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

์œ„ ์ฝ”๋“œ์—์„œ production? ์ด๋ผ๊ณ  ํ•˜๋Š” ๋…ผ๋ฆฌ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์†Œ๋“œ๋Š” ์ปฌ๋Ÿผ 7.1์—์„œ๋„ ์†Œ๊ฐœํ–ˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ™˜๊ฒฝ๋ณ„๋กœ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

if Rails.env.production?
  storage :fog
else
  storage :file
end

์š”์ฆ˜ ์‹œ๋Œ€์—๋Š” ๋งŽ์€ cloud storage ์„œ๋น„์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์ด๋ฒˆ์—๋Š” ์œ ๋ช…ํ•˜๊ณ  ์‹ ๋ขฐ์„ฑ์ด ๋†’์€ Amazon์˜ Simple Storage Service (S3) ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์„ค์น˜ ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. Amazon Web Services ์˜ ๊ณ„์ •์„ ์ƒ์„ฑํ•œ๋‹ค.
  2. WS Identity and Access Management (IAM) ์—์„œ ์œ ์ €๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Access key ์™€ Secret key๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฉ”๋ชจํ•ด๋†“๋Š”๋‹ค.
  3. AWS Console ์—์„œ S3 bucket์„ ์ƒ์„ฑํ•˜๊ณ , ( bucket ์ด๋ฆ„์€ ์•„๋ฌด๊ฑฐ๋‚˜ ์ƒ๊ด€ ์—†์Šต๋‹ˆ๋‹ค. ) 2์—์„œ ์ƒ์„ฑํ•œ ์œ ์ €์—๊ฒŒ Read๊ถŒํ•œ๊ณผ Write ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค.

(S3์˜ ์„ค์น˜๋Š” ๊ฝค๋‚˜ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ์ปฌ๋Ÿผ1.1๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์•Œ์•„๋ด…์‹œ๋‹ค.)

์ข€ ๋” ์ž์„ธํ•˜๊ฒŒ ์•Œ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” S3์˜ Document ๋ฅผ ์ฝ์–ด๋ณด๊ฑฐ๋‚˜, Google์ด๋‚˜ Stack Overflow์—์„œ ๊ฒ€์ƒ‰ํ•ด๋ด…์‹œ๋‹ค.

S3 Account์˜ ์ƒ์„ฑ๊ณผ ์„ค์ •์ด ๋๋‚ฌ๋‹ค๋ฉด, CarrierWave์˜ ์„ค์ •ํŒŒ์ผ์„ ๋‹ค์Œ ์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ด๋ด…์‹œ๋‹ค.

# config/initializers/carrier_wave.rb
if Rails.env.production?
  CarrierWave.configure do |config|
    config.fog_credentials = {
      # Amazon S3์˜ ์„ค์ •
      :provider              => 'AWS',
      :region                => ENV['S3_REGION'],     # ไพ‹: 'ap-northeast-1'
      :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
      :aws_secret_access_key => ENV['S3_SECRET_KEY']
    }
    config.fog_directory     =  ENV['S3_BUCKET']
  end
end

์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์˜ ๋ฉ”์ผ์„ค์ •๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์œ„ ์ฝ”๋“œ์—์„œ๋Š” Heroku์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ENV ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋ฐ€์ •๋ณด๊ฐ€ ๋ˆ„์„ค๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 11.4๋‚˜ 12.4์—์„œ๋Š” SendGrid์˜ AddOn์ด ์ด๋Ÿฌํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ž๋™์ ์œผ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ์œผ๋‚˜, ์ด๋ฒˆ์—๋Š” ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. heroku config:set ์ปค๋งจ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด Heroku ์ƒ์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.

$ heroku config:set S3_ACCESS_KEY="ใ‚ณใ‚ณใซๅ…ˆใปใฉใƒกใƒขใ—ใŸAccessใ‚ญใƒผใ‚’ๅ…ฅๅŠ›"
$ heroku config:set S3_SECRET_KEY="ๅŒๆง˜ใซใ€Secretใ‚ญใƒผใ‚’ๅ…ฅๅŠ›"
$ heroku config:set S3_BUCKET="Bucketใฎๅๅ‰ใ‚’ๅ…ฅๅŠ›"
$ heroku config:set S3_REGION="Regionใฎๅๅ‰ใ‚’ๅ…ฅๅŠ›"

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

// .gitignore

# ์—…๋กœ๋“œ๋œ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€๋ฅผ ๋ฌด์‹œํ•œ๋‹ค.
/public/uploads

๊ทธ๋ ‡๋‹ค๋ฉด ์ง€๊ธˆ๊นŒ์ง€์˜ ๋ณ€๊ฒฝ์„ Topic branch์— commit ํ•˜๊ณ , master branch์— mergeํ•ด๋ด…์‹œ๋‹ค.

$ rails test
$ git add -A
$ git commit -m "Add user microposts"
$ git checkout master
$ git merge user-microposts
$ git push

๋‹ค์Œ์œผ๋กœ, Heroku์— Deploy, Database์˜ Reset, sample data์˜ ์ƒ์„ฑ์„ ์ฐจ๋ก€๋Œ€๋กœ ์‹คํ–‰ํ•ด๋ด…์‹œ๋‹ค.

$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed

Heroku์—๋Š” ์ด๋ฏธ ImageMagick ์ด ์„ค์น˜๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, (์„ค์ •์ด ์ œ๋Œ€๋กœ ๋˜์–ด์žˆ๋‹ค๋ฉด,) ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ๋‚˜ ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ์˜ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋Š” ์„ฑ๊ณตํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์—ฐ์Šต
  1. ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ ํ•ด์ƒ๋„๊ฐ€ ๋†’์€ ์˜์ƒ์„ ์—…๋กœ๋“œํ•˜๊ณ , ์ ์ ˆํ•˜๊ฒŒ ๋ฆฌ์‚ฌ์ด์ฆˆ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค. ์ง์‚ฌ๊ฐํ˜•์˜ ์ด๋ฏธ์ง€์—ฌ๋„ ์ ์ ˆํ•˜๊ฒŒ ๋ฆฌ์‚ฌ์ด์ฆˆ๋ฉ๋‹ˆ๊นŒ?

13.5 ๋งˆ์ง€๋ง‰์œผ๋กœ

Micropost ๋ฆฌ์†Œ์Šค์˜ ์ถ”๊ฐ€๋กœ ์ธํ•˜์—ฌ, Sample Application์€ ๊ฑฐ์˜ ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‚จ์€ ๋‚ด์šฉ์œผ๋กœ๋Š” ์œ ์ €๋ฅผ ์„œ๋กœ ํŒ”๋กœ์šฐํ•  ์ˆ˜ ์žˆ๋Š” Social๊ธฐ๋Šฅ์˜ ์ถ”๊ฐ€์ž…๋‹ˆ๋‹ค. ์ œ 14์žฅ์—์„œ๋Š” ๊ทธ๋Ÿฌํ•œ ์œ ์ €๊ฐ„์˜ ๊ด€๊ณ„ (Relationship) ์„ ๋ชจ๋ธ๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋ฐฐ์šฐ๊ณ , Micropost์˜ ํ”ผ๋“œ์— ์–ด๋–ป๊ฒŒ ๊ด€๊ณ„๋˜๋Š”์ง€ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

๋งŒ์•ฝ 13.4.4 ๋ฅผ ์Šคํ‚ตํ•˜์˜€๋‹ค๋ฉด, ์—ฌ๊ธฐ์„œ ์ง€๊ธˆ๊นŒ์ง€์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ปค๋ฐ‹ํ•˜๊ณ  mergeํ•ด๋ด…์‹œ๋‹ค.

$ rails test
$ git add -A
$ git commit -m "Add user microposts"
$ git checkout master
$ git merge user-microposts
$ git push

์ค€๋น„๋˜์—ˆ๋‹ค๋ฉด ์‹ค์ œ ๋ฐฐํฌํ™˜๊ฒฝ์œผ๋กœ ๋ฐฐํฌํ•ด๋ด…์‹œ๋‹ค.

$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed

๋˜ํ•œ, ํ•„์š”ํ•œ gem์˜ ์„ค์น˜๋Š” ์ด๊ฒƒ์œผ๋กœ ๋๋‚ฌ์Šต๋‹ˆ๋‹ค. ์ดํ›„ ์ƒˆ๋กœ์šด gem์„ ์ถ”๊ฐ€ํ•  ์ผ์€ ์—†์Šต๋‹ˆ๋‹ค. ์ œ์ผ ๋งˆ์ง€๋ง‰์ƒํƒœ์˜ Gemfile ์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
gem 'bootstrap-sass',          '3.3.7'
gem 'puma',                    '3.9.1'
gem 'sass-rails',              '5.0.6'
gem 'uglifier',                '3.2.0'
gem 'coffee-rails',            '4.2.2'
gem 'jquery-rails',            '4.3.1'
gem 'turbolinks',              '5.0.1'
gem 'jbuilder',                '2.7.0'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

group :development do
  gem 'web-console',           '3.5.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.0.2'
  gem 'spring-watcher-listen', '2.0.1'
end

group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'
  gem 'guard',                    '2.14.1'
  gem 'guard-minitest',           '2.4.6'
end

group :production do
  gem 'pg',   '0.20.0'
  gem 'fog',  '1.42'
end

# Windowsํ™˜๊ฒฝ์—์„œ๋Š” ์•„๋ž˜ gem์„ ํฌํ•จ์‹œํ‚ฌ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

13.5.1 13์žฅ์˜ ์ •๋ฆฌ

  • Active Record ๋ชจ๋ธ๋•๋ถ„์—, Micropost๋„ ์œ ์ €์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Resource๋กœ์จ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค.
  • Rails๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ Key Index๋ฅผ ์ง€์›ํ•œ๋‹ค.
  • User๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ Micropost๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ (has_many) Micropost๋Š” 1๋ช…์˜ User์—๊ฒŒ ์˜์กดํ•˜๋Š” (belongs_to) ๊ด€๊ณ„๋ฅผ ๋ชจ๋ธํ™”ํ•œ๋‹ค.
  • has_many ๋‚˜ belongs_to ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ๊ด€๊ณ„๋งบ๊ธฐ๋ฅผ ํ†ตํ•œ ๋งŽ์€ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • user.microposts.build(...) ๋ผ๊ณ  ํ•˜๋Š” ์ฝ”๋“œ๋Š”, ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์–ด์ง„ ์œ ์ €์—๊ฒŒ ๊ด€๊ณ„๋œ micropost๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.
  • default_scope ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๋ณธ์˜ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.
  • default_scope ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋ฌด๋ช…ํ•จ์ˆ˜(->) ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.
  • dependent: :destroy ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด, ๊ด€๊ณ„๋œ ์˜ค๋ธŒ์ ํŠธ์™€ ์ž๊ธฐ์ž์‹ ์„ ๋™์‹œ์— ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • paginate ๋ฉ”์†Œ๋“œ๋‚˜ count ๋ฉ”์†Œ๋“œ๋Š”, ์–ด๋А์ชฝ์ด๋˜ ๊ด€๊ณ„๋งบ๊ธฐ ํ–‰์œ„๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋˜๊ณ , ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ์˜ ์กฐํšŒ๋ฅผ ์‹คํ–‰ํ•˜๋‚Ÿ.
  • fixture๋Š” ๊ด€๊ณ„๋งบ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•œ Object์˜ ์ƒ์„ฑ๋„ ์ง€์›ํ•œ๋‹ค.
  • Partial์„ ํ˜ธ์ถœํ•  ๋•Œ, ๊ฐ™์ด ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค.
  • where ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, Active Record๋ฅผ ํ†ตํ•˜์—ฌ ์„ ํƒ (๋ถ€๋ถ„์ง‘ํ•ฉ์„ ์ทจํ•˜๋Š” ๊ฒƒ)ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์˜์กดํ•˜๊ณ  ์žˆ๋Š” Object๋ฅผ ์ƒ์„ฑ/์‚ญ์ œํ•  ๋•Œ๋Š”, ํ•ญ์ƒ ๊ด€๊ณ„๋ฅผ ํ†ตํ•˜์—ฌ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ๋ณด๋‹ค ๋” ์•ˆ์ „ํ•œ ์กฐ์ž‘์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • CarrierWave๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜์ƒ ์—…๋กœ๋“œ๋‚˜ ์˜์ƒ ๋ฆฌ์‚ฌ์ด์ฆˆ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.