Skip to content

Commit

Permalink
Add sorting_culmn to Field
Browse files Browse the repository at this point in the history
  • Loading branch information
goosys committed Nov 5, 2024
1 parent f9c5f1a commit 4fc6c46
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 41 deletions.
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

0 comments on commit 4fc6c46

Please sign in to comment.