wallaby-core 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/app/controllers/wallaby/application_controller.rb +84 -0
- data/app/controllers/wallaby/resources_controller.rb +381 -0
- data/app/controllers/wallaby/secure_controller.rb +81 -0
- data/app/security/ability.rb +13 -0
- data/config/locales/wallaby.en.yml +140 -0
- data/config/locales/wallaby_class.en.yml +30 -0
- data/config/routes.rb +39 -0
- data/lib/adaptors/wallaby/custom.rb +7 -0
- data/lib/adaptors/wallaby/custom/default_provider.rb +9 -0
- data/lib/adaptors/wallaby/custom/model_decorator.rb +71 -0
- data/lib/adaptors/wallaby/custom/model_finder.rb +13 -0
- data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +14 -0
- data/lib/adaptors/wallaby/custom/model_service_provider.rb +48 -0
- data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +72 -0
- data/lib/authorizers/wallaby/default_authorization_provider.rb +58 -0
- data/lib/authorizers/wallaby/model_authorizer.rb +100 -0
- data/lib/authorizers/wallaby/pundit_authorization_provider.rb +89 -0
- data/lib/concerns/wallaby/authorizable.rb +103 -0
- data/lib/concerns/wallaby/baseable.rb +36 -0
- data/lib/concerns/wallaby/decoratable.rb +101 -0
- data/lib/concerns/wallaby/defaultable.rb +38 -0
- data/lib/concerns/wallaby/engineable.rb +61 -0
- data/lib/concerns/wallaby/fieldable.rb +78 -0
- data/lib/concerns/wallaby/paginatable.rb +72 -0
- data/lib/concerns/wallaby/rails_overridden_methods.rb +42 -0
- data/lib/concerns/wallaby/resourcable.rb +149 -0
- data/lib/concerns/wallaby/servicable.rb +68 -0
- data/lib/concerns/wallaby/shared_helpers.rb +22 -0
- data/lib/concerns/wallaby/themeable.rb +40 -0
- data/lib/decorators/wallaby/resource_decorator.rb +189 -0
- data/lib/errors/wallaby/cell_handling.rb +6 -0
- data/lib/errors/wallaby/forbidden.rb +6 -0
- data/lib/errors/wallaby/general_error.rb +6 -0
- data/lib/errors/wallaby/invalid_error.rb +6 -0
- data/lib/errors/wallaby/model_not_found.rb +11 -0
- data/lib/errors/wallaby/not_authenticated.rb +6 -0
- data/lib/errors/wallaby/not_found.rb +6 -0
- data/lib/errors/wallaby/not_implemented.rb +6 -0
- data/lib/errors/wallaby/resource_not_found.rb +11 -0
- data/lib/errors/wallaby/unprocessable_entity.rb +6 -0
- data/lib/forms/wallaby/form_builder.rb +60 -0
- data/lib/helpers/wallaby/application_helper.rb +79 -0
- data/lib/helpers/wallaby/base_helper.rb +65 -0
- data/lib/helpers/wallaby/configuration_helper.rb +18 -0
- data/lib/helpers/wallaby/form_helper.rb +62 -0
- data/lib/helpers/wallaby/index_helper.rb +84 -0
- data/lib/helpers/wallaby/links_helper.rb +213 -0
- data/lib/helpers/wallaby/resources_helper.rb +52 -0
- data/lib/helpers/wallaby/secure_helper.rb +54 -0
- data/lib/helpers/wallaby/styling_helper.rb +82 -0
- data/lib/interfaces/wallaby/mode.rb +72 -0
- data/lib/interfaces/wallaby/model_authorization_provider.rb +99 -0
- data/lib/interfaces/wallaby/model_decorator.rb +168 -0
- data/lib/interfaces/wallaby/model_finder.rb +12 -0
- data/lib/interfaces/wallaby/model_pagination_provider.rb +107 -0
- data/lib/interfaces/wallaby/model_service_provider.rb +84 -0
- data/lib/paginators/wallaby/model_paginator.rb +115 -0
- data/lib/paginators/wallaby/resource_paginator.rb +12 -0
- data/lib/parsers/wallaby/parser.rb +34 -0
- data/lib/renderers/wallaby/cell.rb +137 -0
- data/lib/renderers/wallaby/cell_resolver.rb +89 -0
- data/lib/renderers/wallaby/custom_lookup_context.rb +64 -0
- data/lib/renderers/wallaby/custom_partial_renderer.rb +33 -0
- data/lib/renderers/wallaby/custom_renderer.rb +16 -0
- data/lib/responders/wallaby/json_api_responder.rb +101 -0
- data/lib/responders/wallaby/resources_responder.rb +28 -0
- data/lib/routes/wallaby/resources_router.rb +72 -0
- data/lib/servicers/wallaby/model_servicer.rb +154 -0
- data/lib/services/wallaby/engine_name_finder.rb +22 -0
- data/lib/services/wallaby/engine_url_for.rb +46 -0
- data/lib/services/wallaby/link_options_normalizer.rb +19 -0
- data/lib/services/wallaby/map/mode_mapper.rb +27 -0
- data/lib/services/wallaby/map/model_class_collector.rb +49 -0
- data/lib/services/wallaby/map/model_class_mapper.rb +38 -0
- data/lib/services/wallaby/prefixes_builder.rb +66 -0
- data/lib/services/wallaby/sorting/hash_builder.rb +19 -0
- data/lib/services/wallaby/sorting/link_builder.rb +69 -0
- data/lib/services/wallaby/sorting/next_builder.rb +63 -0
- data/lib/services/wallaby/sorting/single_builder.rb +20 -0
- data/lib/services/wallaby/type_renderer.rb +50 -0
- data/lib/support/action_dispatch/routing/mapper.rb +75 -0
- data/lib/tree/wallaby/node.rb +25 -0
- data/lib/utils/wallaby/cell_utils.rb +34 -0
- data/lib/utils/wallaby/field_utils.rb +43 -0
- data/lib/utils/wallaby/filter_utils.rb +20 -0
- data/lib/utils/wallaby/model_utils.rb +51 -0
- data/lib/utils/wallaby/module_utils.rb +46 -0
- data/lib/utils/wallaby/params_utils.rb +14 -0
- data/lib/utils/wallaby/preload_utils.rb +44 -0
- data/lib/utils/wallaby/test_utils.rb +34 -0
- data/lib/utils/wallaby/utils.rb +27 -0
- data/lib/wallaby/configuration.rb +103 -0
- data/lib/wallaby/configuration/features.rb +24 -0
- data/lib/wallaby/configuration/mapping.rb +140 -0
- data/lib/wallaby/configuration/metadata.rb +23 -0
- data/lib/wallaby/configuration/models.rb +46 -0
- data/lib/wallaby/configuration/pagination.rb +30 -0
- data/lib/wallaby/configuration/security.rb +98 -0
- data/lib/wallaby/configuration/sorting.rb +28 -0
- data/lib/wallaby/constants.rb +45 -0
- data/lib/wallaby/core.rb +117 -0
- data/lib/wallaby/core/version.rb +7 -0
- data/lib/wallaby/engine.rb +43 -0
- data/lib/wallaby/map.rb +170 -0
- metadata +222 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Resources helper
|
|
5
|
+
module ResourcesHelper
|
|
6
|
+
include BaseHelper
|
|
7
|
+
include FormHelper
|
|
8
|
+
include IndexHelper
|
|
9
|
+
|
|
10
|
+
include Authorizable
|
|
11
|
+
include Decoratable
|
|
12
|
+
include Paginatable
|
|
13
|
+
include Resourcable
|
|
14
|
+
include Servicable
|
|
15
|
+
include Themeable
|
|
16
|
+
|
|
17
|
+
# @deprecated Use {#type_render} instead. It will be removed from 5.3.*
|
|
18
|
+
def type_partial_render(options = {}, locals = {}, &block)
|
|
19
|
+
Utils.deprecate 'deprecation.type_partial_render', caller: caller
|
|
20
|
+
type_render options, locals, &block
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Render type cell/partial
|
|
24
|
+
# @param partial_name [String]
|
|
25
|
+
# @param locals [Hash]
|
|
26
|
+
def type_render(partial_name = '', locals = {}, &block)
|
|
27
|
+
TypeRenderer.render self, partial_name, locals, &block
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Title for show page of given resource
|
|
31
|
+
# @param decorated [Wallaby::ResourceDecorator]
|
|
32
|
+
# @return [String]
|
|
33
|
+
def show_title(decorated)
|
|
34
|
+
raise ::ArgumentError unless decorated.is_a? ResourceDecorator
|
|
35
|
+
|
|
36
|
+
[
|
|
37
|
+
to_model_label(decorated.model_class), decorated.to_label
|
|
38
|
+
].compact.join ': '
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# To find the first field that meets given conditions.
|
|
42
|
+
# @example To find summary field whose name contains _summary_ and type is **string**:
|
|
43
|
+
# first_field_by({ name: /summary/, type: 'string' })
|
|
44
|
+
# @param conditions [Array<Hash>]
|
|
45
|
+
# @return [String, Symbol] field name when found
|
|
46
|
+
# @return [nil] when not found
|
|
47
|
+
def first_field_by(*conditions)
|
|
48
|
+
fields = block_given? ? yield : current_fields
|
|
49
|
+
FieldUtils.first_field_by(*conditions, fields)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Secure helper
|
|
5
|
+
module SecureHelper
|
|
6
|
+
# Image portrait for given user.
|
|
7
|
+
#
|
|
8
|
+
# - if email is present, a gravatar image tag will be returned
|
|
9
|
+
# - otherwise, an user icon will be returned
|
|
10
|
+
# @param user [Object]
|
|
11
|
+
# @return [String] IMG or I element
|
|
12
|
+
def user_portrait(user = current_user)
|
|
13
|
+
email_method = security.email_method || :email
|
|
14
|
+
email = try_to user, email_method
|
|
15
|
+
if email.present?
|
|
16
|
+
https = "http#{request.ssl? ? 's' : EMPTY_STRING}"
|
|
17
|
+
email_md5 = ::Digest::MD5.hexdigest email.downcase
|
|
18
|
+
image_source = "#{https}://www.gravatar.com/avatar/#{email_md5}"
|
|
19
|
+
image_tag image_source, class: 'user'
|
|
20
|
+
else
|
|
21
|
+
fa_icon 'user'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Logout path for given user
|
|
26
|
+
# @see Wallaby::Configuration::Security#logout_path
|
|
27
|
+
# @param user [Object]
|
|
28
|
+
# @param app [Object]
|
|
29
|
+
# @return [String] URL to log out
|
|
30
|
+
def logout_path(user = current_user, app = main_app)
|
|
31
|
+
path = security.logout_path
|
|
32
|
+
path ||=
|
|
33
|
+
if defined? ::Devise
|
|
34
|
+
scope = ::Devise::Mapping.find_scope! user
|
|
35
|
+
"destroy_#{scope}_session_path"
|
|
36
|
+
end
|
|
37
|
+
try_to app, path
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Logout method for given user
|
|
41
|
+
# @see Wallaby::Configuration::Security#logout_method
|
|
42
|
+
# @param user [Object]
|
|
43
|
+
# @return [String, Symbol] http method to log out
|
|
44
|
+
def logout_method(user = current_user)
|
|
45
|
+
http_method = security.logout_method
|
|
46
|
+
http_method ||
|
|
47
|
+
if defined? ::Devise
|
|
48
|
+
scope = ::Devise::Mapping.find_scope! user
|
|
49
|
+
mapping = ::Devise.mappings[scope]
|
|
50
|
+
mapping.sign_out_via
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Helper methods to build custom elements
|
|
5
|
+
module StylingHelper
|
|
6
|
+
# Shortcut to build up the HTML options as keyword arguments
|
|
7
|
+
# @param string_or_array [String, Array<String>]
|
|
8
|
+
# @param options [Hash]
|
|
9
|
+
# @return [Hash]
|
|
10
|
+
def html_classes(string_or_array, options = {})
|
|
11
|
+
{ html_options: options.merge(class: string_or_array) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Shortcut to generate FontAwesome icon using tag <i>.
|
|
15
|
+
# @overload fa_icon(*names, html_options)
|
|
16
|
+
# @param names [Array<String>] names of the icon
|
|
17
|
+
# @param html_options [Hash] HTML options for tag <i>
|
|
18
|
+
# @return [String] HTML I element
|
|
19
|
+
def fa_icon(*args, &block)
|
|
20
|
+
html_options = args.extract_options!
|
|
21
|
+
html_options[:class] = Array html_options[:class]
|
|
22
|
+
html_options[:class] << 'fa'
|
|
23
|
+
args.each { |suffix| html_options[:class] << "fa-#{fa_map suffix}" }
|
|
24
|
+
|
|
25
|
+
content_tag :i, nil, html_options, &block
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Build up tooltip
|
|
29
|
+
# @param title [String]
|
|
30
|
+
# @param icon_suffix [String]
|
|
31
|
+
# @param html_options [Hash]
|
|
32
|
+
# @return [String] tooltip HTML
|
|
33
|
+
def itooltip(title, icon_suffix = 'info-circle', html_options = {})
|
|
34
|
+
html_options[:title] = title
|
|
35
|
+
(html_options[:data] ||= {}).merge! toggle: 'tooltip', placement: 'top'
|
|
36
|
+
|
|
37
|
+
fa_icon icon_suffix, html_options
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Build up modal
|
|
41
|
+
# @param title [String]
|
|
42
|
+
# @param body [String]
|
|
43
|
+
# @param html_options [Hash]
|
|
44
|
+
# @return [String] modal HTML
|
|
45
|
+
def imodal(title, body, html_options = {})
|
|
46
|
+
label ||= html_options.delete(:label) \
|
|
47
|
+
|| html_options.delete(:icon) || fa_icon('clone')
|
|
48
|
+
content_tag :span, class: 'modaler' do
|
|
49
|
+
concat link_to(label, '#', data: { target: '#imodal', toggle: 'modal' })
|
|
50
|
+
concat content_tag(:span, title, class: 'modaler__title')
|
|
51
|
+
concat content_tag(:span, body, class: 'modaler__body')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [String] grey null
|
|
56
|
+
def null
|
|
57
|
+
muted t 'labels.empty'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [String] grey N/A
|
|
61
|
+
def na
|
|
62
|
+
muted t 'labels.na'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Grey text
|
|
66
|
+
# @param text_content [String]
|
|
67
|
+
# @return [String] HTML I element
|
|
68
|
+
def muted(text_content)
|
|
69
|
+
content_tag :i, "<#{text_content}>", class: 'text-muted'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param name [String]
|
|
73
|
+
# @return [String] FontAwesome icon name
|
|
74
|
+
def fa_map(name, major = nil)
|
|
75
|
+
@map ||= begin
|
|
76
|
+
major ||= Gem.loaded_specs['font-awesome-sass'].try(:version).try(:segments).try(:first)
|
|
77
|
+
t("fa.v#{major}").with_indifferent_access
|
|
78
|
+
end
|
|
79
|
+
@map[name] || name
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# This is the interface that all ORM modes should inherit from and implement.
|
|
5
|
+
class Mode
|
|
6
|
+
class << self
|
|
7
|
+
# @see Wallaby::ModelDecorator
|
|
8
|
+
# @return [Class] model decorator for the mode
|
|
9
|
+
def model_decorator
|
|
10
|
+
check_and_constantize __callee__
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @see Wallaby::ModelFinder
|
|
14
|
+
# @return [Class] model finder for the mode
|
|
15
|
+
def model_finder
|
|
16
|
+
check_and_constantize __callee__
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @see Wallaby::ModelServiceProvider
|
|
20
|
+
# @return [Class] service provider for the mode
|
|
21
|
+
def model_service_provider
|
|
22
|
+
check_and_constantize __callee__
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @see Wallaby::ModelPaginationProvider
|
|
26
|
+
# @return [Class] pagination provider for the mode
|
|
27
|
+
# @since 5.2.0
|
|
28
|
+
def model_pagination_provider
|
|
29
|
+
check_and_constantize __callee__
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @see Wallaby::ModelPaginationProvider
|
|
33
|
+
# @return [Class] pagination provider for the mode
|
|
34
|
+
# @since 5.2.0
|
|
35
|
+
def default_authorization_provider
|
|
36
|
+
check_and_constantize __callee__
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Return a list of authorization providers for authorizer to detect which one to use.
|
|
40
|
+
# @see Wallaby::ModelAuthorizationProvider
|
|
41
|
+
# @return [ActiveSupport::HashWithIndifferentAccess<String, Class>] authorization provider hash
|
|
42
|
+
# @since 5.2.0
|
|
43
|
+
def model_authorization_providers(classes = ModelAuthorizationProvider.descendants)
|
|
44
|
+
# NOTE: make sure default provider always comes last
|
|
45
|
+
@model_authorization_providers ||=
|
|
46
|
+
classes
|
|
47
|
+
.select { |klass| klass.name.include? name }
|
|
48
|
+
.sort_by { |klass| klass.provider_name == DEFAULT_PROVIDER ? 1 : 0 }
|
|
49
|
+
.each_with_object(::ActiveSupport::HashWithIndifferentAccess.new) do |klass, hash|
|
|
50
|
+
hash[klass.provider_name] = klass
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# @return [Class] constantized class
|
|
57
|
+
def check_and_constantize(method_id)
|
|
58
|
+
method_class = method_id.to_s.classify
|
|
59
|
+
class_name = "#{name}::#{method_class}"
|
|
60
|
+
parent_class = "Wallaby::#{method_class}".constantize
|
|
61
|
+
class_name.constantize.tap do |klass|
|
|
62
|
+
next if klass < parent_class
|
|
63
|
+
|
|
64
|
+
raise InvalidError, I18n.t('wallaby.mode.inherit_required', klass: klass, parent: parent_class)
|
|
65
|
+
end
|
|
66
|
+
rescue NameError => e
|
|
67
|
+
Rails.logger.error e
|
|
68
|
+
raise NotImplemented, class_name
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Model Authorizer interface.
|
|
5
|
+
# @since 5.2.0
|
|
6
|
+
class ModelAuthorizationProvider
|
|
7
|
+
class << self
|
|
8
|
+
# @!attribute [w] provider_name
|
|
9
|
+
attr_writer :provider_name
|
|
10
|
+
|
|
11
|
+
# @!attribute [r] provider_name
|
|
12
|
+
# This is the provider name that can be set in Wallaby::ModelAuthorizer subclasses.
|
|
13
|
+
# @see Wallaby::ModelAuthorizer.provider_name
|
|
14
|
+
# @return [String/Symbol] provider name
|
|
15
|
+
def provider_name
|
|
16
|
+
@provider_name || name.demodulize.gsub(/(Authorization)?Provider/, EMPTY_STRING).underscore
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @note Template method to check and see if current provider is in used.
|
|
20
|
+
# @param _context [ActionController::Base]
|
|
21
|
+
# @raise [Wallaby::NotImplemented]
|
|
22
|
+
def available?(_context)
|
|
23
|
+
raise NotImplemented
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @note Template method to pull out the args required for contruction from context.
|
|
27
|
+
# @param _context [ActionController::Base]
|
|
28
|
+
# @raise [Wallaby::NotImplemented]
|
|
29
|
+
def args_from(_context)
|
|
30
|
+
raise NotImplemented
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @!attribute [r] context
|
|
35
|
+
# @return [ActionController::Base]
|
|
36
|
+
attr_reader :context
|
|
37
|
+
|
|
38
|
+
# @!attribute [r] user
|
|
39
|
+
# @return [Object]
|
|
40
|
+
attr_reader :user
|
|
41
|
+
|
|
42
|
+
# Empty initialize that accepts all sorts of args
|
|
43
|
+
def initialize(*); end
|
|
44
|
+
|
|
45
|
+
# @note It can be overridden in subclasses for customization purpose.
|
|
46
|
+
# This is the template method to check user's permission for given action on given subject.
|
|
47
|
+
# @param _action [Symbol, String]
|
|
48
|
+
# @param _subject [Object, Class]
|
|
49
|
+
# @raise [Wallaby::NotImplemented]
|
|
50
|
+
def authorize(_action, _subject)
|
|
51
|
+
raise NotImplemented
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @note It can be overridden in subclasses for customization purpose.
|
|
55
|
+
# This is the template method to check if user has permission for given action on given subject.
|
|
56
|
+
# @param _action [Symbol, String]
|
|
57
|
+
# @param _subject [Object, Class]
|
|
58
|
+
# @raise [Wallaby::NotImplemented]
|
|
59
|
+
def authorized?(_action, _subject)
|
|
60
|
+
raise NotImplemented
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @note It can be overridden in subclasses for customization purpose.
|
|
64
|
+
# This is the template method to check if user has no permission for given action on given subject.
|
|
65
|
+
# @param action [Symbol, String]
|
|
66
|
+
# @param subject [Object, Class]
|
|
67
|
+
# @raise [Wallaby::NotImplemented]
|
|
68
|
+
def unauthorized?(action, subject)
|
|
69
|
+
!authorized?(action, subject)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @note It can be overridden in subclasses for customization purpose.
|
|
73
|
+
# This is the template method to restrict user's access to certain scope.
|
|
74
|
+
# @param _action [Symbol, String]
|
|
75
|
+
# @param _scope [Object]
|
|
76
|
+
# @raise [Wallaby::NotImplemented]
|
|
77
|
+
def accessible_for(_action, _scope)
|
|
78
|
+
raise NotImplemented
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @note It can be overridden in subclasses for customization purpose.
|
|
82
|
+
# This is the template method to restrict user's modification to certain fields of given subject.
|
|
83
|
+
# @param _action [Symbol, String]
|
|
84
|
+
# @param _subject [Object]
|
|
85
|
+
# @raise [Wallaby::NotImplemented]
|
|
86
|
+
def attributes_for(_action, _subject)
|
|
87
|
+
raise NotImplemented
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @note It can be overridden in subclasses for customization purpose.
|
|
91
|
+
# This is the template method to restrict user's mass assignment to certain fields of given subject.
|
|
92
|
+
# @param _action [Symbol, String]
|
|
93
|
+
# @param _subject [Object]
|
|
94
|
+
# @raise [Wallaby::NotImplemented]
|
|
95
|
+
def permit_params(_action, _subject)
|
|
96
|
+
raise NotImplemented
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Model Decorator interface, designed to maintain metadata for all fields from the data source (database/api)
|
|
5
|
+
# @see Wallaby::ResourceDecorator for more information on how to customize metadata
|
|
6
|
+
class ModelDecorator
|
|
7
|
+
include Fieldable
|
|
8
|
+
|
|
9
|
+
# Initialize with model class
|
|
10
|
+
# @param model_class [Class]
|
|
11
|
+
def initialize(model_class)
|
|
12
|
+
@model_class = model_class
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @!attribute [r] model_class
|
|
16
|
+
# @return [Class]
|
|
17
|
+
attr_reader :model_class
|
|
18
|
+
|
|
19
|
+
# @!attribute [r] fields
|
|
20
|
+
# @note to be implemented in subclasses.
|
|
21
|
+
# Origin fields metadata.
|
|
22
|
+
#
|
|
23
|
+
# Initially, {#index_fields}, {#show_fields} and {#form_fields} are copies of it.
|
|
24
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
25
|
+
def fields
|
|
26
|
+
raise NotImplemented
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @!attribute [w] model_class
|
|
30
|
+
def fields=(fields)
|
|
31
|
+
@fields = fields.with_indifferent_access
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @!attribute [r] index_fields
|
|
35
|
+
# @note to be implemented in subclasses.
|
|
36
|
+
# Fields metadata for `index` page.
|
|
37
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
38
|
+
def index_fields
|
|
39
|
+
raise NotImplemented
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @!attribute [w] index_fields
|
|
43
|
+
def index_fields=(fields)
|
|
44
|
+
@index_fields = fields.with_indifferent_access
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @!attribute [w] index_field_names
|
|
48
|
+
attr_writer :index_field_names
|
|
49
|
+
|
|
50
|
+
# @!attribute [r] index_field_names
|
|
51
|
+
# A list of field names for `index` page
|
|
52
|
+
# @return [Array<String, Symbol>]
|
|
53
|
+
def index_field_names
|
|
54
|
+
@index_field_names ||= reposition index_fields.keys, primary_key
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @!attribute [r] show_fields
|
|
58
|
+
# @note to be implemented in subclasses.
|
|
59
|
+
# Fields metadata for `show` page.
|
|
60
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
61
|
+
def show_fields
|
|
62
|
+
raise NotImplemented
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @!attribute [w] show_fields
|
|
66
|
+
def show_fields=(fields)
|
|
67
|
+
@show_fields = fields.with_indifferent_access
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @!attribute [w] show_field_names
|
|
71
|
+
attr_writer :show_field_names
|
|
72
|
+
|
|
73
|
+
# @!attribute [r] show_field_names
|
|
74
|
+
# A list of field names for `show` page
|
|
75
|
+
# @return [Array<String, Symbol>]
|
|
76
|
+
def show_field_names
|
|
77
|
+
@show_field_names ||= reposition show_fields.keys, primary_key
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @!attribute [r] form_fields
|
|
81
|
+
# @note to be implemented in subclasses.
|
|
82
|
+
# Fields metadata for form (`new`/`edit`) page.
|
|
83
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
84
|
+
def form_fields
|
|
85
|
+
raise NotImplemented
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @!attribute [w] form_fields
|
|
89
|
+
def form_fields=(fields)
|
|
90
|
+
@form_fields = fields.with_indifferent_access
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @!attribute [w] form_field_names
|
|
94
|
+
attr_writer :form_field_names
|
|
95
|
+
|
|
96
|
+
# @!attribute [r] form_field_names
|
|
97
|
+
# A list of field names for form (`new`/`edit`) page
|
|
98
|
+
# @return [Array<String, Symbol>]
|
|
99
|
+
def form_field_names
|
|
100
|
+
@form_field_names ||= reposition form_fields.keys, primary_key
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @!attribute [r] filters
|
|
104
|
+
# @note to be implemented in subclasses.
|
|
105
|
+
# Filter metadata for index page.
|
|
106
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
107
|
+
def filters
|
|
108
|
+
@filters ||= ::ActiveSupport::HashWithIndifferentAccess.new
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @note to be implemented in subclasses.
|
|
112
|
+
# Validation error for given resource
|
|
113
|
+
# @param _resource [Object]
|
|
114
|
+
# @return [ActiveModel::Errors, Hash]
|
|
115
|
+
def form_active_errors(_resource)
|
|
116
|
+
raise NotImplemented
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @!attribute [w] primary_key
|
|
120
|
+
attr_writer :primary_key
|
|
121
|
+
|
|
122
|
+
# @!attribute [r] primary_key
|
|
123
|
+
# @note to be implemented in subclasses.
|
|
124
|
+
# @return [String] primary key name
|
|
125
|
+
def primary_key
|
|
126
|
+
raise NotImplemented
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @note to be implemented in subclasses.
|
|
130
|
+
# To guess the title for a resource.
|
|
131
|
+
#
|
|
132
|
+
# This title will be used on the following places:
|
|
133
|
+
#
|
|
134
|
+
# - page title on show page
|
|
135
|
+
# - in the response for autocomplete association field
|
|
136
|
+
# - title of show link for a resource
|
|
137
|
+
# @param _resource [Object]
|
|
138
|
+
# @return [String] title of current resource
|
|
139
|
+
def guess_title(_resource)
|
|
140
|
+
raise NotImplemented
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @return [String]
|
|
144
|
+
# @see Wallaby::Map.resources_name_map
|
|
145
|
+
def resources_name
|
|
146
|
+
Map.resources_name_map model_class
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
protected
|
|
150
|
+
|
|
151
|
+
# Move primary key to the front for given field names.
|
|
152
|
+
# @param field_names [Array<String, Symbol>] field names
|
|
153
|
+
# @param primary_key [String, Symbol] primary key name
|
|
154
|
+
# @return [Array<String, Symbol>]
|
|
155
|
+
# a new list of field names that primary key goes first
|
|
156
|
+
def reposition(field_names, primary_key)
|
|
157
|
+
field_names.unshift(primary_key.to_s).uniq
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Validate presence of a type for given field name
|
|
161
|
+
# @param type [String, Symbol, nil]
|
|
162
|
+
# @return [String, Symbol] type
|
|
163
|
+
# @raise [ArgumentError] when type is nil
|
|
164
|
+
def validate_presence_of(field_name, type)
|
|
165
|
+
type || raise(::ArgumentError, I18n.t('errors.invalid.type_required', field_name: field_name))
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|