Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sorting_column to Field #2659

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions app/controllers/administrate/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,18 @@ def order
@order ||= Administrate::Order.new(
sorting_attribute,
sorting_direction,
association_attribute: order_by_field(
sorting_column: sorting_column(
dashboard_attribute(sorting_attribute)
)
)
end

def order_by_field(dashboard)
return unless dashboard.try(:options)
def sorting_column(dashboard_attribute)
return unless dashboard_attribute.try(:options)

dashboard.options.fetch(:order, nil)
dashboard_attribute.options.fetch(:sorting_column) {
dashboard_attribute.options.fetch(:order, nil)
}
end

def dashboard_attribute(attribute)
Expand Down
46 changes: 28 additions & 18 deletions app/views/administrate/application/_collection.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,35 @@ to display a collection of resources in an HTML table.
<tr>
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
<th class="cell-label
cell-label--<%= attr_type.html_class %>
cell-label--<%= collection_presenter.ordered_html_class(attr_name) %>
cell-label--<%= "#{collection_presenter.resource_name}_#{attr_name}" %>"
scope="col"
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
collection_presenter.order_params_for(attr_name, key: collection_field_name)
)) do %>
<%= t(
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
default: resource_class.human_attribute_name(attr_name).titleize,
) %>
<% if collection_presenter.ordered_by?(attr_name) %>
<span class="cell-label__sort-indicator cell-label__sort-indicator--<%= collection_presenter.ordered_html_class(attr_name) %>">
<svg aria-hidden="true">
<use xlink:href="#icon-up-caret" />
</svg>
</span>
cell-label--<%= attr_type.html_class %>
cell-label--<%= collection_presenter.ordered_html_class(attr_name) %>
cell-label--<%= "#{collection_presenter.resource_name}_#{attr_name}" %>"
scope="col"
<% if attr_type.sortable? %>
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>"
<% end %>
>
<% if attr_type.sortable? %>
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
collection_presenter.order_params_for(attr_name, key: collection_field_name)
)) do %>
<%= t(
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
default: resource_class.human_attribute_name(attr_name).titleize,
) %>
<% if collection_presenter.ordered_by?(attr_name) %>
<span class="cell-label__sort-indicator cell-label__sort-indicator--<%= collection_presenter.ordered_html_class(attr_name) %>">
<svg aria-hidden="true">
<use xlink:href="#icon-up-caret" />
</svg>
</span>
<% end %>
<% end %>
<% else %>
<%= t(
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
default: resource_class.human_attribute_name(attr_name).titleize,
) %>
<% end %>
</th>
<% end %>
Expand Down
4 changes: 4 additions & 0 deletions lib/administrate/field/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def self.searchable?
false
end

def self.sortable?
true
end

def self.field_type
to_s.split("::").last.underscore
end
Expand Down
8 changes: 8 additions & 0 deletions lib/administrate/field/deferred.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ def searchable_fields
end
end

def sortable?
options.fetch(:sortable, deferred_class.sortable?)
end

def sortable_field
options.fetch(:sortable_field, nil)
end

def permitted_attribute(attr, opts = {})
if options.key?(:foreign_key)
Administrate.warn_of_deprecated_option(:foreign_key)
Expand Down
23 changes: 21 additions & 2 deletions lib/administrate/field/has_many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,21 @@ def data
def order_from_params(params)
Administrate::Order.new(
params.fetch(:order, sort_by),
params.fetch(:direction, direction)
params.fetch(:direction, direction),
sorting_column: sorting_column(
associated_dashboard_attribute(params.fetch(:order, sort_by))
)
)
end

def order
@order ||= Administrate::Order.new(sort_by, direction)
@order ||= Administrate::Order.new(
sort_by,
direction,
sorting_column: sorting_column(
associated_dashboard_attribute(sort_by)
)
)
end

private
Expand All @@ -109,6 +118,16 @@ def display_candidate_resource(resource)
associated_dashboard.display_resource(resource)
end

def sorting_column(dashboard_attribute)
return unless dashboard_attribute.try(:options)

dashboard_attribute.options.fetch(:sorting_column, nil)
end

def associated_dashboard_attribute(attribute)
associated_dashboard.attribute_types[attribute.to_sym] if attribute
end

def sort_by
options[:sort_by]
end
Expand Down
4 changes: 4 additions & 0 deletions lib/administrate/field/password.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def self.searchable?
false
end

def self.sortable?
false
end

def truncate
data.to_s.gsub(/./, character)[0...truncation_length]
end
Expand Down
17 changes: 9 additions & 8 deletions lib/administrate/order.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
module Administrate
class Order
def initialize(attribute = nil, direction = nil, association_attribute: nil)
def initialize(attribute = nil, direction = nil, association_attribute: nil, sorting_column: nil)
@attribute = attribute
@direction = sanitize_direction(direction)
@association_attribute = association_attribute
# @association_attribute = association_attribute
@sorting_column = sorting_column || attribute
end

def apply(relation)
return order_by_association(relation) unless
reflect_association(relation).nil?

order = relation.arel_table[attribute].public_send(direction)
order = relation.arel_table[sorting_column].public_send(direction)

return relation.reorder(order) if
column_exist?(relation, attribute)
column_exist?(relation, sorting_column)

relation
end
Expand All @@ -33,7 +34,7 @@ def order_params_for(attr)

private

attr_reader :attribute, :association_attribute
attr_reader :attribute, :association_attribute, :sorting_column

def sanitize_direction(direction)
%w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
Expand Down Expand Up @@ -94,9 +95,9 @@ def order_by_id(relation)
end

def ordering_by_association_column?(relation)
association_attribute &&
(attribute != sorting_column) &&
column_exist?(
reflect_association(relation).klass, association_attribute.to_sym
reflect_association(relation).klass, sorting_column.to_sym
)
end

Expand All @@ -113,7 +114,7 @@ def order_by_association_id(relation)
end

def order_by_association_attribute(relation)
order_by_association_column(relation, association_attribute)
order_by_association_column(relation, sorting_column)
end

def order_by_association_column(relation, column_name)
Expand Down
2 changes: 1 addition & 1 deletion spec/dashboards/customer_dashboard_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
expect(fields[:name]).to eq(Field::String)
expect(fields[:email]).to eq(Field::Email)
expect(fields[:lifetime_value])
.to eq(Field::Number.with_options(prefix: "$", decimals: 2))
.to eq(Field::Number.with_options(prefix: "$", decimals: 2, sortable: false))
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/example_app/app/dashboards/customer_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class CustomerDashboard < Administrate::BaseDashboard
created_at: Field::DateTime,
email: Field::Email,
email_subscriber: Field::Boolean,
lifetime_value: Field::Number.with_options(prefix: "$", decimals: 2),
lifetime_value: Field::Number.with_options(prefix: "$", decimals: 2, sortable: false),
name: Field::String,
orders: Field::HasMany.with_options(limit: 2, sort_by: :id),
log_entries: Field::HasManyVariant.with_options(limit: 2, sort_by: :id),
Expand Down
2 changes: 1 addition & 1 deletion spec/example_app/app/dashboards/line_item_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class LineItemDashboard < Administrate::BaseDashboard
created_at: Field::DateTime,
updated_at: Field::DateTime,
order: Field::BelongsTo,
product: Field::BelongsTo,
product: Field::BelongsTo.with_options(sorting_column: :name),
quantity: Field::Number,
total_price: Field::Number.with_options(prefix: "$", decimals: 2),
unit_price: Field::Number.with_options(prefix: "$", decimals: 2)
Expand Down
5 changes: 3 additions & 2 deletions spec/example_app/app/dashboards/order_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ class OrderDashboard < Administrate::BaseDashboard
r.address_state,
r.address_zip
].compact.join("\n")
}
},
sorting_column: :address_zip
),
customer: Field::BelongsTo.with_options(order: "name"),
line_items: Field::HasMany.with_options(
collection_attributes: %i[product quantity unit_price total_price]
),
total_price: Field::Number.with_options(prefix: "$", decimals: 2),
total_price: Field::Number.with_options(prefix: "$", decimals: 2, sortable: false),
shipped_at: Field::DateTime,
payments: Field::HasMany
}
Expand Down
83 changes: 83 additions & 0 deletions spec/features/sort_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require "rails_helper"

feature "Sort" do
describe "sortable" do
context "when the field is sortable" do
it "is a link to sort by that field" do
visit admin_customers_path
expect(page).to have_link("Name")
end
end

context "when the field is NOT sortable" do
it "is not a link" do
visit admin_customers_path
expect(page).not_to have_link("Password")
end
end
end

scenario "admin sorts customers by name" do
dan = create(:customer, name: "Dan Croak")
bob = create(:customer, name: "Bob")
alice = create(:customer, name: "Alice")

visit admin_customers_path
click_on "Name"

expect(page).to have_css("table tr:nth-child(1)", text: alice.name)
expect(page).to have_css("table tr:nth-child(2)", text: bob.name)
expect(page).to have_css("table tr:nth-child(3)", text: dan.name)
end

scenario "admin sorts orders by full_address (virtual field)" do
create(:order, address_zip: "651", address_state: "UT")
create(:order, address_zip: "7", address_state: "WV")
create(:order, address_zip: "59543-0366", address_state: "NJ")

visit admin_orders_path
click_on "Full Address"

expect(page).to have_css("table tr:nth-child(1)", text: "NJ 59543-0366")
expect(page).to have_css("table tr:nth-child(2)", text: "UT 651")
expect(page).to have_css("table tr:nth-child(3)", text: "WV 7")
end

scenario "admin sorts customer's orders by full_address (virtual field)" do
customer = create(:customer, name: "Alice")
customer.orders << create(:order, address_zip: "651", address_state: "UT")
customer.orders << create(:order, address_zip: "7", address_state: "WV")
customer.orders << create(:order, address_zip: "59543-0366", address_state: "NJ")

visit admin_customer_path(customer)
click_on "Full Address"

within(table_for_attribute(:orders)) do
expect(page).to have_css("tr:nth-child(1)", text: "NJ 59543-0366")
end
end

scenario "admin sorts order's list_items by product name" do
product_1 = create(:product, name: "Monopoly 2")
product_2 = create(:product, name: "Monopoly 3")
product_3 = create(:product, name: "Monopoly 1")
customer = create(:customer)
order = create(:order, customer: customer)
order.line_items << create(:line_item, product: product_1)
order.line_items << create(:line_item, product: product_2)
order.line_items << create(:line_item, product: product_3)

visit admin_order_path(order)
click_on "Product"

within(table_for_attribute(:line_items)) do
expect(page).to have_css("tr:nth-child(1)", text: "Monopoly 1")
expect(page).to have_css("tr:nth-child(2)", text: "Monopoly 2")
expect(page).to have_css("tr:nth-child(3)", text: "Monopoly 3")
end
end

def table_for_attribute(attr_name)
find("table[aria-labelledby=#{attr_name}]")
end
end
8 changes: 4 additions & 4 deletions spec/lib/administrate/order_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
order = Administrate::Order.new(
:user,
nil,
association_attribute: "name"
sorting_column: "name"
)
relation = relation_with_association(
:belongs_to,
Expand All @@ -144,7 +144,7 @@
order = Administrate::Order.new(
:user,
nil,
association_attribute: "invalid_column_name"
sorting_column: "invalid_column_name"
)
relation = relation_with_association(
:belongs_to,
Expand Down Expand Up @@ -192,7 +192,7 @@
order = Administrate::Order.new(
:user,
nil,
association_attribute: "name"
sorting_column: "name"
)
relation = relation_with_association(
:has_one,
Expand All @@ -218,7 +218,7 @@
order = Administrate::Order.new(
:user,
nil,
association_attribute: "invalid_column_name"
sorting_column: "invalid_column_name"
)
relation = relation_with_association(
:has_one,
Expand Down
Loading
Loading