It would be nice if there was an option to configure the default order that record collection uses when displaying a list on the dashboard.
Something like:
class UserDashboard < Administrate::BaseDashboard
...
def default_order
resources.order(name: :desc)
end
end
@gylaz The user would choose the attribute used in default_order method ?
Is there a way to change the default order now, for all dashboard/models by default?
@avk As long as you're ok with overwriting a private method, you can definitely set a default order. You'll just have to check the source every update to make sure the private API you're using doesn't change.
For example, say you have a User model with email attribute. If you want to sort by email ascending by default, you could add the following to app/controllers/admin/users_controller.rb:
module Admin
class UsersController < Admin::ApplicationController
private
def order
if params[:order] && params[:direction]
@_order ||= Administrate::Order.new(params[:order], params[:direction)
else
@_order ||= Administrate::Order.new(email: :asc)
end
end
end
end
I found another way that worked for me, across all dashboards:
module Admin
class ApplicationController < Administrate::ApplicationController
before_filter :default_params
def default_params
params[:order] ||= "created_at"
params[:direction] ||= "desc"
end
end
end
馃憤 That is a way better way to set a default order.
I'm using a gem that handles ordering through a method #roots_and_descendants_preordered (specifically, the closure_tree gem, which has preordering https://github.com/mceachen/closure_tree/issues/38). Setting this as the default scope seemed to throw a SQL error from collection_presenter.attributes_for(resource).
default_scope -> { roots_and_descendants_preordered }
I wondered if there were a way to set a default order in administrate by calling a class method. Any thoughts? Thank you!
very nice @avk.
It would be nice to be able to sort nulls also. I tried "DESC NULLS LAST" which is fine in Postgres but I got an error from Administrate.
Direction "DESC NULLS LAST" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]
Fair enough Administrate.
In rails 5.1 I've had to change before_filter into before_action:
module Admin
class ApplicationController < Administrate::ApplicationController
before_action :default_params
def default_params
params[:order] ||= 'id'
params[:direction] ||= 'desc'
end
end
end
I鈥檓 going to close this as that seems like a good solution. If you鈥檙e not on Rails 5.1 yet, I鈥檇 recommend standardising on before_action, like @rozhok suggests. Thanks!
I am building a Rails 5.2.1 app with Administrate 0.11.0.
I tried @rozhok's solution to enforce a default sorting order in a dashboard index of a Recording model, like so:
before_action :default_params
def default_params
params[:order] ||= 'start'
params[:direction] ||= 'desc'
end
but the server log says:
Unpermitted parameter: :recording
Rendering admin/recordings/index.html.haml within layouts/admin/application
Unpermitted parameters: :order, :direction
Unpermitted parameters: :order, :direction
Unpermitted parameters: :order, :direction
Unpermitted parameters: :order, :direction
...and the desired sorting on the start field does not work.
I don't know if it's relevant, but when I click on the Start header in order to manually sort records (which indeed works), the URL becomes:
http://localhost:3000/admin/recordings?recording%5Bdirection%5D=asc&recording%5Border%5D=start
I am afraid I am missing something obvious, but I am thoroughly stumped...
@giuseb I also just ran into this, and can confirm the behavior changed between 0.10.0 and 0.11.0.
This piece in code expects a different nesting of params than before:
app/controllers/administrate/application_controller.rb in 0.11.0:
def order
@order ||= Administrate::Order.new(
params.fetch(resource_name, {}).fetch(:order, nil),
params.fetch(resource_name, {}).fetch(:direction, nil),
)
end
(expects order & direction to be under key resource_name)
vs.
app/controllers/administrate/application_controller.rb in 0.10.0:
def order
@order ||= Administrate::Order.new(params[:order], params[:direction])
end
(expects order & direction to be at root of params)
Here's what I did for now... mostly a workaround for not being able to do a deep_merge on params:
def default_params
resource_params = params.fetch(resource_name, {})
order = resource_params.fetch(:order, "created_at")
direction = resource_params.fetch(:direction, "desc")
params[resource_name] = resource_params.merge(order: order, direction: direction)
end
this is a much simpler workaround (copy past from the applicationcontroller). wouldnt it be a nobrainer to add configuration-options to set those defaults?
def order
@order ||= Administrate::Order.new(
params.fetch(resource_name, {}).fetch(:order, 'created_at'),
params.fetch(resource_name, {}).fetch(:direction, 'desc'),
)
end
+1 for adding a default of created_at (descending) to all dashboards. Thanks @phoet for the snippet.
If this is the solution you're going with, it should be added to the comments in the controllers. That's the second place I looked for a method I needed to overwrite (after the dashboard).
Hello all. Thank you for the discussion and the alternatives proposed. I'd be happy to accept a PR that introduced methods sorting_attribute and sorting_direction (or something similar), in Administrate::ApplicationController. It should include tests and documentation.
Tests and documentation are very important. I can't figure out how to use this. AT ALL.
Not working for Rails 6 :\
@atinder I use the code below in my Rails 6.0.2.2 app, no problem with Rails 6.
# app/controllers/admin/application_controller.rb
module Admin
class ApplicationController < Administrate::ApplicationController
....
def order
@order ||= Administrate::Order.new(
params.fetch(resource_name, {}).fetch(:order, default_sort[:order]),
params.fetch(resource_name, {}).fetch(:direction, default_sort[:direction]),
)
end
# override this in specific controllers as needed
def default_sort
{ order: :updated_at, direction: :desc }
end
...
end
end
thanks a lot @sedubois
@sedubois, can we improve our docs around this? Seems like it's still a bit confusing
Yes @nickcharlton that would be helpful. I can just paste that snippet somewhere why not. Where should it fit in the docs?
"Customising Dashboards", perhaps? It seems like the closest place with the current arrangement.
is there a way to order two data by 'last_name' and 'id' at the same time?
like example>
def order
@order ||= Administrate::Order.new(
params.fetch(resource_name, {}).fetch(:order, ['last_name'.'id']),
params.fetch(resource_name, {}).fetch(:direction, 'asc')
)
end
found a better way in documentation
def default_sorting_attribute
:id
end
def default_sorting_direction
:desc
end
Thanks @atinder. Works for me on Rails 6
is there a way we can sort by two columns? example i have 2 columns, by "last_name" and "amount"?
@valcaro87 the sorting example shared by @atinder above is for all tables, where it makes sense to sort by universal items such as id or created_at columns.
Did you want to sort a single table or all your tables have the last_name and amount columns?
@kaka-ruto single table.. i have "sales" table. i want to sort by "last_name" and their "amount" in desc order..
for example, in my table, 2 employees having the same family name.. so the order should be:
( we can achieved this in sql by: SELECT * FROM sales ORDER BY last_name DESC, amount DESC; )
Done a little digging and it seems at the moment you can only sort by a single column at a time @valcaro87 -> codebase
Something close that could help is to use collection filters
If you are not looking for much flexibility, it might be possible to sort by multiple fields. Let me explain.
It's possible to define a default sorting to be as complex as you want. This is an example based on Administrate's own example app. It overrides CustomersController#scoped_resource to define a default ordering for customers:
class Admin::CustomersController < Admin::ApplicationController
private
def scoped_resource
Customer.where(hidden: false).order(:country_code, :name)
end
end
If default ordering is all you need, that's your solution.
If instead you are looking for a lot of flexibility, that's probably not supported at the moment. For example, say that you are in the same customers index and you want the users to be able to click on "Email", then "Territory", and have all the "Australia" records coming first, ordered by email.
Not only this is not possible at the moment, but also it would be complex to implement. We would need to make the links in the table headers "remember" what the previous clicked columns were. So for example, if you click "Email" and then "Territory", the URI would look something like /admin/customers?customer[direction]=asc,asc&customer[order]=territory,email. That way Administrate would know which columns are involved in the sorting, and in which order.
There's an intermediate way though...
You could override the index template to provide your own links to "blessed" orders, or in general scopes of any type that you need.
For example, I tried by adding this link to the index template of the example app:
</header>
<section class="main-content__body main-content__body--flush">
+ <p><%= link_to "Special order", request.path + "?special-order" %></p>
+
<%= render(
"collection",
collection_presenter: page,
Then I can read this special-order query param from scoped_resource and implement any order that I wish:
class Admin::CustomersController < Admin::ApplicationController
private
def scoped_resource
if @special_order
Customer.where(hidden: false).order(:country_code, :name)
else
Customer.where(hidden: false)
end
end
end
I call it @special_order instead of params["special-order"] because I need one little hack to avoid an ActionController::UnpermittedParameters error. I do this by adding the following to Admin::ApplicationController:
before_action :read_special_order
def read_special_order
@special_order = params.key?("special-order")
params.delete("special-order")
end
This registers the presence of the query param in the instance variable, then deletes the param so that it doesn't cause an error later on.
Most helpful comment
I found another way that worked for me, across all dashboards: