Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Ruby on Rails ดูรูปแบบและ Anti-patterns

ขอต้อนรับกลับสู่ภาคที่สามของซีรีส์ 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 ของเราและไม่พลาดแม้แต่โพสต์เดียว!