resque-reports 0.0.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/app/jobs/resque/reports/report_job.rb +48 -8
- data/lib/resque/reports/base_report.rb +151 -98
- data/lib/resque/reports/cache_file.rb +22 -10
- data/lib/resque/reports/csv_report.rb +54 -30
- data/lib/resque/reports/extensions/const.rb +11 -0
- data/lib/resque/reports/extensions/encodings.rb +11 -0
- data/lib/resque/reports/extensions/event_callbacks.rb +64 -0
- data/lib/resque/reports/extensions/event_templates.rb +22 -0
- data/lib/resque/reports/extensions/filename_gen.rb +29 -0
- data/lib/resque/reports/extensions/table_building.rb +117 -0
- data/lib/resque/reports/extensions.rb +37 -0
- data/lib/resque/reports/version.rb +2 -1
- data/lib/resque/reports.rb +6 -5
- data/lib/resque-reports.rb +1 -0
- data/resque-reports.gemspec +7 -5
- data/spec/resque/reports/base_report_spec.rb +196 -0
- data/spec/resque/reports/csv_report_spec.rb +89 -0
- data/spec/resque/reports/report_job_spec.rb +140 -0
- data/spec/spec_helper.rb +8 -73
- metadata +63 -13
- data/init.rb +0 -4
- data/lib/resque/reports/callbacks.rb +0 -50
- data/lib/resque/reports/encodings.rb +0 -13
@@ -1,60 +1,84 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
require 'csv'
|
3
|
+
|
2
4
|
module Resque
|
3
5
|
module Reports
|
6
|
+
# Class to inherit from for custom CSV reports
|
7
|
+
# To make your custom report you must define at least:
|
8
|
+
# 1. directory, is where to write reports to
|
9
|
+
# 2. source, is symbol of method that retrieves report data
|
10
|
+
# 3. table, report table configuration using DSL
|
4
11
|
class CsvReport < BaseReport
|
5
12
|
extend Forwardable
|
6
|
-
include Callbacks # include on_progress, on_error callbacks, and handle_progress, handle_errors handlers
|
7
13
|
|
8
14
|
class << self
|
9
|
-
attr_accessor :
|
10
|
-
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
alias_method :csv_options, :options=
|
18
|
+
end
|
11
19
|
|
12
|
-
DEFAULT_CSV_OPTIONS = {
|
20
|
+
DEFAULT_CSV_OPTIONS = {col_sep: ';', row_sep: "\r\n"}
|
13
21
|
|
14
22
|
extension :csv
|
15
23
|
|
16
|
-
|
24
|
+
def_delegators TO_EIGENCLASS, :options, :csv_options
|
17
25
|
|
18
26
|
def initialize(*args)
|
19
|
-
csv_options
|
27
|
+
csv_options DEFAULT_CSV_OPTIONS.merge(options || Hash.new)
|
28
|
+
|
20
29
|
super(*args)
|
21
30
|
end
|
22
31
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# 3) build_table_row(object) => returns Array of cell values (same order as header)
|
31
|
-
def write(io)
|
32
|
+
def write(io, force = false)
|
33
|
+
# You must use ancestor methods to work with report data:
|
34
|
+
# 1) data_size => returns source data size
|
35
|
+
# 2) data_each => yields given block for each source data element
|
36
|
+
# 3) build_table_header => returns Array of report column names
|
37
|
+
# 4) build_table_row(object) => returns Array of report cell values
|
38
|
+
# (same order as header)
|
32
39
|
progress = 0
|
33
40
|
|
34
|
-
CSV(io,
|
35
|
-
|
36
|
-
|
37
|
-
if data_collection.size > 0
|
38
|
-
write_line csv, build_table_header
|
41
|
+
CSV(io, options) do |csv|
|
42
|
+
write_line csv, build_table_header
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
handle_progress(progress += 1)
|
44
|
+
data_each(force) do |data_element|
|
45
|
+
begin
|
46
|
+
write_line csv, build_table_row(data_element)
|
47
|
+
rescue
|
48
|
+
handle_error
|
48
49
|
end
|
49
50
|
|
50
|
-
handle_progress(progress,
|
51
|
+
handle_progress(progress += 1, data_size)
|
51
52
|
end
|
52
|
-
|
53
|
+
|
54
|
+
handle_progress(progress, data_size, true)
|
55
|
+
end
|
53
56
|
end
|
54
57
|
|
55
58
|
def write_line(csv, row_cells)
|
56
59
|
csv << row_cells
|
57
60
|
end
|
61
|
+
|
62
|
+
#--
|
63
|
+
# Event handling #
|
64
|
+
#++
|
65
|
+
|
66
|
+
def error_message(error)
|
67
|
+
error_message = []
|
68
|
+
error_message << 'Выгрузка отчета невозможна. '
|
69
|
+
error_message << case error
|
70
|
+
when Encoding::UndefinedConversionError
|
71
|
+
<<-ERR_MSG.gsub(/^ {29}/, '')
|
72
|
+
Символ #{error.error_char} не поддерживается
|
73
|
+
заданной кодировкой
|
74
|
+
ERR_MSG
|
75
|
+
when EncodingError
|
76
|
+
'Ошибка преобразования в заданную кодировку'
|
77
|
+
else
|
78
|
+
fail error
|
79
|
+
end
|
80
|
+
error_message * ' '
|
81
|
+
end
|
58
82
|
end # class CsvReport
|
59
83
|
end # module Report
|
60
84
|
end # module Resque
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Resque
|
3
|
+
module Reports
|
4
|
+
module Extensions
|
5
|
+
# Defines event callbacks and handlers for Resque::Reports::ReportJob
|
6
|
+
module EventCallbacks
|
7
|
+
# TODO: сделать гибкой логику колбеков и хендлеров
|
8
|
+
# Defines callbacks
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
attr_reader :progress_callback, :error_callback
|
12
|
+
|
13
|
+
#--
|
14
|
+
# Callbacks
|
15
|
+
#++
|
16
|
+
|
17
|
+
# Set callback for watching progress of export
|
18
|
+
# @yield [progress] block to be executed on progress
|
19
|
+
# @yieldparam progress [Integer] current progress
|
20
|
+
# @yieldparam total [Integer] data length
|
21
|
+
def on_progress(&block)
|
22
|
+
@progress_callback = block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set callback on error
|
26
|
+
# @yield [error] block to be executed when error occurred
|
27
|
+
# @yieldparam [Exception] error
|
28
|
+
def on_error(&block)
|
29
|
+
@error_callback = block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Defines handlers
|
34
|
+
module InstanceMethods
|
35
|
+
extend Forwardable
|
36
|
+
|
37
|
+
PROGRESS_STEP = 10
|
38
|
+
|
39
|
+
def_delegators Extensions::Const::TO_EIGENCLASS,
|
40
|
+
:error_callback,
|
41
|
+
:progress_callback
|
42
|
+
#--
|
43
|
+
# Handlers
|
44
|
+
#++
|
45
|
+
|
46
|
+
def handle_progress(progress, total, force = false)
|
47
|
+
if progress_callback && (force || progress % PROGRESS_STEP == 0)
|
48
|
+
progress_callback.call progress, total
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_error
|
53
|
+
error_callback ? error_callback.call($ERROR_INFO) : fail
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.included(base)
|
58
|
+
base.extend ClassMethods
|
59
|
+
base.send :include, InstanceMethods
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Resque
|
3
|
+
module Reports
|
4
|
+
module Extensions
|
5
|
+
# Defines base event handling methods for Resque::Reports::EventCallbacks
|
6
|
+
module EventTemplates
|
7
|
+
|
8
|
+
# Specifies progress message generation
|
9
|
+
# Can be overridden in successors
|
10
|
+
def progress_message(p, t)
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# Specifies error message generation
|
15
|
+
# Can be overridden in successors
|
16
|
+
def error_message(e)
|
17
|
+
fail e
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Resque
|
3
|
+
module Reports
|
4
|
+
module Extensions
|
5
|
+
# Module that generates file name
|
6
|
+
# Usage:
|
7
|
+
# class SomeClass
|
8
|
+
# include Resque::Reports::Extensions::FilenameGen
|
9
|
+
#
|
10
|
+
# # ...call somewhere...
|
11
|
+
# fname = generate_filename(%w(a b c), 'pdf')
|
12
|
+
# # 'fname' value is something like this:
|
13
|
+
# # "a60428ee50f1795819b8486c817c27829186fa40.pdf"
|
14
|
+
# end
|
15
|
+
module FilenameGen
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_filename(args, fextension)
|
20
|
+
"#{ hash(self.class.to_s, *args) }.#{ fextension }"
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash(*args)
|
24
|
+
Digest::SHA1.hexdigest(args.to_json)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# Resque namespace
|
3
|
+
module Resque
|
4
|
+
# Resque::Reports namespace
|
5
|
+
module Reports
|
6
|
+
# Resque::Reports::Extensions namespace
|
7
|
+
module Extensions
|
8
|
+
# Defines report table building logic
|
9
|
+
module TableBuilding
|
10
|
+
# Defines table building methods
|
11
|
+
# External(DSL):
|
12
|
+
# - source
|
13
|
+
# - table
|
14
|
+
# - column
|
15
|
+
# Internal:
|
16
|
+
# - init_table
|
17
|
+
# - build_table_header
|
18
|
+
# - build_table_row(row_object)
|
19
|
+
# - data_each
|
20
|
+
# - data_size
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
attr_accessor :source_method
|
24
|
+
alias_method :source, :source_method=
|
25
|
+
|
26
|
+
def table(&block)
|
27
|
+
@table_block = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def column(name, value)
|
31
|
+
add_column_header(name) || add_column_cell(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def init_table
|
35
|
+
@table_header = []
|
36
|
+
@table_row = []
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_column_header(column_name)
|
40
|
+
@table_header << encoded_string(column_name) if @header_collecting
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_column_cell(column_value)
|
44
|
+
return if @header_collecting
|
45
|
+
|
46
|
+
if column_value.is_a? Symbol
|
47
|
+
# Smells bad... changes input variable
|
48
|
+
column_value = @instance.send(column_value, @row_object)
|
49
|
+
end
|
50
|
+
|
51
|
+
@table_row << encoded_string(column_value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_table_row(row_object)
|
55
|
+
@header_collecting = false
|
56
|
+
|
57
|
+
@row_object = row_object # for instance decorate methods calls
|
58
|
+
row = @table_block.call(@row_object)
|
59
|
+
|
60
|
+
finish_row
|
61
|
+
|
62
|
+
row
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_table_header
|
66
|
+
@header_collecting = true
|
67
|
+
@table_block.call(Extensions::Dummy.new)
|
68
|
+
end
|
69
|
+
|
70
|
+
# you may override default string endcoding
|
71
|
+
def encoded_string(obj)
|
72
|
+
obj.to_s.encode('utf-8', invalid: :replace, undef: :replace)
|
73
|
+
end
|
74
|
+
|
75
|
+
def finish_row
|
76
|
+
@table_row = []
|
77
|
+
end
|
78
|
+
|
79
|
+
def data(force = false)
|
80
|
+
if force || @data.nil?
|
81
|
+
@data = @instance.send(@source_method)
|
82
|
+
else
|
83
|
+
@data
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def data_each(force = false)
|
88
|
+
data(force).each do |element|
|
89
|
+
yield element
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def data_size
|
94
|
+
@data_size ||= data.count
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Delegates class methods to instance
|
99
|
+
module InstanceMethods
|
100
|
+
extend Forwardable
|
101
|
+
|
102
|
+
def_delegators Extensions::Const::TO_EIGENCLASS,
|
103
|
+
:data_each,
|
104
|
+
:data_size,
|
105
|
+
:build_table_header,
|
106
|
+
:build_table_row,
|
107
|
+
:init_table
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.included(base)
|
111
|
+
base.extend ClassMethods
|
112
|
+
base.send :include, InstanceMethods
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'resque/reports/extensions/const'
|
3
|
+
require 'resque/reports/extensions/event_callbacks'
|
4
|
+
require 'resque/reports/extensions/event_templates'
|
5
|
+
require 'resque/reports/extensions/filename_gen'
|
6
|
+
require 'resque/reports/extensions/table_building'
|
7
|
+
require 'resque/reports/extensions/encodings'
|
8
|
+
|
9
|
+
# Resque namespace
|
10
|
+
module Resque
|
11
|
+
# Resque::Reports namespace
|
12
|
+
module Reports
|
13
|
+
# Resque::Reports::Extensions namespace
|
14
|
+
module Extensions
|
15
|
+
# Class for dummy object that respond to any method
|
16
|
+
# and returns 'nil' on any method call
|
17
|
+
class Dummy
|
18
|
+
def method_missing(method, *arguments, &block)
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to?(method, include_private = false)
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.send :include, Const # share gem constants
|
29
|
+
base.send :include, TableBuilding # init and build table
|
30
|
+
base.send :include, FilenameGen # generate_filename method
|
31
|
+
base.send :include, EventCallbacks # event callbacks and handlers
|
32
|
+
base.send :include, EventTemplates # template events handling methods
|
33
|
+
base.send :include, Encodings # encoding constants
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/resque/reports.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require 'forwardable'
|
3
|
-
require 'facets/kernel/constant'
|
4
|
-
|
5
3
|
require 'resque-integration'
|
6
4
|
|
7
|
-
require 'resque/reports/
|
5
|
+
require 'resque/reports/extensions'
|
6
|
+
|
8
7
|
require 'resque/reports/cache_file'
|
9
|
-
require 'resque/reports/callbacks'
|
10
|
-
require 'resque/reports/encodings'
|
11
8
|
require 'resque/reports/base_report'
|
12
9
|
require 'resque/reports/csv_report'
|
13
10
|
|
11
|
+
require 'resque/reports/version'
|
12
|
+
|
13
|
+
# Resque namespace
|
14
14
|
module Resque
|
15
|
+
# Resque::Reports namespace
|
15
16
|
module Reports
|
16
17
|
|
17
18
|
end
|