active_element 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +3 -7
- data/active_element.gemspec +1 -1
- data/app/assets/javascripts/active_element/application.js +3 -1
- data/app/assets/javascripts/active_element/popover.js +6 -0
- data/app/assets/javascripts/active_element/setup.js +12 -0
- data/app/assets/javascripts/active_element/{search_field.js → text_search_field.js} +2 -2
- data/app/assets/javascripts/active_element/toast.js +8 -0
- data/app/assets/stylesheets/active_element/application.scss +65 -1
- data/app/controllers/active_element/application_controller.rb +12 -18
- data/app/views/active_element/components/form/_label.html.erb +2 -2
- data/app/views/active_element/components/form/_templates.html.erb +8 -8
- data/app/views/active_element/components/form.html.erb +3 -3
- data/app/views/active_element/components/table/_collection_row.html.erb +3 -3
- data/app/views/active_element/components/table/collection.html.erb +1 -1
- data/app/views/active_element/components/table/item.html.erb +3 -3
- data/app/views/active_element/navbar/_menu.html.erb +2 -2
- data/app/views/layouts/active_element.html.erb +21 -23
- data/config/routes.rb +10 -1
- data/lib/active_element/components/button.rb +6 -41
- data/lib/active_element/components/form.rb +12 -3
- data/lib/active_element/components/text_search/active_record_authorization.rb +13 -0
- data/lib/active_element/components/text_search/authorization.rb +117 -0
- data/lib/active_element/components/text_search/component.rb +118 -0
- data/lib/active_element/components/text_search/sql.rb +107 -0
- data/lib/active_element/components/text_search.rb +23 -0
- data/lib/active_element/components/util/decorator.rb +2 -2
- data/lib/active_element/components/util/record_path.rb +84 -0
- data/lib/active_element/components/util.rb +7 -2
- data/lib/active_element/components.rb +1 -0
- data/lib/active_element/controller_action.rb +9 -10
- data/lib/active_element/controller_interface.rb +78 -0
- data/lib/active_element/permissions_check.rb +33 -29
- data/lib/active_element/permissions_report.rb +57 -0
- data/lib/active_element/route.rb +1 -1
- data/lib/active_element/routes.rb +1 -1
- data/lib/active_element/version.rb +1 -1
- data/lib/active_element.rb +24 -10
- data/lib/tasks/active_element.rake +1 -16
- data/rspec-documentation/pages/Components/Tables.md +2 -2
- data/rspec-documentation/spec_helper.rb +1 -1
- metadata +19 -12
- data/app/controllers/active_element/text_searches_controller.rb +0 -189
- data/lib/active_element/active_record_text_search_authorization.rb +0 -12
- data/lib/active_element/colorized_string.rb +0 -33
@@ -3,6 +3,8 @@
|
|
3
3
|
module ActiveElement
|
4
4
|
# Verifies provided permissions against required permissions.
|
5
5
|
class PermissionsCheck
|
6
|
+
include Paintbrush
|
7
|
+
|
6
8
|
def initialize(required:, actual:, controller_path:, action_name:, rails_component:)
|
7
9
|
@required = required.presence || []
|
8
10
|
@actual = normalized(actual)
|
@@ -18,41 +20,58 @@ module ActiveElement
|
|
18
20
|
|
19
21
|
def message
|
20
22
|
return development_environment_message if rails_component.environment == 'development'
|
21
|
-
return
|
23
|
+
return permitted_message if permitted?
|
22
24
|
|
23
|
-
|
25
|
+
forbidden_message
|
24
26
|
end
|
25
27
|
|
26
28
|
def missing
|
27
29
|
@missing ||= applicable.reject do |permission|
|
28
|
-
|
29
|
-
|
30
|
+
next true if permission.fetch(:always, false)
|
31
|
+
|
32
|
+
actual.include?(permission.fetch(:with).to_s)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
def applicable
|
33
37
|
@applicable ||= default_permissions + required_permissions
|
34
38
|
end
|
35
39
|
|
40
|
+
def applicable_permissions
|
41
|
+
applicable.map { |permission| permission.fetch(:with) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def missing_permissions
|
45
|
+
missing.map { |permission| permission.fetch(:with) }
|
46
|
+
end
|
47
|
+
|
36
48
|
private
|
37
49
|
|
38
50
|
attr_reader :required, :actual, :controller_name, :action_name, :rails_component
|
39
51
|
|
40
52
|
def development_environment_message
|
41
|
-
"Bypassed permission(s) in development environment: #{
|
53
|
+
"Bypassed permission(s) in development environment: #{applicable_permissions.join(', ')}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def permitted_message
|
57
|
+
paintbrush { green "User access granted for permission(s): #{cyan applicable_permissions.join(', ')}" }
|
58
|
+
end
|
59
|
+
|
60
|
+
def forbidden_message
|
61
|
+
paintbrush { red "User access forbidden. Missing user permission(s): #{cyan missing_permissions.join(', ')}" }
|
42
62
|
end
|
43
63
|
|
44
64
|
def default_permissions
|
45
65
|
return [] if normalized_action.nil?
|
46
66
|
|
47
|
-
[
|
67
|
+
[{
|
68
|
+
action: normalized_action,
|
69
|
+
with: "can_#{normalized_action}_#{rails_component.application_name}_#{controller_name}"
|
70
|
+
}]
|
48
71
|
end
|
49
72
|
|
50
73
|
def required_permissions
|
51
|
-
@required_permissions ||= required.
|
52
|
-
next nil unless applicable?(options)
|
53
|
-
|
54
|
-
permission
|
55
|
-
end.compact
|
74
|
+
@required_permissions ||= required.select { |options| applicable?(options) }
|
56
75
|
end
|
57
76
|
|
58
77
|
def normalized_action
|
@@ -74,28 +93,13 @@ module ActiveElement
|
|
74
93
|
end
|
75
94
|
|
76
95
|
def applicable?(options)
|
77
|
-
|
78
|
-
return true if only_applicable?(options)
|
79
|
-
return false if except_applicable?(options)
|
80
|
-
|
81
|
-
false
|
82
|
-
end
|
83
|
-
|
84
|
-
def only_applicable?(options)
|
85
|
-
return false unless options.key?(:only)
|
86
|
-
|
87
|
-
normalized(options.fetch(:only)).include?(action_name)
|
88
|
-
end
|
89
|
-
|
90
|
-
def except_applicable?(options)
|
91
|
-
return false unless options.key?(:except)
|
92
|
-
|
93
|
-
normalized(options.fetch(:except)).include?(action_name)
|
96
|
+
options.fetch(:action).to_s == action_name
|
94
97
|
end
|
95
98
|
|
96
99
|
def raise_unprotected_route_error
|
97
100
|
raise UnprotectedRouteError,
|
98
|
-
"#{controller_name.titleize.tr(' ', '')}##{action_name} must be protected with
|
101
|
+
"#{controller_name.titleize.tr(' ', '')}##{action_name} must be protected with " \
|
102
|
+
'`active_element.permit_action`'
|
99
103
|
end
|
100
104
|
end
|
101
105
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveElement
|
4
|
+
# Generates a report of all permissions used by a given application. Used by `rake active_element:permissions`
|
5
|
+
# to provide a convenient interface to listing all required permissions so that they can be provisioned to
|
6
|
+
# users as needed.
|
7
|
+
class PermissionsReport
|
8
|
+
include Paintbrush
|
9
|
+
|
10
|
+
COLOR_MAP = { list: :cyan, view: :blue, create: :green, delete: :red, edit: :yellow, text: :white }.freeze
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
ActiveElement.eager_load_controllers
|
14
|
+
ActiveElement.eager_load_models
|
15
|
+
@buffer = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def report
|
19
|
+
generate
|
20
|
+
buffer.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :buffer
|
26
|
+
|
27
|
+
def generate
|
28
|
+
buffer << paintbrush { blue "\nThe following user permissions are used by this application:\n" }
|
29
|
+
permissions.each do |permission|
|
30
|
+
buffer << paintbrush { white " * #{public_send(color(permission), permission)}" }
|
31
|
+
end
|
32
|
+
buffer << "\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
def color(permission)
|
36
|
+
COLOR_MAP.find do |action, _|
|
37
|
+
permission.include?("_#{action}_")
|
38
|
+
end&.last || :purple
|
39
|
+
end
|
40
|
+
|
41
|
+
def permissions
|
42
|
+
routes.map(&:permissions).flatten.sort.uniq + text_search_permissions
|
43
|
+
end
|
44
|
+
|
45
|
+
def text_search_permissions
|
46
|
+
ActiveElement::Components::TextSearch.authorized_text_searches.map do |model, with, providing|
|
47
|
+
(Array(with) + Array(providing)).map do |field|
|
48
|
+
ActiveElement::Components::TextSearch::Authorization.permission_for(model: model, field: field)
|
49
|
+
end
|
50
|
+
end.flatten.uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def routes
|
54
|
+
ActiveElement::Routes.new(rails_component: ActiveElement::RailsComponent.new(Rails))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/active_element/route.rb
CHANGED
@@ -41,7 +41,7 @@ module ActiveElement
|
|
41
41
|
|
42
42
|
def descendants_with_permissions
|
43
43
|
@descendants_with_permissions ||= descendants.map do |controller_class|
|
44
|
-
[controller_class.new, controller_class.
|
44
|
+
[controller_class.new, controller_class.active_element.permissions]
|
45
45
|
end.compact
|
46
46
|
end
|
47
47
|
|
data/lib/active_element.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'faraday'
|
4
3
|
require 'rouge'
|
5
4
|
require 'kaminari'
|
6
5
|
require 'sassc'
|
7
6
|
require 'bootstrap'
|
8
7
|
require 'active_record'
|
9
|
-
|
8
|
+
require 'paintbrush'
|
10
9
|
|
11
10
|
require_relative 'active_element/version'
|
12
|
-
require_relative 'active_element/active_record_text_search_authorization'
|
13
|
-
require_relative 'active_element/colorized_string'
|
14
11
|
require_relative 'active_element/active_menu_link'
|
15
12
|
require_relative 'active_element/permissions_check'
|
13
|
+
require_relative 'active_element/permissions_report'
|
14
|
+
require_relative 'active_element/controller_interface'
|
16
15
|
require_relative 'active_element/controller_action'
|
17
16
|
require_relative 'active_element/rails_component'
|
18
17
|
require_relative 'active_element/route'
|
@@ -30,6 +29,8 @@ module ActiveElement
|
|
30
29
|
class << self
|
31
30
|
attr_writer :application_name, :navbar_items
|
32
31
|
|
32
|
+
include Paintbrush
|
33
|
+
|
33
34
|
def application_title
|
34
35
|
@application_name || RailsComponent.new(Rails).application_name.titleize
|
35
36
|
end
|
@@ -38,6 +39,14 @@ module ActiveElement
|
|
38
39
|
@navbar_items || inferred_navbar_items(user)
|
39
40
|
end
|
40
41
|
|
42
|
+
def warning(message)
|
43
|
+
warn "#{log_tag} #{paintbrush { yellow(message) }}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def log_tag
|
47
|
+
paintbrush { cyan "[#{blue 'ActiveElement'}]" }
|
48
|
+
end
|
49
|
+
|
41
50
|
def active_path_class(user:, current_navbar_item:, current_path:, controller_path:, action_name:)
|
42
51
|
if ActiveMenuLink.new(
|
43
52
|
rails_component: RailsComponent.new(Rails),
|
@@ -71,12 +80,17 @@ module ActiveElement
|
|
71
80
|
false
|
72
81
|
end
|
73
82
|
|
83
|
+
def eager_load_models
|
84
|
+
eager_load(:models)
|
85
|
+
end
|
86
|
+
|
74
87
|
def eager_load_controllers
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
88
|
+
eager_load(:controllers)
|
89
|
+
end
|
90
|
+
|
91
|
+
def eager_load(resource)
|
92
|
+
suffix = resource == :controllers ? '_controller' : nil
|
93
|
+
Rails.root.join("app/#{resource}").glob("**/*#{suffix}.rb").each { |path| require path }
|
80
94
|
end
|
81
95
|
|
82
96
|
private
|
@@ -90,7 +104,7 @@ module ActiveElement
|
|
90
104
|
|
91
105
|
def user_routes(user)
|
92
106
|
ActiveElement::Routes.new(
|
93
|
-
permissions: user
|
107
|
+
permissions: user&.permissions,
|
94
108
|
rails_component: ActiveElement::RailsComponent.new(Rails)
|
95
109
|
)
|
96
110
|
end
|
@@ -3,21 +3,6 @@
|
|
3
3
|
namespace :active_element do
|
4
4
|
desc 'Displays all permissions used by this application'
|
5
5
|
task permissions: :environment do
|
6
|
-
ActiveElement.
|
7
|
-
routes = ActiveElement::Routes.new(rails_component: ActiveElement::RailsComponent.new(Rails))
|
8
|
-
permissions = routes.map(&:permissions).flatten.sort.uniq
|
9
|
-
$stdout.puts ActiveElement::ColorizedString.new(
|
10
|
-
"\nThe following user permissions are used by this application:\n",
|
11
|
-
color: :light_blue
|
12
|
-
).value
|
13
|
-
permissions.each do |permission|
|
14
|
-
color = { list: :cyan, view: :blue, create: :green, delete: :red, edit: :yellow }.find do |action, _|
|
15
|
-
permission.include?("_#{action}_")
|
16
|
-
end&.last || :purple
|
17
|
-
$stdout.puts(' ' \
|
18
|
-
"#{ActiveElement::ColorizedString.new('*', color: :white).value} " \
|
19
|
-
"#{ActiveElement::ColorizedString.new(permission, color: color).value}")
|
20
|
-
end
|
21
|
-
$stdout.puts
|
6
|
+
$stdout.puts ActiveElement::PermissionsReport.new.report
|
22
7
|
end
|
23
8
|
end
|
@@ -15,7 +15,7 @@ collection = [
|
|
15
15
|
User.new(name: 'Jane', email: 'jane@example.org')
|
16
16
|
]
|
17
17
|
|
18
|
-
html =
|
18
|
+
html = active_element.component.table collection: collection, fields: [:name, :email]
|
19
19
|
|
20
20
|
it_documents html do
|
21
21
|
expect(html).to include 'John'
|
@@ -32,7 +32,7 @@ end
|
|
32
32
|
|
33
33
|
item = User.new(name: 'John', email: 'john@example.com', overview: 'Writes Ruby code for a living.')
|
34
34
|
|
35
|
-
html =
|
35
|
+
html = active_element.component.table item: item, fields: [:name, :email, :password, :secret]
|
36
36
|
|
37
37
|
it_documents html do
|
38
38
|
expect(html).to include 'John'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_element
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bootstrap
|
@@ -25,33 +25,33 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 5.3.0alpha3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: kaminari
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '2
|
33
|
+
version: '1.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '2
|
40
|
+
version: '1.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: paintbrush
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.1.2
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.1.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,14 +118,15 @@ files:
|
|
118
118
|
- app/assets/javascripts/active_element/form.js
|
119
119
|
- app/assets/javascripts/active_element/json_field.js
|
120
120
|
- app/assets/javascripts/active_element/pagination.js
|
121
|
-
- app/assets/javascripts/active_element/
|
121
|
+
- app/assets/javascripts/active_element/popover.js
|
122
122
|
- app/assets/javascripts/active_element/secret.js
|
123
123
|
- app/assets/javascripts/active_element/setup.js
|
124
|
+
- app/assets/javascripts/active_element/text_search_field.js
|
124
125
|
- app/assets/javascripts/active_element/theme.js
|
126
|
+
- app/assets/javascripts/active_element/toast.js
|
125
127
|
- app/assets/stylesheets/active_element/_variables.scss
|
126
128
|
- app/assets/stylesheets/active_element/application.scss
|
127
129
|
- app/controllers/active_element/application_controller.rb
|
128
|
-
- app/controllers/active_element/text_searches_controller.rb
|
129
130
|
- app/views/active_element/components/_horizontal_tabs.html.erb
|
130
131
|
- app/views/active_element/components/_vertical_tabs.html.erb
|
131
132
|
- app/views/active_element/components/button.html.erb
|
@@ -175,8 +176,6 @@ files:
|
|
175
176
|
- config/routes.rb
|
176
177
|
- lib/active_element.rb
|
177
178
|
- lib/active_element/active_menu_link.rb
|
178
|
-
- lib/active_element/active_record_text_search_authorization.rb
|
179
|
-
- lib/active_element/colorized_string.rb
|
180
179
|
- lib/active_element/component.rb
|
181
180
|
- lib/active_element/components.rb
|
182
181
|
- lib/active_element/components/button.rb
|
@@ -189,6 +188,11 @@ files:
|
|
189
188
|
- lib/active_element/components/secret_fields.rb
|
190
189
|
- lib/active_element/components/tab.rb
|
191
190
|
- lib/active_element/components/tabs.rb
|
191
|
+
- lib/active_element/components/text_search.rb
|
192
|
+
- lib/active_element/components/text_search/active_record_authorization.rb
|
193
|
+
- lib/active_element/components/text_search/authorization.rb
|
194
|
+
- lib/active_element/components/text_search/component.rb
|
195
|
+
- lib/active_element/components/text_search/sql.rb
|
192
196
|
- lib/active_element/components/translations.rb
|
193
197
|
- lib/active_element/components/util.rb
|
194
198
|
- lib/active_element/components/util/association_mapping.rb
|
@@ -199,9 +203,12 @@ files:
|
|
199
203
|
- lib/active_element/components/util/form_value_mapping.rb
|
200
204
|
- lib/active_element/components/util/i18n.rb
|
201
205
|
- lib/active_element/components/util/record_mapping.rb
|
206
|
+
- lib/active_element/components/util/record_path.rb
|
202
207
|
- lib/active_element/controller_action.rb
|
208
|
+
- lib/active_element/controller_interface.rb
|
203
209
|
- lib/active_element/engine.rb
|
204
210
|
- lib/active_element/permissions_check.rb
|
211
|
+
- lib/active_element/permissions_report.rb
|
205
212
|
- lib/active_element/rails_component.rb
|
206
213
|
- lib/active_element/route.rb
|
207
214
|
- lib/active_element/routes.rb
|
@@ -1,189 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveElement
|
4
|
-
# Used by auto-complete search field for executing a text search on the provided model and
|
5
|
-
# attributes.
|
6
|
-
#
|
7
|
-
# The user must have the permission `can_create_<application_name>_active_element_text_searches`.
|
8
|
-
#
|
9
|
-
# A model must call `authorize_active_element_text_search_for` to enable text search. e.g.:
|
10
|
-
#
|
11
|
-
# class MyModel < ApplicationRecord
|
12
|
-
# authorize_active_element_text_search_for :name, exposes: [:email]
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# This will allow searching on the `name` column and permits returning each matching record's
|
16
|
-
# `:email` column's value.
|
17
|
-
#
|
18
|
-
# TODO: Refactor logic into separate classes.
|
19
|
-
# rubocop:disable Metrics/ClassLength
|
20
|
-
class TextSearchesController < ApplicationController
|
21
|
-
DEFAULT_LIMIT = 50
|
22
|
-
|
23
|
-
before_action :verify_parameters
|
24
|
-
before_action :verify_model
|
25
|
-
|
26
|
-
def create
|
27
|
-
render json: { results: results, request_id: params[:request_id] }
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def verify_parameters
|
33
|
-
return if %i[model attributes value query].all? { |parameter| params[parameter].present? }
|
34
|
-
|
35
|
-
render json: { message: 'Must provide parameters: [model, attributes, value, query] for text search.' },
|
36
|
-
status: :unprocessable_entity
|
37
|
-
end
|
38
|
-
|
39
|
-
def verify_model
|
40
|
-
return if [model, search_columns, value_column].all?(&:present?) && authorized?
|
41
|
-
|
42
|
-
render json: { message: verify_fail_message }, status: :unprocessable_entity
|
43
|
-
end
|
44
|
-
|
45
|
-
def verify_fail_message
|
46
|
-
requested = %i[model attributes value].index_with { |key| params[key] }
|
47
|
-
.compact_blank
|
48
|
-
.map { |key, value| "#{key}: #{value}" }
|
49
|
-
"Unpermitted or unavailable search for: { #{requested.join(', ')} }"
|
50
|
-
end
|
51
|
-
|
52
|
-
def results
|
53
|
-
@results ||= model.where(*whereclause)
|
54
|
-
.limit(limit)
|
55
|
-
.pluck(value_column.name, *search_columns.map(&:name))
|
56
|
-
.map { |value, *attributes| result(value, attributes) }
|
57
|
-
end
|
58
|
-
|
59
|
-
def result(value, attributes)
|
60
|
-
{ value: value, attributes: attributes.reject { |attribute| attribute == value } }
|
61
|
-
end
|
62
|
-
|
63
|
-
def model
|
64
|
-
@model ||= params[:model].camelize(:upper).safe_constantize
|
65
|
-
end
|
66
|
-
|
67
|
-
def query
|
68
|
-
params[:query]
|
69
|
-
end
|
70
|
-
|
71
|
-
def value_column
|
72
|
-
return nil if params[:value].blank?
|
73
|
-
|
74
|
-
@value_column ||= model&.columns&.find { |column| column.name == params[:value] }
|
75
|
-
end
|
76
|
-
|
77
|
-
def search_columns
|
78
|
-
return nil if params[:attributes].blank?
|
79
|
-
|
80
|
-
@search_columns ||= params[:attributes].map { |attribute| column_for(attribute) }.compact
|
81
|
-
end
|
82
|
-
|
83
|
-
def column_for(attribute)
|
84
|
-
matched_column = model&.columns&.find { |column| column.name == attribute }
|
85
|
-
return nil if matched_column.blank?
|
86
|
-
|
87
|
-
compatible_column?(matched_column) ? matched_column : nil
|
88
|
-
end
|
89
|
-
|
90
|
-
def authorized?
|
91
|
-
model.authorized_active_element_text_search_fields&.any? do |field, exposed|
|
92
|
-
exposed = [exposed] unless exposed.is_a?(Array)
|
93
|
-
authorized_field?(field, exposed)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def authorized_field?(field, exposed)
|
98
|
-
return false unless search_columns.map { |column| column.name.to_sym }.include?(field.to_sym)
|
99
|
-
return false unless exposed&.map(&:to_sym)&.include?(value_column.name.to_sym)
|
100
|
-
|
101
|
-
true
|
102
|
-
end
|
103
|
-
|
104
|
-
def whereclause
|
105
|
-
clauses = search_columns.map { |column| "#{column.name} #{operator(column)} ?" }
|
106
|
-
[clauses.join(' OR '), search_columns.map { |column| search_param(column) }].flatten
|
107
|
-
end
|
108
|
-
|
109
|
-
def operator(column)
|
110
|
-
case column.type
|
111
|
-
when :string
|
112
|
-
model.connection.adapter_name == 'SQLite' ? 'LIKE' : 'ILIKE'
|
113
|
-
else
|
114
|
-
'='
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def compatible_column?(column) # rubocop:disable Metrics/MethodLength
|
119
|
-
case column.type
|
120
|
-
when :string
|
121
|
-
true
|
122
|
-
when :integer
|
123
|
-
integer?
|
124
|
-
when :float
|
125
|
-
float?
|
126
|
-
when :decimal
|
127
|
-
decimal?
|
128
|
-
else
|
129
|
-
Rails.logger.info("Skipping query `#{query}` for incompatible column: #{column.name}")
|
130
|
-
false
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def integer?
|
135
|
-
Integer(query)
|
136
|
-
true
|
137
|
-
rescue ArgumentError
|
138
|
-
false
|
139
|
-
end
|
140
|
-
|
141
|
-
def float?
|
142
|
-
Float(query)
|
143
|
-
true
|
144
|
-
rescue ArgumentError
|
145
|
-
false
|
146
|
-
end
|
147
|
-
|
148
|
-
def decimal?
|
149
|
-
BigDecimal(query)
|
150
|
-
true
|
151
|
-
rescue ArgumentError
|
152
|
-
false
|
153
|
-
end
|
154
|
-
|
155
|
-
def search_param(column)
|
156
|
-
case column.type
|
157
|
-
when :string
|
158
|
-
"#{query}%"
|
159
|
-
else
|
160
|
-
query
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def limit
|
165
|
-
DEFAULT_LIMIT
|
166
|
-
end
|
167
|
-
|
168
|
-
def permissions_check
|
169
|
-
@permissions_check ||= PermissionsCheck.new(
|
170
|
-
required: [],
|
171
|
-
actual: current_user.permissions,
|
172
|
-
controller_path: controller_path,
|
173
|
-
action_name: action_name,
|
174
|
-
rails_component: rails_component
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
def required_permissions
|
179
|
-
application_name = rails_component.application_name
|
180
|
-
permission = "can_text_search_#{application_name}_#{params[:model]&.pluralize}"
|
181
|
-
[[permission, { only: :create }]]
|
182
|
-
end
|
183
|
-
|
184
|
-
def rails_component
|
185
|
-
@rails_component ||= RailsComponent.new(Rails)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
# rubocop:enable Metrics/ClassLength
|
189
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
ActiveRecord::Base.class_eval do
|
4
|
-
class << self
|
5
|
-
attr_reader :authorized_active_element_text_search_fields
|
6
|
-
|
7
|
-
def authorize_active_element_text_search_for(field, exposes:)
|
8
|
-
@authorized_active_element_text_search_fields ||= []
|
9
|
-
@authorized_active_element_text_search_fields << [field, exposes]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveElement
|
4
|
-
# Wraps strings in terminal escape codes to provide colourisation in (e.g.) logging output.
|
5
|
-
class ColorizedString
|
6
|
-
COLOR_CODES = {
|
7
|
-
cyan: '36',
|
8
|
-
red: '31',
|
9
|
-
green: '32',
|
10
|
-
blue: '34',
|
11
|
-
purple: '35',
|
12
|
-
yellow: '33',
|
13
|
-
light_gray: '37',
|
14
|
-
light_blue: '1;34',
|
15
|
-
white: '1;37'
|
16
|
-
}.freeze
|
17
|
-
|
18
|
-
def initialize(string, color:)
|
19
|
-
@string = string
|
20
|
-
@color = color
|
21
|
-
end
|
22
|
-
|
23
|
-
def value
|
24
|
-
return string unless Rails.env.development? || Rails.env.test?
|
25
|
-
|
26
|
-
"\e[#{COLOR_CODES.fetch(color)}m#{string}\e[0m"
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
attr_reader :string, :color
|
32
|
-
end
|
33
|
-
end
|