OO Design in Rails: Tempat menaruh barang

244

Saya benar-benar menikmati Rails (meskipun saya umumnya tanpa REST), dan saya menikmati Ruby menjadi sangat OO. Namun, kecenderungan untuk membuat subkelas ActiveRecord dan pengontrol besar sangat wajar (bahkan jika Anda memang menggunakan pengontrol per sumber daya). Jika Anda menciptakan dunia objek yang lebih dalam, di mana Anda akan meletakkan kelas (dan modul, saya kira)? Saya bertanya tentang pandangan (di Helpers sendiri?), Pengontrol dan model.

Lib baik-baik saja, dan saya telah menemukan beberapa solusi agar bisa dimuat ulang di lingkungan dev , tapi saya ingin tahu apakah ada cara yang lebih baik untuk melakukan hal ini. Saya benar-benar hanya khawatir tentang kelas yang tumbuh terlalu besar. Juga, bagaimana dengan Mesin dan bagaimana mereka cocok?

Dan Rosenstark
sumber

Jawaban:

384

Karena Rails menyediakan struktur dalam hal MVC, wajar jika hanya menggunakan wadah model, tampilan, dan pengontrol yang disediakan untuk Anda. Ungkapan khas untuk pemula (dan bahkan beberapa programmer menengah) adalah menjejalkan semua logika dalam aplikasi ke dalam model (kelas basis data), pengontrol, atau tampilan.

Pada titik tertentu, seseorang menunjukkan paradigma "model-gemuk, pengontrol-kurus", dan pengembang menengah buru-buru mengeluarkan semuanya dari pengontrol mereka dan memasukkannya ke dalam model, yang mulai menjadi tong sampah baru untuk logika aplikasi.

Pengendali kurus, pada kenyataannya, adalah ide yang bagus, tetapi akibat wajar - meletakkan segala sesuatu dalam model, sebenarnya bukan rencana terbaik.

Di Ruby, Anda memiliki beberapa opsi bagus untuk membuat hal-hal lebih modular. Jawaban yang cukup populer adalah dengan hanya menggunakan modul (biasanya disimpan lib) yang menampung kelompok metode, dan kemudian memasukkan modul ke dalam kelas yang sesuai. Ini membantu dalam kasus-kasus di mana Anda memiliki kategori fungsionalitas yang ingin Anda gunakan kembali di beberapa kelas, tetapi di mana fungsionalitasnya secara melekat masih melekat pada kelas.

Ingat, ketika Anda memasukkan modul ke dalam kelas, metode tersebut menjadi metode instan kelas, jadi Anda masih berakhir dengan kelas yang berisi banyak metode, mereka hanya disusun dengan baik ke dalam beberapa file.

Solusi ini dapat bekerja dengan baik dalam beberapa kasus - dalam kasus lain, Anda akan ingin berpikir tentang menggunakan kelas dalam kode Anda yang bukan model, tampilan atau pengontrol.

Cara yang baik untuk memikirkannya adalah "prinsip tanggung jawab tunggal," yang mengatakan bahwa kelas harus bertanggung jawab atas satu hal (atau jumlah kecil). Model Anda bertanggung jawab untuk menyimpan data dari aplikasi Anda ke basis data. Pengontrol Anda bertanggung jawab untuk menerima permintaan dan mengembalikan respons yang layak.

Jika Anda memiliki konsep yang tidak cocok dengan kotak-kotak itu (ketekunan, manajemen permintaan / tanggapan), Anda mungkin ingin memikirkan bagaimana Anda akan memodelkan ide yang dimaksud. Anda dapat menyimpan kelas non-model di aplikasi / kelas, atau di mana pun, dan menambahkan direktori itu ke jalur pemuatan Anda dengan melakukan:

config.load_paths << File.join(Rails.root, "app", "classes")

Jika Anda menggunakan penumpang atau JRuby, Anda mungkin juga ingin menambahkan jalur Anda ke jalur muatan yang diinginkan:

config.eager_load_paths << File.join(Rails.root, "app", "classes")

Intinya adalah bahwa begitu Anda sampai ke suatu titik di Rails di mana Anda menemukan diri Anda mengajukan pertanyaan ini, sekarang saatnya untuk menambah potongan-potongan Ruby Anda dan mulai memodelkan kelas yang bukan hanya kelas MVC yang Rails berikan secara default.

Pembaruan: Jawaban ini berlaku untuk Rails 2.x dan lebih tinggi.

Yehuda Katz
sumber
Doh. Menambahkan direktori terpisah untuk non-Model tidak terpikir oleh saya. Saya bisa merasakan kedatangan yang rapi ...
Mike Woodhouse
Yehuda, terima kasih untuk itu. Jawaban yang bagus Itulah yang saya lihat di aplikasi yang saya warisi (dan yang saya buat): semua yang ada di controller, model, view, dan helpers secara otomatis disediakan untuk controller dan views. Kemudian datang mixin dari lib, tetapi tidak pernah ada upaya untuk melakukan pemodelan OO nyata. Anda benar, meskipun: di "aplikasi / kelas, atau di mana pun." Hanya ingin memeriksa apakah ada jawaban standar yang saya lewatkan ...
Dan Rosenstark
33
Dengan versi yang lebih baru, config.autoload_paths default ke semua direktori dalam aplikasi. Jadi, Anda tidak perlu mengubah config.load_paths seperti dijelaskan di atas. Saya tidak yakin tentang eager_load_paths (belum), dan perlu melihatnya. Apakah ada yang sudah tahu?
Shyam Habarakada
Agresif pasif terhadap Intermediate: P
Sebastian Patten
8
Akan lebih baik jika Rails dikirim dengan folder "kelas" ini untuk mendorong "prinsip tanggung jawab tunggal" dan memungkinkan pengembang untuk membuat objek yang tidak didukung database. Implementasi "Kekhawatiran" dalam Rails 4 (lihat jawaban Simone) tampaknya telah mengurus implementasi modul untuk berbagi logika lintas model. Namun, tidak ada alat yang dibuat untuk kelas Ruby biasa yang tidak didukung database. Mengingat bahwa Rails sangat beralasan, saya penasaran proses pemikiran di balik TIDAK termasuk folder seperti ini?
Ryan Francis
62

Pembaruan : Penggunaan Kekhawatiran telah dikonfirmasi sebagai default baru di Rails 4 .

Itu sangat tergantung pada sifat modul itu sendiri. Saya biasanya menempatkan ekstensi pengontrol / model di folder / concern di dalam aplikasi.

# concerns/authentication.rb
module Authentication
  ...
end    

# controllers/application_controller.rb
class ApplicationController
  include Authentication
end



# concerns/configurable.rb
module Configurable
  ...
end    

class Model 
  include Indexable
end 

# controllers/foo_controller.rb
class FooController < ApplicationController
  include Indexable
end

# controllers/bar_controller.rb
class BarController < ApplicationController
  include Indexable
end

/ lib adalah pilihan saya untuk perpustakaan tujuan umum. Saya selalu memiliki namespace proyek di lib di mana saya meletakkan semua perpustakaan khusus aplikasi.

/lib/myapp.rb
module MyApp
  VERSION = ...
end

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb

Ekstensi inti Ruby / Rails biasanya terjadi di inisialisasi config sehingga pustaka hanya dimuat satu kali pada peningkatan Rails.

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb

Untuk fragmen kode yang dapat digunakan kembali, saya sering membuat plugin (mikro) sehingga saya dapat menggunakannya kembali di proyek lain.

File helper biasanya berisi metode helper dan kadang-kadang kelas ketika objek dimaksudkan untuk digunakan oleh helper (misalnya Form Builder).

Ini adalah gambaran yang sangat umum. Harap berikan rincian lebih lanjut tentang contoh spesifik jika Anda ingin mendapatkan saran yang lebih khusus. :)

Simone Carletti
sumber
Hal yang aneh. Saya tidak bisa mendapatkan ini diperlukan_dependensi RAILS_ROOT + "/ lib / my_module" untuk bekerja dengan sesuatu di luar direktori lib. Itu pasti mengeksekusi dan mengeluh jika file tidak ditemukan, tetapi itu tidak memuatnya.
Dan Rosenstark
Ruby hanya membutuhkan banyak hal sekali saja. Jika Anda ingin memuat sesuatu tanpa syarat, gunakan memuat.
Chuck
Selain itu, bagi saya cukup aneh bahwa Anda ingin memuat file dua kali selama masa instance aplikasi. Apakah Anda menghasilkan kode saat berjalan?
Chuck
Mengapa Anda menggunakan require_dependency alih-alih mengharuskan? Juga perhatikan bahwa jika Anda mengikuti konvensi penamaan Anda tidak perlu menggunakan sama sekali. Jika Anda membuat MyModule di lib / my_module, Anda dapat memanggil MyModule tanpa persyaratan sebelumnya (bahkan jika menggunakan harus lebih cepat dan terkadang lebih mudah dibaca). Perhatikan juga bahwa file di / lib hanya dimuat satu kali saat bootstrap.
Simone Carletti
1
Penggunaan kekhawatiran menyangkut
bbozo
10

... kecenderungan untuk membuat subkelas ActiveRecord yang sangat besar dan pengontrol yang besar sangat wajar ...

"Besar" adalah kata yang mengkhawatirkan ... ;-)

Bagaimana pengendali Anda menjadi besar? Itu sesuatu yang harus Anda perhatikan: idealnya, pengendali harus tipis. Mengambil aturan praktis dari udara tipis, saya akan menyarankan bahwa jika Anda secara teratur memiliki lebih dari, katakanlah, 5 atau 6 baris kode per metode pengontrol (aksi), maka pengontrol Anda mungkin terlalu gemuk. Apakah ada duplikasi yang dapat dipindahkan ke fungsi pembantu atau filter? Apakah ada logika bisnis yang dapat didorong ke dalam model?

Bagaimana model Anda menjadi besar? Haruskah Anda mencari cara untuk mengurangi jumlah tanggung jawab di setiap kelas? Adakah perilaku umum yang bisa Anda ekstrak menjadi mixin? Atau bidang fungsionalitas yang dapat Anda delegasikan ke kelas pembantu?

EDIT: Mencoba sedikit berkembang, semoga tidak terlalu merusak sesuatu ...

Pembantu: hidup app/helpersdan sebagian besar digunakan untuk membuat tampilan lebih sederhana. Baik itu kontroler khusus (juga tersedia untuk semua tampilan untuk controller itu) atau tersedia secara umum (module ApplicationHelper di application_helper.rb).

Filter: Katakanlah Anda memiliki baris kode yang sama dalam beberapa tindakan (cukup sering, pengambilan objek menggunakan params[:id]atau serupa). Duplikasi itu dapat diabstraksikan terlebih dahulu ke metode yang terpisah dan kemudian keluar dari tindakan sepenuhnya dengan mendeklarasikan filter dalam definisi kelas, seperti before_filter :get_object. Lihat Bagian 6 di Panduan ActionController Rails Biarkan pemrograman deklaratif menjadi teman Anda.

Model refactoring sedikit lebih merupakan hal yang religius. Murid-murid Paman Bob akan menyarankan, misalnya, bahwa Anda mengikuti Lima Perintah PADAT . Joel & Jeff mungkin merekomendasikan pendekatan yang lebih, eh, "pragmatis", meskipun mereka tampaknya sedikit lebih berdamai. sesudahnya. Menemukan satu atau lebih metode dalam kelas yang beroperasi pada subset atribut yang didefinisikan dengan jelas adalah salah satu cara untuk mencoba mengidentifikasi kelas yang mungkin di-refactored dari model turunan ActiveRecord Anda.

Model rails tidak harus berupa subclass dari ActiveRecord :: Base, omong-omong. Atau dengan kata lain, model tidak harus berupa analog tabel, atau bahkan terkait dengan apa pun yang disimpan sama sekali. Bahkan lebih baik, selama Anda memberi nama file Anda app/modelssesuai dengan konvensi Rails (panggil #underscore pada nama kelas untuk mencari tahu apa Rails yang akan dicari), Rails akan menemukannya tanpa requireperlu.

Mike Woodhouse
sumber
Benar dalam semua hal, Mike, dan terima kasih atas perhatian Anda ... Saya telah mewarisi proyek di mana ada beberapa metode pada pengontrol yang sangat besar. Saya telah memecah ini menjadi metode yang lebih kecil tetapi controller itu sendiri masih "gemuk." Jadi yang saya cari adalah semua opsi saya untuk melepas barang. Jawaban Anda adalah, "fungsi penolong," "filter," "model," "mixin" dan "kelas penolong." Jadi, di mana saya bisa meletakkan barang-barang ini? Bisakah saya mengatur hierarki kelas yang akan dimuat secara otomatis di dev env?
Dan Rosenstark