ขอต้อนรับกลับสู่ภาคที่สามของซีรีส์ Ruby on Rails Patterns และ Anti-Patterns ในโพสต์ที่แล้ว เราได้กล่าวถึงรูปแบบและรูปแบบการต่อต้านโดยทั่วไป เช่นเดียวกับที่เกี่ยวข้องกับ Rails Models ในโพสต์นี้ เราจะพูดถึงรูปแบบและรูปแบบการต่อต้านที่เกี่ยวข้องกับมุมมอง Rails
มุมมอง Rails ในบางครั้งสามารถทำงานได้อย่างสมบูรณ์และรวดเร็ว และในบางครั้ง มุมมองเหล่านี้อาจมีปัญหาได้ทุกประเภท หากคุณต้องการเพิ่มความมั่นใจในการจัดการความคิดเห็นของคุณหรือเพียงแค่ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับหัวข้อนี้ โพสต์บล็อกนี้มีไว้สำหรับ คุณ. มาดำดิ่งกันเลย
อย่างที่คุณอาจทราบ เฟรมเวิร์กของ Rails เป็นไปตามแบบแผนมากกว่าการกำหนดค่า และเนื่องจาก Rails มีขนาดใหญ่ในรูปแบบ Model-View-Controller (MVC) คำขวัญจึงใช้กับโค้ด View ด้วยเช่นกัน ซึ่งรวมถึงมาร์กอัปของคุณ (ไฟล์ ERB หรือ Slim), ไฟล์ JavaScript และ CSS เมื่อมองแวบแรก คุณอาจคิดว่าเลเยอร์ "มุมมอง" ค่อนข้างตรงไปตรงมาและใช้งานง่าย แต่โปรดทราบว่าทุกวันนี้ มีเทคโนโลยีหลายอย่างผสมอยู่ในเลเยอร์ "มุมมอง"
เราใช้ JavaScript, HTML และ CSS ในมุมมอง ทั้งสามนี้สามารถนำไปสู่ความสับสนและความระส่ำระสายของรหัส นำไปสู่การนำไปใช้ที่ไม่สมเหตุสมผลในระยะยาว โชคดีที่วันนี้เราจะพูดถึงปัญหาทั่วไปและวิธีแก้ปัญหาด้วยเลเยอร์ Rails View
มุมมอง Powerlifting
นี่เป็นความผิดพลาดที่ไม่ได้เกิดขึ้นบ่อยนัก แต่เมื่อเกิดขึ้นแล้ว กลับรู้สึกแย่ บางครั้ง ผู้คนมักจะใส่ตรรกะของโดเมนหรือสอบถามโดยตรงในมุมมอง สิ่งนี้ทำให้เลเยอร์ View ทำการยกของหนักหรือยกกำลัง สิ่งที่น่าสนใจคือ Rails ช่วยให้สิ่งนี้เกิดขึ้นได้ง่ายจริง ๆ ไม่มี 'เครือข่ายความปลอดภัย' เมื่อพูดถึงเรื่องนี้ คุณสามารถทำอะไรก็ได้ที่คุณต้องการในเลเยอร์มุมมอง
ตามคำจำกัดความ เลเยอร์มุมมองของรูปแบบ MVC ควรมีการนำเสนอตรรกะ ไม่ควรกังวลกับตรรกะของโดเมนหรือกับข้อมูลการสืบค้น ใน Rails คุณจะได้รับไฟล์ ERB (Embedded Ruby) ที่ให้คุณเขียนโค้ด Ruby ที่จะถูกประเมินเป็น HTML หากเราพิจารณาตัวอย่างเว็บไซต์ที่แสดงรายการเพลงในหน้าดัชนี ตรรกะการดูจะอยู่ใน app/views/songs/index.html.erb
.
เพื่อแสดงให้เห็นว่า "powerlifting" หมายถึงอะไรและไม่ควรทำอะไร มาดูตัวอย่างต่อไปนี้:
# app/views/songs/index.html.erb
<div class="songs">
<% Song.where(published: true).order(:title) do |song| %>
<section id="song_<%= song.id %>">
<span><%= song.title %></span>
<span><%= song.description %></span>
<a href="<%= song.download_url %>">Download</a>
</section>
<% end %>
</div>
รูปแบบต่อต้านขนาดใหญ่ที่นี่คือการดึงเพลงในมาร์กอัป ความรับผิดชอบในการดึงข้อมูลควรได้รับมอบหมายให้ควบคุมหรือบริการที่ถูกเรียกจากตัวควบคุม บางครั้งฉันเห็นผู้คนเตรียมข้อมูลบางอย่างในคอนโทรลเลอร์และดึงข้อมูลเพิ่มเติมในมุมมองในภายหลัง นี่เป็นการออกแบบที่ไม่ดีและทำให้เว็บไซต์ของคุณช้าลงเนื่องจากคุณกำลังกดดันฐานข้อมูลของคุณด้วยข้อความค้นหาบ่อยขึ้น
สิ่งที่คุณควรทำแทนคือเปิด @songs
ตัวแปรอินสแตนซ์จากการกระทำของตัวควบคุมและเรียกสิ่งนั้นในมาร์กอัป เช่น:
class SongsController < ApplicationController
...
def index
@songs = Song.all.where(published: true).order(:title)
end
...
end
# app/views/songs/index.html.erb
<div class="songs">
<% @songs.each do |song| %>
<section id="song_<%= song.id %>">
<span><%= song.title %></span>
<span><%= song.description %></span>
<a href="<%= song.download_url %>">Download</a>
</section>
<% end %>
</div>
ตัวอย่างเหล่านี้ยังห่างไกลจากความสมบูรณ์แบบ ถ้าคุณต้องการให้โค้ดคอนโทรลเลอร์ของคุณอ่านง่ายขึ้นและหลีกเลี่ยง SQL Pasta เราขอแนะนำให้คุณดูบล็อกโพสต์ก่อนหน้านี้ นอกจากนี้ การละทิ้งตรรกะในเลเยอร์มุมมองจะเพิ่มโอกาสที่ผู้อื่นจะพยายามสร้างโซลูชันของตนขึ้นมา
ใช้ประโยชน์จากสิ่งที่รางให้คุณ
เราจะทำให้มันสั้นที่นี่ Ruby on Rails เป็นเฟรมเวิร์กมาพร้อมกับตัวช่วยที่เรียบร้อยมากมาย โดยเฉพาะอย่างยิ่งในมุมมอง ตัวช่วยเล็ก ๆ น้อย ๆ ที่ดีเหล่านี้ช่วยให้คุณสร้างเลเยอร์มุมมองของคุณได้อย่างรวดเร็วและง่ายดาย ในฐานะผู้ใช้ Rails มือใหม่ คุณอาจถูกล่อลวงให้เขียน HTML แบบเต็มในไฟล์ ERb ของคุณดังนี้:
# app/views/songs/new.html.erb
<form action="/songs" method="post">
<div class="field">
<label for="song_title">Title</label>
<input type="text" name="song[title]" id="song_title">
</div>
<div class="field">
<label for="song_description">Description</label>
<textarea name="song[description]" id="song_description"></textarea>
</div>
<div class="field">
<label for="song_download_url">Download URL</label>
<textarea name="song[download_url]" id="song_download_url"></textarea>
</div>
<input type="submit" name="commit" value="Create Song">
</form>
ด้วย HTML นี้ คุณควรได้รูปแบบที่ดีสำหรับเพลงใหม่ดังที่แสดงในภาพหน้าจอด้านล่าง:
แต่สำหรับ Rails คุณไม่จำเป็นต้องใช้ และคุณไม่ควรเขียน HTML ธรรมดาๆ แบบนั้น เนื่องจาก Rails ช่วยคุณได้ คุณสามารถใช้ form_with
ดูผู้ช่วยที่จะสร้าง HTML ให้กับคุณ form_with
ถูกนำมาใช้ใน Rails 5.1 และมีการแทนที่ form_tag
และ form_for
ที่ใครๆ ก็คุ้นเคยกันดี มาดูกันว่า form_with
. เป็นอย่างไร สามารถบรรเทาเราจากการเขียนโค้ดเพิ่มเติม:
<%= form_with(model: song, local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div class="field">
<%= form.label :description %>
<%= form.text_area :description %>
</div>
<div class="field">
<%= form.label :download_url do %>
Download URL
<% end %>
<%= form.text_area :download_url %>
</div>
<%= form.submit %>
<% end %>
นอกจากจะสร้าง HTML ให้เราแล้ว form_with
ยังสร้าง Authenticitytoken ที่ป้องกันการโจมตี CSRF ดังนั้นในเกือบทุกกรณี คุณควรใช้ตัวช่วยที่กำหนดไว้ดีกว่าเพราะพวกเขาอาจทำงานได้ดีกับเฟรมเวิร์กของ Rails หากคุณพยายามส่งแบบฟอร์ม HTML ธรรมดา มันจะล้มเหลวเนื่องจากมีการส่งโทเค็นความถูกต้องของ novalid พร้อมกับคำขอ
นอกจาก form_with
, label
, text_area
และ submit
ผู้ช่วยเหลือ มีผู้ช่วยมุมมองเหล่านี้อีกมากมายที่ออกมาพร้อมกับ Rails พวกเขาอยู่ที่นั่นเพื่อทำให้ชีวิตของคุณง่ายขึ้นและคุณควรทำความรู้จักพวกเขาให้ดีขึ้น หนึ่งใน "ดาวเด่น" แน่นอน link_to
:
<%= link_to "Songs", songs_path %>
ซึ่งจะสร้าง HTML ต่อไปนี้:
<a href="/songs">Songs</a>
ฉันจะไม่ลงรายละเอียดเกี่ยวกับผู้ช่วยแต่ละคนมากนัก เนื่องจากโพสต์นี้จะยาวเกินไปและการดำเนินการทั้งหมดนั้นไม่ได้เป็นส่วนหนึ่งของหัวข้อของวันนี้ ฉันแนะนำให้คุณแนะนำ gothroughRails Action View และเลือกสิ่งที่คุณต้องการสำหรับเว็บไซต์ของคุณ
ใช้ซ้ำและจัดระเบียบรหัสดู
มาลองจินตนาการถึงเว็บแอปพลิเคชันที่สมบูรณ์แบบกัน ในกรณีการใช้งานที่สมบูรณ์แบบ ไม่มีคำสั่ง if-else เพียงแค่โค้ดบริสุทธิ์ที่นำข้อมูลจากคอนโทรลเลอร์มาวางไว้ระหว่างแท็ก HTML แอปพลิเคชันประเภทนั้นอาจมีอยู่ในแฮ็กกาธอนและความฝัน แต่แอปพลิเคชันในโลกแห่งความเป็นจริงมีกิ่งก้านและเงื่อนไขมากมายเมื่อแสดงมุมมอง
คุณควรทำอย่างไรเมื่อตรรกะในการแสดงส่วนต่างๆ ของหน้าซับซ้อนเกินไป คุณไปจากที่นั่นที่ไหน คำตอบทั่วไปอาจเป็นการเข้าถึง JavaScriptlibrary หรือ framework ที่ทันสมัย และสร้างบางสิ่งที่ซับซ้อน แต่เนื่องจากโพสต์นี้เกี่ยวกับ Rails Views เรามาดูตัวเลือกที่เรามีในนั้นกันดีกว่า
ผู้ช่วยหลังการขาย (กำหนดเอง)
สมมติว่าคุณต้องการแสดงปุ่มคำกระตุ้นการตัดสินใจ (CTA) ใต้เพลง แต่มีสิ่งที่จับได้ — เพลงสามารถมี URL ดาวน์โหลดหรือไม่ว่าจะด้วยเหตุผลใดก็ตาม เราอาจอยากเขียนโค้ดที่คล้ายกับต่อไปนี้:
app/views/songs/show.html.erb
...
<div class="song-cta">
<% if @song.download_url %>
<%= link_to "Download", download_url %>
<% else %>
<%= link_to "Subscribe to artists updates",
artist_updates_path(@song.artist) %>
<% end %>
</div>
...
หากเราดูตัวอย่างข้างต้นเป็นตรรกะการนำเสนอที่แยกออกมา มันก็ไม่ได้แย่เกินไปใช่ไหม แต่ถ้ามีตัวปรับแก้เงื่อนไขเหล่านี้มากกว่า โค้ดก็จะอ่านได้น้อยลง นอกจากนี้ยังเพิ่มโอกาสของบางสิ่ง บางที่ไม่ได้รับการเรนเดอร์อย่างถูกต้อง โดยเฉพาะอย่างยิ่งหากมีเงื่อนไขเพิ่มเติม
วิธีหนึ่งในการต่อสู้กับสิ่งเหล่านี้คือแยกมันออกมาเป็นผู้ช่วยแยกต่างหาก โชคดีที่ Rails มีวิธีเขียนตัวช่วยที่กำหนดเองได้อย่างง่ายดาย ใน app/helpers
เราสามารถสร้าง SongsHelper
อย่างเช่น:
module SongsHelper
def song_cta_link
content_tag(:div, class: 'song-cta') do
if @song.download_url
link_to "Download", @song.download_url
else
link_to "Subscribe to artists updates",
artist_updates_path(@song.artist)
end
end
end
end
หากเราเปิดหน้าแสดงของเพลง เราจะยังคงได้ผลลัพธ์เหมือนเดิม อย่างไรก็ตาม เราสามารถทำให้ตัวอย่างนี้ดีขึ้นเล็กน้อย ในตัวอย่างข้างต้น เราใช้ตัวแปร aninstance @song
. อาจไม่สามารถใช้ได้หากเราตัดสินใจใช้ตัวช่วยนี้ในที่ที่ @song
คือ nil
. ดังนั้นหากต้องการตัดการพึ่งพาภายนอกในรูปแบบของตัวแปรอินสแตนซ์ เราสามารถส่งต่ออาร์กิวเมนต์ไปยังผู้ช่วยได้ดังนี้:
module SongsHelper
def song_cta_link(song)
content_tag(:div, class: 'song-cta') do
if song.download_url
link_to "Download", song.download_url
else
link_to "Subscribe to artists updates",
artist_updates_path(song.artist)
end
end
end
end
จากนั้นในมุมมองเราสามารถเรียกตัวช่วยดังนี้:
app/views/songs/show.html.erb
...
<%= song_cta_link(@song) %>
...
ด้วยเหตุนี้ เราควรได้ผลลัพธ์ในมุมมองแบบเดียวกับที่เคยทำมาก่อน ข้อดีของการใช้ตัวช่วยคือคุณสามารถเขียนการทดสอบสำหรับพวกเขาเพื่อให้แน่ใจว่าไม่มีการถดถอยเกิดขึ้นในอนาคต ข้อเสียคือมีการกำหนดไว้ทั่วโลก และคุณต้องตรวจสอบให้แน่ใจว่าชื่อผู้ช่วยจะไม่ซ้ำกันในแอปของคุณ
หากคุณไม่ใช่แฟนตัวยงของการเขียนตัวช่วยแบบกำหนดเองของ Rails คุณสามารถเลือกรูปแบบ View Model ด้วย Draper gem ได้เสมอ หรือคุณสามารถม้วนรูปแบบ View Model ของคุณเองได้ที่นี่ ไม่ควรซับซ้อนขนาดนั้น หากคุณเพิ่งเริ่มต้นใช้งานเว็บแอป ขอแนะนำให้เริ่มอย่างช้าๆ โดยการเขียนตัวช่วยที่กำหนดเอง และหากนั่นทำให้เกิดความเจ็บปวด ให้หันไปหาวิธีแก้ปัญหาอื่น
ทำให้มุมมองของคุณแห้ง
สิ่งที่ฉันชอบมากเมื่อเริ่มใช้ Rails คือความสามารถในการทำให้มาร์กอัปของคุณแห้งได้ง่ายจนแทบไม่น่าเชื่อสำหรับฉัน Rails ช่วยให้คุณสามารถสร้างบางส่วน — ชิ้นรหัสที่นำกลับมาใช้ใหม่ได้ ซึ่งคุณสามารถรวมไว้ที่ใดก็ได้ ตัวอย่าง หากคุณกำลังแสดงเพลงในหลาย ๆ ที่ และคุณมีโค้ดเดียวกันในหลาย ๆ ไฟล์ การสร้างเพลงเพียงบางส่วนก็สมเหตุสมผลแล้ว
สมมติว่าคุณแสดงเพลงของคุณตามที่แสดงด้านล่าง:
# app/views/songs/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= @song.title %>
</p>
<p>
<strong>Description:</strong>
<%= @song.description %>
</p>
<%= song_cta_link %>
<%= link_to 'Edit', edit_song_path(@song) %> |
<%= link_to 'Back', songs_path %>
แต่คุณต้องการแสดงบนหน้าอื่นที่มีมาร์กอัปเดียวกันด้วย จากนั้นคุณสามารถสร้างไฟล์ใหม่ที่มีเครื่องหมายขีดล่าง เช่น app/views/songs/_song.html.erb
.
# app/views/songs/_song.html.erb
<p>
<strong>Title:</strong>
<%= @song.title %>
</p>
<p>
<strong>Description:</strong>
<%= @song.description %>
</p>
<%= song_cta_link(@song) %>
จากนั้นไม่ว่าคุณต้องการรวมเพลงบางส่วน ให้ทำดังต่อไปนี้:
...
<%= render "song" %>
...
Rails จะทำการค้นหาอัตโนมัติว่า _song
บางส่วนมีอยู่และจะแสดงผล คล้ายกับตัวอย่างที่มีตัวช่วยที่กำหนดเอง จะเป็นการดีที่สุดถ้าเรากำจัดตัวแปรอินสแตนซ์ @song
ในส่วนของเรา
# app/views/songs/_song.html.erb
<p>
<strong>Title:</strong>
<%= song.title %>
</p>
<p>
<strong>Description:</strong>
<%= song.description %>
</p>
<%= song_cta_link(song) %>
จากนั้นเราจะต้องส่งตัวแปรเพลงไปที่ part เพื่อให้นำกลับมาใช้ใหม่ได้และเหมาะสมที่จะนำไปรวมไว้ที่อื่น
...
<%= render "song", song: @song %>
...
ความคิดสุดท้าย
นั่นคือทุกคนสำหรับโพสต์นี้ โดยสรุป เราได้ผ่านรูปแบบสองสามรูปแบบและรูปแบบการต่อต้านที่คุณสามารถพบได้ในขอบเขต Rails View ต่อไปนี้คือคำแนะนำบางส่วน:
- หลีกเลี่ยงตรรกะที่ซับซ้อนใน UI (อย่าให้ View ทำการ Powerlifting มากนัก)
- เรียนรู้ว่า Rails มอบอะไรให้คุณในทันทีในแง่ของผู้ช่วยดู
- จัดโครงสร้างและนำรหัสของคุณกลับมาใช้ใหม่ด้วยตัวช่วยที่กำหนดเองและบางส่วน
- อย่าพึ่งพาตัวแปรอินสแตนซ์มากเกินไป
ในโพสต์ถัดไป เราจะพูดถึงรูปแบบ Rails Controller และ anti-patterns ซึ่งสิ่งต่าง ๆ อาจดูยุ่งเหยิง โปรดคอยติดตาม
รอตอนต่อไป เชียร์!
ป.ล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่ออกจากสื่อ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!