Class: Avo::BaseResource

Inherits:
Object
  • Object
show all
Extended by:
ActiveSupport::DescendantsTracker
Includes:
ActionView::Helpers::UrlHelper, Concerns::CanReplaceItems, Concerns::HasControls, Concerns::HasDescription, Concerns::HasHelpers, Concerns::HasItems, Concerns::HasStimulusControllers, Concerns::ModelClassConstantized
Defined in:
lib/avo/base_resource.rb

Instance Attribute Summary collapse

Attributes included from Concerns::HasItems

#items_holder

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Concerns::HasHelpers

#helpers

Methods included from Concerns::HasDescription

#description

Methods included from Concerns::HasStimulusControllers

#get_stimulus_controllers, #stimulus_data_attributes

Methods included from Concerns::HasControls

#render_edit_controls, #render_index_controls, #render_row_controls, #render_show_controls

Methods included from Concerns::CanReplaceItems

#with_new_items

Methods included from Concerns::HasItems

#get_field, #get_field_definitions, #get_fields, #get_items, #get_preview_fields, #invalid_fields, #is_empty?, #items, #only_fields, #tab_groups, #tools, #visible_items

Constructor Details

#initialize(record: nil, view: nil, user: nil, params: nil) ⇒ BaseResource

Returns a new instance of BaseResource.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/avo/base_resource.rb', line 236

def initialize(record: nil, view: nil, user: nil, params: nil)
  @view = Avo::ViewInquirer.new(view) if view.present?
  @user = user if user.present?
  @params = params if params.present?

  if record.present?
    @record = record

    hydrate_model_with_default_values if @view&.new?
  end

  detect_fields

  unless self.class.model_class.present?
    if model_class.present? && model_class.respond_to?(:base_class)
      self.class.model_class = model_class.base_class
    end
  end
end

Instance Attribute Details

#recordObject

Returns the value of attribute record.



41
42
43
# File 'lib/avo/base_resource.rb', line 41

def record
  @record
end

#reflectionObject

Returns the value of attribute reflection.



39
40
41
# File 'lib/avo/base_resource.rb', line 39

def reflection
  @reflection
end

#userObject

Returns the value of attribute user.



40
41
42
# File 'lib/avo/base_resource.rb', line 40

def user
  @user
end

#viewObject

Returns the value of attribute view.



38
39
40
# File 'lib/avo/base_resource.rb', line 38

def view
  @view
end

Class Method Details

.action(action_class, arguments: {}) ⇒ Object



78
79
80
# File 'lib/avo/base_resource.rb', line 78

def action(action_class, arguments: {})
  deprecated_dsl_api __method__, "actions"
end

.authorizationObject



107
108
109
# File 'lib/avo/base_resource.rb', line 107

def authorization
  Avo::Services::AuthorizationService.new Avo::Current.user, model_class, policy_class: authorization_policy
end

.class_nameObject



161
162
163
# File 'lib/avo/base_resource.rb', line 161

def class_name
  to_s.demodulize
end

.fetch_search(key, record: nil) ⇒ Object



221
222
223
224
# File 'lib/avo/base_resource.rb', line 221

def fetch_search(key, record: nil)
  # self.class.fetch_search
  Avo::ExecutionContext.new(target: search[key], resource: self, record: record).handle
end

.filter(filter_class, arguments: {}) ⇒ Object



82
83
84
# File 'lib/avo/base_resource.rb', line 82

def filter(filter_class, arguments: {})
  deprecated_dsl_api __method__, "filters"
end

.find_record(id, query: nil, params: nil) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/avo/base_resource.rb', line 208

def find_record(id, query: nil, params: nil)
  Avo::ExecutionContext.new(
    target: find_record_method,
    query: query || find_scope, # If no record is given we'll use the default
    id: id,
    params: params
  ).handle
end

.find_scopeObject

This resolves the scope when finding records (not “where” queries)

It’s used to apply the authorization feature.



103
104
105
# File 'lib/avo/base_resource.rb', line 103

def find_scope
  authorization.apply_policy model_class
end

.get_available_modelsObject



128
129
130
# File 'lib/avo/base_resource.rb', line 128

def get_available_models
  ApplicationRecord.descendants
end

.get_model_by_name(model_name) ⇒ Object



132
133
134
135
136
# File 'lib/avo/base_resource.rb', line 132

def get_model_by_name(model_name)
  get_available_models.find do |m|
    m.to_s == model_name.to_s
  end
end

.get_record_associations(record) ⇒ Object



111
112
113
# File 'lib/avo/base_resource.rb', line 111

def get_record_associations(record)
  record._reflections
end

.model_class(record_class: nil) ⇒ Object

Returns the model class being used for this resource.

The Resource instance has a model_class method too so it can support the STI use cases where we figure out the model class from the record



142
143
144
145
146
147
148
149
150
151
# File 'lib/avo/base_resource.rb', line 142

def model_class(record_class: nil)
  # get the model class off of the static property
  return @model_class if @model_class.present?

  # get the model class off of the record for STI models
  return record_class if record_class.present?

  # generate a model class
  class_name.safe_constantize
end

.model_keyObject

This is used as the model class ID We use this instead of the route_key to maintain compatibility with uncountable models With uncountable models route key appends an _index suffix (Fish->fish_index) Example: User->users, MediaItem->media_items, Fish->fish



157
158
159
# File 'lib/avo/base_resource.rb', line 157

def model_key
  model_class.model_name.plural
end

.nameObject Also known as: singular_name



177
178
179
180
181
182
183
184
185
# File 'lib/avo/base_resource.rb', line 177

def name
  default = class_name.underscore.humanize

  if translation_key
    t(translation_key, count: 1, default: default).humanize
  else
    default
  end
end


204
205
206
# File 'lib/avo/base_resource.rb', line 204

def navigation_label
  plural_name.humanize
end

.plural_nameObject



188
189
190
191
192
193
194
195
196
# File 'lib/avo/base_resource.rb', line 188

def plural_name
  default = name.pluralize

  if translation_key
    t(translation_key, count: 2, default: default).humanize
  else
    default
  end
end

.query_scopeObject

This resolves the scope when doing “where” queries (not find queries)

It’s used to apply the authorization feature.



93
94
95
96
97
98
# File 'lib/avo/base_resource.rb', line 93

def query_scope
  authorization.apply_policy Avo::ExecutionContext.new(
    target: index_query,
    query: model_class
  ).handle
end

.route_keyObject



165
166
167
# File 'lib/avo/base_resource.rb', line 165

def route_key
  class_name.underscore.pluralize
end

.scope(scope_class) ⇒ Object



86
87
88
# File 'lib/avo/base_resource.rb', line 86

def scope(scope_class)
  deprecated_dsl_api __method__, "scopes"
end

.search_queryObject



217
218
219
# File 'lib/avo/base_resource.rb', line 217

def search_query
  search.dig(:query)
end

.singular_route_keyObject



169
170
171
# File 'lib/avo/base_resource.rb', line 169

def singular_route_key
  route_key.singularize
end

.translation_keyObject



173
174
175
# File 'lib/avo/base_resource.rb', line 173

def translation_key
  @translation_key || "avo.resource_translations.#{class_name.underscore}"
end

.underscore_nameObject



198
199
200
201
202
# File 'lib/avo/base_resource.rb', line 198

def underscore_name
  return @name if @name.present?

  name.demodulize.underscore
end

.valid_association_name(record, association_name) ⇒ Object



115
116
117
118
119
# File 'lib/avo/base_resource.rb', line 115

def valid_association_name(record, association_name)
  get_record_associations(record).keys.find do |name|
    name == association_name
  end
end

.valid_attachment_name(record, association_name) ⇒ Object



121
122
123
124
125
126
# File 'lib/avo/base_resource.rb', line 121

def valid_attachment_name(record, association_name)
  association_exists = get_record_associations(record).keys.any? do |name|
    name == "#{association_name}_attachment" || name == "#{association_name}_attachments"
  end
  return association_name if association_exists
end

Instance Method Details

#attachment_fieldsObject



377
378
379
380
381
# File 'lib/avo/base_resource.rb', line 377

def attachment_fields
  get_field_definitions.select do |field|
    [Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
  end
end

#authorization(user: nil) ⇒ Object



414
415
416
417
# File 'lib/avo/base_resource.rb', line 414

def authorization(user: nil)
  current_user = user || Avo::Current.user
  Avo::Services::AuthorizationService.new(current_user, record || model_class, policy_class: authorization_policy)
end

#available_view_typesObject



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/avo/base_resource.rb', line 358

def available_view_types
  if self.class.view_types.present?
    return Array(
      Avo::ExecutionContext.new(
        target: self.class.view_types,
        resource: self,
        record: record
      ).handle
    )
  end

  view_types = [:table]

  view_types << :grid if self.class.grid_view.present?
  view_types << :map if map_view.present?

  view_types
end

#avatarObject



507
508
509
510
511
512
513
514
515
# File 'lib/avo/base_resource.rb', line 507

def avatar
  return avatar_field.to_image if avatar_field.respond_to? :to_image

  return avatar_field.value.variant(resize_to_limit: [480, 480]) if avatar_field.type == "file"

  avatar_field.value
rescue
  nil
end

#avatar_fieldObject



499
500
501
502
503
504
505
# File 'lib/avo/base_resource.rb', line 499

def avatar_field
  get_field_definitions.find do |field|
    field.as_avatar.present?
  end
rescue
  nil
end

#avatar_typeObject



517
518
519
520
521
# File 'lib/avo/base_resource.rb', line 517

def avatar_type
  avatar_field.as_avatar
rescue
  nil
end

#cache_hash(parent_record) ⇒ Object



437
438
439
440
441
442
443
# File 'lib/avo/base_resource.rb', line 437

def cache_hash(parent_record)
  if parent_record.present?
    [record, file_hash, parent_record]
  else
    [record, file_hash]
  end
end

#current_userObject



16
17
18
# File 'lib/avo/base_resource.rb', line 16

def current_user
  Avo::Current.user
end

#default_panel_nameObject



320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/avo/base_resource.rb', line 320

def default_panel_name
  return @params[:related_name].capitalize if @params.present? && @params[:related_name].present?

  case @view
  when :show
    record_title
  when :edit
    record_title
  when :new
    t("avo.create_new_item", item: name.humanize(capitalize: false)).upcase_first
  end
end

#description_attributesObject



539
540
541
542
543
544
545
# File 'lib/avo/base_resource.rb', line 539

def description_attributes
  {
    view: view,
    resource: self,
    record: record
  }
end

#detect_fieldsObject



256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/avo/base_resource.rb', line 256

def detect_fields
  self.items_holder = Avo::Resources::Items::Holder.new

  # Used in testing to replace items
  if temporary_items.present?
    instance_eval(&temporary_items)
  else
    fields
  end

  self
end

#fieldsObject



269
270
271
# File 'lib/avo/base_resource.rb', line 269

def fields
  # blank fields method
end

#fields_by_database_idObject

Map the received params to their actual fields



384
385
386
387
388
389
390
391
392
393
# File 'lib/avo/base_resource.rb', line 384

def fields_by_database_id
  get_field_definitions
    .reject do |field|
      field.computed
    end
    .map do |field|
      [field.database_id.to_s, field]
    end
    .to_h
end

#file_hashObject



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/avo/base_resource.rb', line 419

def file_hash
  content_to_be_hashed = ""

  # resource file hash
  resource_path = Rails.root.join("app", "avo", "resources", "#{self.class.name.underscore}.rb").to_s
  if File.file? resource_path
    content_to_be_hashed += File.read(resource_path)
  end

  # policy file hash
  policy_path = Rails.root.join("app", "policies", "#{self.class.name.underscore.gsub("_resource", "")}_policy.rb").to_s
  if File.file? policy_path
    content_to_be_hashed += File.read(policy_path)
  end

  Digest::MD5.hexdigest(content_to_be_hashed)
end

#fill_record(record, params, extra_params: []) ⇒ Object



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/avo/base_resource.rb', line 395

def fill_record(record, params, extra_params: [])
  # Write the field values
  params.each do |key, value|
    field = fields_by_database_id[key]

    next unless field.present?

    record = field.fill_field record, key, value, params
  end

  # Write the user configured extra params to the record
  if extra_params.present?
    # Let Rails fill in the rest of the params
    record.assign_attributes params.permit(extra_params)
  end

  record
end

#form_scopeObject



523
524
525
# File 'lib/avo/base_resource.rb', line 523

def form_scope
  model_class.base_class.to_s.underscore.downcase
end

#has_record_id?Boolean

Returns:

  • (Boolean)


527
528
529
# File 'lib/avo/base_resource.rb', line 527

def has_record_id?
  record.present? && record_id.present?
end

#hydrate(record: nil, view: nil, user: nil, params: nil) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/avo/base_resource.rb', line 306

def hydrate(record: nil, view: nil, user: nil, params: nil)
  @view = Avo::ViewInquirer.new(view) if view.present?
  @user = user if user.present?
  @params = params if params.present?

  if record.present?
    @record = record

    hydrate_model_with_default_values if @view&.new?
  end

  self
end

#hydrate_model_with_default_valuesObject

We will not overwrite any attributes that come pre-filled in the record.



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/avo/base_resource.rb', line 446

def hydrate_model_with_default_values
  default_values = get_fields
    .select do |field|
      !field.computed && !field.is_a?(Avo::Fields::HeadingField)
    end
    .map do |field|
      value = field.value

      if field.type == "belongs_to"

        reflection = @record._reflections[@params[:via_relation]]

        if field.polymorphic_as.present? && field.types.map(&:to_s).include?(@params[:via_relation_class])
          # set the value to the actual record
          via_resource = Avo.resource_manager.get_resource_by_model_class(@params[:via_relation_class])
          value = via_resource.find_record(@params[:via_record_id])
        elsif reflection.present? && reflection.foreign_key.present? && field.id.to_s == @params[:via_relation].to_s
          resource = Avo.resource_manager.get_resource_by_model_class params[:via_relation_class]
          record = resource.find_record @params[:via_record_id], params: params
          id_param = reflection.options[:primary_key] || :id

          value = record.send(id_param)
        end
      end

      [field, value]
    end
    .to_h
    .select do |_, value|
      value.present?
    end

  default_values.each do |field, value|
    field.assign_value record: @record, value: value
  end
end

#id_attributeObject



531
532
533
# File 'lib/avo/base_resource.rb', line 531

def id_attribute
  :id
end

#model_classObject

Returns the model class being used for this resource.

We use the class method as a fallback but we pass it the record too so it can support the STI use cases where we figure out the model class from that record.



337
338
339
340
341
# File 'lib/avo/base_resource.rb', line 337

def model_class
  record_class = @record&.class

  self.class.model_class record_class: record_class
end

#model_nameObject



483
484
485
# File 'lib/avo/base_resource.rb', line 483

def model_name
  model_class.model_name
end

#record_idObject



535
536
537
# File 'lib/avo/base_resource.rb', line 535

def record_id
  record.send(id_attribute)
end

#record_pathObject



491
492
493
# File 'lib/avo/base_resource.rb', line 491

def record_path
  resource_path(record: record, resource: self)
end

#record_titleObject



343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/avo/base_resource.rb', line 343

def record_title
  return name if @record.nil?

  # Get the title from the record if title is not set, try to get the name, title or label, or fallback to the id
  return @record.try(:name) || @record.try(:title) || @record.try(:label) || @record.id  if title.nil?

  # If the title is a symbol, get the value from the record else execute the block/string
  case title
  when Symbol
    @record.send title
  when Proc
    Avo::ExecutionContext.new(target: title, resource: self, record: @record).handle
  end
end

#records_pathObject



495
496
497
# File 'lib/avo/base_resource.rb', line 495

def records_path
  resources_path(resource: self)
end

#singular_model_keyObject



487
488
489
# File 'lib/avo/base_resource.rb', line 487

def singular_model_key
  model_class.model_name.singular
end