8000 Merge pull request #4591 from chumakoff/smart_allow_destroy_for_neste… · rrrodrigo/activeadmin@cbac7fe · GitHub
[go: up one dir, main page]

Skip to content

Commit cbac7fe

Browse files
Merge pull request activeadmin#4591 from chumakoff/smart_allow_destroy_for_nested_fields
Add the ability to specify Symbol and Proc as `allow_destroy` parameter of `f.has_many`
2 parents d01155f + 01bcf34 commit cbac7fe

File tree

2 files changed

+140
-27
lines changed

2 files changed

+140
-27
lines changed

lib/active_admin/form_builder.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ def input_wrapping(&block)
1313

1414
module ActiveAdmin
1515
class FormBuilder < ::Formtastic::FormBuilder
16+
include MethodOrProcHelper
17+
1618
self.input_namespaces = [::Object, ::ActiveAdmin::Inputs, ::Formtastic::Inputs]
1719

1820
# TODO: remove both class finders after formtastic 4 (where it will be default)
@@ -59,7 +61,7 @@ def has_many(assoc, options = {}, &block)
5961
block.call has_many_form, index
6062
template.concat has_many_actions(has_many_form, builder_options, "".html_safe)
6163
end
62-
64+
6365
template.assign(has_many_block: true)
6466
contents = without_wrapper { inputs(options, &form_block) } || "".html_safe
6567

@@ -83,7 +85,10 @@ def has_many_actions(has_many_form, builder_options, contents)
8385
contents << template.content_tag(:li) do
8486
template.link_to I18n.t('active_admin.has_many_remove'), "#", class: 'button has_many_remove'
8587
end
86-
elsif builder_options[:allow_destroy]
88+
elsif call_method_or_proc_on(has_many_form.object,
89+
builder_options[:allow_destroy],
90+
exec: false)
91+
8792
has_many_form.input(:_destroy, as: :boolean,
8893
wrapper_html: {class: 'has_many_delete'},
8994
label: I18n.t('active_admin.has_many_delete'))
@@ -136,6 +141,5 @@ def js_for_has_many(assoc, form_block, template, new_record, class_string)
136141
html: CGI.escapeHTML(html).html_safe, placeholder: placeholder
137142
}
138143
end
139-
140144
end
141145
end

spec/unit/form_builder_spec.rb

Lines changed: 133 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
require "rspec/mocks/standalone"
33

44
describe ActiveAdmin::FormBuilder do
5-
65
# Setup an ActionView::Base object which can be used for
76
# generating the form for.
87
let(:helpers) do
@@ -510,7 +509,6 @@ def user
510509
it "should add a custom header" do
511510
expect(body).to have_selector("h3", text: "Post")
512511
end
513-
514512
end
515513

516514
describe "without heading and new record link" do
@@ -549,7 +547,6 @@ def user
549547
it "should add a custom header" do
550548
expect(body).to have_selector("h3", "Test heading")
551549
end
552-
553550
end
554551

555552
describe "with custom new record link" do
@@ -565,52 +562,164 @@ def user
565562
it "should add a custom new record link" do
566563
expect(body).to have_selector("a", text: "My Custom New Post")
567564
end
568-
569565
end
570566

571567
describe "with allow destroy" do
572-
context "with an existing post" do
573-
let :body do
574-
s = self
575-
build_form({url: '/categories'}, Category.new) do |f|
576-
s.instance_exec do
577-
allow(f.object.posts.build).to receive(:new_record?).and_return(false)
578-
end
579-
f.has_many :posts, allow_destroy: true do |p|
580-
p.input :title
581-
end
582-
end
568+
shared_examples_for "has many with allow_destroy = true" do |child_num|
569+
it "should render the nested form" do
570+
expect(body).to have_selector("input[name='category[posts_attributes][#{child_num}][title]']")
583571
end
584572

585573
it "should include a boolean field for _destroy" do
586-
expect(body).to have_selector("input[name='category[posts_attributes][0][_destroy]']")
574+
expect(body).to have_selector("input[name='category[posts_attributes][#{child_num}][_destroy]']")
587575
end
588576

589577
it "should have a check box with 'Remove' as its label" do
590-
expect(body).to have_selector("label[for=category_posts_attributes_0__destroy]", text: "Delete")
578+
expect(body).to have_selector("label[for=category_posts_attributes_#{child_num}__destroy]", text: "Delete")
591579
end
592580

593581
it "should wrap the destroy field in an li with class 'has_many_delete'" do
594582
expect(body).to have_selector(".has_many_container > fieldset > ol > li.has_many_delete > input", count: 1)
595583
end
596584
end
597585

598-
context "with a new post" do
586+
shared_examples_for "has many with allow_destroy = false" do |child_num|
587+
it "should render the nested form" do
588+
expect(body).to have_selector("input[name='category[posts_attributes][#{child_num}][title]']")
589+
end
590+
591+
it "should not have a boolean field for _destroy" do
592+
expect(body).not_to have_selector("input[name='category[posts_attributes][#{child_num}][_destroy]']")
593+
end
594+
595+
it "should not have a check box with 'Remove' as its label" do
596+
expect(body).not_to have_selector("label[for=category_posts_attributes_#{child_num}__destroy]", text: "Remove")
597+
end
598+
end
599+
600+
shared_examples_for "has many with allow_destroy as String, Symbol or Proc" do |allow_destroy_option|
599601
let :body do
602+
s = self
600603
build_form({url: '/categories'}, Category.new) do |f|
601-
f.object.posts.build
602-
f.has_many :posts, allow_destroy: true do |p|
604+
s.instance_exec do
605+
allow(f.object.posts.build).to receive(:foo?).and_return(true)
606+
allow(f.object.posts.build).to receive(:foo?).and_return(false)
607+
608+
f.object.posts.each do |post|
609+
allow(post).to receive(:new_record?).and_return(false)
610+
end
611+
end
612+
f.has_many :posts, allow_destroy: allow_destroy_option do |p|
603613
p.input :title
604614
end
605615
end
606616
end
607617

608-
it "should not have a boolean field for _destroy" do
609-
expect(body).not_to have_selector("input[name='category[posts_attributes][0][_destroy]']")
618+
context 'for the child that responds with true' do
619+
it_behaves_like "has many with allow_destroy = true", 0
610620
end
611621

612-
it "should not have a check box with 'Remove' as its label" do
613-
expect(body).not_to have_selector("label[for=category_posts_attributes_0__destroy]", text: "Remove")
622+
context 'for the child that responds with false' do
623+
it_behaves_like "has many with allow_destroy = false", 1
624+
end
625+
end
626+
627+
context "with an existing post" do
628+
context "with allow_destroy = true" do
629+
let :body do
630+
s = self
631+
build_form({url: '/categories'}, Category.new) do |f|
632+
s.instance_exec do
633+
allow(f.object.posts.build).to receive(:new_record?).and_return(false)
634+
end
635+
f.has_many :posts, allow_destroy: true do |p|
636+
p.input :title
637+
end
638+
end
639+
end
640+
641+
it_behaves_like "has many with allow_destroy = true", 0
642+
end
643+
644+
context "with allow_destroy = false" do
645+
let :body do
646+
s = self
647+
build_form({url: '/categories'}, Category.new) do |f|
648+
s.instance_exec do
649+
allow(f.object.posts.build).to receive(:new_record?).and_return(false)
650+
end
651+
f.has_many :posts, allow_destroy: false do |p|
652+
p.input :title
653+
end
654+
end
655+
end
656+
657+
it_behaves_like "has many with allow_destroy = false", 0
658+
end
659+
660+
context "with allow_destroy = nil" do
661+
let :body do
662+
s = self
663+
build_form({url: '/categories'}, Category.new) do |f|
664+
s.instance_exec do
665+
allow(f.object.posts.build).to receive(:new_record?).and_return(false)
666+
end
667+
f.has_many :posts, allow_destroy: nil do |p|
668+
p.input :title
669+
end
670+
end
671+
end
672+
673+
it_behaves_like "has many with allow_destroy = false", 0
674+
end
675+
676+
context "with allow_destroy as Symbol" do
677+
it_behaves_like("has many with allow_destroy as String, Symbol or Proc", :foo?)
678+
end
679+
680+
context "with allow_destroy as String" do
681+
it_behaves_like("has many with allow_destroy as String, Symbol or Proc", "foo?")
682+
end
683+
684+
context "with allow_destroy as proc" do
685+
it_behaves_like("has many with allow_destroy as String, Symbol or Proc",
686+
Proc.new { |child| child.foo? })
687+
end
688+
689+
context "with allow_destroy as lambda" do
690+
it_behaves_like("has many with allow_destroy as String, Symbol or Proc",
691+
lambda { |child| child.foo? })
692+
end
693+
694+
context "with allow_destroy as any other expression that evaluates to true" do
695+
let :body do
696+
s = self
697+
build_form({url: '/categories'}, Category.new) do |f|
698+
s.instance_exec do
699+
allow(f.object.posts.build).to receive(:new_record?).and_return(false)
700+
end
701+
f.has_many :posts, allow_destroy: Object.new do |p|
702+
p.input :title
703+
end
704+
end
705+
end
706+
707+
it_behaves_like "has many with allow_destroy = true", 0
708+
end
709+
end
710+
711+
context "with a new post" do
712+
context "with allow_destroy = true" do
713+
let :body do
714+
build_form({url: '/categories'}, Category.new) do |f|
715+
f.object.posts.build
716+
f.has_many :posts, allow_destroy: true do |p|
717+
p.input :title
718+
end
719+
end
720+
end
721+
722+
it_behaves_like "has many with allow_destroy = false", 0
614723
end
615724
end
616725
end

0 commit comments

Comments
 (0)
0