web-dev-qa-db-ja.com

Rails-結合テーブルデータで並べ替え

作業中のRoRプロジェクトがあります。これが私のモデルの該当するセクションです。

ホーム

has_many :communities, :through => :availabilities
has_many :availabilities, :order => "price ASC"

コミュニティ

has_many :homes, :through => :availabilities
has_many :availabilities

在庫状況

belongs_to :home
belongs_to :community

データベースの「availabilities」テーブルには、追加のデータ列「price」があります

だから今私は呼び出すことができます

@home.availabilities.each do |a|
  a.community.name
  a.price

必要に応じて、価格順に並べられた在庫データを取得します。私の質問はこれです:

avaliabilities.first.price(最初=最低)でホームを自動的に注文する方法はありますか?多分default_scope :orderで何か?

28
Steve Davis

特に別のテーブルの価格などでは、default_scopeを使用しないことをお勧めします。そのテーブルを使用するたびに、結合と順序付けが行われ、複雑なクエリで奇妙な結果が得られたり、クエリが遅くなったりする可能性があります。

それ自体のスコープに問題はありません。より単純で、さらに明確です。次のように単純にすることができます。

scope :ordered, -> { includes(:availabilities).order('availabilities.price') }

PS:priceにインデックスを追加することを忘れないでください。 join/includeを決定するには、ここにある他のすばらしい回答も参照してください。

32
ecoologic

この関連する投稿 の助けを借りてそれを理解しました。

注文をホームモデルから可用性モデルに移動しました。

在庫状況

default_scope :order => "price ASC"

次に、可用性をHomeモデルにロードし、価格で並べ替えました。

ホーム

default_scope :include => :availabilities, :order => "availabilities.price ASC"
22
Steve Davis

@ecoologic answer

_scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
_

素晴らしいですが、includesは可能であり、場合によってはjoinsに置き換える必要があることに言及してください。 どちらにも最適な使用例があります

実用的な観点から見ると、2つの主な違いがあります。

  1. includesは関連するレコードをロードします。この場合はAvailabilityレコードです。 joins関連するレコードをロードしません。したがって、結合モデルのデータを使用する場合は、includesを使用する必要があります。どこかにpriceを表示します。一方、結合モデルのデータをクエリでのみ使用する場合は、joinsを使用する必要があります。 _ORDER BY_またはWHERE句内。

  2. includesはすべてのレコードをロードしますが、joinsは結合モデルに関連付けられているレコードのみをロードします。したがって、OPの場合、Home.includes(:availabilities)はすべてのホームをロードし、Home.joins(:availabilities)は少なくとも1つの可用性に関連付けられているホームのみをロードします。

この質問 も参照してください。

11
TeWu

Rails 5.2+)では、文字列パラメーターを注文メソッドに渡すと、非推奨の警告が表示される場合があります。

非推奨の警告:非属性の引数で呼び出された危険なクエリメソッド(引数が生のSQLとして使用されるメソッド): "table.column"。 Rails 6.0では、属性以外の引数は許可されません。このメソッドは、リクエストパラメータやモデル属性などのユーザー指定の値で呼び出すことはできません。

これを解決するには、Arel.sql()を使用できます。

scope :ordered, -> {
  includes(:availabilities).order(Arel.sql('availabilities.price'))
}
5
Lucas Caton

これを達成する別の方法:

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price]) }

ASC方向を指定することもできます

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

DESC

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

ActiveRecordモデルでarel_tableを使用すると、テーブル名が変更された場合のシナリオを回避できます(ただし、まれにしか発生しません)。

ソートを確定するためにmain_table#idを追加すると便利です。

したがって、最終バージョンは次のようになります。

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

次のようにリンクテーブルを並べ替えることもできます(例):

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

よろしく

0
mld.oscar