wallaby-core 0.1.0

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