Rails - 按连接表数据排序

Ste*_*vis 27 ruby join ruby-on-rails has-many-through

我在工作中有一个RoR项目.以下是我的模型的适用部分.

has_many :communities, :through => :availabilities
has_many :availabilities, :order => "price ASC"
Run Code Online (Sandbox Code Playgroud)

社区

has_many :homes, :through => :availabilities
has_many :availabilities
Run Code Online (Sandbox Code Playgroud)

可用性

belongs_to :home
belongs_to :community
Run Code Online (Sandbox Code Playgroud)

数据库中的"可用性"表具有附加数据列"价格"

所以现在我可以打电话了

@home.availabilities.each do |a|
  a.community.name
  a.price
Run Code Online (Sandbox Code Playgroud)

并按我的要求取回按价格订购的可用性数据.我的问题是:

有没有办法自动订购房屋avaliabilities.first.price(第一个=最低)?也许有点什么default_scope :order

eco*_*gic 34

我建议避免使用default_scope,尤其是在另一张桌子上的价格等方面.每次使用该表时,都会发生加入和排序,可能会在复杂查询中产生奇怪的结果,并且无论如何都会使查询变慢.

它的范围没有任何问题,它更简单,更清晰,你可以简单地说:

scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
Run Code Online (Sandbox Code Playgroud)

PS:记得要添加一个索引 price


Ste*_*vis 22

这篇相关文章的帮助下弄明白了.

我将订单从Home模型移到了Availability模型中:

可用性

default_scope :order => "price ASC"
Run Code Online (Sandbox Code Playgroud)

然后我急切地将可用性加载到Home模型中并按价格排序:

default_scope :include => :availabilities, :order => "availabilities.price ASC"
Run Code Online (Sandbox Code Playgroud)


TeW*_*eWu 11

@ecoologic 答案:

scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
Run Code Online (Sandbox Code Playgroud)

是伟大的,但应该提到includes可能,并在某些情况下应该被替换joins.他们都有最佳用例.

从实际角度来看,有两个主要区别:

  1. includes加载相关记录; 在这种情况下Availability记录.joins不要加载任何相关的记录.因此,includes当您想要使用来自连接模型的数据时,您应该使用它,例如在price某处显示.另一方面,joins如果您打算仅在查询中使用连接模型的数据,例如在ORDER BYWHERE子句中,则应该使用它.

  2. includes加载所有记录,同时joins仅加载那些具有关联联接模型的记录.所以在OP的情况下,Home.includes(:availabilities)将加载所有家庭,而Home.joins(:availabilities)只加载那些至少有一个可用性的家庭.

另见这个问题.


not*_*ere 8

实现这一目标的另一种方法:

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price]) }
Run Code Online (Sandbox Code Playgroud)

您还可以指定 ASC 方向

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].asc) }
Run Code Online (Sandbox Code Playgroud)

DESC

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].desc) }
Run Code Online (Sandbox Code Playgroud)

arel_tableActiveRecord模型上使用可以让您避免表名更改时的情况(但这种情况很少发生)。

请注意,main_table#id为确定排序添加是很好的。

所以最终版本将是:

scope :ordered, -> {
  includes(:availabilities).
    order(Availability.arel_table[:price].asc, order(Home.arel_table[:id].asc)
}
Run Code Online (Sandbox Code Playgroud)


Luc*_*ton 5

在Rails 5.2+中,将字符串参数传递给order方法时,您可能会收到弃用警告:

优先警告:危险的查询方法(其参数用作原始SQL的方法)以非属性参数“ table.column”调用。在Rails 6.0中将禁止使用非属性参数。不应使用用户提供的值(例如请求参数或模型属性)来调用此方法。

为了解决这个问题,您可以使用Arel.sql()

scope :ordered, -> {
  includes(:availabilities).order(Arel.sql('availabilities.price'))
}
Run Code Online (Sandbox Code Playgroud)


mld*_*car 5

您还可以像这样对链接表进行排序(例如):

class User
  has_many :posts
end

class Post
  belongs_to :user

  scope :sorted_by_user_and_title, -> { 
    joins(:user).merge(
     User.order(first_name: :asc, last_name: :asc)
    )
    .order(title: :desc)
    # SELECT * FROM `posts`
    # INNER JOIN `users` ON `posts`.`user_id` = `users`.`id`
    # ORDER BY
    # `users`.`first_name` ASC, `users`.`last_name` ASC, `posts`.`title` DESC;
  }
  scope :sorted_by_title_and_user, -> { 
    order(title: :desc)
    .joins(:user).merge(
     User.order(first_name: :asc, last_name: :asc)
    )
    # SELECT * FROM `posts`
    # INNER JOIN `users` ON `posts`.`user_id` = `users`.`id`
    # ORDER BY
    # `posts`.`title` DESC, `users`.`first_name` ASC, `users`.`last_name` ASC;
  }
end
Run Code Online (Sandbox Code Playgroud)

问候