rails-excel-reporter 0.1.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/README.md ADDED
@@ -0,0 +1,506 @@
1
+ # Rails Excel Reporter
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/rails-excel-reporter.svg)](https://badge.fury.io/rb/rails-excel-reporter)
4
+ [![Build Status](https://github.com/rails-excel-reporter/rails-excel-reporter/workflows/CI/badge.svg)](https://github.com/rails-excel-reporter/rails-excel-reporter/actions)
5
+
6
+ A Ruby gem that integrates seamlessly with Ruby on Rails to generate Excel reports (.xlsx format) using a simple DSL. Features include streaming for large datasets, custom styling, callbacks, and Rails helpers.
7
+
8
+ ## Features
9
+
10
+ - 🚀 **Simple DSL** - Define reports with a clean, intuitive syntax
11
+ - 📊 **Excel Generation** - Create .xlsx files using the powerful caxlsx gem
12
+ - 🎨 **Custom Styling** - Apply styles to headers, columns, and cells
13
+ - 🔄 **Streaming Support** - Handle large datasets efficiently with streaming
14
+ - 📱 **Rails Integration** - Auto-registers with Rails, includes controller helpers
15
+ - 🔧 **Callbacks** - Hook into the generation process with before/after callbacks
16
+ - 🎯 **Flexible Data Sources** - Works with ActiveRecord, arrays, and any enumerable
17
+ - 📁 **Rails Generator** - Scaffold reports quickly with `rails g report`
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'rails-excel-reporter'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ bundle install
31
+ ```
32
+
33
+ Or install it yourself as:
34
+
35
+ ```bash
36
+ gem install rails-excel-reporter
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### 1. Create a Report
42
+
43
+ Create a report in `app/reports/`:
44
+
45
+ ```ruby
46
+ class UserReport < RailsExcelReporter::Base
47
+ attributes :id, :name, :email, :created_at
48
+
49
+ def created_at
50
+ object.created_at.strftime("%Y-%m-%d")
51
+ end
52
+ end
53
+ ```
54
+
55
+ ### 2. Use in Controller
56
+
57
+ ```ruby
58
+ class ReportsController < ApplicationController
59
+ def users
60
+ @users = User.active.order(:created_at)
61
+ report = UserReport.new(@users)
62
+ send_excel_report(report)
63
+ end
64
+ end
65
+ ```
66
+
67
+ ### 3. Generate with Rails Generator
68
+
69
+ ```bash
70
+ rails g report User name:string email:string role:string
71
+ ```
72
+
73
+ This creates `app/reports/user_report.rb` with the basic structure.
74
+
75
+ ## Basic Usage
76
+
77
+ ### Defining Attributes
78
+
79
+ ```ruby
80
+ class ProductReport < RailsExcelReporter::Base
81
+ # Simple attributes
82
+ attributes :id, :name, :price
83
+
84
+ # Custom headers
85
+ attributes :id, { name: :product_name, header: "Product Name" }, :price
86
+
87
+ # Or use individual attribute method
88
+ attribute :sku, header: "SKU Code"
89
+ end
90
+ ```
91
+
92
+ ### Custom Methods
93
+
94
+ Override attribute methods to customize output:
95
+
96
+ ```ruby
97
+ class OrderReport < RailsExcelReporter::Base
98
+ attributes :id, :customer_name, :total, :status
99
+
100
+ def customer_name
101
+ "#{object.customer.first_name} #{object.customer.last_name}"
102
+ end
103
+
104
+ def total
105
+ "$#{object.total.round(2)}"
106
+ end
107
+
108
+ def status
109
+ object.status.upcase
110
+ end
111
+ end
112
+ ```
113
+
114
+ ### Data Sources
115
+
116
+ Works with various data sources:
117
+
118
+ ```ruby
119
+ # ActiveRecord collections
120
+ report = UserReport.new(User.active)
121
+
122
+ # Arrays
123
+ users = [
124
+ { id: 1, name: "John", email: "john@example.com" },
125
+ { id: 2, name: "Jane", email: "jane@example.com" }
126
+ ]
127
+ report = UserReport.new(users)
128
+
129
+ # Any enumerable
130
+ report = UserReport.new(csv_data.lazy.map(&:to_h))
131
+ ```
132
+
133
+ ## Advanced Features
134
+
135
+ ### Streaming for Large Datasets
136
+
137
+ Automatically handles large datasets with streaming:
138
+
139
+ ```ruby
140
+ class LargeDataReport < RailsExcelReporter::Base
141
+ attributes :id, :name, :data
142
+
143
+ # Customize streaming threshold (default: 1000)
144
+ self.streaming_threshold = 5000
145
+ end
146
+
147
+ # Usage with progress tracking
148
+ report = LargeDataReport.new(huge_dataset) do |progress|
149
+ puts "Processing: #{progress.current}/#{progress.total} (#{progress.percentage}%)"
150
+ end
151
+ ```
152
+
153
+ ### Custom Styling
154
+
155
+ Apply custom styles to headers and columns:
156
+
157
+ ```ruby
158
+ class StyledReport < RailsExcelReporter::Base
159
+ attributes :id, :name, :email, :status
160
+
161
+ # Header styling
162
+ style :header, {
163
+ bg_color: "4472C4",
164
+ fg_color: "FFFFFF",
165
+ bold: true,
166
+ font_size: 12
167
+ }
168
+
169
+ # Column-specific styling
170
+ style :id, {
171
+ alignment: { horizontal: :center },
172
+ font_size: 10
173
+ }
174
+
175
+ style :status, {
176
+ bold: true,
177
+ bg_color: "E7E6E6"
178
+ }
179
+ end
180
+ ```
181
+
182
+ ### Callbacks
183
+
184
+ Hook into the generation process:
185
+
186
+ ```ruby
187
+ class CallbackReport < RailsExcelReporter::Base
188
+ attributes :id, :name, :email
189
+
190
+ def before_render
191
+ Rails.logger.info "Starting report generation at #{Time.current}"
192
+ end
193
+
194
+ def after_render
195
+ Rails.logger.info "Report generated successfully"
196
+ end
197
+
198
+ def before_row(object)
199
+ # Called before each row is processed
200
+ end
201
+
202
+ def after_row(object)
203
+ # Called after each row is processed
204
+ end
205
+ end
206
+ ```
207
+
208
+ ## Controller Integration
209
+
210
+ ### Helper Methods
211
+
212
+ The gem provides several helper methods for controllers:
213
+
214
+ ```ruby
215
+ class ReportsController < ApplicationController
216
+ def download_users
217
+ report = UserReport.new(User.all)
218
+
219
+ # Simple download
220
+ send_excel_report(report)
221
+
222
+ # With custom filename
223
+ send_excel_report(report, filename: "users_#{Date.current}.xlsx")
224
+
225
+ # Stream large reports
226
+ stream_excel_report(report)
227
+
228
+ # Automatic streaming based on size
229
+ excel_report_response(report)
230
+ end
231
+ end
232
+ ```
233
+
234
+ ### Response Methods
235
+
236
+ Available response methods:
237
+
238
+ ```ruby
239
+ # Basic file download
240
+ send_excel_report(report)
241
+
242
+ # Streaming response (for large files)
243
+ stream_excel_report(report)
244
+
245
+ # Automatic selection based on report size
246
+ excel_report_response(report)
247
+ ```
248
+
249
+ ## API Reference
250
+
251
+ ### Report Instance Methods
252
+
253
+ ```ruby
254
+ report = UserReport.new(users)
255
+
256
+ # Get the generated file
257
+ file = report.file # Returns Tempfile
258
+
259
+ # Get binary data
260
+ xlsx_data = report.to_xlsx # Returns String
261
+
262
+ # Save to specific path
263
+ report.save_to("/path/to/file.xlsx")
264
+
265
+ # Get suggested filename
266
+ filename = report.filename # Returns "user_report_2024_01_15.xlsx"
267
+
268
+ # Get IO stream
269
+ stream = report.stream # Returns StringIO
270
+
271
+ # Get hash representation
272
+ hash = report.to_h # Returns Hash with metadata
273
+ ```
274
+
275
+ ### Configuration
276
+
277
+ Configure the gem globally:
278
+
279
+ ```ruby
280
+ # config/initializers/rails_excel_reporter.rb
281
+ RailsExcelReporter.configure do |config|
282
+ config.default_styles = {
283
+ header: {
284
+ bg_color: "2E75B6",
285
+ fg_color: "FFFFFF",
286
+ bold: true
287
+ },
288
+ cell: {
289
+ border: { style: :thin, color: "CCCCCC" }
290
+ }
291
+ }
292
+
293
+ config.date_format = "%d/%m/%Y"
294
+ config.streaming_threshold = 2000
295
+ config.temp_directory = Rails.root.join("tmp", "reports")
296
+ end
297
+ ```
298
+
299
+ ## Error Handling
300
+
301
+ The gem includes comprehensive error handling:
302
+
303
+ ```ruby
304
+ begin
305
+ report = UserReport.new(users)
306
+ report.to_xlsx
307
+ rescue RailsExcelReporter::AttributeNotFoundError => e
308
+ Rails.logger.error "Missing attribute: #{e.message}"
309
+ rescue RailsExcelReporter::InvalidConfigurationError => e
310
+ Rails.logger.error "Configuration error: #{e.message}"
311
+ rescue RailsExcelReporter::Error => e
312
+ Rails.logger.error "Report generation failed: #{e.message}"
313
+ end
314
+ ```
315
+
316
+ ## Performance Considerations
317
+
318
+ ### Streaming
319
+
320
+ For large datasets (>1000 records by default), the gem automatically uses streaming:
321
+
322
+ ```ruby
323
+ # This will stream automatically
324
+ large_report = UserReport.new(User.limit(10000))
325
+ large_report.should_stream? # => true
326
+ ```
327
+
328
+ ### Memory Usage
329
+
330
+ The gem is designed to be memory-efficient:
331
+
332
+ - Uses streaming for large datasets
333
+ - Lazy evaluation where possible
334
+ - Efficient Excel generation with caxlsx
335
+ - Automatic garbage collection of temporary files
336
+
337
+ ## Testing
338
+
339
+ ### Test Helpers
340
+
341
+ The gem includes test helpers for easier testing:
342
+
343
+ ```ruby
344
+ RSpec.describe UserReport do
345
+ let(:users) { create_list(:user, 3) }
346
+ let(:report) { UserReport.new(users) }
347
+
348
+ describe "#to_xlsx" do
349
+ it "generates Excel file" do
350
+ xlsx_data = report.to_xlsx
351
+ expect(xlsx_data).to be_present
352
+ expect(xlsx_data[0, 4]).to eq("PK\x03\x04") # ZIP signature
353
+ end
354
+ end
355
+
356
+ describe "#filename" do
357
+ it "generates appropriate filename" do
358
+ expect(report.filename).to match(/user_report_\d{4}_\d{2}_\d{2}\.xlsx/)
359
+ end
360
+ end
361
+ end
362
+ ```
363
+
364
+ ### Running Tests
365
+
366
+ ```bash
367
+ # Run all tests
368
+ bundle exec rspec
369
+
370
+ # Run specific test file
371
+ bundle exec rspec spec/rails_excel_reporter/base_spec.rb
372
+
373
+ # Run with coverage
374
+ bundle exec rspec --format documentation
375
+ ```
376
+
377
+ ## Examples
378
+
379
+ ### Basic Report
380
+
381
+ ```ruby
382
+ class ProductReport < RailsExcelReporter::Base
383
+ attributes :id, :name, :price, :category
384
+
385
+ def price
386
+ "$#{object.price.round(2)}"
387
+ end
388
+
389
+ def category
390
+ object.category.name
391
+ end
392
+ end
393
+
394
+ # Usage
395
+ products = Product.includes(:category).order(:name)
396
+ report = ProductReport.new(products)
397
+ report.save_to("products.xlsx")
398
+ ```
399
+
400
+ ### Advanced Report with Styling
401
+
402
+ ```ruby
403
+ class SalesReport < RailsExcelReporter::Base
404
+ attributes :date, :product, :quantity, :revenue, :profit
405
+
406
+ style :header, {
407
+ bg_color: "1F4E79",
408
+ fg_color: "FFFFFF",
409
+ bold: true,
410
+ font_size: 14
411
+ }
412
+
413
+ style :revenue, {
414
+ bg_color: "E2EFDA",
415
+ alignment: { horizontal: :right }
416
+ }
417
+
418
+ style :profit, {
419
+ bg_color: "FCE4D6",
420
+ alignment: { horizontal: :right }
421
+ }
422
+
423
+ def date
424
+ object.created_at.strftime("%Y-%m-%d")
425
+ end
426
+
427
+ def product
428
+ object.product.name
429
+ end
430
+
431
+ def revenue
432
+ "$#{object.revenue.round(2)}"
433
+ end
434
+
435
+ def profit
436
+ "$#{object.profit.round(2)}"
437
+ end
438
+
439
+ def before_render
440
+ Rails.logger.info "Generating sales report for #{collection.count} records"
441
+ end
442
+ end
443
+ ```
444
+
445
+ ### Streaming Report with Progress
446
+
447
+ ```ruby
448
+ class MassiveDataReport < RailsExcelReporter::Base
449
+ attributes :id, :data, :processed_at
450
+
451
+ self.streaming_threshold = 10000
452
+
453
+ def processed_at
454
+ object.processed_at.strftime("%Y-%m-%d %H:%M:%S")
455
+ end
456
+ end
457
+
458
+ # Usage with progress tracking
459
+ report = MassiveDataReport.new(huge_dataset) do |progress|
460
+ puts "Progress: #{progress.percentage}% (#{progress.current}/#{progress.total})"
461
+ end
462
+ ```
463
+
464
+ ## Requirements
465
+
466
+ - Ruby 2.7+
467
+ - Rails 6.0+
468
+ - caxlsx ~> 4.0
469
+
470
+ ## Contributing
471
+
472
+ 1. Fork it
473
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
474
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
475
+ 4. Push to the branch (`git push origin feature/my-new-feature`)
476
+ 5. Create new Pull Request
477
+
478
+ ## Development
479
+
480
+ ```bash
481
+ # Clone the repository
482
+ git clone https://github.com/rails-excel-reporter/rails-excel-reporter.git
483
+ cd rails-excel-reporter
484
+
485
+ # Install dependencies
486
+ bundle install
487
+
488
+ # Run tests
489
+ bundle exec rspec
490
+
491
+ # Run linter
492
+ bundle exec rubocop
493
+
494
+ # Generate documentation
495
+ bundle exec yard
496
+ ```
497
+
498
+ ## License
499
+
500
+ This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
501
+
502
+ ## Support
503
+
504
+ - **Issues**: [GitHub Issues](https://github.com/rails-excel-reporter/rails-excel-reporter/issues)
505
+ - **Documentation**: [GitHub Wiki](https://github.com/rails-excel-reporter/rails-excel-reporter/wiki)
506
+ - **Changelog**: [CHANGELOG.md](https://github.com/rails-excel-reporter/rails-excel-reporter/blob/main/CHANGELOG.md)
@@ -0,0 +1,32 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/named_base'
3
+
4
+ module Rails
5
+ module Generators
6
+ class ReportGenerator < NamedBase
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
10
+
11
+ def create_report_file
12
+ template 'report.rb.erb', File.join('app/reports', class_path, "#{file_name}_report.rb")
13
+ end
14
+
15
+ private
16
+
17
+ def report_class_name
18
+ "#{class_name}Report"
19
+ end
20
+
21
+ def parsed_attributes
22
+ attributes.map do |attr|
23
+ { name: attr.name, type: attr.type }
24
+ end
25
+ end
26
+
27
+ def attribute_names
28
+ parsed_attributes.map { |attr| ":#{attr[:name]}" }.join(', ')
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ class <%= report_class_name %> < RailsExcelReporter::Base
2
+ <% if parsed_attributes.any? %>
3
+ attributes <%= attribute_names %>
4
+ <% else %>
5
+ attributes :id, :name, :created_at
6
+ <% end %>
7
+
8
+ <% parsed_attributes.each do |attr| %>
9
+ <% if attr[:type] == "date" || attr[:type] == "datetime" %>
10
+ def <%= attr[:name] %>
11
+ object.<%= attr[:name] %>&.strftime("%Y-%m-%d")
12
+ end
13
+ <% end %>
14
+ <% end %>
15
+
16
+ # Example of custom styling
17
+ # style :header, {
18
+ # bg_color: "4472C4",
19
+ # fg_color: "FFFFFF",
20
+ # bold: true
21
+ # }
22
+
23
+ # style :id, {
24
+ # alignment: { horizontal: :center }
25
+ # }
26
+
27
+ # Example of callbacks
28
+ # def before_render
29
+ # Rails.logger.info "Starting <%= report_class_name %> generation"
30
+ # end
31
+
32
+ # def after_render
33
+ # Rails.logger.info "Completed <%= report_class_name %> generation"
34
+ # end
35
+
36
+ # def before_row(object)
37
+ # # Called before each row
38
+ # end
39
+
40
+ # def after_row(object)
41
+ # # Called after each row
42
+ # end
43
+ end