Class: Spree::Export

Inherits:
Object
  • Object
show all
Includes:
NumberAsParam, NumberIdentifier, SingleStoreResource, VendorConcern
Defined in:
app/models/spree/export.rb

Constant Summary collapse

SUPPORTED_FILE_FORMATS =
i[csv].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.available_modelsObject



181
182
183
# File 'app/models/spree/export.rb', line 181

def available_models
  available_types.map(&:model_class)
end

.available_typesObject



177
178
179
# File 'app/models/spree/export.rb', line 177

def available_types
  Spree.export_types
end

.model_classObject

eg. Spree::Exports::Products => Spree::Product

Raises:

  • (NameError)


190
191
192
193
194
195
196
# File 'app/models/spree/export.rb', line 190

def model_class
  klass = "Spree::#{to_s.demodulize.singularize}".safe_constantize

  raise NameError, "Missing model class for #{to_s}" unless klass

  klass
end

.type_for_model(model) ⇒ Object



185
186
187
# File 'app/models/spree/export.rb', line 185

def type_for_model(model)
  available_types.find { |type| type.model_class.to_s == model.to_s }
end

Instance Method Details

#build_csv_line(_record) ⇒ Object

Raises:

  • (NotImplementedError)


100
101
102
# File 'app/models/spree/export.rb', line 100

def build_csv_line(_record)
  raise NotImplementedError, 'build_csv_line must be implemented'
end

#csv_headersObject

Raises:

  • (NotImplementedError)


89
90
91
# File 'app/models/spree/export.rb', line 89

def csv_headers
  raise NotImplementedError, 'csv_headers must be implemented'
end

#current_abilityObject



159
160
161
# File 'app/models/spree/export.rb', line 159

def current_ability
  @current_ability ||= Spree.ability_class.new(user, { store: store })
end

#done?Boolean

Returns:

  • (Boolean)


54
55
56
# File 'app/models/spree/export.rb', line 54

def done?
  attachment.present? && attachment.attached?
end

#export_file_nameObject

eg. Spree::Exports::Products => products-store-my-store-code-20241030133348.csv



164
165
166
# File 'app/models/spree/export.rb', line 164

def export_file_name
  "#{type.demodulize.underscore}-#{store.code}-#{created_at.strftime('%Y%m%d%H%M%S')}.#{format}"
end

#export_tmp_file_pathObject



168
169
170
# File 'app/models/spree/export.rb', line 168

def export_tmp_file_path
  Rails.root.join('tmp', export_file_name)
end

#generateObject



62
63
64
65
66
# File 'app/models/spree/export.rb', line 62

def generate
  send(:"generate_#{format}")
  handle_attachment
  send_export_done_email
end

#generate_asyncObject



58
59
60
# File 'app/models/spree/export.rb', line 58

def generate_async
  Spree::Exports::GenerateJob.perform_later(id)
end

#generate_csvObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/models/spree/export.rb', line 68

def generate_csv
  ::CSV.open(export_tmp_file_path, 'wb', encoding: 'UTF-8', col_sep: ',', row_sep: "\r\n") do |csv|
    csv << csv_headers
    records_to_export.includes(scope_includes).find_in_batches do |batch|
      batch.each do |record|
        if multi_line_csv?
          record.to_csv(store).each do |line|
            csv << line
          end
        else
          csv << record.to_csv(store)
        end
      end
    end
  end
end

#handle_attachmentObject



104
105
106
107
108
# File 'app/models/spree/export.rb', line 104

def handle_attachment
  file = ::File.open(export_tmp_file_path)
  attachment.attach(io: file, filename: export_file_name)
  ::File.delete(export_tmp_file_path) if ::File.exist?(export_tmp_file_path)
end

#metafields_headersArray<String>

Returns an array of metafield headers for the model

Returns:

  • (Array<String>)


96
97
98
# File 'app/models/spree/export.rb', line 96

def metafields_headers
  @metafields_headers ||= Spree::MetafieldDefinition.for_resource_type(model_class.to_s).order(:namespace, :key).map(&:csv_header_name)
end

#model_classObject

eg. Spree::Exports::Products => Spree::Product



130
131
132
133
134
135
136
# File 'app/models/spree/export.rb', line 130

def model_class
  if type == 'Spree::Exports::Customers'
    Spree.user_class
  else
    "Spree::#{type.demodulize.singularize}".constantize
  end
end

#multi_line_csv?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'app/models/spree/export.rb', line 85

def multi_line_csv?
  false
end

#normalize_search_paramsObject

Ensures search params maintain consistent format between UI and exports

  • Preserves valid JSON unchanged

  • Converts Ruby hashes to JSON strings

  • Handles malformed input gracefully



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/models/spree/export.rb', line 142

def normalize_search_params
  return if search_params.blank?

  if search_params.is_a?(Hash)
    self.search_params = search_params.to_json
    return
  end

  begin
    # It's a string, so we parse and re-dump to ensure consistency
    parsed = JSON.parse(search_params.to_s)
    self.search_params = parsed.to_json
  rescue JSON::ParserError
    # Leave as-is if not valid JSON string
  end
end

#records_to_exportObject



117
118
119
120
121
122
123
# File 'app/models/spree/export.rb', line 117

def records_to_export
  if search_params.present?
    scope.ransack(search_params.is_a?(String) ? JSON.parse(search_params.to_s).to_h : search_params)
  else
    scope.ransack
  end.result
end

#scopeObject



110
111
112
113
114
115
# File 'app/models/spree/export.rb', line 110

def scope
  scope = model_class
  scope = scope.for_store(store) if model_class.respond_to?(:for_store)
  scope = scope.for_vendor(vendor) if model_class.respond_to?(:for_vendor) && vendor.present?
  scope.accessible_by(current_ability)
end

#scope_includesObject



125
126
127
# File 'app/models/spree/export.rb', line 125

def scope_includes
  []
end

#send_export_done_emailObject



172
173
174
# File 'app/models/spree/export.rb', line 172

def send_export_done_email
  Spree::ExportMailer.export_done(self).deliver_later
end