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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Type renderer
5
+ class TypeRenderer
6
+ class << self
7
+ # Render partial
8
+ # @param view [ActionView]
9
+ # @param options [Hash]
10
+ # @param locals [Hash]
11
+ # @return [String] HTML
12
+ def render(view, options = {}, locals = {}, &block)
13
+ locals[:object] ||= locals[:form].try :object
14
+ check locals
15
+ complete locals, view.params[:action]
16
+ view.render options, locals, &block
17
+ end
18
+
19
+ private
20
+
21
+ # @param locals [Hash]
22
+ # @raise [ArgumentError] if form is set but blank
23
+ # @raise [ArgumentError] if field_name is not provided
24
+ # @raise [ArgumentError] if object is not decorated
25
+ def check(locals)
26
+ raise ArgumentError, I18n.t('errors.required', subject: 'form') if locals.key?(:form) && locals[:form].blank?
27
+ raise ArgumentError, I18n.t('errors.required', subject: 'field_name') if locals[:field_name].blank?
28
+ raise ArgumentError, 'Object is not decorated.' unless locals[:object].is_a? ResourceDecorator
29
+ end
30
+
31
+ # @param locals [Hash]
32
+ # @param action [String]
33
+ def complete(locals, action)
34
+ action_name = CellUtils.to_action_prefix action
35
+ locals[:metadata] = locals[:object].public_send :"#{action_name}_metadata_of", locals[:field_name]
36
+ locals[:value] = locals[:object].public_send locals[:field_name]
37
+ end
38
+
39
+ # @param options [String]
40
+ # @param view [ActionView]
41
+ # @return [String] partial path string
42
+ # @return [String] blank string
43
+ def find_partial(options, view)
44
+ formats = [view.request.format.to_sym]
45
+ lookup = view.lookup_context
46
+ lookup.find_template options, lookup.prefixes, true, [], formats: formats
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Routing
5
+ # Re-open `ActionDispatch::Routing::Mapper` to add route helpers for Wallaby.
6
+ class Mapper
7
+ # Generate **resourceful** routes that works for Wallaby.
8
+ # @example To generate resourceful routes that works for Wallaby:
9
+ # wresources :postcodes
10
+ # # => same effect as
11
+ # resources(
12
+ # :postcodes,
13
+ # path: ':resources',
14
+ # defaults: { resources: :postcodes },
15
+ # constraints: { resources: :postcodes }
16
+ # )
17
+ # @param resource_names [Array<String, Symbol>]
18
+ # @see https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources
19
+ # ActionDispatch::Routing::Mapper::Resources#resources
20
+ def wresources(*resource_names, &block)
21
+ options = resource_names.extract_options!.dup
22
+ resource_names.each do |resource_name|
23
+ new_options = wallaby_resources_options_for resource_name, options
24
+ resources resource_name, new_options, &block
25
+ end
26
+ end
27
+
28
+ # Generate **resourceful** routes that works for Wallaby.
29
+ # @example To generate resourceful routes that works for Wallaby:
30
+ # wresource :profile
31
+ # # => same effect as
32
+ # resource(
33
+ # :profile,
34
+ # path: ':resource',
35
+ # defaults: { resource: :profile, resources: :profiles },
36
+ # constraints: { resource: :profile, resources: :profiles }
37
+ # )
38
+ # @param resource_names [Array<String, Symbol>]
39
+ # @see https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resource
40
+ # ActionDispatch::Routing::Mapper::Resources#resource
41
+ def wresource(*resource_names, &block)
42
+ options = resource_names.extract_options!.dup
43
+ resource_names.each do |resource_name|
44
+ new_options = wallaby_resource_options_for resource_name, options
45
+ resource resource_name, new_options, &block
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ # Fill in the **resources** options required by Wallaby
52
+ # @param resources_name [String, Symbol]
53
+ # @param options [Hash]
54
+ def wallaby_resources_options_for(resources_name, options)
55
+ { path: ':resources' }.merge!(options).tap do |new_options|
56
+ %i(defaults constraints).each do |key|
57
+ new_options[key] = { resources: resources_name }.merge!(new_options[key] || {})
58
+ end
59
+ end
60
+ end
61
+
62
+ # Fill in the **resource** options required by Wallaby
63
+ # @param resource_name [String, Symbol]
64
+ # @param options [Hash]
65
+ def wallaby_resource_options_for(resource_name, options)
66
+ plural_resources = Wallaby::ModelUtils.to_resources_name resource_name
67
+ { path: ':resource' }.merge!(options).tap do |new_options|
68
+ %i(defaults constraints).each do |key|
69
+ new_options[key] = { resource: resource_name, resources: plural_resources }.merge!(new_options[key] || {})
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # To present classes in tree structure.
5
+ class Node
6
+ # @!attribute [r] klass
7
+ # Represent the current class
8
+ attr_reader :klass
9
+ # @!attribute parent
10
+ # Represent the parent class of current class
11
+ attr_accessor :parent
12
+
13
+ delegate :name, to: :klass
14
+
15
+ # @param klass [Class]
16
+ def initialize(klass)
17
+ @klass = klass
18
+ end
19
+
20
+ # @return [Array<Class>] a list of children classes
21
+ def children
22
+ @children ||= []
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Cell utils
5
+ module CellUtils
6
+ class << self
7
+ # Render a cell and produce output
8
+ # @param context [ActionView::Context]
9
+ # @param file_name [String]
10
+ # @param locals [Hash]
11
+ # @return [String] output
12
+ def render(context, file_name, locals = {}, &block)
13
+ snake_class = file_name[%r{(?<=app/).+(?=\.rb)}].split(SLASH, 2).last
14
+ cell_class = snake_class.camelize.constantize
15
+ Rails.logger.info " Rendered [cell] #{file_name}"
16
+ cell_class.new(context, locals).render_complete(&block)
17
+ end
18
+
19
+ # Check if a partial is a cell or not
20
+ # @param partial_path [String]
21
+ # @return [true] if partial is a `rb` file
22
+ # @return [false] otherwise
23
+ def find_cell(*partials)
24
+ partials.find { |partial| partial.end_with? '.rb' }
25
+ end
26
+
27
+ # @param action_name [String, Symbol]
28
+ # @return [String, Symbol] action prefix
29
+ def to_action_prefix(action_name)
30
+ FORM_ACTIONS[action_name] || action_name
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Field utils
5
+ module FieldUtils
6
+ class << self
7
+ # Find the first field that meets the first condition.
8
+ # @example to find the possible text field
9
+ # Wallaby::FieldUtils.first_field_by({ name: /name|title/ }, { type: 'string' }, fields)
10
+ # # => if any field name that has `name` or `title`, return this field
11
+ # # => otherwise, find the first field that has type `string`
12
+ # @param conditions [Array<Hash>]
13
+ # @param fields [Hash] field metadata
14
+ # @return [String, Symbol] field name
15
+ def first_field_by(*conditions, fields)
16
+ return if [conditions, fields].any?(&:blank?)
17
+
18
+ conditions.each do |condition|
19
+ fields.each do |field_name, metadata|
20
+ return field_name if meet? field_name, metadata.with_indifferent_access, condition
21
+ end
22
+ end
23
+ nil
24
+ end
25
+
26
+ protected
27
+
28
+ # @param field_name [String]
29
+ # @param metadata [Hash]
30
+ # @param condition [Hash]
31
+ # @return [true] if field's metadata meets the condition
32
+ # @return [true] otherwise
33
+ def meet?(field_name, metadata, condition)
34
+ condition.all? do |key, requirement|
35
+ operator = requirement.is_a?(::Regexp) ? '=~' : '=='
36
+ value = metadata[key]
37
+ value ||= field_name.to_s if key.to_sym == :name
38
+ ModuleUtils.try_to value, operator, requirement
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Filter utils
5
+ module FilterUtils
6
+ # Find filter name in the following precedences from high to low:
7
+ #
8
+ # - `filter_name` argument
9
+ # - filters that has been marked as default
10
+ # - `:all`
11
+ # @param filter_name [String, Symbol] filter name
12
+ # @param filters [Hash] filter metadata
13
+ # @return [String, Symbol]
14
+ def self.filter_name_by(filter_name, filters)
15
+ filter = filter_name # from param
16
+ filter ||= filters.find { |_k, v| v[:default] }.try(:first) # from default value
17
+ filter || :all # last resort
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Utils for model
5
+ module ModelUtils
6
+ class << self
7
+ # Convert model class (e.g. `Namespace::Product`) into resources name (e.g. `namespace::products`)
8
+ # @param model_class [Class, String] model class
9
+ # @return [String] resources name
10
+ def to_resources_name(model_class)
11
+ return EMPTY_STRING if model_class.blank?
12
+
13
+ model_class.to_s.underscore.gsub(SLASH, COLONS).pluralize
14
+ end
15
+
16
+ # Produce model label (e.g. `Namespace / Product`) for model class (e.g. `Namespace::Product`)
17
+ # @param model_class [Class, String] model class
18
+ # @return [String] model label
19
+ def to_model_label(model_class)
20
+ # TODO: change to use i18n translation
21
+ return EMPTY_STRING if model_class.blank?
22
+
23
+ model_class_name = to_model_name model_class
24
+ model_class_name.titleize.gsub(SLASH, SPACE + SLASH + SPACE)
25
+ end
26
+
27
+ # Convert resources name (e.g. `namespace::products`) into model class (e.g. `Namespace::Product`)
28
+ # @param resources_name [String] resources name
29
+ # @return [Class] model class
30
+ # @return [nil] when not found
31
+ def to_model_class(resources_name)
32
+ return if resources_name.blank?
33
+
34
+ class_name = to_model_name resources_name
35
+ class_name.constantize
36
+ rescue NameError
37
+ Rails.logger.warn I18n.t('errors.not_found.model', model: class_name)
38
+ nil
39
+ end
40
+
41
+ # Convert resources name (e.g. `namespace::products`) into model name (e.g. `Namespace::Product`)
42
+ # @param resources_name [String] resources name
43
+ # @return [String] model name
44
+ def to_model_name(resources_name)
45
+ return EMPTY_STRING if resources_name.blank?
46
+
47
+ resources_name.to_s.singularize.gsub(COLONS, SLASH).camelize
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Utils for module and class
5
+ module ModuleUtils
6
+ class << self
7
+ # A helper method to check if subject responds to given method and to return the result if so
8
+ # @param subject [Object]
9
+ # @param method_id [String, Symbol]
10
+ # @param args [Array] a list of arguments
11
+ # @return [Object] result from executing given method on subject
12
+ # @return [nil] if subject doesn't respond to given method
13
+ def try_to(subject, method_id, *args, &block)
14
+ return if method_id.blank?
15
+
16
+ subject.respond_to?(method_id) && subject.public_send(method_id, *args, &block) || nil
17
+ end
18
+
19
+ # Check whether a class is anonymous or not
20
+ # @param klass [Class]
21
+ # @return [true] if a class is anonymous
22
+ # @return [false] otherwise
23
+ def anonymous_class?(klass)
24
+ klass.name.blank? || klass.to_s.start_with?('#<Class')
25
+ end
26
+
27
+ # Check if a child class inherits from parent class
28
+ # @param child [Class] child class
29
+ # @param parent [Class] parent class
30
+ # @raise [ArgumentError] if given class is not a child of the other class
31
+ def inheritance_check(child, parent)
32
+ return unless child && parent
33
+ return if child < parent
34
+
35
+ raise ::ArgumentError, I18n.t('errors.invalid.inheritance', klass: child, parent: parent)
36
+ end
37
+
38
+ # If block is given, run the block. Otherwise, return subject
39
+ # @param subject [Object]
40
+ # @yield [subject]
41
+ def yield_for(subject)
42
+ block_given? ? yield(subject) : subject
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Hash utils
5
+ module ParamsUtils
6
+ class << self
7
+ # @param params [Array<Hash>]
8
+ # @return [Hash] combined hash that removes empty values
9
+ def presence(*params)
10
+ params.reduce({}, :merge).delete_if { |_, v| v.nil? || v == '' }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Preload utils
5
+ module PreloadUtils
6
+ class << self
7
+ # Preload all files under app folder
8
+ def require_all
9
+ eager_load_paths.map(&method(:require_one))
10
+ end
11
+
12
+ # Require files under a load path
13
+ # @param load_path [String, Pathname]
14
+ # @see https://api.rubyonrails.org/classes/Rails/Engine.html#method-i-eager_load-21 Rails::Engine#eager_load!
15
+ def require_one(load_path)
16
+ Dir.glob("#{load_path}/**/*.rb").sort.each(&method(:load_class_for))
17
+ end
18
+
19
+ protected
20
+
21
+ # @return [Array<String, Pathname>] a list of sorted eager load paths which lists `app/models`
22
+ # at highest precedence
23
+ def eager_load_paths
24
+ Rails.configuration.eager_load_paths.sort_by do |path|
25
+ - path.index(%r{/models$}).to_i
26
+ end
27
+ end
28
+
29
+ # `constantize` is used to make Rails to handle all sort of load errors
30
+ #
31
+ # NOTE: don't try to use `ActiveSupport::Dependencies::Loadable.require_dependency`.
32
+ # As `require_dependency` does not take care all errors raised when class/module is loaded.
33
+ # @param file_path [Pathname, String]
34
+ def load_class_for(file_path)
35
+ module_name = file_path[%r{app/[^/]+/(.+)\.rb}, 1].gsub('/concerns/', '/')
36
+ class_name = module_name.camelize
37
+ class_name.constantize unless Module.const_defined? class_name
38
+ rescue NameError, LoadError => e
39
+ Rails.logger.debug " [WALLABY] Preload warning: #{e.message} from #{file_path}"
40
+ Rails.logger.debug e.backtrace.slice(0, 5)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Utils for test
5
+ module TestUtils
6
+ class << self
7
+ # @param context
8
+ # @param controller_class [Class]
9
+ def around_crud(context, controller_class = nil)
10
+ context.before do
11
+ controller_class ||= described_class
12
+ controller_path = controller_class.controller_path
13
+ Wallaby::TestUtils.draw(routes, controller_path)
14
+ end
15
+
16
+ context.after { Rails.application.reload_routes! }
17
+ end
18
+
19
+ # @param routes
20
+ # @param controller_path [String]
21
+ def draw(routes, controller_path)
22
+ routes.draw do
23
+ get ':resources', to: "#{controller_path}#index", as: :resources
24
+ get ':resources/:id', to: "#{controller_path}#show", as: :resource
25
+ get ':resources/new', to: "#{controller_path}#new"
26
+ get ':resources/:id/edit', to: "#{controller_path}#edit"
27
+ post ':resources', to: "#{controller_path}#create"
28
+ patch ':resources/:id', to: "#{controller_path}#update"
29
+ delete ':resources/:id', to: "#{controller_path}#destroy"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end