activeadmin-xls 1.0.4

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.
@@ -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