Bagaimana cara mengurutkan hubungan has_many secara otomatis di Rails?

96

Ini sepertinya pertanyaan yang sangat sederhana tetapi saya belum melihatnya dijawab di mana pun.

Di rel jika Anda memiliki:

class Article < ActiveRecord::Base 
  has_many :comments 
end 
class Comments < ActiveRecord::Base 
  belongs_to :article 
end

Mengapa Anda tidak bisa memesan komentar dengan sesuatu seperti ini:

@article.comments(:order=>"created_at DESC")

Ruang lingkup bernama berfungsi jika Anda perlu banyak referensi dan bahkan orang melakukan hal-hal seperti ini:

@article.comments.sort { |x,y| x.created_at <=> y.created_at }

Tapi ada yang memberitahuku bahwa ini harus lebih sederhana. Apa yang saya lewatkan?

Brian Armstrong
sumber
Hati-hati, Anda menggunakan metode yang tidak terduga: @ article.comments (reload = false) adalah untuk memaksa cache-miss (untuk memaksa memuat ulang relasi). Jika Anda memberikan hash, itu sama dengan @ article.comments (true). Jangan lupa gunakan .all (: order => '...'). Kakiku sudah patah beberapa kali.
Marcel Jackwerth

Jawaban:

152

Anda dapat menentukan urutan sortir untuk koleksi telanjang dengan opsi pada has_manydirinya sendiri:

class Article < ActiveRecord::Base 
  has_many :comments, :order => 'created_at DESC'
end 
class Comment < ActiveRecord::Base 
  belongs_to :article 
end

Atau, jika Anda menginginkan metode pengurutan non-database yang sederhana, gunakan sort_by :

article.comments.sort_by &:created_at

Mengumpulkan ini dengan metode pemesanan yang ditambahkan ActiveRecord:

article.comments.find(:all, :order => 'created_at DESC')
article.comments.all(:order => 'created_at DESC')

Jarak tempuh Anda mungkin berbeda: karakteristik kinerja dari solusi di atas akan berubah secara drastis bergantung pada bagaimana Anda mengambil data di tempat pertama dan Ruby mana yang Anda gunakan untuk menjalankan aplikasi Anda.

Jim Puls
sumber
Terima kasih, "semua" mungkin yang paling sederhana. Barang bagus!
Brian Armstrong
58
di Rails 4, opsi order telah dihapus. Gunakan lambda -> { order(created_at: :desc) }sebagai gantinya. Lihat: stackoverflow.com/questions/18284606/…
d_rail
ini tidak digunakan lagi dengan rel 4, lihat stackoverflow.com/questions/18284606/…
bjelli
41

Pada Rails 4, Anda akan melakukan:

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }
end 
class Comment < ActiveRecord::Base 
  belongs_to :article 
end

Untuk suatu has_many :throughhubungan, urutan argumen penting (harus menjadi yang kedua):

class Article
  has_many :comments, -> { order('postables.sort' :desc) }, 
           :through => :postable
end

Jika Anda selalu ingin mengakses komentar dalam urutan yang sama apa pun konteksnya, Anda juga dapat melakukannya melalui default_scopedalam Commentseperti:

class Comment < ActiveRecord::Base 
  belongs_to :article 
  default_scope { order(created_at: :desc) }
end

Namun ini bisa menjadi masalah karena alasan yang dibahas dalam pertanyaan ini .

Sebelum Rails 4 Anda dapat menentukan ordersebagai kunci pada hubungan tersebut, seperti:

class Article < ActiveRecord::Base 
  has_many :comments, :order => 'created_at DESC'
end 

Seperti yang disebutkan Jim, Anda juga dapat menggunakan sort_bysetelah Anda mengambil hasil meskipun dalam kumpulan hasil apa pun ukurannya akan jauh lebih lambat (dan menggunakan lebih banyak memori) daripada melakukan pengurutan melalui SQL / ActiveRecord.

Jika Anda melakukan sesuatu di mana menambahkan urutan default merepotkan karena suatu alasan atau Anda ingin menimpa default Anda dalam kasus tertentu, sangatlah mudah untuk menentukannya dalam tindakan pengambilan itu sendiri:

sorted = article.comments.order('created_at').all
Matt Sanders
sumber
1
Di mana saya dapat menentukannya dalam tindakan pengambilan itu sendiri? Apakah saya mengganti metode dalam model?
Wit
@Wit - Anda dapat menambahkan .order()ke rantai metode, seperti pada contoh terakhir. Apakah ini yang Anda tanyakan?
Matt Sanders
Saya menyesal. Saya tidak dapat mengingat apa yang saya coba capai.
Wit
Contoh has_many, through polymorphic sangat membantu di sini!
Vijay
7

Jika Anda menggunakan Rails 2.3 dan ingin menggunakan pengurutan default yang sama untuk semua koleksi objek ini, Anda dapat menggunakan default_scope untuk memesan koleksi Anda.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Kemudian jika Anda menelepon

@students = @class.students

Mereka akan diurutkan sesuai default_scope Anda. TBH dalam arti yang sangat umum, pengurutan adalah satu-satunya penggunaan cakupan default yang sangat baik.

nitecoder
sumber
Pada Rails 4, ini tidak sesuai. Lihat solusi ini untuk sintaks Rails 4 yang benar: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs
0

Dan jika Anda perlu menyampaikan beberapa argumen tambahan seperti dependent: :destroyatau apa pun, Anda harus menambahkan argumen setelah lambda, seperti ini:

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }, dependent: :destroy
end
Max L.
sumber