如何为Ruby的Quote模型创建自定义排序Scope并保留ActiveRelation?
问题
我开发了一个管理书籍引文及对应笔记/感想的应用,希望按照自定义书籍顺序、章节顺序、段落及句子顺序来排列引文。想请教能不能把排序逻辑迁移到模型的Scope中,这样就能保留ActiveRelation的特性?
现有问题
- 目前在控制器里实现了按书籍顺序排序的代码,但在视图中调用
quote.image时会报错:undefined method 'image' for #[<Quote id: 4, created_at: "2023-11-21 15:19:.... - 控制器里尝试按段落和句子排序的
sort_by!代码也没生效(已注释)
现有代码及数据库结构
控制器代码(/app/controllers/quotes_controller.rb)
def quotes @quotes = Quotes.all @sorted_quotes = [] order_of_books.each do |book| @temp_array = [] if @quotes.any? { |quote| quote[:reference_book] == book} @temp_array << @quotes.detect { |quote| quote[:reference_book] == book} # @temp_array.sort_by! { |quote| quote.reference_paragraph, quote.reference_sentence } @sorted_quotes << @temp_array end end end
模型Concern代码(/app/models/concerns/book_concern.rb)
def order_of_books [ "Book A", "Book B", "Book C", ] end
数据库表结构(db/schema.rb)
create_table "quotes", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "text", null: false t.string "reference_book", null: false t.integer "reference_chapter", null: false t.integer "reference_paragraph", null: false t.integer "reference_sentence", null: false t.string "image" t.text "notes" end
解决方案
1. 把排序逻辑移到模型Scope,保留ActiveRelation
当然可以,这才是符合Rails MVC原则的做法——把业务逻辑从控制器剥离到模型,同时保留ActiveRelation的链式调用能力。
先修改book_concern.rb,把实例方法改成类方法,同时定义排序Scope:
module BookConcern extend ActiveSupport::Concern class_methods do def order_of_books ["Book A", "Book B", "Book C"] end def sorted_by_custom_order # 用SQL的CASE语句实现自定义书籍顺序,同时按章节、段落、句子升序排列 case_clause = order_of_books.each_with_index.map do |book, idx| "WHEN reference_book = '#{book}' THEN #{idx}" end.join(" ") order("CASE #{case_clause} END, reference_chapter, reference_paragraph, reference_sentence") end end end
然后在Quote模型里引入这个Concern:
class Quote < ApplicationRecord include BookConcern end
控制器里直接调用这个Scope即可,代码极简:
def quotes @sorted_quotes = Quote.sorted_by_custom_order end
2. 解决quote.image报错的问题
你当前的控制器代码有两个核心问题导致报错:
@quotes.detect只会返回第一个匹配指定书籍的Quote,而非所有匹配项- 把单个Quote塞进数组后再存入
@sorted_quotes,最终得到的是二维数组([[quote1], [quote2], ...])
视图遍历的时候拿到的是数组而非Quote对象,自然调用不了image方法。用上面的Scope方案后,@sorted_quotes是一维的ActiveRelation集合,直接遍历就能拿到每个Quote实例,报错问题自然解决。
3. 修复段落、句子排序无效的问题
之前的sort_by!没生效,一是因为@temp_array里只有一个Quote(detect只取第一个),二是写法存在小问题——排序键需要用数组包裹:sort_by! { |q| [q.reference_paragraph, q.reference_sentence] }。不过更推荐用上面的SQL排序方案,直接在数据库层面完成排序,比在Ruby内存里排序效率更高,也更符合ActiveRelation的设计。
内容的提问来源于stack exchange,提问作者CWarrington
相关产品推荐
相关产品推荐

