Class: Feed
Overview
== Schema Information
Table name: feeds
Database name: primary
id :integer not null, primary key
active :boolean default(TRUE), not null
cc_email :string
contact_info :text
custom1 :string(255)
custom2 :string(255)
custom3 :string(255)
custom_file_formatter :string
destination :enum default("ftp")
emails :string default([]), is an Array
encoding :string(25) default("UTF-8")
feed_type :enum default("inventory")
file_name_template :string(255)
frequency :enum default("hourly")
ftp_passive :boolean default(FALSE), not null
hostname :string(255)
name :string(255) not null
new_line_method :enum default("crlf")
next_attempt :datetime
output_format :enum default("txt")
output_layout :text not null
password :string(255)
remote_directory :string(255)
sender_email :string
username :string(255)
created_at :datetime
updated_at :datetime
creator_id :integer
customer_id :integer not null
updater_id :integer
Indexes
feeds_customer_id_idx (customer_id)
Foreign Keys
feeds_customer_id_fk (customer_id => parties.id) ON DELETE => cascade
DEPRECATED / dormant. The reseller-inventory and OpenAI product feeds were
converged onto Edi::ResellerInventory and Edi::Openai (EdiCommunicationLog) — see
doc/tasks/202606190800_OPENAI_ADS_EDI_CONVERGENCE.md (PR #1219). This PR removed
the legacy runtime (FeedRunnerWorker, the scheduled process_feeds driver, the CRM
feeds UI). The model and feeds table are kept for now as the Zeitwerk namespace
anchor for Feed::Google::* / Feed::OpenaiAds::* and to preserve history; the
remaining transmit/render instance methods are no longer invoked and are stripped
together with the table drop in the stage-2 cleanup once the deprecation window is
clear.
Defined Under Namespace
Classes: ItemBaseGenerator, ItemInventoryGenerator, ItemListGenerator, ItemPresenter, ProductTypeGenerator, TransmitEmail, TransmitFtp, TransmitFtps, TransmitSftp
Constant Summary
Models::Auditable::ALWAYS_IGNORED
Constants included
from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
Delegated Instance Attributes
collapse
#creator, #updater
Has and belongs to many
collapse
Class Method Summary
collapse
Instance Method Summary
collapse
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
config
#after_commit
#publish_event
Instance Attribute Details
68
|
# File 'app/models/feed.rb', line 68
validates :custom_file_formatter, presence: { if: :custom_formatter? }
|
#destination ⇒ Object
66
|
# File 'app/models/feed.rb', line 66
validates :name, :feed_type, :destination, presence: true
|
#encoding ⇒ Object
71
|
# File 'app/models/feed.rb', line 71
validates :encoding, presence: true
|
#feed_type ⇒ Object
66
|
# File 'app/models/feed.rb', line 66
validates :name, :feed_type, :destination, presence: true
|
#hostname ⇒ Object
69
|
# File 'app/models/feed.rb', line 69
validates :hostname, :username, :password, presence: { if: :ftp? }
|
#name ⇒ Object
66
|
# File 'app/models/feed.rb', line 66
validates :name, :feed_type, :destination, presence: true
|
67
|
# File 'app/models/feed.rb', line 67
validates :output_format, :output_layout, presence: { if: :requires_output_format? }
|
#output_layout ⇒ Object
67
|
# File 'app/models/feed.rb', line 67
validates :output_format, :output_layout, presence: { if: :requires_output_format? }
|
#password ⇒ Object
69
|
# File 'app/models/feed.rb', line 69
validates :hostname, :username, :password, presence: { if: :ftp? }
|
#sender_email ⇒ Object
70
|
# File 'app/models/feed.rb', line 70
validates :sender_email, presence: { if: :email? }
|
#username ⇒ Object
69
|
# File 'app/models/feed.rb', line 69
validates :hostname, :username, :password, presence: { if: :ftp? }
|
Class Method Details
.active ⇒ ActiveRecord::Relation<Feed>
A relation of Feeds that are active. Active Record Scope
78
|
# File 'app/models/feed.rb', line 78
scope :active, -> { where(active: true) }
|
117
118
119
|
# File 'app/models/feed.rb', line 117
def self.custom_file_formatter_for_select
%w[]
end
|
.destinations_for_select ⇒ Object
109
110
111
|
# File 'app/models/feed.rb', line 109
def self.destinations_for_select
destinations.keys
end
|
.encoding_list_for_select ⇒ Object
208
209
210
|
# File 'app/models/feed.rb', line 208
def self.encoding_list_for_select
Encoding.list.map(&:to_s)
end
|
.feed_types_for_select ⇒ Object
101
102
103
|
# File 'app/models/feed.rb', line 101
def self.feed_types_for_select
feed_types.keys
end
|
.frequencies_for_select ⇒ Object
113
114
115
|
# File 'app/models/feed.rb', line 113
def self.frequencies_for_select
frequencies.keys
end
|
105
106
107
|
# File 'app/models/feed.rb', line 105
def self.output_formats_for_select
output_formats.keys
end
|
.timed ⇒ ActiveRecord::Relation<Feed>
A relation of Feeds that are timed. Active Record Scope
79
|
# File 'app/models/feed.rb', line 79
scope :timed, -> { where.not(frequency: nil) }
|
Instance Method Details
#all_to_emails ⇒ Object
131
132
133
|
# File 'app/models/feed.rb', line 131
def all_to_emails
contact_points.map(&:detail) + (emails || [])
end
|
#build_transmitter ⇒ Object
Builds a transmitter for ftp or email destination
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
# File 'app/models/feed.rb', line 259
def build_transmitter
case destination.to_sym
when :ftp
transmitter = Feed::TransmitFtp.new(
logger:,
hostname:,
ftp_passive:,
username:,
password:,
remote_directory:,
feed_id: id
)
when :ftps
transmitter = Feed::TransmitFtps.new(
logger:,
hostname:,
ftp_passive:,
username:,
password:,
remote_directory:,
feed_id: id
)
when :sftp
transmitter = Feed::TransmitSftp.new(
logger:,
hostname:,
username:,
password:,
remote_directory:,
feed_id: id
)
when :email
transmitter = Feed::TransmitEmail.new(
logger:,
recipient_contact_points: contact_points,
sender_email:,
cc_email:,
extra_emails: emails,
feed_id: id
)
else
raise 'Unsupported Destination, cannot resolve Transmitter Type'
end
transmitter
end
|
#calculate_next_attempt ⇒ Object
229
230
231
232
233
234
235
236
237
238
239
|
# File 'app/models/feed.rb', line 229
def calculate_next_attempt
case frequency
when 'hourly'
1.hour.from_now
when 'weekly'
1.week.from_now
else
1.day.from_now
end
end
|
#catalog ⇒ Object
151
152
153
|
# File 'app/models/feed.rb', line 151
def catalog
customer&.catalog
end
|
#catalog_items ⇒ Object
155
156
157
158
159
160
161
|
# File 'app/models/feed.rb', line 155
def catalog_items
return CatalogItem.none unless catalog
catalog_item_query = catalog.catalog_items.for_edi_feeds
catalog_item_query = catalog_item_query.where('third_party_sku ~ ?', catalog.third_party_sku_filter_regex) if catalog.is_active_third_party_sku_filter
catalog_item_query
end
|
#catalog_items_size ⇒ Object
Alias for Catalog_items#size
60
|
# File 'app/models/feed.rb', line 60
delegate :size, to: :catalog_items, prefix: true
|
64
|
# File 'app/models/feed.rb', line 64
has_and_belongs_to_many :contact_points, inverse_of: :feeds
|
121
122
123
|
# File 'app/models/feed.rb', line 121
def contact_points_for_select
customer.all_contact_points.emails.map { |cp| ["#{cp.party.full_name} - #{cp.detail}", cp.id] }.sort_by { |cp| cp[0] }
end
|
178
179
180
181
182
183
|
# File 'app/models/feed.rb', line 178
def custom_formatter_class
klass = "Edi::FileFormat::#{custom_file_formatter}".safe_constantize
raise NameError, "Custom file formatter class Edi::FileFormat::#{custom_file_formatter} not found for Feed ##{id}" unless klass
klass
end
|
62
|
# File 'app/models/feed.rb', line 62
belongs_to :customer, inverse_of: :feeds, optional: true
|
#deep_dup ⇒ Object
92
93
94
95
96
97
98
99
|
# File 'app/models/feed.rb', line 92
def deep_dup
deep_clone do |original, copy|
if copy.is_a?(Feed)
copy.name = "#{original.name} (Copy)"
copy.active = false
end
end
end
|
#encoding_to_use ⇒ Object
198
199
200
201
202
203
204
205
206
|
# File 'app/models/feed.rb', line 198
def encoding_to_use
enc = begin
Encoding.find(encoding)
rescue StandardError
nil
end
enc ||= Encoding.find('UTF-8')
enc.to_s
end
|
#feed_attributes ⇒ Object
Returns relevant instance variables to be used in template merge
141
142
143
144
145
146
147
148
149
|
# File 'app/models/feed.rb', line 141
def feed_attributes
res = {}
case feed_type.to_sym
when :inventory
res[:catalog_items] = catalog_items
end
res[:unique_id] = Time.current.to_i
res
end
|
#feed_histories ⇒ ActiveRecord::Relation<FeedHistory>
63
|
# File 'app/models/feed.rb', line 63
has_many :feed_histories, inverse_of: :feed
|
#file_name ⇒ Object
216
217
218
219
220
221
222
223
224
225
226
227
|
# File 'app/models/feed.rb', line 216
def file_name
@file_name ||= ERB.new(file_name_template).result(binding) if file_name_template.present?
if @file_name.nil? && custom_formatter?
begin
@file_name = custom_formatter_class.file_name
rescue NameError => e
Rails.logger.error("[Feed#file_name] #{e.message} — falling back to default name")
end
end
@file_name ||= "#{feed_type}_#{id}_#{customer_id}_#{Time.current.strftime('%y%m%d%H%M')}.#{output_format}"
@file_name
end
|
#last_feed ⇒ Object
332
333
334
|
# File 'app/models/feed.rb', line 332
def last_feed
feed_histories.order(id: :desc).try(:first)
end
|
#last_run_date ⇒ Object
336
337
338
|
# File 'app/models/feed.rb', line 336
def last_run_date
last_feed.try(:created_at)
end
|
#last_run_result ⇒ Object
340
341
342
|
# File 'app/models/feed.rb', line 340
def last_run_result
last_feed.try(:result)
end
|
#output_to_file ⇒ Object
305
306
307
308
309
|
# File 'app/models/feed.rb', line 305
def output_to_file
local_file_path = Rails.application.config.x.temp_storage_path.join(file_name)
render_to_file local_file_path
local_file_path
end
|
#render ⇒ Object
163
164
165
166
167
168
169
170
171
172
173
174
175
176
|
# File 'app/models/feed.rb', line 163
def render
if product_data?
res = Feed::ItemListGenerator.new.process
output = res.output
else @date = Time.current
feed_attributes.each { |k, v| instance_variable_set(:"@#{k}", v) }
@feed = self
output = ERB.new(output_layout).result(binding)
end
output = output.gsub("\r\n", "\n") if universal?
output.encode(encoding_to_use, invalid: :replace, undef: :replace)
end
|
#render_to_file(file_path) ⇒ Object
185
186
187
188
189
190
191
192
193
194
195
196
|
# File 'app/models/feed.rb', line 185
def render_to_file(file_path)
if custom_formatter?
formatter = custom_formatter_class.new(output_location: file_path)
formatter.process feed_attributes
else
File.open(file_path, "w+:#{encoding_to_use}") do |f|
f.write(render)
f.flush
f.fsync
end
end
end
|
#run ⇒ Object
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
# File 'app/models/feed.rb', line 241
def run
return unless active
Rails.logger.info "Running feed #{id}"
res = transmit
if res.transmitted?
update_column(:next_attempt, calculate_next_attempt) else
Rails.logger.error " ** Error running feed #{id} #{res.message}"
update_column(:next_attempt, 1.hour.from_now) end
Rails.logger.info "Feed id #{id} complete, next run scheduled for #{next_attempt}"
res
end
|
#sender_email_options_for_select ⇒ Object
125
126
127
128
129
|
# File 'app/models/feed.rb', line 125
def sender_email_options_for_select
['ediadmin@warmlyyours.com'] +
['orders@warmlyyours.com'] +
Employee.active_employees.pluck(:email).sort
end
|
#ssc(str) ⇒ Object
136
137
138
|
# File 'app/models/feed.rb', line 136
def ssc(str)
str.gsub(%r{[^0-9A-Za-z.-/]}, ' ').squish
end
|
#to_s ⇒ Object
212
213
214
|
# File 'app/models/feed.rb', line 212
def to_s
"Feed #{id}"
end
|
#transmit ⇒ Object
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
|
# File 'app/models/feed.rb', line 311
def transmit
local_file_path = output_to_file
raise "Feed #{local_file_path} not found" unless File.exist? local_file_path
upload = Upload.uploadify(local_file_path.to_s, 'Feed')
logger.info "Job output uploaded to upload id #{upload.id}"
transmitter = build_transmitter
res = transmitter.process(upload)
feed_histories.create(result: res.status,
file_name: upload.try(:attachment_name),
notes: res.message,
communication: res.try(:communication),
upload:)
res
end
|