diff --git a/.gemignore b/.gemignore new file mode 100644 index 0000000..c913e96 --- /dev/null +++ b/.gemignore @@ -0,0 +1 @@ +.bundle/* diff --git a/.gitignore b/.gitignore index 16b8311..c18d73f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ doc .rbenv-version .ruby-version /Gemfile.lock +gemfiles/*.gemfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ed53b..51c649f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,17 @@ # Changelog -## 1.0.2 +## 1.0.5 -### Bug Fixes +### Updates -* Fixes undefined local variable or `method max_per_page` [#3][] by [@rewritten][] +* Fix #1 - Unnecessary database access +* Fix broken tests + +## 1.0.4 + +### Updates + +* Minor bug fixes / typo corrections ## 1.0.3 @@ -12,6 +19,12 @@ * Move require rake from gemspec to lib/activeadmin-xls.rb [#4][] by [@ejaypcanaria][] +## 1.0.2 + +### Bug Fixes + +* Fixes undefined local variable or `method max_per_page` [#3][] by [@rewritten][] + [#3]: https://github.com/thambley/activeadmin-xls/issues/3 [#4]: https://github.com/thambley/activeadmin-xls/pull/4 diff --git a/Gemfile b/Gemfile index 4d7cfce..64a7b03 100644 --- a/Gemfile +++ b/Gemfile @@ -1,30 +1,17 @@ source 'https://rubygems.org' -gem 'activeadmin', '~> 1.0' gem 'spreadsheet', '~> 1.1', '>= 1.1.4' group :development, :test do - gem 'haml', require: false gem 'rails-i18n' # Gives us default i18n for many languages - gem 'rdiscount' # For yard - gem 'sprockets' gem 'sqlite3' gem 'yard' end group :test do - gem 'capybara' gem 'cucumber-rails', require: false gem 'database_cleaner' - gem 'guard-coffeescript' - gem 'guard-rspec' - gem 'inherited_resources' - gem 'jasmine' - gem 'jslint_on_rails', '~> 1.0.6' - gem 'launchy' - gem 'rspec-mocks' - gem 'rspec-rails' - gem 'sass-rails' - gem 'shoulda-matchers', '1.0.0' + gem 'rspec-mocks', '~> 3.7' + gem 'rspec-rails', '~> 3.7' gem 'simplecov', require: false end diff --git a/README.md b/README.md index e8830eb..bb8b8bb 100644 --- a/README.md +++ b/README.md @@ -113,15 +113,35 @@ end ## Specs Running specs for this gem requires that you construct a rails application. -To execute the specs, navigate to the gem directory, -run bundle install and run these to rake tasks: + +To execute the specs, navigate to the gem directory, run bundle install and run these to rake tasks: + +### Rails 3.2 + +```text +bundle install --gemfile=gemfiles/rails_32.gemfile +``` + +```text +BUNDLE_GEMFILE=gemfiles/rails_32.gemfile bundle exec rake setup +``` + +```text +BUNDLE_GEMFILE=gemfiles/rails_32.gemfile bundle exec rake +``` + +### Rails 4.2 + +```text +bundle install --gemfile=gemfiles/rails_42.gemfile +``` ```text -bundle exec rake setup +BUNDLE_GEMFILE=gemfiles/rails_42.gemfile bundle exec rake setup ``` ```text -bundle exec rake +BUNDLE_GEMFILE=gemfiles/rails_42.gemfile bundle exec rake ``` ## Copyright and License diff --git a/Rakefile b/Rakefile index a6cbc76..36c160b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,23 +1,23 @@ #!/usr/bin/env rake -require "activeadmin" -require "rspec/core/rake_task" +require 'rspec/core/rake_task' -desc "Creates a test rails app for the specs to run against" +desc 'Creates a test rails app for the specs to run against' task :setup do require 'rails/version' - system("mkdir spec/rails") unless File.exists?("spec/rails") + system('mkdir spec/rails') unless File.exist?('spec/rails') + puts "system \"bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} -m spec/support/rails_template_with_data.rb\"" system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} -m spec/support/rails_template_with_data.rb" end RSpec::Core::RakeTask.new -task :default => :spec -task :test => :spec +task default: :spec +task test: :spec -desc "build the gem" +desc 'build the gem' task :build do - system "gem build activeadmin-xls.gemspec" + system 'gem build activeadmin-xls.gemspec' end -desc "build and release the gem" -task :release => :build do +desc 'build and release the gem' +task release: :build do system "gem push activeadmin-xls-#{ActiveAdmin::Xls::VERSION}.gem" end diff --git a/activeadmin-xls.gemspec b/activeadmin-xls.gemspec index 7e22f1b..786f6eb 100644 --- a/activeadmin-xls.gemspec +++ b/activeadmin-xls.gemspec @@ -15,9 +15,11 @@ Gem::Specification.new do |s| s.description = <<-DESC This gem provides excel/xls downloads for resources in Active Admin. DESC - s.files = `git ls-files`.split("\n").sort - s.test_files = `git ls-files -- {spec}/*`.split("\n") - s.test_files = Dir.glob('{spec/**/*}') + + git_tracked_files = `git ls-files`.split("\n").sort + gem_ignored_files = `git ls-files -i -X .gemignore`.split("\n") + + s.files = git_tracked_files - gem_ignored_files s.add_runtime_dependency 'activeadmin', '>= 0.6.6', '< 2' s.add_runtime_dependency 'spreadsheet', '~> 1.0' diff --git a/gemfiles/rails_32.gemfile b/gemfiles/rails_32.gemfile new file mode 100644 index 0000000..d090587 --- /dev/null +++ b/gemfiles/rails_32.gemfile @@ -0,0 +1,30 @@ +#!/usr/bin/env ruby +source 'https://rubygems.org' + +ruby_major_version = RUBY_VERSION.split('.')[0].to_i +ruby_minor_version = RUBY_VERSION.split('.')[1].to_i + +eval_gemfile(File.expand_path(File.join('..', 'Gemfile'), __dir__)) + +gem 'rails', '3.2.22.5' + +gem 'activeadmin', '0.6.6' + +group :assets do + gem 'coffee-rails', '~> 3.2.1' + gem 'sass-rails', '~> 3.2.3' + + # See https://github.com/sstephenson/execjs#readme for more supported runtimes + # gem 'therubyracer', :platforms => :ruby + + gem 'uglifier', '>= 1.0.3' +end + +group :test do + gem 'shoulda-matchers', '~> 2.8.0' + if ruby_major_version > 2 || (ruby_major_version == 2 && ruby_minor_version > 1) + gem 'test-unit', '~> 3.0' + end +end + +gemspec path: "../" \ No newline at end of file diff --git a/gemfiles/rails_42.gemfile b/gemfiles/rails_42.gemfile new file mode 100644 index 0000000..5ef7f07 --- /dev/null +++ b/gemfiles/rails_42.gemfile @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby +source 'https://rubygems.org' + +ruby_major_version = RUBY_VERSION.split('.')[0].to_i +ruby_minor_version = RUBY_VERSION.split('.')[1].to_i + +eval_gemfile(File.expand_path(File.join('..', 'Gemfile'), __dir__)) + +gem 'activeadmin', '1.0.0' +gem 'devise', '~> 4.2' +gem 'rails', '4.2.10' +gem 'turbolinks', '~> 5.0.0' +gem 'tzinfo-data' + +group :test do + gem 'shoulda-matchers', '~> 3.1' + if ruby_major_version > 2 || (ruby_major_version == 2 && ruby_minor_version > 1) + gem 'test-unit', '~> 3.0' + end +end + +gemspec path: "../" \ No newline at end of file diff --git a/lib/active_admin/xls/builder.rb b/lib/active_admin/xls/builder.rb index 831a0b8..9a5fb73 100644 --- a/lib/active_admin/xls/builder.rb +++ b/lib/active_admin/xls/builder.rb @@ -29,7 +29,10 @@ class Builder # @see ActiveAdmin::Axlsx::DSL def initialize(resource_class, options = {}, &block) @skip_header = false - @columns = resource_columns(resource_class) + @resource_class = resource_class + @columns = [] + @columns_loaded = false + @column_updates = [] parse_options options instance_eval(&block) if block_given? end @@ -60,9 +63,7 @@ def skip_header # The scope to use when looking up column names to generate the # report header - def i18n_scope - @i18n_scope ||= nil - end + attr_reader :i18n_scope # This is the I18n scope that will be used when looking up your # colum names in the current I18n locale. @@ -83,7 +84,12 @@ def before_filter(&block) end # The columns this builder will be serializing - attr_reader :columns + def columns + # execute each update from @column_updates + # set @columns_loaded = true + load_columns unless @columns_loaded + @columns + end # The collection we are serializing. # @note This is only available after serialize has been called, @@ -94,34 +100,50 @@ def before_filter(&block) # only render specific columns. To remove specific columns use # ignore_column. def clear_columns + @columns_loaded = true + @column_updates = [] + @columns = [] end # Clears the default columns array so you can whitelist only the columns # you want to export - def whitelist - @columns = [] - end + alias whitelist clear_columns # Add a column # @param [Symbol] name The name of the column. # @param [Proc] block A block of code that is executed on the resource # when generating row data for this column. def column(name, &block) - @columns << Column.new(name, block) + if @columns_loaded + columns << Column.new(name, block) + else + column_lambda = lambda do + column(name, &block) + end + @column_updates << column_lambda + end end # removes columns by name # each column_name should be a symbol def delete_columns(*column_names) - @columns.delete_if { |column| column_names.include?(column.name) } + if @columns_loaded + columns.delete_if { |column| column_names.include?(column.name) } + else + delete_lambda = lambda do + delete_columns(*column_names) + end + @column_updates << delete_lambda + end end # Serializes the collection provided # @return [Spreadsheet::Workbook] - def serialize(collection, view_context) + def serialize(collection, view_context = nil) @collection = collection @view_context = view_context + load_columns unless @columns_loaded apply_filter @before_filter export_collection(collection) apply_filter @after_filter @@ -145,6 +167,15 @@ def localized_name(i18n_scope = nil) private + def load_columns + return if @columns_loaded + @columns = resource_columns(@resource_class) + @columns_loaded = true + @column_updates.each(&:call) + @column_updates = [] + columns + end + def to_stream stream = StringIO.new('') book.write stream @@ -158,11 +189,11 @@ def clean_up def export_collection(collection) return if columns.none? - row_index = 0 + row_index = sheet.dimensions[1] unless @skip_header - header_row(collection) - row_index = 1 + header_row(sheet.row(row_index), collection) + row_index += 1 end collection.each do |resource| @@ -173,14 +204,13 @@ def export_collection(collection) # tranform column names into array of localized strings # @return [Array] - def header_row(collection) - row = sheet.row(0) + def header_row(row, collection) apply_format_to_row(row, create_format(header_format)) fill_row(row, header_data_for(collection)) end def header_data_for(collection) - resource = collection.first + resource = collection.first || @resource_class.new columns.map do |column| column.localized_name(i18n_scope) if in_scope(resource, column) end.compact diff --git a/lib/active_admin/xls/resource_controller_extension.rb b/lib/active_admin/xls/resource_controller_extension.rb index 07624b0..912691a 100644 --- a/lib/active_admin/xls/resource_controller_extension.rb +++ b/lib/active_admin/xls/resource_controller_extension.rb @@ -10,15 +10,22 @@ def self.included(base) def index_with_xls index_without_xls do |format| - yield format if block_given? - format.xls do - xls = active_admin_config.xls_builder.serialize(collection, - view_context) + xls_collection = if method(:find_collection).arity.zero? + collection + else + find_collection except: :pagination + end + xls = active_admin_config.xls_builder.serialize( + xls_collection, + view_context + ) send_data(xls, filename: xls_filename, type: Mime::Type.lookup_by_extension(:xls)) end + + yield(format) if block_given? end end diff --git a/lib/active_admin/xls/version.rb b/lib/active_admin/xls/version.rb index aff0077..43af3a9 100644 --- a/lib/active_admin/xls/version.rb +++ b/lib/active_admin/xls/version.rb @@ -1,5 +1,5 @@ module ActiveAdmin module Xls - VERSION = '1.0.4'.freeze + VERSION = '1.0.5'.freeze end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1efc359..a744a75 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,25 +1,38 @@ require 'simplecov' SimpleCov.start do - add_filter "/rails/" + add_filter '/rails/' + add_filter '/spec/' end +ENV['RAILS_ENV'] = 'test' + # prepare ENV for rails require 'rails' -ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{Rails::VERSION::STRING}", __FILE__) +ENV['RAILS_ROOT'] = File.expand_path( + "../rails/rails-#{Rails::VERSION::STRING}", + __FILE__ +) # ensure testing application is in place -unless File.exists?(ENV['RAILS_ROOT']) - puts "Please run bundle exec rake setup before running the specs." +unless File.exist?(ENV['RAILS_ROOT']) + puts 'Please run bundle exec rake setup before running the specs.' exit end # load up activeadmin and activeadmin-xls +require 'active_record' +require 'active_admin' +require 'devise' require 'activeadmin-xls' -ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"] +ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + '/app/admin'] + # start up rails require ENV['RAILS_ROOT'] + '/config/environment' # and finally,here's rspec require 'rspec/rails' + +# Disabling authentication in specs so that we don't have to worry about +# it allover the place ActiveAdmin.application.authentication_method = false ActiveAdmin.application.current_user_method = false diff --git a/spec/support/rails_template.rb b/spec/support/rails_template.rb index 819f328..c110493 100644 --- a/spec/support/rails_template.rb +++ b/spec/support/rails_template.rb @@ -1,73 +1,102 @@ # Rails template to build the sample app for specs # Create a cucumber database and environment -copy_file File.expand_path('../templates/cucumber.rb', __FILE__), "config/environments/cucumber.rb" -copy_file File.expand_path('../templates/cucumber_with_reloading.rb', __FILE__), "config/environments/cucumber_with_reloading.rb" +copy_file File.expand_path('../templates/cucumber.rb', __FILE__), + 'config/environments/cucumber.rb' +copy_file File.expand_path('../templates/cucumber_with_reloading.rb', __FILE__), + 'config/environments/cucumber_with_reloading.rb' gsub_file 'config/database.yml', /^test:.*\n/, "test: &test\n" -gsub_file 'config/database.yml', /\z/, "\ncucumber:\n <<: *test\n database: db/cucumber.sqlite3" -gsub_file 'config/database.yml', /\z/, "\ncucumber_with_reloading:\n <<: *test\n database: db/cucumber.sqlite3" +gsub_file 'config/database.yml', + /\z/, + "\ncucumber:\n <<: *test\n database: db/cucumber.sqlite3" +gsub_file 'config/database.yml', + /\z/, + "\ncucumber_with_reloading:\n <<: *test\n database: db/cucumber.sqlite3" # Generate some test models -generate :model, "post title:string body:text published_at:datetime author_id:integer category_id:integer" -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" +generate :model, 'post title:string body:text published_at:datetime author_id:integer category_id:integer' +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" # Rails 3.2.3 model generator declare attr_accessible -inject_into_file 'app/models/post.rb', " attr_accessible :author\n", :before => "end" if Rails::VERSION::STRING >= '3.2.3' -generate :model, "user type:string first_name:string last_name:string username:string age:integer" -inject_into_file 'app/models/user.rb', " has_many :posts, :foreign_key => 'author_id'\n", :after => "class User < ActiveRecord::Base\n" -generate :model, "publisher --migration=false --parent=User" +if Rails::VERSION::MAJOR == 3 + inject_into_file 'app/models/post.rb', + " attr_accessible :author\n", + before: 'end' +end +generate :model, 'user type:string first_name:string last_name:string username:string age:integer' +inject_into_file 'app/models/user.rb', " has_many :posts, foreign_key: 'author_id'\n", after: "class User < ActiveRecord::Base\n" +generate :model, 'publisher --migration=false --parent=User' generate :model, 'category name:string description:text' -inject_into_file 'app/models/category.rb', " has_many :posts\n accepts_nested_attributes_for :posts\n", :after => "class Category < ActiveRecord::Base\n" +inject_into_file 'app/models/category.rb', " has_many :posts\n accepts_nested_attributes_for :posts\n", after: "class Category < ActiveRecord::Base\n" generate :model, 'store name:string' # Generate a model with string ids -generate :model, "tag name:string" -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" ) -id_model_setup = <<-EOF +generate :model, 'tag name:string' +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" ) +id_model_setup = <<-MODEL self.primary_key = :id before_create :set_id - + private def set_id - self.id = 8.times.inject("") { |s,e| s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr } + self.id = 8.times.inject('') { |s,e| s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr } end -EOF -inject_into_file 'app/models/tag.rb', id_model_setup, :after => "class Tag < ActiveRecord::Base\n" +MODEL + +inject_into_file 'app/models/tag.rb', + id_model_setup, + after: "class Tag < ActiveRecord::Base\n" -if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 1 #Rails 3.1 Gotcha - gsub_file 'app/models/tag.rb', /self\.primary_key.*$/, "define_attr_method :primary_key, :id" +if Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR == 1 # Rails 3.1 Gotcha + gsub_file 'app/models/tag.rb', + /self\.primary_key.*$/, + 'define_attr_method :primary_key, :id' end # Configure default_url_options in test environment -inject_into_file "config/environments/test.rb", " config.action_mailer.default_url_options = { :host => 'example.com' }\n", :after => "config.cache_classes = true\n" +inject_into_file 'config/environments/test.rb', + " config.action_mailer.default_url_options = { host: 'example.com' }\n", + after: "config.cache_classes = true\n" +puts File.expand_path('../../../lib/activeadmin-xls', __FILE__) # Add our local Active Admin to the load path -inject_into_file "config/environment.rb", "\nrequire \"activeadmin-xls\"\n", :after => "require File.expand_path('../application', __FILE__)" +inject_into_file 'config/environment.rb', + "\nrequire \"#{File.expand_path('../../../lib/activeadmin-xls', __FILE__)}\"\n", + after: "require File.expand_path('../application', __FILE__)" # Add some translations -append_file "config/locales/en.yml", File.read(File.expand_path('../templates/en.yml', __FILE__)) +append_file 'config/locales/en.yml', + File.read(File.expand_path('../templates/en.yml', __FILE__)) # Add predefined admin resources -directory File.expand_path('../templates/admin', __FILE__), "app/admin" +directory File.expand_path('../templates/admin', __FILE__), 'app/admin' -run "rm Gemfile" -run "rm -r test" -run "rm -r spec" +run 'rm Gemfile' +run 'rm -r test' +run 'rm -r spec' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -# we need this routing path, named "logout_path", for testing -route <<-EOS +if Rails::VERSION::MAJOR == 3 + # we need this routing path, named "logout_path", for testing + route <<-ROUTE devise_scope :user do - match '/admin/logout' => 'active_admin/devise/sessions#destroy', :as => :logout + match '/admin/logout' => 'active_admin/devise/sessions#destroy', as: :logout end -EOS + ROUTE +end generate :'active_admin:install' +if Rails::VERSION::MAJOR > 3 + inject_into_file 'config/initializers/active_admin.rb', + " config.download_links = %i[csv xml json xls]\n", + after: " # == Download Links\n" +end + # Setup a root path for devise -route "root :to => 'admin/dashboard#index'" +route "root to: 'admin/dashboard#index'" -rake "db:migrate" -rake "db:test:prepare" -run "/usr/bin/env RAILS_ENV=cucumber rake db:migrate" +rake 'db:migrate' +rake 'db:test:prepare' +run '/usr/bin/env RAILS_ENV=cucumber rake db:migrate' diff --git a/spec/support/rails_template_with_data.rb b/spec/support/rails_template_with_data.rb index b5d8ea7..c0baa7c 100644 --- a/spec/support/rails_template_with_data.rb +++ b/spec/support/rails_template_with_data.rb @@ -1,13 +1,13 @@ # Use the default -apply File.expand_path("../rails_template.rb", __FILE__) +apply File.expand_path('../rails_template.rb', __FILE__) # Register Active Admin controllers -%w{ Post User Category }.each do |type| +%w[Post User Category].each do |type| generate :'active_admin:resource', type end -scopes = <<-EOF - scope :all, :default => true +scopes = <<-SCOPES + scope :all, default: true scope :drafts do |posts| posts.where(["published_at IS NULL"]) @@ -24,22 +24,25 @@ scope :my_posts do |posts| posts.where(:author_id => current_admin_user.id) end +SCOPES -EOF -inject_into_file 'app/admin/posts.rb', scopes , :after => "ActiveAdmin.register Post do\n" +inject_into_file 'app/admin/post.rb', + scopes, + after: "ActiveAdmin.register Post do\n" # Setup some default data -append_file "db/seeds.rb", <<-EOF - users = ["Jimi Hendrix", "Jimmy Page", "Yngwie Malmsteen", "Eric Clapton", "Kirk Hammett"].collect do |name| +append_file 'db/seeds.rb', <<-SEEDS + + users = ['Jimi Hendrix', 'Jimmy Page', 'Yngwie Malmsteen', 'Eric Clapton', 'Kirk Hammett'].collect do |name| first, last = name.split(" ") - User.create! :first_name => first, - :last_name => last, - :username => [first,last].join('-').downcase, - :age => rand(80) + User.create! first_name: first, + last_name: last, + username: [first,last].join('-').downcase, + age: rand(80) end - categories = ["Rock", "Pop Rock", "Alt-Country", "Blues", "Dub-Step"].collect do |name| - Category.create! :name => name + categories = ['Rock', 'Pop Rock', 'Alt-Country', 'Blues', 'Dub-Step'].collect do |name| + Category.create! name: name end published_at_values = [Time.now.utc - 5.days, Time.now.utc - 1.day, nil, Time.now.utc + 3.days] @@ -48,12 +51,12 @@ user = users[i % users.size] cat = categories[i % categories.size] published_at = published_at_values[i % published_at_values.size] - Post.create :title => "Blog Post \#{i}", - :body => "Blog post \#{i} is written by \#{user.username} about \#{cat.name}", - :category_id => cat.id, - :published_at => published_at, - :author => user + Post.create title: "Blog Post \#{i}", + body: "Blog post \#{i} is written by \#{user.username} about \#{cat.name}", + category_id: cat.id, + published_at: published_at, + author: user end -EOF + SEEDS rake 'db:seed' diff --git a/spec/support/templates/en.yml b/spec/support/templates/en.yml index accf428..32ee8ad 100644 --- a/spec/support/templates/en.yml +++ b/spec/support/templates/en.yml @@ -1,5 +1,5 @@ # Sample translations used to test ActiveAdmin's I18n integration. - axlsx: + xls: post: id: ID title: Title diff --git a/spec/xls/unit/build_download_format_links_spec.rb b/spec/xls/unit/build_download_format_links_spec.rb index ebed422..cbc05a9 100644 --- a/spec/xls/unit/build_download_format_links_spec.rb +++ b/spec/xls/unit/build_download_format_links_spec.rb @@ -19,30 +19,34 @@ def mock_action_view(assigns = {}) let(:view) do view = mock_action_view - view.request.stub!(:query_parameters).and_return({:controller => 'admin/posts', :action => 'index', :page => '1'}) - view.controller.params = {:controller => 'admin/posts', :action => 'index'} + allow(view.request).to receive(:query_parameters) { { page: '1' } } + allow(view.request).to receive(:path_parameters) do + { controller: 'admin/posts', action: 'index' } + end view end # Helper to render paginated collections within an arbre context def paginated_collection(*args) - render_arbre_component({:paginated_collection_args => args}, view) do + render_arbre_component({ paginated_collection_args: args }, view) do paginated_collection(*paginated_collection_args) end end let(:collection) do - posts = [Post.new(:title => "First Post")] + posts = [Post.new(title: 'First Post')] Kaminari.paginate_array(posts).page(1).per(5) end let(:pagination) { paginated_collection(collection) } before do - collection.stub!(:reorder) { collection } + allow(collection).to receive(:except) { collection } unless collection.respond_to? :except + allow(collection).to receive(:group_values) { [] } unless collection.respond_to? :group_values + allow(collection).to receive(:reorder) { collection } end - it "renders the xls download link" do - pagination.children.last.content.should match(/XLS/) + it 'renders the xls download link' do + expect(pagination.children.last.content).to match(/XLS/) end end diff --git a/spec/xls/unit/builder_spec.rb b/spec/xls/unit/builder_spec.rb index f30b7c8..f68a8f3 100644 --- a/spec/xls/unit/builder_spec.rb +++ b/spec/xls/unit/builder_spec.rb @@ -3,191 +3,190 @@ module ActiveAdmin module Xls describe Builder do - let(:builder) { Builder.new(Post) } let(:content_columns) { Post.content_columns } context 'the default builder' do - subject { builder } - its(:header_style) { should == { :bg_color => '00', :fg_color => 'FF', :sz => 12, :alignment => { :horizontal => :center } } } - its(:i18n_scope) { should be_nil } - its("columns.size") { should == content_columns.size + 1 } + it 'has no header style' do + expect(builder.header_style).to eq({}) + end + it 'has no i18n scope' do + expect(builder.i18n_scope).to be_nil + end + it 'has default columns' do + expect(builder.columns.size).to eq(content_columns.size + 1) + end end context 'customizing a builder' do it 'deletes columns we tell it we dont want' do builder.delete_columns :id, :body - builder.columns.size.should == content_columns.size - 1 + expect(builder.columns.size).to eq(content_columns.size - 1) end it 'lets us say we dont want the header' do builder.skip_header - builder.instance_values["skip_header"].should be_true + expect(builder.instance_values['skip_header']).to be_truthy end it 'lets us add custom columns' do builder.column(:hoge) - builder.columns.size.should == content_columns.size + 2 + expect(builder.columns.size).to eq(content_columns.size + 2) end it 'lets us clear all columns' do builder.clear_columns - builder.columns.size.should == 0 + expect(builder.columns.size).to eq(0) end context 'Using Procs for delayed content generation' do - - let(:post) { Post.new(:title => "Hot Dawg") } + let(:post) { Post.new(title: 'Hot Dawg') } before do - builder.column(:hoge) { |resource| "#{resource.title} - with cheese" } + builder.column(:hoge) do |resource| + "#{resource.title} - with cheese" + end end it 'stores the block when defining a column for later execution.' do - builder.columns.last.data.should be_a(Proc) + expect(builder.columns.last.data).to be_a(Proc) end it 'evaluates custom column blocks' do - builder.columns.last.data.call(post).should == "Hot Dawg - with cheese" + expect(builder.columns.last.data.call(post)).to eq('Hot Dawg - with cheese') end end end context 'sheet generation without headers' do - let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] } + let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] } - let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] } + let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] } - let!(:builder) { - Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:xls, :post]) do + let!(:builder) do + Builder.new(Post, header_format: { weight: :bold }, i18n_scope: %i[xls post]) do skip_header end - } + end before do - User.stub!(:all) { users } - Post.stub!(:all) { posts } - # disable clean up so we can get the package. - builder.stub(:clean_up) { false } - builder.serialize(Post.all) - @package = builder.send(:package) + # disable clean up so we can get the book. + allow(builder).to receive(:clean_up) { false } + # @book = Spreadsheet.open(builder.serialize(posts)) + builder.serialize(posts) + @book = builder.send(:book) @collection = builder.collection end it 'does not serialize the header' do - not_header = @package.workbook.worksheets.first.rows.first - not_header.cells.first.value.should_not == 'Title' + expect(@book.worksheets.first[0, 0]).not_to eq('Title') end end context 'whitelisted sheet generation' do - let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] } + let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] } - let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] } + let!(:posts) do + [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] + end - let!(:builder) { - Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:xls, :post]) do + let!(:builder) do + Builder.new(Post, header_style: {}, i18n_scope: %i[xls post]) do skip_header whitelist column :title end - } + end before do - User.stub!(:all) { users } - Post.stub!(:all) { posts } - # disable clean up so we can get the package. - builder.stub(:clean_up) { false } + allow(User).to receive(:all) { users } + allow(Post).to receive(:all) { posts } + # disable clean up so we can get the book. + allow(builder).to receive(:clean_up) { false } builder.serialize(Post.all) - @package = builder.send(:package) + @book = builder.send(:book) @collection = builder.collection end it 'does not serialize the header' do - sheet = @package.workbook.worksheets.first - sheet.rows.first.cells.size.should == 1 - sheet.rows.first.cells.first.value.should == @collection.first.title + sheet = @book.worksheets.first + expect(sheet.column_count).to eq(1) + expect(sheet[0, 0]).to eq(@collection.first.title) end end context 'Sheet generation with a highly customized configuration.' do - - let!(:users) { [User.new(first_name: 'bob', last_name: 'nancy')] } - - let!(:posts) { [Post.new(title: 'bob', body: 'is a swell guy', author: users.first)] } - - let!(:builder) { - Builder.new(Post, header_style: { sz: 10, fg_color: "FF0000" }, i18n_scope: [:xls, :post]) do + let!(:builder) do + Builder.new(Post, header_style: { size: 10, color: 'red' }, i18n_scope: %i[xls post]) do delete_columns :id, :created_at, :updated_at - column(:author) { |resource| "#{resource.author.first_name} #{resource.author.last_name}" } - after_filter { |sheet| - sheet.add_row [] - sheet.add_row ['Author Name', 'Number of Posts'] - data = [] - labels = [] - User.all.each do |user| - data << user.posts.size - labels << "#{user.first_name} #{user.last_name}" - sheet.add_row [labels.last, data.last] - end - chart_color = %w(88F700 279CAC B2A200 FD66A3 F20062 C8BA2B 67E6F8 DFFDB9 FFE800 B6F0F8) - sheet.add_chart(::xls::Pie3DChart, :title => "post by author") do |chart| - chart.add_series :data => data, :labels => labels, :colors => chart_color - chart.start_at 4, 0 - chart.end_at 7, 20 + column(:author) do |resource| + "#{resource.author.first_name} #{resource.author.last_name}" + end + after_filter do |sheet| + row_number = sheet.dimensions[1] + sheet.update_row(row_number) + row_number += 1 + sheet.update_row(row_number, 'Author Name', 'Number of Posts') + users = collection.map(&:author).uniq(&:id) + users.each do |user| + row_number += 1 + sheet.update_row(row_number, + "#{user.first_name} #{user.last_name}", + user.posts.size) end - } + end before_filter do |sheet| - collection.first.author.first_name = 'Set In Proc' - sheet.add_row ['Created', Time.zone.now] - sheet.add_row [] + users = collection.map(&:author) + users.each do |user| + user.first_name = 'Set In Proc' if user.first_name == 'bob' + end + row_number = sheet.dimensions[1] + sheet.update_row(row_number, 'Created', Time.zone.now) + row_number += 1 + sheet.update_row(row_number, '') end end - } + end - before(:all) do - User.stub!(:all) { users } - Post.stub!(:all) { posts } - # disable clean up so we can get the package. - builder.stub(:clean_up) { false } + before do + Post.all.each(&:destroy) + User.all.each(&:destroy) + @user = User.create!(first_name: 'bob', last_name: 'nancy') + @post = Post.create!(title: 'bob', + body: 'is a swell guy', + author: @user) + # disable clean up so we can get the book. + allow(builder).to receive(:clean_up) { false } builder.serialize(Post.all) - @package = builder.send(:package) + @book = builder.send(:book) @collection = builder.collection end it 'provides the collection object' do - @collection.count.should == Post.all.count + expect(@collection.count).to eq(Post.all.count) end it 'merges our customizations with the default header style' do - builder.header_style[:sz].should be(10) - builder.header_style[:fg_color].should == 'FF0000' - builder.header_style[:bg_color].should == '00' + expect(builder.header_style[:size]).to eq(10) + expect(builder.header_style[:color]).to eq('red') + # expect(builder.header_style[:pattern_bg_color]).to eq('00') end it 'uses the specified i18n_scope' do - builder.i18n_scope.should == [:xls, :post] + expect(builder.i18n_scope).to eq(%i[xls post]) end it 'translates the header row based on our i18n scope' do - header_row = @package.workbook.worksheets.first.rows[2] - header_row.cells.map(&:value).should == ['Title', 'Content', 'Published On', 'Publisher'] + header_row = @book.worksheets.first.row(2) + expect(header_row).to eq(['Title', 'Content', 'Published On', 'Publisher']) end it 'processes the before filter' do - @package.workbook.worksheets.first["A1"].value.should == 'Created' + expect(@book.worksheets.first.cell(0, 0)).to eq('Created') end it 'lets us work against the collection in the before filter' do - @package.workbook.worksheets.first.rows.last.cells.first.value.should == 'Set In Proc nancy' - end - - it 'processes the after filter' do - @package.workbook.charts.size.should == 1 - end - - it 'has no OOXML validation errors' do - @package.validate.size.should == 0 + expect(@book.worksheets.first.last_row[0]).to eq('Set In Proc nancy') end end end diff --git a/spec/xls/unit/dsl_spec.rb b/spec/xls/unit/dsl_spec.rb index a483941..7ebedbe 100644 --- a/spec/xls/unit/dsl_spec.rb +++ b/spec/xls/unit/dsl_spec.rb @@ -3,48 +3,55 @@ module ActiveAdmin module Xls describe ::ActiveAdmin::ResourceDSL do - context 'in a registraiton block' do - let(:builder) { + context 'in a registration block' do + let(:builder) do config = ActiveAdmin.register(Post) do - xls(i18n_scope: [:rspec], header_style: { sz: 20 }) do + xls(i18n_scope: [:rspec], header_style: { size: 20 }) do delete_columns :id, :created_at column(:author) { |post| post.author.first_name } - before_filter { |sheet| sheet.add_row ['before_filter'] } - after_filter { |sheet| sheet.add_row['after_filter'] } + before_filter do |sheet| + row_number = sheet.dimensions[1] + sheet.update_row(row_number, 'before_filter') + end + after_filter do |sheet| + row_number = sheet.dimensions[1] + sheet.update_row(row_number, 'after_filter') + end skip_header end end config.xls_builder - } - + end - it "uses our customized i18n scope" do - builder.i18n_scope.should == [:rspec] + it 'uses our customized i18n scope' do + expect(builder.i18n_scope).to eq([:rspec]) end - it "removed the columns we told it to ignore" do - [:id, :create_at].each do |removed| - builder.columns.index{|column| column.name == removed}.should be_nil + it 'removed the columns we told it to ignore' do + %i[id create_at].each do |removed| + column_index = builder.columns.index { |col| col.name == removed } + expect(column_index).to be_nil end end - it "added the columns we declared" do - builder.columns.index{ |column| column.name == :author}.should_not be_nil + it 'added the columns we declared' do + added_index = builder.columns.index { |col| col.name == :author } + expect(added_index).not_to be_nil end - it "has a before filter set" do - builder.instance_values["before_filter"].should be_a(Proc) + it 'has a before filter set' do + expect(builder.instance_values['before_filter']).to be_a(Proc) end - it "has an after filter set" do - builder.instance_values["after_filter"].should be_a(Proc) + it 'has an after filter set' do + expect(builder.instance_values['after_filter']).to be_a(Proc) end - it "indicates that the header should be excluded" do - builder.instance_values['skip_header'].should be_true + it 'indicates that the header should be excluded' do + expect(builder.instance_values['skip_header']).to be_truthy end - it "updates the header style" do - builder.header_style[:sz].should be(20) + it 'updates the header style' do + expect(builder.header_style[:size]).to eq(20) end end end diff --git a/spec/xls/unit/resource_controller_spec.rb b/spec/xls/unit/resource_controller_spec.rb index bf79b85..8932e5f 100644 --- a/spec/xls/unit/resource_controller_spec.rb +++ b/spec/xls/unit/resource_controller_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' describe ActiveAdmin::ResourceController do - let(:mime) { Mime::Type.lookup_by_extension(:xls) } let(:request) do @@ -18,27 +17,36 @@ end end - let(:filename) { "#{controller.resource_class.to_s.downcase.pluralize}-#{Time.now.strftime("%Y-%m-%d")}.xls" } + let(:filename) do + "#{controller.resource_class.to_s.downcase.pluralize}-#{Time.now.strftime('%Y-%m-%d')}.xls" + end it 'generates an xls filename' do - controller.xls_filename.should == filename + expect(controller.xls_filename).to eq(filename) end context 'when making requests with the xls mime type' do - it 'returns xls attachment when requested' do + it 'returns xls attachment when requested' do controller.send :index - response.headers["Content-Disposition"].should == "attachment; filename=\"#{filename}\"" - response.headers["Content-Transfer-Encoding"].should == 'binary' + disposition = "attachment; filename=\"#{filename}\"" + expect(response.headers['Content-Disposition']).to eq(disposition) + expect(response.headers['Content-Transfer-Encoding']).to eq('binary') end it 'returns max_csv_records for per_page' do - controller.send(:per_page).should == controller.send(:max_csv_records) + # this might need to go away! + max_csv_records = if controller.respond_to?(:max_csv_records, true) + controller.send(:max_csv_records) + else + controller.send(:per_page) + end + expect(controller.send(:per_page)).to eq(max_csv_records) end - it 'kicks back to the default per_page when we are not specifying a xls mime type' do + it 'uses to the default per_page when we do not specify xls mime type' do controller.request.accept = 'text/html' - controller.send(:per_page).should == ActiveAdmin.application.default_per_page + aa_default_per_page = ActiveAdmin.application.default_per_page + expect(controller.send(:per_page)).to eq(aa_default_per_page) end end end - diff --git a/spec/xls/unit/resource_spec.rb b/spec/xls/unit/resource_spec.rb index c14877a..b31f92b 100644 --- a/spec/xls/unit/resource_spec.rb +++ b/spec/xls/unit/resource_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'spec_helper' include ActiveAdmin module ActiveAdmin @@ -7,18 +7,18 @@ module Xls let(:resource) { ActiveAdmin.register(Post) } let(:custom_builder) do - Builder.new(Post) do |builder| + Builder.new(Post) do column(:fake) { :fake } end end context 'when registered' do - it "each resource has an xls_builer" do - resource.xls_builder.should be_a(Builder) + it 'each resource has an xls_builder' do + expect(resource.xls_builder).to be_a(Builder) end - it "We can specify our own configured builder" do - lambda { resource.xls_builder = custom_builder }.should_not raise_error + it 'We can specify our own configured builder' do + expect { resource.xls_builder = custom_builder }.not_to raise_error end end end