class Avo::Resources::Base

def action(action_class, arguments: {})

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

def attachment_fields

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

def authorization

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

def authorization(user: nil)

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

def available_view_types

def available_view_types
  @available_view_types ||= begin
    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
end

def cache_hash(parent_record)

def cache_hash(parent_record)
  result = [record, file_hash]
  if parent_record.present?
    result << parent_record
  end
  result
end

def class_name

def class_name
  @class_name ||= to_s.demodulize
end

def current_user

def current_user
  Avo::Current.user
end

def custom_components

def custom_components
  @custom_components ||= Avo::ExecutionContext.new(
    target: components,
    resource: self,
    record: @record,
    view: @view
  ).handle.with_indifferent_access
end

def default_panel_name

def default_panel_name
  return @params[:related_name].capitalize if @params.present? && @params[:related_name].present?
  case @view.to_sym
  when :show
    record_title
  when :edit
    record_title
  when :new
    t("avo.create_new_item", item: name.humanize(capitalize: false)).upcase_first
  end
end

def description_attributes

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

def detect_fields

def detect_fields
  self.items_holder = Avo::Resources::Items::Holder.new(parent: self)
  # Used in testing to replace items
  if temporary_items.present?
    instance_eval(&temporary_items)
  else
    fetch_fields
  end
  self
end

def divider(**kwargs)

def divider(**kwargs)
  entity_loader(:action).use({class: Divider, **kwargs}.compact)
end

def entity_loader(entity)

def entity_loader(entity)
  instance_variable_get(:"@#{entity.to_s.pluralize}_loader")
end

def fetch_cards

def fetch_cards
  cards
end

def fetch_fields

def fetch_fields
  if view.preview?
    [:fields, :index_fields, :show_fields, :display_fields].each do |fields_method|
      send(fields_method) if respond_to?(fields_method)
    end
    return
  end
  possible_methods_for_view = VIEW_METHODS_MAPPING[view.to_sym]
  # Safe navigation operator is used because the view can be "destroy"
  possible_methods_for_view&.each do |method_for_view|
    return send(method_for_view) if respond_to?(method_for_view)
  end
  fields
end

def fetch_record_title

def fetch_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 to_param
  return @record.try(:name) || @record.try(:title) || @record.try(:label) || @record.to_param 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

def fetch_search(key, record: nil)

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

def fields_by_database_id(resource: self)

'resource' argument is used on avo-advanced, don't remove
Map the received params to their actual fields
def fields_by_database_id(resource: self)
  resource.get_field_definitions
    .reject do |field|
      field.computed
    end
    .map do |field|
      [field.database_id.to_s, field]
    end
    .to_h
end

def file_hash

def file_hash
  content_to_be_hashed = ""
  resource_path = Rails.root.join("app", "avo", "resources", "#{file_name}.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", "#{file_name.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

def file_name

def file_name
  @file_name ||= self.class.underscore_name.tr(" ", "_")
end

def fill_record(record, permitted_params, extra_params: [], fields: nil)

def fill_record(record, permitted_params, extra_params: [], fields: nil)
  # Write the field values
  permitted_params.each do |key, value|
    field = if fields.present?
      fields.find { |f| f.id == key.to_sym }
    else
      fields_by_database_id[key]
    end
    next unless field.present?
    record = field.fill_field record, key, value, permitted_params
  end
  # Write the user configured extra params to the record
  if extra_params.present?
    # Pick only the extra params
    # params at this point are already permitted, only need the keys to access them
    extra_attributes = permitted_params.slice(*flatten_keys(extra_params))
    # Let Rails fill in the rest of the params
    record.assign_attributes extra_attributes
  end
  safe_call(:fill_nested_records, record:, permitted_params:) || record
end

def filter(filter_class, arguments: {})

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

def find_record(id, query: nil, params: nil)

def find_record(id, query: nil, params: nil)
  query ||= find_scope # If no record is given we'll use the default
  if single_includes.present?
    query = query.includes(*single_includes)
  end
  if single_attachments.present?
    single_attachments.each do |attachment|
      query = query.send(:"with_attached_#{attachment}")
    end
  end
  Avo::ExecutionContext.new(
    target: find_record_method,
    query: query,
    id: id,
    params: params
  ).handle
end

def find_scope

It's used to apply the authorization feature.

This resolves the scope when finding records (not "where" queries)
def find_scope
  authorization.apply_policy model_class
end

def flatten_keys(array)

def flatten_keys(array)
  # [:fish_type, information: [:name, :history], reviews_attributes: [:body, :user_id]]
  # becomes
  # [:fish_type, :information, :reviews_attributes]
  array.flat_map do |item|
    case item
    when Hash
      item.keys
    else
      item
    end
  end
end

def form_scope

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

def get_available_models

def get_available_models
  ApplicationRecord.descendants
end

def get_external_link

def get_external_link
  return unless record.persisted?
  Avo::ExecutionContext.new(target: external_link, resource: self, record: record).handle
end

def get_model_by_name(model_name)

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

def has_record_id?

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

def hydrate(...)

def hydrate(...)
  super
  if @record.present?
    hydrate_model_with_default_values if @view&.new?
  end
  self
end

def hydrate_model_with_default_values

We will not overwrite any attributes that come pre-filled in the record.
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.class.reflect_on_association(@params[:via_relation]) if @params[:via_relation].present?
        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

def id_attribute

def id_attribute
  :id
end

def initialize(record: nil, view: nil, user: nil, params: nil)

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
  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

def model_class(record_class: nil)

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

Returns the model class being used for this resource.
def model_class(record_class: nil)
  # get the model class off of the static property
  return constantized_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

def model_class

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

Returns the model class being used for this resource.
def model_class
  record_class = @record&.class
  self.class.model_class record_class: record_class
end

def model_key

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

def model_name

def model_name
  model_class.model_name
end

def name

def name
  name_from_translation_key(count: 1, default: class_name.underscore.humanize)
end

def name_from_translation_key(count:, default:)

save: "Save product"
product:
resource_translations:
avo:
en:
---
Example:
It can raise I18n::InvalidPluralizationData when using only resource_translation without pluralization keys like: one, two or other key
Get the name from the translation_key and fallback to default
def name_from_translation_key(count:, default:)
  t(translation_key, count:, default:).humanize
rescue I18n::InvalidPluralizationData
  default
end

def navigation_label

def navigation_label
  plural_name.humanize
end

def plural_name

def plural_name
  name_from_translation_key(count: 2, default: name.pluralize)
end

def query_scope

It's used to apply the authorization feature.

This resolves the scope when doing "where" queries (not find queries)
def query_scope
  authorization.apply_policy Avo::ExecutionContext.new(
    target: index_query,
    query: model_class
  ).handle
end

def record_id

def record_id
  record.send(id_attribute)
end

def record_param

def record_param
  @record_param ||= @record.persisted? ? @record.to_param : nil
end

def record_path

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

def record_title

def record_title
  fetch_record_title.to_s
end

def records_path

def records_path
  resources_path(resource: self)
end

def resolve_component(original_component)

def resolve_component(original_component)
  custom_components.dig(original_component.to_s)&.to_s&.safe_constantize || original_component
end

def resource_type_array? = false

def resource_type_array? = false

def route_key

def route_key
  class_name.underscore.pluralize
end

def scope(scope_class)

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

def search_query

def search_query
  search.dig(:query)
end

def search_results_count

def search_results_count
  search.dig(:results_count)
end

def singular_model_key

def singular_model_key
  model_class.model_name.singular
end

def singular_route_key

def singular_route_key
  route_key.singularize
end

def sort_by_param

def sort_by_param
  available_columns = model_class.column_names
  if available_columns.include?(default_sort_column.to_s)
    default_sort_column
  elsif available_columns.include?("created_at")
    :created_at
  end
end

def sorting_supported? = true

def sorting_supported? = true

def translation_key

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

def underscore_name

def underscore_name
  name.demodulize.underscore
end

def valid_association_name(record, association_name)

def valid_association_name(record, association_name)
  association_name if record.class.reflect_on_association(association_name).present?
end

def valid_attachment_name(record, association_name)

def valid_attachment_name(record, association_name)
  association_name if record.class.reflect_on_attachment(association_name).present?
end

def view_type

def view_type
  @view_type ||= if @params[:view_type].present?
    @params[:view_type]
  elsif available_view_types.size == 1
    available_view_types.first
  else
    Avo::ExecutionContext.new(
      target: default_view_type || Avo.configuration.default_view_type,
      resource: self,
      view: @view
    ).handle
  end
end