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.
@@ -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 :csv_options
10
- end
15
+ attr_accessor :options
16
+
17
+ alias_method :csv_options, :options=
18
+ end
11
19
 
12
- DEFAULT_CSV_OPTIONS = { col_sep: ';', row_sep: "\r\n" }
20
+ DEFAULT_CSV_OPTIONS = {col_sep: ';', row_sep: "\r\n"}
13
21
 
14
22
  extension :csv
15
23
 
16
- def_delegator 'self.class', :csv_options
24
+ def_delegators TO_EIGENCLASS, :options, :csv_options
17
25
 
18
26
  def initialize(*args)
19
- csv_options = DEFAULT_CSV_OPTIONS.merge(csv_options)
27
+ csv_options DEFAULT_CSV_OPTIONS.merge(options || Hash.new)
28
+
20
29
  super(*args)
21
30
  end
22
31
 
23
- # Callbacks
24
- on_progress { |progress, total| at(progress, total, progress_message(progress, total)) }
25
- on_error { |error| raise error }
26
-
27
- # You must use ancestor methods to work with data:
28
- # 1) get_data => returns Enumerable of source objects
29
- # 2) build_table_header => returns Array of column names
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, csv_options) do |csv|
35
- data_collection = get_data
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
- data_collection.each do |data_element|
41
- begin
42
- write_line csv, build_table_row(data_element)
43
- rescue
44
- handle_error
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, true)
51
+ handle_progress(progress += 1, data_size)
51
52
  end
52
- end
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,11 @@
1
+ # coding: utf-8
2
+ module Resque
3
+ module Reports
4
+ module Extensions
5
+ module Const
6
+ TO_EIGENCLASS = 'self.class'.freeze
7
+ TO_SUPER = 'self.class.superclass'.freeze
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # coding: utf-8
2
+ module Resque
3
+ module Reports
4
+ module Extensions
5
+ module Encodings
6
+ CP1251 = 'cp1251'.freeze
7
+ UTF8 = 'utf-8'.freeze
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
@@ -1,5 +1,6 @@
1
+ # coding: utf-8
1
2
  module Resque
2
3
  module Reports
3
- VERSION = "0.0.2"
4
+ VERSION = '0.3.0'
4
5
  end
5
6
  end
@@ -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/version'
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