Skip to content
This repository has been archived by the owner on Dec 12, 2021. It is now read-only.

changed to use button_to_add instead of link_to_add to make it more sema... #289

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ gemspec :path => '.'

instance_eval File.read(File.expand_path('../gemfiles/Gemfile.base', __FILE__))

gem 'rails', '~> 3.2.0'
gem 'rails', '~> 4.0.0'
40 changes: 27 additions & 13 deletions lib/nested_form/builder_mixin.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
module NestedForm
module BuilderMixin
# Adds a link to insert a new associated records. The first argument is the name of the link, the second is the name of the association.
# Adds a button to insert a new associated records. The first argument is the name of the link, the second is the name of the association.
#
# f.link_to_add("Add Task", :tasks)
# f.button_to_add("Add Task", :tasks)
#
# You can pass HTML options in a hash at the end and a block for the content.
#
# <%= f.link_to_add(:tasks, :class => "add_task", :href => new_task_path) do %>
# <%= f.button_to_add(:tasks, :class => "add_task", :href => new_task_path) do %>
# Add Task
# <% end %>
#
# You can also pass <tt>model_object</tt> option with an object for use in
# the blueprint, e.g.:
#
# <%= f.link_to_add(:tasks, :model_object => Task.new(:name => 'Task')) %>
# <%= f.button_to_add(:tasks, :model_object => Task.new(:name => 'Task')) %>
#
# See the README for more details on where to call this method.
def link_to_add(*args, &block)
def button_to_add(*args, &block)
options = args.extract_options!.symbolize_keys
association = args.pop

Expand All @@ -31,8 +31,8 @@ def link_to_add(*args, &block)

options[:class] = [options[:class], "add_nested_fields"].compact.join(" ")
options["data-association"] = association
options["type"] = "button"
options["data-blueprint-id"] = fields_blueprint_id = fields_blueprint_id_for(association)
args << (options.delete(:href) || "javascript:void(0)")
args << options

@fields ||= {}
Expand All @@ -43,32 +43,46 @@ def link_to_add(*args, &block)
blueprint[:"data-blueprint"] = fields_for(association, model_object, options, &block).to_str
@template.content_tag(:div, nil, blueprint)
end
@template.link_to(*args, &block)
@template.button_tag(*args, &block)
end

# Adds a link to remove the associated record. The first argment is the name of the link.
# Adds a button to remove the associated record. The first argment is the name of the link.
#
# f.link_to_remove("Remove Task")
# f.button_to_remove("Remove Task")
#
# You can pass HTML options in a hash at the end and a block for the content.
#
# <%= f.link_to_remove(:class => "remove_task", :href => "#") do %>
# <%= f.button_to_remove(:class => "remove_task", :href => "#") do %>
# Remove Task
# <% end %>
#
# See the README for more details on where to call this method.
def link_to_remove(*args, &block)
def button_to_remove(*args, &block)
options = args.extract_options!.symbolize_keys
options[:class] = [options[:class], "remove_nested_fields"].compact.join(" ")

# Extracting "milestones" from "...[milestones_attributes][...]"
md = object_name.to_s.match /(\w+)_attributes\]\[[\w\d]+\]$/
association = md && md[1]
options["data-association"] = association
options["type"] = "button"

args << (options.delete(:href) || "javascript:void(0)")
args << options
hidden_field(:_destroy) << @template.link_to(*args, &block)
hidden_field(:_destroy) << @template.button_tag(*args, &block)
end

def button_to_diable(*args, &block)
options = args.extract_options!.symbolize_keys
options[:class] = [options[:class], "remove_nested_fields"].compact.join(" ")

# Extracting "milestones" from "...[milestones_attributes][...]"
md = object_name.to_s.match /(\w+)_attributes\]\[[\w\d]+\]$/
association = md && md[1]
options["data-association"] = association
options["type"] = "button"

args << options
hidden_field(:disabled) << @template.button_tag(*args, &block)
end

def fields_for_with_nested_attributes(association_name, *args)
Expand Down
8 changes: 4 additions & 4 deletions spec/dummy/app/views/companies/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
<%= tf.text_field :name %>
<%= tf.fields_for :milestones do |mf| %>
<%= mf.text_field :name %>
<%= mf.link_to_remove 'Remove milestone' %>
<%= mf.button_to_remove 'Remove milestone' %>
<% end %>
<%= tf.link_to_add 'Add new milestone', :milestones %>
<%= tf.link_to_remove 'Remove' %>
<%= tf.button_to_add 'Add new milestone', :milestones %>
<%= tf.button_to_remove 'Remove' %>
<% end -%>
<%= pf.link_to_add 'Add new task', :tasks %>
<%= pf.button_to_add 'Add new task', :tasks %>
<% end -%>
<% end -%>
8 changes: 4 additions & 4 deletions spec/dummy/app/views/projects/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<%= tf.text_field :name %>
<%= tf.fields_for :milestones do |mf| %>
<%= mf.text_field :name %>
<%= mf.link_to_remove 'Remove milestone' %>
<%= mf.button_to_remove 'Remove milestone' %>
<% end %>
<%= tf.link_to_add 'Add new milestone', :milestones %>
<%= tf.link_to_remove 'Remove' %>
<%= tf.button_to_add 'Add new milestone', :milestones %>
<%= tf.button_to_remove 'Remove' %>
<% end -%>
<%= f.link_to_add 'Add new task', :tasks %>
<%= f.button_to_add 'Add new task', :tasks %>
<% end -%>
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<%= f.fields_for :tasks do |tf| -%>
<%= tf.fields_for :milestones do |mf| %>
<%= mf.text_field :name %>
<%= mf.link_to_remove 'Remove milestone' %>
<%= mf.button_to_remove 'Remove milestone' %>
<% end %>
<%= tf.link_to_add 'Add new milestone', :milestones %>
<%= tf.link_to_remove 'Remove' %>
<%= tf.button_to_add 'Add new milestone', :milestones %>
<%= tf.button_to_remove 'Remove' %>
<% end -%>
<%= f.link_to_add 'Add new task', :tasks %>
<%= f.button_to_add 'Add new task', :tasks %>
<% end -%>
20 changes: 10 additions & 10 deletions spec/events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
context 'when field was added' do
it 'emits general add event' do
visit url
click_link 'Add new task'
click_button 'Add new task'

page.should have_content 'Added some field'
end

it 'emits add event for current association' do
visit url
click_link 'Add new task'
click_button 'Add new task'

page.should have_content 'Added task field'
page.should_not have_content 'Added milestone field'

click_link 'Add new milestone'
click_button 'Add new milestone'

page.should have_content 'Added milestone field'
end
Expand All @@ -35,26 +35,26 @@
context 'when field was removed' do
it 'emits general remove event' do
visit url
click_link 'Add new task'
click_link 'Remove'
click_button 'Add new task'
click_button 'Remove'

page.should have_content 'Removed some field'
end

it 'emits remove event for current association' do
visit url
2.times { click_link 'Add new task' }
click_link 'Remove'
2.times { click_button 'Add new task' }
click_button 'Remove'

page.should have_content 'Removed task field'
page.should_not have_content 'Removed milestone field'

click_link 'Add new milestone'
click_link 'Remove milestone'
click_button 'Add new milestone'
click_button 'Remove milestone'

page.should have_content 'Removed milestone field'
end
end
end
end
end
end
24 changes: 12 additions & 12 deletions spec/form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@

def check_form
page.should have_no_css('form .fields input[id$=name]')
click_link 'Add new task'
click_button 'Add new task'
page.should have_css('form .fields input[id$=name]', :count => 1)
find('form .fields input[id$=name]').should be_visible
find('form .fields input[id$=_destroy]').value.should == 'false'

click_link 'Remove'
click_button 'Remove'
find('form .fields input[id$=_destroy]').value.should == '1'
find('form .fields input[id$=name]').should_not be_visible

click_link 'Add new task'
click_link 'Add new task'
click_button 'Add new task'
click_button 'Add new task'
fields = all('form .fields')
fields.select { |field| field.visible? }.count.should == 2
fields.reject { |field| field.visible? }.count.should == 1
Expand All @@ -33,32 +33,32 @@ def check_form

it 'works when there are no inputs for intermediate association', :js => true do
visit '/projects/without_intermediate_inputs'
click_link 'Add new task'
click_link 'Add new milestone'
click_link 'Add new milestone'
click_button 'Add new task'
click_button 'Add new milestone'
click_button 'Add new milestone'
inputs = all('.fields .fields input[id$=name]')
inputs.first[:name].should_not eq(inputs.last[:name])
end

it 'generates correct name for the nested input', :js => true do
visit '/projects/new?type=jquery'
click_link 'Add new task'
click_link 'Add new milestone'
click_button 'Add new task'
click_button 'Add new milestone'
name = find('.fields .fields input[id$=name]')[:name]
name.should match(/\Aproject\[tasks_attributes\]\[\d+\]\[milestones_attributes\]\[\d+\]\[name\]\z/)
end

it 'generates correct name for the nested input (has_one => has_many)', :js => true do
visit '/companies/new?type=jquery'
click_link 'Add new task'
click_button 'Add new task'
name = find('.fields .fields input[id$=name]')[:name]
name.should match(/\Acompany\[project_attributes\]\[tasks_attributes\]\[\d+\]\[name\]\z/)
end

it 'generates correct name for the nested input (has_one => has_many => has_many)', :js => true do
visit '/companies/new?type=jquery'
click_link 'Add new task'
click_link 'Add new milestone'
click_button 'Add new task'
click_button 'Add new milestone'
name = find('.fields .fields .fields input[id$=name]')[:name]
name.should match(/\Acompany\[project_attributes\]\[tasks_attributes\]\[\d+\]\[milestones_attributes\]\[\d+\]\[name\]\z/)
end
Expand Down
48 changes: 24 additions & 24 deletions spec/nested_form/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,46 @@
builder.new(:item, project, template, {}, proc {})
end

describe '#link_to_add' do
it "behaves similar to a Rails link_to" do
subject.link_to_add("Add", :tasks).should == '<a href="javascript:void(0)" class="add_nested_fields" data-association="tasks" data-blueprint-id="tasks_fields_blueprint">Add</a>'
subject.link_to_add("Add", :tasks, :class => "foo", :href => "url").should == '<a href="url" class="foo add_nested_fields" data-association="tasks" data-blueprint-id="tasks_fields_blueprint">Add</a>'
subject.link_to_add(:tasks) { "Add" }.should == '<a href="javascript:void(0)" class="add_nested_fields" data-association="tasks" data-blueprint-id="tasks_fields_blueprint">Add</a>'
describe '#button_to_add' do
it "behaves similar to a Rails button_to" do
subject.button_to_add("Add", :tasks).should == '<button class="add_nested_fields" data-association="tasks" data-blueprint-id="tasks_fields_blueprint" name="button" type="button">Add</button>'
subject.button_to_add("Add", :tasks, :class => "foo").should == '<button class="foo add_nested_fields" data-association="tasks" data-blueprint-id="tasks_fields_blueprint" name="button" type="button">Add</button>'
subject.button_to_add(:tasks) { "Add" }.should == '<button class="add_nested_fields" data-association="tasks" data-blueprint-id="tasks_fields_blueprint" type="button">Add</button>'
end

it 'raises ArgumentError when missing association is provided' do
expect {
subject.link_to_add('Add', :bugs)
subject.button_to_add('Add', :bugs)
}.to raise_error(ArgumentError)
end

it 'raises ArgumentError when accepts_nested_attributes_for is missing' do
expect {
subject.link_to_add('Add', :not_nested_tasks)
subject.button_to_add('Add', :not_nested_tasks)
}.to raise_error(ArgumentError)
end
end

describe '#link_to_remove' do
it "behaves similar to a Rails link_to" do
subject.link_to_remove("Remove").should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><a href="javascript:void(0)" class="remove_nested_fields">Remove</a>'
subject.link_to_remove("Remove", :class => "foo", :href => "url").should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><a href="url" class="foo remove_nested_fields">Remove</a>'
subject.link_to_remove { "Remove" }.should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><a href="javascript:void(0)" class="remove_nested_fields">Remove</a>'
describe '#button_to_remove' do
it "behaves similar to a Rails button_to" do
subject.button_to_remove("Remove").should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><button class="remove_nested_fields" name="button" type="button">Remove</button>'
subject.button_to_remove("Remove", :class => "foo").should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><button class="foo remove_nested_fields" name="button" type="button">Remove</button>'
subject.button_to_remove { "Remove" }.should == '<input id="item__destroy" name="item[_destroy]" type="hidden" value="false" /><button class="remove_nested_fields" type="button">Remove</button>'
end

it 'has data-association attribute' do
project.tasks.build
subject.fields_for(:tasks, :builder => builder) do |tf|
tf.link_to_remove 'Remove'
end.should match '<a.+data-association="tasks">Remove</a>'
tf.button_to_remove 'Remove'
end.should match '<button.+data-association="tasks".*>Remove</button>'
end

context 'when association is declared in a model by the class_name' do
it 'properly detects association name' do
project.assignments.build
subject.fields_for(:assignments, :builder => builder) do |tf|
tf.link_to_remove 'Remove'
end.should match '<a.+data-association="assignments">Remove</a>'
tf.button_to_remove 'Remove'
end.should match '<button.+data-association="assignments".*>Remove</button>'
end
end

Expand All @@ -66,9 +66,9 @@
task.milestones.build
subject.fields_for(:tasks, :builder => builder) do |tf|
tf.fields_for(:milestones, :builder => builder) do |mf|
mf.link_to_remove 'Remove'
mf.button_to_remove 'Remove'
end
end.should match '<a.+data-association="milestones">Remove</a>'
end.should match '<button.+data-association="milestones".*>Remove</button>'
end
end
end
Expand Down Expand Up @@ -98,7 +98,7 @@
task = project.tasks.build
task.mark_for_destruction
subject.fields_for(:tasks) { 'Task' }
subject.link_to_add('Add', :tasks)
subject.button_to_add('Add', :tasks)
output = template.send(:after_nested_form_callbacks)
expected = ERB::Util.html_escape '<div class="fields">Task</div>'
output.should match(/div.+data-blueprint="#{expected}"/)
Expand All @@ -109,7 +109,7 @@
task.milestones.build
subject.fields_for(:tasks, :builder => builder) do |tf|
tf.fields_for(:milestones, :builder => builder) { 'Milestone' }
tf.link_to_add('Add', :milestones)
tf.button_to_add('Add', :milestones)
end
output = template.send(:after_nested_form_callbacks)
output.should match(/div.+id="tasks_milestones_fields_blueprint"/)
Expand All @@ -121,7 +121,7 @@

fields.should eq('Task')

subject.link_to_add 'Add', :tasks
subject.button_to_add 'Add', :tasks
output = template.send(:after_nested_form_callbacks)

output.should match(/div.+data-blueprint="Task"/)
Expand All @@ -133,7 +133,7 @@

fields.should eq('Task')

subject.link_to_add 'Add', :tasks
subject.button_to_add 'Add', :tasks
output = template.send(:after_nested_form_callbacks)

output.should match(/div.+data-blueprint="Task"/)
Expand All @@ -145,7 +145,7 @@

fields.should eq('Task')

subject.link_to_add 'Add', :tasks
subject.button_to_add 'Add', :tasks
output = template.send(:after_nested_form_callbacks)

output.should match(/div.+data-blueprint="Task"/)
Expand All @@ -158,7 +158,7 @@
context "when model_object given" do
it "should use it instead of new generated" do
subject.fields_for(:tasks) {|f| f.object.name }
subject.link_to_add("Add", :tasks, :model_object => Task.new(:name => 'for check'))
subject.button_to_add("Add", :tasks, :model_object => Task.new(:name => 'for check'))
output = template.send(:after_nested_form_callbacks)
expected = ERB::Util.html_escape '<div class="fields">for check</div>'
output.should match(/div.+data-blueprint="#{expected}"/)
Expand Down
Loading