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,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Service object to find the engine name by given request environment variables.
|
|
5
|
+
class EngineNameFinder
|
|
6
|
+
class << self
|
|
7
|
+
# Use script name to find out the corresponding named route and its engine name.
|
|
8
|
+
#
|
|
9
|
+
# When it can't find the engine name, it will return empty string. Reason is to prevent it from being run again.
|
|
10
|
+
# @param env [Hash, String] request env or script name
|
|
11
|
+
# @return [String] engine name or empty string if not found
|
|
12
|
+
def find(env)
|
|
13
|
+
script_name = env[SCRIPT_NAME] || env[PATH_INFO] if env.is_a? Hash
|
|
14
|
+
script_name = env if env.is_a? String
|
|
15
|
+
return EMPTY_STRING if script_name.blank?
|
|
16
|
+
|
|
17
|
+
named_route = Rails.application.routes.routes.find { |route| route.path.match(script_name) }
|
|
18
|
+
named_route.try(:name) || EMPTY_STRING
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# {Wallaby::ApplicationHelper#url_for} helper for Wallaby engine
|
|
5
|
+
class EngineUrlFor
|
|
6
|
+
# A constant to map actions to their route paths defined in Wallaby routes.
|
|
7
|
+
ACTION_TO_PATH_MAP =
|
|
8
|
+
Wallaby::ERRORS.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) do |error, map|
|
|
9
|
+
map[error] = :"#{error}_path"
|
|
10
|
+
end.merge(
|
|
11
|
+
home: :root_path,
|
|
12
|
+
index: :resources_path,
|
|
13
|
+
new: :new_resource_path,
|
|
14
|
+
show: :resource_path,
|
|
15
|
+
edit: :edit_resource_path
|
|
16
|
+
).freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Generate URL that Wallaby engine supports (e.g. home/resourceful/errors)
|
|
20
|
+
# @see https://github.com/reinteractive/wallaby/blob/master/config/routes.rb config/routes.rb
|
|
21
|
+
# @param engine_name [string]
|
|
22
|
+
# @param parameters [ActionController::Parameters, Hash]
|
|
23
|
+
# @return [String] path string for wallaby engine
|
|
24
|
+
# @return [nil] nil if given engine name cannot be found
|
|
25
|
+
def handle(engine_name:, parameters:)
|
|
26
|
+
route = Rails.application.routes.named_routes[engine_name]
|
|
27
|
+
return unless route
|
|
28
|
+
|
|
29
|
+
params = { script_name: route.path.spec.to_s }.merge(parameters).symbolize_keys
|
|
30
|
+
|
|
31
|
+
ModuleUtils.try_to(
|
|
32
|
+
Engine.routes.url_helpers, action_path_from(params), params
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
# Find out the named path from given params
|
|
39
|
+
# @return [Symbol] named path
|
|
40
|
+
def action_path_from(params)
|
|
41
|
+
action = params[:action] || params.fetch(:_recall, {})[:action]
|
|
42
|
+
ACTION_TO_PATH_MAP[action]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# Link renderer
|
|
5
|
+
class LinkOptionsNormalizer
|
|
6
|
+
# Normalize options for `link_to` to use
|
|
7
|
+
# @param html_options [Hash] HTML options
|
|
8
|
+
# @param block [Proc] a block
|
|
9
|
+
# @param defaults [Hash]
|
|
10
|
+
# @return [Array<Hash, Proc>] html_options and the block
|
|
11
|
+
def self.normalize(html_options, block, defaults)
|
|
12
|
+
block ||= defaults[:block]
|
|
13
|
+
html_options[:title] ||= defaults[:block].call
|
|
14
|
+
# allow empty class to be set
|
|
15
|
+
html_options[:class] = defaults[:class] if !html_options.key?(:class) && defaults[:class]
|
|
16
|
+
[html_options, block]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class Map
|
|
5
|
+
# To generate a hash map (`model` => `mode`).
|
|
6
|
+
# This will be used to tell if a model can be handled by Wallaby
|
|
7
|
+
class ModeMapper
|
|
8
|
+
# @param mode_classes [Array<Class>] model classes
|
|
9
|
+
def initialize(mode_classes)
|
|
10
|
+
@mode_classes = mode_classes
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# This will walk through each mode (e.g. **ActiveRecord**/**Her**) then pull out all the models,
|
|
14
|
+
# and then form a hash of (`model` => `mode`).
|
|
15
|
+
# @return [Hash] { model_class => mode }
|
|
16
|
+
def map
|
|
17
|
+
return {} if @mode_classes.blank?
|
|
18
|
+
|
|
19
|
+
@mode_classes.each_with_object({}) do |mode_class, map|
|
|
20
|
+
mode_class.model_finder.new.all.each do |model_class|
|
|
21
|
+
map[model_class] = mode_class
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class Map
|
|
5
|
+
# To collect model classes that are configured to be handled by Wallaby
|
|
6
|
+
class ModelClassCollector
|
|
7
|
+
# @param configuration [Configuration]
|
|
8
|
+
# @param models [Array<Class>]
|
|
9
|
+
def initialize(configuration, models = nil)
|
|
10
|
+
@configuration = configuration
|
|
11
|
+
@models = models || []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Array<Class>] model class
|
|
15
|
+
def collect
|
|
16
|
+
return @models - excluded_models if configured_models.blank?
|
|
17
|
+
|
|
18
|
+
invalid_models_check
|
|
19
|
+
configured_models
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Check if the models are valid, raise if invalid
|
|
25
|
+
def invalid_models_check
|
|
26
|
+
invalid_models = configured_models - @models
|
|
27
|
+
return if invalid_models.blank?
|
|
28
|
+
|
|
29
|
+
message = I18n.t 'errors.invalid.models', models: invalid_models.to_sentence
|
|
30
|
+
raise InvalidError, message
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Wallaby::Configuration::Models]
|
|
34
|
+
def models
|
|
35
|
+
@configuration.models
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Array<Class>] a list of models to exclude
|
|
39
|
+
def excluded_models
|
|
40
|
+
models.excludes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Array<Class>] a list of models to set
|
|
44
|
+
def configured_models
|
|
45
|
+
models.presence
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
class Map
|
|
5
|
+
# Generate a map.
|
|
6
|
+
class ModelClassMapper
|
|
7
|
+
# Iterate all classes and generate a hash using their model classes as the key
|
|
8
|
+
# @see #map
|
|
9
|
+
# @param class_array [Array<Class>]
|
|
10
|
+
# @return [Hash] model class => descendant class
|
|
11
|
+
def self.map(class_array, &block)
|
|
12
|
+
new.send :map, class_array, &block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
# @return [Hash] model class => descendant class
|
|
18
|
+
def map(class_array)
|
|
19
|
+
(class_array || EMPTY_ARRAY).each_with_object({}) do |klass, map|
|
|
20
|
+
next if anonymous?(klass) || base_class?(klass) || !klass.model_class
|
|
21
|
+
|
|
22
|
+
map[klass.model_class] = block_given? ? yield(klass) : klass
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @see Wallaby::ModuleUtils.anonymous_class?
|
|
27
|
+
def anonymous?(klass)
|
|
28
|
+
ModuleUtils.anonymous_class? klass
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param klass [Class]
|
|
32
|
+
# @return [Boolean] whether the class is base or not
|
|
33
|
+
def base_class?(klass)
|
|
34
|
+
ModuleUtils.try_to klass, :base_class?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
# To extend prefixes to provide more possibility
|
|
5
|
+
class PrefixesBuilder
|
|
6
|
+
# @param origin_prefixes [Array<string>] a list of all the origin prefixes
|
|
7
|
+
# @param action_name [String] action name
|
|
8
|
+
# @param resources_name [String] resources name
|
|
9
|
+
# @param script_name [String] script name
|
|
10
|
+
# @param theme_name [String] theme name
|
|
11
|
+
def self.build(origin_prefixes:, action_name:, resources_name:, script_name:, theme_name:)
|
|
12
|
+
new(
|
|
13
|
+
origin_prefixes: origin_prefixes,
|
|
14
|
+
action_name: action_name,
|
|
15
|
+
resources_name: resources_name,
|
|
16
|
+
script_name: script_name,
|
|
17
|
+
theme_name: theme_name
|
|
18
|
+
).send :build
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Constructor
|
|
24
|
+
def initialize(origin_prefixes:, action_name:, resources_name:, script_name:, theme_name:)
|
|
25
|
+
@origin_prefixes = origin_prefixes
|
|
26
|
+
@action_name = action_name
|
|
27
|
+
@resources_name = resources_name
|
|
28
|
+
@script_name = script_name
|
|
29
|
+
@theme_name = theme_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Array<String>] prefixes
|
|
33
|
+
def build
|
|
34
|
+
prefixes = minimal_prefixes
|
|
35
|
+
prefixes.unshift mounted_prefix unless prefixes.include? resources_path
|
|
36
|
+
prefixes.uniq.compact.each_with_object([]) do |prefix, result|
|
|
37
|
+
result << "#{prefix}/#{suffix}" << prefix
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Array<String>] prefixes starting from wallaby controller path
|
|
42
|
+
def minimal_prefixes
|
|
43
|
+
index = @origin_prefixes.index ResourcesController.controller_path
|
|
44
|
+
@origin_prefixes.slice(0..index).tap do |prefixes|
|
|
45
|
+
insert_index = prefixes.length > 1 ? -2 : 0
|
|
46
|
+
prefixes.insert insert_index, @theme_name if @theme_name.present?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [String] a prefix of the mouted path
|
|
51
|
+
def mounted_prefix
|
|
52
|
+
prefix = (@script_name || '').sub(%r{^/}, '')
|
|
53
|
+
prefix << SLASH if prefix.present?
|
|
54
|
+
prefix << resources_path if resources_path
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String]
|
|
58
|
+
def suffix
|
|
59
|
+
@suffix ||= CellUtils.to_action_prefix @action_name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def resources_path
|
|
63
|
+
@resources_path ||= @resources_name.try :gsub, COLONS, SLASH
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
module Sorting
|
|
5
|
+
# Turn a string e.g.`'name asc,id desc'` into sort hash e.g.`{name: 'asc', id: 'desc'}`
|
|
6
|
+
class HashBuilder
|
|
7
|
+
SORT_REGEX = /(([^\s,]+)\s+(asc|desc))\s*,?\s*/i.freeze
|
|
8
|
+
|
|
9
|
+
# Turn a string e.g.`'name asc,id desc'` into sort hash e.g.`{name: 'asc', id: 'desc'}`
|
|
10
|
+
# @param sort_string [String]
|
|
11
|
+
# @return [Hash] { field_name => 'asc|desc' }
|
|
12
|
+
def self.build(sort_string)
|
|
13
|
+
::ActiveSupport::HashWithIndifferentAccess.new.tap do |hash|
|
|
14
|
+
(sort_string || EMPTY_STRING).scan(SORT_REGEX) { |_, key, order| hash[key] = order }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
module Sorting
|
|
5
|
+
# Build link for sorting
|
|
6
|
+
class LinkBuilder
|
|
7
|
+
SORT_STRATEGIES = { single: SingleBuilder }.with_indifferent_access.freeze
|
|
8
|
+
|
|
9
|
+
delegate :model_class, to: :@model_decorator
|
|
10
|
+
delegate :index_link, :url_for, to: :@helper
|
|
11
|
+
|
|
12
|
+
# @param model_decorator [Wallaby::ModelDecorator]
|
|
13
|
+
# @param params [ActionController::Parameters]
|
|
14
|
+
# @param helper [ActionView::Helpers]
|
|
15
|
+
def initialize(model_decorator, params, helper, strategy)
|
|
16
|
+
@model_decorator = model_decorator
|
|
17
|
+
@params = params
|
|
18
|
+
@helper = helper
|
|
19
|
+
@strategy = strategy
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# To return the sort hash converted from string value, e.g. `{ 'title' => 'asc', 'updated_at' => 'desc' }`
|
|
23
|
+
# converted from `'title asc, updated_at desc'`
|
|
24
|
+
# @return [Hash] current sort hash
|
|
25
|
+
def current_sort
|
|
26
|
+
@current_sort ||= HashBuilder.build @params[:sort]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Build sort link for given field name:
|
|
30
|
+
#
|
|
31
|
+
# ```
|
|
32
|
+
# <a title="Product" href="/admin/products?sort=published_at+asc">Name</a>
|
|
33
|
+
# ```
|
|
34
|
+
#
|
|
35
|
+
# If the field is not sortable, it returns a text, e.g.:
|
|
36
|
+
#
|
|
37
|
+
# ```
|
|
38
|
+
# Name
|
|
39
|
+
# ```
|
|
40
|
+
# @param field_name [String]
|
|
41
|
+
# @return [String] link or text
|
|
42
|
+
def build(field_name)
|
|
43
|
+
metadata = @model_decorator.index_metadata_of field_name
|
|
44
|
+
label = @model_decorator.index_label_of field_name
|
|
45
|
+
return label unless sortable? field_name, metadata
|
|
46
|
+
|
|
47
|
+
sort_field_name = metadata[:sort_field_name] || field_name
|
|
48
|
+
url_params = next_builder.next_params sort_field_name
|
|
49
|
+
url = url_for url_params.merge(with_query: true)
|
|
50
|
+
index_link(model_class, options: { url: url }) { label }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# @return [Wallaby::Sorting::NextBuilder]
|
|
56
|
+
def next_builder
|
|
57
|
+
@next_builder ||= begin
|
|
58
|
+
klass = SORT_STRATEGIES[@strategy] || NextBuilder
|
|
59
|
+
klass.new @params, current_sort
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Boolean] true if sortable or `sort_field_name` is provided in the metadata
|
|
64
|
+
def sortable?(field_name, metadata)
|
|
65
|
+
!metadata[:sort_disabled] && (@model_decorator.fields[field_name] || metadata[:sort_field_name])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
module Sorting
|
|
5
|
+
# Generate sort param for given field's next sort order
|
|
6
|
+
# (e.g. from empty to `asc`, from `asc` to `desc`, from `desc` to empty)
|
|
7
|
+
class NextBuilder
|
|
8
|
+
ASC = 'asc'
|
|
9
|
+
DESC = 'desc'
|
|
10
|
+
|
|
11
|
+
# @param params [ActionController::Parameters]
|
|
12
|
+
# @param hash [Hash, nil] a hash containing sorting info, e.g. `{ name: 'asc' }`
|
|
13
|
+
def initialize(params, hash = nil)
|
|
14
|
+
@params = params
|
|
15
|
+
@hash = hash.try(:with_indifferent_access) || HashBuilder.build(params[:sort])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Update the `sort` parameter.
|
|
19
|
+
# @example for param `{sort: 'name asc'}`, it updates the parameters to:
|
|
20
|
+
# {sort: 'name desc'}
|
|
21
|
+
# @param field_name [String] field name
|
|
22
|
+
# @return [ActionController::Parameters]
|
|
23
|
+
# updated parameters with new sort order for given field
|
|
24
|
+
def next_params(field_name)
|
|
25
|
+
params = @params.dup
|
|
26
|
+
params[:sort] = complete_sorting_str_with field_name
|
|
27
|
+
params
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
protected
|
|
31
|
+
|
|
32
|
+
# @param field_name [String] field name
|
|
33
|
+
# @return [String] a sort order string, e.g. `'name asc'`
|
|
34
|
+
def complete_sorting_str_with(field_name)
|
|
35
|
+
hash = @hash.except field_name
|
|
36
|
+
current_sort = @hash[field_name]
|
|
37
|
+
hash[field_name] = next_value_for current_sort
|
|
38
|
+
rebuild_str_from hash
|
|
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
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param current [String, nil] current sort order
|
|
53
|
+
# @return [String, nil] next sort order
|
|
54
|
+
def next_value_for(current)
|
|
55
|
+
case current
|
|
56
|
+
when ASC then DESC
|
|
57
|
+
when DESC then nil
|
|
58
|
+
else ASC
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wallaby
|
|
4
|
+
module Sorting
|
|
5
|
+
# Generate sort param for only one field's next sort order
|
|
6
|
+
# (e.g. from empty to `asc`, from `asc` to `desc`, from `desc` to empty)
|
|
7
|
+
class SingleBuilder < NextBuilder
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
# @param field_name [String] field name
|
|
11
|
+
# @return [String] a sort order string, e.g. `'name asc'`
|
|
12
|
+
def complete_sorting_str_with(field_name)
|
|
13
|
+
hash = {}
|
|
14
|
+
current_sort = @hash[field_name]
|
|
15
|
+
hash[field_name] = next_value_for current_sort
|
|
16
|
+
rebuild_str_from hash
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|