Skip to content

Commit 91bd319

Browse files
committed
Add active_admin_import_context convention for controller-provided context
Controllers can define an `active_admin_import_context` method returning a hash; it is merged into the import model after form params so values the controller is authoritative about (parent.id, current_user.id, request attributes, etc.) cannot be overridden by tampered form fields. This supersedes PR #137 by offering a general-purpose hook that works for nested belongs_to imports and any other controller-derived state.
1 parent ca06c33 commit 91bd319

5 files changed

Lines changed: 101 additions & 12 deletions

File tree

lib/active_admin_import/dsl.rb

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ module ActiveAdminImport
2525
# +plural_resource_label+:: pluralized resource label value (default config.plural_resource_label)
2626
#
2727
module DSL
28+
CONTEXT_METHOD = :active_admin_import_context
29+
30+
def self.build_template_object(template_object)
31+
template_object.is_a?(Proc) ? template_object.call : template_object
32+
end
33+
34+
def self.apply_import_context(model, controller)
35+
return unless controller.respond_to?(CONTEXT_METHOD, true)
36+
context = controller.send(CONTEXT_METHOD)
37+
model.assign_attributes(context) if context.is_a?(Hash)
38+
end
39+
2840
DEFAULT_RESULT_PROC = lambda do |result, options|
2941
model_name = options[:resource_label].downcase
3042
plural_model_name = options[:plural_resource_label].downcase
@@ -57,11 +69,8 @@ def active_admin_import(options = {}, &block)
5769

5870
collection_action :import, method: :get do
5971
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
72+
@active_admin_import_model = ActiveAdminImport::DSL.build_template_object(options[:template_object])
73+
ActiveAdminImport::DSL.apply_import_context(@active_admin_import_model, self)
6574
render template: options[:template]
6675
end
6776

@@ -78,13 +87,10 @@ def active_admin_import(options = {}, &block)
7887
authorize!(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class)
7988
_params = params.respond_to?(:to_unsafe_h) ? params.to_unsafe_h : params
8089
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
90+
@active_admin_import_model = ActiveAdminImport::DSL.build_template_object(options[:template_object])
8691
params_key = ActiveModel::Naming.param_key(@active_admin_import_model.class)
8792
@active_admin_import_model.assign_attributes(params[params_key].try(:deep_symbolize_keys) || {})
93+
ActiveAdminImport::DSL.apply_import_context(@active_admin_import_model, self)
8894
# go back to form
8995
return render template: options[:template] unless @active_admin_import_model.valid?
9096
@importer = Importer.new(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"Body"
2+
"First comment"
3+
"Second comment"

spec/import_spec.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,4 +588,67 @@ def upload_file!(name, ext = 'csv')
588588
expect { add_author_resource(options) }.to raise_error(ArgumentError)
589589
end
590590
end
591+
592+
context 'with active_admin_import_context defined on the controller' do
593+
before { Author.create!(name: 'John', last_name: 'Doe') }
594+
595+
let(:author) { Author.take }
596+
597+
context 'when context returns request-derived attributes' do
598+
before do
599+
author_id = author.id
600+
add_post_resource(
601+
template_object: ActiveAdminImport::Model.new(author_id: author_id),
602+
before_batch_import: lambda do |importer|
603+
ip = importer.model.request_ip
604+
a_id = importer.model.author_id
605+
importer.csv_lines.map! { |row| row << ip << a_id }
606+
importer.headers.merge!(:'Request Ip' => :request_ip, :'Author Id' => :author_id)
607+
end,
608+
controller_block: proc do
609+
def active_admin_import_context
610+
{ request_ip: request.remote_ip }
611+
end
612+
end
613+
)
614+
visit '/admin/posts/import'
615+
upload_file!(:posts_for_author)
616+
end
617+
618+
it 'merges the context into the import model so callbacks see it' do
619+
expect(page).to have_content 'Successfully imported 2 posts'
620+
expect(Post.count).to eq(2)
621+
Post.all.each do |post|
622+
expect(post.request_ip).to eq('127.0.0.1')
623+
expect(post.author_id).to eq(author.id)
624+
end
625+
end
626+
end
627+
628+
context 'when context returns parent id for a nested belongs_to resource' do
629+
let(:post) { Post.create!(title: 'A post', body: 'body', author: author) }
630+
631+
before do
632+
add_nested_post_comment_resource(
633+
before_batch_import: lambda do |importer|
634+
importer.csv_lines.map! { |row| row << importer.model.post_id }
635+
importer.headers.merge!(:'Post Id' => :post_id)
636+
end,
637+
controller_block: proc do
638+
def active_admin_import_context
639+
{ post_id: parent.id }
640+
end
641+
end
642+
)
643+
visit "/admin/posts/#{post.id}/post_comments/import"
644+
upload_file!(:post_comments)
645+
end
646+
647+
it 'automatically assigns the parent post_id to every imported comment' do
648+
expect(page).to have_content 'Successfully imported 2 post comments'
649+
expect(PostComment.count).to eq(2)
650+
expect(PostComment.where(post_id: post.id).count).to eq(2)
651+
end
652+
end
653+
end
591654
end

spec/support/admin.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@ def add_author_resource(options = {}, &block)
88
end
99

1010
def add_post_resource(options = {}, &block)
11+
cb = options.delete(:controller_block)
1112
ActiveAdmin.register Post do
1213
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
1329
active_admin_import(options, &block)
1430
end
1531
Rails.application.reload_routes!

spec/support/rails_template.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
create_file "app/assets/config/manifest.js", skip: true
22

33
generate :model, 'author name:string{10}:uniq last_name:string birthday:date --force'
4-
generate :model, 'post title:string:uniq body:text author:references --force'
4+
generate :model, 'post title:string:uniq body:text request_ip:string author:references --force'
5+
generate :model, 'post_comment body:text post:references --force'
56

67
inject_into_file 'app/models/author.rb', " validates_presence_of :name\n validates_uniqueness_of :last_name\n", before: 'end'
7-
inject_into_file 'app/models/post.rb', " validates_presence_of :author\n", before: 'end'
8+
inject_into_file 'app/models/post.rb', " validates_presence_of :author\n has_many :post_comments\n", before: 'end'
89

910
# Add our local Active Admin to the load path (Rails 7.1+)
1011
gsub_file "config/environment.rb",

0 commit comments

Comments
 (0)