activeadmin-axlsx 1.0.0 → 2.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.
data/.gitignore CHANGED
@@ -1 +1,4 @@
1
1
  spec/rails
2
+ *.gem
3
+ coverage
4
+ *.xlsx
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile CHANGED
@@ -1,10 +1,9 @@
1
1
  source 'https://rubygems.org'
2
-
2
+ gem 'axlsx'
3
3
  gemspec
4
- gem 'activeadmin'
4
+
5
5
  group :development, :test do
6
6
  gem 'sqlite3'
7
-
8
7
  gem 'rake', '~> 0.9.2.2', :require => false
9
8
  gem 'haml', '~> 3.1.1', :require => false
10
9
  gem 'yard'
@@ -12,10 +11,11 @@ group :development, :test do
12
11
  gem "sprockets"
13
12
  gem 'rails-i18n' # Gives us default i18n for many languages
14
13
  end
15
-
14
+ gem 'simplecov', :require => false, :group => :test
16
15
  group :test do
17
16
  gem 'inherited_resources'
18
17
  gem 'sass-rails'
18
+ gem 'rspec-mocks'
19
19
  gem 'rspec-rails', '~> 2.9.0'
20
20
  gem 'cucumber-rails', '1.2.1', :require => false
21
21
  gem 'capybara', '1.1.2'
@@ -1,26 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activeadmin-axlsx (1.0.0)
4
+ activeadmin-axlsx (2.0.0)
5
5
  activeadmin (>= 0.5.0)
6
6
  axlsx
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actionmailer (3.2.8)
12
- actionpack (= 3.2.8)
11
+ actionmailer (3.2.9)
12
+ actionpack (= 3.2.9)
13
13
  mail (~> 2.4.4)
14
- actionpack (3.2.8)
15
- activemodel (= 3.2.8)
16
- activesupport (= 3.2.8)
14
+ actionpack (3.2.9)
15
+ activemodel (= 3.2.9)
16
+ activesupport (= 3.2.9)
17
17
  builder (~> 3.0.0)
18
18
  erubis (~> 2.7.0)
19
19
  journey (~> 1.0.4)
20
20
  rack (~> 1.4.0)
21
21
  rack-cache (~> 1.2)
22
22
  rack-test (~> 0.6.1)
23
- sprockets (~> 2.1.3)
23
+ sprockets (~> 2.2.1)
24
24
  activeadmin (0.5.0)
25
25
  arbre (>= 1.0.1)
26
26
  bourbon (>= 1.0.0)
@@ -33,18 +33,18 @@ GEM
33
33
  meta_search (>= 0.9.2)
34
34
  rails (>= 3.0.0)
35
35
  sass (>= 3.1.0)
36
- activemodel (3.2.8)
37
- activesupport (= 3.2.8)
36
+ activemodel (3.2.9)
37
+ activesupport (= 3.2.9)
38
38
  builder (~> 3.0.0)
39
- activerecord (3.2.8)
40
- activemodel (= 3.2.8)
41
- activesupport (= 3.2.8)
39
+ activerecord (3.2.9)
40
+ activemodel (= 3.2.9)
41
+ activesupport (= 3.2.9)
42
42
  arel (~> 3.0.2)
43
43
  tzinfo (~> 0.3.29)
44
- activeresource (3.2.8)
45
- activemodel (= 3.2.8)
46
- activesupport (= 3.2.8)
47
- activesupport (3.2.8)
44
+ activeresource (3.2.9)
45
+ activemodel (= 3.2.9)
46
+ activesupport (= 3.2.9)
47
+ activesupport (3.2.9)
48
48
  i18n (~> 0.6)
49
49
  multi_json (~> 1.0)
50
50
  addressable (2.3.2)
@@ -56,8 +56,9 @@ GEM
56
56
  nokogiri (>= 1.4.1)
57
57
  rubyzip (>= 0.9.5)
58
58
  bcrypt-ruby (3.0.1)
59
- bourbon (2.1.1)
60
- sass (>= 3.1)
59
+ bourbon (2.1.2)
60
+ sass (>= 3.2)
61
+ thor
61
62
  builder (3.0.4)
62
63
  capybara (1.1.2)
63
64
  mime-types (>= 1.16)
@@ -84,13 +85,13 @@ GEM
84
85
  cucumber (>= 1.1.3)
85
86
  nokogiri (>= 1.5.0)
86
87
  database_cleaner (0.9.1)
87
- debugger (1.2.1)
88
+ debugger (1.2.2)
88
89
  columnize (>= 0.3.1)
89
90
  debugger-linecache (~> 1.1.1)
90
- debugger-ruby_core_source (~> 1.1.4)
91
+ debugger-ruby_core_source (~> 1.1.5)
91
92
  debugger-linecache (1.1.2)
92
93
  debugger-ruby_core_source (>= 1.1.1)
93
- debugger-ruby_core_source (1.1.4)
94
+ debugger-ruby_core_source (1.1.5)
94
95
  devise (2.1.2)
95
96
  bcrypt-ruby (~> 3.0)
96
97
  orm_adapter (~> 0.1)
@@ -101,17 +102,17 @@ GEM
101
102
  execjs (1.4.0)
102
103
  multi_json (~> 1.0)
103
104
  fastercsv (1.5.5)
104
- ffi (1.1.5)
105
+ ffi (1.2.0)
105
106
  formtastic (2.2.1)
106
107
  actionpack (>= 3.0)
107
108
  gherkin (2.11.5)
108
109
  json (>= 1.4.6)
109
- guard (1.5.1)
110
+ guard (1.5.4)
110
111
  listen (>= 0.4.2)
111
112
  lumberjack (>= 1.0.2)
112
113
  pry (>= 0.9.10)
113
114
  thor (>= 0.14.6)
114
- guard-coffeescript (1.2.0)
115
+ guard-coffeescript (1.2.1)
115
116
  coffee-script (>= 2.2.0)
116
117
  guard (>= 1.1.0)
117
118
  guard-rspec (1.2.1)
@@ -141,8 +142,8 @@ GEM
141
142
  activesupport (>= 3.0.0)
142
143
  launchy (2.1.2)
143
144
  addressable (~> 2.3)
144
- libwebsocket (0.1.5)
145
- addressable
145
+ libwebsocket (0.1.6.1)
146
+ websocket
146
147
  listen (0.5.3)
147
148
  lumberjack (1.0.2)
148
149
  mail (2.4.4)
@@ -156,7 +157,7 @@ GEM
156
157
  polyamorous (~> 0.5.0)
157
158
  method_source (0.8.1)
158
159
  mime-types (1.19)
159
- multi_json (1.3.6)
160
+ multi_json (1.3.7)
160
161
  nokogiri (1.5.5)
161
162
  orm_adapter (0.4.0)
162
163
  polyamorous (0.5.0)
@@ -173,19 +174,19 @@ GEM
173
174
  rack
174
175
  rack-test (0.6.2)
175
176
  rack (>= 1.0)
176
- rails (3.2.8)
177
- actionmailer (= 3.2.8)
178
- actionpack (= 3.2.8)
179
- activerecord (= 3.2.8)
180
- activeresource (= 3.2.8)
181
- activesupport (= 3.2.8)
177
+ rails (3.2.9)
178
+ actionmailer (= 3.2.9)
179
+ actionpack (= 3.2.9)
180
+ activerecord (= 3.2.9)
181
+ activeresource (= 3.2.9)
182
+ activesupport (= 3.2.9)
182
183
  bundler (~> 1.0)
183
- railties (= 3.2.8)
184
+ railties (= 3.2.9)
184
185
  rails-i18n (0.7.0)
185
186
  i18n (~> 0.5)
186
- railties (3.2.8)
187
- actionpack (= 3.2.8)
188
- activesupport (= 3.2.8)
187
+ railties (3.2.9)
188
+ actionpack (= 3.2.9)
189
+ activesupport (= 3.2.9)
189
190
  rack-ssl (~> 1.3.2)
190
191
  rake (>= 0.8.7)
191
192
  rdoc (~> 3.4)
@@ -210,31 +211,37 @@ GEM
210
211
  railties (>= 3.0)
211
212
  rspec (~> 2.9.0)
212
213
  rubyzip (0.9.9)
213
- sass (3.2.1)
214
+ sass (3.2.3)
214
215
  sass-rails (3.2.5)
215
216
  railties (~> 3.2.0)
216
217
  sass (>= 3.1.10)
217
218
  tilt (~> 1.3)
218
- selenium-webdriver (2.25.0)
219
+ selenium-webdriver (2.26.0)
219
220
  childprocess (>= 0.2.5)
220
221
  libwebsocket (~> 0.1.3)
221
222
  multi_json (~> 1.0)
222
223
  rubyzip
223
224
  shoulda-matchers (1.0.0)
225
+ simplecov (0.7.1)
226
+ multi_json (~> 1.0)
227
+ simplecov-html (~> 0.7.1)
228
+ simplecov-html (0.7.1)
224
229
  slop (3.3.3)
225
- sprockets (2.1.3)
230
+ sprockets (2.2.1)
226
231
  hike (~> 1.2)
232
+ multi_json (~> 1.0)
227
233
  rack (~> 1.0)
228
234
  tilt (~> 1.1, != 1.3.0)
229
235
  sqlite3 (1.3.6)
230
236
  thor (0.16.0)
231
237
  tilt (1.3.3)
232
- treetop (1.4.11)
238
+ treetop (1.4.12)
233
239
  polyglot
234
240
  polyglot (>= 0.3.1)
235
- tzinfo (0.3.34)
241
+ tzinfo (0.3.35)
236
242
  warden (1.2.1)
237
243
  rack (>= 1.0)
244
+ websocket (1.0.3)
238
245
  xpath (0.1.4)
239
246
  nokogiri (~> 1.3)
240
247
  yard (0.8.3)
@@ -243,8 +250,8 @@ PLATFORMS
243
250
  ruby
244
251
 
245
252
  DEPENDENCIES
246
- activeadmin
247
253
  activeadmin-axlsx!
254
+ axlsx
248
255
  capybara (= 1.1.2)
249
256
  cucumber-rails (= 1.2.1)
250
257
  database_cleaner
@@ -259,9 +266,11 @@ DEPENDENCIES
259
266
  rails-i18n
260
267
  rake (~> 0.9.2.2)
261
268
  rdiscount
269
+ rspec-mocks
262
270
  rspec-rails (~> 2.9.0)
263
271
  sass-rails
264
272
  shoulda-matchers (= 1.0.0)
273
+ simplecov
265
274
  sprockets
266
275
  sqlite3
267
276
  yard
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Active Admin Axlsx: Office Open XML Spreadsheet Export for Active Admin
2
2
  ====================================
3
3
 
4
- If you are using this for comercial purposes, or just want to show your
4
+ If you are using this for commercial purposes, or just want to show your
5
5
  appreciation for the gem, please don't hesitate to make a donation.
6
6
 
7
7
  [![Click here to lend your support to: axlsx and make a donation at www.pledgie.com !](http://www.pledgie.com/campaigns/17814.png?skin_name=chrome)](http://www.pledgie.com/campaigns/17814)
@@ -38,12 +38,10 @@ resources. It lets you harness the full power of Axlsx when you want to
38
38
  but for the most part just stays out of your way and adds a link next to
39
39
  the csv download for xlsx (Excel/numbers/Libre Office/Google Docs)
40
40
 
41
- git ls-files -- {test,spec,features}/*
42
-
43
41
  ![Screen 1](https://github.com/randym/activeadmin-axlsx/raw/master/screen_capture.png)
44
42
 
45
43
  Usage example:
46
- Simple add the following to your gemfile and you are good to go.
44
+ Simply add the following to your Gemfile and you are good to go.
47
45
  All resource index views will now include a link for download directly
48
46
  to xlsx.
49
47
 
@@ -62,7 +60,7 @@ something adventurous please ping me on irc. (freenode#axlsx)
62
60
 
63
61
  ```ruby
64
62
  #app/admin/posts.rb
65
- ActiveAdmin.register Posts do
63
+ ActiveAdmin.register Post do
66
64
  config.xlsx_builder.i18n_scope [:active_record, :models, :posts]
67
65
  end
68
66
  ```
@@ -71,7 +69,7 @@ end
71
69
 
72
70
  ```ruby
73
71
  #app/admin/posts.rb
74
- ActiveAdmin.register Posts do
72
+ ActiveAdmin.register Post do
75
73
  config.xlsx_builder.column('author_name') do |resource|
76
74
  resource.author.name
77
75
  end
@@ -82,12 +80,68 @@ end
82
80
 
83
81
  ```ruby
84
82
  #app/admin/posts.rb
85
- ActiveAdmin.register Posts do
83
+ ActiveAdmin.register Post do
86
84
  config.xlsx_builder.header_style = { :bg_color => 'FF0000',
87
85
  :fg_color => 'FF' }
88
86
  end
89
87
  ```
90
88
 
89
+ ##Remove columns
90
+
91
+ ```ruby
92
+ #app/admin/posts.rb
93
+ ActiveAdmin.register Post do
94
+ config.xlsx_builder.delete_columns :id, :created_at, :updated_at
95
+ end
96
+ ```
97
+
98
+ #Using the DSL
99
+
100
+ Everything that you do with the config'd default builder can be done via
101
+ the resource DSL.
102
+
103
+ Below is an example of the DSL
104
+
105
+ ```ruby
106
+ ActiveAdmin.register Post do
107
+ # i18n_scope and header style are set via options
108
+ xlsx(:i18n_scope => [:active_admin, :axlsx, :post],
109
+ :header_style => {:bg_color => 'FF0000', :fg_color => 'FF' }) do
110
+
111
+ # deleting columns from the report
112
+ delete_columns :id, :created_at, :updated_at
113
+
114
+ # adding a column to the report
115
+ column(:author) { |resource| "#{resource.author.first_name} #{resource.author.last_name}" }
116
+
117
+ # creating a chart and inserting additional data with after_filter
118
+ after_filter { |sheet|
119
+ sheet.add_row []
120
+ sheet.add_row ['Author Name', 'Number of Posts']
121
+ data = []
122
+ labels = []
123
+ User.all.each do |user|
124
+ data << user.posts.size
125
+ labels << "#{user.first_name} #{user.last_name}"
126
+ sheet.add_row [labels.last, data.last]
127
+ end
128
+ chart_color = %w(88F700 279CAC B2A200 FD66A3 F20062 C8BA2B 67E6F8 DFFDB9 FFE800 B6F0F8)
129
+ sheet.add_chart(::Axlsx::Pie3DChart, :title => "post by author") do |chart|
130
+ chart.add_series :data => data, :labels => labels, :colors => chart_color
131
+ chart.start_at 4, 0
132
+ chart.end_at 7, 20
133
+ end
134
+ }
135
+
136
+ # iserting data with before_filter
137
+ before_filter do |sheet|
138
+ sheet.add_row ['Created', Time.zone.now]
139
+ sheet.add_row []
140
+ end
141
+ end
142
+ end
143
+ ```
144
+
91
145
  #Specs
92
146
  ------
93
147
  Running specs for this gem requires that you construct a rails application.
@@ -101,6 +155,15 @@ bundle exec rake setup
101
155
  ```
102
156
  bundle exec rake
103
157
  ```
158
+ # Changelog
159
+ **2012.11.29** Release 2.0.0
160
+ - resouce content column are now pre-populated.
161
+ - added before and after filters
162
+ - 100% spec coverage
163
+
164
+ **2012.11.16**
165
+ - Fixed DSL referencing
166
+ - Added delete_columns to builder and DSL
104
167
 
105
168
  #Copyright and License
106
169
  ----------
data/Rakefile CHANGED
@@ -21,4 +21,3 @@ desc "build and release the gem"
21
21
  task :release => :build do
22
22
  system "gem push activeadmin-axlsx-#{ActiveAdmin::Axlsx::VERSION}.gem"
23
23
  end
24
-
@@ -1,5 +1,4 @@
1
1
  require 'active_admin'
2
- require 'active_admin/axlsx/autoload_extension'
3
2
  require 'active_admin/axlsx/build_download_format_links'
4
3
  require 'active_admin/axlsx/version'
5
4
  require 'active_admin/axlsx/builder'
@@ -14,9 +13,9 @@ class Railtie < ::Rails::Railtie
14
13
  Mime::Type.register "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :xlsx
15
14
  end
16
15
  rescue NameError
17
- puts "Mime module not defined. Skipping registration of xlsx"
16
+ # noop
18
17
  end
19
- ActiveAdmin.send :include, ActiveAdmin::Axlsx::AutoloadExtension
18
+
20
19
  ActiveAdmin::ResourceDSL.send :include, ActiveAdmin::Axlsx::DSL
21
20
  ActiveAdmin::Resource.send :include, ActiveAdmin::Axlsx::ResourceExtension
22
21
  ActiveAdmin::ResourceController.send :include, ActiveAdmin::Axlsx::ResourceControllerExtension
@@ -5,6 +5,8 @@ module ActiveAdmin
5
5
  base.send :alias_method_chain, :build_download_format_links, :xlsx
6
6
  end
7
7
 
8
+ # We are patching the build_download_format_links to include the :xlsx format
9
+ # Once ActiveAdmin 0.5.1 or higher has been released this can and should be removed.
8
10
  def build_download_format_links_with_xlsx(formats = [:csv, :xml, :json, :xlsx])
9
11
  build_download_format_links_without_xlsx(formats)
10
12
  end
@@ -2,47 +2,69 @@ require 'axlsx'
2
2
 
3
3
  module ActiveAdmin
4
4
  module Axlsx
5
- # XlsxBuilder extends CSVBuilder adding in xlsx specific options
6
- #
7
- # Usage example
8
- #
9
- # xlsx_builder = XlsxBuilder.new
10
- # xlsx_builder.column :id
11
- # xlsx_builder.column('Name') { |resource| resource.full_name }
12
- #
13
- # xlsx_builder = XlsxBuilder.new :shared_strings => true
14
- # xlsx_builder.column :id
15
- #
16
- # xlsx_builder = XlsxBuiler.new :header_style => { :bg_color => '00', :fg_color => 'FF', :sz => 14, :alignment => { :horizontal => :center } }
17
- # xlsx_buider.i18n_scope [:active_record, :models, :posts]
18
- class Builder < ActiveAdmin::CSVBuilder
5
+ # Builder for xlsx data using the axlsx gem.
6
+ class Builder
19
7
 
20
8
  include MethodOrProcHelper
21
9
 
22
- @@default_header_style = { :bg_color => '00', :fg_color => 'FF', :sz => 12, :alignment => { :horizontal => :center } }
23
-
24
- # Return a default XlsxBuilder for a resource
25
- # The XlsxBuilder columns will be id, follwed by this resource's content columns
26
- # The default header_style is applied.
27
- def self.default_for_resource(resource)
28
- xlsx_builder = super
29
- xlsx_builder.header_style = @@default_header_style
30
- xlsx_builder
10
+ # @param resource_class The resource this builder generate column information for.
11
+ # @param [Hash] options the options for this builder
12
+ # @option [Hash] :header_style - a hash of style properties to apply
13
+ # to the header row. Any properties specified will be merged with the default
14
+ # header styles. @see Axlsx::Styles#add_style
15
+ # @option [Array] :i18n_scope - the I18n scope to use when looking
16
+ # up localized column headers.
17
+ # @param [Block] Any block given will evaluated against this instance of Builder.
18
+ # That means you can call any method on the builder from withing that block.
19
+ # @example
20
+ # ActiveAdmin::Axlsx:Builder.new(Post, i18n: [:axlsx]) do
21
+ # delete_columns :id, :created_at, :updated_at
22
+ # column(:author_name) { |post| post.author.name }
23
+ # column(:
24
+ # after_filter { |sheet|
25
+ # sheet.add_row []
26
+ #
27
+ # sheet.add_row ['Author Name', 'Number of Posts'], :style => self.header_style
28
+ # data = labels = []
29
+ # User.all.each do |user|
30
+ # data << [user.posts.size]
31
+ # labels << user.name
32
+ # sheet.add_row [labels.last, data.last]
33
+ # end
34
+ # chart_color = %w(88F700, 279CAC, B2A200, FD66A3, F20062, C8BA2B, 67E6F8, DFFDB9, FFE800, B6F0F8)
35
+ # sheet.add_chart(Axlsx::Pie3DChart, :title => "post by author") do |chart|
36
+ # chart.add_series :data => data, :labels => labels, :colors => chart_color
37
+ # chart.start_at 2, sheet.rows.size
38
+ # chart.end_at 3, sheet.rows.size + 20
39
+ # end
40
+ # }
41
+ # end
42
+ # @see ActiveAdmin::Axlsx::DSL
43
+ def initialize(resource_class, options={}, &block)
44
+ @columns = resource_columns(resource_class)
45
+ parse_options options
46
+ instance_eval &block if block_given?
31
47
  end
32
48
 
33
- attr_reader :columns
34
-
35
- # when this is set to true the xlsx file will be generated with
36
- # shared strings and will inter-operate with Numbers for Mac
37
- # This is true by default, but you can set it to false to minimize the generation time.
38
- attr_accessor :shared_strings
49
+ # The default header style
50
+ # @return [Hash]
51
+ def header_style
52
+ @header_style ||= { :bg_color => '00', :fg_color => 'FF', :sz => 12, :alignment => { :horizontal => :center } }
53
+ end
39
54
 
40
55
  # This has can be used to override the default header style for your
41
- # sheet.
56
+ # sheet. Any values you provide will be merged with the default styles.
57
+ # Precidence is given to your hash
42
58
  # @see https://github.com/randym/axlsx for more details on how to
43
59
  # create and apply style.
44
- # @return [Hash]
45
- attr_accessor :header_style
60
+ def header_style=(style_hash)
61
+ @header_style = header_style.merge(style_hash)
62
+ end
63
+
64
+ # The scope to use when looking up column names to generate the report header
65
+ def i18n_scope
66
+ @i18n_scope ||= nil
67
+ end
46
68
 
47
69
  # This is the I18n scope that will be used when looking up your
48
70
  # colum names in the current I18n locale.
@@ -50,65 +72,114 @@ module ActiveAdmin
50
72
  # serializer will render the value at active_admin.resources.posts.title in the
51
73
  # current translations
52
74
  # @note If you do not set this, the column name will be titleized.
53
- attr_accessor :i18n_scope
54
-
55
- # @param [Hash] options the options for this builder
56
- # @option [Hash] :header_style - a hash of style properties to apply
57
- # to the header row
58
- # @option [Array] :i18n_scope - the I18n scope to use when looking
59
- # up localized column headers.
60
- # @option [Boolean] :shared_strings - Tells the serializer to use
61
- # shared strings or not when parsing out the package.
62
- # @note shared strings are an optional part of the ECMA-376 spec,and
63
- # are only required when you need to support Numbers.
64
- def initialize(options={}, &block)
65
- super
66
- @header_style = options.delete(:header_style) || @@default_header_style
67
- @i18n_scope = options.delete(:i18n_scope)
68
- @shared_strings = options.delete(:shared_strings) || true
69
- instance_eval &block if block_given?
75
+ def i18n_scope=(scope)
76
+ @i18n_scope = scope
70
77
  end
71
78
 
72
- # the Axlsx::Package
73
- def package
74
- @package ||= ::Axlsx::Package.new(:use_shared_strings => shared_strings)
79
+ # The stored block that will be executed after your report is generated.
80
+ def after_filter(&block)
81
+ @after_filter = block
75
82
  end
76
83
 
77
- # Serializes the collection provided
78
- # @return [Axlsx::Package]
79
- def serialize(collection)
80
- header_style_id = package.workbook.styles.add_style header_style
81
- package.workbook.add_worksheet do |sheet|
82
- sheet.add_row header_row, :style => header_style_id
83
- collection.each do |resource|
84
- sheet.add_row columns.map { |column| call_method_or_proc_on resource, column.data }
85
- end
86
- end
87
- package
84
+ # the stored block that will be executed before your report is generated.
85
+ def before_filter(&block)
86
+ @before_filter = block
88
87
  end
89
88
 
90
- # tranform column names into array of localized strings
91
- # @return [Array]
92
- def header_row
93
- columns.map { |column| column.localized_name(i18n_scope) }
89
+ # The columns this builder will be serializing
90
+ attr_reader :columns
91
+
92
+ # removes all columns from the builder. This is useful when you want to
93
+ # only render specific columns. To remove specific columns use ignore_column.
94
+ def clear_columns
95
+ @columns = []
94
96
  end
95
97
 
96
98
  # Add a column
99
+ # @param [Symbol] name The name of the column.
100
+ # @param [Proc] block A block of code that is executed on the resource
101
+ # when generating row data for this column.
97
102
  def column(name, &block)
98
103
  @columns << Column.new(name, block)
99
104
  end
100
105
 
106
+ # removes columns by name
107
+ # each column_name should be a symbol
108
+ def delete_columns(*column_names)
109
+ @columns.delete_if { |column| column_names.include?(column.name) }
110
+ end
111
+
112
+ # Serializes the collection provided
113
+ # @return [Axlsx::Package]
114
+ def serialize(collection)
115
+ apply_filter @before_filter
116
+ export_collection(collection)
117
+ apply_filter @after_filter
118
+ package
119
+ end
120
+
121
+ protected
122
+
101
123
  class Column
102
- attr_reader :name, :data
103
124
 
104
125
  def initialize(name, block = nil)
105
126
  @name = name.to_sym
106
127
  @data = block || @name
107
128
  end
108
129
 
130
+ attr_reader :name, :data
131
+
109
132
  def localized_name(i18n_scope = nil)
110
133
  return name.to_s.titleize unless i18n_scope
111
- I18n.t name, i18n_scope
134
+ I18n.t name, scope: i18n_scope
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def export_collection(collection)
141
+ header_row
142
+ collection.each do |resource|
143
+ sheet.add_row resource_data(resource)
144
+ end
145
+ end
146
+
147
+ def apply_filter(filter)
148
+ filter.call(sheet) if filter
149
+ end
150
+
151
+ def parse_options(options)
152
+ options.each do |key, value|
153
+ self.send("#{key}=", value) if self.respond_to?("#{key}=") && value != nil
154
+ end
155
+ end
156
+
157
+ def resource_data(resource)
158
+ columns.map { |column| call_method_or_proc_on resource, column.data }
159
+ end
160
+
161
+ def sheet
162
+ @sheet ||= package.workbook.add_worksheet
163
+ end
164
+
165
+ # the Axlsx::Package
166
+ def package
167
+ @package ||= ::Axlsx::Package.new(:use_shared_strings => true)
168
+ end
169
+
170
+ # tranform column names into array of localized strings
171
+ # @return [Array]
172
+ def header_row
173
+ sheet.add_row columns.map { |column| column.localized_name(i18n_scope) }, :style => header_style_id
174
+ end
175
+
176
+ def header_style_id
177
+ package.workbook.styles.add_style header_style
178
+ end
179
+
180
+ def resource_columns(resource)
181
+ [Column.new(:id)] + resource.content_columns.map do |column|
182
+ Column.new(column.name.to_sym)
112
183
  end
113
184
  end
114
185
  end
@@ -1,23 +1,11 @@
1
1
  module ActiveAdmin
2
2
  module Axlsx
3
3
  module DSL
4
- # Configure the xlsx format
5
- #
6
- # For example:
7
- #
8
- # xlsx do
9
- # i18n_scope = [:active_admin, :resources, :post]
10
- # column :name
11
- # column(:author) { |post| post.author.full_name }
12
- # end
13
- #
14
- # xlsx :header_style => { :bg_color => "00", :fg_color => "FF" } do
15
- # column :name
16
- # end
4
+ delegate :ingnore_columns, :column, :after_filer, :i18n_scope, :header_style, to: :xlsx_builder, prefix: :config
5
+ # @see ActiveAdmin::Axlsx::Builder
17
6
  def xlsx(options={}, &block)
18
- config.xlsx_builder = XlsxBuilder.new(options, &block)
7
+ config.xlsx_builder = ActiveAdmin::Axlsx::Builder.new(config.resource_class, options, &block)
19
8
  end
20
-
21
9
  end
22
10
  end
23
11
  end
@@ -7,17 +7,19 @@ module ActiveAdmin
7
7
  base.send :respond_to, :xlsx
8
8
  end
9
9
 
10
+ # patching the index method to allow the xlsx format.
10
11
  def index_with_xlsx(options={}, &block)
11
12
  index_without_xlsx(options) do |format|
12
13
  format.xlsx do
13
14
  xlsx = active_admin_config.xlsx_builder.serialize(collection)
14
- send_data xlsx.to_stream.read, :filename => "#{xlsx_filename}", :type => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
15
+ send_data xlsx.to_stream.read, :filename => "#{xlsx_filename}", :type => Mime::Type.lookup_by_extension(:xlsx)
15
16
  end
16
17
  end
17
18
  end
18
19
 
20
+ # patching per_page to use the CSV record max for pagination when the format is xlsx
19
21
  def per_page_with_xlsx
20
- if request.format == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
22
+ if request.format == Mime::Type.lookup_by_extension(:xlsx)
21
23
  return max_csv_records
22
24
  end
23
25
  per_page_without_xlsx
@@ -1,20 +1,14 @@
1
1
  module ActiveAdmin
2
2
  module Axlsx
3
3
  module ResourceExtension
4
-
5
4
  def xlsx_builder=(builder)
6
5
  @xlsx_builder = builder
7
6
  end
8
7
 
9
8
  def xlsx_builder
10
- @xlsx_builder ||= default_xlsx_builder
9
+ @xlsx_builder ||= ActiveAdmin::Axlsx::Builder.new(resource_class)
11
10
  end
12
11
 
13
- private
14
-
15
- def default_xlsx_builder
16
- @default_xlsx_builder ||= Axlsx::Builder.default_for_resource(resource_class)
17
- end
18
12
  end
19
13
  end
20
14
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveAdmin
2
2
  module Axlsx
3
- VERSION = '1.0.0'
3
+ VERSION = '2.0.0'
4
4
  end
5
5
  end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ describe ActiveAdmin::Views::PaginatedCollection do
3
+ def arbre(assigns = {}, helpers = mock_action_view, &block)
4
+ Arbre::Context.new(assigns, helpers, &block)
5
+ end
6
+
7
+ def render_arbre_component(assigns = {}, helpers = mock_action_view, &block)
8
+ arbre(assigns, helpers, &block).children.first
9
+ end
10
+
11
+ # Returns a fake action view instance to use with our renderers
12
+ def mock_action_view(assigns = {})
13
+ controller = ActionView::TestCase::TestController.new
14
+ ActionView::Base.send :include, ActionView::Helpers
15
+ ActionView::Base.send :include, ActiveAdmin::ViewHelpers
16
+ ActionView::Base.send :include, Rails.application.routes.url_helpers
17
+ ActionView::Base.new(ActionController::Base.view_paths, assigns, controller)
18
+ end
19
+
20
+ let(:view) do
21
+ view = mock_action_view
22
+ view.request.stub!(:query_parameters).and_return({:controller => 'admin/posts', :action => 'index', :page => '1'})
23
+ view.controller.params = {:controller => 'admin/posts', :action => 'index'}
24
+ view
25
+ end
26
+
27
+ # Helper to render paginated collections within an arbre context
28
+ def paginated_collection(*args)
29
+ render_arbre_component({:paginated_collection_args => args}, view) do
30
+ paginated_collection(*paginated_collection_args)
31
+ end
32
+ end
33
+
34
+ let(:collection) do
35
+ posts = [Post.new(:title => "First Post")]
36
+ Kaminari.paginate_array(posts).page(1).per(5)
37
+ end
38
+
39
+ let(:pagination) { paginated_collection(collection) }
40
+
41
+ before do
42
+ collection.stub!(:reorder) { collection }
43
+ end
44
+
45
+ it "renders the xlsx download link" do
46
+ pagination.children.last.content.should match(/XLSX/)
47
+ end
48
+ end
@@ -1,38 +1,117 @@
1
1
  require 'spec_helper'
2
+
2
3
  module ActiveAdmin
3
4
  module Axlsx
4
5
  describe Builder do
5
- context "with shared strings" do
6
- let(:builder) do
7
- Builder.new :shared_strings => true
6
+
7
+ let(:builder) { Builder.new(Post) }
8
+ let(:content_columns) { Post.content_columns }
9
+
10
+ context 'the default builder' do
11
+ subject { builder }
12
+ its(:header_style) { should == { :bg_color => '00', :fg_color => 'FF', :sz => 12, :alignment => { :horizontal => :center } } }
13
+ its(:i18n_scope) { should be_nil }
14
+ its("columns.size") { should == content_columns.size + 1 }
15
+ end
16
+
17
+ context 'customizing a builder' do
18
+ it 'deletes columns we tell it we dont want' do
19
+ builder.delete_columns :id, :body
20
+ builder.columns.size.should == content_columns.size - 1
8
21
  end
9
22
 
10
- it "should be set to use shared strings" do
11
- builder.shared_strings.should be_true
23
+ it 'lets us add custom columns' do
24
+ builder.column(:hoge)
25
+ builder.columns.size.should == content_columns.size + 2
12
26
  end
13
- end
14
27
 
15
- context "with i18n_scope" do
16
- let(:builder) do
17
- Builder.new :i18n_scope => [:active_admin, :resource, :category]
28
+ it 'lets us clear all columns' do
29
+ builder.clear_columns
30
+ builder.columns.size.should == 0
18
31
  end
19
32
 
20
- it "should have the defined i18n_scope set" do
21
- builder.i18n_scope.should == [:active_admin, :resource, :category]
33
+ context 'Using Procs for delayed content generation' do
34
+
35
+ let(:post) { Post.new(:title => "Hot Dawg") }
36
+
37
+ before do
38
+ builder.column(:hoge) { |resource| "#{resource.title} - with cheese" }
39
+ end
40
+
41
+ it 'stores the block when defining a column for later execution.' do
42
+ builder.columns.last.data.should be_a(Proc)
43
+ end
44
+
45
+ it 'evaluates custom column blocks' do
46
+ builder.columns.last.data.call(post).should == "Hot Dawg - with cheese"
47
+ end
22
48
  end
23
49
  end
24
50
 
25
- context "with a customized header style" do
26
- let (:header_style) do
27
- { :bg_color => '00', :fg_color => 'FF', :sz => 12, :alignment => { :horizontal=> :center } }
51
+ context 'Sheet generation with a highly customized configuration.' do
52
+
53
+ let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] }
54
+
55
+ let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] }
56
+
57
+ let!(:builder) {
58
+ Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:axlsx, :post]) do
59
+ delete_columns :id, :created_at, :updated_at
60
+ column(:author) { |resource| "#{resource.author.first_name} #{resource.author.last_name}" }
61
+ after_filter { |sheet|
62
+ sheet.add_row []
63
+ sheet.add_row ['Author Name', 'Number of Posts']
64
+ data = []
65
+ labels = []
66
+ User.all.each do |user|
67
+ data << user.posts.size
68
+ labels << "#{user.first_name} #{user.last_name}"
69
+ sheet.add_row [labels.last, data.last]
70
+ end
71
+ chart_color = %w(88F700 279CAC B2A200 FD66A3 F20062 C8BA2B 67E6F8 DFFDB9 FFE800 B6F0F8)
72
+ sheet.add_chart(::Axlsx::Pie3DChart, :title => "post by author") do |chart|
73
+ chart.add_series :data => data, :labels => labels, :colors => chart_color
74
+ chart.start_at 4, 0
75
+ chart.end_at 7, 20
76
+ end
77
+ }
78
+ before_filter do |sheet|
79
+ sheet.add_row ['Created', Time.zone.now]
80
+ sheet.add_row []
81
+ end
82
+ end
83
+ }
84
+
85
+ before(:all) do
86
+ User.stub!(:all) { users }
87
+ Post.stub!(:all) { posts }
88
+ @package = builder.serialize(Post.all)
89
+ end
90
+
91
+ it 'merges our customizations with the default header style' do
92
+ builder.header_style[:sz].should be(10)
93
+ builder.header_style[:fg_color].should == 'FF0000'
94
+ builder.header_style[:bg_color].should == '00'
28
95
  end
29
96
 
30
- let (:builder) do
31
- Builder.new :header_style => header_style
97
+ it 'uses the specified i18n_scope' do
98
+ builder.i18n_scope.should == [:axlsx, :post]
99
+ end
100
+
101
+ it 'translates the header row based on our i18n scope' do
102
+ header_row = @package.workbook.worksheets.first.rows[2]
103
+ header_row.cells.map(&:value).should == ['Title', 'Content', 'Published On', 'Publisher']
104
+ end
105
+
106
+ it 'processes the before filter' do
107
+ @package.workbook.worksheets.first["A1"].value.should == 'Created'
108
+ end
109
+ it 'processes the after filter' do
110
+ @package.workbook.charts.size.should == 1
32
111
  end
33
112
 
34
- it "should be set up with a header style" do
35
- header_style.each { |key , value| builder.header_style[key].should == value }
113
+ it 'has no OOXML validation errors' do
114
+ @package.validate.size.should == 0
36
115
  end
37
116
  end
38
117
  end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ module ActiveAdmin
4
+ module Axlsx
5
+ describe ::ActiveAdmin::ResourceDSL do
6
+ context 'in a registraiton block' do
7
+ let(:builder) {
8
+ config = ActiveAdmin.register(Post) do
9
+ xlsx(i18n_scope: [:rspec], header_style: { sz: 20 }) do
10
+ delete_columns :id, :created_at
11
+ column(:author) { |post| post.author.first_name }
12
+ before_filter { |sheet| sheet.add_row ['before_filter'] }
13
+ after_filter { |sheet| sheet.add_row['after_filter'] }
14
+ end
15
+ end
16
+ config.xlsx_builder
17
+ }
18
+
19
+
20
+ it "uses our customized i18n scope" do
21
+ builder.i18n_scope.should == [:rspec]
22
+ end
23
+
24
+ it "removed the columns we told it to ignore" do
25
+ [:id, :create_at].each do |removed|
26
+ builder.columns.index{|column| column.name == removed}.should be_nil
27
+ end
28
+ end
29
+
30
+ it "added the columns we declared" do
31
+ builder.columns.index{ |column| column.name == :author}.should_not be_nil
32
+ end
33
+
34
+ it "has a before filter set" do
35
+ builder.instance_values["before_filter"].should be_a(Proc)
36
+ end
37
+ it "has an after filter set" do
38
+ builder.instance_values["after_filter"].should be_a(Proc)
39
+ end
40
+ it "updates the header style" do
41
+ builder.header_style[:sz].should be(20)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ describe ActiveAdmin::ResourceController do
3
+
4
+
5
+ let(:mime) { Mime::Type.lookup_by_extension(:xlsx) }
6
+
7
+ let(:request) do
8
+ ActionController::TestRequest.new.tap do |test_request|
9
+ test_request.accept = mime
10
+ end
11
+ end
12
+
13
+ let(:response) { ActionController::TestResponse.new }
14
+
15
+ let(:controller) do
16
+ Admin::CategoriesController.new.tap do |controller|
17
+ controller.request = request
18
+ controller.response = response
19
+ end
20
+ end
21
+
22
+ let(:filename) { "#{controller.resource_class.to_s.downcase.pluralize}-#{Time.now.strftime("%Y-%m-%d")}.xlsx" }
23
+
24
+ it 'generates an xlsx filename' do
25
+ controller.xlsx_filename.should == filename
26
+ end
27
+
28
+
29
+ context 'when making requests with the xlsx mime type' do
30
+ it 'returns xlsx attachment when requested' do
31
+ controller.send :index
32
+ response.headers["Content-Disposition"].should == "attachment; filename=\"#{filename}\""
33
+ response.headers["Content-Transfer-Encoding"].should == 'binary'
34
+ end
35
+
36
+ it 'returns max_csv_records for per_page' do
37
+ controller.send(:per_page).should == controller.send(:max_csv_records)
38
+ end
39
+
40
+ it 'kicks back to the default per_page when we are not specifying a xlsx mime type' do
41
+ controller.request.accept = 'text/html'
42
+ controller.send(:per_page).should == ActiveAdmin.application.default_per_page
43
+ end
44
+ end
45
+ end
46
+
@@ -4,31 +4,23 @@ include ActiveAdmin
4
4
  module ActiveAdmin
5
5
  module Axlsx
6
6
  describe Resource do
7
- before { load_defaults! }
7
+ let(:resource) { ActiveAdmin.register(Post) }
8
8
 
9
- let(:application){ ActiveAdmin::Application.new }
10
- let(:namespace){ Namespace.new(application, :admin) }
11
-
12
- def config(options = {})
13
- @config ||= Resource.new(namespace, Category, options)
9
+ let(:custom_builder) do
10
+ Builder.new(Post) do |builder|
11
+ column(:fake) { :fake }
12
+ end
14
13
  end
15
14
 
16
- describe "#xlsx_builder" do
17
- context "when no xlsx_builder set" do
18
- it "should return a default xlsx_builder with id and content columns" do
19
- config.xlsx_builder.columns.size.should == Category.content_columns.size + 1
20
- end
15
+ context 'when registered' do
16
+ it "each resource has an xlsx_builer" do
17
+ resource.xlsx_builder.should be_a(Builder)
21
18
  end
22
19
 
23
- context "when xslx_builder set" do
24
- it "should return the xlsx_builder we set" do
25
- xlsx_builder = Builder.new
26
- config.xlsx_builder = xlsx_builder
27
- config.xlsx_builder.should == xlsx_builder
28
- end
20
+ it "We can specify our own configured builder" do
21
+ lambda { resource.xlsx_builder = custom_builder }.should_not raise_error
29
22
  end
30
23
  end
31
-
32
24
  end
33
25
  end
34
26
  end
@@ -1,22 +1,24 @@
1
- require 'rails'
2
- require 'activeadmin'
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter "/rails/"
4
+ end
3
5
 
6
+ # prepare ENV for rails
7
+ require 'rails'
4
8
  ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{Rails::VERSION::STRING}", __FILE__)
5
9
 
10
+ # ensure testing application is in place
6
11
  unless File.exists?(ENV['RAILS_ROOT'])
7
12
  puts "Please run bundle exec rake setup before running the specs."
8
13
  exit
9
14
  end
10
15
 
11
- def load_defaults!
12
- ActiveAdmin.unload!
13
- ActiveAdmin.load!
14
- ActiveAdmin.register(Category)
15
- ActiveAdmin.register(User)
16
- ActiveAdmin.register(Post){ belongs_to :user, :optional => true }
17
- end
18
-
19
- ENV['RAILS_ENV'] = 'test'
16
+ # load up activeadmin and activeadmin-axlsx
17
+ require 'activeadmin-axlsx'
20
18
  ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"]
19
+
20
+ # start up rails
21
21
  require ENV['RAILS_ROOT'] + '/config/environment'
22
- require 'activeadmin-axlsx'
22
+
23
+ # and finally,here's rspec
24
+ require 'rspec/rails'
@@ -42,7 +42,7 @@ end
42
42
  inject_into_file "config/environments/test.rb", " config.action_mailer.default_url_options = { :host => 'example.com' }\n", :after => "config.cache_classes = true\n"
43
43
 
44
44
  # Add our local Active Admin to the load path
45
- inject_into_file "config/environment.rb", "\n$LOAD_PATH.unshift('#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))}')\nrequire \"active_admin\"\n", :after => "require File.expand_path('../application', __FILE__)"
45
+ inject_into_file "config/environment.rb", "\nrequire \"activeadmin-axlsx\"\n", :after => "require File.expand_path('../application', __FILE__)"
46
46
 
47
47
  # Add some translations
48
48
  append_file "config/locales/en.yml", File.read(File.expand_path('../templates/en.yml', __FILE__))
@@ -24,6 +24,7 @@ scopes = <<-EOF
24
24
  scope :my_posts do |posts|
25
25
  posts.where(:author_id => current_admin_user.id)
26
26
  end
27
+
27
28
  EOF
28
29
  inject_into_file 'app/admin/posts.rb', scopes , :after => "ActiveAdmin.register Post do\n"
29
30
 
@@ -49,7 +50,7 @@ append_file "db/seeds.rb", <<-EOF
49
50
  published_at = published_at_values[i % published_at_values.size]
50
51
  Post.create :title => "Blog Post \#{i}",
51
52
  :body => "Blog post \#{i} is written by \#{user.username} about \#{cat.name}",
52
- :category => cat,
53
+ :category_id => cat.id,
53
54
  :published_at => published_at,
54
55
  :author => user
55
56
  end
@@ -1,4 +1,13 @@
1
1
  # Sample translations used to test ActiveAdmin's I18n integration.
2
+ axlsx:
3
+ post:
4
+ id: ID
5
+ title: Title
6
+ body: Content
7
+ published_at: Published On
8
+ author: Publisher
9
+ created_at: Created
10
+ updated_at: Updated
2
11
  activerecord:
3
12
  models:
4
13
  store:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeadmin-axlsx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-03 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activeadmin
@@ -54,6 +54,7 @@ extensions: []
54
54
  extra_rdoc_files: []
55
55
  files:
56
56
  - .gitignore
57
+ - .rspec
57
58
  - .yardops
58
59
  - CHANGELOG.md
59
60
  - Gemfile
@@ -63,7 +64,6 @@ files:
63
64
  - Rakefile
64
65
  - activeadmin-axlsx.gemspec
65
66
  - lib/active_admin/axlsx.rb
66
- - lib/active_admin/axlsx/autoload_extension.rb
67
67
  - lib/active_admin/axlsx/build_download_format_links.rb
68
68
  - lib/active_admin/axlsx/builder.rb
69
69
  - lib/active_admin/axlsx/dsl.rb
@@ -72,7 +72,10 @@ files:
72
72
  - lib/active_admin/axlsx/version.rb
73
73
  - lib/activeadmin-axlsx.rb
74
74
  - screen_capture.png
75
+ - spec/axlsx/unit/build_download_format_links_spec.rb
75
76
  - spec/axlsx/unit/builder_spec.rb
77
+ - spec/axlsx/unit/dsl_spec.rb
78
+ - spec/axlsx/unit/resource_controller_spec.rb
76
79
  - spec/axlsx/unit/resource_spec.rb
77
80
  - spec/spec_helper.rb
78
81
  - spec/support/rails_template.rb
@@ -101,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
104
  version: '0'
102
105
  segments:
103
106
  - 0
104
- hash: -3314868073732542076
107
+ hash: 646391432693346211
105
108
  requirements: []
106
109
  rubyforge_project:
107
110
  rubygems_version: 1.8.24
@@ -110,7 +113,10 @@ specification_version: 3
110
113
  summary: Adds excel downloads for resources within the Active Admin framework via
111
114
  Axlsx.
112
115
  test_files:
116
+ - spec/axlsx/unit/build_download_format_links_spec.rb
113
117
  - spec/axlsx/unit/builder_spec.rb
118
+ - spec/axlsx/unit/dsl_spec.rb
119
+ - spec/axlsx/unit/resource_controller_spec.rb
114
120
  - spec/axlsx/unit/resource_spec.rb
115
121
  - spec/spec_helper.rb
116
122
  - spec/support/rails_template.rb
@@ -1,7 +0,0 @@
1
- module ActiveAdmin
2
- module Axlsx
3
- module AutoloadExtension
4
- autoload :XlsxBuilder, 'active_admin/xlsx_builder'
5
- end
6
- end
7
- end