activeadmin-axlsx 1.0.0 → 2.0.0

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