avo 1.9.0 → 1.10.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -3
  3. data/app/components/avo/fields/has_one_field/show_component.html.erb +1 -1
  4. data/app/components/avo/fields/has_one_field/show_component.rb +15 -0
  5. data/app/components/avo/index/resource_controls_component.html.erb +6 -6
  6. data/app/components/avo/index/resource_controls_component.rb +10 -1
  7. data/app/components/avo/resource_component.rb +16 -0
  8. data/app/components/avo/views/resource_index_component.html.erb +3 -3
  9. data/app/components/avo/views/resource_index_component.rb +11 -3
  10. data/app/components/avo/views/resource_show_component.html.erb +5 -2
  11. data/app/components/avo/views/resource_show_component.rb +5 -1
  12. data/app/controllers/avo/actions_controller.rb +8 -2
  13. data/app/controllers/avo/relations_controller.rb +1 -2
  14. data/app/controllers/avo/search_controller.rb +1 -1
  15. data/app/helpers/avo/resources_helper.rb +1 -1
  16. data/app/packs/js/controllers/actions_picker_controller.js +2 -0
  17. data/app/packs/js/controllers/item_selector_controller.js +18 -6
  18. data/app/views/avo/base/_actions.html.erb +10 -4
  19. data/avo.gemspec +0 -1
  20. data/lib/avo/base_action.rb +10 -2
  21. data/lib/avo/base_resource.rb +1 -1
  22. data/lib/avo/services/authorization_service.rb +17 -1
  23. data/lib/avo/version.rb +1 -1
  24. data/lib/generators/avo/action_generator.rb +11 -1
  25. data/lib/generators/avo/templates/standalone_action.tt +8 -0
  26. data/public/avo-packs/css/application-797341b7.css.map +1 -1
  27. data/public/avo-packs/css/application-797341b7.css.map.br +0 -0
  28. data/public/avo-packs/css/application-797341b7.css.map.gz +0 -0
  29. data/public/avo-packs/js/application-feb7dddcfb6598273e06.js +26 -0
  30. data/public/avo-packs/js/{application-651ed7b9bc727c83f673.js.LICENSE.txt → application-feb7dddcfb6598273e06.js.LICENSE.txt} +0 -0
  31. data/public/avo-packs/js/application-feb7dddcfb6598273e06.js.br +0 -0
  32. data/public/avo-packs/js/application-feb7dddcfb6598273e06.js.gz +0 -0
  33. data/public/avo-packs/js/application-feb7dddcfb6598273e06.js.map +1 -0
  34. data/public/avo-packs/js/application-feb7dddcfb6598273e06.js.map.br +0 -0
  35. data/public/avo-packs/js/application-feb7dddcfb6598273e06.js.map.gz +0 -0
  36. data/public/avo-packs/manifest.json +8 -8
  37. metadata +10 -23
  38. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js +0 -26
  39. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.br +0 -0
  40. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.gz +0 -0
  41. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map +0 -1
  42. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map.br +0 -0
  43. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 219e6a5235aed0e22ffacfd3f18f7844735aa38ec3bf894b5d0f4f9b38c3c625
4
- data.tar.gz: 13f60c9fcbbb74c861832532630d2f22a8a711cb5b0feec3cb66bc210506204f
3
+ metadata.gz: e661094465d41e00b859576b04cee6ccf8615bce749f97fdaf5279845bd76bff
4
+ data.tar.gz: 5587659e55ed7a75bceda42e8bdfae2372fb5c7e4c8d8a578ad4fa53dab1b735
5
5
  SHA512:
6
- metadata.gz: 1466e8390b54d6def5ba74f1cafceb4e10ecb13b2c580083b42089dc548bb4e89d6e256f9384645a4ae7c10da0508da33a2620256c9e34525ead03043ef8908a
7
- data.tar.gz: cfef9e4787ad8caf2e81282bbc35c1a646aaeb907738f05b812454bc23dd174c71d3e02e6571ee1b20ada1530c0255d4ce5f8a19648f964a82dc47500d6ecee1
6
+ metadata.gz: e6b60b9d982dcc662a287eb4b96e2c38085c4e0bb39fae22853b0b2c556cf088d0d81fefc930ddc858c2467305a3a1e83d955e2156940a0226c8090c7c9a86f0
7
+ data.tar.gz: 4a12ab93b6543d3916dd427b715ccae7fced14637e73e4912403112a8476d035c6da088a3f8ae977d0eabd487cb93d2fa4da2814f71834f2b4bb74540cb735ce
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (1.9.0)
4
+ avo (1.10.3)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -14,7 +14,6 @@ PATH
14
14
  pagy
15
15
  pundit
16
16
  rails (>= 6.0)
17
- ransack
18
17
  view_component
19
18
  zeitwerk
20
19
 
@@ -213,7 +212,7 @@ GEM
213
212
  nokogiri (1.11.7-x86_64-linux)
214
213
  racc (~> 1.4)
215
214
  orm_adapter (0.5.0)
216
- pagy (4.8.1)
215
+ pagy (4.10.0)
217
216
  parallel (1.20.1)
218
217
  parser (3.0.0.0)
219
218
  ast (~> 2.4.1)
@@ -5,7 +5,7 @@
5
5
  <% else %>
6
6
  <%= render Avo::PanelComponent.new(title: @field.id.capitalize) do |c| %>
7
7
  <% c.tools do %>
8
- <% unless @field.readonly %>
8
+ <% if !@field.readonly && can_attach? %>
9
9
  <%= a_link helpers.resource_attach_path(@resource.model.model_name.route_key, @resource.model.id, @field.id), color: 'indigo', 'data-turbo-frame': 'attach_modal' do %>
10
10
  <%= svg 'view-grid-add' %> <%= t('avo.attach_item', item: @field.id).capitalize %>
11
11
  <% end %>
@@ -2,4 +2,19 @@
2
2
 
3
3
  class Avo::Fields::HasOneField::ShowComponent < Avo::Fields::ShowComponent
4
4
  include Avo::ApplicationHelper
5
+
6
+ def can_attach?
7
+ attach_policy = true
8
+ if @field.present?
9
+ reflection_resource = @field.target_resource
10
+ if reflection_resource.present? && @resource.present?
11
+ method_name = ('attach_' + reflection_resource.model_class.model_name.singular_route_key.underscore + '?').to_sym
12
+ defined_policy_methods = @resource.authorization.defined_methods(@resource.model_class, raise_exception: false)
13
+ if defined_policy_methods.present? && defined_policy_methods.include?(method_name)
14
+ attach_policy = @resource.authorization.authorize_action(method_name, raise_exception: false)
15
+ end
16
+ end
17
+ end
18
+ attach_policy
19
+ end
5
20
  end
@@ -2,7 +2,7 @@
2
2
  <% if can_view? %>
3
3
  <%= link_to helpers.svg('eye', class: 'text-gray-400 h-6 hover:text-gray-600'),
4
4
  show_path,
5
- title: t('avo.view_item', item: @resource.model.model_name.name.downcase ).capitalize,
5
+ title: t('avo.view_item', item: singular_resource_name).capitalize,
6
6
  data: {
7
7
  control: :show,
8
8
  'tippy': 'tooltip',
@@ -13,7 +13,7 @@
13
13
  <% if can_edit? %>
14
14
  <%= link_to helpers.svg('edit', class: 'text-gray-400 h-6 hover:text-gray-600'),
15
15
  edit_path,
16
- title: t('avo.edit_item', item: @resource.model.model_name.name.downcase ).capitalize,
16
+ title: t('avo.edit_item', item: singular_resource_name).capitalize,
17
17
  data: {
18
18
  control: :edit,
19
19
  'resource-id': @resource.model.id,
@@ -27,10 +27,10 @@
27
27
  'data-turbo-frame': params[:turbo_frame]
28
28
  } do |form| %>
29
29
  <%= form.button helpers.svg('trash-sm', class: 'text-gray-400 h-6 hover:text-gray-600'),
30
- title: t('avo.detach_item', item: @resource.model.model_name.name.downcase).capitalize,
30
+ title: t('avo.detach_item', item: singular_resource_name).capitalize,
31
31
  type: :submit,
32
32
  data: {
33
- confirm: t('avo.are_you_sure_detach_item', item: @resource.model.model_name.name.downcase),
33
+ confirm: t('avo.are_you_sure_detach_item', item: singular_resource_name),
34
34
  control: :detach,
35
35
  'resource-id': @resource.model.id,
36
36
  'tippy': 'tooltip',
@@ -46,10 +46,10 @@
46
46
  'data-turbo-frame': params[:turbo_frame]
47
47
  } do |form| %>
48
48
  <%= form.button helpers.svg('trash', class: 'text-gray-400 h-6 hover:text-gray-600'),
49
- title: t('avo.delete_item', item: @resource.model.model_name.name.downcase ).capitalize,
49
+ title: t('avo.delete_item', item: singular_resource_name).capitalize,
50
50
  type: :submit,
51
51
  data: {
52
- confirm: t('avo.are_you_sure', item: @resource.model.model_name.name.downcase ),
52
+ confirm: t('avo.are_you_sure', item: singular_resource_name),
53
53
  control: :destroy,
54
54
  'resource-id': @resource.model.id,
55
55
  'tippy': 'tooltip',
@@ -10,7 +10,8 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
10
10
  def can_detach?
11
11
  @reflection.present? &&
12
12
  @resource.model.present? &&
13
- (@reflection.is_a?(::ActiveRecord::Reflection::HasManyReflection) || @reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection))
13
+ (@reflection.is_a?(::ActiveRecord::Reflection::HasManyReflection) || @reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection)) &&
14
+ authorize_association_for('detach')
14
15
  end
15
16
 
16
17
  def can_edit?
@@ -36,4 +37,12 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
36
37
  helpers.edit_resource_path(@resource.model)
37
38
  end
38
39
  end
40
+
41
+ def singular_resource_name
42
+ if @reflection.present?
43
+ @reflection.name.to_s.downcase.singularize
44
+ else
45
+ @resource.singular_name.present? ? @resource.singular_name : @resource.model_class.model_name.name.downcase
46
+ end
47
+ end
39
48
  end
@@ -7,6 +7,22 @@ class Avo::ResourceComponent < ViewComponent::Base
7
7
  @resource.authorization.authorize_action(:destroy, raise_exception: false)
8
8
  end
9
9
 
10
+ def authorize_association_for(policy_method)
11
+ association_policy = true
12
+ if @reflection.present?
13
+ reflection_resource = ::Avo::App.get_resource_by_model_name(@reflection.active_record.name)
14
+ if reflection_resource.present?
15
+ method_name = ("#{policy_method}_#{@reflection.name.to_s.underscore}?").to_sym
16
+ defined_policy_methods = reflection_resource.authorization.defined_methods(reflection_resource.model_class, raise_exception: false)
17
+ if defined_policy_methods.present? && defined_policy_methods.include?(method_name)
18
+ association_policy = reflection_resource.authorization.authorize_action(method_name, raise_exception: false)
19
+ end
20
+ end
21
+ end
22
+
23
+ association_policy
24
+ end
25
+
10
26
  private
11
27
 
12
28
  def simple_relation?
@@ -11,20 +11,20 @@
11
11
 
12
12
  <% if can_detach? %>
13
13
  <%= a_link detach_path, color: 'indigo', method: :delete, data: { confirm: "Are you sure you want to detach this #{@resource.singular_name}." } do %>
14
- <%= svg 'trash' %> <%= t('avo.detach_item', item: @resource.singular_name).capitalize %>
14
+ <%= svg 'trash' %> <%= t('avo.detach_item', item: singular_resource_name).capitalize %>
15
15
  <% end %>
16
16
  <% end %>
17
17
 
18
18
  <% if can_attach? %>
19
19
  <%= a_link attach_path, color: 'indigo', 'data-turbo-frame': 'attach_modal' do %>
20
- <%= svg 'view-grid-add' %> <%= t('avo.attach_item', item: @resource.singular_name).capitalize %>
20
+ <%= svg 'view-grid-add' %> <%= t('avo.attach_item', item: singular_resource_name).capitalize %>
21
21
  <% end %>
22
22
  <% end %>
23
23
  <% end %>
24
24
 
25
25
  <% c.body do %>
26
26
  <div class="flex justify-between pt-2 pb-2 min-h-16"
27
- data-selected-resources-name="<%= @resource.plural_name.downcase %>"
27
+ data-selected-resources-name="<%= @resource.model_class.model_name.route_key %>"
28
28
  data-selected-resources="[]"
29
29
  >
30
30
  <div class="flex items-center px-6 w-64">
@@ -52,11 +52,11 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
52
52
  klass = @reflection
53
53
  klass = @reflection.through_reflection if klass.is_a? ::ActiveRecord::Reflection::ThroughReflection
54
54
 
55
- @reflection.present? && klass.is_a?(::ActiveRecord::Reflection::HasManyReflection) && !has_reflection_and_is_read_only
55
+ @reflection.present? && klass.is_a?(::ActiveRecord::Reflection::HasManyReflection) && !has_reflection_and_is_read_only && authorize_association_for('attach')
56
56
  end
57
57
 
58
58
  def can_detach?
59
- @reflection.present? && @reflection.is_a?(::ActiveRecord::Reflection::HasOneReflection) && @models.present? && !has_reflection_and_is_read_only
59
+ @reflection.present? && @reflection.is_a?(::ActiveRecord::Reflection::HasOneReflection) && @models.present? && !has_reflection_and_is_read_only && authorize_association_for('detach')
60
60
  end
61
61
 
62
62
  def has_reflection_and_is_read_only
@@ -67,7 +67,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
67
67
  return false
68
68
  end
69
69
 
70
- if filtered_fields
70
+ if filtered_fields.present?
71
71
  is_field_read_only = filtered_fields.filter{ |f| f.id == @reflection.name}[0].readonly
72
72
  else
73
73
  is_field_read_only = false
@@ -101,6 +101,14 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
101
101
  helpers.resource_detach_path(via_resource_name, via_resource_id, via_relation_param, @models.first.id)
102
102
  end
103
103
 
104
+ def singular_resource_name
105
+ if @reflection.present?
106
+ @reflection.name.to_s.downcase.singularize
107
+ else
108
+ @resource.singular_name.present? ? @resource.singular_name : @resource.model_class.model_name.name.downcase
109
+ end
110
+ end
111
+
104
112
  private
105
113
 
106
114
  def simple_relation?
@@ -8,9 +8,12 @@
8
8
  <% if resource_panel[:name] == @resource.default_panel_name %>
9
9
  <%= render 'actions' %>
10
10
 
11
+
11
12
  <% if @reflection.present? && @resource.model.present? %>
12
- <%= a_link detach_path, color: 'indigo', method: :delete, data: { confirm: "Are you sure you want to detach this #{@resource.singular_name}." } do %>
13
- <%= svg 'trash' %> <%= t('avo.detach_item', item: @resource.singular_name).capitalize %>
13
+ <% if can_detach? %>
14
+ <%= a_link detach_path, color: 'indigo', method: :delete, data: { confirm: "Are you sure you want to detach this #{@reflection.name.to_s}." } do %>
15
+ <%= svg 'trash' %> <%= t('avo.detach_item', item: @reflection.name.to_s).capitalize %>
16
+ <% end %>
14
17
  <% end %>
15
18
  <% else %>
16
19
  <%= a_link back_path do %>
@@ -30,13 +30,17 @@ class Avo::Views::ResourceShowComponent < Avo::ResourceComponent
30
30
  end
31
31
 
32
32
  def detach_path
33
- helpers.resource_detach_path(params[:resource_name], params[:id], @resource.model_class.model_name.route_key, @resource.model.id)
33
+ helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.model.id)
34
34
  end
35
35
 
36
36
  def destroy_path
37
37
  helpers.resource_path(@resource.model)
38
38
  end
39
39
 
40
+ def can_detach?
41
+ authorize_association_for('detach')
42
+ end
43
+
40
44
  private
41
45
 
42
46
  def via_resource?
@@ -11,14 +11,20 @@ module Avo
11
11
  end
12
12
 
13
13
  def handle
14
- resource_ids = action_params[:fields][:resource_ids].split(",").map(&:to_i)
14
+ resource_ids = action_params[:fields][:resource_ids].split(",")
15
15
  models = @resource.class.find_scope.find resource_ids
16
16
 
17
17
  fields = action_params[:fields].select do |key, value|
18
18
  key != "resource_ids"
19
19
  end
20
20
 
21
- performed_action = @action.handle_action(models: models, fields: fields)
21
+ args = {
22
+ fields: fields
23
+ }
24
+
25
+ args[:models] = models unless @action.standalone
26
+
27
+ performed_action = @action.handle_action(**args)
22
28
 
23
29
  respond performed_action.response
24
30
  end
@@ -10,7 +10,7 @@ module Avo
10
10
  before_action :set_attachment_class
11
11
  before_action :set_attachment_resource
12
12
  before_action :set_attachment_model, only: [:create, :destroy]
13
- before_action :set_reflection, only: [:index]
13
+ before_action :set_reflection, only: [:index, :show]
14
14
 
15
15
  def index
16
16
  @parent_resource = @resource.dup
@@ -23,7 +23,6 @@ module Avo
23
23
  end
24
24
 
25
25
  def show
26
- @reflection = params[:related_name]
27
26
  @resource, @model = @related_resource, @related_model
28
27
 
29
28
  super
@@ -32,7 +32,7 @@ module Avo
32
32
  .select do |payload|
33
33
  payload.present?
34
34
  end
35
- .sort do |payload|
35
+ .sort_by do |payload|
36
36
  payload.last[:count]
37
37
  end
38
38
  .reverse
@@ -42,7 +42,7 @@ module Avo
42
42
  end
43
43
 
44
44
  def item_selector_init(resource)
45
- "data-resource-name='#{resource.plural_name.downcase}' data-resource-id='#{resource.model.id}' data-controller='item-selector'"
45
+ "data-resource-name='#{resource.model_class.model_name.route_key}' data-resource-id='#{resource.model.id}' data-controller='item-selector'"
46
46
  end
47
47
 
48
48
  def item_selector_input(floating: false, size: :md)
@@ -2,6 +2,8 @@ import { AttributeObserver } from '@stimulus/mutation-observers'
2
2
  import { Controller } from 'stimulus'
3
3
 
4
4
  export default class extends Controller {
5
+ static targets = ['resourceAction', 'standaloneAction']
6
+
5
7
  target = {}
6
8
 
7
9
  enableTarget() {
@@ -22,9 +22,9 @@ export default class extends Controller {
22
22
 
23
23
  if (this.actionsPanelPresent) {
24
24
  if (value.length > 0) {
25
- this.enableActionsPanel()
25
+ this.enableResourceActions()
26
26
  } else {
27
- this.disableActionsPanel()
27
+ this.disableResourceActions()
28
28
  }
29
29
  }
30
30
  }
@@ -58,11 +58,23 @@ export default class extends Controller {
58
58
  }
59
59
  }
60
60
 
61
- enableActionsPanel() {
62
- this.actionsButtonElement.disabled = false
61
+ enableResourceActions() {
62
+ (document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]'))
63
+ .forEach((link) => {
64
+ link.classList.add('text-gray-700')
65
+ link.classList.remove('text-gray-500')
66
+ link.setAttribute('data-href', link.getAttribute('href'))
67
+ link.dataset.disabled = false
68
+ })
63
69
  }
64
70
 
65
- disableActionsPanel() {
66
- this.actionsButtonElement.disabled = true
71
+ disableResourceActions() {
72
+ (document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]'))
73
+ .forEach((link) => {
74
+ link.classList.remove('text-gray-700')
75
+ link.classList.add('text-gray-500')
76
+ link.setAttribute('href', link.getAttribute('data-href'))
77
+ link.dataset.disabled = true
78
+ })
67
79
  }
68
80
  }
@@ -1,10 +1,9 @@
1
1
  <% if @actions.count > 0 %>
2
- <div class="relative z-30" data-controller="toggle-panel actions-picker">
2
+ <div class="relative z-30 js-actions-dropdown" data-controller="toggle-panel actions-picker">
3
3
  <%= a_button class: "focus:outline-none",
4
4
  color: 'light-blue',
5
5
  'data-action': "click->toggle-panel#togglePanel",
6
- 'data-actions-dropdown-button': @resource.plural_name.downcase,
7
- disabled: action_name != 'show' do
6
+ 'data-actions-dropdown-button': @resource.model_class.model_name.route_key do
8
7
  %>
9
8
  <%= svg 'arrow-left', class: 'h-4 mr-1 transform -rotate-90' %> <%= t 'avo.actions' %>
10
9
  <% end %>
@@ -17,11 +16,18 @@
17
16
  path = action_name == 'show' ?
18
17
  "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/#{@model.id}/actions/#{action.param_id}" :
19
18
  "#{avo.root_path}resources/#{@resource.model_class.model_name.route_key}/actions/#{action.param_id}"
19
+ if action_name == 'show' || action.standalone
20
+ disabled = false
21
+ else
22
+ disabled = true
23
+ end
20
24
  %>
21
25
  <%= link_to path,
22
26
  'data-turbo-frame': 'actions_show',
23
27
  'data-action': 'click->actions-picker#visitAction',
24
- class: 'flex items-center w-full py-2 px-4 font-bold text-gray-700 hover:text-white hover:bg-blue-500' do %>
28
+ 'data-actions-picker-target': action.standalone ? 'standaloneAction' : 'resourceAction',
29
+ class: "flex items-center w-full py-2 px-4 font-bold text-gray-700 hover:text-white hover:bg-blue-500 #{disabled ? 'text-gray-500' : 'text-gray-700'}",
30
+ 'data-disabled': disabled do %>
25
31
  <%= svg 'play', class: 'h-5 mr-1 inline' %> <%= action.action_name %>
26
32
  <% end %>
27
33
  <% end %>
data/avo.gemspec CHANGED
@@ -45,5 +45,4 @@ Gem::Specification.new do |spec|
45
45
  spec.add_dependency "meta-tags"
46
46
  spec.add_dependency "breadcrumbs_on_rails"
47
47
  spec.add_dependency "manifester"
48
- spec.add_dependency "ransack"
49
48
  end
@@ -13,6 +13,7 @@ module Avo
13
13
  class_attribute :user
14
14
  class_attribute :resource
15
15
  class_attribute :fields
16
+ class_attribute :standalone, default: false
16
17
 
17
18
  attr_accessor :response
18
19
  attr_accessor :model
@@ -68,7 +69,8 @@ module Avo
68
69
  .to_h
69
70
  end
70
71
 
71
- def handle_action(models:, fields:)
72
+ def handle_action(**args)
73
+ models, fields = args.values_at(:models, :fields)
72
74
  avo_fields = get_fields.map { |field| [field.id, field] }.to_h
73
75
 
74
76
  if fields.present?
@@ -81,7 +83,13 @@ module Avo
81
83
  processed_fields = {}
82
84
  end
83
85
 
84
- handle models: models, fields: processed_fields
86
+ args = {
87
+ fields: processed_fields
88
+ }
89
+
90
+ args[:models] = models unless standalone
91
+
92
+ handle(**args)
85
93
 
86
94
  self
87
95
  end