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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +31 -0
  4. data/app/controllers/wallaby/application_controller.rb +84 -0
  5. data/app/controllers/wallaby/resources_controller.rb +381 -0
  6. data/app/controllers/wallaby/secure_controller.rb +81 -0
  7. data/app/security/ability.rb +13 -0
  8. data/config/locales/wallaby.en.yml +140 -0
  9. data/config/locales/wallaby_class.en.yml +30 -0
  10. data/config/routes.rb +39 -0
  11. data/lib/adaptors/wallaby/custom.rb +7 -0
  12. data/lib/adaptors/wallaby/custom/default_provider.rb +9 -0
  13. data/lib/adaptors/wallaby/custom/model_decorator.rb +71 -0
  14. data/lib/adaptors/wallaby/custom/model_finder.rb +13 -0
  15. data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +14 -0
  16. data/lib/adaptors/wallaby/custom/model_service_provider.rb +48 -0
  17. data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +72 -0
  18. data/lib/authorizers/wallaby/default_authorization_provider.rb +58 -0
  19. data/lib/authorizers/wallaby/model_authorizer.rb +100 -0
  20. data/lib/authorizers/wallaby/pundit_authorization_provider.rb +89 -0
  21. data/lib/concerns/wallaby/authorizable.rb +103 -0
  22. data/lib/concerns/wallaby/baseable.rb +36 -0
  23. data/lib/concerns/wallaby/decoratable.rb +101 -0
  24. data/lib/concerns/wallaby/defaultable.rb +38 -0
  25. data/lib/concerns/wallaby/engineable.rb +61 -0
  26. data/lib/concerns/wallaby/fieldable.rb +78 -0
  27. data/lib/concerns/wallaby/paginatable.rb +72 -0
  28. data/lib/concerns/wallaby/rails_overridden_methods.rb +42 -0
  29. data/lib/concerns/wallaby/resourcable.rb +149 -0
  30. data/lib/concerns/wallaby/servicable.rb +68 -0
  31. data/lib/concerns/wallaby/shared_helpers.rb +22 -0
  32. data/lib/concerns/wallaby/themeable.rb +40 -0
  33. data/lib/decorators/wallaby/resource_decorator.rb +189 -0
  34. data/lib/errors/wallaby/cell_handling.rb +6 -0
  35. data/lib/errors/wallaby/forbidden.rb +6 -0
  36. data/lib/errors/wallaby/general_error.rb +6 -0
  37. data/lib/errors/wallaby/invalid_error.rb +6 -0
  38. data/lib/errors/wallaby/model_not_found.rb +11 -0
  39. data/lib/errors/wallaby/not_authenticated.rb +6 -0
  40. data/lib/errors/wallaby/not_found.rb +6 -0
  41. data/lib/errors/wallaby/not_implemented.rb +6 -0
  42. data/lib/errors/wallaby/resource_not_found.rb +11 -0
  43. data/lib/errors/wallaby/unprocessable_entity.rb +6 -0
  44. data/lib/forms/wallaby/form_builder.rb +60 -0
  45. data/lib/helpers/wallaby/application_helper.rb +79 -0
  46. data/lib/helpers/wallaby/base_helper.rb +65 -0
  47. data/lib/helpers/wallaby/configuration_helper.rb +18 -0
  48. data/lib/helpers/wallaby/form_helper.rb +62 -0
  49. data/lib/helpers/wallaby/index_helper.rb +84 -0
  50. data/lib/helpers/wallaby/links_helper.rb +213 -0
  51. data/lib/helpers/wallaby/resources_helper.rb +52 -0
  52. data/lib/helpers/wallaby/secure_helper.rb +54 -0
  53. data/lib/helpers/wallaby/styling_helper.rb +82 -0
  54. data/lib/interfaces/wallaby/mode.rb +72 -0
  55. data/lib/interfaces/wallaby/model_authorization_provider.rb +99 -0
  56. data/lib/interfaces/wallaby/model_decorator.rb +168 -0
  57. data/lib/interfaces/wallaby/model_finder.rb +12 -0
  58. data/lib/interfaces/wallaby/model_pagination_provider.rb +107 -0
  59. data/lib/interfaces/wallaby/model_service_provider.rb +84 -0
  60. data/lib/paginators/wallaby/model_paginator.rb +115 -0
  61. data/lib/paginators/wallaby/resource_paginator.rb +12 -0
  62. data/lib/parsers/wallaby/parser.rb +34 -0
  63. data/lib/renderers/wallaby/cell.rb +137 -0
  64. data/lib/renderers/wallaby/cell_resolver.rb +89 -0
  65. data/lib/renderers/wallaby/custom_lookup_context.rb +64 -0
  66. data/lib/renderers/wallaby/custom_partial_renderer.rb +33 -0
  67. data/lib/renderers/wallaby/custom_renderer.rb +16 -0
  68. data/lib/responders/wallaby/json_api_responder.rb +101 -0
  69. data/lib/responders/wallaby/resources_responder.rb +28 -0
  70. data/lib/routes/wallaby/resources_router.rb +72 -0
  71. data/lib/servicers/wallaby/model_servicer.rb +154 -0
  72. data/lib/services/wallaby/engine_name_finder.rb +22 -0
  73. data/lib/services/wallaby/engine_url_for.rb +46 -0
  74. data/lib/services/wallaby/link_options_normalizer.rb +19 -0
  75. data/lib/services/wallaby/map/mode_mapper.rb +27 -0
  76. data/lib/services/wallaby/map/model_class_collector.rb +49 -0
  77. data/lib/services/wallaby/map/model_class_mapper.rb +38 -0
  78. data/lib/services/wallaby/prefixes_builder.rb +66 -0
  79. data/lib/services/wallaby/sorting/hash_builder.rb +19 -0
  80. data/lib/services/wallaby/sorting/link_builder.rb +69 -0
  81. data/lib/services/wallaby/sorting/next_builder.rb +63 -0
  82. data/lib/services/wallaby/sorting/single_builder.rb +20 -0
  83. data/lib/services/wallaby/type_renderer.rb +50 -0
  84. data/lib/support/action_dispatch/routing/mapper.rb +75 -0
  85. data/lib/tree/wallaby/node.rb +25 -0
  86. data/lib/utils/wallaby/cell_utils.rb +34 -0
  87. data/lib/utils/wallaby/field_utils.rb +43 -0
  88. data/lib/utils/wallaby/filter_utils.rb +20 -0
  89. data/lib/utils/wallaby/model_utils.rb +51 -0
  90. data/lib/utils/wallaby/module_utils.rb +46 -0
  91. data/lib/utils/wallaby/params_utils.rb +14 -0
  92. data/lib/utils/wallaby/preload_utils.rb +44 -0
  93. data/lib/utils/wallaby/test_utils.rb +34 -0
  94. data/lib/utils/wallaby/utils.rb +27 -0
  95. data/lib/wallaby/configuration.rb +103 -0
  96. data/lib/wallaby/configuration/features.rb +24 -0
  97. data/lib/wallaby/configuration/mapping.rb +140 -0
  98. data/lib/wallaby/configuration/metadata.rb +23 -0
  99. data/lib/wallaby/configuration/models.rb +46 -0
  100. data/lib/wallaby/configuration/pagination.rb +30 -0
  101. data/lib/wallaby/configuration/security.rb +98 -0
  102. data/lib/wallaby/configuration/sorting.rb +28 -0
  103. data/lib/wallaby/constants.rb +45 -0
  104. data/lib/wallaby/core.rb +117 -0
  105. data/lib/wallaby/core/version.rb +7 -0
  106. data/lib/wallaby/engine.rb +43 -0
  107. data/lib/wallaby/map.rb +170 -0
  108. 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