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
Related
I am trying to show the list of jobs ordered by median_salary by descending order. So far, it seems to only take into account the first number of median_salary. So something like 900 is listed above 1000, even though the value of 1000 > 900.
homes_controller.rb:
def index
nyc_highest = Highestpaidjob.where("city = ?", "NYC")
#nyc_highest = nyc_highest.order("median_salary DESC")
end
index.html.erb:
<%= #nyc_highest.inspect %>
returns:
#<ActiveRecord::Relation [#<Highestpaidjob id: 11, job: "Architect, City Planner", median_salary: "95928.48", count: 237, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:17:17", updated_at: "2016-07-25 18:17:17">, #<Highestpaidjob id: 7, job: "Medical", median_salary: "170507.69", count: 128, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:09:30", updated_at: "2016-07-25 18:09:30">]>
It is listing 95928.48 as higher than 170507.69. Am I missing a condition?
I've looked at Best way to implement sort asc or desc in rails and it seemed to suggest the way I am currently writing the sort.
It's because your median_salary database field is string and it's sorted as string. You need to cast it to integer in order clause, or create a migration, which will change field datatype.
Difference between strings being sorting and floats being sorted:
irb(main):001:0> ["95928.48", "170507.69"].sort
=> ["170507.69", "95928.48"]
irb(main):002:0> [95928.48, 170507.69].sort
=> [95928.48, 170507.69]
In postgres your order clause should looks like this:
#nyc_highest = nyc_highest.order("CAST(median_salary as FLOAT) DESC")
As #teksisto said, you should change the median_salary for float or some type that accepts decimals. Also, I would suggest to create a scope on your model, something like
scope :nyc_highest, -> { where("city = ?", "NYC").order("median_salary DESC") }
on your Highestpaidjob model. Then, you just call Highestpaidjob.nyc_highest in any place of your application you like.
For changing the median_salary data type:
rails g migration ChangeMedianSalaryType
then edit your migration file:
class ChangeMedianSalaryType < ActiveRecord::Migration
def up
change_column :highestpaidjobs, :median_salary, :float
end
def down
change_column :highestpaidjobs, :median_slary, :string
end
end
I have following models:
class Task
belongs_to :task_category
end
class TaskCategory
has_many :tasks
end
I want to group tasks by task category and this works for me:
Task.all.group_by(&:task_category)
# =>
{
#<TaskCategory id: 1, name: "call", ... } =>[#<Task id: 1, ...>, #<Task id: 2, ...>],
#<TaskCategory id: 2, name: "event", ... } =>[#<Task id: 3, ...>, #<Task id: 4, ...>]
}
The problem is I want all task categories returned even if the task collection is empty. Therefore, something like this would work:
#<TaskCategory id: 3, name: "todo", ... } =>[]
In this case, the task category has no tasks, so the value is an empty array. Does the group_by support an option to allow this? If not, can this be done elegantly in a one-liner?
TaskCategory.all.includes(:task) would work wouldn't it? The data you get back would be in a slightly different format, but not significantly so.
If you just do TaskCategory.all, you can get the tasks grouped by the category that you need. The format isn't exactly the same but still grouped the way you want it:
TaskCategory.all
# Assuming the first TaskCategory has no tasks
TaskCategory.all.first.tasks
=> #<ActiveRecord::Relation []>
A TaskCategory with no tasks would yield #<ActiveRecord::Relation []> which is somewhat equivalent to [].
Given
class Foo
has_many :bar
end
class Bar
belongs_to :foo
end
I want:
=> #<ActiveRecord::Relation [#<Foo id: 11, qux: 'hi', bar_id: 1, bar_name: 'blah', bar_something: 'blahblah' >, #<Foo id: 23, qux: 'hi', bar_id: 2, bar_name: 'lorem', bar_something: 'ipsum' >]>
I can do this:
> Foo.where(qux: 'hi').includes(:bar)
=> #<ActiveRecord::Relation [#<Foo id: 11, qux: 'hi', bar_id: 1 >, #<Foo id: 23, qux: 'hi', bar_id: 2 >]>
But it does not load the child records. It seems just to hold on to it in case it's needed.
There must be something more elegant than this?
Foo.where(qux: 'hi').includes(:bar).to_a.map do | f |
f.keys.each { |k| f[ k.to_s ] = f.delete(k) if k.class == :symbol }
Bar.column_names.except('id','foo_id').each do | ba |
ba_name = 'bar_' + ba
f.merge({ba_name => f.bar.send(ba.to_sym)})
end
f
end
includes(:bar) lazy loads the child records, in this case bar. It's one way to avoid n+1 queries (so that you don't run one query for each instance of foo). And you do have access to it.
Foo.where(qux: 'hi').each do |foo|
puts foo.bar.inspect
end
If you want to get all foos where their bar.qux = hi, then go the otherway:
Bar.joins(:foo).where(foo: { qux: 'hi' })
Foo.select("foos.id,foos.qux,bar_id,bars.bar_name,bars.something").joins(:bar).where(qux: 'hi')
includes lazy load the association so it basically does not merge both tables. What you are looking can be done through joins which allow you to query on both tables and select all required columns which you want. You can find more help here http://tomdallimore.com/blog/includes-vs-joins-in-rails-when-and-where/
Do you actually need the AR relation to have all those values loaded up-front? The lazy loading is intentional to keep you from beating up the DB unnecessarily...
You can still reference any attribute of bar directly:
Foo.where(qux: 'hi').includes(:bar).each do |foo|
puts foo.bar.name # This will actually load bar from the DB
end
There aren't usually great reasons for overriding this, especially if the dataset could be large-ish.
I'm seeing some weird behaviour in my models, and was hoping someone could shed some light on the issue.
# user model
class User < ActiveRecord::Base
has_many :events
has_and_belongs_to_many :attended_events
def attend(event)
self.attended_events << event
end
end
# helper method in /spec-dir
def attend_events(host, guest)
host.events.each do |event|
guest.attend(event)
end
end
This, for some reason inserts the event with id 2 before the event with id 1, like so:
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 2, name: "dummy-event", user_id: 1>, #<Event id: 1, name: "dummy-event", user_id: 1>
But, when I do something seemlingly random - like for instance change the attend_event method like so:
def attend_event(event)
self.attended_events << event
p self.attended_events # random puts statement
end
It gets inserted in the correct order.
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 1, name: "dummy-event", user_id: 1>, #<Event id: 2, name: "dummy-event", user_id: 1>
What am I not getting here?
Unless you specify an order on the association, associations are unordered when they are retrieved from the database (the generated sql won't have an order clause so the database is free to return things in whatever order it wants)
You can specify an order by doing (rails 4.x upwards)
has_and_belongs_to_many :attended_events, scope: -> {order("something")}
or, on earlier versions
has_and_belongs_to_many :attended_events, :order => "something"
When you've just inserted the object you may see a different object - here you are probably seeing the loaded version of the association, which is just an array (wrapped by the proxy)
My Rails app is getting the history of changes for two models, using the auditor gem like so:
#audit = Audit.where( :auditable_id => current_user.posts,
:auditable_type => "Post") +
Audit.where( :auditable_id => #comments,
:auditable_type => "Comment")
This works, but then I need to sort the whole #audit variable by the time the change was made.
I have two issues to solve.
the following methods have not worked: sort, sort_by, order
I need to figure out which of the following fields I need to sort by:
=> Audit(id: integer, auditable_id: integer, auditable_type: string, owner_id: integer, owner_type: string, user_id: integer, user_type: string, action: string, audited_changes: text, version: integer, comment: text, **created_at**: datetime)
1.9.3-p194 :002 > Audit.last
Audit Load (168.0ms) SELECT "audits".* FROM "audits" ORDER BY version DESC, created_at DESC LIMIT 1
=> #<Audit id: 5, auditable_id: 58, auditable_type: "Post", owner_id: 58, owner_type: "Post", user_id: 1, user_type: "User", action: "update", audited_changes: {"status"=>["to approve", "to review"], " **updated_at** "=>[2012-08-24 15:29:26 UTC, 2012-08-24 19:29:52 UTC]}, version: 2, comment: "post modified by Bruno Amaral ", created_at : "2012-08-24 19:29:52">
You should be able to build a single query to load all of the Audit objects you're interested in. Since it's a single query, the database can handle the sorting too.
The SQL you want to execute looks something like this:
SELECT *
FROM audits
WHERE
auditable_type = 'Post' AND auditable_id = … OR
auditable_type = 'Comment' AND auditable_id IN (…)
ORDER BY created_at
Which you should be able to build using Arel with something like this (assuming you're using Rails 3):
t = Audit.arel_table
for_post = t[:auditable_type].eq('Post').and(t[:auditable_id].eq(post.id))
for_comments = t[:auditable_type].eq('Comment').and(t[:auditable_id].in(comment_ids))
audits = Audit.where(for_post.or(for_comments)).order(:created_at)
For more information about building complex queries with Arel, see ASCIIcasts episode 215 and the Arel README.
If you don't like the Arel syntax, you can also use find_by_sql or pass a string to where, but bear in mind that using Arel will shield you from various kinds of errors and subtle difference between databases.
#audit = Audit.where( "auditable_id IN (?) OR auditable_id IN (?)",
current_user.posts,
#comments ).order_by( :created_at )