Order by in Rails adding it's own order - ruby-on-rails

In the Rails tutorial, it says that I can simply use .order("something") and it'd work. However, when I write Course.order("name DESC") I get the query:
SELECT "courses".* FROM "courses" ORDER BY name ASC, name DESC
When I really want (notice that it's just ordered by name DESC):
SELECT "courses".* FROM "courses" ORDER BY name DESC
How could I force it through?

if you have a default order befined by a default_scope, you can override by using reorder
Order.reorder('name DESC')
UPDATE: Using unscoped will also work but be wary that this totally removes all scopes defined on the query. For example, the following will all return the same sql
Order.where('id IS NOT NULL').unscoped.order('name DESC')
Order.unscoped.order('name DESC')
Order.scope1.scope2.unscoped.order('name DESC')
current_user.orders.unscoped.order('name DESC')

It was because I was using default_scope in the model that caused it. Running this avoids the scoping:
Course.unscoped.order("name DESC")
Edit: for future reference this is code smell and default_scope should be used carefully because often developers will forget (months after writing code) that default_scope is set and bite you back.

Related

Creating ActiveRecord scope with multiple conditionals

I have a Rails application with a number of Products, some of which are associated with an Issue model. Some of these products have an issue_id (so an Issue has_many products) and some do not. The products without an issue ID are a new addition I'm working on.
I previously had a named scope so that I can list products using Product.published, which looks like this:
scope :published, -> {
joins(:issue).reorder('products.created_at ASC, products.number ASC')
.where('products.status = ?', Product.statuses[:available])
.where('issues.status = ?', Issue.statuses[:published])
}
The result of this is that I can find only products that are associated with a published issue (think magazine issue).
I'm now adding products that will not be associated with a particular issue but will still have a draft/available state. The above scope does not find these products, as it looks for an issue_id that does not exist.
I thought I could modify the scope like this, adding the OR issue_id IS NULL part in the last line:
scope :published, -> {
joins(:issue).reorder('products.created_at ASC, products.number ASC')
.where('products.status = ?', Product.statuses[:available])
.where('issues.status = ? OR issue_id IS NULL', Issue.statuses[:published])
}
But this doesn't work. I still only get 'available' products associated with a 'published' issue. The products without an issue_id are not included in the returned collection.
(There is a window in which a product will be set to available before its associated issue is published, so for these situations I do need to check the status of both records.)
Here's the SQL generated by the above (wrapped for readability):
pry(main)> Product.published.to_sql
=> "SELECT `products`.* FROM `products` INNER JOIN `issues` ON `issues`.`id` =
`products`.`issue_id` WHERE (products.status = 1) AND (issues.status = 1 OR
issue_id IS NULL) ORDER BY products.created_at ASC, products.number ASC"
I've already created a Product class method that takes an argument as an alternate approach but doesn't work in all cases because I'm often looking up a product based on the ID without knowing in advance whether there's an Issue association or not (eg, for the product's show view).
Also, Product.published is nice and concise and the alternative is to load all published products (eg, Product.where(:status => :published)) and then iterate through to remove those associated with a not-yet-published issue in a second operation.
I feel like there's something I'm not quite grasping about doing more complex queries within a scope. My ideal outcome is a modified scope that can return available products, both with and without an issue, and without supplying an argument.
Is this possible, or should I resign myself to finding an alternate approach now that I'm adding these unassociated products?
The problem is that you are using joins(:issue). That method does an INNER JOIN between products and issues tables and discards all the products that doesn't have an issue_id. Maybe you could use LEFT JOIN so you can keep all the products regardless they have an issue.
scope :published, -> {
joins('LEFT JOIN issues ON issues.id = products.issue_id')
.select('products.*')
.reorder('products.created_at ASC, products.number ASC')
.where('products.status = ?', Product.statuses[:available])
.where('issues.status = ? OR products.issue_id IS NULL', Issue.statuses[:published])
}

Order and limit clauses unexpectedly passed down to scope

(the queries here have no sensible semantic but I chose them for the sake of simplicity)
Project.limit(10).where(id: Project.select(:id))
generates as expected the following SQL query:
SELECT
"projects".*
FROM
"projects"
WHERE
"projects"."id" IN (
SELECT
"projects"."id"
FROM
"projects"
) LIMIT 10
But if I defined in my Project class the method
def self.my_filter
where(id: Project.select(:id))
end
Then
Project.limit(10).my_filter
generates the following query
SELECT
"projects".*
FROM
"projects"
WHERE
"projects"."id" IN (
SELECT
"projects"."id"
FROM
"projects" LIMIT 10
) LIMIT 10
See how the LIMIT 10 has now been also applied to the subquery.
Same issue when using a .order clause.
It happens with Rails 4.2.2 and Rails 3.2.20. It happens when the subquery is done on the Project table, it does happens if the subquery is done on another table.
Is there something I'm doing wrong here or do you think it is a Rails bug?
A workaround is to build my_filter by explicitly adding limit(nil).reorder(nil) to it but it is hackish.
EDIT: another workaround is to append the limit clause after the my_filter scope: Project.my_filter.limit(10).
This is actually a feature. Class methods work similar to scopes in ActiveRecord models.
And if you want to remove the already added scopes, you can either use unscoped, either call the method on a class directly, not on a scope:
def self.my_filter
unscoped.where(id: Project.select(:id))
end
# or
Project.my_filter
Your class method is applied in a way you may not be expecting:
Project.limit(10) # => a relation, not the Project class
.my_filter # => calling a class method on a relation
# Does, the following, suddenly:
# scoping { Project.my_filter }
# It's a relation's wrapper
From: .../ruby-2.0.0-p598/gems/activerecord-4.1.6/lib/active_record/relation.rb # line 281:
Owner: ActiveRecord::Relation
Visibility: public
Signature: scoping()
Scope all queries to the current scope.
Comment.where(post_id: 1).scoping do
Comment.first
end
# => SELECT "comments".* FROM "comments"
# WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
Please check unscoped if you want to remove all previous scopes (including
the default_scope) during the execution of a block.
Inside that scoping block, your class will include all the scoping rules of a relation it was built from into all queries, as scoping will enforce context. This is done so class methods can be properly chained, while still retaining the correct self. Of course, when you try using a class method inside the class method, stuff blows up.
In your first, "expected outcome" example, where is "natively" defined on relations, so no scope enforcement takes place: it's just not necessary.
Yeah, documentation hints that you can use unscoped in your nested query, like so:
def my_filter
where(id: Project.unscoped.select(:id))
end
...since that's where you need the "bare basis". Or, as you've already found out, you can just place limit at the end:
Project.my_filter.limit(10)
...here, at the time my_filter gets to execute, scoping will do effectively nothing: there will be no context built up to this point.

Add index with sort order?

In Rails, how can I add an index in a migration to a Postgres database with a specific sort order?
Ultimately wanting to pull off this query:
CREATE INDEX index_apps_kind_release_date_rating ON apps(kind, itunes_release_date DESC, rating_count DESC);
But right now in my migration I have this:
add_index :apps, [:kind, 'itunes_release_date desc', 'rating_count desc'], name: 'index_apps_kind_release_date_rating'
Which spits out this:
CREATE INDEX "index_apps_kind_release_date_rating" ON "apps" ("kind", "itunes_release_date desc", "rating_count desc")
Which errors out:
PG::Error: ERROR: column "itunes_release_date desc" does not exist
Rails now supports specifying index order in migrations:
def change
add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
end
From http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index
You do not need to specify DESC in the index. It will give a small speed benefit for the queries, that use this particular ordering, but in general - an index can be used for any oredring of a column.
Looks like Rails doesn't support ordered indexes.
I suspect that you can safely remove the two desc, btw: kind is in your where clause based on your previous question, so PG should happily look up the index in reverse order.

Rails: How to sort many-to-many relation

I have a many-to-many relationship between a model User and Picture. These are linked by a join table called Picturization.
If I obtain a list of users of a single picture, i.e. picture.users -> how can I ensure that the result obtained is sorted by either creation of the Picturization row (i.e. the order at which a picture was associated to a user). How would this change if I wanted to obtain this in order of modification?
Thanks!
Edit
Maybe something like
picture.users.where(:order => "created_at")
but this created_at refers to the created_at in picturization
Have an additional column something like sequence in picturization table and define sort order as default scope in your Picturization
default_scope :order => 'sequence ASC'
If you want default sort order based on modified_at then use following default scope
default_scope :order => 'modified_at DESC'
You can specify the table name in the order method/clause:
picture.users.order("picturizations.created_at DESC")
Well, in my case, I need to sort many-to-many relation by a column named weight in the middle-table. After hours of trying, I figured out two solutions to sort many-to-many relation.
Solution1: In Rails Way
picture.users.where(:order => "created_at")
cannot return a ActiveRecord::Relation sorted by Picturization's created_at column.
I have tried to rewrite a default_scope method in Picturization, but it does not work:
def self.default_scope
return Picturization.all.order(weight: :desc)
end
Instead, first, you need to get the ids of sorted Picturization:
ids = Picturization.where(user_id:user.id).order(created_at: :desc).ids
Then, you can get the sorted objects by using MySQL field functin
picture.users.order("field(picturizations.id, #{ids.join(",")})")
which generates SQL looks like this:
SELECT `users`.*
FROM `pictures` INNER JOIN `picturizations`
ON `pictures`.`id` = `picturizations`.`picture_id`
WHERE `picturizations`.`user_id = 1#for instance
ORDER BY field(picturizations.id, 9,18,6,8,7)#for instance
Solution2: In raw SQL Way
you can get the answer directly by using an order by function:
SELECT `users`.*
FROM `pictures` INNER JOIN `picturizations`
ON `pictures`.`id` = `picturizations`.`picture_id`
WHERE `picturizations`.`user_id = 1
order by picturizations.created_at desc

Why is my controller not ordering by what Im telling to order by?

My controller action is calling all images belonging to a specific user and Im trying to order by its position (Im using the acts_as_list gem) but when I go to the page, the images are FIRST sorted by the created date, and then position (according to rails console). But because it orders by the creation date first my controller order is being ignored which is no good.
here is my action
def manage_tattoos
#tattoos = current_member.tattoos.order("position DESC")
end
and my server console shows:
Tattoo Load (0.6ms) SELECT `tattoos`.* FROM `tattoos` WHERE
(`tattoos`.member_id = 1) ORDER BY tattoos.created_at DESC, position DESC
Have you tried specifiing the order in the association?
class TodoList < ActiveRecord::Base
has_many :todo_items, :order => "position"
end
Does your association between Member and Tattoo have an order clause? E.g.
# Member class
has_many :tattoos, :order => "created_at DESC"
Is this the case? If so you might need to change your query to something like:
Tattoo.where(:member_id=>current_member.id).order("position DESC")
I'm unaware of a way to clear the order clause from an ActiveRecord association.
Or specify what to do with created_at:
current_member.tattoos.order("position DESC, created_at DESC")
Well it seems Im just very absent minded and had completely forgotten that I set a default scope on the model. Took that out and everything is fine

Resources