active_admin_import 5.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,11 +25,29 @@ module ActiveAdminImport
25
25
  # +plural_resource_label+:: pluralized resource label value (default config.plural_resource_label)
26
26
  #
27
27
  module DSL
28
+ CONTEXT_METHOD = :active_admin_import_context
29
+
30
+ ACTIVE_ADMIN_V4 = Gem::Version.new(ActiveAdmin::VERSION) >= Gem::Version.new('4.0.0.beta1')
31
+
32
+ def self.prepare_import_model(template_object, controller, params: nil)
33
+ model = template_object.is_a?(Proc) ? template_object.call : template_object
34
+ if params
35
+ params_key = ActiveModel::Naming.param_key(model.class)
36
+ model.assign_attributes(params[params_key].try(:deep_symbolize_keys) || {})
37
+ end
38
+ return model unless controller.respond_to?(CONTEXT_METHOD, true)
39
+ context = controller.send(CONTEXT_METHOD)
40
+ return model unless context.is_a?(Hash)
41
+ context = context.merge(file: model.file) if model.respond_to?(:file) && !context.key?(:file)
42
+ model.assign_attributes(context)
43
+ model
44
+ end
45
+
28
46
  DEFAULT_RESULT_PROC = lambda do |result, options|
29
47
  model_name = options[:resource_label].downcase
30
48
  plural_model_name = options[:plural_resource_label].downcase
31
49
  if result.empty?
32
- flash[:warning] = I18n.t('active_admin_import.file_empty_error')
50
+ flash[ACTIVE_ADMIN_V4 ? :alert : :warning] = I18n.t('active_admin_import.file_empty_error')
33
51
  else
34
52
  if result.failed?
35
53
  flash[:error] = I18n.t(
@@ -57,11 +75,7 @@ module ActiveAdminImport
57
75
 
58
76
  collection_action :import, method: :get do
59
77
  authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
60
- @active_admin_import_model = if options[:template_object].is_a?(Proc)
61
- options[:template_object].call
62
- else
63
- options[:template_object]
64
- end
78
+ @active_admin_import_model = ActiveAdminImport::DSL.prepare_import_model(options[:template_object], self)
65
79
  render template: options[:template]
66
80
  end
67
81
 
@@ -69,7 +83,8 @@ module ActiveAdminImport
69
83
  if authorized?(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
70
84
  link_to(
71
85
  I18n.t('active_admin_import.import_model', plural_model: options[:plural_resource_label]),
72
- action: :import
86
+ { action: :import },
87
+ options[:action_item_html_options]
73
88
  )
74
89
  end
75
90
  end
@@ -78,13 +93,9 @@ module ActiveAdminImport
78
93
  authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
79
94
  _params = params.respond_to?(:to_unsafe_h) ? params.to_unsafe_h : params
80
95
  params = ActiveSupport::HashWithIndifferentAccess.new _params
81
- @active_admin_import_model = if options[:template_object].is_a?(Proc)
82
- options[:template_object].call
83
- else
84
- options[:template_object]
85
- end
86
- params_key = ActiveModel::Naming.param_key(@active_admin_import_model.class)
87
- @active_admin_import_model.assign_attributes(params[params_key].try(:deep_symbolize_keys) || {})
96
+ @active_admin_import_model = ActiveAdminImport::DSL.prepare_import_model(
97
+ options[:template_object], self, params: params
98
+ )
88
99
  # go back to form
89
100
  return render template: options[:template] unless @active_admin_import_model.valid?
90
101
  @importer = Importer.new(
@@ -96,7 +107,7 @@ module ActiveAdminImport
96
107
  result = @importer.import
97
108
 
98
109
  if block_given?
99
- instance_eval(&block)
110
+ instance_exec result, options, &block
100
111
  else
101
112
  instance_exec result, options, &DEFAULT_RESULT_PROC
102
113
  end
@@ -18,7 +18,8 @@ module ActiveAdminImport
18
18
  :headers_rewrites,
19
19
  :batch_size,
20
20
  :batch_transaction,
21
- :csv_options
21
+ :csv_options,
22
+ :result_class
22
23
  ].freeze
23
24
 
24
25
  def initialize(resource, model, options)
@@ -29,7 +30,7 @@ module ActiveAdminImport
29
30
  end
30
31
 
31
32
  def import_result
32
- @import_result ||= ImportResult.new
33
+ @import_result ||= (options[:result_class] || ImportResult).new
33
34
  end
34
35
 
35
36
  def file
@@ -37,7 +37,7 @@ module ActiveAdminImport
37
37
  validate :file_contents_present, if: ->(me) { me.file.present? }
38
38
 
39
39
  before_validation :unzip_file, if: ->(me) { me.archive? && me.allow_archive? }
40
- before_validation :encode_file, if: ->(me) { me.force_encoding? && me.file.present? }
40
+ after_validation :encode_file, if: ->(me) { me.errors.empty? && me.force_encoding? && me.file.present? }
41
41
 
42
42
  attr_reader :attributes
43
43
 
@@ -48,6 +48,7 @@ module ActiveAdminImport
48
48
  end
49
49
 
50
50
  def assign_attributes(args = {}, new_record = false)
51
+ args[:file] = nil unless args.key?(:file)
51
52
  @attributes.merge!(args)
52
53
  @new_record = new_record
53
54
  args.keys.each do |key|
@@ -16,12 +16,14 @@ module ActiveAdminImport
16
16
  :ignore,
17
17
  :template,
18
18
  :template_object,
19
+ :result_class,
19
20
  :resource_class,
20
21
  :resource_label,
21
22
  :plural_resource_label,
22
23
  :error_limit,
23
24
  :headers_rewrites,
24
- :if
25
+ :if,
26
+ :action_item_html_options
25
27
  ].freeze
26
28
 
27
29
  def self.options_for(config, options = {})
@@ -38,7 +40,10 @@ module ActiveAdminImport
38
40
  plural_resource_label: config.plural_resource_label,
39
41
  error_limit: 5,
40
42
  headers_rewrites: {},
41
- if: true
43
+ if: true,
44
+ # AA 4's built-in action_items hardcode this class for Tailwind styling
45
+ # (lib/active_admin/resource/action_items.rb). It's a no-op on AA 3.
46
+ action_item_html_options: { class: 'action-item-button' }
42
47
  }.deep_merge(options)
43
48
  end
44
49
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveAdminImport
4
- VERSION = '5.1.0'
4
+ VERSION = '7.0.0'
5
5
  end
@@ -0,0 +1,2 @@
1
+ Name,Last name,Birthday
2
+ John,Doe,1986-05-01
@@ -0,0 +1,3 @@
1
+ "Body"
2
+ "First comment"
3
+ "Second comment"
data/spec/import_spec.rb CHANGED
@@ -27,7 +27,7 @@ describe 'import', type: :feature do
27
27
  zip_file = File.expand_path("./spec/fixtures/files/#{name}.zip")
28
28
 
29
29
  begin
30
- Zip::File.open(zip_file, Zip::File::CREATE) do |z|
30
+ Zip::File.open(zip_file, create: true) do |z|
31
31
  z.add "#{name}.csv", File.expand_path("./spec/fixtures/files/#{name}.csv")
32
32
  end
33
33
  instance_eval &block
@@ -41,8 +41,8 @@ describe 'import', type: :feature do
41
41
  end
42
42
 
43
43
  def upload_file!(name, ext = 'csv')
44
- attach_file('active_admin_import_model_file', File.expand_path("./spec/fixtures/files/#{name}.#{ext}"))
45
- find_button('Import').click
44
+ attach_file(ImportFormSelectors.file_input_id, File.expand_path("./spec/fixtures/files/#{name}.#{ext}"))
45
+ find_button(ImportFormSelectors.import_button_text).click
46
46
  end
47
47
 
48
48
  context 'posts index' do
@@ -118,7 +118,7 @@ describe 'import', type: :feature do
118
118
  # reload page
119
119
  visit '/admin/posts/import'
120
120
  # submit form without file
121
- find_button('Import').click
121
+ find_button(ImportFormSelectors.import_button_text).click
122
122
  end
123
123
 
124
124
  it 'should render validation error' do
@@ -171,7 +171,7 @@ describe 'import', type: :feature do
171
171
  # TODO: removing this causes undefined method `ransack' for #<ActiveRecord::Relation []>
172
172
  allow_any_instance_of(Admin::AuthorsController).to receive(:find_collection).and_return(Author.all)
173
173
  visit '/admin/authors'
174
- find_link('Import Authors').click
174
+ find_link(ImportFormSelectors.import_link_text).click
175
175
  expect(current_path).to eq('/admin/authors/import')
176
176
  end
177
177
  end
@@ -228,14 +228,14 @@ describe 'import', type: :feature do
228
228
  end
229
229
 
230
230
  it 'has valid form' do
231
- form = find('#new_active_admin_import_model')
231
+ form = find(ImportFormSelectors.form_css)
232
232
  expect(form['action']).to eq('/admin/authors/do_import')
233
233
  expect(form['enctype']).to eq('multipart/form-data')
234
- file_input = form.find('input#active_admin_import_model_file')
234
+ file_input = form.find(ImportFormSelectors.file_input_css)
235
235
  expect(file_input[:type]).to eq('file')
236
236
  expect(file_input.value).to be_blank
237
- submit_input = form.find('#active_admin_import_model_submit_action input')
238
- expect(submit_input[:value]).to eq('Import')
237
+ submit_input = form.find(ImportFormSelectors.submit_css)
238
+ expect(submit_input[:value]).to eq(ImportFormSelectors.import_button_text)
239
239
  expect(submit_input[:type]).to eq('submit')
240
240
  end
241
241
 
@@ -261,7 +261,7 @@ describe 'import', type: :feature do
261
261
 
262
262
  context 'when no file' do
263
263
  it 'should render error' do
264
- find_button('Import').click
264
+ find_button(ImportFormSelectors.import_button_text).click
265
265
  expect(Author.count).to eq(0)
266
266
  expect(page).to have_content I18n.t('active_admin_import.no_file_error')
267
267
  end
@@ -588,4 +588,147 @@ describe 'import', type: :feature do
588
588
  expect { add_author_resource(options) }.to raise_error(ArgumentError)
589
589
  end
590
590
  end
591
+
592
+ context 'when submitting empty form after validation error' do
593
+ let(:options) { {} }
594
+
595
+ before do
596
+ add_author_resource(options)
597
+ visit '/admin/authors/import'
598
+ end
599
+
600
+ it 'should NOT reuse cached file from previous submission' do
601
+ expect do
602
+ upload_file!(:author_broken_header)
603
+ expect(page).to have_content("can't write unknown attribute")
604
+ end.not_to change { Author.count }
605
+
606
+ # Second submission without selecting a file
607
+ expect do
608
+ find_button(ImportFormSelectors.import_button_text).click
609
+ expect(page).to have_content(I18n.t('active_admin_import.no_file_error'))
610
+ end.not_to change { Author.count }
611
+ end
612
+ end
613
+
614
+ context 'when importing file with invalid format and auto force_encoding' do
615
+ let(:options) { { template_object: ActiveAdminImport::Model.new(force_encoding: :auto) } }
616
+
617
+ before do
618
+ add_author_resource(options)
619
+ visit '/admin/authors/import'
620
+ end
621
+
622
+ it 'should reject invalid file format before encoding' do
623
+ expect do
624
+ upload_file!(:author_invalid_format, 'txt')
625
+ expect(page).to have_content I18n.t('active_admin_import.file_format_error')
626
+ end.not_to change { Author.count }
627
+ end
628
+ end
629
+
630
+ context 'with active_admin_import_context defined on the controller' do
631
+ before { Author.create!(name: 'John', last_name: 'Doe') }
632
+
633
+ let(:author) { Author.take }
634
+
635
+ context 'when context returns request-derived attributes' do
636
+ before do
637
+ author_id = author.id
638
+ add_post_resource(
639
+ template_object: ActiveAdminImport::Model.new(author_id: author_id),
640
+ before_batch_import: lambda do |importer|
641
+ ip = importer.model.request_ip
642
+ a_id = importer.model.author_id
643
+ importer.csv_lines.map! { |row| row << ip << a_id }
644
+ importer.headers.merge!(:'Request Ip' => :request_ip, :'Author Id' => :author_id)
645
+ end,
646
+ controller_block: proc do
647
+ def active_admin_import_context
648
+ { request_ip: request.remote_ip }
649
+ end
650
+ end
651
+ )
652
+ visit '/admin/posts/import'
653
+ upload_file!(:posts_for_author)
654
+ end
655
+
656
+ it 'merges the context into the import model so callbacks see it' do
657
+ expect(page).to have_content 'Successfully imported 2 posts'
658
+ expect(Post.count).to eq(2)
659
+ Post.all.each do |post|
660
+ expect(post.request_ip).to eq('127.0.0.1')
661
+ expect(post.author_id).to eq(author.id)
662
+ end
663
+ end
664
+ end
665
+
666
+ context 'when context returns parent id for a nested belongs_to resource' do
667
+ let(:post) { Post.create!(title: 'A post', body: 'body', author: author) }
668
+
669
+ before do
670
+ add_nested_post_comment_resource(
671
+ before_batch_import: lambda do |importer|
672
+ importer.csv_lines.map! { |row| row << importer.model.post_id }
673
+ importer.headers.merge!(:'Post Id' => :post_id)
674
+ end,
675
+ controller_block: proc do
676
+ def active_admin_import_context
677
+ { post_id: parent.id }
678
+ end
679
+ end
680
+ )
681
+ visit "/admin/posts/#{post.id}/post_comments/import"
682
+ upload_file!(:post_comments)
683
+ end
684
+
685
+ it 'automatically assigns the parent post_id to every imported comment' do
686
+ expect(page).to have_content 'Successfully imported 2 post comments'
687
+ expect(PostComment.count).to eq(2)
688
+ expect(PostComment.where(post_id: post.id).count).to eq(2)
689
+ end
690
+ end
691
+ end
692
+
693
+ # PG-only: activerecord-import populates `result.ids` reliably on PostgreSQL
694
+ # via RETURNING. On MySQL/SQLite the array is not populated by default, so
695
+ # the assertion would not be meaningful there. The :result_class option
696
+ # itself works on every adapter — this spec just demonstrates the canonical
697
+ # PR #191 use case (collecting inserted ids) on the adapter that supports it.
698
+ if ENV['DB'] == 'postgres'
699
+ context 'with custom result_class (PostgreSQL)' do
700
+ # Subclass that captures inserted ids alongside the standard counters.
701
+ # Lives in user-land so the gem itself stays free of adapter-specific
702
+ # quirks around RETURNING. This is the example documented in the README.
703
+ class ImportResultWithIds < ActiveAdminImport::ImportResult
704
+ attr_reader :ids
705
+
706
+ def initialize
707
+ super
708
+ @ids = []
709
+ end
710
+
711
+ def add(batch_result, qty)
712
+ super
713
+ @ids.concat(Array(batch_result.ids))
714
+ end
715
+ end
716
+
717
+ before do
718
+ add_author_resource(result_class: ImportResultWithIds) do |result, _options|
719
+ # Expose the captured ids on the flash so the test asserts via the
720
+ # rendered page rather than closure capture.
721
+ flash[:notice] = "Imported ids: [#{result.ids.sort.join(',')}]"
722
+ end
723
+ visit '/admin/authors/import'
724
+ upload_file!(:authors)
725
+ end
726
+
727
+ it 'collects the ids of inserted records via the custom subclass' do
728
+ expect(Author.count).to eq(2)
729
+ expected = "Imported ids: [#{Author.pluck(:id).sort.join(',')}]"
730
+ expect(page).to have_content(expected)
731
+ end
732
+ end
733
+ end
591
734
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
- # frozen_string_literal: true
2
- require 'coveralls'
3
- Coveralls.wear!
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/spec/'
4
+ end
4
5
 
5
6
  $LOAD_PATH.unshift(File.dirname(__FILE__))
6
7
  $LOAD_PATH << File.expand_path('../support', __FILE__)
@@ -10,44 +11,36 @@ require 'bundler'
10
11
  Bundler.setup
11
12
 
12
13
  ENV['RAILS_ENV'] = 'test'
13
- # Ensure the Active Admin load path is happy
14
14
  require 'rails'
15
+ require 'test_app_paths'
15
16
  ENV['RAILS'] = Rails.version
16
- ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{ENV['RAILS']}", __FILE__)
17
- # Create the test app if it doesn't exists
17
+ ENV['RAILS_ROOT'] = TestAppPaths.app_root
18
18
  system 'rake setup' unless File.exist?(ENV['RAILS_ROOT'])
19
19
 
20
20
  require 'active_model'
21
- # require ActiveRecord to ensure that Ransack loads correctly
22
21
  require 'active_record'
23
22
  require 'action_view'
24
23
  require 'active_admin'
25
24
  ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + '/app/admin']
26
25
  require ENV['RAILS_ROOT'] + '/config/environment.rb'
27
- # Disabling authentication in specs so that we don't have to worry about
28
- # it allover the place
29
26
  ActiveAdmin.application.authentication_method = false
30
27
  ActiveAdmin.application.current_user_method = false
31
28
 
32
29
  require 'rspec/rails'
33
30
  require 'support/admin'
31
+ require 'support/import_form_selectors'
34
32
  require 'capybara/rails'
35
33
  require 'capybara/rspec'
36
- require 'capybara/poltergeist'
37
-
38
- Capybara.register_driver :poltergeist do |app|
39
- Capybara::Poltergeist::Driver.new(app, js_errors: true,
40
- timeout: 80,
41
- debug: true,
42
- phantomjs_options: ['--debug=no', '--load-images=no'])
43
- end
44
34
 
45
- Capybara.javascript_driver = :poltergeist
35
+ # Specs exercise ActiveAdmin through Capybara's default rack_test driver — no
36
+ # JavaScript or real browser is needed, so no Cuprite/Chrome or app server.
37
+ Capybara.default_driver = :rack_test
46
38
 
47
39
  RSpec.configure do |config|
48
40
  config.use_transactional_fixtures = false
49
41
 
50
42
  config.before(:suite) do
43
+ ActiveRecord::Migration.maintain_test_schema!
51
44
  DatabaseCleaner.strategy = :truncation
52
45
  DatabaseCleaner.clean_with(:truncation)
53
46
  end
@@ -8,8 +8,24 @@ def add_author_resource(options = {}, &block)
8
8
  end
9
9
 
10
10
  def add_post_resource(options = {}, &block)
11
+ cb = options.delete(:controller_block)
11
12
  ActiveAdmin.register Post do
12
13
  config.filters = false
14
+ controller(&cb) if cb
15
+ active_admin_import(options, &block)
16
+ end
17
+ Rails.application.reload_routes!
18
+ end
19
+
20
+ def add_nested_post_comment_resource(options = {}, &block)
21
+ cb = options.delete(:controller_block)
22
+ ActiveAdmin.register Post do
23
+ config.filters = false
24
+ end
25
+ ActiveAdmin.register PostComment do
26
+ config.filters = false
27
+ belongs_to :post
28
+ controller(&cb) if cb
13
29
  active_admin_import(options, &block)
14
30
  end
15
31
  Rails.application.reload_routes!
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Formtastic 6 (AA 4) keeps the same DOM IDs as Formtastic 4 (AA 3) for this
4
+ # form, so one selector set serves both — branch here if a future AA shifts an ID.
5
+ module ImportFormSelectors
6
+ module_function
7
+
8
+ SELECTORS = {
9
+ form_id: 'new_active_admin_import_model',
10
+ file_input_id: 'active_admin_import_model_file',
11
+ file_input_css: 'input#active_admin_import_model_file',
12
+ submit_css: '#active_admin_import_model_submit_action input',
13
+ import_button_text: 'Import',
14
+ import_link_text: 'Import Authors'
15
+ }.freeze
16
+
17
+ def form_id = SELECTORS[:form_id]
18
+ def form_css = "##{form_id}"
19
+ def file_input_id = SELECTORS[:file_input_id]
20
+ def file_input_css = SELECTORS[:file_input_css]
21
+ def submit_css = SELECTORS[:submit_css]
22
+ def import_button_text = SELECTORS[:import_button_text]
23
+ def import_link_text = SELECTORS[:import_link_text]
24
+ end
@@ -1,29 +1,71 @@
1
- # frozen_string_literal: true
2
- # Rails template to build the sample app for specs
1
+ create_file "app/assets/config/manifest.js", skip: true
3
2
 
4
- generate :model, 'author name:string{10}:uniq last_name:string birthday:date'
5
- generate :model, 'post title:string:uniq body:text author:references'
3
+ db = ENV['DB'] || 'sqlite'
4
+ case db
5
+ when 'mysql'
6
+ remove_file 'config/database.yml'
7
+ create_file 'config/database.yml', <<~YAML
8
+ default: &default
9
+ adapter: mysql2
10
+ encoding: utf8mb4
11
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
12
+ host: <%= ENV.fetch("DB_HOST", "127.0.0.1") %>
13
+ port: <%= ENV.fetch("DB_PORT", 3306) %>
14
+ username: <%= ENV.fetch("DB_USERNAME", "root") %>
15
+ password: <%= ENV.fetch("DB_PASSWORD", "root") %>
6
16
 
7
- # Add validation
8
- inject_into_file 'app/models/author.rb', " validates_presence_of :name\n validates_uniqueness_of :last_name\n", before: 'end'
9
- inject_into_file 'app/models/post.rb', " validates_presence_of :author\n", before: 'end'
17
+ test:
18
+ <<: *default
19
+ database: active_admin_import_test
20
+ YAML
21
+ when 'postgres', 'postgresql'
22
+ remove_file 'config/database.yml'
23
+ create_file 'config/database.yml', <<~YAML
24
+ default: &default
25
+ adapter: postgresql
26
+ encoding: unicode
27
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
28
+ host: <%= ENV.fetch("DB_HOST", "127.0.0.1") %>
29
+ port: <%= ENV.fetch("DB_PORT", 5432) %>
30
+ username: <%= ENV.fetch("DB_USERNAME", "postgres") %>
31
+ password: <%= ENV.fetch("DB_PASSWORD", "postgres") %>
32
+
33
+ test:
34
+ <<: *default
35
+ database: active_admin_import_test
36
+ YAML
37
+ end
10
38
 
11
- # Configure default_url_options in test environment
12
- inject_into_file 'config/environments/test.rb', " config.action_mailer.default_url_options = { :host => 'example.com' }\n", after: "config.cache_classes = true\n"
39
+ generate :model, 'author name:string{10}:uniq last_name:string birthday:date --force'
40
+ generate :model, 'post title:string:uniq body:text request_ip:string author:references --force'
41
+ generate :model, 'post_comment body:text post:references --force'
13
42
 
14
- # Add our local Active Admin to the load path
15
- 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__)"
43
+ inject_into_file 'app/models/author.rb', " validates_presence_of :name\n validates_uniqueness_of :last_name\n", before: 'end'
44
+ inject_into_file 'app/models/post.rb', " validates_presence_of :author\n has_many :post_comments\n", before: 'end'
16
45
 
17
- run 'rm Gemfile'
46
+ # Add our local Active Admin to the load path (Rails 7.1+)
47
+ gsub_file "config/environment.rb",
48
+ 'require_relative "application"',
49
+ "require_relative \"application\"\n$LOAD_PATH.unshift('#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))}')\nrequire \"active_admin\"\n"
18
50
 
19
51
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
20
52
 
53
+ aa_v4 = ENV['AA']&.start_with?('4')
54
+
21
55
  generate :'active_admin:install --skip-users'
22
- generate :'formtastic:install'
23
56
 
24
- run 'rm -r test'
25
- run 'rm -r spec'
57
+ if aa_v4
58
+ # `active_admin:assets` swaps AA 3's Sprockets SCSS/JS for AA 4's Tailwind CSS
59
+ # stub. We don't compile it — specs assert on DOM and flash text, not styling,
60
+ # so the stub suffices and no Node is needed. `builds/` satisfies cssbundling-rails.
61
+ generate :'active_admin:assets'
62
+ run 'mkdir -p app/assets/builds'
63
+ else
64
+ generate :'formtastic:install'
65
+ end
26
66
 
67
+ run 'rm -rf test'
27
68
  route "root :to => 'admin/dashboard#index'"
69
+ rake 'db:create db:migrate'
28
70
 
29
- rake 'db:migrate'
71
+ run 'rm -f Gemfile Gemfile.lock'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestAppPaths
4
+ module_function
5
+
6
+ def app_dir_name
7
+ "rails-#{Rails::VERSION::STRING}-#{ENV['DB'] || 'sqlite'}-aa#{ENV['AA'] || 'default'}"
8
+ end
9
+
10
+ # Absolute path under spec/rails/, used as RAILS_ROOT.
11
+ def app_root
12
+ File.expand_path("../rails/#{app_dir_name}", __dir__)
13
+ end
14
+ end
data/tasks/test.rake CHANGED
@@ -1,7 +1,30 @@
1
- # frozen_string_literal: true
2
- desc 'Creates a test rails app for the specs to run against'
1
+ require_relative '../spec/support/test_app_paths'
2
+
3
+ desc "Creates a test rails app for the specs to run against"
3
4
  task :setup do
4
5
  require 'rails/version'
5
- system('mkdir spec/rails') unless File.exist?('spec/rails')
6
- system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} -m spec/support/rails_template.rb --skip-spring --skip-turbolinks --skip-bootsnap"
6
+
7
+ db = ENV['DB'] || 'sqlite'
8
+ rails_db = case db
9
+ when 'mysql' then 'mysql'
10
+ when 'postgres', 'postgresql' then 'postgresql'
11
+ else 'sqlite3'
12
+ end
13
+ aa_v4 = ENV['AA']&.start_with?('4')
14
+
15
+ puts "[setup] ActiveAdmin: #{ENV['AA'] || '(Gemfile default)'} / Rails: #{Rails::VERSION::STRING} / DB: #{rails_db}"
16
+
17
+ rails_new_opts = %W(
18
+ --skip-turbolinks
19
+ --skip-spring
20
+ --skip-bootsnap
21
+ -d #{rails_db}
22
+ -m
23
+ spec/support/rails_template.rb
24
+ )
25
+ # v4 drops sprockets-rails (see Gemfile), so skip the asset pipeline to
26
+ # avoid the auto-generated `config/initializers/assets.rb` crashing at boot.
27
+ rails_new_opts.unshift('--skip-asset-pipeline') if aa_v4
28
+
29
+ system "bundle exec rails new spec/rails/#{TestAppPaths.app_dir_name} #{rails_new_opts.join(' ')}"
7
30
  end