Module: Controllers::Attachable
- Extended by:
- ActiveSupport::Concern
- Included in:
- ActivitiesController, ArticleProceduresController, ArticleTechnicalsController, ArticleTrainingsController, CommunicationsController, Crm::ExportedCatalogItemPacketsController, Crm::PostCommentsController, Crm::PostsController, Crm::SmsMessagesController, OpportunitiesController, RmasController, SupportCasesController, TopicsController, VouchersController
- Defined in:
- app/concerns/controllers/attachable.rb
Overview
ActiveSupport::Concern mixin: attachable.
Constant Summary collapse
- PUBLICATIONS_PER_PAGE =
Publications per page.
20
Instance Method Summary collapse
- #attach ⇒ Object
-
#publication_modal ⇒ Object
Render publication picker offcanvas on-demand into Turbo Frame This avoids nested forms by loading the offcanvas separately.
- #remove_attachment ⇒ Object
- #search_library ⇒ Object
Instance Method Details
#attach ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'app/concerns/controllers/attachable.rb', line 8 def attach load_context # Set controller_path for route generation in components # This handles irregular controllers (e.g., article_trainings vs Article model) @controller_path = controller_name # Capture parent_form_id if provided (for new records - hidden fields need form attribute) @parent_form_id = params[:parent_form_id] # Determine the scenario: new record, existing record, or invalid record context_object_id = params[:context_object_id] || params[:id] is_new_record = context_object_id.blank? || context_object_id.to_i == 0 is_invalid_record = !is_new_record && @context_object.nil? is_existing_record = !is_new_record && @context_object.present? # Execute appropriate action based on scenario if is_new_record # New record: create uploads but don't attach them (will be submitted via hidden fields) perform_attach(nil) elsif is_invalid_record # Existing record ID provided but record not found - set flash error flash[:error] = 'Unable to attach: Communication not found. Please refresh the page.' @uploads = [] # Ensure @uploads is set for template elsif is_existing_record # Existing record: attach uploads to the record perform_attach(@context_object) end # Single response block for all scenarios # Flash messages are automatically handled by TurboStreamFlashable after_action respond_to do |format| format.turbo_stream { render 'attachable/attach' } end end |
#publication_modal ⇒ Object
Render publication picker offcanvas on-demand into Turbo Frame
This avoids nested forms by loading the offcanvas separately
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'app/concerns/controllers/attachable.rb', line 165 def publication_modal load_context @controller_path = controller_name context_object_id = params[:context_object_id] || @context_object&.id || 0 @offcanvas_id = "publication-picker-#{context_object_id}" render partial: 'attachable/publication_offcanvas', layout: false, locals: { offcanvas_id: @offcanvas_id, context_object: @context_object, context_class_name: params[:context_class], controller_path: @controller_path } end |
#remove_attachment ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'app/concerns/controllers/attachable.rb', line 44 def @upload = Upload.find(params[:upload_id]) # Load context if provided (for existing records) if params[:context_object_id].present? load_context # For saved records: unlink the upload from the record, and only delete if safe # (uploaded files can be deleted, but literature/publications should only be unlinked) (@context_object) elsif @upload.ok_to_delete? && can?(:destroy, @upload) # For new/unsaved records (no context_object_id yet): # - Uploaded files: can be safely deleted (ok_to_delete? returns true) # - Literature/publications: should NOT be deleted (ok_to_delete? returns false) # The upload is removed from the DOM via Turbo Stream regardless @upload.destroy end respond_to do |format| format.turbo_stream { render 'attachable/remove_attachment' } format.html { redirect_back_or_to(root_path, notice: 'Attachment removed') } end end |
#search_library ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'app/concerns/controllers/attachable.rb', line 70 def search_library load_context context_object_id = @context_object.try(:id).to_i is_append = params[:append].to_s == 'true' if params[:term].present? # Cursor-based pagination using item.id for keyset navigation # Eager load literature and primary_image to avoid N+1 queries publications_query = Item.publications.active .joins(:literature) .includes(:literature, :primary_image) .keywords_search(params[:term]) .order('items.id DESC') # Apply cursor filter if provided (keyset pagination) publications_query = publications_query.where(items: { id: ...params[:cursor].to_i }) if params[:cursor].present? @publications = publications_query.limit(PUBLICATIONS_PER_PAGE + 1).to_a # Check if there are more results @has_more = @publications.size > PUBLICATIONS_PER_PAGE @publications = @publications.first(PUBLICATIONS_PER_PAGE) # Next cursor is the last item's ID @cursor = @publications.last&.id else @publications = [] @has_more = false @cursor = nil end # Get existing upload IDs to show "Already Attached" state existing_upload_ids = @context_object.try(:upload_ids) || [] respond_to do |format| format.turbo_stream do if is_append # Appending more results (infinite scroll) # Use multiple streams: append items to grid, replace trigger with new trigger render 'attachable/search_library_append', locals: { publications: @publications, existing_upload_ids: existing_upload_ids, context_object_id: context_object_id, context_class_name: params[:context_class], controller_path: controller_name, cursor: @cursor, has_more: @has_more, search_term: params[:term] } else # Fresh search - replace entire frame render turbo_stream: turbo_stream.update( "publication-picker-frame-#{context_object_id}", Crm::PublicationPickerComponent.new( offcanvas_id: "publication-picker-#{context_object_id}", context_object: @context_object, publications: @publications, existing_upload_ids: existing_upload_ids, body_only: true, search_term: params[:term], cursor: @cursor, has_more: @has_more ) ) end end format.html do if is_append # Turbo-frame lazy loading with turbo_stream response # Must set content_type so Turbo processes the stream actions render 'attachable/search_library_append', formats: [:turbo_stream], content_type: 'text/vnd.turbo-stream.html', locals: { publications: @publications, existing_upload_ids: existing_upload_ids, context_object_id: context_object_id, context_class_name: params[:context_class], controller_path: controller_name, cursor: @cursor, has_more: @has_more, search_term: params[:term] } elsif request.headers['Turbo-Frame'].present? render 'attachable/search_library', layout: false else render 'attachable/search_library' end end end end |