resque-reports 0.0.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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