lib/avo/base_resource.rb in avo-3.10.6 vs lib/avo/base_resource.rb in avo-3.10.7
- old
+ new
@@ -1,604 +1,5 @@
module Avo
- class BaseResource
- extend ActiveSupport::DescendantsTracker
-
- include ActionView::Helpers::UrlHelper
- include Avo::Concerns::HasItems
- include Avo::Concerns::CanReplaceItems
- include Avo::Concerns::HasControls
- include Avo::Concerns::HasResourceStimulusControllers
- include Avo::Concerns::ModelClassConstantized
- include Avo::Concerns::HasDescription
- include Avo::Concerns::HasCoverPhoto
- include Avo::Concerns::HasProfilePhoto
- include Avo::Concerns::HasHelpers
- include Avo::Concerns::Hydration
- include Avo::Concerns::Pagination
-
- # Avo::Current methods
- delegate :context, to: Avo::Current
- def current_user
- Avo::Current.user
- end
- delegate :params, to: Avo::Current
- delegate :request, to: Avo::Current
- delegate :view_context, to: Avo::Current
-
- # view_context methods
- delegate :simple_format, :content_tag, to: :view_context
- delegate :main_app, to: :view_context
- delegate :avo, to: :view_context
- delegate :resource_path, to: :view_context
- delegate :resources_path, to: :view_context
-
- # I18n methods
- delegate :t, to: ::I18n
-
- # class methods
- delegate :class_name, to: :class
- delegate :route_key, to: :class
- delegate :singular_route_key, to: :class
-
- attr_accessor :view
- attr_accessor :reflection
- attr_accessor :user
- attr_accessor :record
-
- class_attribute :id, default: :id
- class_attribute :title
- class_attribute :search, default: {}
- class_attribute :includes, default: []
- class_attribute :attachments, default: []
- class_attribute :single_includes, default: []
- class_attribute :single_attachments, default: []
- class_attribute :authorization_policy
- class_attribute :translation_key
- class_attribute :default_view_type, default: :table
- class_attribute :devise_password_optional, default: false
- class_attribute :scopes_loader
- class_attribute :filters_loader
- class_attribute :view_types
- class_attribute :grid_view
- class_attribute :visible_on_sidebar, default: true
- class_attribute :index_query, default: -> {
- query
- }
- class_attribute :find_record_method, default: -> {
- query.find id
- }
- class_attribute :after_create_path, default: :show
- class_attribute :after_update_path, default: :show
- class_attribute :record_selector, default: true
- class_attribute :keep_filters_panel_open, default: false
- class_attribute :extra_params
- class_attribute :link_to_child_resource, default: false
- class_attribute :map_view
- class_attribute :components, default: {}
-
- # EXTRACT:
- class_attribute :ordering
-
- class << self
- delegate :t, to: ::I18n
- delegate :context, to: ::Avo::Current
-
- def action(action_class, arguments: {})
- deprecated_dsl_api __method__, "actions"
- end
-
- def filter(filter_class, arguments: {})
- deprecated_dsl_api __method__, "filters"
- end
-
- def scope(scope_class)
- deprecated_dsl_api __method__, "scopes"
- end
-
- # This resolves the scope when doing "where" queries (not find queries)
- #
- # It's used to apply the authorization feature.
- def query_scope
- authorization.apply_policy Avo::ExecutionContext.new(
- target: index_query,
- query: model_class
- ).handle
- end
-
- # This resolves the scope when finding records (not "where" queries)
- #
- # It's used to apply the authorization feature.
- def find_scope
- authorization.apply_policy model_class
- end
-
- def authorization
- Avo::Services::AuthorizationService.new Avo::Current.user, model_class, policy_class: authorization_policy
- end
-
- def valid_association_name(record, association_name)
- association_name if record._reflections.with_indifferent_access[association_name].present?
- end
-
- def valid_attachment_name(record, association_name)
- association_name if record.class.reflect_on_attachment(association_name).present?
- end
-
- def get_available_models
- ApplicationRecord.descendants
- end
-
- def get_model_by_name(model_name)
- get_available_models.find do |m|
- m.to_s == model_name.to_s
- end
- end
-
- # 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
- 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
-
- # 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
- def model_key
- @model_key ||= model_class.model_name.plural
- end
-
- def class_name
- @class_name ||= to_s.demodulize
- end
-
- def route_key
- class_name.underscore.pluralize
- end
-
- def singular_route_key
- route_key.singularize
- end
-
- def translation_key
- @translation_key || "avo.resource_translations.#{class_name.underscore}"
- end
-
- def name
- @name ||= name_from_translation_key(count: 1, default: class_name.underscore.humanize)
- end
- alias_method :singular_name, :name
-
- def plural_name
- name_from_translation_key(count: 2, default: name.pluralize)
- end
-
- # Get the name from the translation_key and fallback to default
- # It can raise I18n::InvalidPluralizationData when using only resource_translation without pluralization keys like: one, two or other key
- # Example:
- # ---
- # en:
- # avo:
- # resource_translations:
- # product:
- # save: "Save product"
- def name_from_translation_key(count:, default:)
- t(translation_key, count:, default:).humanize
- rescue I18n::InvalidPluralizationData
- default
- end
-
- def underscore_name
- return @name if @name.present?
-
- name.demodulize.underscore
- end
-
- def navigation_label
- plural_name.humanize
- end
-
- 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 search_query
- search.dig(:query)
- end
-
- def search_results_count
- search.dig(:results_count)
- end
-
- def fetch_search(key, record: nil)
- # self.class.fetch_search
- Avo::ExecutionContext.new(target: search[key], resource: self, record: record).handle
- end
- end
-
- delegate :context, to: ::Avo::Current
- delegate :name, to: :class
- delegate :singular_name, to: :class
- delegate :plural_name, to: :class
- delegate :underscore_name, to: :class
- delegate :to_param, to: :class
- delegate :find_record, to: :class
- delegate :model_key, to: :class
- delegate :tab, to: :items_holder
-
- 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 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
-
- VIEW_METHODS_MAPPING = {
- index: [:index_fields, :display_fields],
- show: [:show_fields, :display_fields],
- edit: [:edit_fields, :form_fields],
- update: [:edit_fields, :form_fields],
- new: [:new_fields, :form_fields],
- create: [:new_fields, :form_fields]
- } unless defined? VIEW_METHODS_MAPPING
-
- def fetch_fields
- possible_methods_for_view = VIEW_METHODS_MAPPING[view.to_sym]
-
- # Safe navigation operator is used because the view can be "destroy" or "preview"
- 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_cards
- cards
- end
-
- def divider(label = nil)
- entity_loader(:action).use({class: Divider, label: label}.compact)
- end
-
- # def fields / def cards
- [:fields, :cards].each do |method_name|
- define_method method_name do
- # Empty method
- end
- end
-
- [:action, :filter, :scope].each do |entity|
- plural_entity = entity.to_s.pluralize
-
- # def actions / def filters / def scopes
- define_method plural_entity do
- # blank entity method
- end
-
- # def action / def filter / def scope
- define_method entity do |entity_class, arguments: {}, icon: nil|
- entity_loader(entity).use({class: entity_class, arguments: arguments, icon: icon}.compact)
- end
-
- # def get_actions / def get_filters / def get_scopes
- define_method "get_#{plural_entity}" do
- return entity_loader(entity).bag if entity_loader(entity).present?
-
- # ex: @actions_loader = Avo::Loaders::ActionsLoader.new
- instance_variable_set(
- "@#{plural_entity}_loader",
- "Avo::Loaders::#{plural_entity.humanize}Loader".constantize.new
- )
-
- send plural_entity
-
- entity_loader(entity).bag
- end
-
- # def get_action_arguments / def get_filter_arguments / def get_scope_arguments
- define_method "get_#{entity}_arguments" do |entity_class|
- klass = send("get_#{plural_entity}").find { |entity| entity[:class].to_s == entity_class.to_s }
-
- raise "Couldn't find '#{entity_class}' in the 'def #{plural_entity}' method on your '#{self.class}' resource." if klass.nil?
-
- klass[:arguments]
- end
- end
-
- def hydrate(...)
- super(...)
-
- if @record.present?
- hydrate_model_with_default_values if @view&.new?
- end
-
- self
- end
-
- 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
-
- # 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.
- def model_class
- record_class = @record&.class
-
- self.class.model_class record_class: record_class
- end
-
- 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
-
- 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
-
- def attachment_fields
- get_field_definitions.select do |field|
- [Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
- end
- end
-
- # Map the received params to their actual fields
- 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
-
- 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
-
- 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 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
- @file_name ||= self.class.underscore_name.tr(" ", "_")
- end
-
- def cache_hash(parent_record)
- result = [record, file_hash]
-
- if parent_record.present?
- result << parent_record
- end
-
- result
- end
-
- # 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._reflections.with_indifferent_access[@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
-
- def model_name
- model_class.model_name
- end
-
- def singular_model_key
- model_class.model_name.singular
- end
-
- def record_path
- resource_path(record: record, resource: self)
- end
-
- def records_path
- resources_path(resource: self)
- end
-
- def avatar_field
- get_field_definitions.find do |field|
- field.as_avatar.present?
- end
- rescue
- nil
- end
-
- 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
-
- def avatar_type
- avatar_field.as_avatar
- rescue
- nil
- end
-
- def form_scope
- model_class.base_class.to_s.underscore.downcase
- end
-
- def has_record_id?
- record.present? && record_id.present?
- end
-
- def id_attribute
- :id
- end
-
- def record_id
- record.send(id_attribute)
- end
-
- def description_attributes
- {
- view: view,
- resource: self,
- record: record
- }
- end
-
- def entity_loader(entity)
- instance_variable_get("@#{entity.to_s.pluralize}_loader")
- end
+ class BaseResource < Avo::Resources::Base
+ # Users can override this class to add custom methods for all resources.
end
end