avo 2.17.1.pre.3 → 2.17.1.pre.5.stackedlayout

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +20 -9
  3. data/Gemfile.lock +78 -79
  4. data/app/components/avo/field_wrapper_component.html.erb +9 -11
  5. data/app/components/avo/field_wrapper_component.rb +10 -3
  6. data/app/components/avo/fields/date_field/edit_component.html.erb +6 -6
  7. data/app/components/avo/fields/date_time_field/edit_component.html.erb +7 -6
  8. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -0
  9. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -0
  10. data/app/components/avo/fields/edit_component.rb +1 -1
  11. data/app/components/avo/fields/show_component.rb +1 -1
  12. data/app/components/avo/fields/time_field/edit_component.html.erb +6 -6
  13. data/app/components/avo/index/resource_table_component.html.erb +1 -1
  14. data/app/components/avo/item_switcher_component.html.erb +4 -1
  15. data/app/components/avo/panel_component.html.erb +5 -2
  16. data/app/components/avo/views/resource_edit_component.html.erb +3 -1
  17. data/app/components/avo/views/resource_index_component.html.erb +4 -4
  18. data/app/components/avo/views/resource_show_component.html.erb +5 -2
  19. data/app/controllers/avo/actions_controller.rb +6 -5
  20. data/app/controllers/avo/application_controller.rb +9 -17
  21. data/app/controllers/avo/associations_controller.rb +1 -1
  22. data/app/controllers/avo/cards_controller.rb +12 -2
  23. data/app/javascript/js/controllers/fields/date_field_controller.js +34 -21
  24. data/app/views/avo/actions/show.html.erb +1 -1
  25. data/app/views/avo/cards/chartkick_missing.html.erb +14 -0
  26. data/avo.gemspec +2 -5
  27. data/db/factories.rb +5 -5
  28. data/lib/avo/base_action.rb +1 -1
  29. data/lib/avo/base_resource.rb +1 -0
  30. data/lib/avo/concerns/can_replace_fields.rb +36 -0
  31. data/lib/avo/configuration.rb +4 -0
  32. data/lib/avo/engine.rb +10 -1
  33. data/lib/avo/fields/base_field.rb +2 -0
  34. data/lib/avo/fields/country_field.rb +5 -1
  35. data/lib/avo/fields/date_time_field.rb +2 -0
  36. data/lib/avo/fields/time_field.rb +1 -7
  37. data/lib/avo/html/builder.rb +14 -0
  38. data/lib/avo/services/authorization_clients/pundit_client.rb +51 -0
  39. data/lib/avo/services/authorization_service.rb +43 -61
  40. data/lib/avo/version.rb +1 -1
  41. data/lib/avo.rb +4 -0
  42. data/lib/generators/avo/templates/initializer/avo.tt +2 -0
  43. data/public/avo-assets/avo.base.css +5 -0
  44. data/public/avo-assets/avo.base.js +73 -73
  45. data/public/avo-assets/avo.base.js.map +2 -2
  46. metadata +11 -50
@@ -25,8 +25,11 @@
25
25
  <% end %>
26
26
  <% if body? %>
27
27
  <div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:gap-4 w-full">
28
- <div class="relative flex-1 overflow-auto <%= white_panel_classes %> <%= @body_classes %> <% if sidebar? %> w-2/3 <% end %>">
29
- <%= body %>
28
+ <div class="relative flex-1 <% if sidebar? %> w-2/3 <% else %> w-full <% end %>">
29
+ <% # The body is wrapped inside another div in order to avoid long & tall panels next to sidebars when the sidebar taller. %>
30
+ <div class="relative <%= white_panel_classes %> <%= @body_classes %>">
31
+ <%= body %>
32
+ </div>
30
33
  </div>
31
34
  <% if sidebar? %>
32
35
  <div class="w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
@@ -1,6 +1,8 @@
1
1
  <%= content_tag :div,
2
2
  data: {
3
- 'model-id': @resource.model.id,
3
+ model_name: @resource.model_name.to_s,
4
+ resource_name: @resource.class.to_s,
5
+ model_id: @resource.model.id,
4
6
  selected_resources_name: @resource.model_key,
5
7
  selected_resources: [@resource.model.id],
6
8
  **@resource.stimulus_data_attributes
@@ -1,5 +1,7 @@
1
1
  <%= content_tag :div,
2
2
  data: {
3
+ model_name: @resource.model_name.to_s,
4
+ resource_name: @resource.class.to_s,
3
5
  **@resource.stimulus_data_attributes
4
6
  } do %>
5
7
  <%= render Avo::PanelComponent.new(name: title, description: description, data: { component: 'resources-index' }, display_breadcrumbs: @reflection.blank?) do |c| %>
@@ -46,10 +48,8 @@
46
48
  </div>
47
49
  <% if view_type.to_sym == :table %>
48
50
  <% if @resources.present? %>
49
- <div class="w-full overflow-auto flex flex-col mt-0 mac-styled-scrollbar">
50
- <div class="relative flex-1 flex">
51
- <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
52
- </div>
51
+ <div class="w-full relative flex-1 flex mt-0">
52
+ <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
53
53
  </div>
54
54
  <% else %>
55
55
  <%= helpers.empty_state resource_name: @resource.name.downcase.pluralize, related_name: params[:related_name], view_type: view_type, add_background: true %>
@@ -1,6 +1,8 @@
1
1
  <%= content_tag :div,
2
2
  data: {
3
- 'model-id': @resource.model.id,
3
+ model_name: @resource.model_name.to_s,
4
+ resource_name: @resource.class.to_s,
5
+ model_id: @resource.model.id,
4
6
  selected_resources_name: @resource.model_key,
5
7
  selected_resources: [@resource.model.id],
6
8
  **@resource.stimulus_data_attributes
@@ -154,7 +156,8 @@
154
156
  <% end %>
155
157
  <% if main_panel.present? %>
156
158
  <% c.body do %>
157
- <div class="divide-y">
159
+ <%# the overflow helps with long values %>
160
+ <div class="divide-y overflow-auto">
158
161
  <% main_panel.items.each_with_index do |field, index| %>
159
162
  <%= render field
160
163
  .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: view)
@@ -12,7 +12,10 @@ module Avo
12
12
  before_action :set_action, only: [:show, :handle]
13
13
 
14
14
  def show
15
- @resource.hydrate(model: @model, view: :show, user: _current_user, params: params)
15
+ # Se the view to :new so the default value gets prefilled
16
+ @view = :new
17
+
18
+ @resource.hydrate(model: @model, view: @view, user: _current_user, params: params)
16
19
  @model = ActionModel.new @action.get_attributes_for_action
17
20
  end
18
21
 
@@ -48,17 +51,15 @@ module Avo
48
51
  end
49
52
 
50
53
  def set_action
51
- @action = action_class.new(model: @model, resource: @resource, user: _current_user, view: :edit)
54
+ @action = action_class.new(model: @model, resource: @resource, user: _current_user, view: @view)
52
55
  end
53
56
 
54
57
  def action_class
55
58
  klass_name = params[:action_id].gsub("avo_actions_", "").camelize
56
59
 
57
- valid_klass = Avo::BaseAction.descendants.find do |action|
60
+ Avo::BaseAction.descendants.find do |action|
58
61
  action.to_s == klass_name
59
62
  end
60
-
61
- valid_klass
62
63
  end
63
64
 
64
65
  def respond(response)
@@ -1,11 +1,5 @@
1
1
  module Avo
2
2
  class ApplicationController < ::ActionController::Base
3
- if defined?(Pundit::Authorization)
4
- include Pundit::Authorization
5
- else
6
- include Pundit
7
- end
8
-
9
3
  include Pagy::Backend
10
4
  include Avo::ApplicationHelper
11
5
  include Avo::UrlHelpers
@@ -24,7 +18,7 @@ module Avo
24
18
  before_action :set_view
25
19
  before_action :set_sidebar_open
26
20
 
27
- rescue_from Pundit::NotAuthorizedError, with: :render_unauthorized
21
+ rescue_from Avo::NotAuthorizedError, with: :render_unauthorized
28
22
  rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
29
23
 
30
24
  helper_method :_current_user, :resources_path, :resource_path, :new_resource_path, :edit_resource_path, :resource_attach_path, :resource_detach_path, :related_resources_path, :turbo_frame_request?, :resource_view_path
@@ -257,18 +251,16 @@ module Avo
257
251
  instance_eval(&Avo.configuration.authenticate)
258
252
  end
259
253
 
260
- def render_unauthorized(exception)
261
- if !exception.is_a? Pundit::NotDefinedError
262
- flash.now[:notice] = t "avo.not_authorized"
263
-
264
- redirect_url = if request.referrer.blank? || (request.referrer == request.url)
265
- root_url
266
- else
267
- request.referrer
268
- end
254
+ def render_unauthorized(_exception)
255
+ flash.now[:notice] = t "avo.not_authorized"
269
256
 
270
- redirect_to(redirect_url)
257
+ redirect_url = if request.referrer.blank? || (request.referrer == request.url)
258
+ root_url
259
+ else
260
+ request.referrer
271
261
  end
262
+
263
+ redirect_to(redirect_url)
272
264
  end
273
265
 
274
266
  def set_authorization
@@ -157,7 +157,7 @@ module Avo
157
157
  private
158
158
 
159
159
  def set_related_authorization
160
- @authorization = if related_resource
160
+ @related_authorization = if related_resource
161
161
  related_resource.authorization(user: _current_user)
162
162
  else
163
163
  Services::AuthorizationService.new _current_user
@@ -2,10 +2,12 @@ require_dependency "avo/application_controller"
2
2
 
3
3
  module Avo
4
4
  class CardsController < ApplicationController
5
- before_action :set_dashboard, only: :show
6
- before_action :set_card, only: :show
5
+ before_action :set_dashboard
6
+ before_action :set_card
7
+ before_action :detect_chartkick
7
8
 
8
9
  def show
10
+ render(:chartkick_missing) unless @chartkick_installed
9
11
  end
10
12
 
11
13
  private
@@ -21,5 +23,13 @@ module Avo
21
23
  card.hydrate(dashboard: @dashboard, params: params)
22
24
  end
23
25
  end
26
+
27
+ def detect_chartkick
28
+ @chartkick_installed = if @card.class.ancestors.map(&:to_s).include?("Avo::Dashboards::ChartkickCard")
29
+ defined?(Chartkick)
30
+ else
31
+ true
32
+ end
33
+ end
24
34
  end
25
35
  end
@@ -60,6 +60,22 @@ export default class extends Controller {
60
60
  return this.viewValue === 'show'
61
61
  }
62
62
 
63
+ get fieldIsDate() {
64
+ return this.fieldTypeValue === 'date'
65
+ }
66
+
67
+ get fieldIsDateTime() {
68
+ return this.fieldTypeValue === 'dateTime'
69
+ }
70
+
71
+ get fieldIsTime() {
72
+ return this.fieldTypeValue === 'time'
73
+ }
74
+
75
+ get fieldHasTime() {
76
+ return this.fieldIsTime || this.fieldIsDateTime
77
+ }
78
+
63
79
  // Parse the time as if it were UTC
64
80
  get parsedValue() {
65
81
  return DateTime.fromISO(this.initialValue, { zone: 'UTC' })
@@ -98,7 +114,7 @@ export default class extends Controller {
98
114
  let value = this.parsedValue
99
115
 
100
116
  // Set the zone only if the type of field is date time or relative time.
101
- if (this.enableTimeValue && this.relativeValue) {
117
+ if (this.fieldHasTime && this.relativeValue) {
102
118
  value = value.setZone(this.displayTimezone)
103
119
  }
104
120
 
@@ -136,16 +152,22 @@ export default class extends Controller {
136
152
  // Hide calendar and only keep time picker.
137
153
  options.noCalendar = this.noCalendarValue
138
154
 
155
+ if (this.fieldHasTime) {
156
+ options.dateFormat = 'Y-m-d H:i:S'
157
+ }
158
+
139
159
  if (this.initialValue) {
140
- // Enable timezone display
141
- if (this.enableTimeValue && this.relativeValue) {
142
- options.defaultDate = this.parsedValue.setZone(this.displayTimezone).toISO()
143
-
144
- options.dateFormat = 'Y-m-d H:i:S'
145
- } else {
146
- // Because the browser treats the date like a timestamp and updates it at 00:00 hour, when on a western timezone the date will be converted with one day offset.
147
- // Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
148
- options.defaultDate = universalTimestamp(this.initialValue)
160
+ switch (this.fieldTypeValue) {
161
+ case 'date':
162
+ options.defaultDate = universalTimestamp(this.initialValue)
163
+ break
164
+ default:
165
+ case 'time':
166
+ options.defaultDate = this.parsedValue.setZone(this.displayTimezone, { keepLocalTime: !this.relativeValue }).toISO()
167
+ break
168
+ case 'dateTime':
169
+ options.defaultDate = this.parsedValue.setZone(this.displayTimezone, { keepLocalTime: !this.relativeValue }).toISO()
170
+ break
149
171
  }
150
172
  }
151
173
 
@@ -182,27 +204,18 @@ export default class extends Controller {
182
204
  return
183
205
  }
184
206
 
185
- let args = {}
186
-
187
- // For values that involve time we should keep the local time.
188
- if (this.timezoneValue || !this.relativeValue) {
189
- args = { keepLocalTime: true }
190
- } else {
191
- args = { keepLocalTime: false }
192
- }
193
-
194
207
  let value
195
208
  switch (this.fieldTypeValue) {
196
209
  case 'time':
197
210
  // For time values, we should maintain the real value and format it to a time-friendly format.
198
- value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args).toFormat(RAW_TIME_FORMAT)
211
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: !this.relativeValue }).toFormat(RAW_TIME_FORMAT)
199
212
  break
200
213
  case 'date':
201
214
  value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true }).toFormat(RAW_DATE_FORMAT)
202
215
  break
203
216
  default:
204
217
  case 'dateTime':
205
- value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', args).toISO()
218
+ value = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: !this.relativeValue }).toISO()
206
219
  break
207
220
  }
208
221
 
@@ -27,7 +27,7 @@
27
27
  <% @action.get_fields.each_with_index do |field, index| %>
28
28
  <%= render field
29
29
  .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: @view)
30
- .component_for_view(:edit)
30
+ .component_for_view(@view)
31
31
  .new(field: field, resource: @resource, index: index, form: form, compact: true)
32
32
  %>
33
33
  <% end %>
@@ -0,0 +1,14 @@
1
+ <%= render Avo::TurboFrameWrapperComponent.new(params[:turbo_frame]) do %>
2
+ <div class="w-full h-full flex items-center text-center">
3
+ <div class="flex-1">
4
+ Chartkick is missing.
5
+ <a
6
+ href="https://docs.avohq.io/2.0/upgrade.html#upgrade-from-2-17-to-2-18"
7
+ target="_blank"
8
+ >
9
+ Manually require it in your
10
+ <code class="inline">Gemfile</code>
11
+ </a>.
12
+ </div>
13
+ </div>
14
+ <% end %>
data/avo.gemspec CHANGED
@@ -32,20 +32,17 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.files = Dir["{bin,app,config,db,lib,public}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "avo.gemspec", "Gemfile", "Gemfile.lock"]
34
34
 
35
- spec.add_dependency "rails", ">= 6.0"
35
+ spec.add_dependency "activerecord", ">= 6.0"
36
+ spec.add_dependency "actionview", ">= 6.0"
36
37
  spec.add_dependency "pagy"
37
38
  spec.add_dependency "zeitwerk"
38
- spec.add_dependency "countries"
39
- spec.add_dependency "pundit"
40
39
  spec.add_dependency "httparty"
41
40
  spec.add_dependency "active_link_to"
42
- spec.add_dependency "image_processing"
43
41
  spec.add_dependency "view_component"
44
42
  spec.add_dependency "turbo-rails"
45
43
  spec.add_dependency "addressable"
46
44
  spec.add_dependency "meta-tags"
47
45
  spec.add_dependency "breadcrumbs_on_rails"
48
- spec.add_dependency "chartkick"
49
46
  spec.add_dependency "dry-initializer"
50
47
  spec.add_dependency "docile"
51
48
  spec.add_dependency "inline_svg"
data/db/factories.rb CHANGED
@@ -90,10 +90,10 @@ FactoryBot.define do
90
90
  end
91
91
 
92
92
  factory :product do
93
- title { "MyString" }
94
- description { "MyText" }
95
- price { 1 }
96
- status { "MyString" }
97
- category { "MyString" }
93
+ title { Faker::App.name }
94
+ description { Faker::Lorem.paragraphs(number: rand(1...3)).join("\n") }
95
+ price { rand(10000..999000) }
96
+ status { "status" }
97
+ category { ::Product.categories.values.sample }
98
98
  end
99
99
  end
@@ -72,7 +72,7 @@ module Avo
72
72
 
73
73
  def get_attributes_for_action
74
74
  get_fields.map do |field|
75
- [field.id, field.value]
75
+ [field.id, field.value || field.default]
76
76
  end.to_h
77
77
  end
78
78
 
@@ -4,6 +4,7 @@ module Avo
4
4
 
5
5
  include ActionView::Helpers::UrlHelper
6
6
  include Avo::Concerns::HasFields
7
+ include Avo::Concerns::CanReplaceFields
7
8
  include Avo::Concerns::HasEditableControls
8
9
  include Avo::Concerns::HasStimulusControllers
9
10
  include Avo::Concerns::ModelClassConstantized
@@ -0,0 +1,36 @@
1
+ module Avo
2
+ module Concerns
3
+ module CanReplaceFields
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :backup_items_holder
8
+ end
9
+
10
+ class_methods do
11
+ def with_temporary_items(&block)
12
+ # back-up the previous items
13
+ self.backup_items_holder = items_holder
14
+
15
+ self.items_holder = Avo::ItemsHolder.new
16
+
17
+ instance_eval(&block)
18
+ end
19
+
20
+ def restore_items_from_backup
21
+ self.items_holder = backup_items_holder if backup_items_holder.present?
22
+ end
23
+
24
+ def with_new_items(&block)
25
+ self.items_holder = Avo::ItemsHolder.new
26
+
27
+ instance_eval(&block)
28
+ end
29
+ end
30
+
31
+ def with_new_items(&block)
32
+ self.class.with_new_items(&block)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -37,6 +37,8 @@ module Avo
37
37
  attr_accessor :model_resource_mapping
38
38
  attr_accessor :tabs_style
39
39
  attr_accessor :resource_default_view
40
+ attr_accessor :authorization_client
41
+ attr_accessor :field_wrapper_layout
40
42
  attr_writer :branding
41
43
 
42
44
  def initialize
@@ -85,6 +87,8 @@ module Avo
85
87
  @model_resource_mapping = {}
86
88
  @tabs_style = :tabs
87
89
  @resource_default_view = :show
90
+ @authorization_client = :pundit
91
+ @field_wrapper_layout = :inline
88
92
  end
89
93
 
90
94
  def current_user_method(&block)
data/lib/avo/engine.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  # requires all dependencies
2
2
  Gem.loaded_specs["avo"].dependencies.each do |d|
3
- require d.name
3
+ case d.name
4
+ when "activerecord"
5
+ require "active_record/railtie"
6
+ when "actionview"
7
+ require "action_view/railtie"
8
+ when "activestorage"
9
+ require "active_storage/engine"
10
+ else
11
+ require d.name
12
+ end
4
13
  end
5
14
 
6
15
  # In development we should load the engine so we get the autoload for components
@@ -36,6 +36,7 @@ module Avo
36
36
  attr_reader :as_avatar
37
37
  attr_reader :as_description
38
38
  attr_reader :index_text_align
39
+ attr_reader :stacked
39
40
 
40
41
  # Private options
41
42
  attr_reader :updatable
@@ -78,6 +79,7 @@ module Avo
78
79
  @html = args[:html] || nil
79
80
  @view = args[:view] || nil
80
81
  @value = args[:value] || nil
82
+ @stacked = args[:stacked] || nil
81
83
 
82
84
  @args = args
83
85
 
@@ -11,7 +11,11 @@ module Avo
11
11
 
12
12
  super(id, **args, &block)
13
13
 
14
- @countries = ISO3166::Country.translations.sort_by { |code, name| name }.to_h
14
+ @countries = begin
15
+ ISO3166::Country.translations.sort_by { |code, name| name }.to_h
16
+ rescue
17
+ {none: "You need to install the countries gem for this field to work properly"}
18
+ end
15
19
  @display_code = args[:display_code].present? ? args[:display_code] : false
16
20
  end
17
21
 
@@ -5,6 +5,7 @@ module Avo
5
5
  attr_reader :picker_format
6
6
  attr_reader :time_24hr
7
7
  attr_reader :timezone
8
+ attr_reader :relative
8
9
 
9
10
  def initialize(id, **args, &block)
10
11
  super(id, **args, &block)
@@ -13,6 +14,7 @@ module Avo
13
14
  add_string_prop args, :picker_format, "Y-m-d H:i:S"
14
15
  add_string_prop args, :format, "yyyy-LL-dd TT"
15
16
  add_string_prop args, :timezone
17
+ add_boolean_prop args, :relative, true
16
18
  end
17
19
 
18
20
  def formatted_value
@@ -1,20 +1,14 @@
1
1
  module Avo
2
2
  module Fields
3
- class TimeField < DateField
3
+ class TimeField < DateTimeField
4
4
  attr_reader :format
5
5
  attr_reader :picker_format
6
- attr_reader :time_24hr
7
- attr_reader :timezone
8
- attr_reader :relative
9
6
 
10
7
  def initialize(id, **args, &block)
11
8
  super(id, **args, &block)
12
9
 
13
- add_boolean_prop args, :time_24hr
14
10
  add_string_prop args, :picker_format, "H:i:S"
15
11
  add_string_prop args, :format, "TT"
16
- add_string_prop args, :timezone
17
- add_boolean_prop args, :relative, true
18
12
  end
19
13
 
20
14
  def formatted_value
@@ -13,6 +13,8 @@ class Avo::HTML::Builder
13
13
  attr_accessor :edit_stack
14
14
  attr_accessor :index_stack
15
15
  attr_accessor :input_stack
16
+ attr_accessor :label_stack
17
+ attr_accessor :content_stack
16
18
 
17
19
  attr_accessor :record
18
20
  attr_accessor :resource
@@ -30,6 +32,8 @@ class Avo::HTML::Builder
30
32
  @edit_stack = {}
31
33
  @index_stack = {}
32
34
  @input_stack = {}
35
+ @label_stack = {}
36
+ @content_stack = {}
33
37
 
34
38
  @record = record
35
39
  @resource = resource
@@ -77,6 +81,16 @@ class Avo::HTML::Builder
77
81
  capture_block :input, &block
78
82
  end
79
83
 
84
+ # Takes a block
85
+ def label(&block)
86
+ capture_block :label, &block
87
+ end
88
+
89
+ # Takes a block
90
+ def content(&block)
91
+ capture_block :content, &block
92
+ end
93
+
80
94
  # Takes a block
81
95
  def show(&block)
82
96
  capture_block :show, &block
@@ -0,0 +1,51 @@
1
+ module Avo
2
+ module Services
3
+ module AuthorizationClients
4
+ class PunditClient
5
+ def authorize(user, record, action, policy_class: nil)
6
+ Pundit.authorize(user, record, action, policy_class: policy_class)
7
+ rescue Pundit::NotDefinedError => error
8
+ raise NoPolicyError.new error.message
9
+ rescue Pundit::NotAuthorizedError => error
10
+ raise NotAuthorizedError.new error.message
11
+ end
12
+
13
+ def policy(user, record)
14
+ Pundit.policy(user, record)
15
+ end
16
+
17
+ def policy!(user, record)
18
+ Pundit.policy!(user, record)
19
+ rescue Pundit::NotDefinedError => error
20
+ raise NoPolicyError.new error.message
21
+ end
22
+
23
+ def apply_policy(user, model, policy_class: nil)
24
+ # Try and figure out the scope from a given policy or auto-detected one
25
+ scope_from_policy_class = scope_for_policy_class(policy_class)
26
+
27
+ # If we discover one use it.
28
+ # Else fallback to pundit.
29
+ if scope_from_policy_class.present?
30
+ scope_from_policy_class.new(user, model).resolve
31
+ else
32
+ Pundit.policy_scope!(user, model)
33
+ end
34
+ rescue Pundit::NotDefinedError => error
35
+ raise NoPolicyError.new error.message
36
+ end
37
+
38
+ private
39
+
40
+ # Fetches the scope for a given policy
41
+ def scope_for_policy_class(policy_class = nil)
42
+ return if policy_class.blank?
43
+
44
+ if policy_class.present? && defined?(policy_class::Scope)
45
+ policy_class::Scope
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end