简体   繁体   中英

Rails order records by column and custom method

For one of my models I'm trying to set a default scope that sorts by year and season. Since year is an integer, it's easy to order by that. My trouble is ordering by season (if the year is the same). Here's just ordering by year:

class League < ActiveRecord::Base
  def self.default_scope
    order(:year)
  end

  # The season's that are allowed to be used
  # This is also the order I'd like to use
  def self.season_collection
    {
      "Spring" => "Spring",
      "Summer" => "Summer",
      "Fall"   => "Fall"
    }
  end
end

If I try order(:year, :season) then that will just do it alphabetically. Is there any way to use order (so it's done on the database end)?

You can order them in the database, but it isn't going to be very efficient as you'll need to coerce the value of the season field into a integer, and then use that to order the records. See this answer for an example:

SQL: ORDER BY using a substring within a specific column... possible?

A better way would be to store the season as an integer, not a string, in the database. The easiest way to use this would be ActiveRecord::Enum available in Rails 4.1+. In your model add this:

class League < ActiveRecord::Base
  enum season: %w{Spring Summer Autumn Winter}
end

Then you can create records like this:

0> league1 = League.create!(season: 'Summer')
=> #<League id: 1>
1> league2 = League.create!(season: 'Spring')
=> #<League id: 2>
2> league3 = League.create!(season: 'Autumn')
=> #<League id: 3>
3> league3.season
=> "Autumn"

Under the hood ActiveRecord doesn't store the string, but an integer referring to it. You can find the integers as follows:

4> League.seasons
=> {"Spring"=>0, "Summer"=>1, "Autumn"=>2, "Winter"=>3}

To get them in order it's then just a case of ordering the field:

5> League.order(:season)
SELECT * FROM leagues ORDER BY season
=> #<ActiveRecord::Relation [#<League id: 2>, #<League id: 1>, #<League id: 3>]>

If you want to query for a specific season ActiveRecord will automatically map the name to the ID:

6> League.where(season: 'Summer')
SELECT * FROM leagues WHERE season = 1
=> #<ActiveRecord::Relation [#<League id: 1>]>

If you try and set an invalid season, ActiveRecord will let you know:

7> league3.season = 'Tomato'
ArgumentError: 'Tomato' is not a valid season

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM