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

โค้ด Rails ไปไหน? จัดระเบียบ Logic ที่ไม่ใช่ CRUD

หลังจบ Rails tutorials เริ่มแอปตัวเอง สับสน อย่างว่า non-CRUD general logic ไปไหน? การดึง followers จาก Twitter เข้า MVC ยังไง? ถาม 2 คน ได้ 4 คำตอบ หรือ thread กลายเป็น smart people ตำรวนกันชั่วโมง ขณะคุณตบหัวโต๊ะ ไม่ว่าจะแบบไหน คุณปวดหัว

ไม่สร้างแอปฝันได้โดยไม่มี บาง general non-Rails logic โค้ดไปไหน จะเก็บง่ายๆ?

ที่เริ่มต้นง่าย

ตอนมี logic ที่รู้สึกเกี่ยวข้องกับ ActiveRecord model ที่มีอยู่ ผมเริ่มใส่ใน model นั้น เช่น มี Game model อยาก import games จาก CSV จะวางเมธอดบน Game class:

app/models/game.rb
class Game < ActiveRecord::Base def self.parse_from_csv(csv_string) games = [] CSV.parse(csv_string, quote_char: "'") do |row| games << Game.from_csv_row(row) if (row[0] == 'G') end games end def self.from_csv_row(row) Game.new({ dgs_game_id: row[1], opponent_name: row[2], created_at: row[4], updated_at: row[4], }) end end 

ข้อมูลที่เมธอดต้องการอยู่มือ ไม่ยากต่อการทดสอบ และน่าจะเป็นที่ contributor ใหม่มองหา logic นี้ก่อน

แตถ้าเพิ่มและเปลี่ยน model นั้นต่อไป มันจะใหญ่และซับซ้อน ส่วนต่างๆ ของ model จะ interact แบบแปลก ยิ่งเปลี่ยน ยิ่งยากต่อการเปลี่ยน

ในกรณีนั้น คุณอาจอยาก refactor โค้ดออกไป non-ActiveRecord model

Non-ActiveRecord Models

เพราะอยู่ใน Rails app ไม่แปลว่าต้อง inherit Active/Action-whatever

คุณเขียน Ruby code ของตัวเอง ใน plain Ruby objects และใช้ใน Rails app ได้ ออบเจ็กต์เหล่านี้ยังเรียกได้ว่า models เพราะ model ส่วนหนึ่งของปัญหา แค่ไม่มี ActiveRecord database เก็บข้อมูล

ครั้งต่อไปที่ทำ game CSV parser Game class ใหญ่เกินไป ย้าย parser logic ไป GameCSVParser class

Commit เต็มอยู่ที่นี่ แต non-ActiveRecord class 长这样:

app/models/game_csv_parser.rb
class GameCSVParser def initialize(csv_string) @rows = CSV.parse(csv_string, quote_char: "'") end def games game_rows.map { |row| Game.new(game_attributes(row)) } end private def game_rows @rows.select { |row| is_game_row?(row) } end def game_attributes(row) { dgs_game_id: row[1], opponent_name: row[2], created_at: row[4], updated_at: row[4], } end def is_game_row?(row) row[0] == 'G' end end 

ผมสร้าง plain Ruby object ใหม่ทันที ถ้า logic ไม่รู้สึกเกี่ยวข้องกับ ActiveRecord model ใดๆ หรือโค้ดดูเหมือนควรเป็นส่วนของสิ่งที่ยังไม่มีในแอป ไม่เช่นนั้นส่วนใหญ่ขึ้นมาจาก refactoring

ด้วย plain Ruby objects เขียนอะไรก็ได้ แตการรู้ว่าเขียน อะไรก็ได้ ไม่ช่วยให้มีทิศทาง ต้องการเมธอดอะไร? ออบเจ็กต์ใหม่ๆ จะโต้ตอบยังไง?

หลาย Rails apps ใช้ categories เดียวกันของ plain Ruby objects categories นี้คือ patterns ที่ follow ได้ เพื่อเขียนโค้ดที่ dev อื่นๆ จะ recognize คุณอาจเคยได้ยินบ้าง

Service Objects, Presenters, และ Jobs

Service objects, presenters, jobs ไม่มีอะไรพิเศษ แค่ plain Ruby objects ที่ทำงานแบบเฉพาะที่ recognize ได้

เช่น Resque job คือ plain Ruby class ที่มี perform method และ @queue:

app/workers/fetch_games_for_player.rb
class FetchGamesForPlayer @queue = :default def self.perform(player_id) player = Player.scoped_by_id(player_id).ready_for_fetching.first player && player.fetch_new_games! end end 

perform ถูกเรียกตอน job รัน

Presenter คือ plain Ruby object ที่มีโค้ดที่มีความหมายแค่ใน view:

app/presenters/user_presenter.rb
class UserPresenter def show_related_users? @user.related.count > 3 end end 

อาจ include Rails view helpers หรือรับ objects หลายตัวมารวมเป็น object เดียวสำหรับ view

Service object คือ plain Ruby object ที่ represent กระบวนการที่อยากทำ เช่น เขียน comment บน post อาจ:

  1. ทิ้ง comment
  2. ส่ง notification mail ให้ผู้เขียน post

Service object ทำทั้งสองอย่าง และเก็บ logic นั้นไว้นอก controller

มีบทความดีเกี่ยว service objects ที่นี่ เต็มไปด้วยตัวอย่าง

กระบวนการง่ายๆ ผมไม่ยุ่ง service objects แตถ้า controller เริ่มหนักเกินไป พวกมันเป็นที่ดีสำหรับ logic เพิ่มเติม

ใช้ patterns นี้จัดระเบียบ business logic ของตัวเอง ได้ แค่ plain Ruby objects แตเป็น objects ที่มี "รสชาติ" เหมือนกัน มีชื่อ และคุยกับ dev อื่นๆ ได้

เริ่มที่ไหน

ที่ non-Rails business logic ไปได้เยอะ อาจยากเลือก นี่คือสิ่งที่ผมทำ:

  1. ถ้า logic เกี่ยวข้องกับ class ที่มีอยู่ (แม้ ActiveRecord model) ใส่ใน class นั้น
  2. ถ้าไม่ fit class ที่มี สร้าง plain Ruby class ใหม่เก็บ logic
  3. ถ้ารู้สึกว่า logic ควรเป็นส่วนของสิ่งที่ยังไม่มี สร้าง plain Ruby class ใหม่
  4. ถ้ากลับมาดูภายหลัง model ซับซ้อนเกินไป หรือโค้ดไม่น่าเข้าใจใน model อีก refactor ไป plain Ruby class
  5. ถ้าโค้ดมีความหมายแค่ใน view ใส่ helper หรือสร้าง presenter
  6. ถ้าโค้ดไม่ต้องรันระหว่าง HTTP request หรือต้องรัน background ไปใน job
  7. ถ้าจัดการหลาย models หรือ stages ของ process ทำให้ controller ยากเข้าใจ ใส่ service object

คุณล่ะ โค้ดไปไหน? มี patterns อื่นช่วยได้ไหม? คอมเมนต์บอกผมหน่อย

ถ้ายังไม่มี process ลองของผมดู เหมาะกับคุณไหม ไม่มีวิธีเขียนโค้ดที่สมบูรณ์ แตเวลาค้าง process อย่างนี้จะช่วยเริ่มต้นได้