lib/avo/base_resource.rb in avo-3.0.0.beta1 vs lib/avo/base_resource.rb in avo-3.0.0.pre1
- old
+ new
@@ -6,106 +6,125 @@
include Avo::Concerns::HasItems
include Avo::Concerns::CanReplaceItems
include Avo::Concerns::HasControls
include Avo::Concerns::HasStimulusControllers
include Avo::Concerns::ModelClassConstantized
- include Avo::Concerns::HasDescription
- # Avo::Current methods
delegate :context, to: Avo::Current
- def curent_user
- Avo::Current.user
- end
+ delegate :current_user, to: Avo::Current
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 :params
attr_accessor :record
class_attribute :id, default: :id
- class_attribute :title
- class_attribute :search, default: {}
+ class_attribute :title, default: :id
+ class_attribute :description, default: :id
+ class_attribute :search_query, default: nil
+ class_attribute :search_query_help, default: ""
class_attribute :includes, 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 :actions_loader
class_attribute :scopes_loader
class_attribute :filters_loader
- class_attribute :view_types
- class_attribute :grid_view
+ class_attribute :grid_loader
class_attribute :visible_on_sidebar, default: true
- class_attribute :index_query, default: -> {
- query
+ class_attribute :unscoped_queries_on_index, default: false
+ class_attribute :resolve_index_query
+ class_attribute :resolve_find_scope
+ # TODO: refactor this into a Host without args
+ class_attribute :find_record_method, default: ->(model_class:, id:, params:) {
+ model_class.find id
}
- class_attribute :find_record_method, default: -> {
- query.find id
- }
+ class_attribute :hide_from_global_search, default: false
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
# EXTRACT:
class_attribute :ordering
class << self
delegate :t, to: ::I18n
delegate :context, to: ::Avo::Current
+ def grid(&block)
+ grid_collector = GridCollector.new
+ grid_collector.instance_eval(&block)
+
+ self.grid_loader = grid_collector
+ end
+
def action(action_class, arguments: {})
- deprecated_dsl_api __method__, "actions"
+ self.actions_loader ||= Avo::Loaders::Loader.new
+
+ action = {class: action_class, arguments: arguments}
+ self.actions_loader.use action
end
def filter(filter_class, arguments: {})
- deprecated_dsl_api __method__, "filters"
+ self.filters_loader ||= Avo::Loaders::Loader.new
+
+ filter = { class: filter_class , arguments: arguments }
+ self.filters_loader.use filter
end
- def scope(scope_class)
- deprecated_dsl_api __method__, "scopes"
+ # This is the search_query scope
+ # This should be removed and passed to the search block
+ def scope
+ query_scope
end
+ def scopes(*args)
+ self.scopes_loader ||= Avo::Loaders::Loader.new
+
+ args.each do |scope_class|
+ self.scopes_loader.use scope_class
+ end
+ 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
+ final_scope = if resolve_index_query.present?
+ Avo::ExecutionContext.new(target: resolve_index_query, model_class: model_class).handle
+ else
+ model_class
+ end
+
+ authorization.apply_policy final_scope
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
+ final_scope = if resolve_find_scope.present?
+ Avo::ExecutionContext.new(target: resolve_find_scope, model_class: model_class).handle
+ else
+ model_class
+ end
+
+ authorization.apply_policy final_scope
end
def authorization
- Avo::Services::AuthorizationService.new Avo::Current.user, model_class, policy_class: authorization_policy
+ Avo::Services::AuthorizationService.new Avo::Current.current_user, model_class, policy_class: authorization_policy
end
def get_record_associations(record)
record._reflections
end
@@ -130,187 +149,70 @@
def get_model_by_name(model_name)
get_available_models.find do |m|
m.to_s == model_name.to_s
end
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?
+ delegate :context, to: ::Avo::Current
- # 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_class.model_name.plural
- end
-
- def 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
- default = class_name.underscore.humanize
-
- if translation_key
- t(translation_key, count: 1, default: default).humanize
- else
- default
+ def initialize
+ 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
- alias_method :singular_name, :name
-
- def plural_name
- default = name.pluralize
-
- if translation_key
- t(translation_key, count: 2, default: default).humanize
- else
- default
- end
- 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)
- 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
-
- def search_query
- search.dig(:query)
- 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 :underscore_name, to: :class
- delegate :find_record, to: :class
- delegate :model_key, to: :class
-
- def initialize(record: nil, view: nil, user: nil, params: nil)
+ def hydrate(record: nil, view: nil, user: nil, params: nil)
@view = 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
+ self
end
- def detect_fields
- self.items_holder = Avo::Resources::Items::Holder.new
+ def get_grid_fields
+ return if self.class.grid_loader.blank?
- # Used in testing to replace items
- if temporary_items.present?
- instance_eval(&temporary_items)
- else
- fields
- end
-
- self
+ self.class.grid_loader.hydrate(record: @record, view: @view, resource: self)
end
- def fields
- # blank fields method
+ def get_filters
+ return [] if self.class.filters_loader.blank?
+
+ self.class.filters_loader.bag
end
- [:action, :filter, :scope].each do |entity|
- plural_entity = entity.to_s.pluralize
+ def get_filter_arguments(filter_class)
+ filter = get_filters.find { |filter| filter[:class] == filter_class.constantize }
- # def actions / def filters / def scopes
- define_method plural_entity do
- # blank entity method
- end
+ filter[:arguments]
+ end
- # def action / def filter / def scope
- define_method entity do |entity_class, arguments: {}|
- entity_loader(entity).use({class: entity_class, arguments: arguments})
- end
+ def get_actions
+ return [] if self.class.actions_loader.blank?
- # 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?
+ self.class.actions_loader.bag
+ end
- instance_variable_set("@#{plural_entity}_loader", Avo::Loaders::Loader.new)
- send plural_entity
+ def get_action_arguments(action_class)
+ action = get_actions.find { |action| action[:class].to_s == action_class.to_s }
- 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|
- send("get_#{plural_entity}").find { |entity| entity[:class].to_s == entity_class.to_s }[:arguments]
- end
+ action[:arguments]
end
- def hydrate(record: nil, view: nil, user: nil, params: nil)
- @view = view if view.present?
- @user = user if user.present?
- @params = params if params.present?
+ def get_scopes
+ return [] if self.class.scopes_loader.blank?
- if record.present?
- @record = record
-
- hydrate_model_with_default_values if @view == :new
- end
-
- self
+ self.class.scopes_loader.bag
end
def default_panel_name
return @params[:related_name].capitalize if @params.present? && @params[:related_name].present?
@@ -318,77 +220,115 @@
when :show
record_title
when :edit
record_title
when :new
- t("avo.create_new_item", item: name.humanize(capitalize: false)).upcase_first
+ t("avo.create_new_item", item: name.downcase).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 class_name
+ self.class.name.demodulize
+ end
+
def model_class
- record_class = @record&.class
+ # get the model class off of the static property
+ return self.class.model_class if self.class.model_class.present?
- self.class.model_class record_class: record_class
+ # get the model class off of the record
+ return @record.base_class if @record.present?
+
+ # generate a model class
+ class_name.safe_constantize
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?
+ the_title = @record.send title
+ return the_title if the_title.present?
- # 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
+ @record.id
+ rescue
+ name
+ end
+
+ def resource_description
+ return instance_exec(&self.class.description) if self.class.description.respond_to? :call
+
+ # Show the description only on the resource index view.
+ # If the user wants to conditionally it on all pages, they should use a block.
+ if view == :index
+ return self.class.description if self.class.description.is_a? String
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
- )
+ def translation_key
+ self.class.translation_key || "avo.resource_translations.#{class_name.underscore}"
+ end
+
+ def name
+ default = class_name.underscore.humanize
+
+ return @name if @name.present?
+
+ if translation_key
+ t(translation_key, count: 1, default: default).capitalize
+ else
+ default
end
+ end
+ def singular_name
+ name
+ end
+
+ def plural_name
+ default = name.pluralize
+
+ if translation_key
+ t(translation_key, count: 2, default: default).capitalize
+ else
+ default
+ end
+ end
+
+ def underscore_name
+ return @name if @name.present?
+
+ self.class.name.demodulize.underscore
+ end
+
+ def navigation_label
+ plural_name.humanize
+ end
+
+ def available_view_types
view_types = [:table]
- view_types << :grid if self.class.grid_view.present?
- view_types << :map if map_view.present?
+ view_types << :grid if get_grid_fields.present?
view_types
end
- def attachment_fields
+ def attached_file_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
+ def fill_record(record, params, extra_params: [])
+ # Map the received params to their actual fields
+ 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?
@@ -404,11 +344,11 @@
record
end
def authorization(user: nil)
- current_user = user || Avo::Current.user
+ current_user = user || Avo::Current.current_user
Avo::Services::AuthorizationService.new(current_user, record || model_class, policy_class: authorization_policy)
end
def file_hash
content_to_be_hashed = ""
@@ -441,41 +381,60 @@
default_values = get_fields
.select do |field|
!field.computed
end
.map do |field|
+ id = field.id
value = field.value
if field.type == "belongs_to"
+ id = field.foreign_key.to_sym
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])
+ value = @params[:via_relation_class].safe_constantize.find(@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]
+ resource = Avo::App.resources.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]
+ [id, value]
end
.to_h
- .select do |_, value|
+ .select do |id, value|
value.present?
end
- default_values.each do |field, value|
- field.assign_value record: @record, value: value
+ default_values.each do |id, value|
+ if @record.send(id).nil?
+ @record.send("#{id}=", value)
+ end
end
end
+ def route_key
+ class_name.underscore.pluralize
+ end
+
+ def singular_route_key
+ route_key.singularize
+ 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_class.model_name.plural
+ end
+
def model_name
model_class.model_name
end
def singular_model_key
@@ -488,10 +447,22 @@
def records_path
resources_path(resource: self)
end
+ def label_field
+ get_field_definitions.find do |field|
+ field.as_label.present?
+ end
+ rescue
+ nil
+ end
+
+ def label
+ label_field&.value || record_title
+ end
+
def avatar_field
get_field_definitions.find do |field|
field.as_avatar.present?
end
rescue
@@ -512,36 +483,40 @@
avatar_field.as_avatar
rescue
nil
end
+ def description_field
+ get_field_definitions.find do |field|
+ field.as_description.present?
+ end
+ rescue
+ nil
+ end
+
+ def description
+ description_field&.value
+ end
+
def form_scope
model_class.base_class.to_s.underscore.downcase
end
def has_record_id?
record.present? && record_id.present?
end
+ def find_record(id, query: nil, params: nil)
+ query ||= self.class.find_scope
+
+ self.class.find_record_method.call(model_class: query, id: id, params: params)
+ end
+
def id_attribute
:id
end
def record_id
record.send(id_attribute)
- end
-
- def description_attributes
- {
- view: view,
- resource: self,
- record: record
- }
- end
-
- private
-
- def entity_loader(entity)
- instance_variable_get("@#{entity.to_s.pluralize}_loader")
end
end
end