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,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
|