rails-excel-reporter 0.3.0 → 1.0.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/Gemfile.lock +12 -4
- data/README.md +2 -2
- data/lib/rails_excel_reporter/base.rb +82 -53
- data/lib/rails_excel_reporter/configuration.rb +26 -12
- data/lib/rails_excel_reporter/controller_helpers.rb +18 -11
- data/lib/rails_excel_reporter/railtie.rb +30 -17
- data/lib/rails_excel_reporter/streaming.rb +26 -23
- data/lib/rails_excel_reporter/styling.rb +31 -26
- data/lib/rails_excel_reporter/version.rb +1 -1
- data/rails_excel_reporter.gemspec +3 -4
- data/spec/rails_excel_reporter/base_spec.rb +15 -15
- data/spec/rails_excel_reporter/controller_helpers_spec.rb +17 -16
- data/spec/rails_excel_reporter/streaming_spec.rb +18 -18
- data/spec/rails_excel_reporter/styling_spec.rb +9 -9
- data/spec/spec_helper.rb +2 -2
- metadata +8 -23
- data/CLAUDE.md +0 -94
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b6f920ce797a160c37ecc253bc0046b4212df1857966fec30100d4fcdad15d2
|
4
|
+
data.tar.gz: 5f9d145e21d5a5970178d8da52a1972345e78a6b64777bc05a7954dcf71b1cef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23ee34c51036cc83cd9030ea76466b76a1af1778e6817ceac117d760569ea327e9b93c18c222ee4909349f346bda6c4a1334813db47fc452e28061f1ca71c9b7
|
7
|
+
data.tar.gz: 62953ee7433e7dc98483013549b1a1d32d5e80b5a90a7296a1754856b3269baab298db3a927c1e7eb3d17ed4ad51a1dcce52422426185dcb84eec8a7a3cc84a0
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rails-excel-reporter (0.
|
5
|
-
activesupport (>=
|
4
|
+
rails-excel-reporter (1.0.0)
|
5
|
+
activesupport (>= 8.0)
|
6
6
|
caxlsx (~> 4.0)
|
7
|
-
rails (>=
|
7
|
+
rails (>= 8.0)
|
8
8
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
@@ -125,6 +125,7 @@ GEM
|
|
125
125
|
marcel (1.0.4)
|
126
126
|
method_source (1.1.0)
|
127
127
|
mini_mime (1.1.5)
|
128
|
+
mini_portile2 (2.8.9)
|
128
129
|
minitest (5.25.5)
|
129
130
|
net-imap (0.5.9)
|
130
131
|
date
|
@@ -247,6 +248,10 @@ GEM
|
|
247
248
|
rubocop-ast (1.45.1)
|
248
249
|
parser (>= 3.3.7.2)
|
249
250
|
prism (~> 1.4)
|
251
|
+
rubocop-performance (1.25.0)
|
252
|
+
lint_roller (~> 1.1)
|
253
|
+
rubocop (>= 1.75.0, < 2.0)
|
254
|
+
rubocop-ast (>= 1.38.0, < 2.0)
|
250
255
|
ruby-progressbar (1.13.0)
|
251
256
|
rubyzip (2.4.1)
|
252
257
|
securerandom (0.4.1)
|
@@ -256,6 +261,8 @@ GEM
|
|
256
261
|
simplecov_json_formatter (~> 0.1)
|
257
262
|
simplecov-html (0.13.1)
|
258
263
|
simplecov_json_formatter (0.1.4)
|
264
|
+
sqlite3 (1.7.3)
|
265
|
+
mini_portile2 (~> 2.8.0)
|
259
266
|
sqlite3 (1.7.3-aarch64-linux)
|
260
267
|
sqlite3 (1.7.3-arm-linux)
|
261
268
|
sqlite3 (1.7.3-arm64-darwin)
|
@@ -297,7 +304,8 @@ DEPENDENCIES
|
|
297
304
|
rake (~> 13.0)
|
298
305
|
rspec (~> 3.0)
|
299
306
|
rspec-rails (~> 5.0)
|
300
|
-
rubocop (~> 1.
|
307
|
+
rubocop (~> 1.78)
|
308
|
+
rubocop-performance (~> 1.24)
|
301
309
|
simplecov (~> 0.21)
|
302
310
|
sqlite3 (~> 1.4)
|
303
311
|
yard (~> 0.9)
|
data/README.md
CHANGED
@@ -23,16 +23,18 @@ module RailsExcelReporter
|
|
23
23
|
if attrs.empty?
|
24
24
|
@attributes ||= []
|
25
25
|
else
|
26
|
-
@attributes = attrs.map
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
@attributes = attrs.map { |attr| process_attribute attr }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.process_attribute(attr)
|
31
|
+
case attr
|
32
|
+
when Symbol
|
33
|
+
{ name: attr, header: attr.to_s.humanize }
|
34
|
+
when Hash
|
35
|
+
symbolize_hash_keys(attr)
|
36
|
+
else
|
37
|
+
{ name: attr.to_sym, header: attr.to_s.humanize }
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -51,7 +53,7 @@ module RailsExcelReporter
|
|
51
53
|
|
52
54
|
def self.inherited(subclass)
|
53
55
|
super
|
54
|
-
subclass.instance_variable_set
|
56
|
+
subclass.instance_variable_set :@attributes, @attributes.dup if @attributes
|
55
57
|
end
|
56
58
|
|
57
59
|
def attributes
|
@@ -70,18 +72,18 @@ module RailsExcelReporter
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def save_to(path)
|
73
|
-
File.open
|
74
|
-
file.write
|
75
|
+
File.open path, 'wb' do |file|
|
76
|
+
file.write to_xlsx
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
78
80
|
def filename
|
79
|
-
timestamp = Time.now.strftime(RailsExcelReporter.config.date_format).gsub
|
81
|
+
timestamp = Time.now.strftime(RailsExcelReporter.config.date_format).gsub '-', '_'
|
80
82
|
"#{worksheet_name.parameterize}_report_#{timestamp}.xlsx"
|
81
83
|
end
|
82
84
|
|
83
85
|
def stream
|
84
|
-
StringIO.new
|
86
|
+
StringIO.new to_xlsx
|
85
87
|
end
|
86
88
|
|
87
89
|
def to_h
|
@@ -114,25 +116,29 @@ module RailsExcelReporter
|
|
114
116
|
|
115
117
|
def render
|
116
118
|
validate_attributes!
|
117
|
-
|
118
119
|
before_render
|
119
120
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
package = ::Axlsx::Package.new
|
124
|
-
workbook = package.workbook
|
121
|
+
create_tempfile
|
122
|
+
create_worksheet
|
125
123
|
|
126
|
-
|
124
|
+
@rendered = true
|
125
|
+
after_render
|
126
|
+
end
|
127
127
|
|
128
|
-
|
129
|
-
|
128
|
+
def create_tempfile
|
129
|
+
@tempfile = Tempfile.new [filename.gsub('.xlsx', ''), '.xlsx'],
|
130
|
+
RailsExcelReporter.config.temp_directory
|
131
|
+
end
|
130
132
|
|
131
|
-
|
133
|
+
def create_worksheet
|
134
|
+
package = ::Axlsx::Package.new
|
135
|
+
workbook = package.workbook
|
136
|
+
worksheet = workbook.add_worksheet name: worksheet_name
|
132
137
|
|
133
|
-
|
138
|
+
add_headers worksheet
|
139
|
+
add_data_rows worksheet
|
134
140
|
|
135
|
-
|
141
|
+
package.serialize @tempfile.path
|
136
142
|
end
|
137
143
|
|
138
144
|
def validate_attributes!
|
@@ -141,56 +147,79 @@ module RailsExcelReporter
|
|
141
147
|
|
142
148
|
def add_headers(worksheet)
|
143
149
|
header_values = attributes.map { |attr| attr[:header] }
|
144
|
-
|
150
|
+
add_header_row worksheet, header_values
|
151
|
+
add_auto_filter worksheet
|
152
|
+
end
|
153
|
+
|
154
|
+
def add_header_row(worksheet, header_values)
|
155
|
+
header_style = build_caxlsx_style get_header_style
|
145
156
|
|
146
157
|
if header_style.any?
|
147
|
-
style_id = worksheet.workbook.styles.add_style
|
148
|
-
worksheet.add_row
|
158
|
+
style_id = worksheet.workbook.styles.add_style header_style
|
159
|
+
worksheet.add_row header_values, style: style_id
|
149
160
|
else
|
150
|
-
worksheet.add_row
|
161
|
+
worksheet.add_row header_values
|
151
162
|
end
|
163
|
+
end
|
152
164
|
|
153
|
-
|
165
|
+
def add_auto_filter(worksheet)
|
166
|
+
worksheet.auto_filter = "A1:#{column_letter attributes.size}1"
|
154
167
|
end
|
155
168
|
|
156
169
|
def add_data_rows(worksheet)
|
157
170
|
with_progress_tracking do |object, _progress|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
get_attribute_value(object, attr[:name])
|
162
|
-
end
|
171
|
+
process_data_row worksheet, object
|
172
|
+
end
|
173
|
+
end
|
163
174
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
175
|
+
def process_data_row(worksheet, object)
|
176
|
+
before_row object
|
177
|
+
row_values = build_row_values object
|
178
|
+
row_styles = build_row_styles worksheet
|
179
|
+
worksheet.add_row row_values, style: row_styles
|
180
|
+
after_row object
|
181
|
+
end
|
168
182
|
|
169
|
-
|
183
|
+
def build_row_values(object)
|
184
|
+
attributes.map do |attr|
|
185
|
+
get_attribute_value object, attr[:name]
|
186
|
+
end
|
187
|
+
end
|
170
188
|
|
171
|
-
|
189
|
+
def build_row_styles(worksheet)
|
190
|
+
attributes.map do |attr|
|
191
|
+
style_options = build_caxlsx_style get_column_style(attr[:name])
|
192
|
+
worksheet.workbook.styles.add_style style_options if style_options.any?
|
172
193
|
end
|
173
194
|
end
|
174
195
|
|
175
196
|
def get_attribute_value(object, attribute_name)
|
176
|
-
if respond_to?
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
object.send(attribute_name)
|
183
|
-
elsif object.respond_to?(:[])
|
184
|
-
object[attribute_name] || object[attribute_name.to_s]
|
197
|
+
if respond_to? attribute_name
|
198
|
+
call_custom_method object, attribute_name
|
199
|
+
elsif object.respond_to? attribute_name
|
200
|
+
object.send attribute_name
|
201
|
+
elsif object.respond_to? :[]
|
202
|
+
get_hash_value object, attribute_name
|
185
203
|
end
|
186
204
|
end
|
187
205
|
|
206
|
+
def call_custom_method(object, attribute_name)
|
207
|
+
@object = object
|
208
|
+
result = send attribute_name
|
209
|
+
@object = nil
|
210
|
+
result
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_hash_value(object, attribute_name)
|
214
|
+
object[attribute_name] || object[attribute_name.to_s]
|
215
|
+
end
|
216
|
+
|
188
217
|
attr_reader :object
|
189
218
|
|
190
219
|
def collection_data
|
191
220
|
@collection_data ||= stream_data.map do |item|
|
192
221
|
attributes.map do |attr|
|
193
|
-
get_attribute_value
|
222
|
+
get_attribute_value item, attr[:name]
|
194
223
|
end
|
195
224
|
end
|
196
225
|
end
|
@@ -3,17 +3,7 @@ module RailsExcelReporter
|
|
3
3
|
attr_accessor :default_styles, :date_format, :streaming_threshold, :temp_directory
|
4
4
|
|
5
5
|
def initialize
|
6
|
-
@default_styles =
|
7
|
-
header: {
|
8
|
-
bg_color: '4472C4',
|
9
|
-
fg_color: 'FFFFFF',
|
10
|
-
bold: true,
|
11
|
-
border: { style: :thin, color: '000000' }
|
12
|
-
},
|
13
|
-
cell: {
|
14
|
-
border: { style: :thin, color: 'CCCCCC' }
|
15
|
-
}
|
16
|
-
}
|
6
|
+
@default_styles = default_style_config
|
17
7
|
@date_format = '%Y-%m-%d'
|
18
8
|
@streaming_threshold = 1000
|
19
9
|
@temp_directory = nil
|
@@ -22,6 +12,30 @@ module RailsExcelReporter
|
|
22
12
|
def temp_directory
|
23
13
|
@temp_directory || Dir.tmpdir
|
24
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def default_style_config
|
19
|
+
{
|
20
|
+
header: header_style,
|
21
|
+
cell: cell_style
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def header_style
|
26
|
+
{
|
27
|
+
bg_color: '4472C4',
|
28
|
+
fg_color: 'FFFFFF',
|
29
|
+
bold: true,
|
30
|
+
border: { style: :thin, color: '000000' }
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def cell_style
|
35
|
+
{
|
36
|
+
border: { style: :thin, color: 'CCCCCC' }
|
37
|
+
}
|
38
|
+
end
|
25
39
|
end
|
26
40
|
|
27
41
|
class << self
|
@@ -30,7 +44,7 @@ module RailsExcelReporter
|
|
30
44
|
end
|
31
45
|
|
32
46
|
def configure
|
33
|
-
yield
|
47
|
+
yield configuration
|
34
48
|
end
|
35
49
|
|
36
50
|
def config
|
@@ -4,31 +4,38 @@ module RailsExcelReporter
|
|
4
4
|
filename = options[:filename] || report.filename
|
5
5
|
disposition = options[:disposition] || 'attachment'
|
6
6
|
|
7
|
-
send_data
|
7
|
+
send_data \
|
8
8
|
report.to_xlsx,
|
9
9
|
filename: filename,
|
10
|
-
type:
|
10
|
+
type: excel_content_type,
|
11
11
|
disposition: disposition
|
12
|
-
)
|
13
12
|
end
|
14
13
|
|
15
14
|
def stream_excel_report(report, options = {})
|
16
15
|
filename = options[:filename] || report.filename
|
17
|
-
|
18
|
-
response.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
19
|
-
response.headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
|
20
|
-
response.headers['Content-Transfer-Encoding'] = 'binary'
|
21
|
-
response.headers['Last-Modified'] = Time.now.httpdate
|
22
|
-
|
16
|
+
set_excel_response_headers filename
|
23
17
|
self.response_body = report.stream
|
24
18
|
end
|
25
19
|
|
26
20
|
def excel_report_response(report, options = {})
|
27
21
|
if report.should_stream?
|
28
|
-
stream_excel_report
|
22
|
+
stream_excel_report report, options
|
29
23
|
else
|
30
|
-
send_excel_report
|
24
|
+
send_excel_report report, options
|
31
25
|
end
|
32
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_excel_response_headers(filename)
|
31
|
+
response.headers['Content-Type'] = excel_content_type
|
32
|
+
response.headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
|
33
|
+
response.headers['Content-Transfer-Encoding'] = 'binary'
|
34
|
+
response.headers['Last-Modified'] = Time.now.httpdate
|
35
|
+
end
|
36
|
+
|
37
|
+
def excel_content_type
|
38
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
39
|
+
end
|
33
40
|
end
|
34
41
|
end
|
@@ -7,29 +7,42 @@ module RailsExcelReporter
|
|
7
7
|
end
|
8
8
|
|
9
9
|
initializer 'rails_excel_reporter.include_controller_helpers' do
|
10
|
-
ActiveSupport.on_load
|
10
|
+
ActiveSupport.on_load :action_controller do
|
11
11
|
include RailsExcelReporter::ControllerHelpers
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
config.after_initialize do
|
16
|
-
if
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
16
|
+
configure_reports_path if rails_application_available?
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def self.rails_application_available?
|
22
|
+
defined?(Rails.application) && Rails.application
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.configure_reports_path
|
26
|
+
reports_path = Rails.root.join 'app/reports'
|
27
|
+
setup_reports_path reports_path if Rails.application.paths['app/reports']
|
28
|
+
rescue StandardError => e
|
29
|
+
log_configuration_warning e
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.setup_reports_path(reports_path)
|
33
|
+
app_reports_paths = Rails.application.paths['app/reports']
|
34
|
+
|
35
|
+
unless app_reports_paths.paths.include? reports_path.to_s
|
36
|
+
app_reports_paths << reports_path.to_s
|
32
37
|
end
|
38
|
+
|
39
|
+
app_reports_paths.eager_load! if app_reports_paths.respond_to? :eager_load!
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.log_configuration_warning(error)
|
43
|
+
return unless Rails.logger
|
44
|
+
|
45
|
+
Rails.logger.warn "RailsExcelReporter: Failed to configure app/reports path: #{error.message}"
|
33
46
|
end
|
34
47
|
end
|
35
48
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module RailsExcelReporter
|
2
2
|
module Streaming
|
3
3
|
def self.included(base)
|
4
|
-
base.extend
|
4
|
+
base.extend ClassMethods
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
@@ -21,19 +21,23 @@ module RailsExcelReporter
|
|
21
21
|
def collection_size
|
22
22
|
return @collection_size if defined?(@collection_size)
|
23
23
|
|
24
|
-
@collection_size =
|
25
|
-
@collection.count
|
26
|
-
elsif @collection.respond_to?(:size)
|
27
|
-
@collection.size
|
28
|
-
elsif @collection.respond_to?(:length)
|
29
|
-
@collection.length
|
30
|
-
else
|
31
|
-
@collection.to_a.size
|
32
|
-
end
|
24
|
+
@collection_size = calculate_collection_size
|
33
25
|
end
|
34
26
|
|
27
|
+
private
|
28
|
+
|
29
|
+
def calculate_collection_size
|
30
|
+
return @collection.count if @collection.respond_to? :count
|
31
|
+
return @collection.size if @collection.respond_to? :size
|
32
|
+
return @collection.length if @collection.respond_to? :length
|
33
|
+
|
34
|
+
@collection.to_a.size
|
35
|
+
end
|
36
|
+
|
37
|
+
public
|
38
|
+
|
35
39
|
def stream_data(&block)
|
36
|
-
return enum_for
|
40
|
+
return enum_for :stream_data unless block_given?
|
37
41
|
|
38
42
|
if should_stream?
|
39
43
|
stream_large_dataset(&block)
|
@@ -43,27 +47,26 @@ module RailsExcelReporter
|
|
43
47
|
end
|
44
48
|
|
45
49
|
def with_progress_tracking
|
46
|
-
return enum_for
|
47
|
-
|
48
|
-
total = collection_size
|
49
|
-
current = 0
|
50
|
+
return enum_for :with_progress_tracking unless block_given?
|
50
51
|
|
52
|
+
total, current = collection_size, 0
|
51
53
|
stream_data do |item|
|
52
54
|
current += 1
|
53
|
-
progress =
|
54
|
-
|
55
|
-
@progress_callback&.call(progress)
|
56
|
-
|
55
|
+
progress = build_progress_info current, total
|
56
|
+
@progress_callback&.call progress
|
57
57
|
yield item, progress
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
|
61
|
+
def build_progress_info(current, total)
|
62
|
+
percentage = (current.to_f / total * 100).round 2
|
63
|
+
OpenStruct.new current: current, total: total, percentage: percentage
|
64
|
+
end
|
62
65
|
|
63
66
|
def stream_large_dataset(&block)
|
64
|
-
if @collection.respond_to?
|
67
|
+
if @collection.respond_to? :find_each
|
65
68
|
@collection.find_each(batch_size: 1000, &block)
|
66
|
-
elsif @collection.respond_to?
|
69
|
+
elsif @collection.respond_to? :each
|
67
70
|
@collection.each(&block)
|
68
71
|
else
|
69
72
|
@collection.to_a.each(&block)
|
@@ -71,7 +74,7 @@ module RailsExcelReporter
|
|
71
74
|
end
|
72
75
|
|
73
76
|
def stream_small_dataset(&block)
|
74
|
-
if @collection.respond_to?
|
77
|
+
if @collection.respond_to? :each
|
75
78
|
@collection.each(&block)
|
76
79
|
else
|
77
80
|
@collection.to_a.each(&block)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module RailsExcelReporter
|
2
2
|
module Styling
|
3
3
|
def self.included(base)
|
4
|
-
base.extend
|
4
|
+
base.extend ClassMethods
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
@@ -16,7 +16,7 @@ module RailsExcelReporter
|
|
16
16
|
|
17
17
|
def inherited(subclass)
|
18
18
|
super
|
19
|
-
subclass.instance_variable_set
|
19
|
+
subclass.instance_variable_set :@styles, @styles.dup if @styles
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -24,36 +24,37 @@ module RailsExcelReporter
|
|
24
24
|
style_options = self.class.styles[style_name.to_sym] || {}
|
25
25
|
return unless style_options.any?
|
26
26
|
|
27
|
-
worksheet.add_style
|
27
|
+
worksheet.add_style cell_range, style_options
|
28
28
|
end
|
29
29
|
|
30
30
|
def build_caxlsx_style(style_options)
|
31
31
|
caxlsx_options = {}
|
32
|
+
apply_style_mappings caxlsx_options, style_options
|
33
|
+
caxlsx_options
|
34
|
+
end
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
caxlsx_options[:fg_color] = style_options[:fg_color] if style_options[:fg_color]
|
36
|
-
|
37
|
-
caxlsx_options[:b] = style_options[:bold] if style_options[:bold]
|
38
|
-
|
39
|
-
caxlsx_options[:i] = style_options[:italic] if style_options[:italic]
|
40
|
-
|
41
|
-
caxlsx_options[:alignment] = style_options[:alignment] if style_options[:alignment]
|
42
|
-
|
43
|
-
caxlsx_options[:border] = style_options[:border] if style_options[:border]
|
44
|
-
|
45
|
-
caxlsx_options[:sz] = style_options[:font_size] if style_options[:font_size]
|
36
|
+
private
|
46
37
|
|
47
|
-
|
38
|
+
def apply_style_mappings(caxlsx_options, style_options)
|
39
|
+
style_mappings.each do |from_key, to_key|
|
40
|
+
caxlsx_options[to_key] = style_options[from_key] if style_options[from_key]
|
41
|
+
end
|
42
|
+
end
|
48
43
|
|
49
|
-
|
44
|
+
def style_mappings
|
45
|
+
{
|
46
|
+
bg_color: :bg_color, fg_color: :fg_color, bold: :b, italic: :i,
|
47
|
+
alignment: :alignment, border: :border, font_size: :sz, font_name: :font_name
|
48
|
+
}
|
50
49
|
end
|
51
50
|
|
51
|
+
public
|
52
|
+
|
52
53
|
def merge_styles(*style_names)
|
53
54
|
merged = {}
|
54
55
|
style_names.each do |style_name|
|
55
56
|
style_options = self.class.styles[style_name.to_sym] || {}
|
56
|
-
merged = deep_merge_hashes
|
57
|
+
merged = deep_merge_hashes merged, style_options
|
57
58
|
end
|
58
59
|
merged
|
59
60
|
end
|
@@ -61,13 +62,13 @@ module RailsExcelReporter
|
|
61
62
|
def get_column_style(column_name)
|
62
63
|
column_style = self.class.styles[column_name.to_sym] || {}
|
63
64
|
default_style = RailsExcelReporter.config.default_styles[:cell] || {}
|
64
|
-
deep_merge_hashes
|
65
|
+
deep_merge_hashes default_style, column_style
|
65
66
|
end
|
66
67
|
|
67
68
|
def get_header_style
|
68
69
|
header_style = self.class.styles[:header] || {}
|
69
70
|
default_style = RailsExcelReporter.config.default_styles[:header] || {}
|
70
|
-
deep_merge_hashes
|
71
|
+
deep_merge_hashes default_style, header_style
|
71
72
|
end
|
72
73
|
|
73
74
|
private
|
@@ -75,13 +76,17 @@ module RailsExcelReporter
|
|
75
76
|
def deep_merge_hashes(hash1, hash2)
|
76
77
|
result = hash1.dup
|
77
78
|
hash2.each do |key, value|
|
78
|
-
result[key] =
|
79
|
-
deep_merge_hashes(result[key], value)
|
80
|
-
else
|
81
|
-
value
|
82
|
-
end
|
79
|
+
result[key] = merge_hash_value result[key], value
|
83
80
|
end
|
84
81
|
result
|
85
82
|
end
|
83
|
+
|
84
|
+
def merge_hash_value(existing_value, new_value)
|
85
|
+
if existing_value.is_a?(Hash) && new_value.is_a?(Hash)
|
86
|
+
deep_merge_hashes existing_value, new_value
|
87
|
+
else
|
88
|
+
new_value
|
89
|
+
end
|
90
|
+
end
|
86
91
|
end
|
87
92
|
end
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = 'A Ruby gem that integrates seamlessly with Ruby on Rails to generate Excel reports using a simple DSL. Features include streaming, styling, callbacks, and Rails helpers.'
|
11
11
|
spec.homepage = 'https://github.com/EliSebastian/rails-excel-reporter.git'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new '>= 3.2.0'
|
14
14
|
|
15
15
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
16
16
|
spec.metadata['homepage_uri'] = spec.homepage
|
@@ -22,14 +22,13 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
|
-
spec.add_dependency 'activesupport', '>=
|
25
|
+
spec.add_dependency 'activesupport', '>= 8.0'
|
26
26
|
spec.add_dependency 'caxlsx', '~> 4.0'
|
27
|
-
spec.add_dependency 'rails', '>=
|
27
|
+
spec.add_dependency 'rails', '>= 8.0'
|
28
28
|
|
29
29
|
spec.add_development_dependency 'pry', '~> 0.14'
|
30
30
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
31
31
|
spec.add_development_dependency 'rspec-rails', '~> 5.0'
|
32
|
-
spec.add_development_dependency 'rubocop', '~> 1.0'
|
33
32
|
spec.add_development_dependency 'simplecov', '~> 0.21'
|
34
33
|
spec.add_development_dependency 'sqlite3', '~> 1.4'
|
35
34
|
spec.add_development_dependency 'yard', '~> 0.9'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe RailsExcelReporter::Base do
|
4
|
-
let
|
4
|
+
let :sample_data do
|
5
5
|
[
|
6
6
|
OpenStruct.new(id: 1, name: 'John Doe', email: 'john@example.com', created_at: Time.parse('2024-01-01')),
|
7
7
|
OpenStruct.new(id: 2, name: 'Jane Smith', email: 'jane@example.com', created_at: Time.parse('2024-01-02')),
|
@@ -9,8 +9,8 @@ RSpec.describe RailsExcelReporter::Base do
|
|
9
9
|
]
|
10
10
|
end
|
11
11
|
|
12
|
-
let
|
13
|
-
Class.new
|
12
|
+
let :report_class do
|
13
|
+
Class.new RailsExcelReporter::Base do
|
14
14
|
attributes :id, :name, :email
|
15
15
|
|
16
16
|
def name
|
@@ -19,7 +19,7 @@ RSpec.describe RailsExcelReporter::Base do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
let(:report) { report_class.new
|
22
|
+
let(:report) { report_class.new sample_data }
|
23
23
|
|
24
24
|
describe 'class methods' do
|
25
25
|
describe '.attributes' do
|
@@ -34,7 +34,7 @@ RSpec.describe RailsExcelReporter::Base do
|
|
34
34
|
|
35
35
|
describe '.attribute' do
|
36
36
|
it 'adds a single attribute with custom header' do
|
37
|
-
klass = Class.new
|
37
|
+
klass = Class.new RailsExcelReporter::Base
|
38
38
|
klass.attribute :custom_field, header: 'Custom Header'
|
39
39
|
|
40
40
|
expect(klass.attributes).to contain_exactly(
|
@@ -51,7 +51,7 @@ RSpec.describe RailsExcelReporter::Base do
|
|
51
51
|
end
|
52
52
|
|
53
53
|
it 'accepts custom worksheet name' do
|
54
|
-
custom_report = report_class.new
|
54
|
+
custom_report = report_class.new sample_data, worksheet_name: 'Custom Sheet'
|
55
55
|
expect(custom_report.worksheet_name).to eq('Custom Sheet')
|
56
56
|
end
|
57
57
|
|
@@ -94,8 +94,8 @@ RSpec.describe RailsExcelReporter::Base do
|
|
94
94
|
end
|
95
95
|
|
96
96
|
it 'returns true for large collections' do
|
97
|
-
large_data = (1..2000).map { |i| OpenStruct.new
|
98
|
-
large_report = report_class.new
|
97
|
+
large_data = (1..2000).map { |i| OpenStruct.new id: i, name: "User #{i}" }
|
98
|
+
large_report = report_class.new large_data
|
99
99
|
expect(large_report.should_stream?).to be true
|
100
100
|
end
|
101
101
|
end
|
@@ -116,10 +116,10 @@ RSpec.describe RailsExcelReporter::Base do
|
|
116
116
|
|
117
117
|
it 'saves to file' do
|
118
118
|
temp_path = '/tmp/test_report.xlsx'
|
119
|
-
report.save_to
|
119
|
+
report.save_to temp_path
|
120
120
|
expect(File.exist?(temp_path)).to be true
|
121
121
|
expect(File.size(temp_path)).to be > 0
|
122
|
-
File.delete
|
122
|
+
File.delete temp_path
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
@@ -135,16 +135,16 @@ RSpec.describe RailsExcelReporter::Base do
|
|
135
135
|
|
136
136
|
describe 'error handling' do
|
137
137
|
it 'raises error when no attributes are defined' do
|
138
|
-
empty_class = Class.new
|
139
|
-
empty_report = empty_class.new
|
138
|
+
empty_class = Class.new RailsExcelReporter::Base
|
139
|
+
empty_report = empty_class.new []
|
140
140
|
|
141
141
|
expect { empty_report.to_xlsx }.to raise_error(RuntimeError, /No attributes defined/)
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
145
|
describe 'callbacks' do
|
146
|
-
let
|
147
|
-
Class.new
|
146
|
+
let :callback_class do
|
147
|
+
Class.new RailsExcelReporter::Base do
|
148
148
|
attributes :id, :name
|
149
149
|
|
150
150
|
attr_reader :before_render_called, :after_render_called,
|
@@ -176,7 +176,7 @@ RSpec.describe RailsExcelReporter::Base do
|
|
176
176
|
end
|
177
177
|
end
|
178
178
|
|
179
|
-
let(:callback_report) { callback_class.new
|
179
|
+
let(:callback_report) { callback_class.new sample_data }
|
180
180
|
|
181
181
|
it 'calls all callbacks in correct order' do
|
182
182
|
callback_report.to_xlsx
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe RailsExcelReporter::ControllerHelpers do
|
4
|
-
let
|
4
|
+
let :controller_class do
|
5
5
|
Class.new do
|
6
6
|
include RailsExcelReporter::ControllerHelpers
|
7
7
|
|
@@ -24,7 +24,7 @@ RSpec.describe RailsExcelReporter::ControllerHelpers do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
let
|
27
|
+
let :mock_response do
|
28
28
|
Class.new do
|
29
29
|
attr_accessor :headers, :body
|
30
30
|
|
@@ -36,28 +36,28 @@ RSpec.describe RailsExcelReporter::ControllerHelpers do
|
|
36
36
|
|
37
37
|
let(:controller) { controller_class.new }
|
38
38
|
|
39
|
-
let
|
40
|
-
Class.new
|
39
|
+
let :report_class do
|
40
|
+
Class.new RailsExcelReporter::Base do
|
41
41
|
attributes :id, :name
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
let
|
45
|
+
let :sample_data do
|
46
46
|
[
|
47
47
|
OpenStruct.new(id: 1, name: 'John'),
|
48
48
|
OpenStruct.new(id: 2, name: 'Jane')
|
49
49
|
]
|
50
50
|
end
|
51
51
|
|
52
|
-
let(:report) { report_class.new
|
52
|
+
let(:report) { report_class.new sample_data }
|
53
53
|
|
54
54
|
before do
|
55
|
-
stub_const
|
55
|
+
stub_const 'MockResponse', mock_response
|
56
56
|
end
|
57
57
|
|
58
58
|
describe '#send_excel_report' do
|
59
59
|
it 'sends Excel data with default filename' do
|
60
|
-
controller.send_excel_report
|
60
|
+
controller.send_excel_report report
|
61
61
|
|
62
62
|
expect(controller.sent_data).to be_a(String)
|
63
63
|
expect(controller.send_options[:filename]).to match(/\.xlsx$/)
|
@@ -66,13 +66,13 @@ RSpec.describe RailsExcelReporter::ControllerHelpers do
|
|
66
66
|
end
|
67
67
|
|
68
68
|
it 'sends Excel data with custom filename' do
|
69
|
-
controller.send_excel_report
|
69
|
+
controller.send_excel_report report, filename: 'custom_report.xlsx'
|
70
70
|
|
71
71
|
expect(controller.send_options[:filename]).to eq('custom_report.xlsx')
|
72
72
|
end
|
73
73
|
|
74
74
|
it 'sends Excel data with custom disposition' do
|
75
|
-
controller.send_excel_report
|
75
|
+
controller.send_excel_report report, disposition: 'inline'
|
76
76
|
|
77
77
|
expect(controller.send_options[:disposition]).to eq('inline')
|
78
78
|
end
|
@@ -80,9 +80,10 @@ RSpec.describe RailsExcelReporter::ControllerHelpers do
|
|
80
80
|
|
81
81
|
describe '#stream_excel_report' do
|
82
82
|
it 'sets up streaming response headers' do
|
83
|
-
controller.stream_excel_report
|
83
|
+
controller.stream_excel_report report
|
84
84
|
|
85
|
-
|
85
|
+
expected_content_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
86
|
+
expect(controller.response.headers['Content-Type']).to eq(expected_content_type)
|
86
87
|
expect(controller.response.headers['Content-Disposition']).to match(/attachment; filename=/)
|
87
88
|
expect(controller.response.headers['Content-Transfer-Encoding']).to eq('binary')
|
88
89
|
expect(controller.response.headers['Last-Modified']).to be_present
|
@@ -90,7 +91,7 @@ RSpec.describe RailsExcelReporter::ControllerHelpers do
|
|
90
91
|
end
|
91
92
|
|
92
93
|
it 'uses custom filename in Content-Disposition' do
|
93
|
-
controller.stream_excel_report
|
94
|
+
controller.stream_excel_report report, filename: 'custom_stream.xlsx'
|
94
95
|
|
95
96
|
expect(controller.response.headers['Content-Disposition']).to include('custom_stream.xlsx')
|
96
97
|
end
|
@@ -101,21 +102,21 @@ RSpec.describe RailsExcelReporter::ControllerHelpers do
|
|
101
102
|
allow(report).to receive(:should_stream?).and_return(false)
|
102
103
|
expect(controller).to receive(:send_excel_report).with(report, {})
|
103
104
|
|
104
|
-
controller.excel_report_response
|
105
|
+
controller.excel_report_response report
|
105
106
|
end
|
106
107
|
|
107
108
|
it 'uses stream_excel_report for large reports' do
|
108
109
|
allow(report).to receive(:should_stream?).and_return(true)
|
109
110
|
expect(controller).to receive(:stream_excel_report).with(report, {})
|
110
111
|
|
111
|
-
controller.excel_report_response
|
112
|
+
controller.excel_report_response report
|
112
113
|
end
|
113
114
|
|
114
115
|
it 'passes options to the appropriate method' do
|
115
116
|
allow(report).to receive(:should_stream?).and_return(false)
|
116
117
|
expect(controller).to receive(:send_excel_report).with(report, { filename: 'test.xlsx' })
|
117
118
|
|
118
|
-
controller.excel_report_response
|
119
|
+
controller.excel_report_response report, filename: 'test.xlsx'
|
119
120
|
end
|
120
121
|
end
|
121
122
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe RailsExcelReporter::Streaming do
|
4
|
-
let
|
5
|
-
(1..10).map { |i| OpenStruct.new
|
4
|
+
let :small_data do
|
5
|
+
(1..10).map { |i| OpenStruct.new id: i, name: "User #{i}" }
|
6
6
|
end
|
7
7
|
|
8
|
-
let
|
9
|
-
(1..2000).map { |i| OpenStruct.new
|
8
|
+
let :large_data do
|
9
|
+
(1..2000).map { |i| OpenStruct.new id: i, name: "User #{i}" }
|
10
10
|
end
|
11
11
|
|
12
|
-
let
|
13
|
-
Class.new
|
12
|
+
let :report_class do
|
13
|
+
Class.new RailsExcelReporter::Base do
|
14
14
|
attributes :id, :name
|
15
15
|
end
|
16
16
|
end
|
@@ -28,44 +28,44 @@ RSpec.describe RailsExcelReporter::Streaming do
|
|
28
28
|
|
29
29
|
describe '#should_stream?' do
|
30
30
|
it 'returns false for small collections' do
|
31
|
-
report = report_class.new
|
31
|
+
report = report_class.new small_data
|
32
32
|
expect(report.should_stream?).to be false
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'returns true for large collections' do
|
36
|
-
report = report_class.new
|
36
|
+
report = report_class.new large_data
|
37
37
|
expect(report.should_stream?).to be true
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
describe '#collection_size' do
|
42
42
|
it 'calculates size for arrays' do
|
43
|
-
report = report_class.new
|
43
|
+
report = report_class.new small_data
|
44
44
|
expect(report.collection_size).to eq(10)
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'uses count method when available' do
|
48
|
-
mock_collection = double
|
48
|
+
mock_collection = double 'Collection'
|
49
49
|
allow(mock_collection).to receive(:count).and_return(100)
|
50
50
|
|
51
|
-
report = report_class.new
|
51
|
+
report = report_class.new mock_collection
|
52
52
|
expect(report.collection_size).to eq(100)
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'falls back to size method' do
|
56
|
-
mock_collection = double
|
56
|
+
mock_collection = double 'Collection'
|
57
57
|
allow(mock_collection).to receive(:respond_to?).with(:count).and_return(false)
|
58
58
|
allow(mock_collection).to receive(:respond_to?).with(:size).and_return(true)
|
59
59
|
allow(mock_collection).to receive(:size).and_return(50)
|
60
60
|
|
61
|
-
report = report_class.new
|
61
|
+
report = report_class.new mock_collection
|
62
62
|
expect(report.collection_size).to eq(50)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
66
|
describe '#stream_data' do
|
67
67
|
it 'yields each item in small collections' do
|
68
|
-
report = report_class.new
|
68
|
+
report = report_class.new small_data
|
69
69
|
yielded_items = []
|
70
70
|
|
71
71
|
report.stream_data do |item|
|
@@ -76,7 +76,7 @@ RSpec.describe RailsExcelReporter::Streaming do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'uses find_each for large ActiveRecord-like collections' do
|
79
|
-
mock_collection = double
|
79
|
+
mock_collection = double 'ActiveRecord Collection'
|
80
80
|
allow(mock_collection).to receive(:respond_to?).with(:count).and_return(true)
|
81
81
|
allow(mock_collection).to receive(:count).and_return(2000)
|
82
82
|
allow(mock_collection).to receive(:respond_to?).with(:find_each).and_return(true)
|
@@ -86,7 +86,7 @@ RSpec.describe RailsExcelReporter::Streaming do
|
|
86
86
|
large_data.each(&block)
|
87
87
|
end
|
88
88
|
|
89
|
-
report = report_class.new
|
89
|
+
report = report_class.new mock_collection
|
90
90
|
|
91
91
|
report.stream_data do |item|
|
92
92
|
yielded_items << item
|
@@ -96,7 +96,7 @@ RSpec.describe RailsExcelReporter::Streaming do
|
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'returns enumerator when no block given' do
|
99
|
-
report = report_class.new
|
99
|
+
report = report_class.new small_data
|
100
100
|
enumerator = report.stream_data
|
101
101
|
|
102
102
|
expect(enumerator).to be_a(Enumerator)
|
@@ -106,7 +106,7 @@ RSpec.describe RailsExcelReporter::Streaming do
|
|
106
106
|
|
107
107
|
describe '#with_progress_tracking' do
|
108
108
|
it 'tracks progress and yields items with progress info' do
|
109
|
-
report = report_class.new
|
109
|
+
report = report_class.new small_data
|
110
110
|
progress_updates = []
|
111
111
|
|
112
112
|
report.with_progress_tracking do |_item, progress|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe RailsExcelReporter::Styling do
|
4
|
-
let
|
5
|
-
Class.new
|
4
|
+
let :styled_class do
|
5
|
+
Class.new RailsExcelReporter::Base do
|
6
6
|
attributes :id, :name, :email
|
7
7
|
|
8
8
|
style :header, {
|
@@ -22,14 +22,14 @@ RSpec.describe RailsExcelReporter::Styling do
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
let
|
25
|
+
let :sample_data do
|
26
26
|
[
|
27
27
|
OpenStruct.new(id: 1, name: 'John', email: 'john@example.com'),
|
28
28
|
OpenStruct.new(id: 2, name: 'Jane', email: 'jane@example.com')
|
29
29
|
]
|
30
30
|
end
|
31
31
|
|
32
|
-
let(:report) { styled_class.new
|
32
|
+
let(:report) { styled_class.new sample_data }
|
33
33
|
|
34
34
|
describe 'class methods' do
|
35
35
|
describe '.style' do
|
@@ -66,12 +66,12 @@ RSpec.describe RailsExcelReporter::Styling do
|
|
66
66
|
|
67
67
|
describe '#get_column_style' do
|
68
68
|
it 'returns merged column style with defaults' do
|
69
|
-
id_style = report.get_column_style
|
69
|
+
id_style = report.get_column_style :id
|
70
70
|
expect(id_style[:alignment]).to eq({ horizontal: :center })
|
71
71
|
end
|
72
72
|
|
73
73
|
it 'returns default style for undefined columns' do
|
74
|
-
email_style = report.get_column_style
|
74
|
+
email_style = report.get_column_style :email
|
75
75
|
expect(email_style).to eq(RailsExcelReporter.config.default_styles[:cell])
|
76
76
|
end
|
77
77
|
end
|
@@ -86,7 +86,7 @@ RSpec.describe RailsExcelReporter::Styling do
|
|
86
86
|
alignment: { horizontal: :center }
|
87
87
|
}
|
88
88
|
|
89
|
-
caxlsx_style = report.build_caxlsx_style
|
89
|
+
caxlsx_style = report.build_caxlsx_style style_options
|
90
90
|
|
91
91
|
expect(caxlsx_style[:bg_color]).to eq('FF0000')
|
92
92
|
expect(caxlsx_style[:fg_color]).to eq('FFFFFF')
|
@@ -101,7 +101,7 @@ RSpec.describe RailsExcelReporter::Styling do
|
|
101
101
|
styled_class.style :base, { bold: true, font_size: 10 }
|
102
102
|
styled_class.style :override, { font_size: 12, italic: true }
|
103
103
|
|
104
|
-
merged = report.merge_styles
|
104
|
+
merged = report.merge_styles :base, :override
|
105
105
|
|
106
106
|
expect(merged[:bold]).to be true
|
107
107
|
expect(merged[:font_size]).to eq(12)
|
@@ -112,7 +112,7 @@ RSpec.describe RailsExcelReporter::Styling do
|
|
112
112
|
|
113
113
|
describe 'inheritance' do
|
114
114
|
it 'inherits styles from parent class' do
|
115
|
-
child_class = Class.new
|
115
|
+
child_class = Class.new styled_class do
|
116
116
|
style :email, { italic: true }
|
117
117
|
end
|
118
118
|
|
data/spec/spec_helper.rb
CHANGED
@@ -24,7 +24,7 @@ RSpec.configure do |config|
|
|
24
24
|
config.order = :random
|
25
25
|
Kernel.srand config.seed
|
26
26
|
|
27
|
-
config.before
|
28
|
-
RailsExcelReporter.instance_variable_set
|
27
|
+
config.before :each do
|
28
|
+
RailsExcelReporter.instance_variable_set :@configuration, nil
|
29
29
|
end
|
30
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-excel-reporter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elí Sebastian Herrera Aguilar
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '8.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '8.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: caxlsx
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '8.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '8.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: pry
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,20 +94,6 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '5.0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: rubocop
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.0'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
98
|
name: simplecov
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -159,7 +145,6 @@ executables: []
|
|
159
145
|
extensions: []
|
160
146
|
extra_rdoc_files: []
|
161
147
|
files:
|
162
|
-
- CLAUDE.md
|
163
148
|
- Gemfile
|
164
149
|
- Gemfile.lock
|
165
150
|
- README.md
|
@@ -197,14 +182,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
197
182
|
requirements:
|
198
183
|
- - ">="
|
199
184
|
- !ruby/object:Gem::Version
|
200
|
-
version: 2.
|
185
|
+
version: 3.2.0
|
201
186
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
202
187
|
requirements:
|
203
188
|
- - ">="
|
204
189
|
- !ruby/object:Gem::Version
|
205
190
|
version: '0'
|
206
191
|
requirements: []
|
207
|
-
rubygems_version: 3.
|
192
|
+
rubygems_version: 3.4.1
|
208
193
|
signing_key:
|
209
194
|
specification_version: 4
|
210
195
|
summary: Generate Excel reports (.xlsx) in Rails with a simple DSL
|
data/CLAUDE.md
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
# CLAUDE.md
|
2
|
-
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
-
|
5
|
-
## Project Overview
|
6
|
-
|
7
|
-
This is a Ruby gem called `rails-excel-reporter` that provides a simple DSL for generating Excel reports (.xlsx format) in Ruby on Rails applications. The gem uses the `caxlsx` library (version 4.x) for Excel generation and includes features like streaming for large datasets, custom styling, callbacks, and Rails controller helpers.
|
8
|
-
|
9
|
-
## Development Commands
|
10
|
-
|
11
|
-
### Testing
|
12
|
-
```bash
|
13
|
-
# Run all tests
|
14
|
-
bundle exec rspec
|
15
|
-
|
16
|
-
# Run specific test file
|
17
|
-
bundle exec rspec spec/rails_excel_reporter/base_spec.rb
|
18
|
-
|
19
|
-
# Run tests with documentation format
|
20
|
-
bundle exec rspec --format documentation
|
21
|
-
```
|
22
|
-
|
23
|
-
### Code Quality
|
24
|
-
```bash
|
25
|
-
# Run linter
|
26
|
-
bundle exec rubocop
|
27
|
-
|
28
|
-
# Generate documentation
|
29
|
-
bundle exec yard
|
30
|
-
```
|
31
|
-
|
32
|
-
### Build and Install
|
33
|
-
```bash
|
34
|
-
# Build the gem
|
35
|
-
gem build rails_excel_reporter.gemspec
|
36
|
-
|
37
|
-
# Install locally
|
38
|
-
gem install rails-excel-reporter-0.1.0.gem
|
39
|
-
```
|
40
|
-
|
41
|
-
## Architecture
|
42
|
-
|
43
|
-
### Core Components
|
44
|
-
|
45
|
-
1. **`RailsExcelReporter::Base`** (`lib/rails_excel_reporter/base.rb`) - Main class that report classes inherit from. Contains the core DSL and rendering logic.
|
46
|
-
|
47
|
-
2. **Modules:**
|
48
|
-
- **`Styling`** (`lib/rails_excel_reporter/styling.rb`) - Handles custom styling for headers, columns, and cells
|
49
|
-
- **`Streaming`** (`lib/rails_excel_reporter/streaming.rb`) - Manages streaming for large datasets with configurable thresholds
|
50
|
-
- **`ControllerHelpers`** (`lib/rails_excel_reporter/controller_helpers.rb`) - Provides Rails controller methods for sending Excel responses
|
51
|
-
|
52
|
-
3. **`Configuration`** (`lib/rails_excel_reporter/configuration.rb`) - Global configuration management with defaults for styling, streaming thresholds, and file paths
|
53
|
-
|
54
|
-
4. **`ReportGenerator`** (`lib/generators/report/report_generator.rb`) - Rails generator for scaffolding new report classes
|
55
|
-
|
56
|
-
### Key Patterns
|
57
|
-
|
58
|
-
- **DSL Design**: Uses `attributes` class method to define report columns with support for custom headers and methods
|
59
|
-
- **Streaming**: Automatically streams large datasets (>1000 records by default) using `find_each` for ActiveRecord or manual iteration
|
60
|
-
- **Styling**: Supports custom styling through the `style` class method with conversion to caxlsx format
|
61
|
-
- **Callbacks**: Provides `before_render`, `after_render`, `before_row`, and `after_row` hooks
|
62
|
-
- **Flexible Data Sources**: Works with ActiveRecord collections, arrays, and any enumerable
|
63
|
-
|
64
|
-
### Report Class Structure
|
65
|
-
|
66
|
-
```ruby
|
67
|
-
class MyReport < RailsExcelReporter::Base
|
68
|
-
attributes :column1, :column2, { name: :column3, header: "Custom Header" }
|
69
|
-
|
70
|
-
style :header, { bg_color: "4472C4", fg_color: "FFFFFF", bold: true }
|
71
|
-
style :column1, { alignment: { horizontal: :center } }
|
72
|
-
|
73
|
-
# Custom attribute methods override default behavior
|
74
|
-
def column1
|
75
|
-
# Access current record via `object`
|
76
|
-
object.column1.upcase
|
77
|
-
end
|
78
|
-
end
|
79
|
-
```
|
80
|
-
|
81
|
-
### Error Handling
|
82
|
-
|
83
|
-
The gem defines specific error classes:
|
84
|
-
- `RailsExcelReporter::Error` - Base error class
|
85
|
-
- `AttributeNotFoundError` - When an attribute doesn't exist on the data source
|
86
|
-
- `InvalidConfigurationError` - For configuration issues
|
87
|
-
|
88
|
-
## File Structure
|
89
|
-
|
90
|
-
- `lib/rails_excel_reporter.rb` - Main entry point and module definition
|
91
|
-
- `lib/rails_excel_reporter/` - Core gem modules
|
92
|
-
- `lib/generators/` - Rails generator for creating report classes
|
93
|
-
- `spec/` - RSpec test suite
|
94
|
-
- `rails_excel_reporter.gemspec` - Gem specification
|