Skip to content

Commit dfc8198

Browse files
committed
Fixes #38024 - Add ordering by virtual columns
1 parent 0ea2685 commit dfc8198

File tree

7 files changed

+84
-3
lines changed

7 files changed

+84
-3
lines changed

app/controllers/api/v2/base_controller.rb

+14
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,20 @@ def self.hide_taxonomy_options
175175
param :organization_id, Integer, :show => false
176176
end
177177
end
178+
179+
def resource_scope_for_index(...)
180+
virt_column = params[:order]&.split(' ')&.first
181+
select_method = "select_#{virt_column}"
182+
scope = resource_scope(...)
183+
184+
if virt_column.blank? || resource_class.columns_hash[virt_column] || !scope.respond_to?(select_method)
185+
super(...)
186+
else
187+
ordered_scope = scope.send(select_method).search_for(params[:search]).order(params[:order])
188+
return ordered_scope if paginate_options[:per_page] == 'all'
189+
ordered_scope.paginate(**paginate_options)
190+
end
191+
end
178192
end
179193
end
180194
end

app/controllers/application_controller.rb

+17-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,13 @@ def remote_user_provided?
232232
end
233233

234234
def resource_base_with_search
235-
resource_base.search_for(params[:search], :order => params[:order])
235+
virt_column = params[:order]&.split(' ')&.first
236+
select_method = "select_#{virt_column}"
237+
if virt_column.blank? || model_of_controller.columns_hash[virt_column] || !resource_base.respond_to?(select_method)
238+
resource_base.search_for(params[:search], :order => params[:order])
239+
else
240+
resource_base.send(select_method).search_for(params[:search]).order(params[:order])
241+
end
236242
end
237243

238244
def resource_base_search_and_page(tables = [])
@@ -393,6 +399,16 @@ def parameter_filter_context
393399
Foreman::ParameterFilter::Context.new(:ui, controller_name, params[:action])
394400
end
395401

402+
def wrap_for_virt_column_select(base_scope)
403+
virt_column = params[:order]&.split(' ')&.first
404+
select_method = "select_#{virt_column}"
405+
if virt_column.blank? || model_of_controller.columns_hash[virt_column] || !base_scope.respond_to?(select_method)
406+
base_scope.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page])
407+
else
408+
base_scope.send(select_method).search_for(params[:search]).order(params[:order]).paginate(:page => params[:page], :per_page => params[:per_page])
409+
end
410+
end
411+
396412
class << self
397413
def parameter_filter_context
398414
Foreman::ParameterFilter::Context.new(:ui, controller_name, nil)

app/controllers/roles_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class RolesController < ApplicationController
2222

2323
def index
2424
params[:order] ||= 'name'
25-
@roles = Role.authorized(:view_roles).search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page])
25+
@roles = resource_base_search_and_page
2626
end
2727

2828
def new

app/models/role.rb

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Role < ApplicationRecord
3939
where("#{compare} builtin = 0")
4040
}
4141
scope :cloned, -> { where.not(:cloned_from_id => nil) }
42+
scope :select_locked, -> { select("roles.*,(origin IS NOT NULL AND builtin <> #{BUILTIN_DEFAULT_ROLE}) as locked") }
4243

4344
validates_lengths_from_database
4445
before_destroy :check_deletable

developer_docs/how_to_create_a_plugin.asciidoc

+33-1
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ module ForemanRemoteExecution
15211521
# We need to make sure, there's a AR relation we'd be searching on, because the class name can't be determined by the
15221522
# association name, it needs to be specified explicitly (as a string). Similarly for the foreign key.
15231523
has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id'
1524-
1524+
15251525
# Then we define the searching, the relation is the association name we define on a line above
15261526
# :rename key indicates the term user will use to search hosts for a given state, the convention is $feature_status
15271527
# :complete_value must be a hash with symbolized keys specifying all possible state values, otherwise the autocompletion
@@ -2391,6 +2391,38 @@ Foreman Webhooks plugin ships with an example "Remote Execution Host Job" templa
23912391

23922392
You can find all observable events by calling `Foreman::EventSubscribers.all_observable_events` in the Rails console.
23932393

2394+
=== How to order by a virtual column
2395+
_Requires Foreman 3.14 or higher, set `requires_foreman '>= 3.14'` in
2396+
engine.rb_
2397+
2398+
NOTE: The following assumes usage of:
2399+
* `resource_scope_for_index` in the API controller's index action.
2400+
* `resource_base_with_search` or `resource_base_search_and_page` in the UI controller's index action.
2401+
2402+
Starting Foreman 3.14, both UI and API controllers can order by virtual columns.
2403+
This is achieved by using the `select_<virtual_column>` scope in the model.
2404+
The scope should return the virtual column in the select statement.
2405+
2406+
[source, ruby]
2407+
....
2408+
# app/models/model.rb
2409+
scope :select_virtual_column, lambda {
2410+
select("model_table.*,(column,column2) as virtual_column")
2411+
}
2412+
....
2413+
2414+
NOTE: In case your UI controller doesn't use `resource_base_with_search` or `resource_base_search_and_page`,
2415+
you can use the `wrap_for_virt_column_select` method in the controller to order by virtual columns.
2416+
2417+
[source, ruby]
2418+
....
2419+
# app/controllers/plugin/controller.rb
2420+
def index
2421+
# Model can be already scoped by other means
2422+
@objects = wrap_for_virt_column_select(Model)
2423+
end
2424+
....
2425+
23942426
[[translating]]
23952427
== Translating
23962428

test/controllers/api/v2/roles_controller_test.rb

+13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ class Api::V2::RolesControllerTest < ActionController::TestCase
1212
assert_equal Role.order(:name).pluck(:name), roles['results'].map { |r| r['name'] }
1313
end
1414

15+
test "should order index by locked" do
16+
unlocked_role = FactoryBot.create(:role, :name => "unlocked role", :origin => '', :builtin => 0)
17+
get :index, params: { :order => "locked DESC" }
18+
assert_response :success
19+
roles = ActiveSupport::JSON.decode(@response.body)
20+
assert_equal unlocked_role.id, roles['results'].first['id']
21+
22+
get :index, params: { :order => "locked ASC" }
23+
assert_response :success
24+
roles = ActiveSupport::JSON.decode(@response.body)
25+
assert_not_equal unlocked_role.id, roles['results'].first['id']
26+
end
27+
1528
test "should show individual record" do
1629
get :show, params: { :id => roles(:manager).to_param }
1730
assert_response :success

test/controllers/roles_controller_test.rb

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ class RolesControllerTest < ActionController::TestCase
6161
assert_not_nil Role.find_by_id(roles(:default_role).id)
6262
end
6363

64+
test "should order index by locked" do
65+
get :index, params: { order: "locked DESC" }, session: set_session_user
66+
assert_response :success
67+
end
68+
6469
context "with taxonomies" do
6570
before do
6671
@permission1 = FactoryBot.create(:permission, :domain, :name => 'permission1')

0 commit comments

Comments
 (0)