active_element 0.0.1 → 0.0.3
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 +12 -0
- data/.strong_versions.yml +2 -0
- data/Gemfile +10 -2
- data/Gemfile.lock +229 -4
- data/Rakefile +1 -0
- data/active_element.gemspec +7 -0
- data/app/assets/config/active_element/manifest.js +2 -0
- data/app/assets/javascripts/active_element/application.js +10 -0
- data/app/assets/javascripts/active_element/confirm.js +67 -0
- data/app/assets/javascripts/active_element/form.js +61 -0
- data/app/assets/javascripts/active_element/json_field.js +316 -0
- data/app/assets/javascripts/active_element/pagination.js +18 -0
- data/app/assets/javascripts/active_element/search_field.js +127 -0
- data/app/assets/javascripts/active_element/secret.js +40 -0
- data/app/assets/javascripts/active_element/setup.js +36 -0
- data/app/assets/javascripts/active_element/theme.js +42 -0
- data/app/assets/stylesheets/active_element/_variables.scss +142 -0
- data/app/assets/stylesheets/active_element/application.scss +77 -0
- data/app/controllers/active_element/application_controller.rb +41 -0
- data/app/controllers/active_element/text_searches_controller.rb +189 -0
- data/app/views/active_element/components/_horizontal_tabs.html.erb +32 -0
- data/app/views/active_element/components/_vertical_tabs.html.erb +38 -0
- data/app/views/active_element/components/button.html.erb +27 -0
- data/app/views/active_element/components/fields/_boolean.html.erb +11 -0
- data/app/views/active_element/components/form/_check_box.html.erb +3 -0
- data/app/views/active_element/components/form/_check_boxes.html.erb +33 -0
- data/app/views/active_element/components/form/_field.html.erb +28 -0
- data/app/views/active_element/components/form/_generic_field.html.erb +3 -0
- data/app/views/active_element/components/form/_json.html.erb +12 -0
- data/app/views/active_element/components/form/_label.html.erb +17 -0
- data/app/views/active_element/components/form/_option_groups_summary.html.erb +17 -0
- data/app/views/active_element/components/form/_select.html.erb +4 -0
- data/app/views/active_element/components/form/_summary.html.erb +40 -0
- data/app/views/active_element/components/form/_templates.html.erb +85 -0
- data/app/views/active_element/components/form/_text_area.html.erb +4 -0
- data/app/views/active_element/components/form/_text_search.html.erb +16 -0
- data/app/views/active_element/components/form.html.erb +78 -0
- data/app/views/active_element/components/json.html.erb +8 -0
- data/app/views/active_element/components/page_description.html.erb +3 -0
- data/app/views/active_element/components/secret/_field.html.erb +1 -0
- data/app/views/active_element/components/secret/_templates.html.erb +11 -0
- data/app/views/active_element/components/table/_collection_row.html.erb +30 -0
- data/app/views/active_element/components/table/_grouped_collection.html.erb +88 -0
- data/app/views/active_element/components/table/_pagination.html.erb +17 -0
- data/app/views/active_element/components/table/_ungrouped_collection.html.erb +49 -0
- data/app/views/active_element/components/table/collection.html.erb +39 -0
- data/app/views/active_element/components/table/item.html.erb +39 -0
- data/app/views/active_element/components/tabs.html.erb +7 -0
- data/app/views/active_element/decorators/_boolean.html.erb +5 -0
- data/app/views/active_element/decorators/_date.html.erb +3 -0
- data/app/views/active_element/decorators/_datetime.html.erb +3 -0
- data/app/views/active_element/decorators/_time.html.erb +3 -0
- data/app/views/active_element/forbidden.html.erb +33 -0
- data/app/views/active_element/main_menu/_item.html.erb +9 -0
- data/app/views/active_element/navbar/_menu.html.erb +30 -0
- data/app/views/active_element/theme/_select.html.erb +1 -0
- data/app/views/active_element/theme/_templates.html.erb +6 -0
- data/app/views/kaminari/_first_page.html.erb +3 -0
- data/app/views/kaminari/_gap.html.erb +3 -0
- data/app/views/kaminari/_last_page.html.erb +3 -0
- data/app/views/kaminari/_next_page.html.erb +3 -0
- data/app/views/kaminari/_page.html.erb +9 -0
- data/app/views/kaminari/_paginator.html.erb +17 -0
- data/app/views/kaminari/_prev_page.html.erb +3 -0
- data/app/views/layouts/active_element.html.erb +65 -0
- data/app/views/layouts/active_element_error.html.erb +40 -0
- data/config/routes.rb +5 -0
- data/lib/active_element/active_menu_link.rb +80 -0
- data/lib/active_element/active_record_text_search_authorization.rb +12 -0
- data/lib/active_element/colorized_string.rb +33 -0
- data/lib/active_element/component.rb +122 -0
- data/lib/active_element/components/button.rb +156 -0
- data/lib/active_element/components/collection_table.rb +118 -0
- data/lib/active_element/components/form.rb +210 -0
- data/lib/active_element/components/item_table.rb +57 -0
- data/lib/active_element/components/json.rb +31 -0
- data/lib/active_element/components/link_helpers.rb +9 -0
- data/lib/active_element/components/page_description.rb +28 -0
- data/lib/active_element/components/secret_fields.rb +15 -0
- data/lib/active_element/components/tab.rb +37 -0
- data/lib/active_element/components/tabs.rb +35 -0
- data/lib/active_element/components/translations.rb +18 -0
- data/lib/active_element/components/util/association_mapping.rb +80 -0
- data/lib/active_element/components/util/decorator.rb +107 -0
- data/lib/active_element/components/util/display_value_mapping.rb +48 -0
- data/lib/active_element/components/util/field_mapping.rb +144 -0
- data/lib/active_element/components/util/form_field_mapping.rb +104 -0
- data/lib/active_element/components/util/form_value_mapping.rb +49 -0
- data/lib/active_element/components/util/i18n.rb +66 -0
- data/lib/active_element/components/util/record_mapping.rb +111 -0
- data/lib/active_element/components/util.rb +43 -0
- data/lib/active_element/components.rb +20 -0
- data/lib/active_element/controller_action.rb +91 -0
- data/lib/active_element/engine.rb +26 -0
- data/lib/active_element/permissions_check.rb +101 -0
- data/lib/active_element/rails_component.rb +40 -0
- data/lib/active_element/route.rb +112 -0
- data/lib/active_element/routes.rb +62 -0
- data/lib/active_element/version.rb +1 -1
- data/lib/active_element.rb +91 -1
- data/lib/tasks/active_element.rake +23 -0
- data/rspec-documentation/dummy +1 -0
- data/rspec-documentation/pages/Components/Forms.md +1 -0
- data/rspec-documentation/pages/Components/Tables.md +47 -0
- data/rspec-documentation/pages/Components/Tabs.md +1 -0
- data/rspec-documentation/pages/Components.md +1 -0
- data/rspec-documentation/pages/Decorators/Inline Decorators.md +1 -0
- data/rspec-documentation/pages/Decorators/View Decorators.md +1 -0
- data/rspec-documentation/pages/Index.md +3 -0
- data/rspec-documentation/pages/Util/I18n.md +1 -0
- data/rspec-documentation/spec_helper.rb +35 -0
- metadata +191 -3
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveElement
|
|
4
|
+
# Verifies provided permissions against required permissions.
|
|
5
|
+
class PermissionsCheck
|
|
6
|
+
def initialize(required:, actual:, controller_path:, action_name:, rails_component:)
|
|
7
|
+
@required = required.presence || []
|
|
8
|
+
@actual = normalized(actual)
|
|
9
|
+
@controller_name = controller_path.to_s.gsub('/', '_')
|
|
10
|
+
@action_name = action_name.to_s
|
|
11
|
+
@rails_component = rails_component
|
|
12
|
+
raise_unprotected_route_error if applicable.empty?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def permitted?
|
|
16
|
+
rails_component.environment == 'development' || missing.blank?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
return development_environment_message if rails_component.environment == 'development'
|
|
21
|
+
return "User access granted for permission(s): #{applicable.join(', ')}" if permitted?
|
|
22
|
+
|
|
23
|
+
"User access forbidden. Missing user permission(s): #{missing.join(', ')}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def missing
|
|
27
|
+
@missing ||= applicable.reject do |permission|
|
|
28
|
+
actual.include?(permission.to_s)
|
|
29
|
+
end.map(&:to_s)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def applicable
|
|
33
|
+
@applicable ||= default_permissions + required_permissions
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :required, :actual, :controller_name, :action_name, :rails_component
|
|
39
|
+
|
|
40
|
+
def development_environment_message
|
|
41
|
+
"Bypassed permission(s) in development environment: #{applicable.join(', ')}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def default_permissions
|
|
45
|
+
return [] if normalized_action.nil?
|
|
46
|
+
|
|
47
|
+
["can_#{normalized_action}_#{rails_component.application_name}_#{controller_name}"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def required_permissions
|
|
51
|
+
@required_permissions ||= required.map do |permission, options|
|
|
52
|
+
next nil unless applicable?(options)
|
|
53
|
+
|
|
54
|
+
permission
|
|
55
|
+
end.compact
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def normalized_action
|
|
59
|
+
{
|
|
60
|
+
index: 'list',
|
|
61
|
+
show: 'view',
|
|
62
|
+
edit: 'edit',
|
|
63
|
+
update: 'edit',
|
|
64
|
+
create: 'create',
|
|
65
|
+
new: 'create',
|
|
66
|
+
destroy: 'delete'
|
|
67
|
+
}.fetch(action_name.to_sym, nil)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def normalized(val)
|
|
71
|
+
return val&.map(&:to_s) if val.is_a?(Array)
|
|
72
|
+
|
|
73
|
+
[val].map(&:to_s)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def applicable?(options)
|
|
77
|
+
return true if !options.key?(:only) && !options.key?(:except)
|
|
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)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def raise_unprotected_route_error
|
|
97
|
+
raise UnprotectedRouteError,
|
|
98
|
+
"#{controller_name.titleize.tr(' ', '')}##{action_name} must be protected with `permit_user`"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveElement
|
|
4
|
+
# Abstraction of various Rails interfaces.
|
|
5
|
+
class RailsComponent
|
|
6
|
+
def initialize(rails)
|
|
7
|
+
@rails = rails
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def routes
|
|
11
|
+
rails.application.routes
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def environment
|
|
15
|
+
rails.env
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def application_name
|
|
19
|
+
rails.application.class.module_parent.name.underscore
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Provides array of e.g. { path: "/admin/users", controller: "admin/users", action: "index" }
|
|
23
|
+
def route_paths_with_requirements
|
|
24
|
+
rails.application.routes.routes.map do |route|
|
|
25
|
+
{ path: path_from_route_spec(route.path.spec) }.merge(route.requirements)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :rails
|
|
32
|
+
|
|
33
|
+
# Translates "/admin/users/:id(.:format)" into "/admin/users"
|
|
34
|
+
def path_from_route_spec(spec)
|
|
35
|
+
# FIXME: Find a more robust way of doing this ?
|
|
36
|
+
path = spec.to_s.gsub(/:.*/, '').gsub(/\(.*/, '')
|
|
37
|
+
path == '/' ? path : path.chomp
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveElement
|
|
4
|
+
# Abstraction of a Rails route, includes path, permitted state (based on user permissions), etc.
|
|
5
|
+
class Route
|
|
6
|
+
include Comparable
|
|
7
|
+
|
|
8
|
+
attr_reader :controller
|
|
9
|
+
|
|
10
|
+
def initialize(controller:, required_permissions:, user_permissions:, action:, rails_component:)
|
|
11
|
+
@controller = controller
|
|
12
|
+
@required_permissions = required_permissions
|
|
13
|
+
@user_permissions = user_permissions
|
|
14
|
+
@action = action
|
|
15
|
+
@rails_component = rails_component
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def <=>(other)
|
|
19
|
+
return 0 if path.nil? && other.path.nil?
|
|
20
|
+
return 1 if path.nil?
|
|
21
|
+
return -1 if other.path.nil?
|
|
22
|
+
|
|
23
|
+
path <=> other.path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def permitted?
|
|
27
|
+
return @permitted if defined?(@permitted)
|
|
28
|
+
|
|
29
|
+
(@permitted = permitted_action?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def path
|
|
33
|
+
@path ||= rails_path
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def primary?
|
|
37
|
+
return false if rails_non_index_action?
|
|
38
|
+
return false unless resourceless_get_request?
|
|
39
|
+
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def title
|
|
44
|
+
controller.controller_name.titleize
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def spec
|
|
48
|
+
{ controller: controller.controller_path, action: action.to_s }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def permissions
|
|
52
|
+
permissions_check.applicable.map(&:to_s)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def rails_route?
|
|
56
|
+
rails_application_route? || active_element_route?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
attr_reader :required_permissions, :user_permissions, :action, :rails_component
|
|
62
|
+
|
|
63
|
+
def rails_application_route?
|
|
64
|
+
rails_component.routes.routes.map(&:requirements).any? { |requirements| match_spec?(requirements) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def active_element_route?
|
|
68
|
+
ActiveElement::Engine.routes.routes.map(&:requirements).any? { |requirements| match_spec?(requirements) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def match_spec?(requirements)
|
|
72
|
+
requirements.to_set.superset?(spec.to_set)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def rails_path
|
|
76
|
+
rails_component.routes.url_for(**spec, only_path: true)
|
|
77
|
+
rescue ActionController::UrlGenerationError
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def rails_non_index_action?
|
|
82
|
+
%i[show edit update new create destroy].include?(action)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def resourceless_get_request?
|
|
86
|
+
spec_set = spec.to_set
|
|
87
|
+
rails_component.routes.routes.find do |rails_route|
|
|
88
|
+
next false unless rails_route.requirements&.to_set&.superset?(spec_set)
|
|
89
|
+
next false unless rails_route.verb == 'GET'
|
|
90
|
+
next false unless rails_route.required_parts.empty?
|
|
91
|
+
|
|
92
|
+
true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def permitted_action?
|
|
97
|
+
permissions_check.permitted?
|
|
98
|
+
rescue UnprotectedRouteError
|
|
99
|
+
false
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def permissions_check
|
|
103
|
+
@permissions_check ||= PermissionsCheck.new(
|
|
104
|
+
required: required_permissions,
|
|
105
|
+
actual: user_permissions,
|
|
106
|
+
controller_path: controller.controller_path,
|
|
107
|
+
action_name: action,
|
|
108
|
+
rails_component: rails_component
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveElement
|
|
4
|
+
# Provides an interface to available admin routes, used for populating a default navigation bar
|
|
5
|
+
# and detecting available permitted routes if the default root path is not permitted.
|
|
6
|
+
class Routes
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
def initialize(rails_component:, permissions: [])
|
|
10
|
+
@permissions = permissions
|
|
11
|
+
@rails_component = rails_component
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def permitted
|
|
15
|
+
@permitted ||= available_routes.select(&:permitted?)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def available
|
|
19
|
+
@available ||= available_routes
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def alternative_routes
|
|
23
|
+
@alternative_routes ||= available.select(&:primary?).reject { |route| route.path == '/' }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def each(&block)
|
|
27
|
+
available.each(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :permissions, :rails_component
|
|
33
|
+
|
|
34
|
+
def available_routes
|
|
35
|
+
@available_routes ||= descendants_with_permissions.map do |descendant, required_permissions|
|
|
36
|
+
descendant.public_methods(false).map do |action|
|
|
37
|
+
route(descendant, action, required_permissions)
|
|
38
|
+
end
|
|
39
|
+
end.flatten.compact.select(&:rails_route?).sort
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def descendants_with_permissions
|
|
43
|
+
@descendants_with_permissions ||= descendants.map do |controller_class|
|
|
44
|
+
[controller_class.new, controller_class.active_element_permissions]
|
|
45
|
+
end.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def descendants
|
|
49
|
+
@descendants ||= ActiveElement::ApplicationController.descendants
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def route(controller, action, required_permissions)
|
|
53
|
+
Route.new(
|
|
54
|
+
controller: controller,
|
|
55
|
+
action: action,
|
|
56
|
+
required_permissions: required_permissions,
|
|
57
|
+
user_permissions: permissions,
|
|
58
|
+
rails_component: rails_component
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/active_element.rb
CHANGED
|
@@ -1,8 +1,98 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'rouge'
|
|
5
|
+
require 'kaminari'
|
|
6
|
+
require 'sassc'
|
|
7
|
+
require 'bootstrap'
|
|
8
|
+
require 'active_record'
|
|
9
|
+
# require 'rspec/documentation' # FIXME: Load dynamically when running rspec documentation command.
|
|
10
|
+
|
|
3
11
|
require_relative 'active_element/version'
|
|
12
|
+
require_relative 'active_element/active_record_text_search_authorization'
|
|
13
|
+
require_relative 'active_element/colorized_string'
|
|
14
|
+
require_relative 'active_element/active_menu_link'
|
|
15
|
+
require_relative 'active_element/permissions_check'
|
|
16
|
+
require_relative 'active_element/controller_action'
|
|
17
|
+
require_relative 'active_element/rails_component'
|
|
18
|
+
require_relative 'active_element/route'
|
|
19
|
+
require_relative 'active_element/routes'
|
|
20
|
+
require_relative 'active_element/component'
|
|
21
|
+
require_relative 'active_element/components'
|
|
22
|
+
require_relative 'active_element/engine'
|
|
4
23
|
|
|
24
|
+
# ActiveElement API Admin UI template and menu system.
|
|
5
25
|
module ActiveElement
|
|
6
26
|
class Error < StandardError; end
|
|
7
|
-
|
|
27
|
+
class UnprotectedRouteError < Error; end
|
|
28
|
+
class UnknownAttributeError < Error; end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
attr_writer :application_name, :navbar_items
|
|
32
|
+
|
|
33
|
+
def application_title
|
|
34
|
+
@application_name || RailsComponent.new(Rails).application_name.titleize
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def navbar_items(user)
|
|
38
|
+
@navbar_items || inferred_navbar_items(user)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def active_path_class(user:, current_navbar_item:, current_path:, controller_path:, action_name:)
|
|
42
|
+
if ActiveMenuLink.new(
|
|
43
|
+
rails_component: RailsComponent.new(Rails),
|
|
44
|
+
navbar_items: navbar_items(user),
|
|
45
|
+
current_path: current_path,
|
|
46
|
+
current_navbar_item: current_navbar_item,
|
|
47
|
+
controller_path: controller_path,
|
|
48
|
+
action_name: action_name
|
|
49
|
+
).active?
|
|
50
|
+
'active'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def json_pretty_print(json)
|
|
55
|
+
Components::Util.json_pretty_print(json)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def with_silenced_logging(&block)
|
|
59
|
+
return block.call unless silence_logging?
|
|
60
|
+
|
|
61
|
+
ActiveSupport::Notifications.unsubscribe 'render_template.action_view'
|
|
62
|
+
ActiveSupport::Notifications.unsubscribe 'render_partial.action_view'
|
|
63
|
+
|
|
64
|
+
block.call
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def silence_logging?
|
|
68
|
+
return true unless Rails.env.development? || Rails.env.test?
|
|
69
|
+
return true unless ENV.key?('ACTIVE_ELEMENT_DEBUG')
|
|
70
|
+
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def eager_load_controllers
|
|
75
|
+
Pathname.new(__dir__)
|
|
76
|
+
.join('../app/controllers/active_element')
|
|
77
|
+
.glob('**/*_controller.rb')
|
|
78
|
+
.each { |path| require path }
|
|
79
|
+
Rails.root.join('app/controllers/admin/').glob('**/*_controller.rb').each { |path| require path }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def inferred_navbar_items(user)
|
|
85
|
+
eager_load_controllers
|
|
86
|
+
user_routes(user).available.select(&:primary?).map do |route|
|
|
87
|
+
{ path: route.path, title: route.title, spec: route.spec }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def user_routes(user)
|
|
92
|
+
ActiveElement::Routes.new(
|
|
93
|
+
permissions: user.permissions,
|
|
94
|
+
rails_component: ActiveElement::RailsComponent.new(Rails)
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
8
98
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :active_element do
|
|
4
|
+
desc 'Displays all permissions used by this application'
|
|
5
|
+
task permissions: :environment do
|
|
6
|
+
ActiveElement.eager_load_controllers
|
|
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
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../spec/dummy/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Forms
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Tables
|
|
2
|
+
|
|
3
|
+
## Collection Table
|
|
4
|
+
|
|
5
|
+
The _Collection Table_ component provides a vertical table containing a collection of items item. Use with _Active Record_ model instances, an array of objects that extend `ActiveModel::Naming`, or simple hash-like objects.
|
|
6
|
+
|
|
7
|
+
Field types are automatically inferred from their respective database columns and rendered using an appropriate formatter.
|
|
8
|
+
|
|
9
|
+
```rspec:html
|
|
10
|
+
class User < ActiveRecord::Base
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
collection = [
|
|
14
|
+
User.new(name: 'John', email: 'john@example.com'),
|
|
15
|
+
User.new(name: 'Jane', email: 'jane@example.org')
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
html = active_element_component.table collection: collection, fields: [:name, :email]
|
|
19
|
+
|
|
20
|
+
it_documents html do
|
|
21
|
+
expect(html).to include 'John'
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Item Table
|
|
26
|
+
|
|
27
|
+
The _Item Table_ component provides a horizontal table containing a single item and its attributes. It supports the same item types as [Collection Tables](##Collection Table).
|
|
28
|
+
|
|
29
|
+
```rspec:html
|
|
30
|
+
class User < ActiveRecord::Base
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
item = User.new(name: 'John', email: 'john@example.com', overview: 'Writes Ruby code for a living.')
|
|
34
|
+
|
|
35
|
+
html = active_element_component.table item: item, fields: [:name, :email, :password, :secret]
|
|
36
|
+
|
|
37
|
+
it_documents html do
|
|
38
|
+
expect(html).to include 'John'
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```rspec
|
|
43
|
+
foo = { foo: 'bar', baz: 'qux' }
|
|
44
|
+
it_documents foo do
|
|
45
|
+
expect('hello').to eql 'hello'
|
|
46
|
+
end
|
|
47
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Tabs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Components
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Inline Decorators
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# View Decorators
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Internationalization
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'active_element'
|
|
2
|
+
require_relative 'dummy/config/environment'
|
|
3
|
+
|
|
4
|
+
RSpec::Documentation.configure do |config|
|
|
5
|
+
ActiveRecord::Migration.class_eval do
|
|
6
|
+
drop_table :users
|
|
7
|
+
rescue ActiveRecord::StatementInvalid
|
|
8
|
+
# Skip
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
ActiveRecord::Migration.class_eval do
|
|
12
|
+
create_table :users do |t|
|
|
13
|
+
t.string :name
|
|
14
|
+
t.string :email
|
|
15
|
+
t.text :overview
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
config.context do |context|
|
|
20
|
+
class StubbedController < ActiveElement::ApplicationController
|
|
21
|
+
|
|
22
|
+
def initialize(*args, &block)
|
|
23
|
+
append_view_path File.expand_path(File.join(__dir__, '../app/views/'))
|
|
24
|
+
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def params
|
|
29
|
+
{}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context.active_element_component = ActiveElement::Component.new(StubbedController.new)
|
|
34
|
+
end
|
|
35
|
+
end
|