class Avo::AssociationsController
def additional_params
def additional_params @additional_params ||= params[:fields].slice(*@attach_fields&.map(&:id)) end
def association_from_params
def association_from_params @field&.for_attribute || params[:related_name] end
def attachment_id
def attachment_id params[:related_id] || params.dig(:fields, :related_id) end
def authorize_attach_action
def authorize_attach_action authorize_if_defined "attach_#{@field.id}?" end
def authorize_detach_action
def authorize_detach_action authorize_if_defined "detach_#{@field.id}?", @attachment_record end
def authorize_if_defined(method, record = @record)
def authorize_if_defined(method, record = @record) return unless Avo.configuration.authorization_enabled? @authorization.set_record(record) if @authorization.has_method?(method.to_sym) @authorization.authorize_action method.to_sym elsif Avo.configuration.explicit_authorization raise Avo::NotAuthorizedError.new end end
def authorize_index_action
def authorize_index_action authorize_if_defined "view_#{@field.id}?" end
def create
def create if create_association create_success_action else create_fail_action end end
def create_association
def create_association association_name = BaseResource.valid_association_name(@record, association_from_params) perform_action_and_record_errors do if through_reflection? && additional_params.present? new_join_record.save elsif has_many_reflection? || through_reflection? @record.send(association_name) << @attachment_record else @record.send(:"#{association_name}=", @attachment_record) @record.save! end end end
def create_fail_action
def create_fail_action flash[:error] = t("avo.attachment_failed", attachment_class: @related_resource.name) respond_to do |format| format.turbo_stream { render turbo_stream: turbo_stream.append("alerts", partial: "avo/partials/all_alerts") } end end
def create_success_action
def create_success_action flash[:notice] = t("avo.attachment_class_attached", attachment_class: @related_resource.name) respond_to do |format| if params[:turbo_frame].present? format.turbo_stream { render turbo_stream: reload_frame_turbo_streams } else format.html { redirect_back fallback_location: resource_view_response_path } end end end
def destroy
def destroy association_name = BaseResource.valid_association_name(@record, @field.for_attribute || params[:related_name]) if through_reflection? join_record.destroy! elsif has_many_reflection? @record.send(association_name).delete @attachment_record else @record.send(:"#{association_name}=", nil) end destroy_success_action end
def destroy_success_action
def destroy_success_action flash[:notice] = t("avo.attachment_class_detached", attachment_class: @attachment_class) respond_to do |format| if params[:turbo_frame].present? format.turbo_stream do render turbo_stream: reload_frame_turbo_streams end else format.html { redirect_to params[:referrer] || resource_view_response_path } end end end
def has_many_reflection?
def has_many_reflection? reflection_class.in? [ ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection ] end
def index
def index @parent_resource = @resource.dup @resource = @related_resource @parent_record = @parent_resource.find_record(params[:id], params: params) @parent_resource.hydrate(record: @parent_record) # When array field the records are fetched from the field block, from the parent record or from the resource def records # When other field type, like has_many the @query is directly fetched from the parent record # Don't apply policy on array type since it can return an array of hashes where `.all` and other methods used on policy will fail. @query = if @field.type == "array" @resource.fetch_records(Avo::ExecutionContext.new(target: @field.block, record: @parent_record).handle || @parent_record.try(@field.id)) else @related_authorization.apply_policy( @parent_record.send( BaseResource.valid_association_name(@parent_record, association_from_params) ) ) end @association_field = find_association_field(resource: @parent_resource, association: params[:related_name]) if @association_field.present? && @association_field.scope.present? @query = Avo::ExecutionContext.new( target: @association_field.scope, query: @query, parent: @parent_record, resource: @resource, parent_resource: @parent_resource ).handle end super end
def join_record
def join_record @reflection.through_reflection.klass.find_by(source_foreign_key => @attachment_record.id, through_foreign_key => @record.id) end
def new
def new @resource.hydrate(record: @record) if @field.present? && !@field.is_searchable? query = @related_authorization.apply_policy @attachment_class # Add the association scope to the query scope if @field.attach_scope.present? query = Avo::ExecutionContext.new(target: @field.attach_scope, query: query, parent: @record).handle end @options = select_options(query) end @url = Avo::Services::URIService.parse(avo.root_url.to_s) .append_paths("resources", params[:resource_name], params[:id], params[:related_name]) .append_query( { view: @resource&.view&.to_s, for_attribute: @field&.try(:for_attribute) }.compact ) .to_s end
def new_join_record
def new_join_record @resource.fill_record( @reflection.through_reflection.klass.new( source_foreign_key => @attachment_record.id, through_foreign_key => @record.id ), additional_params, fields: @attach_fields, ) end
def pagination_key
def pagination_key @pagination_key ||= "#{@parent_resource.class.to_s.parameterize}.has_many.#{@related_resource.class.to_s.parameterize}" end
def reflection_class
def reflection_class if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) @reflection.through_reflection.class else @reflection.class end end
def reload_frame_turbo_streams
def reload_frame_turbo_streams turbo_streams = super # We want to close the modal if the user wants to add just one record turbo_streams << turbo_stream.avo_close_modal if params[:button] != "attach_another" turbo_streams end
def select_options(query)
def select_options(query) query.all.limit(Avo.configuration.associations_lookup_list_limit).map do |record| [@attachment_resource.new(record: record).record_title, record.to_param] end.tap do |options| options << t("avo.more_records_available") if options.size == Avo.configuration.associations_lookup_list_limit end end
def set_attach_fields
def set_attach_fields @attach_fields = if @field.attach_fields.present? Avo::FieldsExecutionContext.new(target: @field.attach_fields) .detect_fields .items_holder .items end end
def set_attachment_class
def set_attachment_class # @reflection is nil whe using an Array field. @attachment_class = @reflection&.klass end
def set_attachment_record
def set_attachment_record @attachment_record = @related_resource.find_record attachment_id, params: params end
def set_attachment_resource
def set_attachment_resource @attachment_resource = @field.use_resource || (Avo.resource_manager.get_resource_by_model_class @attachment_class) end
def set_page_param
def set_page_param # avo-resources-project.has_many.avo-resources-user.page page_key = "#{pagination_key}.page" @index_params[:page] = if Avo.configuration.session_persistence_enabled? session[page_key] = params[:page] || session[page_key] || 1 else params[:page] || 1 end end
def set_pagination_params
def set_pagination_params set_page_param set_per_page_param end
def set_per_page_param
def set_per_page_param # avo-resources-project.has_many.avo-resources-user.per_page per_page_key = "#{pagination_key}.per_page" @index_params[:per_page] = if Avo.configuration.session_persistence_enabled? session[per_page_key] = params[:per_page] || session[per_page_key] || Avo.configuration.via_per_page else params[:per_page] || Avo.configuration.via_per_page end end
def set_reflection
def set_reflection @reflection = @record.class.try(:reflect_on_association, association_from_params) return if @reflection.blank? && @field.type == "array" # Ensure inverse_of is present on STI if !@record.class.descends_from_active_record? && @reflection.inverse_of.blank? && Rails.env.development? raise "Avo relies on the 'inverse_of' option to establish the inverse association and perform some specific logic.\n" \ "Please configure the 'inverse_of' option for the '#{@reflection.macro} :#{@reflection.name}' association " \ "in the '#{@reflection.active_record.name}' model." end end
def set_reflection_field
def set_reflection_field @field = find_association_field(resource: @resource, association: @related_resource_name) @field.hydrate(resource: @resource, record: @record, view: Avo::ViewInquirer.new(:new)) rescue end
def set_related_authorization
def set_related_authorization @related_authorization = if @related_resource.present? @related_resource.authorization(user: _current_user) else Services::AuthorizationService.new _current_user end end
def show
def show @parent_resource, @parent_record = @resource, @record @resource, @record = @related_resource, @related_record super end
def source_foreign_key
def source_foreign_key @reflection.source_reflection.foreign_key end
def through_foreign_key
def through_foreign_key @reflection.through_reflection.foreign_key end
def through_reflection?
def through_reflection? @reflection.instance_of? ActiveRecord::Reflection::ThroughReflection end