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,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Wallaby application helper
5
+ module ApplicationHelper
6
+ include ConfigurationHelper
7
+ include Engineable
8
+ include SharedHelpers
9
+
10
+ # @!method try_to(subject, method_id, *args, &block)
11
+ # (see Wallaby::ModuleUtils.try_to)
12
+ # @see Wallaby::ModuleUtils.try_to
13
+ delegate :try_to, to: ModuleUtils
14
+
15
+ # Override the origin view_renderer to support {Wallaby::Cell} rendering
16
+ # @!attribute [r] view_renderer
17
+ # @see Wallaby::CustomRenderer
18
+ def view_renderer
19
+ return @view_renderer if @view_renderer.is_a? CustomRenderer
20
+
21
+ @view_renderer = CustomRenderer.new @view_renderer.lookup_context
22
+ end
23
+
24
+ # Override origin method to handle URL for Wallaby engine.
25
+ #
26
+ # As Wallaby's routes are declared in a
27
+ # {https://guides.rubyonrails.org/routing.html#routing-to-rack-applications Rack application} fashion, this will
28
+ # lead to **ActionController::RoutingError** exception when using ordinary **url_for**
29
+ # (e.g. `url_for action: :index`).
30
+ #
31
+ # Gotcha: Wallaby can't cope well with the following situation.
32
+ # It's due to the limit of route declaration and matching:
33
+ #
34
+ # ```
35
+ # scope path: '/prefix' do
36
+ # wresources :products, controller: 'wallaby/resources'
37
+ # end
38
+ # ```
39
+ # @param options [String, Hash, ActionController::Parameters]
40
+ # @option options [Boolean]
41
+ # :with_query to include `request.query_parameters` values for url generation.
42
+ # @option options [String]
43
+ # :engine_name to specify the engine_name to use, default to {Wallaby::Engineable#current_engine_name}
44
+ # @return [String] URL string
45
+ # @see Wallaby::EngineUrlFor.handle
46
+ # @see https://api.rubyonrails.org/classes/ActionView/RoutingUrlFor.html#method-i-url_for
47
+ # ActionView::RoutingUrlFor#url_for
48
+ def url_for(options = nil)
49
+ if options.is_a?(Hash) || try_to(options, :permitted?)
50
+ # merge with all current query parameters
51
+ options = request.query_parameters.merge(options) if options.delete(:with_query)
52
+ options = ParamsUtils.presence url_options, options # remove blank values
53
+ EngineUrlFor.handle(
54
+ engine_name: options.fetch(:engine_name, current_engine_name), parameters: options
55
+ )
56
+ end || super(options)
57
+ end
58
+
59
+ # Override origin method to add turbolinks tracking when it's enabled
60
+ # @param sources [Array<String>]
61
+ # @return [String] stylesheet link tags HTML
62
+ def stylesheet_link_tag(*sources)
63
+ default_options =
64
+ features.turbolinks_enabled ? { 'data-turbolinks-track' => true } : {}
65
+ options = default_options.merge!(sources.extract_options!.stringify_keys)
66
+ super(*sources, options)
67
+ end
68
+
69
+ # Override origin method to add turbolinks tracking when it's enabled
70
+ # @param sources [Array<String>]
71
+ # @return [String] javascript script tags HTML
72
+ def javascript_include_tag(*sources)
73
+ default_options =
74
+ features.turbolinks_enabled ? { 'data-turbolinks-track' => true, 'data-turbolinks-eval' => false } : {}
75
+ options = default_options.merge!(sources.extract_options!.stringify_keys)
76
+ super(*sources, options)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # NOTE: Global helper methods should go in here
5
+ module BaseHelper
6
+ include StylingHelper
7
+ include LinksHelper
8
+
9
+ # @see ModelUtils.to_model_label
10
+ # @return [String] label for given model class
11
+ def to_model_label(model_class)
12
+ ModelUtils.to_model_label model_class
13
+ end
14
+
15
+ # @see Map.resources_name_map
16
+ # @return [String] resources name for given model class
17
+ def to_resources_name(model_class)
18
+ Map.resources_name_map model_class
19
+ end
20
+
21
+ # Generate body class from the following sources:
22
+ # - `:action` parameter
23
+ # - converted current resources name (e.g. `order__item` from `Order::Item`)
24
+ # - `:custom_body_class` content
25
+ # @return [String] css classes for body tag
26
+ def body_class
27
+ [
28
+ params[:action],
29
+ controller_path.gsub(SLASH, '__'),
30
+ current_resources_name.try(:gsub, COLONS, '__'),
31
+ content_for(:custom_body_class)
32
+ ].compact.join SPACE
33
+ end
34
+
35
+ # Turn a list of classes into tree structure by inheritance.
36
+ # @param classes [Array<Class>]
37
+ # a list of all the classes that wallaby supports
38
+ # @return [Array<Wallaby::Node>] a tree structure of given classes
39
+ def model_classes(classes = Map.model_classes)
40
+ nested_hash = classes.each_with_object({}) do |klass, hash|
41
+ hash[klass] = Node.new(klass)
42
+ end
43
+ nested_hash.each do |klass, node|
44
+ node.parent = parent = nested_hash[klass.superclass]
45
+ parent.children << node if parent
46
+ end
47
+ nested_hash.values.select { |v| v.parent.nil? }
48
+ end
49
+
50
+ # Turn the tree of classes into a nested `ul` list.
51
+ # @param array [Array<Wallaby::Node>] root classes
52
+ # @return [String] HTML for the whole tree
53
+ def model_tree(array, base_class = nil)
54
+ return EMPTY_STRING if array.blank?
55
+
56
+ options = { html_options: { class: 'dropdown-item' } }
57
+ content_tag :ul, class: 'dropdown-menu', 'aria-labelledby': base_class do
58
+ array.sort_by(&:name).each do |node|
59
+ content = index_link(node.klass, options).try :<<, model_tree(node.children)
60
+ concat content_tag(:li, content)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Configuration helper module. Provide shortcut methods to configurations.
5
+ module ConfigurationHelper
6
+ delegate :models, :security, :mapping, :pagination, :features, :sorting, to: :configuration
7
+
8
+ # @return [Wallaby::Configuration] shortcut method of configuration
9
+ def configuration
10
+ Wallaby.configuration
11
+ end
12
+
13
+ # @return [Wallaby::Configuration::Metadata] shortcut method of metadata
14
+ def default_metadata
15
+ configuration.metadata
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Form helper
5
+ module FormHelper
6
+ # @deprecated Use {Wallaby::ResourcesHelper#type_render} instead. It will be removed from 5.3.*
7
+ def form_type_partial_render(options = {}, locals = {}, &block)
8
+ Utils.deprecate 'deprecation.form_type_partial_render', caller: caller
9
+ type_render options, locals, &block
10
+ end
11
+
12
+ # To generate remote URL for auto select plugin.
13
+ # @see https://github.com/reinteractive/wallaby/blob/master/app/assets/javascripts/wallaby/auto_select.js
14
+ # auto_select.js
15
+ # @param url [String, nil]
16
+ # if URL is nil, it will fall back to default remote URL
17
+ # @param model_class [Class]
18
+ # @param wildcard [String] wildcard that auto_select uses to replace with
19
+ # the typed keyword
20
+ # @return [String] URL for autocomplete
21
+ def remote_url(url, model_class, wildcard = 'QUERY')
22
+ url || index_path(model_class, url_params: { q: wildcard, per: pagination.page_size })
23
+ end
24
+
25
+ # To generate dropdown options (class => url) for polymorphic class.
26
+ # @see https://github.com/reinteractive/wallaby/blob/master/app/assets/javascripts/wallaby/auto_select.js
27
+ # auto_select.js
28
+ # This function will pull out remote URLs from `metadata[:remote_urls]`
29
+ # (Class => url).
30
+ # @see ActionView::Helpers::FormOptionsHelper#options_for_select
31
+ # @param metadata [Hash]
32
+ # @param wildcard [String] wildcard to be used in the URL
33
+ # @param select_options [Hash]
34
+ # @return [String] options HTML
35
+ def polymorphic_options(metadata, wildcard = 'QUERY', select_options = {})
36
+ urls = metadata[:remote_urls] || {}
37
+ options = (metadata[:polymorphic_list] || []).map do |klass|
38
+ [
39
+ klass, klass,
40
+ { data: { url: remote_url(urls[klass], klass, wildcard) } }
41
+ ]
42
+ end
43
+ options_for_select options, select_options
44
+ end
45
+
46
+ # To fetch the hints from the following keys:
47
+ # - hints.#\{ type \}_html
48
+ # - hints.#\{ type \}
49
+ # @param metadata [Hash]
50
+ # @return [String, nil] HTML
51
+ def hint_of(metadata)
52
+ type = metadata[:type]
53
+ hint = metadata[:hint]
54
+ # @see http://guides.rubyonrails.org/i18n.html#using-safe-html-translations
55
+ hint ||= type && t("hints.#{type}_html", default: '').presence
56
+ hint ||= type && t("hints.#{type}", default: '').presence
57
+ return unless hint
58
+
59
+ content_tag :p, hint, class: 'help-block'
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Helper methods for index action
5
+ module IndexHelper
6
+ # @deprecated This method is no longer in use. It will be removed from 5.3.*
7
+ def index_params(parameters = params)
8
+ Utils.deprecate 'deprecation.index_params', caller: caller
9
+ parameters
10
+ end
11
+
12
+ # @deprecated Use {Wallaby::Paginatable#current_paginator} instead. It will be removed from 5.3.*
13
+ def paginator_of(_model_class, _collection, _params)
14
+ Utils.deprecate 'deprecation.paginator_of', caller: caller
15
+ current_paginator
16
+ end
17
+
18
+ # Just a label
19
+ # @return [String]
20
+ def all_label
21
+ t 'filters.all'
22
+ end
23
+
24
+ # If `:fields` parameter is given, only display fields that is in `index_field_names`
25
+ # Otherwise, `index_field_names`
26
+ # @param decorated_collection [Array<Wallaby::ResourceDecorator>]
27
+ # @param fields_from_params [Array<String>]
28
+ # @return [Array<String>] a list of field names for json builder
29
+ def json_fields_of(decorated_collection, fields_from_params = params[:fields])
30
+ return [] if decorated_collection.blank?
31
+
32
+ decorated = decorated_collection.first
33
+ index_field_names = decorated.index_field_names.map(&:to_s)
34
+ fields = (fields_from_params.presence || index_field_names).split(/\s*,\s*/).flatten
35
+ fields & index_field_names
36
+ end
37
+
38
+ # @param filter_name [String, Symbol]
39
+ # @param filters [Hash]
40
+ # @return [String] filter label for the given field name
41
+ def filter_label(filter_name, filters)
42
+ # TODO: use locale for filter_name label
43
+ filters[filter_name].try(:[], :label) || filter_name.to_s.humanize
44
+ end
45
+
46
+ # @param filter_name [String, Symbol]
47
+ # @param filters [Hash]
48
+ # @return [String] filter name
49
+ # @see Wallaby::FilterUtils.filter_name_by
50
+ def filter_name_by(filter_name, filters)
51
+ FilterUtils.filter_name_by filter_name, filters
52
+ end
53
+
54
+ # Link for a given model class and filter name
55
+ # @param model_class [Class]
56
+ # @param filter_name [String, Symbol]
57
+ # @param filters [Hash]
58
+ # @param url_params [Hash, ActionController::Parameters]
59
+ # @return [String] HTML anchor link
60
+ def filter_link(model_class, filter_name, filters: {}, url_params: {})
61
+ is_all = filter_name == :all
62
+ config = filters[filter_name] || {}
63
+ label = is_all ? all_label : filter_label(filter_name, filters)
64
+ url_params[:filter] = config[:default] ? nil : filter_name
65
+ index_link(model_class, options: { url: url_for(url_params.merge(with_query: true)) }) { label }
66
+ end
67
+
68
+ # @param model_class [Class]
69
+ # @param url_params [Hash, ActionController::Parameters] extra URL params
70
+ # @return [String] Export link for the given model_class.
71
+ def export_link(model_class, url_params: {})
72
+ index_link(model_class, url_params: { format: 'csv', page: nil, per: nil, with_query: true }.merge(url_params)) do
73
+ t 'links.export', ext: 'CSV'
74
+ end
75
+ end
76
+
77
+ # @return [Wallaby::Sorting::LinkBuilder]
78
+ # @see Wallaby::Sorting::LinkBuilder
79
+ def sort_link_builder
80
+ @sort_link_builder ||=
81
+ Sorting::LinkBuilder.new current_model_decorator, params.slice(:sort).permit!, self, sorting.strategy
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Links helper
5
+ module LinksHelper
6
+ # Return link to index page for a given model class
7
+ # @param model_class [Class]
8
+ # @param options [Hash]
9
+ # @option options [String] :url url/path for the link
10
+ # @param url_params [ActionController::Parameters, Hash]
11
+ # @param html_options [Hash] (see
12
+ # {https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
13
+ # ActionView::Helpers::UrlHelper#link_to})
14
+ # @yield block to return the link label
15
+ # @return [String] anchor link to index page for a given model class
16
+ # @return [nil] if user's not authorized
17
+ def index_link(model_class, options: {}, url_params: {}, html_options: {}, &block)
18
+ return if unauthorized? :index, model_class
19
+
20
+ html_options, block = LinkOptionsNormalizer.normalize(
21
+ html_options, block,
22
+ block: -> { to_model_label model_class }
23
+ )
24
+
25
+ url_params = request.query_parameters.merge(url_params) if url_params.delete(:with_query)
26
+ url = options[:url] || index_path(model_class, url_params: url_params)
27
+ link_to url, html_options, &block
28
+ end
29
+
30
+ # Return link to create page for a given model class
31
+ # @param model_class [Class]
32
+ # @param options [Hash]
33
+ # @option options [String] :url url/path for the link
34
+ # @param url_params [ActionController::Parameters, Hash]
35
+ # @param html_options [Hash] (see
36
+ # {https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
37
+ # ActionView::Helpers::UrlHelper#link_to})
38
+ # @yield block to return the link label
39
+ # @return [String, nil] anchor element
40
+ # @return [nil] if user's not authorized
41
+ def new_link(model_class, options: {}, url_params: {}, html_options: {}, &block)
42
+ return if unauthorized? :new, model_class
43
+
44
+ html_options, block = LinkOptionsNormalizer.normalize(
45
+ html_options, block,
46
+ class: 'resource__create',
47
+ block: -> { t 'links.new', model: to_model_label(model_class) }
48
+ )
49
+
50
+ url = options[:url] || new_path(model_class, url_params: url_params)
51
+ link_to url, html_options, &block
52
+ end
53
+
54
+ # Return link to show page for a given model class.
55
+ # @param resource [Object, Wallaby::ResourceDecorator] model class
56
+ # @param options [Hash]
57
+ # @option options [String] :url url/path for the link
58
+ # @option options [Boolean] :readonly readonly and therefore output the label
59
+ # @option options [Boolean] :is_resource to tell {#show_path} if it is a resource
60
+ # @param url_params [ActionController::Parameters, Hash]
61
+ # @param html_options [Hash] (see
62
+ # {https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
63
+ # ActionView::Helpers::UrlHelper#link_to})
64
+ # @yield block to return the link label
65
+ # @return [String] anchor element / text
66
+ # @return [nil] if user's not authorized
67
+ def show_link(resource, options: {}, url_params: {}, html_options: {}, &block)
68
+ # NOTE: to_s is a must
69
+ # if a block is returning integer (e.g. `{ 1 }`)
70
+ # `link_to` will render blank text note inside hyper link
71
+ html_options, block = LinkOptionsNormalizer.normalize(
72
+ html_options, block,
73
+ block: -> { decorate(resource).to_label.to_s }
74
+ )
75
+
76
+ default = options[:readonly] && block.call || nil
77
+ return default if unauthorized? :show, extract(resource)
78
+
79
+ url = options[:url] || show_path(resource, is_resource: options[:is_resource], url_params: url_params)
80
+ link_to url, html_options, &block
81
+ end
82
+
83
+ # Return link to edit page for a given model class.
84
+ # @param resource [Object, Wallaby::ResourceDecorator] model class
85
+ # @param options [Hash]
86
+ # @option options [String] :url url/path for the link
87
+ # @option options [Boolean] :readonly readonly and therefore output the label
88
+ # @option options [Boolean] :is_resource to tell {#edit_path} if it is a resource
89
+ # @param url_params [ActionController::Parameters, Hash]
90
+ # @param html_options [Hash] (see
91
+ # {https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
92
+ # ActionView::Helpers::UrlHelper#link_to})
93
+ # @yield block to return the link label
94
+ # @return [String] anchor element / text
95
+ # @return [nil] if user's not authorized
96
+ def edit_link(resource, options: {}, url_params: {}, html_options: {}, &block)
97
+ html_options, block = LinkOptionsNormalizer.normalize(
98
+ html_options, block,
99
+ class: 'resource__update',
100
+ block: -> { "#{t 'links.edit'} #{decorate(resource).to_label}" }
101
+ )
102
+
103
+ default = options[:readonly] && block.call || nil
104
+ return default if unauthorized? :edit, extract(resource)
105
+
106
+ url = options[:url] || edit_path(resource, is_resource: options[:is_resource], url_params: url_params)
107
+ link_to url, html_options, &block
108
+ end
109
+
110
+ # Return link to delete action for a given model class.
111
+ # @param resource [Object, Wallaby::ResourceDecorator] model class
112
+ # @param options [Hash]
113
+ # @option options [String] :url url/path for the link
114
+ # @option options [Boolean] :is_resource to tell {#edit_path} if it is a resource
115
+ # @param url_params [ActionController::Parameters, Hash]
116
+ # @param html_options [Hash] (see
117
+ # {https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
118
+ # ActionView::Helpers::UrlHelper#link_to})
119
+ # @yield block to return the link label
120
+ # @return [String, nil] anchor element
121
+ # @return [nil] if user's not authorized
122
+ def delete_link(resource, options: {}, url_params: {}, html_options: {}, &block)
123
+ return if unauthorized? :destroy, extract(resource)
124
+
125
+ html_options, block = LinkOptionsNormalizer.normalize(
126
+ html_options, block, class: 'resource__destroy', block: -> { t 'links.delete' }
127
+ )
128
+
129
+ html_options[:method] ||= :delete
130
+ (html_options[:data] ||= {})[:confirm] ||= t 'links.confirm.delete'
131
+
132
+ url = options[:url] || show_path(resource, is_resource: options[:is_resource], url_params: url_params)
133
+ link_to url, html_options, &block
134
+ end
135
+
136
+ # Return link to cancel action
137
+ # @param html_options [Hash] (see
138
+ # {https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
139
+ # ActionView::Helpers::UrlHelper#link_to})
140
+ # @yield block to return the link label
141
+ # @return [String] anchor link of cancel action
142
+ def cancel_link(html_options: {}, &block)
143
+ block ||= -> { t 'links.cancel' }
144
+ link_to 'javascript:history.back()', html_options, &block
145
+ end
146
+
147
+ # @param model_class [Class]
148
+ # @param url_params [Hash]
149
+ # @return [String] index page path
150
+ def index_path(model_class, url_params: {})
151
+ hash = ParamsUtils.presence(
152
+ { action: :index },
153
+ default_path_params(resources: to_resources_name(model_class)),
154
+ url_params.to_h
155
+ )
156
+ current_engine.try(:resources_path, hash) || url_for(hash)
157
+ end
158
+
159
+ # @param model_class [Class]
160
+ # @param url_params [Hash]
161
+ # @return [String] new page path
162
+ def new_path(model_class, url_params: {})
163
+ hash = ParamsUtils.presence(
164
+ { action: :new },
165
+ default_path_params(resources: to_resources_name(model_class)),
166
+ url_params.to_h
167
+ )
168
+ current_engine.try(:new_resource_path, hash) || url_for(hash)
169
+ end
170
+
171
+ # @param resource [Object]
172
+ # @param is_resource [Boolean]
173
+ # @param url_params [Hash]
174
+ # @return [String] show page path
175
+ def show_path(resource, is_resource: false, url_params: {})
176
+ decorated = decorate resource
177
+ return unless is_resource || decorated.primary_key_value
178
+
179
+ hash = ParamsUtils.presence(
180
+ { action: :show, id: decorated.primary_key_value },
181
+ default_path_params(resources: decorated.resources_name),
182
+ url_params.to_h
183
+ )
184
+
185
+ current_engine.try(:resource_path, hash) || url_for(hash)
186
+ end
187
+
188
+ # @param resource [Object]
189
+ # @param is_resource [Boolean]
190
+ # @param url_params [Hash]
191
+ # @return [String] edit page path
192
+ def edit_path(resource, is_resource: false, url_params: {})
193
+ decorated = decorate resource
194
+ return unless is_resource || decorated.primary_key_value
195
+
196
+ hash = ParamsUtils.presence(
197
+ { action: :edit, id: decorated.primary_key_value },
198
+ default_path_params(resources: decorated.resources_name),
199
+ url_params.to_h
200
+ )
201
+
202
+ current_engine.try(:edit_resource_path, hash) || url_for(hash)
203
+ end
204
+
205
+ # @return [Hash] default path params
206
+ def default_path_params(resources: nil)
207
+ { script_name: request.env[SCRIPT_NAME] }.tap do |default|
208
+ default[:resources] = resources if current_engine || resources
209
+ default[:only_path] = true unless default.key?(:only_path)
210
+ end
211
+ end
212
+ end
213
+ end