module Exportable extend ActiveSupport::Concern def attach_export_file file_path = generate_excel filename = file_path&.split('/')&.last export_file.attach(io: File.open(file_path), filename:) rescue StandardError => e failed! raise e end def generate_excel params = self.params.with_indifferent_access klass_name = params[:class_name] associated_klass_name = params[:associated_class_name] action_name = params[:action_name] || 'index' model = CmAdmin::Model.find_by({ name: klass_name }) klass = klass_name.constantize current_action = CmAdmin::Models::Action.find_by(model, name: action_name) if associated_klass_name.present? && params[:parent_id].present? scoped_model = "CmAdmin::#{model.name}Policy::#{action_name.classify}Scope".constantize.new(Current.user, klass).resolve parent_model = model model = CmAdmin::Model.find_by({ name: associated_klass_name }) parent_record = fetch_ar_object(scoped_model, params[:parent_id]) child_records = parent_record.send(params[:child_records]) end selected_column_names = params[:columns] || [] records = if associated_klass_name.present? child_records else "CmAdmin::#{klass_name}Policy::Scope".constantize.new(Current.user, klass).resolve end records.includes(current_action.eager_load_associations) if current_action.eager_load_associations.present? filter_params = JSON.parse(params[:filters] || '{}') filtered_data = CmAdmin::Models::Filter.filtered_data(filter_params, records, model.filters) formatted_data = format_records(filtered_data, model, selected_column_names, parent_model:, action_name:) file_path = "#{Rails.root}/tmp/#{model.name}_data_#{DateTime.now.strftime('%Y-%m-%d_%H-%M-%S')}.xlsx" create_workbook(model, formatted_data, file_path) file_path end def exportable_columns(klass, action_name: :index) klass.available_fields[action_name].select(&:exportable) end private def fetch_ar_object(model_object, id) return model_object.friendly.find(id) if model_object.respond_to?(:friendly) model_object.find(id) end def format_records(records, model, selected_column_names, parent_model:, action_name: :index) deserialized_columns = CmAdmin::Utils.deserialize_csv_columns(selected_column_names, :as_json_params) main_model = parent_model || model available_fields = main_model.available_fields[action_name.to_sym] # This includes isn't recursve, a full solution should be recursive records_arr = [] records.includes(deserialized_columns[:include].keys).find_each do |record| record_hash = record.as_json({ only: selected_column_names.map(&:to_sym) }) selected_column_names.each do |column_name| break unless available_fields.map(&:field_name).include?(column_name.to_sym) column = CmAdmin::Models::Column.find_by(model, :index, { name: column_name.to_sym }) record_hash[column.field_name] = if column.field_type == :custom send(column.helper_method, record, column.field_name).to_s else record.send(column.field_name).to_s end end records_arr << record_hash end records_arr end def create_workbook(cm_model, records, file_path) flattened_records = records.map { |record| CmAdmin::Utils.flatten_hash(record) } columns = exportable_columns(cm_model).select do |column| flattened_records.first.keys.include?(column.field_name.to_s) end size_arr = [] columns.size.times { size_arr << 22 } xl = Axlsx::Package.new xl.workbook.add_worksheet do |sheet| sheet.add_row columns&.map(&:header), b: true flattened_records.each do |record| sheet.add_row(columns.map { |column| record[column.field_name.to_s] }) end sheet.column_widths(*size_arr) end xl.serialize(file_path) end end