active_element 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|