-
-
Notifications
You must be signed in to change notification settings - Fork 262
/
base_application_controller.rb
343 lines (277 loc) · 11 KB
/
base_application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
module Avo
class BaseApplicationController < ::ActionController::Base
if defined?(Pundit::Authorization)
Avo::BaseApplicationController.include Pundit::Authorization
elsif defined?(Pundit)
Avo::BaseApplicationController.include Pundit
end
include Avo::InitializesAvo
include Avo::CommonController
include Avo::ApplicationHelper
include Avo::UrlHelpers
include Avo::Concerns::Breadcrumbs
include Avo::Concerns::FindAssociationField
protect_from_forgery with: :exception
around_action :set_avo_locale
around_action :set_force_locale, if: -> { params[:force_locale].present? }
before_action :init_app
before_action :set_active_storage_current_host
before_action :set_resource_name
before_action :_authenticate!
before_action :set_authorization
before_action :set_container_classes
before_action :add_initial_breadcrumbs
before_action :set_view
before_action :set_sidebar_open
before_action :set_stylesheet_assets_path
rescue_from Avo::NotAuthorizedError, with: :render_unauthorized
rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
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, :preview_resource_path
add_flash_types :info, :warning, :success, :error
def exception_logger(exception)
respond_to do |format|
format.html { raise exception }
format.json {
render json: {
errors: (exception.respond_to?(:record) && exception.record.present?) ? exception.record.errors : [],
message: exception.message,
traces: exception.backtrace
}, status: ActionDispatch::ExceptionWrapper.status_code_for_exception(exception.class.name)
}
end
end
# This is coming from Turbo::Frames::FrameRequest module.
# Exposing it as public method
def turbo_frame_request?
super
end
private
# Get the pluralized resource name for this request
# Ex: projects, teams, users
def resource_name
return params[:resource_name] if params[:resource_name].present?
return controller_name if controller_name.present?
begin
request.path
.match(/\/?#{Avo.root_path.delete("/")}\/resources\/([a-z1-9\-_]*)\/?/mi)
.captures
.first
rescue
end
end
def related_resource_name
params[:related_name]
end
# Gets the Avo resource for this request based on the request from the `resource_name` "param"
# Ex: Avo::Resources::Project, Avo::Resources::Team, Avo::Resources::User
def resource
resource = Avo.resource_manager.get_resource @resource_name.to_s.camelize.singularize
return resource if resource.present?
Avo.resource_manager.get_resource_by_controller_name @resource_name
end
def related_resource
# Find the field from the parent resource
field = find_association_field(resource: @resource, association: params[:related_name])
return field.use_resource if field&.use_resource.present?
reflection = @record.class.reflect_on_association(field&.for_attribute || params[:related_name])
reflected_model = reflection.klass
Avo.resource_manager.get_resource_by_model_class reflected_model
end
def set_resource_name
@resource_name = resource_name
end
def set_related_resource_name
@related_resource_name = related_resource_name
end
def set_resource
raise ActionController::RoutingError.new "No route matches" if resource.nil?
@resource = resource.new(view: params[:view].presence || action_name.to_s, user: _current_user, params: params)
set_authorization
end
def detect_fields
@resource.detect_fields
end
def set_related_resource
raise Avo::MissingResourceError.new(related_resource_name) if related_resource.nil?
action_view = action_name.to_sym
# Get view from params unless actions is index or show or forms...
# Else, for example for detach action we want the view from params to can fetch the correct fields
# This logic avoid the following scenario:
# When a has many field is rendered the action is index and params[:view] is show or edit but we want to
# keep @view as index for the related_resource
# Same do not happen with other actions except the list below.
view = if action_view.in?([:index, :show, :new, :edit, :create])
action_view
else
params[:view].presence || action_view
end
@related_resource = related_resource.new(
params: params,
view: view,
user: _current_user,
record: @related_record
).detect_fields
end
def set_record
id = if @resource.model_class.primary_key.is_a?(Array) && params.respond_to?(:extract_value)
params.extract_value(:id)
else
params[:id]
end
@record = @resource.find_record(id, query: model_scope, params:)
@resource.hydrate(record: @record)
end
def set_related_record
association_name = BaseResource.valid_association_name(@record, params[:related_name])
@related_record = if @field.is_a? Avo::Fields::HasOneField
@record.send association_name
else
@related_resource.find_record params[:related_id], query: @record.send(association_name), params: params
end
@related_resource.hydrate(record: @related_record)
end
def model_scope
# abort @resource.inspect
@resource.class.find_scope
end
# Force actions to have specific view
unless defined? VIEW_ACTION_MAPPING
VIEW_ACTION_MAPPING = {
update: :edit,
create: :new
}
end
def set_view
@view = Avo::ViewInquirer.new(VIEW_ACTION_MAPPING[action_name.to_sym] || action_name)
end
def set_record_to_fill
@record_to_fill = if @view.new?
@resource.model_class.new
elsif @view.edit?
@record
end
# If resource.record is nil, most likely the user is creating a new record.
# In that case, to access resource.record in visible and readonly blocks we hydrate the resource with a new record.
# TODO: commented this
@resource.hydrate(record: @record_to_fill) if @resource.record.nil?
end
def fill_record
# We have to skip filling the the record if this is an attach action
return if is_attach_action?
@record = @resource.fill_record(@record_to_fill, cast_nullable(model_params), extra_params: extra_params)
assign_default_value_to_disabled_fields if @view.create?
end
def is_attach_action?
params[model_param_key].blank? && params[:related_name].present? && params[:fields].present?
end
def assign_default_value_to_disabled_fields
@resource.get_field_definitions.select do |field|
field.is_disabled? && field.visible? && !field.computed
end.each do |field|
# Get the default value from the field default definition
# If there is no default value specified on the resource, get the value from the record (DB, Callbacks, etc.)
default_value = field.default || @record.send(field.id)
field.fill_field @record, field.id, default_value, params
end
end
def authorize_base_action
class_to_authorize = @record || @resource.model_class
authorize_action class_to_authorize
end
def authorize_action(class_to_authorize, action = nil)
# Use the provided action or figure it out from the request
action_to_authorize = action || action_name
@authorization.set_record(class_to_authorize).authorize_action action_to_authorize.to_sym
end
def _authenticate!
instance_eval(&Avo.configuration.authenticate)
end
def render_unauthorized(exception)
flash[:notice] = t "avo.not_authorized"
redirect_url = if request.referrer.blank? || (request.referrer == request.url)
root_url
else
request.referrer
end
redirect_to(redirect_url)
end
def set_authorization
# We need to set @resource_name for the #resource method to work properly
set_resource_name
@authorization = if @resource
@resource.authorization(user: _current_user)
else
Services::AuthorizationService.new _current_user
end
end
def set_container_classes
contain = true
if Avo.configuration.full_width_container
contain = false
elsif Avo.configuration.full_width_index_view && action_name.to_sym == :index && self.class.superclass.to_s == "Avo::ResourcesController"
contain = false
end
@container_classes = contain ? "2xl:container 2xl:mx-auto" : ""
end
def add_initial_breadcrumbs
instance_eval(&Avo.configuration.initial_breadcrumbs) if Avo.configuration.initial_breadcrumbs.present?
end
def model_param_key
@resource.form_scope
end
# Sets the locale set in avo.rb initializer or if to something that the user set using the `?set_locale=pt-BR` param
def set_avo_locale(&action)
locale = Avo.configuration.default_locale
if params[:set_locale].present?
locale = params[:set_locale]
Avo.configuration.locale = locale
end
I18n.with_locale(locale, &action)
end
# Temporary set the locale and reverting at the end of the request.
def set_force_locale(&action)
I18n.with_locale(params[:force_locale], &action)
end
def set_sidebar_open
value = cookies["#{Avo::COOKIES_KEY}.sidebar.open"]
@sidebar_open = value.blank? || value == "1"
end
# Set the current host for ActiveStorage
def set_active_storage_current_host
if defined?(ActiveStorage::Current)
if Rails::VERSION::MAJOR === 6
ActiveStorage::Current.host = request.base_url
elsif Gem::Version.new(Rails.version) >= Gem::Version.new("7.0.0")
ActiveStorage::Current.url_options = {protocol: request.protocol, host: request.host, port: request.port}
end
end
rescue => exception
Avo.logger.debug "Failed to set ActiveStorage::Current.url_options, #{exception.inspect}"
end
def set_stylesheet_assets_path
# Prefer the user's tailwind config if it exists, otherwise use the default one from Avo
@stylesheet_assets_path = if Rails.root.join("config", "avo", "tailwind.config.js").exist?
"avo.tailwind"
elsif Avo::PACKED
"/avo-assets/avo.base"
else
"avo.base"
end
end
def choose_layout
if turbo_frame_request?
"avo/blank"
else
"avo/application"
end
end
def authenticate_developer_or_admin!
# We don't care about this in development
return if Rails.env.development?
raise_404 unless Avo::Current.user_is_developer? || Avo::Current.user_is_admin?
end
def raise_404
raise ActionController::RoutingError.new "No route matches"
end
end
end