wallaby-core 0.2.11 → 0.3.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/wallaby/resources_controller.rb +20 -7
- data/app/security/ability.rb +1 -1
- data/config/routes.rb +21 -14
- data/lib/adaptors/wallaby/custom/default_provider.rb +1 -1
- data/lib/adaptors/wallaby/custom/model_decorator.rb +5 -17
- data/lib/adaptors/wallaby/custom/model_finder.rb +3 -3
- data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +1 -1
- data/lib/adaptors/wallaby/custom/model_service_provider.rb +1 -1
- data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +12 -5
- data/lib/authorizers/wallaby/default_authorization_provider.rb +10 -1
- data/lib/authorizers/wallaby/model_authorizer.rb +41 -16
- data/lib/authorizers/wallaby/pundit_authorization_provider.rb +22 -8
- data/lib/concerns/wallaby/application_concern.rb +41 -71
- data/lib/concerns/wallaby/authentication_concern.rb +29 -127
- data/lib/concerns/wallaby/authorizable.rb +14 -57
- data/lib/concerns/wallaby/baseable.rb +24 -57
- data/lib/concerns/wallaby/configurable.rb +416 -0
- data/lib/concerns/wallaby/decoratable.rb +29 -60
- data/lib/concerns/wallaby/engineable.rb +29 -46
- data/lib/concerns/wallaby/fieldable.rb +45 -56
- data/lib/concerns/wallaby/paginatable.rb +20 -51
- data/lib/concerns/wallaby/prefixable.rb +24 -4
- data/lib/concerns/wallaby/resourcable.rb +130 -72
- data/lib/concerns/wallaby/resources_concern.rb +205 -305
- data/lib/concerns/wallaby/servicable.rb +8 -48
- data/lib/concerns/wallaby/urlable.rb +69 -0
- data/lib/decorators/wallaby/resource_decorator.rb +91 -34
- data/lib/errors/wallaby/class_not_found.rb +1 -2
- data/lib/errors/wallaby/forbidden.rb +1 -2
- data/lib/errors/wallaby/general_error.rb +1 -1
- data/lib/errors/wallaby/invalid_error.rb +1 -2
- data/lib/errors/wallaby/method_removed.rb +5 -0
- data/lib/errors/wallaby/model_not_found.rb +1 -2
- data/lib/errors/wallaby/not_authenticated.rb +1 -2
- data/lib/errors/wallaby/not_found.rb +1 -2
- data/lib/errors/wallaby/not_implemented.rb +1 -2
- data/lib/errors/wallaby/resource_not_found.rb +1 -2
- data/lib/errors/wallaby/unprocessable_entity.rb +1 -2
- data/lib/fields/wallaby/all_fields.rb +63 -0
- data/lib/forms/wallaby/form_builder.rb +1 -1
- data/lib/generators/wallaby/engine/application_generator.rb +33 -0
- data/lib/generators/wallaby/engine/authorizer/USAGE +20 -0
- data/lib/generators/wallaby/engine/authorizer/authorizer_generator.rb +19 -0
- data/lib/generators/wallaby/engine/authorizer/templates/authorizer.rb.erb +35 -0
- data/lib/generators/wallaby/engine/controller/USAGE +20 -0
- data/lib/generators/wallaby/engine/controller/controller_generator.rb +23 -0
- data/lib/generators/wallaby/engine/controller/templates/controller.rb.erb +130 -0
- data/lib/generators/wallaby/engine/decorator/USAGE +20 -0
- data/lib/generators/wallaby/engine/decorator/decorator_generator.rb +19 -0
- data/lib/generators/wallaby/engine/decorator/templates/decorator.rb.erb +5 -0
- data/lib/generators/wallaby/engine/install/USAGE +19 -0
- data/lib/generators/wallaby/engine/install/install_generator.rb +91 -0
- data/lib/generators/wallaby/engine/install/templates/application_authorizer.rb.erb +37 -0
- data/lib/generators/wallaby/engine/install/templates/application_controller.rb.erb +173 -0
- data/lib/generators/wallaby/engine/install/templates/application_decorator.rb.erb +7 -0
- data/lib/generators/wallaby/engine/install/templates/application_paginator.rb.erb +27 -0
- data/lib/generators/wallaby/engine/install/templates/application_servicer.rb.erb +47 -0
- data/lib/generators/wallaby/engine/install/templates/initializer.rb.erb +16 -0
- data/lib/generators/wallaby/engine/paginator/USAGE +20 -0
- data/lib/generators/wallaby/engine/paginator/paginator_generator.rb +19 -0
- data/lib/generators/wallaby/engine/paginator/templates/paginator.rb.erb +25 -0
- data/lib/generators/wallaby/engine/servicer/USAGE +20 -0
- data/lib/generators/wallaby/engine/servicer/servicer_generator.rb +19 -0
- data/lib/generators/wallaby/engine/servicer/templates/servicer.rb.erb +45 -0
- data/lib/helpers/wallaby/application_helper.rb +10 -59
- data/lib/helpers/wallaby/base_helper.rb +11 -11
- data/lib/helpers/wallaby/configuration_helper.rb +36 -4
- data/lib/helpers/wallaby/form_helper.rb +1 -1
- data/lib/helpers/wallaby/index_helper.rb +19 -9
- data/lib/helpers/wallaby/links_helper.rb +18 -85
- data/lib/helpers/wallaby/resources_helper.rb +39 -7
- data/lib/helpers/wallaby/secure_helper.rb +20 -19
- data/lib/interfaces/wallaby/mode.rb +8 -8
- data/lib/interfaces/wallaby/model_authorization_provider.rb +23 -22
- data/lib/interfaces/wallaby/model_decorator.rb +36 -48
- data/lib/interfaces/wallaby/model_finder.rb +3 -3
- data/lib/interfaces/wallaby/model_pagination_provider.rb +2 -6
- data/lib/interfaces/wallaby/model_service_provider.rb +4 -4
- data/lib/paginators/wallaby/model_paginator.rb +1 -1
- data/lib/responders/wallaby/json_api_responder.rb +10 -5
- data/lib/responders/wallaby/resources_responder.rb +10 -3
- data/lib/routes/wallaby/engines/base_route.rb +78 -0
- data/lib/routes/wallaby/engines/custom_app_route.rb +92 -0
- data/lib/routes/wallaby/engines/engine_route.rb +77 -0
- data/lib/routes/wallaby/resources_router.rb +100 -45
- data/lib/servicers/wallaby/model_servicer.rb +13 -13
- data/lib/services/wallaby/authorizer_finder.rb +23 -0
- data/lib/services/wallaby/class_finder.rb +42 -0
- data/lib/services/wallaby/controller_finder.rb +29 -0
- data/lib/services/wallaby/decorator_finder.rb +34 -0
- data/lib/services/wallaby/default_models_excluder.rb +45 -0
- data/lib/services/wallaby/engine_name_finder.rb +14 -11
- data/lib/services/wallaby/engine_url_for.rb +82 -37
- data/lib/services/wallaby/fields_regulator.rb +34 -0
- data/lib/services/wallaby/map/mode_mapper.rb +5 -5
- data/lib/services/wallaby/map/model_class_mapper.rb +1 -1
- data/lib/services/wallaby/model_class_filter.rb +29 -0
- data/lib/services/wallaby/paginator_finder.rb +24 -0
- data/lib/services/wallaby/prefixes_builder.rb +49 -8
- data/lib/services/wallaby/servicer_finder.rb +31 -0
- data/lib/services/wallaby/sorting/hash_builder.rb +9 -0
- data/lib/services/wallaby/sorting/link_builder.rb +7 -10
- data/lib/services/wallaby/sorting/next_builder.rb +1 -12
- data/lib/services/wallaby/sorting/single_builder.rb +1 -1
- data/lib/support/action_dispatch/routing/mapper.rb +29 -4
- data/lib/utils/wallaby/field_utils.rb +9 -8
- data/lib/utils/wallaby/inflector.rb +94 -0
- data/lib/utils/wallaby/locale.rb +2 -2
- data/lib/utils/wallaby/module_utils.rb +3 -10
- data/lib/utils/wallaby/utils.rb +21 -14
- data/lib/wallaby/class_array.rb +18 -13
- data/lib/wallaby/class_hash.rb +16 -14
- data/lib/wallaby/classifier.rb +4 -2
- data/lib/wallaby/configuration/features.rb +8 -2
- data/lib/wallaby/configuration/mapping.rb +66 -112
- data/lib/wallaby/configuration/metadata.rb +15 -12
- data/lib/wallaby/configuration/models.rb +27 -25
- data/lib/wallaby/configuration/pagination.rb +15 -19
- data/lib/wallaby/configuration/security.rb +88 -80
- data/lib/wallaby/configuration/sorting.rb +15 -17
- data/lib/wallaby/configuration.rb +58 -23
- data/lib/wallaby/constants.rb +21 -13
- data/lib/wallaby/core/version.rb +1 -1
- data/lib/wallaby/core.rb +34 -10
- data/lib/wallaby/deprecator.rb +81 -0
- data/lib/wallaby/engine.rb +1 -18
- data/lib/wallaby/guesser.rb +45 -0
- data/lib/wallaby/logger.rb +35 -13
- data/lib/wallaby/map.rb +11 -88
- data/lib/wallaby/preloader.rb +8 -28
- metadata +113 -14
- data/config/locales/wallaby_class.en.yml +0 -9
- data/lib/concerns/wallaby/defaultable.rb +0 -38
- data/lib/concerns/wallaby/shared_helpers.rb +0 -22
- data/lib/services/wallaby/map/model_class_collector.rb +0 -49
- data/lib/services/wallaby/type_renderer.rb +0 -40
- data/lib/utils/wallaby/model_utils.rb +0 -52
- data/lib/utils/wallaby/test_utils.rb +0 -34
@@ -1,46 +1,91 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Wallaby
|
4
|
-
# {
|
4
|
+
# URL service object for {Urlable#url_for} helper
|
5
|
+
#
|
6
|
+
# Since {Engine}'s {https://github.com/wallaby-rails/wallaby-core/blob/master/config/routes.rb routes}
|
7
|
+
# are declared in
|
8
|
+
# {https://guides.rubyonrails.org/routing.html#routing-to-rack-applications Rack application}
|
9
|
+
# fashion via {ResourcesRouter} to recoganize path in the pattern of `/:mount_path/:resources`.
|
10
|
+
# It means when the current request path (e.g. `/admin/categories`)
|
11
|
+
# is under the same mount path of {Engine} (e.g. `/admin`),
|
12
|
+
# using the original Rails **usl_for** (e.g. `url_for action: :index`)
|
13
|
+
# without providing the `:resources` param and script name
|
14
|
+
# will lead to an **ActionController::RoutingError** exception.
|
15
|
+
#
|
16
|
+
# To generate the proper URL from given params and options for this kind of requests,
|
17
|
+
# there are three kinds of scenarios that need to be considered
|
18
|
+
# (assume that {Engine} is mounted at `/admin`):
|
19
|
+
#
|
20
|
+
# - if the URL to generate is a regular route defined before mounting the {Engine}
|
21
|
+
# that does not override the resources `categories` routes handled by {Engine}, such as:
|
22
|
+
#
|
23
|
+
# namespace :admin do
|
24
|
+
# resources :custom_categories
|
25
|
+
# end
|
26
|
+
# wallaby_mount at: '/admin'
|
27
|
+
#
|
28
|
+
# - if the URL to generate is a route that overrides the existing {Engine} route
|
29
|
+
# (assume that `categories` is one of the resources handled by {Engine}):
|
30
|
+
#
|
31
|
+
# namespace :admin do
|
32
|
+
# resources :categories
|
33
|
+
# end
|
34
|
+
# wallaby_mount at: '/admin'
|
35
|
+
#
|
36
|
+
# - regular resources handled by {ResourcesRouter}, e.g. (`/admin/products`)
|
5
37
|
class EngineUrlFor
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
include ActiveModel::Model
|
39
|
+
|
40
|
+
# @!attribute context
|
41
|
+
# @return [ActionController::Base, ActionView::Base]
|
42
|
+
attr_accessor :context
|
43
|
+
# @!attribute options
|
44
|
+
# @return [Hash]
|
45
|
+
attr_accessor :options
|
46
|
+
# @!attribute params
|
47
|
+
# @return [Hash, ActionController::Parameters]
|
48
|
+
attr_accessor :params
|
49
|
+
|
50
|
+
# Generate the proper URL depending on the context
|
51
|
+
# @param context [ActionController::Base, ActionView::Base]
|
52
|
+
# @param params [Hash, ActionController::Parameters]
|
53
|
+
# @param options [Hash]
|
54
|
+
# @option model_class [Class]
|
55
|
+
# @option with_query [true] indicate if all query params should be included
|
56
|
+
# @return [nil] nil if params is not a **Hash** or **ActionController::Parameters**
|
57
|
+
# @see #execute
|
58
|
+
def self.execute(context:, params:, options:)
|
59
|
+
return unless params.is_a?(Hash) || params.try(:permitted?)
|
60
|
+
|
61
|
+
new(context: context, params: params, options: options).execute
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [String] URL
|
65
|
+
# @return [nil] nil if current request is not under any mounted {Engine}
|
66
|
+
# @see https://github.com/reinteractive/wallaby/blob/master/config/routes.rb
|
67
|
+
def execute
|
68
|
+
return if context.current_engine_route.blank?
|
69
|
+
return custom_app_route.url if custom_app_route.exist?
|
35
70
|
|
36
|
-
|
71
|
+
engine_route.url
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @return [Wallaby::Engines::CustomAppRoute]
|
77
|
+
def custom_app_route
|
78
|
+
@custom_app_route ||=
|
79
|
+
Engines::CustomAppRoute.new(
|
80
|
+
context: context, params: params, options: options
|
81
|
+
)
|
82
|
+
end
|
37
83
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
84
|
+
# @return [Wallaby::Engines::EngineRoute]
|
85
|
+
def engine_route
|
86
|
+
Engines::EngineRoute.new(
|
87
|
+
context: context, params: params, options: options
|
88
|
+
)
|
44
89
|
end
|
45
90
|
end
|
46
91
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
# Validate and convert the field metadata to `ActiveSupport::HashWithIndifferentAccess`
|
5
|
+
FieldsRegulator = Struct.new(:fields) do
|
6
|
+
# @return [ActiveSupport::HashWithIndifferentAccess]
|
7
|
+
def execute
|
8
|
+
ensure_fields_is_a_hash
|
9
|
+
ensure_type_is_present
|
10
|
+
normalized_fields
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def ensure_fields_is_a_hash
|
16
|
+
return if fields.is_a?(Hash)
|
17
|
+
|
18
|
+
raise ArgumentError, 'Please provide a Hash metadata'
|
19
|
+
end
|
20
|
+
|
21
|
+
def ensure_type_is_present
|
22
|
+
missing_types = normalized_fields.select do |_field, metadata|
|
23
|
+
metadata[:type].blank?
|
24
|
+
end
|
25
|
+
return if missing_types.blank?
|
26
|
+
|
27
|
+
raise ArgumentError, "Please provide the type for #{missing_types.keys.join(',')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def normalized_fields
|
31
|
+
@normalized_fields ||= fields.with_indifferent_access
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -2,21 +2,21 @@
|
|
2
2
|
|
3
3
|
module Wallaby
|
4
4
|
class Map
|
5
|
-
# Go through each {
|
5
|
+
# Go through each {Mode} (e.g. **ActiveRecord**/**Her**)
|
6
6
|
# and find out all the model classes respectively.
|
7
|
-
# Then a hash (Model => {
|
8
|
-
# to tell {Wallaby} which {
|
7
|
+
# Then a hash (Model => {Mode}) is constructed
|
8
|
+
# to tell {Wallaby} which {Mode} to use for a given model.
|
9
9
|
class ModeMapper
|
10
10
|
extend Classifier
|
11
11
|
|
12
|
-
# @param class_names [
|
12
|
+
# @param class_names [ClassArray] mode class names
|
13
13
|
# @return [WallabyClassHash]
|
14
14
|
def self.execute(class_names)
|
15
15
|
ClassHash.new.tap do |hash|
|
16
16
|
next if class_names.blank?
|
17
17
|
|
18
18
|
class_names.each_with_object(hash) do |mode_name, map|
|
19
|
-
mode_name.model_finder.new.all.each do |model_class|
|
19
|
+
mode_name.model_finder.new.all.each do |model_class| # rubocop:disable Rails/FindEach
|
20
20
|
map[model_class] = mode_name
|
21
21
|
end
|
22
22
|
end
|
@@ -5,7 +5,7 @@ module Wallaby
|
|
5
5
|
# Go through the class list and generate a {.map .map} that uses the class's model_class as the key.
|
6
6
|
class ModelClassMapper
|
7
7
|
# @param class_array [Array<Class>]
|
8
|
-
# @return [
|
8
|
+
# @return [ClassHash] model class => descendant class
|
9
9
|
def self.map(class_array)
|
10
10
|
(class_array || EMPTY_ARRAY).each_with_object(ClassHash.new) do |klass, hash|
|
11
11
|
next if ModuleUtils.anonymous_class?(klass)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
# Fitler the model classes using
|
5
|
+
class ModelClassFilter
|
6
|
+
class << self
|
7
|
+
# @param all [Array<Class>]
|
8
|
+
# @param allowlisted [Array<Class>]
|
9
|
+
# @param denylisted [Array<Class>]
|
10
|
+
def execute(all:, allowlisted:, denylisted:)
|
11
|
+
invalid, valid =
|
12
|
+
allowlisted.present? ? [allowlisted - all, allowlisted] : [denylisted - all, all - denylisted]
|
13
|
+
return valid if invalid.blank?
|
14
|
+
|
15
|
+
raise InvalidError, <<~INSTRUCTION
|
16
|
+
Wallaby can't handle these models: #{invalid.map(&:name).to_sentence}.
|
17
|
+
If it's set via controller_class.models= or controller_class.models_to_exclude=,
|
18
|
+
please remove them from the list.
|
19
|
+
|
20
|
+
Or they can be added to Custom model list as below, and custom implementation will be required:
|
21
|
+
|
22
|
+
Wallaby.config do |config|
|
23
|
+
config.custom_models = #{invalid.map(&:name).join ', '}
|
24
|
+
end
|
25
|
+
INSTRUCTION
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
# (see #execute)
|
5
|
+
class PaginatorFinder < ServicerFinder
|
6
|
+
# Find paginator class by script name and model class from the following places:
|
7
|
+
#
|
8
|
+
# - {#current_controller_class #current_controller_class}'s
|
9
|
+
# {Configurable::ClassMethods#model_paginator #model_paginator}
|
10
|
+
# - possible paginator class built from script name and model class,
|
11
|
+
# e.g. **/admin** and **Order::Item** will give us the possible paginators:
|
12
|
+
# - Admin::Order::ItemPaginator
|
13
|
+
# - Order::ItemPaginator
|
14
|
+
# - ItemPaginator
|
15
|
+
# - {#current_controller_class #current_controller_class}'s default
|
16
|
+
# {Configurable::ClassMethods#application_paginator #application_paginator}
|
17
|
+
# @return [Class] paginator class
|
18
|
+
def execute
|
19
|
+
current_controller_class.model_paginator ||
|
20
|
+
possible_default_class ||
|
21
|
+
current_controller_class.application_paginator
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,32 +1,73 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Wallaby
|
4
|
-
#
|
4
|
+
# (see #execute)
|
5
5
|
class PrefixesBuilder
|
6
6
|
include ActiveModel::Model
|
7
7
|
|
8
|
+
delegate :controller_path, to: :controller_class
|
9
|
+
|
10
|
+
# @!attribute prefixes
|
11
|
+
# @return [Array<String>]
|
8
12
|
attr_accessor :prefixes
|
13
|
+
# @!attribute resources_name
|
14
|
+
# @return [String, nil]
|
9
15
|
attr_accessor :resources_name
|
16
|
+
# @!attribute script_name
|
17
|
+
# @return [String]
|
10
18
|
attr_accessor :script_name
|
19
|
+
# @!attribute controller_class
|
20
|
+
# @return [String]
|
21
|
+
attr_accessor :controller_class
|
11
22
|
|
23
|
+
# To extend prefixes to be able to look up the directory
|
24
|
+
# even if the custom controller doesn't not exist
|
25
|
+
# @return [Array<String>] modified prefixes
|
12
26
|
def execute
|
13
|
-
return
|
27
|
+
return prefixes_dup unless resourceful?
|
28
|
+
return prefixes_dup if possible_resources_path == controller_path
|
14
29
|
|
15
|
-
|
30
|
+
prefixes_dup.insert(
|
31
|
+
prefixes_dup.index(controller_path) + offset,
|
32
|
+
possible_resources_path
|
33
|
+
)
|
34
|
+
end
|
16
35
|
|
17
|
-
|
36
|
+
protected
|
18
37
|
|
19
|
-
|
38
|
+
# @return [Array<String>]
|
39
|
+
def prefixes_dup
|
40
|
+
@prefixes_dup ||= prefixes.dup
|
20
41
|
end
|
21
42
|
|
22
|
-
|
43
|
+
# @return [Boolean]
|
44
|
+
def resourceful?
|
45
|
+
resources_name.present?
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Boolean]
|
49
|
+
def based_controller?
|
50
|
+
controller_class.model_class.blank?
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Integer] 0 or 1
|
54
|
+
def offset
|
55
|
+
based_controller? ? 0 : 1
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String]
|
59
|
+
def possible_resources_path
|
60
|
+
[script_path, resources_path].compact.join(SLASH)
|
61
|
+
end
|
23
62
|
|
63
|
+
# @return [String, nil]
|
24
64
|
def resources_path
|
25
|
-
|
65
|
+
resources_name.try(:gsub, COLONS, SLASH)
|
26
66
|
end
|
27
67
|
|
68
|
+
# @return [String, nil]
|
28
69
|
def script_path
|
29
|
-
|
70
|
+
script_name.try(:[], 1..-1)
|
30
71
|
end
|
31
72
|
end
|
32
73
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
# (see #execute)
|
5
|
+
class ServicerFinder < ControllerFinder
|
6
|
+
# Find servicer class by script name and model class from the following places:
|
7
|
+
#
|
8
|
+
# - {#current_controller_class #current_controller_class}'s
|
9
|
+
# {Configurable::ClassMethods#model_servicer #model_servicer}
|
10
|
+
# - possible servicer class built from script name and model class,
|
11
|
+
# e.g. **/admin** and **Order::Item** will give us the possible servicers:
|
12
|
+
# - Admin::Order::ItemServicer
|
13
|
+
# - Order::ItemServicer
|
14
|
+
# - ItemServicer
|
15
|
+
# - {#current_controller_class #current_controller_class}'s default
|
16
|
+
# {Configurable::ClassMethods#application_servicer #application_servicer}
|
17
|
+
# @return [Class] servicer class
|
18
|
+
def execute
|
19
|
+
current_controller_class.model_servicer ||
|
20
|
+
possible_default_class ||
|
21
|
+
current_controller_class.application_servicer
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# (see ClassFinder#denamespace?)
|
27
|
+
def denamespace?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -14,6 +14,15 @@ module Wallaby
|
|
14
14
|
(sort_string || EMPTY_STRING).scan(SORT_REGEX) { |_, key, order| hash[key] = order }
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
def self.to_str(hash)
|
19
|
+
hash.each_with_object(EMPTY_STRING.dup) do |(name, sort), str|
|
20
|
+
next unless /\A(asc|desc)( nulls (first|last))?\Z/i.match?(sort)
|
21
|
+
|
22
|
+
str << (str == EMPTY_STRING ? str : COMMA)
|
23
|
+
str << name.to_s << SPACE << sort << (block_given? ? yield(name) : EMPTY_STRING)
|
24
|
+
end
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
19
28
|
end
|
@@ -9,7 +9,7 @@ module Wallaby
|
|
9
9
|
delegate :model_class, to: :@model_decorator
|
10
10
|
delegate :index_link, :url_for, to: :@helper
|
11
11
|
|
12
|
-
# @param model_decorator [
|
12
|
+
# @param model_decorator [ModelDecorator]
|
13
13
|
# @param params [ActionController::Parameters]
|
14
14
|
# @param helper [ActionView::Helpers]
|
15
15
|
def initialize(model_decorator, params, helper, strategy)
|
@@ -28,15 +28,11 @@ module Wallaby
|
|
28
28
|
|
29
29
|
# Build sort link for given field name:
|
30
30
|
#
|
31
|
-
#
|
32
|
-
# <a title="Product" href="/admin/products?sort=published_at+asc">Name</a>
|
33
|
-
# ```
|
31
|
+
# <a title="Product" href="/admin/products?sort=published_at+asc">Name</a>
|
34
32
|
#
|
35
33
|
# If the field is not sortable, it returns a text, e.g.:
|
36
34
|
#
|
37
|
-
#
|
38
|
-
# Name
|
39
|
-
# ```
|
35
|
+
# Name
|
40
36
|
# @param field_name [String]
|
41
37
|
# @return [String] link or text
|
42
38
|
def build(field_name)
|
@@ -46,13 +42,14 @@ module Wallaby
|
|
46
42
|
|
47
43
|
sort_field_name = metadata[:sort_field_name] || field_name
|
48
44
|
url_params = next_builder.next_params sort_field_name
|
49
|
-
|
50
|
-
|
45
|
+
index_link(
|
46
|
+
model_class, url_params: url_params.merge(with_query: true)
|
47
|
+
) { label }
|
51
48
|
end
|
52
49
|
|
53
50
|
private
|
54
51
|
|
55
|
-
# @return [
|
52
|
+
# @return [Sorting::NextBuilder]
|
56
53
|
def next_builder
|
57
54
|
@next_builder ||= begin
|
58
55
|
klass = SORT_STRATEGIES[@strategy] || NextBuilder
|
@@ -35,18 +35,7 @@ module Wallaby
|
|
35
35
|
hash = @hash.except field_name
|
36
36
|
current_sort = @hash[field_name]
|
37
37
|
hash[field_name] = next_value_for current_sort
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
# @param hash [Hash] sort order hash
|
42
|
-
# @return [String] a sort order string, e.g. `'name asc'`
|
43
|
-
def rebuild_str_from(hash)
|
44
|
-
hash.each_with_object(EMPTY_STRING.dup) do |(name, sort), str|
|
45
|
-
next unless sort
|
46
|
-
|
47
|
-
str << (str == EMPTY_STRING ? str : COMMA)
|
48
|
-
str << name.to_s << SPACE << sort
|
49
|
-
end
|
38
|
+
HashBuilder.to_str(hash)
|
50
39
|
end
|
51
40
|
|
52
41
|
# @param current [String, nil] current sort order
|
@@ -2,8 +2,33 @@
|
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
4
|
module Routing
|
5
|
-
# Re-open
|
5
|
+
# Re-open {https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper.html
|
6
|
+
# ActionDispatch::Routing::Mapper}
|
7
|
+
# to add route helpers for {Wallaby}.
|
6
8
|
class Mapper
|
9
|
+
# Mount {Wallaby::Engine} at given path.
|
10
|
+
# And prepend custom routes to Rails app if block is given.
|
11
|
+
# @example Mount {Wallaby::Engine} at a path
|
12
|
+
# wallaby_mount at: '/admin'
|
13
|
+
# @example Mount {Wallaby::Engine} and prepend custom routes
|
14
|
+
# wallaby_mount at: '/super_admin' do
|
15
|
+
# resource :accounts
|
16
|
+
# end
|
17
|
+
# # the above code is the same as:
|
18
|
+
# namespace :super_admin do
|
19
|
+
# resource :accounts
|
20
|
+
# end
|
21
|
+
# mount Wallaby::Engine, at: '/super_admin'
|
22
|
+
# @param options [Hash]
|
23
|
+
# @option options [String] :at the path which {Wallaby::Engine} is mounted at
|
24
|
+
# @option options [Symbol, String] :as {Wallaby::Engine} alias name for URL helpers
|
25
|
+
# @option options [Array] :via HTTP methods that {Wallaby::Engine} processes
|
26
|
+
def wallaby_mount(options, &block)
|
27
|
+
# define routes under namespace (e.g. `:admin`) before mounting the {Wallaby:Engine} (e.g. at `/admin`)
|
28
|
+
namespace(options[:at][1..] || '', options.except(:at), &block) if block
|
29
|
+
mount Wallaby::Engine, options.slice(:at, :as, :via)
|
30
|
+
end
|
31
|
+
|
7
32
|
# Generate **resourceful** routes that works for Wallaby.
|
8
33
|
# @example To generate resourceful routes that works for Wallaby:
|
9
34
|
# wresources :postcodes
|
@@ -53,7 +78,7 @@ module ActionDispatch
|
|
53
78
|
# @param options [Hash]
|
54
79
|
def wallaby_resources_options_for(resources_name, options)
|
55
80
|
{ path: ':resources' }.merge!(options).tap do |new_options|
|
56
|
-
%i
|
81
|
+
%i[defaults constraints].each do |key|
|
57
82
|
new_options[key] = { resources: resources_name }.merge!(new_options[key] || {})
|
58
83
|
end
|
59
84
|
end
|
@@ -63,9 +88,9 @@ module ActionDispatch
|
|
63
88
|
# @param resource_name [String, Symbol]
|
64
89
|
# @param options [Hash]
|
65
90
|
def wallaby_resource_options_for(resource_name, options)
|
66
|
-
plural_resources = Wallaby::
|
91
|
+
plural_resources = Wallaby::Inflector.to_resources_name resource_name
|
67
92
|
{ path: ':resource' }.merge!(options).tap do |new_options|
|
68
|
-
%i
|
93
|
+
%i[defaults constraints].each do |key|
|
69
94
|
new_options[key] = { resource: resource_name, resources: plural_resources }.merge!(new_options[key] || {})
|
70
95
|
end
|
71
96
|
end
|
@@ -7,8 +7,8 @@ module Wallaby
|
|
7
7
|
# Find the first field that meets the first condition.
|
8
8
|
# @example to find the possible text field
|
9
9
|
# Wallaby::FieldUtils.first_field_by({ name: /name|title/ }, { type: 'string' }, fields)
|
10
|
-
# # => if any field name that
|
11
|
-
# # => otherwise, find the first field
|
10
|
+
# # => if any field name that includes `name` or `title`, return this field
|
11
|
+
# # => otherwise, find the first field whose type is `string`
|
12
12
|
# @param conditions [Array<Hash>]
|
13
13
|
# @param fields [Hash] field metadata
|
14
14
|
# @return [String, Symbol] field name
|
@@ -17,25 +17,26 @@ module Wallaby
|
|
17
17
|
|
18
18
|
conditions.each do |condition|
|
19
19
|
fields.each do |field_name, metadata|
|
20
|
-
return field_name if meet? field_name, metadata
|
20
|
+
return field_name if meet? condition, field_name, metadata
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
23
24
|
nil
|
24
25
|
end
|
25
26
|
|
26
27
|
protected
|
27
28
|
|
29
|
+
# @param condition [Hash]
|
28
30
|
# @param field_name [String]
|
29
31
|
# @param metadata [Hash]
|
30
|
-
# @param condition [Hash]
|
31
32
|
# @return [true] if field's metadata meets the condition
|
32
33
|
# @return [true] otherwise
|
33
|
-
def meet?(field_name, metadata
|
34
|
+
def meet?(condition, field_name, metadata)
|
34
35
|
condition.all? do |key, requirement|
|
35
|
-
|
36
|
-
value = metadata[key]
|
36
|
+
value = metadata.with_indifferent_access[key]
|
37
37
|
value ||= field_name.to_s if key.to_sym == :name
|
38
|
-
|
38
|
+
value = value.to_s if value.is_a? Symbol
|
39
|
+
requirement === value
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
# Convert strings
|
5
|
+
module Inflector
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# @param name [String]
|
9
|
+
# @return [String] class name
|
10
|
+
# @return [nil] if name is blank
|
11
|
+
def to_class_name(name)
|
12
|
+
return EMPTY_STRING if name.blank?
|
13
|
+
|
14
|
+
name = name.to_s unless name.is_a?(String)
|
15
|
+
name.gsub(COLONS, SLASH).classify
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param script_name [String]
|
19
|
+
# @param resources_name [Class,String]
|
20
|
+
# @param suffix [String,nil]
|
21
|
+
# @return [String] generate the prefix for all classes
|
22
|
+
def to_script(script_name, resources_name, suffix = nil)
|
23
|
+
normalized_suffix = suffix.try(:underscore).try(:gsub, /\A_?/, UNDERSCORE)
|
24
|
+
"#{script_name}/#{resources_name}#{normalized_suffix}".gsub(%r{\A/+}, EMPTY_STRING)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param script_name [String]
|
28
|
+
# @param resources_name [Class,String]
|
29
|
+
# @return [String] controller name
|
30
|
+
def to_controller_name(script_name, resources_name)
|
31
|
+
to_class_name(to_script(script_name, to_resources_name(resources_name), CONTROLLER))
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param script_name [String]
|
35
|
+
# @param resources_name [Class,String]
|
36
|
+
# @return [String] decorator name
|
37
|
+
def to_decorator_name(script_name, resources_name)
|
38
|
+
to_class_name(to_script(script_name, to_resource_name(resources_name), DECORATOR))
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param script_name [String]
|
42
|
+
# @param resources_name [Class,String]
|
43
|
+
# @return [String] authorizer name
|
44
|
+
def to_authorizer_name(script_name, resources_name)
|
45
|
+
to_class_name(to_script(script_name, to_resource_name(resources_name), AUTHORIZER))
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param script_name [String]
|
49
|
+
# @param resources_name [Class,String]
|
50
|
+
# @return [String] servicer name
|
51
|
+
def to_servicer_name(script_name, resources_name)
|
52
|
+
to_class_name(to_script(script_name, to_resource_name(resources_name), SERVICER))
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param script_name [String]
|
56
|
+
# @param resources_name [Class,String]
|
57
|
+
# @return [String] paginator name
|
58
|
+
def to_paginator_name(script_name, resources_name)
|
59
|
+
to_class_name(to_script(script_name, to_resource_name(resources_name), PAGINATOR))
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param name [Class, String]
|
63
|
+
# @return [String] resources name
|
64
|
+
def to_resources_name(name)
|
65
|
+
return EMPTY_STRING if name.blank?
|
66
|
+
|
67
|
+
name = name.to_s unless name.is_a?(String)
|
68
|
+
name.tableize.gsub(SLASH, COLONS)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param name [Class, String]
|
72
|
+
# @return [String] resource name
|
73
|
+
def to_resource_name(name)
|
74
|
+
to_resources_name(name).singularize
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param name [Class, String]
|
78
|
+
# @return [String] resource name
|
79
|
+
def to_model_name(name)
|
80
|
+
to_class_name(to_resource_name(name))
|
81
|
+
end
|
82
|
+
|
83
|
+
# Produce model label (e.g. `Namespace / Product`) for model class (e.g. `Namespace::Product`)
|
84
|
+
# @param model_class [Class, String] model class
|
85
|
+
# @return [String] model label
|
86
|
+
def to_model_label(model_class)
|
87
|
+
# TODO: change to use i18n translation
|
88
|
+
return EMPTY_STRING if model_class.blank?
|
89
|
+
|
90
|
+
model_class_name = to_model_name model_class
|
91
|
+
model_class_name.titleize.gsub(SLASH, SPACE + SLASH + SPACE)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|