lib/avo/fields/base_field.rb
module Avo
module Fields
class BaseField
extend ActiveSupport::DescendantsTracker
prepend Avo::Concerns::HasItemType
prepend Avo::Concerns::IsResourceItem
include Avo::Concerns::IsVisible
include Avo::Concerns::VisibleInDifferentViews
include Avo::Concerns::HasHelpers
include Avo::Fields::Concerns::HasFieldName
include Avo::Fields::Concerns::HasDefault
include Avo::Fields::Concerns::HasHTMLAttributes
include Avo::Fields::Concerns::HandlesFieldArgs
include Avo::Fields::Concerns::IsReadonly
include Avo::Fields::Concerns::IsDisabled
include Avo::Fields::Concerns::IsRequired
include Avo::Fields::Concerns::UseViewComponents
include ActionView::Helpers::UrlHelper
delegate :app, to: ::Avo::Current
delegate :view_context, to: :app
delegate :context, to: :app
delegate :simple_format, :content_tag, to: :view_context
delegate :main_app, to: :view_context
delegate :avo, to: :view_context
delegate :t, to: ::I18n
attr_reader :id
attr_reader :block
attr_reader :required
attr_reader :readonly
attr_reader :sortable
attr_reader :summarizable
attr_reader :nullable
attr_reader :null_values
attr_reader :format_using
attr_reader :autocomplete
attr_reader :help
attr_reader :default
attr_reader :as_avatar
attr_reader :stacked
attr_reader :for_presentation_only
attr_reader :for_attribute
# Private options
attr_reader :computable # if allowed to be computable
attr_reader :computed # if block is present
attr_reader :computed_value # the value after computation
# Hydrated payload
attr_accessor :record
attr_accessor :action
attr_accessor :user
attr_accessor :panel_name
class_attribute :field_name_attribute
def initialize(id, **args, &block)
@id = id
@name = args[:name]
@translation_key = args[:translation_key]
@block = block
@required = args.dig(:required) # Value if :required present on args, nil otherwise
@readonly = args[:readonly] || false
@disabled = args[:disabled] || false
@sortable = args[:sortable] || false
@summarizable = args[:summarizable] || false
@nullable = args[:nullable] || false
@null_values = args[:null_values] || [nil, ""]
@format_using = args[:format_using] || nil
@update_using = args[:update_using] || nil
@placeholder = args[:placeholder]
@autocomplete = args[:autocomplete] || nil
@help = args[:help] || nil
@default = args[:default] || nil
@visible = args[:visible]
@as_avatar = args[:as_avatar] || false
@html = args[:html] || nil
@view = Avo::ViewInquirer.new(args[:view]) || nil
@value = args[:value] || nil
@stacked = args[:stacked] || nil
@for_presentation_only = args[:for_presentation_only] || false
@resource = args[:resource]
@action = args[:action]
@components = args[:components] || {}
@for_attribute = args[:for_attribute]
@args = args
@computable = true
@computed = block.present?
@computed_value = nil
post_initialize if respond_to?(:post_initialize)
end
def translation_key
@translation_key || "avo.field_translations.#{@id}"
end
def translated_name(default:)
t(translation_key, count: 1, default: default).humanize
end
def translated_plural_name(default:)
t(translation_key, count: 2, default: default).humanize
end
# Getting the name of the resource (user/users, post/posts)
# We'll first check to see if the user passed a name
# Secondly we'll try to find a translation key
# We'll fallback to humanizing the id
def name
return @name if custom_name?
if translation_key
translated_name default: default_name
else
default_name
end
end
def plural_name
default = name.pluralize
if translation_key
translated_plural_name default: default
else
default
end
end
def table_header_label
@table_header_label ||= name
end
def custom_name?
!@name.nil?
end
def default_name
@id.to_s.humanize(keep_id_suffix: true)
end
def placeholder
Avo::ExecutionContext.new(target: @placeholder || name, record: record, resource: @resource, view: @view).handle
end
def value(property = nil)
return @value if @value.present?
property ||= @for_attribute || id
# Get record value
final_value = record.send(property) if is_model?(record) && record.respond_to?(property)
# On new views and actions modals we need to prefill the fields with the default value if value is nil
if final_value.nil? && should_fill_with_default_value? && default.present?
final_value = computed_default_value
end
# Run computable callback block if present
if computable && block.present?
final_value = execute_block
end
# Run the value through resolver if present
if format_using.present?
final_value = Avo::ExecutionContext.new(
target: format_using,
value: final_value,
record: record,
resource: resource,
view: view,
field: self,
include: self.class.included_modules
).handle
end
final_value
end
def execute_block
Avo::ExecutionContext.new(
target: block,
record: record,
resource: resource,
view: view,
field: self,
include: self.class.included_modules
).handle
end
# Fills the record with the received value on create and update actions.
def fill_field(record, key, value, params)
key = @for_attribute.to_s if @for_attribute.present?
return record unless has_attribute?(record, key)
if @update_using.present?
value = Avo::ExecutionContext.new(
target: @update_using,
record: record,
key: key,
value: value,
resource: resource,
field: self,
include: self.class.included_modules
).handle
end
record.public_send(:"#{key}=", value)
record
end
def has_attribute?(record, attribute)
record.methods.include? attribute.to_sym
end
# Try to see if the field has a different database ID than it's name
def database_id
foreign_key
rescue
id
end
def has_own_panel?
false
end
def resolve_attribute(value)
value
end
def to_permitted_param
id.to_sym
end
def record_errors
record.nil? ? {} : record.errors
end
def type
self.class.name.demodulize.to_s.underscore.gsub("_field", "")
end
def custom?
!method(:initialize).source_location.first.include?("lib/avo/field")
rescue
true
end
def visible_in_reflection?
true
end
def hidden_in_reflection?
!visible_in_reflection?
end
def options_for_filter
options
end
def updatable
!is_disabled? && visible?
end
# Used by Avo to fill the record with the default value on :new and :edit views
def assign_value(record:, value:)
id = (type == "belongs_to") ? foreign_key : database_id
if record.send(id).nil?
record.send(:"#{id}=", value)
end
end
private
def model_or_class(model)
model.instance_of?(String) ? "class" : "model"
end
def is_model?(model)
model_or_class(model) == "model"
end
def should_fill_with_default_value?
on_create? || in_action?
end
def on_create?
@view.in?(%w[new create])
end
def in_action?
@action.present?
end
def get_resource_by_model_class(model_class)
resource = Avo.resource_manager.get_resource_by_model_class(model_class)
resource || (raise Avo::MissingResourceError.new(model_class))
end
end
end
end