Class: Spree::Export
Constant Summary
collapse
- SUPPORTED_FILE_FORMATS =
%i[csv].freeze
Class Method Summary
collapse
Instance Method Summary
collapse
Class Method Details
.available_models ⇒ Object
253
254
255
|
# File 'app/models/spree/export.rb', line 253
def available_models
available_types.map(&:model_class)
end
|
.available_types ⇒ Object
234
235
236
|
# File 'app/models/spree/export.rb', line 234
def available_types
Spree.export_types
end
|
.model_class ⇒ Object
eg. Spree::Exports::Products => Spree::Product
262
263
264
265
266
267
268
|
# File 'app/models/spree/export.rb', line 262
def model_class
klass = "Spree::#{to_s.demodulize.singularize}".safe_constantize
raise NameError, "Missing model class for #{self}" unless klass
klass
end
|
.required_scope ⇒ Symbol?
Admin API scope family gating this export type — an export is a bulk
read, so an API key needs read_<required_scope> to create, view, and
download it. Derived from the class name
(Spree::Exports::Customers => :customers); override in subclasses whose
records are gated by a different scope (e.g. coupon codes =>
:promotions). Returns nil on the base class, so unmapped types are
only accessible to read_all/write_all keys.
247
248
249
250
251
|
# File 'app/models/spree/export.rb', line 247
def required_scope
return nil if self == Spree::Export
to_s.demodulize.underscore.to_sym
end
|
.type_for_model(model) ⇒ Object
257
258
259
|
# File 'app/models/spree/export.rb', line 257
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
123
124
125
|
# File 'app/models/spree/export.rb', line 123
def build_csv_line(_record)
raise NotImplementedError, 'build_csv_line must be implemented'
end
|
112
113
114
|
# File 'app/models/spree/export.rb', line 112
def
raise NotImplementedError, 'csv_headers must be implemented'
end
|
#current_ability ⇒ Object
204
205
206
|
# File 'app/models/spree/export.rb', line 204
def current_ability
@current_ability ||= Spree.ability_class.new(user, { store: store })
end
|
#decode_prefixed_id_filters(params) ⇒ Object
Replace any prefixed IDs in search_params with their raw DB IDs so
Ransack can match them. Without this, an admin filtering an export by
a foreign key (promotion_id_eq: 'promo_xxx', vendor_id_in: [...])
would always get zero rows. We only touch values that look like
prefixed IDs — anything else (numeric IDs, code strings, ranges,
state names) passes through untouched.
155
156
157
|
# File 'app/models/spree/export.rb', line 155
def decode_prefixed_id_filters(params)
params.transform_values { |value| decode_search_value(value) }
end
|
#decode_search_value(value) ⇒ Object
159
160
161
162
163
164
165
166
167
168
|
# File 'app/models/spree/export.rb', line 159
def decode_search_value(value)
case value
when String
Spree::PrefixedId.prefixed_id?(value) ? (Spree::PrefixedId.decode_prefixed_id(value) || value) : value
when Array
value.map { |v| decode_search_value(v) }
else
value
end
end
|
#done? ⇒ Boolean
77
78
79
|
# File 'app/models/spree/export.rb', line 77
def done?
attachment.present? && attachment.attached?
end
|
#event_serializer_class ⇒ Object
73
74
75
|
# File 'app/models/spree/export.rb', line 73
def event_serializer_class
'Spree::Api::V3::ExportSerializer'.safe_constantize
end
|
#export_file_name ⇒ Object
eg. Spree::Exports::Products => products-store-my-store-code-20241030133348.csv
209
210
211
|
# File 'app/models/spree/export.rb', line 209
def export_file_name
"#{type.demodulize.underscore}-#{store.code}-#{created_at.strftime('%Y%m%d%H%M%S')}.#{format}"
end
|
#export_tmp_file_path ⇒ Object
213
214
215
|
# File 'app/models/spree/export.rb', line 213
def export_tmp_file_path
Rails.root.join('tmp', export_file_name)
end
|
#generate ⇒ Object
85
86
87
88
89
|
# File 'app/models/spree/export.rb', line 85
def generate
send(:"generate_#{format}")
handle_attachment
send_export_done_email
end
|
#generate_async ⇒ Object
81
82
83
|
# File 'app/models/spree/export.rb', line 81
def generate_async
Spree::Exports::GenerateJob.perform_later(id)
end
|
#generate_csv ⇒ Object
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
# File 'app/models/spree/export.rb', line 91
def generate_csv
::CSV.open(export_tmp_file_path, 'wb', encoding: 'UTF-8', col_sep: ',', row_sep: "\r\n") do |csv|
csv <<
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 << Spree::CSV::FormulaSanitizer.row(line)
end
else
csv << Spree::CSV::FormulaSanitizer.row(record.to_csv(store))
end
end
end
end
end
|
#handle_attachment ⇒ Object
127
128
129
130
131
|
# File 'app/models/spree/export.rb', line 127
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
|
Returns an array of metafield headers for the model
119
120
121
|
# File 'app/models/spree/export.rb', line 119
def
@metafields_headers ||= Spree::MetafieldDefinition.for_resource_type(model_class.to_s).order(:namespace, :key).map(&:csv_header_name)
end
|
#model_class ⇒ Object
eg. Spree::Exports::Products => Spree::Product
175
176
177
178
179
180
181
|
# File 'app/models/spree/export.rb', line 175
def model_class
if type == 'Spree::Exports::Customers'
Spree.user_class
else
"Spree::#{type.demodulize.singularize}".constantize
end
end
|
#multi_line_csv? ⇒ Boolean
108
109
110
|
# File 'app/models/spree/export.rb', line 108
def multi_line_csv?
false
end
|
#normalize_search_params ⇒ Object
Ensures search params maintain consistent format between UI and exports
- Preserves valid JSON unchanged
- Converts Ruby hashes to JSON strings
- Handles malformed input gracefully
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
# File 'app/models/spree/export.rb', line 187
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
parsed = JSON.parse(search_params.to_s)
self.search_params = parsed.to_json
rescue JSON::ParserError
end
end
|
#records_to_export ⇒ Object
140
141
142
143
144
145
146
147
|
# File 'app/models/spree/export.rb', line 140
def records_to_export
if search_params.present?
params = search_params.is_a?(String) ? JSON.parse(search_params.to_s).to_h : search_params
scope.ransack(decode_prefixed_id_filters(params))
else
scope.ransack
end.result
end
|
#results_url ⇒ Object
Public API name for the results_url preference (read/write symmetry).
String preferences round-trip nil as "" — normalize blank to nil.
219
220
221
|
# File 'app/models/spree/export.rb', line 219
def results_url
preferred_results_url.presence
end
|
#results_url=(value) ⇒ Object
223
224
225
|
# File 'app/models/spree/export.rb', line 223
def results_url=(value)
self.preferred_results_url = value
end
|
#scope ⇒ Object
133
134
135
136
137
138
|
# File 'app/models/spree/export.rb', line 133
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_includes ⇒ Object
170
171
172
|
# File 'app/models/spree/export.rb', line 170
def scope_includes
[]
end
|
#send_export_done_email ⇒ Object
227
228
229
230
231
|
# File 'app/models/spree/export.rb', line 227
def send_export_done_email
return if user.blank?
Spree::ExportMailer.export_done(self).deliver_later
end
|