wallaby-core 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|