activeadmin-xls 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter "/rails/"
4
+ end
5
+
6
+ # prepare ENV for rails
7
+ require 'rails'
8
+ ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{Rails::VERSION::STRING}", __FILE__)
9
+
10
+ # ensure testing application is in place
11
+ unless File.exists?(ENV['RAILS_ROOT'])
12
+ puts "Please run bundle exec rake setup before running the specs."
13
+ exit
14
+ end
15
+
16
+ # load up activeadmin and activeadmin-xls
17
+ require 'activeadmin-xls'
18
+ ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"]
19
+ # start up rails
20
+ require ENV['RAILS_ROOT'] + '/config/environment'
21
+
22
+ # and finally,here's rspec
23
+ require 'rspec/rails'
24
+ ActiveAdmin.application.authentication_method = false
25
+ ActiveAdmin.application.current_user_method = false
@@ -0,0 +1,73 @@
1
+ # Rails template to build the sample app for specs
2
+
3
+ # Create a cucumber database and environment
4
+ copy_file File.expand_path('../templates/cucumber.rb', __FILE__), "config/environments/cucumber.rb"
5
+ copy_file File.expand_path('../templates/cucumber_with_reloading.rb', __FILE__), "config/environments/cucumber_with_reloading.rb"
6
+
7
+ gsub_file 'config/database.yml', /^test:.*\n/, "test: &test\n"
8
+ gsub_file 'config/database.yml', /\z/, "\ncucumber:\n <<: *test\n database: db/cucumber.sqlite3"
9
+ gsub_file 'config/database.yml', /\z/, "\ncucumber_with_reloading:\n <<: *test\n database: db/cucumber.sqlite3"
10
+
11
+ # Generate some test models
12
+ generate :model, "post title:string body:text published_at:datetime author_id:integer category_id:integer"
13
+ inject_into_file 'app/models/post.rb', " belongs_to :author, :class_name => 'User'\n belongs_to :category\n accepts_nested_attributes_for :author\n", :after => "class Post < ActiveRecord::Base\n"
14
+ # Rails 3.2.3 model generator declare attr_accessible
15
+ inject_into_file 'app/models/post.rb', " attr_accessible :author\n", :before => "end" if Rails::VERSION::STRING >= '3.2.3'
16
+ generate :model, "user type:string first_name:string last_name:string username:string age:integer"
17
+ inject_into_file 'app/models/user.rb', " has_many :posts, :foreign_key => 'author_id'\n", :after => "class User < ActiveRecord::Base\n"
18
+ generate :model, "publisher --migration=false --parent=User"
19
+ generate :model, 'category name:string description:text'
20
+ inject_into_file 'app/models/category.rb', " has_many :posts\n accepts_nested_attributes_for :posts\n", :after => "class Category < ActiveRecord::Base\n"
21
+ generate :model, 'store name:string'
22
+
23
+ # Generate a model with string ids
24
+ generate :model, "tag name:string"
25
+ gsub_file(Dir['db/migrate/*_create_tags.rb'][0], /\:tags\sdo\s.*/, ":tags, :id => false, :primary_key => :id do |t|\n\t\t\tt.string :id\n" )
26
+ id_model_setup = <<-EOF
27
+ self.primary_key = :id
28
+ before_create :set_id
29
+
30
+ private
31
+ def set_id
32
+ self.id = 8.times.inject("") { |s,e| s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }
33
+ end
34
+ EOF
35
+ inject_into_file 'app/models/tag.rb', id_model_setup, :after => "class Tag < ActiveRecord::Base\n"
36
+
37
+ if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 1 #Rails 3.1 Gotcha
38
+ gsub_file 'app/models/tag.rb', /self\.primary_key.*$/, "define_attr_method :primary_key, :id"
39
+ end
40
+
41
+ # Configure default_url_options in test environment
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
+
44
+ # Add our local Active Admin to the load path
45
+ inject_into_file "config/environment.rb", "\nrequire \"activeadmin-xls\"\n", :after => "require File.expand_path('../application', __FILE__)"
46
+
47
+ # Add some translations
48
+ append_file "config/locales/en.yml", File.read(File.expand_path('../templates/en.yml', __FILE__))
49
+
50
+ # Add predefined admin resources
51
+ directory File.expand_path('../templates/admin', __FILE__), "app/admin"
52
+
53
+ run "rm Gemfile"
54
+ run "rm -r test"
55
+ run "rm -r spec"
56
+
57
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
58
+
59
+ # we need this routing path, named "logout_path", for testing
60
+ route <<-EOS
61
+ devise_scope :user do
62
+ match '/admin/logout' => 'active_admin/devise/sessions#destroy', :as => :logout
63
+ end
64
+ EOS
65
+
66
+ generate :'active_admin:install'
67
+
68
+ # Setup a root path for devise
69
+ route "root :to => 'admin/dashboard#index'"
70
+
71
+ rake "db:migrate"
72
+ rake "db:test:prepare"
73
+ run "/usr/bin/env RAILS_ENV=cucumber rake db:migrate"
@@ -0,0 +1,59 @@
1
+ # Use the default
2
+ apply File.expand_path("../rails_template.rb", __FILE__)
3
+
4
+ # Register Active Admin controllers
5
+ %w{ Post User Category }.each do |type|
6
+ generate :'active_admin:resource', type
7
+ end
8
+
9
+ scopes = <<-EOF
10
+ scope :all, :default => true
11
+
12
+ scope :drafts do |posts|
13
+ posts.where(["published_at IS NULL"])
14
+ end
15
+
16
+ scope :scheduled do |posts|
17
+ posts.where(["posts.published_at IS NOT NULL AND posts.published_at > ?", Time.now.utc])
18
+ end
19
+
20
+ scope :published do |posts|
21
+ posts.where(["posts.published_at IS NOT NULL AND posts.published_at < ?", Time.now.utc])
22
+ end
23
+
24
+ scope :my_posts do |posts|
25
+ posts.where(:author_id => current_admin_user.id)
26
+ end
27
+
28
+ EOF
29
+ inject_into_file 'app/admin/posts.rb', scopes , :after => "ActiveAdmin.register Post do\n"
30
+
31
+ # Setup some default data
32
+ append_file "db/seeds.rb", <<-EOF
33
+ users = ["Jimi Hendrix", "Jimmy Page", "Yngwie Malmsteen", "Eric Clapton", "Kirk Hammett"].collect do |name|
34
+ first, last = name.split(" ")
35
+ User.create! :first_name => first,
36
+ :last_name => last,
37
+ :username => [first,last].join('-').downcase,
38
+ :age => rand(80)
39
+ end
40
+
41
+ categories = ["Rock", "Pop Rock", "Alt-Country", "Blues", "Dub-Step"].collect do |name|
42
+ Category.create! :name => name
43
+ end
44
+
45
+ published_at_values = [Time.now.utc - 5.days, Time.now.utc - 1.day, nil, Time.now.utc + 3.days]
46
+
47
+ 1_000.times do |i|
48
+ user = users[i % users.size]
49
+ cat = categories[i % categories.size]
50
+ published_at = published_at_values[i % published_at_values.size]
51
+ Post.create :title => "Blog Post \#{i}",
52
+ :body => "Blog post \#{i} is written by \#{user.username} about \#{cat.name}",
53
+ :category_id => cat.id,
54
+ :published_at => published_at,
55
+ :author => user
56
+ end
57
+ EOF
58
+
59
+ rake 'db:seed'
@@ -0,0 +1 @@
1
+ ActiveAdmin.register Store
@@ -0,0 +1,24 @@
1
+ require File.expand_path('config/environments/test', Rails.root)
2
+
3
+ # rails/railties/lib/rails/test_help.rb aborts if the environment is not 'test'. (Rails 3.0.0.beta3)
4
+ # We can't run Cucumber/RSpec/Test_Unit tests in different environments then.
5
+ #
6
+ # For now, I patch StringInquirer so that Rails.env.test? returns true when Rails.env is 'test' or 'cucumber'
7
+ #
8
+ # https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4458-rails-should-allow-test-to-run-in-cucumber-environment
9
+ module ActiveSupport
10
+ class StringInquirer < String
11
+ def method_missing(method_name, *arguments)
12
+ if method_name.to_s[-1,1] == "?"
13
+ test_string = method_name.to_s[0..-2]
14
+ if test_string == 'test'
15
+ self == 'test' or self == 'cucumber'
16
+ else
17
+ self == test_string
18
+ end
19
+ else
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path('config/environments/cucumber', Rails.root)
2
+
3
+ Rails.application.class.configure do
4
+ config.cache_classes = false
5
+ end
@@ -0,0 +1,15 @@
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
11
+ activerecord:
12
+ models:
13
+ store:
14
+ one: Bookstore
15
+ other: Bookstores
@@ -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 xls download link" do
46
+ pagination.children.last.content.should match(/XLS/)
47
+ end
48
+ end
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+
3
+ module ActiveAdmin
4
+ module Xls
5
+ describe Builder do
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
21
+ end
22
+
23
+ it 'lets us say we dont want the header' do
24
+ builder.skip_header
25
+ builder.instance_values["skip_header"].should be_true
26
+ end
27
+
28
+ it 'lets us add custom columns' do
29
+ builder.column(:hoge)
30
+ builder.columns.size.should == content_columns.size + 2
31
+ end
32
+
33
+ it 'lets us clear all columns' do
34
+ builder.clear_columns
35
+ builder.columns.size.should == 0
36
+ end
37
+
38
+ context 'Using Procs for delayed content generation' do
39
+
40
+ let(:post) { Post.new(:title => "Hot Dawg") }
41
+
42
+ before do
43
+ builder.column(:hoge) { |resource| "#{resource.title} - with cheese" }
44
+ end
45
+
46
+ it 'stores the block when defining a column for later execution.' do
47
+ builder.columns.last.data.should be_a(Proc)
48
+ end
49
+
50
+ it 'evaluates custom column blocks' do
51
+ builder.columns.last.data.call(post).should == "Hot Dawg - with cheese"
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'sheet generation without headers' do
57
+ let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] }
58
+
59
+ let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] }
60
+
61
+ let!(:builder) {
62
+ Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:xls, :post]) do
63
+ skip_header
64
+ end
65
+ }
66
+
67
+ before do
68
+ User.stub!(:all) { users }
69
+ Post.stub!(:all) { posts }
70
+ # disable clean up so we can get the package.
71
+ builder.stub(:clean_up) { false }
72
+ builder.serialize(Post.all)
73
+ @package = builder.send(:package)
74
+ @collection = builder.collection
75
+ end
76
+
77
+ it 'does not serialize the header' do
78
+ not_header = @package.workbook.worksheets.first.rows.first
79
+ not_header.cells.first.value.should_not == 'Title'
80
+ end
81
+ end
82
+
83
+ context 'whitelisted sheet generation' do
84
+ let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] }
85
+
86
+ let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] }
87
+
88
+ let!(:builder) {
89
+ Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:xls, :post]) do
90
+ skip_header
91
+ whitelist
92
+ column :title
93
+ end
94
+ }
95
+
96
+ before do
97
+ User.stub!(:all) { users }
98
+ Post.stub!(:all) { posts }
99
+ # disable clean up so we can get the package.
100
+ builder.stub(:clean_up) { false }
101
+ builder.serialize(Post.all)
102
+ @package = builder.send(:package)
103
+ @collection = builder.collection
104
+ end
105
+
106
+ it 'does not serialize the header' do
107
+ sheet = @package.workbook.worksheets.first
108
+ sheet.rows.first.cells.size.should == 1
109
+ sheet.rows.first.cells.first.value.should == @collection.first.title
110
+ end
111
+ end
112
+
113
+ context 'Sheet generation with a highly customized configuration.' do
114
+
115
+ let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] }
116
+
117
+ let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] }
118
+
119
+ let!(:builder) {
120
+ Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:xls, :post]) do
121
+ delete_columns :id, :created_at, :updated_at
122
+ column(:author) { |resource| "#{resource.author.first_name} #{resource.author.last_name}" }
123
+ after_filter { |sheet|
124
+ sheet.add_row []
125
+ sheet.add_row ['Author Name', 'Number of Posts']
126
+ data = []
127
+ labels = []
128
+ User.all.each do |user|
129
+ data << user.posts.size
130
+ labels << "#{user.first_name} #{user.last_name}"
131
+ sheet.add_row [labels.last, data.last]
132
+ end
133
+ chart_color = %w(88F700 279CAC B2A200 FD66A3 F20062 C8BA2B 67E6F8 DFFDB9 FFE800 B6F0F8)
134
+ sheet.add_chart(::xls::Pie3DChart, :title => "post by author") do |chart|
135
+ chart.add_series :data => data, :labels => labels, :colors => chart_color
136
+ chart.start_at 4, 0
137
+ chart.end_at 7, 20
138
+ end
139
+ }
140
+ before_filter do |sheet|
141
+ collection.first.author.first_name = 'Set In Proc'
142
+ sheet.add_row ['Created', Time.zone.now]
143
+ sheet.add_row []
144
+ end
145
+ end
146
+ }
147
+
148
+ before(:all) do
149
+ User.stub!(:all) { users }
150
+ Post.stub!(:all) { posts }
151
+ # disable clean up so we can get the package.
152
+ builder.stub(:clean_up) { false }
153
+ builder.serialize(Post.all)
154
+ @package = builder.send(:package)
155
+ @collection = builder.collection
156
+ end
157
+
158
+ it 'provides the collection object' do
159
+ @collection.count.should == Post.all.count
160
+ end
161
+
162
+ it 'merges our customizations with the default header style' do
163
+ builder.header_style[:sz].should be(10)
164
+ builder.header_style[:fg_color].should == 'FF0000'
165
+ builder.header_style[:bg_color].should == '00'
166
+ end
167
+
168
+ it 'uses the specified i18n_scope' do
169
+ builder.i18n_scope.should == [:xls, :post]
170
+ end
171
+
172
+ it 'translates the header row based on our i18n scope' do
173
+ header_row = @package.workbook.worksheets.first.rows[2]
174
+ header_row.cells.map(&:value).should == ['Title', 'Content', 'Published On', 'Publisher']
175
+ end
176
+
177
+ it 'processes the before filter' do
178
+ @package.workbook.worksheets.first["A1"].value.should == 'Created'
179
+ end
180
+
181
+ it 'lets us work against the collection in the before filter' do
182
+ @package.workbook.worksheets.first.rows.last.cells.first.value.should == 'Set In Proc nancy'
183
+ end
184
+
185
+ it 'processes the after filter' do
186
+ @package.workbook.charts.size.should == 1
187
+ end
188
+
189
+ it 'has no OOXML validation errors' do
190
+ @package.validate.size.should == 0
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module ActiveAdmin
4
+ module Xls
5
+ describe ::ActiveAdmin::ResourceDSL do
6
+ context 'in a registraiton block' do
7
+ let(:builder) {
8
+ config = ActiveAdmin.register(Post) do
9
+ xls(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
+ skip_header
15
+ end
16
+ end
17
+ config.xls_builder
18
+ }
19
+
20
+
21
+ it "uses our customized i18n scope" do
22
+ builder.i18n_scope.should == [:rspec]
23
+ end
24
+
25
+ it "removed the columns we told it to ignore" do
26
+ [:id, :create_at].each do |removed|
27
+ builder.columns.index{|column| column.name == removed}.should be_nil
28
+ end
29
+ end
30
+
31
+ it "added the columns we declared" do
32
+ builder.columns.index{ |column| column.name == :author}.should_not be_nil
33
+ end
34
+
35
+ it "has a before filter set" do
36
+ builder.instance_values["before_filter"].should be_a(Proc)
37
+ end
38
+ it "has an after filter set" do
39
+ builder.instance_values["after_filter"].should be_a(Proc)
40
+ end
41
+
42
+ it "indicates that the header should be excluded" do
43
+ builder.instance_values['skip_header'].should be_true
44
+ end
45
+
46
+ it "updates the header style" do
47
+ builder.header_style[:sz].should be(20)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ describe ActiveAdmin::ResourceController do
3
+
4
+ let(:mime) { Mime::Type.lookup_by_extension(:xls) }
5
+
6
+ let(:request) do
7
+ ActionController::TestRequest.new.tap do |test_request|
8
+ test_request.accept = mime
9
+ end
10
+ end
11
+
12
+ let(:response) { ActionController::TestResponse.new }
13
+
14
+ let(:controller) do
15
+ Admin::CategoriesController.new.tap do |controller|
16
+ controller.request = request
17
+ controller.response = response
18
+ end
19
+ end
20
+
21
+ let(:filename) { "#{controller.resource_class.to_s.downcase.pluralize}-#{Time.now.strftime("%Y-%m-%d")}.xls" }
22
+
23
+ it 'generates an xls filename' do
24
+ controller.xls_filename.should == filename
25
+ end
26
+
27
+ context 'when making requests with the xls mime type' do
28
+ it 'returns xls attachment when requested' do
29
+ controller.send :index
30
+ response.headers["Content-Disposition"].should == "attachment; filename=\"#{filename}\""
31
+ response.headers["Content-Transfer-Encoding"].should == 'binary'
32
+ end
33
+
34
+ it 'returns max_csv_records for per_page' do
35
+ controller.send(:per_page).should == controller.send(:max_csv_records)
36
+ end
37
+
38
+ it 'kicks back to the default per_page when we are not specifying a xls mime type' do
39
+ controller.request.accept = 'text/html'
40
+ controller.send(:per_page).should == ActiveAdmin.application.default_per_page
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ include ActiveAdmin
3
+
4
+ module ActiveAdmin
5
+ module Xls
6
+ describe Resource do
7
+ let(:resource) { ActiveAdmin.register(Post) }
8
+
9
+ let(:custom_builder) do
10
+ Builder.new(Post) do |builder|
11
+ column(:fake) { :fake }
12
+ end
13
+ end
14
+
15
+ context 'when registered' do
16
+ it "each resource has an xls_builer" do
17
+ resource.xls_builder.should be_a(Builder)
18
+ end
19
+
20
+ it "We can specify our own configured builder" do
21
+ lambda { resource.xls_builder = custom_builder }.should_not raise_error
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end