module Avo class BaseAction include Avo::Concerns::HasItems class_attribute :name, default: nil class_attribute :message class_attribute :confirm_button_label class_attribute :cancel_button_label class_attribute :no_confirmation, default: false class_attribute :record class_attribute :view class_attribute :user class_attribute :resource class_attribute :standalone, default: false class_attribute :visible class_attribute :may_download_file, default: false class_attribute :turbo attr_accessor :response attr_accessor :record attr_accessor :resource attr_accessor :user attr_reader :arguments delegate :view, to: :class # TODO: find a differnet way to delegate this to the uninitialized Current variable delegate :context, to: Avo::Current def current_user Avo::Current.user end delegate :params, to: Avo::Current delegate :view_context, to: Avo::Current delegate :avo, to: :view_context delegate :main_app, to: :view_context class << self delegate :context, to: ::Avo::Current def form_data_attributes # We can't respond with a file download from Turbo se we disable it on the form if may_download_file {turbo: turbo || false, remote: false, action_target: :form} else {turbo: turbo, turbo_frame: :_top, action_target: :form}.compact end end # We can't respond with a file download from Turbo se we disable close the modal manually after a while (it's a hack, we know) def submit_button_data_attributes if may_download_file {action: "click->modal#delayedClose"} else {} end end end def action_name return name if name.present? self.class.to_s.demodulize.underscore.humanize(keep_id_suffix: true) end def initialize(record: nil, resource: nil, user: nil, view: nil, arguments: {}) self.class.record = record self.class.resource = resource self.class.user = user self.class.view = view @arguments = arguments self.class.message ||= I18n.t("avo.are_you_sure_you_want_to_run_this_option") self.class.confirm_button_label ||= I18n.t("avo.run") self.class.cancel_button_label ||= I18n.t("avo.cancel") self.items_holder = Avo::Resources::Items::Holder.new fields @response ||= {} @response[:messages] = [] end # Blank method def fields end def get_message Avo::ExecutionContext.new(target: self.class.message, record: self.class.record, resource: self.class.resource).handle end def get_attributes_for_action get_fields.map do |field| value = field.value || Avo::ExecutionContext.new( target: field.default, record: self.class.record, resource: self.class.resource, view: view ).handle [field.id, value] end.to_h end def handle_action(**args) records, fields, current_user, resource = args.values_at(:records, :fields, :current_user, :resource) # Fetching the field definitions and not the actual fields (get_fields) because they will break if the user uses a `visible` block and adds a condition using the `params` variable. The params are different in the show method and the handle method. action_fields = get_field_definitions.map { |field| [field.id, field] }.to_h # For some fields, like belongs_to, the id and database_id differ (user vs user_id). # That's why we need to fetch the database_id for when we process the action. action_fields_by_database_id = action_fields.map do |id, value| [value.database_id.to_sym, value] end.to_h if fields.present? processed_fields = fields.to_unsafe_h.map do |name, value| field = action_fields_by_database_id[name.to_sym] next if field.blank? [name, field.resolve_attribute(value)] end processed_fields = processed_fields.reject(&:blank?).to_h else processed_fields = {} end args = { fields: processed_fields.with_indifferent_access, current_user: current_user, resource: resource } args[:records] = records handle(**args) self end def visible_in_view(parent_resource: nil) if visible.blank? # Hide on the :new view by default return false if view == :new # Show on all other views return true end # Run the visible block if available Avo::ExecutionContext.new( target: visible, params: params, parent_resource: parent_resource, resource: self.class.resource, view: self.class.view, arguments: arguments ).handle end def param_id self.class.to_s end def succeed(text) add_message text, :success self end def error(text) add_message text, :error self end def inform(text) add_message text, :info self end def warn(text) add_message text, :warning self end def keep_modal_open response[:keep_modal_open] = true self end # Add a placeholder silent message from when a user wants to do a redirect action or something similar def silent add_message nil, :silent self end def redirect_to(path = nil, allow_other_host: nil, status: nil, &block) response[:type] = :redirect response[:allow_other_host] = allow_other_host response[:status] = status response[:path] = if block.present? block else path end self end def reload response[:type] = :reload self end def download(path, filename) response[:type] = :download response[:path] = path response[:filename] = filename self end # We're overriding this method to hydrate with the proper resource attribute. def hydrate_fields fields.map do |field| field.hydrate(record: @record, view: @view, resource: resource) end self end private def add_message(body, type = :info) response[:messages] << { type: type, body: body } end end end