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,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Resolver to provide support for cell and partial
|
|
5
|
+
# @since 5.2.0
|
|
6
|
+
class CellResolver < ActionView::OptimizedFileSystemResolver
|
|
7
|
+
# for Rails 5.2 and below
|
|
8
|
+
begin
|
|
9
|
+
# @note this method is only applicable to Rails 5.2 and below
|
|
10
|
+
# A cell query looks like:
|
|
11
|
+
#
|
|
12
|
+
# ```
|
|
13
|
+
# app/views/wallaby/resources/index/integer{_en,}{_html,}.rb
|
|
14
|
+
# ```
|
|
15
|
+
#
|
|
16
|
+
# Wallaby adds it to the front of the whole query as below:
|
|
17
|
+
#
|
|
18
|
+
# ```
|
|
19
|
+
# {app/views/wallaby/resources/index/integer{_en,}{_html,}.rb,
|
|
20
|
+
# app/views/wallaby/resources/index/_integer{.en,}{.html,}{.erb,}}
|
|
21
|
+
# ```
|
|
22
|
+
# @param path [String]
|
|
23
|
+
# @param details [Hash]
|
|
24
|
+
# see {https://api.rubyonrails.org/classes/ActionView/LookupContext/ViewPaths.html#method-i-detail_args_for
|
|
25
|
+
# Detials from ViewPaths}
|
|
26
|
+
# @return [String] a path query
|
|
27
|
+
def build_query(path, details)
|
|
28
|
+
# NOTE: super is impacted by {#escape_entry}
|
|
29
|
+
origin = super
|
|
30
|
+
file_name = origin[%r{(?<=/\{,_\})[^/\{]+}]
|
|
31
|
+
return origin unless file_name
|
|
32
|
+
|
|
33
|
+
base_dir = origin.gsub(%r{/[^/]*$}, '')
|
|
34
|
+
locales = convert details[:locale]
|
|
35
|
+
formats = convert details[:formats]
|
|
36
|
+
cell = "#{base_dir}/#{file_name}{#{locales}}{#{formats}}.rb"
|
|
37
|
+
"{#{cell},#{origin}}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# for Rails 6 and above
|
|
42
|
+
begin
|
|
43
|
+
# @note this method is only applicable to Rails 6 and above
|
|
44
|
+
# This is to extend the origin functionality to enable to return cell file.
|
|
45
|
+
# at highest precedence.
|
|
46
|
+
# @param path [String]
|
|
47
|
+
# @param details [Hash]
|
|
48
|
+
# @return [ActionView::Template] found template
|
|
49
|
+
def find_template_paths_from_details(path, details)
|
|
50
|
+
details[:handlers].unshift(:rb) if details[:handlers].try(:first) != :rb
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @note this method is only applicable to Rails 6 and above
|
|
55
|
+
# This is to extend the origin functionality to enable to query cell files.
|
|
56
|
+
# @param path [String]
|
|
57
|
+
# @param details [Hash]
|
|
58
|
+
# @return [Regexp]
|
|
59
|
+
def build_regex(path, details)
|
|
60
|
+
origin = super.source
|
|
61
|
+
Regexp.new(
|
|
62
|
+
origin
|
|
63
|
+
.gsub(%r{/\{,_\}([^/]+)\z}, '/_?\\1')
|
|
64
|
+
.gsub('\\.', '[_\\.]')
|
|
65
|
+
.gsub('raw|', 'rb|raw|')
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# This is to extend the origin funcationality to enable the query
|
|
71
|
+
# to look for cell files
|
|
72
|
+
# @example extend the query
|
|
73
|
+
# escape_entry('integer') # => '/{,_}integer'
|
|
74
|
+
# @param entry [String]
|
|
75
|
+
# @return [String] an escaped and extended query
|
|
76
|
+
def escape_entry(entry)
|
|
77
|
+
super.gsub(%r{/_([^/]+)\z}, '/{,_}\1')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# @example concat a list of values into a string
|
|
83
|
+
# convert(['html', 'csv']) # => '_html,_cvs,'
|
|
84
|
+
# @param values [Array<String>]
|
|
85
|
+
def convert(values)
|
|
86
|
+
(values.map { |v| "_#{v}" } << '').join ','
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# A custom lookup context that uses {Wallaby::CellResolver} to find cell/partial
|
|
5
|
+
class CustomLookupContext < ::ActionView::LookupContext
|
|
6
|
+
def self.normalize(lookup, details: nil, prefixes: nil)
|
|
7
|
+
return lookup if lookup.is_a? self
|
|
8
|
+
|
|
9
|
+
CustomLookupContext.new(
|
|
10
|
+
lookup.view_paths,
|
|
11
|
+
details || lookup.instance_variable_get('@details'),
|
|
12
|
+
prefixes || lookup.prefixes
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @note for Rails 6 and above
|
|
17
|
+
# It overrides the origin method to convert paths to {Wallaby::CellResolver}
|
|
18
|
+
# @param paths [Array]
|
|
19
|
+
# @return [ActionView::PathSet]
|
|
20
|
+
def build_view_paths(paths)
|
|
21
|
+
ActionView::PathSet.new Array(paths).map(&method(:convert))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @note for Rails 5.2 and below
|
|
25
|
+
# It overrides the origin method to convert paths to {Wallaby::CellResolver}
|
|
26
|
+
# @param paths [Array]
|
|
27
|
+
# @return [ActionView::PathSet]
|
|
28
|
+
def view_paths=(paths)
|
|
29
|
+
@view_paths = build_view_paths paths
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# It overrides the oirgin method to call the origin `find_template` and cache the result during a request.
|
|
33
|
+
# @param name [String]
|
|
34
|
+
# @param prefixes [Array<String>]
|
|
35
|
+
# @param partial [Boolean]
|
|
36
|
+
# @param keys [Array<String>] keys of local variables
|
|
37
|
+
# @param options [Hash]
|
|
38
|
+
def find_template(name, prefixes = [], partial = false, keys = [], options = {})
|
|
39
|
+
prefixes = [] if partial && name.include?(SLASH) # reset the prefixes if `/` is detected
|
|
40
|
+
key = [name, prefixes, partial, keys, options].map(&:inspect).join(SLASH)
|
|
41
|
+
cached_lookup[key] ||= super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
# @!attribute [r] cached_lookup
|
|
47
|
+
# Cached lookup result
|
|
48
|
+
def cached_lookup
|
|
49
|
+
@cached_lookup ||= {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Convert path to {Wallaby::CellResolver}
|
|
53
|
+
# @param path [Object]
|
|
54
|
+
# @return [Wallaby::CellResolver]
|
|
55
|
+
def convert(path)
|
|
56
|
+
case path
|
|
57
|
+
when ActionView::OptimizedFileSystemResolver, Pathname, String
|
|
58
|
+
CellResolver.new path.to_s
|
|
59
|
+
else
|
|
60
|
+
path
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Custom partial renderer to provide support for cell rendering
|
|
5
|
+
class CustomPartialRenderer < ::ActionView::PartialRenderer
|
|
6
|
+
# When a type partial is found, it works as usual.
|
|
7
|
+
#
|
|
8
|
+
# But when a cell is found, there is an exception {Wallaby::CellHandling} raised. This error will be captured,
|
|
9
|
+
# and the cell will be rendered.
|
|
10
|
+
# @param context [ActionView::Context]
|
|
11
|
+
# @param options [Hash]
|
|
12
|
+
# @param block [Proc]
|
|
13
|
+
# @return [String] HTML output
|
|
14
|
+
def render(context, options, block)
|
|
15
|
+
super.try do |rendered|
|
|
16
|
+
ModuleUtils.try_to(rendered, :body) || # Rails 6 and above
|
|
17
|
+
rendered # Rails 5.2 and below
|
|
18
|
+
end
|
|
19
|
+
rescue CellHandling => e
|
|
20
|
+
CellUtils.render context, e.message, options[:locals], &block
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Override origin method to stop rendering when a cell is found.
|
|
24
|
+
# @return [ActionView::Template] partial template
|
|
25
|
+
# @raise [Wallaby:::CellHandling] when a cell is found
|
|
26
|
+
def find_partial(*)
|
|
27
|
+
super.tap do |partial|
|
|
28
|
+
cell = CellUtils.find_cell(partial.identifier, partial.inspect)
|
|
29
|
+
raise CellHandling, cell if cell
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Custom view renderer to provide support for cell rendering
|
|
5
|
+
class CustomRenderer < ::ActionView::Renderer
|
|
6
|
+
def initialize(lookup_context)
|
|
7
|
+
super CustomLookupContext.normalize(lookup_context)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @return [String] HTML output
|
|
11
|
+
# @see Wallaby::CustomPartialRenderer
|
|
12
|
+
def render_partial(context, options, &block) #:nodoc:
|
|
13
|
+
CustomPartialRenderer.new(lookup_context).render(context, options, block)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Easter egg: simple responder for JSON API
|
|
5
|
+
class JsonApiResponder < ResourcesResponder
|
|
6
|
+
delegate :params, :headers, to: :request
|
|
7
|
+
|
|
8
|
+
# @see #to_json
|
|
9
|
+
def to_html
|
|
10
|
+
to_json
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [String] JSON
|
|
14
|
+
def to_json(*)
|
|
15
|
+
json_options = { content_type: 'application/vnd.api+json', status: options[:status] }
|
|
16
|
+
if exception?
|
|
17
|
+
render json_options.merge(json: exception_details)
|
|
18
|
+
else
|
|
19
|
+
render json_options.merge(action_options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def action_options
|
|
26
|
+
if !get? && has_errors?
|
|
27
|
+
{ json: resource_errors, status: :unprocessable_entity }
|
|
28
|
+
elsif index?
|
|
29
|
+
{ json: collection_data }
|
|
30
|
+
else
|
|
31
|
+
{ json: resource_data }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def single(resource)
|
|
36
|
+
{
|
|
37
|
+
id: resource.id,
|
|
38
|
+
type: params[:resources],
|
|
39
|
+
attributes: attributes_of(resource)
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def collection_data
|
|
44
|
+
{
|
|
45
|
+
data: resource.map(&method(:single)),
|
|
46
|
+
links: {
|
|
47
|
+
self: controller.url_for(resources: params[:resources], action: 'index')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resource_data
|
|
53
|
+
{
|
|
54
|
+
data: single(resource),
|
|
55
|
+
links: {
|
|
56
|
+
self: controller.url_for(resources: params[:resources], action: 'show', id: resource.id)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def resource_errors
|
|
62
|
+
decorated = controller.decorate resource
|
|
63
|
+
{
|
|
64
|
+
errors: decorated.errors.each_with_object([]) do |(field, message), json|
|
|
65
|
+
json.push(
|
|
66
|
+
status: 422,
|
|
67
|
+
source: { pointer: "/data/attributes/#{field}" },
|
|
68
|
+
detail: message
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def attributes_of(resource)
|
|
75
|
+
decorated = controller.decorate resource
|
|
76
|
+
field_names = index? ? decorated.index_field_names : decorated.show_field_names
|
|
77
|
+
field_names.each_with_object({}) do |name, attributes|
|
|
78
|
+
attributes[name] = decorated.public_send name
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def exception_details
|
|
83
|
+
{
|
|
84
|
+
errors: [
|
|
85
|
+
{
|
|
86
|
+
status: options[:status],
|
|
87
|
+
detail: resource.try(:message)
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def index?
|
|
94
|
+
params[:action] == 'index'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def exception?
|
|
98
|
+
(resource.nil? || resource.is_a?(Exception)) && options[:template] == ERROR_PATH
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Resources responder
|
|
5
|
+
class ResourcesResponder < ActionController::Responder
|
|
6
|
+
include ::Responders::FlashResponder
|
|
7
|
+
|
|
8
|
+
# Render CSV with a file name that contains export timestamp.
|
|
9
|
+
def to_csv
|
|
10
|
+
controller.headers['Content-Disposition'] = "attachment; filename=\"#{file_name_to_export}\""
|
|
11
|
+
default_render
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
# @return [String] file name with export timestamp
|
|
17
|
+
def file_name_to_export
|
|
18
|
+
timestamp = Time.zone.now.to_s(:number)
|
|
19
|
+
"#{request.params[:resources]}-exported-#{timestamp}.#{format}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Boolean] true if there is exception or resource has errors
|
|
23
|
+
# @return [Boolean] false otherwise
|
|
24
|
+
def has_errors? # rubocop:disable Naming/PredicateName
|
|
25
|
+
resource.nil? || resource.is_a?(Exception) || controller.decorate(resource).errors.present?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# This is the core of wallaby that dynamically dispatches request to appropriate controller and action.
|
|
5
|
+
class ResourcesRouter
|
|
6
|
+
# @see http://edgeguides.rubyonrails.org/routing.html#routing-to-rack-applications
|
|
7
|
+
# It tries to find out the controller that has the same model class from converted resources name.
|
|
8
|
+
# Otherwise, it falls back to base resources controller which will come from the following sources:
|
|
9
|
+
#
|
|
10
|
+
# 1. `:resources_controller` parameter
|
|
11
|
+
# 2. resources_controller mapping configuration,
|
|
12
|
+
# e.g. `Admin::ApplicationController` if defined or `Wallaby::ResourcesController`
|
|
13
|
+
# @param env [Hash] @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC
|
|
14
|
+
def call(env)
|
|
15
|
+
params = env[ActionDispatch::Http::Parameters::PARAMETERS_KEY]
|
|
16
|
+
controller = find_controller_by params
|
|
17
|
+
controller.action(params[:action]).call env
|
|
18
|
+
rescue ::AbstractController::ActionNotFound, ModelNotFound => e
|
|
19
|
+
set_message_for e, env
|
|
20
|
+
default_controller(params).action(:not_found).call env
|
|
21
|
+
rescue UnprocessableEntity => e
|
|
22
|
+
set_message_for e, env
|
|
23
|
+
default_controller(params).action(:unprocessable_entity).call env
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# Find controller class
|
|
29
|
+
# @param params [Hash]
|
|
30
|
+
# @return [Class] controller class
|
|
31
|
+
def find_controller_by(params)
|
|
32
|
+
model_class = find_model_class_by params
|
|
33
|
+
Map.controller_map(model_class, params[:resources_controller]) || default_controller(params)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Default controller class sources from:
|
|
37
|
+
#
|
|
38
|
+
# 1. `:resources_controller` parameter
|
|
39
|
+
# 2. resources_controller mapping configuration,
|
|
40
|
+
# @param params [Hash]
|
|
41
|
+
# @return [Class] controller class
|
|
42
|
+
def default_controller(params)
|
|
43
|
+
params[:resources_controller] || Wallaby.configuration.mapping.resources_controller
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Find out the model class
|
|
47
|
+
# @param params [Hash]
|
|
48
|
+
# @return [Class]
|
|
49
|
+
# @raise [Wallaby::ModelNotFound] when model class is not found
|
|
50
|
+
# @raise [Wallaby::UnprocessableEntity] when there is no corresponding mode found for model class
|
|
51
|
+
def find_model_class_by(params)
|
|
52
|
+
model_class = Map.model_class_map params[:resources]
|
|
53
|
+
return model_class unless MODEL_ACTIONS.include? params[:action].to_sym
|
|
54
|
+
raise ModelNotFound, params[:resources] unless model_class
|
|
55
|
+
unless Map.mode_map[model_class]
|
|
56
|
+
raise UnprocessableEntity, I18n.t('errors.unprocessable_entity.model', model: model_class)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
model_class
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Set flash error message
|
|
63
|
+
# @param exception [Exception]
|
|
64
|
+
# @param env [Hash] @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC
|
|
65
|
+
def set_message_for(exception, env)
|
|
66
|
+
session = env[ActionDispatch::Request::Session::ENV_SESSION_KEY] || {}
|
|
67
|
+
env[ActionDispatch::Flash::KEY] ||= ActionDispatch::Flash::FlashHash.from_session_value session['flash']
|
|
68
|
+
flash = env[ActionDispatch::Flash::KEY]
|
|
69
|
+
flash[:alert] = exception.message
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Model servicer contains resourceful operations for Rails resourceful actions.
|
|
5
|
+
class ModelServicer
|
|
6
|
+
extend Baseable::ClassMethods
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
# @!attribute [w] model_class
|
|
10
|
+
attr_writer :model_class
|
|
11
|
+
|
|
12
|
+
# @!attribute [r] model_class
|
|
13
|
+
# Return associated model class, e.g. return **Product** for **ProductServicer**.
|
|
14
|
+
#
|
|
15
|
+
# If Wallaby can't recognise the model class for Servicer, it's required to be configured as below example:
|
|
16
|
+
# @example To configure model class
|
|
17
|
+
# class Admin::ProductServicer < Admin::ApplicationServicer
|
|
18
|
+
# self.model_class = Product
|
|
19
|
+
# end
|
|
20
|
+
# @example To configure model class for version below 5.2.0
|
|
21
|
+
# class Admin::ProductServicer < Admin::ApplicationServicer
|
|
22
|
+
# def self.model_class
|
|
23
|
+
# Product
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# @return [Class] assoicated model class
|
|
27
|
+
# @return [nil] if current class is marked as base class
|
|
28
|
+
# @return [nil] if current class is the same as the value of {Wallaby::Configuration::Mapping#model_servicer}
|
|
29
|
+
# @return [nil] if current class is {Wallaby::ModelServicer}
|
|
30
|
+
# @return [nil] if assoicated model class is not found
|
|
31
|
+
def model_class
|
|
32
|
+
return unless self < ModelServicer
|
|
33
|
+
return if base_class? || self == Wallaby.configuration.mapping.model_servicer
|
|
34
|
+
|
|
35
|
+
@model_class ||= Map.model_class_map(name.gsub(/(^#{namespace}::)|(Servicer$)/, EMPTY_STRING))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @!attribute provider_class
|
|
39
|
+
# @return [Class] service provider class
|
|
40
|
+
# @since 5.2.0
|
|
41
|
+
attr_accessor :provider_class
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @!attribute [r] model_class
|
|
45
|
+
# @return [Class]
|
|
46
|
+
attr_reader :model_class
|
|
47
|
+
|
|
48
|
+
# @!attribute [r] model_decorator
|
|
49
|
+
# @return [Wallaby::ModelDecorator]
|
|
50
|
+
# @since 5.2.0
|
|
51
|
+
attr_reader :model_decorator
|
|
52
|
+
|
|
53
|
+
# @!attribute [r] authorizer
|
|
54
|
+
# @return [Wallaby::ModelAuthorizer]
|
|
55
|
+
# @since 5.2.0
|
|
56
|
+
attr_reader :authorizer
|
|
57
|
+
|
|
58
|
+
# @!attribute [r] provider
|
|
59
|
+
# @return [Wallaby::ModelServiceProvider]
|
|
60
|
+
# @since 5.2.0
|
|
61
|
+
attr_reader :provider
|
|
62
|
+
|
|
63
|
+
# @!method user
|
|
64
|
+
# @return [Object]
|
|
65
|
+
# @since 5.2.0
|
|
66
|
+
delegate :user, to: :authorizer
|
|
67
|
+
|
|
68
|
+
# During initialization, Wallaby will assign a service provider for this servicer
|
|
69
|
+
# to carry out the actual execution.
|
|
70
|
+
#
|
|
71
|
+
# Therefore, all its actions can be completely replaced by user's own implemnetation.
|
|
72
|
+
# @param model_class [Class]
|
|
73
|
+
# @param authorizer [Wallaby::ModelAuthorizer]
|
|
74
|
+
# @param model_decorator [Wallaby::ModelDecorator]
|
|
75
|
+
# @raise [ArgumentError] if param model_class is blank
|
|
76
|
+
def initialize(model_class, authorizer, model_decorator = nil)
|
|
77
|
+
@model_class = model_class || self.class.model_class
|
|
78
|
+
raise ArgumentError, I18n.t('errors.required', subject: 'model_class') unless @model_class
|
|
79
|
+
|
|
80
|
+
@model_decorator = model_decorator || Map.model_decorator_map(model_class)
|
|
81
|
+
@authorizer = authorizer
|
|
82
|
+
provider_class = self.class.provider_class || Map.service_provider_map(@model_class)
|
|
83
|
+
@provider = provider_class.new(@model_class, @model_decorator)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
87
|
+
# Whitelist parameters for mass assignment.
|
|
88
|
+
# @param params [ActionController::Parameters, Hash]
|
|
89
|
+
# @param action [String, Symbol]
|
|
90
|
+
# @return [ActionController::Parameters] permitted params
|
|
91
|
+
def permit(params, action = nil)
|
|
92
|
+
provider.permit params, action, authorizer
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
96
|
+
# Return a collection by querying the datasource (e.g. database, REST API).
|
|
97
|
+
# @param params [ActionController::Parameters, Hash]
|
|
98
|
+
# @return [Enumerable] list of records
|
|
99
|
+
def collection(params)
|
|
100
|
+
provider.collection params, authorizer
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
104
|
+
# Paginate given {#collection}.
|
|
105
|
+
# @param query [Enumerable]
|
|
106
|
+
# @param params [ActionController::Parameters]
|
|
107
|
+
# @return [Enumerable] list of records
|
|
108
|
+
delegate :paginate, to: :provider
|
|
109
|
+
|
|
110
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
111
|
+
# Initialize an instance of the model class.
|
|
112
|
+
# @param params [ActionController::Parameters]
|
|
113
|
+
# @return [Object] initialized object
|
|
114
|
+
def new(params)
|
|
115
|
+
provider.new params, authorizer
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
119
|
+
# To find a record.
|
|
120
|
+
# @param id [Object]
|
|
121
|
+
# @param params [ActionController::Parameters]
|
|
122
|
+
# @return [Object] resource object
|
|
123
|
+
def find(id, params)
|
|
124
|
+
provider.find id, params, authorizer
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
128
|
+
# To create a record.
|
|
129
|
+
# @param resource [Object]
|
|
130
|
+
# @param params [ActionController::Parameters]
|
|
131
|
+
# @return [Object] resource object
|
|
132
|
+
def create(resource, params)
|
|
133
|
+
provider.create resource, params, authorizer
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
137
|
+
# To update a record.
|
|
138
|
+
# @param resource [Object]
|
|
139
|
+
# @param params [ActionController::Parameters]
|
|
140
|
+
# @return [Object] resource object
|
|
141
|
+
def update(resource, params)
|
|
142
|
+
provider.update resource, params, authorizer
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @note This is a template method that can be overridden by subclasses.
|
|
146
|
+
# To delete a record.
|
|
147
|
+
# @param resource [Object]
|
|
148
|
+
# @param params [ActionController::Parameters]
|
|
149
|
+
# @return [Object] resource object
|
|
150
|
+
def destroy(resource, params)
|
|
151
|
+
provider.destroy resource, params, authorizer
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|