Open sclinede opened 11 years ago
@dkron, @vkuznetsov, @Strech жду советов и предложений
Есть мысль использовать instance_eval, report.column -> column, будет медленнее, конечно, но проще с т.з. DSL
Еще можно сделать lazy_load, для data_source. т.е. принимать не результат метода, а некий объект с известным интерфейсом.. Позволит еще кстати для некоторых отчетов не выгружать все данные за раз, а делать это итеративно => прогресс будет нагляднее.
Например так:
@order_lists_report = Resque::Reports::Report.new(
directory: File.join(Rails.root, 'public/files/shared/tmp/order_list'),
file_name: "order-list-#{date_from}-#{date_to}.csv",
data: {
source: OrderListReport.new,
lazy_load: :find_all, date_from, date_to
}
format: {
coding: 'windows-1251'
type: 'csv',
options: {
col_sep: ';',
row_sep: "\r\n"
}
}
)
Мне не нравится вот эта часть
@order_lists_report = Resque::Reports::Report.new(
directory: File.join(Rails.root, 'public/files/shared/tmp/order_list'),
file_name: "order-list-#{date_from}-#{date_to}.csv",
data_source: find_all(date_from, date_to),
format: {
coding: 'windows-1251'
type: 'csv',
options: {
col_sep: ';',
row_sep: "\r\n"
}
}
)
Какие задачи будет решать твой DSL? DSL только для одного конкретного отчета? Если он универсальный покажи пример.
Согласен.. может так:
report_file = Resque::Reports::File.new(
directory: File.join(Rails.root, 'public/files/shared/tmp/order_list'),
file_name: "order-list-#{date_from}-#{date_to}.csv",
)
report_format = Resque::Reports::Format.new(
coding: 'windows-1251'
type: 'csv',
options: {
col_sep: ';',
row_sep: "\r\n"
}
)
@order_lists_report = Resque::Reports::Report.new(
file: report_file,
format: report_format,
data_source: find_all(date_from, date_to)
)
и если так, то их можно где-то в одном месте определять, потом повторно использовать. Например:
CSV_1251_FORMAT = Resque::Reports::Format.new(
coding: 'windows-1251'
type: 'csv',
options: {
col_sep: ';',
row_sep: "\r\n"
}
)
XLS_UTF8_FORMAT = Resque::Reports::Format.new(coding: 'utf-8', type: 'xls')
to @vkuznetsov: нет, конечно DSL - для формирования произвольного отчета Показать пример результата или кода для создания?
Пример использования. Я пользователь твоего DSL, хочу понять как им пользоваться и чем он может мне помочь.
@sclinede Покажи как сейчас и покажи как будет, например
с объявлением репорта намудрил. да. зачем передавать какие-то formats, в которых в свою очередь закладывается тип отчета. в контексте того, что все csv у нас выгружаются с одинаковой конфигурацией, мне больше нравился подход с CSVWriter, который в свою очередь уже можно конфигурировать при желании.
и опиши пожалуйста схематично структуру в целом, я видел первоначальный вариант и примеры отсюда уже с ним расходятся весьма ощутимо.
to @vkuznetsov: вот как бы выглядела выгрузка товаров в csv из kirby-hoover
class ExportProductsCSVReport
def initialize(company)
products_scope = company.products.for_owners.includes(:product_properties, :rubric, :main_image)
report_file = Resque::Reports::File.new(
directory: File.join(Rails.root, 'public/files/shared/tmp/kirby_csv_export'),
file_name: "#{@company.id}.csv",
)
@products_csv_report = Resque::Reports::Report.new(
file: report_file,
format: Resque::Reports::Format::CSV_1251,
data_source: products_scope
)
end
def build
@products_csv_report.build do |product|
column 'Наименование товара', product.name
column 'Цена товара', product.product_properties.price
column 'Краткое описание', product.announce
column 'Валюта', Currency.title product.product_properties.currency
column 'Полное описание', product.description
column 'Наличие', (product.exists ? 'в наличии' : 'под заказ')
column 'Ссылка на изображение товара/услуги' do
path = product.main_image.try(:img).try(:url)
"http://#{HOST}#{path}" if path
end
column 'Рубрика', (predl_path(:host => HOST, :path => product.rubric.path) if product.rubric)
column 'Условия оплаты', product.paid_conditions
column 'Сроки доставки', product.delivery_time
column 'ID товара' do
# если встретился товар без yml_id, то нужно присвоить ему новый yml_id
if product.yml_id.blank?
product.update_attribute(:yml_id, product.id)
end
product.yml_id || Digest::SHA1.hexdigest(product.name)
end
end
end # #build
end # class ExportProductsCSVReport
# Пример использования тот же, а именно:
report = ExportProductsCSVReport.new(company).build
if report.ready?
send_data(File.read(report.file_name),...)
else
@job_id = report.progress_job_id
end
to @dkron: мы здесь только DSL обсуждаем, так что считаю обсуждение внутренностей здесь будет оффтопом
уродливо выглядит "декорация" значений для столбцов.. можно их впринципе в методы выносить и вызывать метод
# было
column 'ID товара' do
# если встретился товар без yml_id, то нужно присвоить ему новый yml_id
if product.yml_id.blank?
product.update_attribute(:yml_id, p.id)
end
product.yml_id || Digest::SHA1.hexdigest(product.name)
end
# стало
column 'ID товара', product_id(product)
...
def product_id(p)
# если встретился товар без yml_id, то нужно присвоить ему новый yml_id
if p.yml_id.blank?
p.update_attribute(:yml_id, p.id)
end
p.yml_id || Digest::SHA1.hexdigest(p.name)
end
to @Strech: можешь посмотреть ExportProductsCSV в blizko/vendor/plugins/kirby-hoover, сколько кода там написано и что нужно знать о resque для того чтобы реализовать ту же функцию. Просто много кода, думаю нет смысла его сюда приводить.
в моей реализации хочется спрятать от пользователя знания о resque-integration, сообщать ему только job_id для наблюдения за прогрессом либо вообще сразу выдать уже ранее сформированный отчет.
А где здесь эта.. инкапсуляция? data_source - чем должен быть? Зачем в клиентском коде каждый раз писать Resque::Reports::File.new, Resque::Reports::Report.new ?
вот так примерно можно сделать?
class MyReport < CSVReport
source :products
directory: File.join(Rails.root, 'public/files/shared/tmp/kirby_csv_export')
csv_options :delimiter => ',' ....
encoding: CP1251
table(ну или другое слово) do |product|
column 'Наименование товара', product.name
end
def products
@company.products
end
end
to @vkuznetsov: да была изначально именно такая идея, но после разговора с @Strech начал думать в сторону DSL и ушел слишком)
Твой вариант мне нравится, source здесь Enumerable, согласен с наследованием проще получается. table - мне нравится. Без опечаток - вот так?
class MyReport < CSVReport
source :products
directory File.join(Rails.root, 'public/files/shared/tmp/kirby_csv_export')
file "#{@company.id}.csv"
csv_options :delimiter => ',' ....
encoding CP1251
table do |product|
column 'Наименование товара', product.name
end
def products
@company.products
end
end
по file планирую делать уникальность задачи в очереди
откуда @company там возьмешь? уникальность, я считаю, надо делать по аргументам, передаваемым в конструктор
file сразу предлагаю заменить на filename и пусть эти методы могут принимать значение или символ. Если передан символ, то вызывается метод инстанса. Тогда можно будет написать:
filename :report_name
def report_name
"#{@company.id}.csv"
end
to @vkuznetsov про уникальность: ну а как же кеширование. если имя у нас не будет идентификатором отчета по заданным атрибутам, то мы всегда будем заново создавать отчет, даже если у нас только что был создан аналогичный. мне кажется идея с именем корректна, только вот использовать так
unique_filename :report_name
def report_name
"#{@company.id}.csv"
end
а про использование что думаешь? так и оставить?
report = OrderListsCSVReport.new(date_from, date_to).build
if report.ready?
send_data(File.read(report.file_name),...)
else
@job_id = report.progress_job_id
end
@Strech @vkuznetsov - обновил topic
filename :report_name
читается лучше чем unique_filename :report_name
report = OrderListsCSVReport.new(date_from, date_to)
if report.exists?
...
else
@job_id = report.bg_build
end
почему нельзя кэшировать по аргументам?
а как ты именно кэшировать предлагаешь ? я хотел по простому, проверяю наличие файла и дату создания = закешированный файл, а ты предлагаешь хранить инфу о параметрах где-то еще ?
я согласен, что по аргументам - логичнее. и не надо на пользователя возлагать лишних умозаключений.. только вот теперь и случай с одинаковыми именами надо обработать. и доп. информацию где-то хранить.
можно впринципе отказаться от filename, можно аргументы конструктора хешировать и все. 2х зайцев. прямые ссылки не используются.
обновил topic @Strech @vkuznetsov
Можно еще учесть случай параллельных запросов. Когда уже файл есть, но еще не заполнен до конца.
ну да. тогда тоже надо возвращать job_id, дабы за прогрессом наблюдать..
достаточно ли будет проверки enqueued? в данном случае или придется где-то хранить ссылку на job_id для данного файла?
Ну это ты сам подумай как лучше сделать. Хорошо бы иметь увереноость в том что предыдущий писатель доделал свою работу до конца. Это не критично, но полезно. Надобность этого оцени по формуле полезность / сложность. Еще нужно подумать о способе удаления старых файлов.
Да, это само собой. итак @dkron, @Strech, @vkuznetsov считаем DSL хорошим ? см. topic
Смущает вот это еще
order['rubric_title'].to_s.encode('cp1251', :invalid => :replace, :undef => :replace)
можно тоже спрятать. согласен, если задали формат CP1251, все строки приведем к данной кодировке
обновил @dkron, @Strech, @vkuznetsov
Считаю нужно чтобы column тоже мог принимать символ. Тогда запускается метод инстанса и ёму аргументом передаётся значение итератора.
ну да, чтобы не писать order каждый раз, согласен
на приведенном в топике примере не показательно получается
добавь column :company_name для примера
или счётчик - номер товара в выгрузке
@Strech @vkuznetsov @dkron если есть возможность прошу активно покомментировать еще сегодня ибо я рано утром начинаю работать, вот, хотелось бы сегодня собрать инфы побольше чтобы утром уже сделать чего-нибудь)
пили уже )
@sclinede Да вроде уже более-менее хорошо, попробуй первую версию чтоли :1234:
@Strech @vkuznetsov надо каким-то образом определять основные параметры отчета (например: для лога заказов это даты с какого по какое число), не хочется это делать через конструктор, предлагаю сделать это через аргументы для source метода, чтобы знать, какие переменные туда передать:
# типа вот так:
source :get_data, args: [:date_from, :date_to]
...
attr_reader :date_from, :date_to
@sclinede Пример использования приведи, не понятно
Типа вот так:
class OrderListsCSVReport < CSVReport
source :orders, args: [:date_from, :date_to]
directory File.join(Rails.root, 'public/files/shared/tmp/order_lists_report')
csv_options :delimiter => ',' ....
encoding CP1251
table do |order|
column 'Дата КД', (DateTime.parse(order['created_at']).in_time_zone.strftime('%d.%m.%Y %H:%M') if order['created_at'])
column 'Город', :city
column 'Название компании', order['company_name']
column 'Ссылка на место КД', order['referer']
column 'ID товара', order['product_id']
column 'Тип КД', order['conversion_type']
column 'Ссылка', order['lead_url']
end
def city(order)
order['company_main_region_name']
end
def initialize(date_from, date_to)
@date_from, @date_to, @another_var = date_from, date_to, "some usefull message with #{date_from} and #{date_to}"
end
def orders(date_from, date_to)
...# запрос данных
end
end
Второй вариант, мне нравится меньше:
class OrderListsCSVReport < CSVReport
source :orders
directory File.join(Rails.root, 'public/files/shared/tmp/order_lists_report')
csv_options :delimiter => ',' ....
encoding CP1251
table do |order|
column 'Дата КД', (DateTime.parse(order['created_at']).in_time_zone.strftime('%d.%m.%Y %H:%M') if order['created_at'])
column 'Город', :city
column 'Название компании', order['company_name']
column 'Ссылка на место КД', order['referer']
column 'ID товара', order['product_id']
column 'Тип КД', order['conversion_type']
column 'Ссылка', order['lead_url']
end
def city(order)
order['company_main_region_name']
end
def initialize(date_from, date_to)
@date_from, @date_to, @another_var = date_from, date_to, "some usefull message with #{date_from} and #{date_to}"
super(@date_from, @date_to)
end
def orders
...# запрос данных с использованием @date_from, @date_to
end
end
мне нужно знать, какой набор параметров уникально идентифицирует данный отчет, для кеширования.
Вот пример существующего отчета "Лог заказов" в csv:
Хотелось бы достаточно просто формировать подобные отчеты, причем с возможностью выполнять формирование в фоне. На первый взгляд DSL для такой задачи видится следующим: