freeform 1.0.4 → 1.0.5
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.
- checksums.yaml +8 -8
- data/Gemfile.lock +1 -1
- data/lib/freeform/builder/builder_mixin.rb +8 -3
- data/lib/freeform/form/nested.rb +8 -8
- data/lib/freeform/form/property.rb +15 -9
- data/lib/freeform/form.rb +62 -12
- data/lib/freeform/version.rb +1 -1
- data/spec/acceptance_spec.rb +105 -4
- data/spec/behavior_spec.rb +48 -0
- data/spec/dummy/app/controllers/projects_controller.rb +8 -2
- data/spec/dummy/app/forms/task_form.rb +3 -2
- data/spec/dummy/app/views/projects/new.html.erb +2 -1
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +27711 -0
- data/spec/javascript_spec.rb +1 -2
- data/vendor/assets/javascripts/jquery_freeform.js +10 -5
- metadata +4 -9
- data/spec/dummy/app/assets/javascripts/prototype.js +0 -6082
- data/spec/dummy/app/assets/javascripts/prototype_events_test.js +0 -20
- data/spec/dummy/app/views/projects/without_intermediate_inputs.html.erb +0 -11
- data/vendor/assets/javascripts/prototype_freeform.js +0 -69
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NmE5OGYzZDc5Mzg0NjRiZmY2ZDQ5MzIyMzFkNmIxNmU4MmY0M2NiMw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZjBjODM1YzBkNTI4NjY1MGU5YWVlOTRhY2ZlYWM3YWVhNjhlZTIxNA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDFmMDcwMjc3YjJjOGI0OWY1M2Q1ZjAyOTE2NWM1MGVhNzczZjhmZmM0ZGZj
|
10
|
+
NTEyNzBhZGRiYjEwYWQwNmEyNGQ2MTUxZDE0YWEyOTUzYzE5YTBhM2E4Mjkz
|
11
|
+
ODMyZDQxYzljODQ4YzAzN2Y2OWFjYTQzY2IzNDY0MTUwYWZiMTU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OGYwZjZjNTZkZTAxNDg2YTg2OTRjNjUzNjk0NDE5Y2JmMWQ4Mzk5ZDI0MDFm
|
14
|
+
NGQwMWM3Y2I5MGQ4OThiODUyYzE4OGIyNDllMGQ4NDdjMTI0ODM1ODQ1OTU5
|
15
|
+
ZDFhNGY4YzM4MjgxMjViN2EwZTQxMzk3NzRlYTk3NzBjYzU0ZWM=
|
data/Gemfile.lock
CHANGED
@@ -24,7 +24,6 @@ module FreeForm
|
|
24
24
|
raise ArgumentError, "Invalid association. Make sure that a nested form for #{association.to_s} exists."
|
25
25
|
end
|
26
26
|
|
27
|
-
#FIXME: I need to use the build method for child models
|
28
27
|
model_object = options.delete(:model_object) do
|
29
28
|
object.send("build_#{association.to_s.singularize}")
|
30
29
|
end
|
@@ -68,10 +67,16 @@ module FreeForm
|
|
68
67
|
|
69
68
|
args << (options.delete(:href) || "javascript:void(0)")
|
70
69
|
args << options
|
71
|
-
|
70
|
+
|
71
|
+
destroy = if self.object.send(:marked_for_destruction?)
|
72
|
+
"1"
|
73
|
+
else
|
74
|
+
"0"
|
75
|
+
end
|
76
|
+
hidden_field(:_destroy, :value => destroy) << @template.link_to(*args, &block)
|
72
77
|
end
|
78
|
+
|
73
79
|
def fields_for_with_nested_attributes(association_name, *args)
|
74
|
-
# TODO Test this better
|
75
80
|
block = args.pop || Proc.new { |fields| @template.render(:partial => "#{association_name.to_s.singularize}_fields", :locals => {:f => fields}) }
|
76
81
|
|
77
82
|
options = args.dup.extract_options!
|
data/lib/freeform/form/nested.rb
CHANGED
@@ -3,7 +3,7 @@ module FreeForm
|
|
3
3
|
def self.included(base)
|
4
4
|
base.extend(ClassMethods)
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
module ClassMethods
|
8
8
|
# Nested Forms
|
9
9
|
#------------------------------------------------------------------------
|
@@ -32,21 +32,21 @@ module FreeForm
|
|
32
32
|
def define_nested_model_methods(attribute, form_class, options={})
|
33
33
|
singularized_attribute = attribute.to_s.singularize.to_s
|
34
34
|
|
35
|
-
# Example: form.addresses will return all nested address forms
|
35
|
+
# Example: form.addresses will return all nested address forms
|
36
36
|
define_method(:"#{attribute}") do
|
37
37
|
value = instance_variable_get("@nested_#{attribute}")
|
38
38
|
value ||= []
|
39
39
|
instance_variable_set("@nested_#{attribute}", value)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
# Example: form.build_addresses (optional custom initializer)
|
43
43
|
define_method(:"build_#{attribute}") do |initializer=nil|
|
44
44
|
# Builder object
|
45
45
|
parent_object = self
|
46
|
-
|
46
|
+
|
47
47
|
# Get correct class
|
48
48
|
form_class = self.class.nested_forms[:"#{attribute}"]
|
49
|
-
|
49
|
+
|
50
50
|
# Default Initializer
|
51
51
|
if options[:default_initializer]
|
52
52
|
initializer ||= instance_eval(&options[:default_initializer])
|
@@ -84,11 +84,11 @@ module FreeForm
|
|
84
84
|
|
85
85
|
# Instance Methods
|
86
86
|
#--------------------------------------------------------------------------
|
87
|
-
|
87
|
+
protected
|
88
88
|
def build_models_from_param_count(attribute, params)
|
89
89
|
# Get the difference between sets of params passed and current form objects
|
90
90
|
num_param_models = params.length
|
91
|
-
num_built_models = self.send(:"#{attribute}").nil? ? 0 : self.send(:"#{attribute}").length
|
91
|
+
num_built_models = self.send(:"#{attribute}").nil? ? 0 : self.send(:"#{attribute}").length
|
92
92
|
additional_models_needed = num_param_models - num_built_models
|
93
93
|
|
94
94
|
# Make up the difference by building new nested form models
|
@@ -97,4 +97,4 @@ module FreeForm
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
end
|
100
|
-
end
|
100
|
+
end
|
@@ -19,11 +19,11 @@ module FreeForm
|
|
19
19
|
|
20
20
|
def declared_model(name, opts={})
|
21
21
|
@models ||= []
|
22
|
-
@models << name
|
22
|
+
@models << name
|
23
23
|
attr_accessor name
|
24
24
|
end
|
25
25
|
alias_method :form_model, :declared_model
|
26
|
-
|
26
|
+
|
27
27
|
def declared_models(*names)
|
28
28
|
names.each do |name|
|
29
29
|
declared_model(name)
|
@@ -33,9 +33,9 @@ module FreeForm
|
|
33
33
|
|
34
34
|
def child_model(name, opts={}, &block)
|
35
35
|
@models ||= []
|
36
|
-
@models << name
|
36
|
+
@models << name
|
37
37
|
@child_models ||= []
|
38
|
-
@child_models << name
|
38
|
+
@child_models << name
|
39
39
|
attr_accessor name
|
40
40
|
define_method("initialize_#{name}") do
|
41
41
|
instance_variable_set("@#{name}", instance_eval(&block))
|
@@ -53,8 +53,14 @@ module FreeForm
|
|
53
53
|
# Define _destroy method for marked-for-destruction handling
|
54
54
|
attr_accessor :_destroy
|
55
55
|
define_method(:_destroy=) do |value|
|
56
|
-
false_values
|
57
|
-
|
56
|
+
false_values = [nil, 0 , false, "0", "false"]
|
57
|
+
true_values = [1 , true, "1", "true"]
|
58
|
+
|
59
|
+
#TODO: Test this!!
|
60
|
+
@_destroy = !false_values.include?(value) # Mark initially
|
61
|
+
# if (@_destroy == false) # Can only switch if false
|
62
|
+
# @_destroy = true if true_values.include?(value)
|
63
|
+
# end
|
58
64
|
end
|
59
65
|
alias_method :marked_for_destruction?, :_destroy
|
60
66
|
define_method(:mark_for_destruction) do
|
@@ -108,7 +114,7 @@ module FreeForm
|
|
108
114
|
def params_to_date(year, month, day)
|
109
115
|
day ||= 1 # FIXME: is that really what we want? test.
|
110
116
|
begin # TODO: test fails.
|
111
|
-
return Date.new(year.to_i, month.to_i, day.to_i)
|
117
|
+
return Date.new(year.to_i, month.to_i, day.to_i)
|
112
118
|
rescue ArgumentError => e
|
113
119
|
return nil
|
114
120
|
end
|
@@ -128,7 +134,7 @@ module FreeForm
|
|
128
134
|
|
129
135
|
private
|
130
136
|
def assign_attribute(attribute, value)
|
131
|
-
self.send :"#{attribute}=", value unless ignore?(attribute, value)
|
137
|
+
self.send :"#{attribute}=", value unless ignore?(attribute, value)
|
132
138
|
end
|
133
139
|
|
134
140
|
def ignore?(attribute, value)
|
@@ -136,4 +142,4 @@ module FreeForm
|
|
136
142
|
return (mapping[:ignore_blank] && value.blank?) unless mapping.nil?
|
137
143
|
end
|
138
144
|
end
|
139
|
-
end
|
145
|
+
end
|
data/lib/freeform/form.rb
CHANGED
@@ -26,44 +26,94 @@ module FreeForm
|
|
26
26
|
|
27
27
|
def initialize(h={})
|
28
28
|
h.each {|k,v| send("#{k}=",v)}
|
29
|
-
initialize_child_models
|
30
|
-
end
|
31
|
-
|
29
|
+
initialize_child_models
|
30
|
+
end
|
31
|
+
|
32
|
+
# def save
|
33
|
+
# return false unless valid?
|
34
|
+
|
35
|
+
# self.class.models.each do |form_model|
|
36
|
+
# model = send(form_model)
|
37
|
+
# model.is_a?(Array) ? model.each { |m| m.save } : save_or_destroy(model)
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
|
41
|
+
# def save!
|
42
|
+
# raise FreeForm::FormInvalid, "form invalid." unless valid?
|
43
|
+
|
44
|
+
# self.class.models.each do |form_model|
|
45
|
+
# model = send(form_model)
|
46
|
+
# model.is_a?(Array) ? model.each { |m| m.save! } : save_or_destroy!(model)
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
|
50
|
+
|
32
51
|
def save
|
33
|
-
return false unless valid?
|
52
|
+
return false unless valid? || marked_for_destruction?
|
34
53
|
|
54
|
+
# Loop through nested models first, sending them the save call
|
35
55
|
self.class.models.each do |form_model|
|
36
56
|
model = send(form_model)
|
37
|
-
|
57
|
+
# This is for nested models.
|
58
|
+
if model.is_a?(Array)
|
59
|
+
model.each { |m| m.save }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# puts "Saving... #{self}"
|
64
|
+
|
65
|
+
# After nested models are handled, save or destroy myself.
|
66
|
+
self.class.models.each do |form_model|
|
67
|
+
model = send(form_model)
|
68
|
+
# This is for form models.
|
69
|
+
unless model.is_a?(Array)
|
70
|
+
# puts "Saving model...#{model}"
|
71
|
+
if model.is_a?(Project)
|
72
|
+
# puts "All Tasks = #{Task.all}"
|
73
|
+
# puts "Tasks = #{model.tasks.inspect}"
|
74
|
+
end
|
75
|
+
if marked_for_destruction?
|
76
|
+
# puts "destroying model...#{model}"
|
77
|
+
model.destroy
|
78
|
+
else
|
79
|
+
model.save
|
80
|
+
end
|
81
|
+
end
|
38
82
|
end
|
39
83
|
end
|
40
|
-
|
84
|
+
|
41
85
|
def save!
|
42
86
|
raise FreeForm::FormInvalid, "form invalid." unless valid?
|
43
87
|
|
88
|
+
# Destroy all marked-for-destruction models
|
89
|
+
|
44
90
|
self.class.models.each do |form_model|
|
45
91
|
model = send(form_model)
|
46
92
|
model.is_a?(Array) ? model.each { |m| m.save! } : save_or_destroy!(model)
|
47
93
|
end
|
48
|
-
end
|
49
|
-
|
94
|
+
end
|
95
|
+
|
50
96
|
private
|
51
97
|
def initialize_child_models
|
52
98
|
self.class.child_models.each do |c|
|
53
99
|
send("initialize_#{c}")
|
54
|
-
end
|
100
|
+
end
|
55
101
|
end
|
56
102
|
|
57
103
|
def save_or_destroy(model)
|
58
104
|
marked_for_destruction? ? model.destroy : model.save
|
59
105
|
end
|
60
|
-
|
106
|
+
|
61
107
|
def save_or_destroy!(model)
|
62
|
-
marked_for_destruction?
|
108
|
+
if marked_for_destruction?
|
109
|
+
model.destroy
|
110
|
+
else
|
111
|
+
model.save!
|
112
|
+
end
|
63
113
|
end
|
64
114
|
|
65
115
|
def marked_for_destruction?
|
66
116
|
respond_to?(:_destroy) ? _destroy : false
|
67
117
|
end
|
68
118
|
end
|
69
|
-
end
|
119
|
+
end
|
data/lib/freeform/version.rb
CHANGED
data/spec/acceptance_spec.rb
CHANGED
@@ -36,7 +36,7 @@ describe FreeForm::Form do
|
|
36
36
|
has_many :tasks, :class => Module::TaskForm, :default_initializer => :default_task_initializer
|
37
37
|
|
38
38
|
def default_task_initializer
|
39
|
-
{ :task =>
|
39
|
+
{ :task => project.tasks.build }
|
40
40
|
end
|
41
41
|
end
|
42
42
|
# This wrapper just avoids CONST warnings
|
@@ -67,7 +67,7 @@ describe FreeForm::Form do
|
|
67
67
|
|
68
68
|
it "initializes with Task with project parent" do
|
69
69
|
task = form.tasks.first.task
|
70
|
-
task.
|
70
|
+
task.should eq(form.project.tasks.first)
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -229,7 +229,7 @@ describe FreeForm::Form do
|
|
229
229
|
end
|
230
230
|
end
|
231
231
|
|
232
|
-
context "with invalid form, and invalid marked for destruction nested model"
|
232
|
+
context "with invalid form, and invalid marked for destruction nested model" do
|
233
233
|
let(:attributes) do {
|
234
234
|
:company_name => "",
|
235
235
|
:project_name => "rails app",
|
@@ -374,10 +374,111 @@ describe FreeForm::Form do
|
|
374
374
|
end
|
375
375
|
|
376
376
|
it "should raise error on 'save!'" do
|
377
|
-
expect{ form.save
|
377
|
+
expect{ form.save! }.to raise_error(FreeForm::FormInvalid)
|
378
378
|
end
|
379
379
|
end
|
380
380
|
|
381
|
+
context "with invalid attributes, and marked for destruction nested model", :failing => true do
|
382
|
+
let(:attributes) do {
|
383
|
+
:company_name => "dummycorp",
|
384
|
+
:project_name => "",
|
385
|
+
"due_date(1i)" => "2014",
|
386
|
+
"due_date(2i)" => "10",
|
387
|
+
"due_date(3i)" => "30",
|
388
|
+
:tasks_attributes => {
|
389
|
+
"0" => {
|
390
|
+
:name => "task_1",
|
391
|
+
"start_date(1i)" => "2012",
|
392
|
+
"start_date(2i)" => "1",
|
393
|
+
"start_date(3i)" => "2",
|
394
|
+
},
|
395
|
+
"3234322345" => {
|
396
|
+
:name => "task_2",
|
397
|
+
"end_date(1i)" => "2011",
|
398
|
+
"end_date(2i)" => "12",
|
399
|
+
"end_date(3i)" => "15",
|
400
|
+
:_destroy => "1"
|
401
|
+
}
|
402
|
+
} }
|
403
|
+
end
|
404
|
+
|
405
|
+
before(:each) do
|
406
|
+
form.fill(attributes)
|
407
|
+
end
|
408
|
+
|
409
|
+
it "should not be valid" do
|
410
|
+
form.should_not be_valid
|
411
|
+
end
|
412
|
+
|
413
|
+
describe "persisting destroy after failed save" do
|
414
|
+
before(:each) { form.save }
|
415
|
+
|
416
|
+
it "should have the second form still set to _destroy" do
|
417
|
+
form.tasks.second.should be_marked_for_destruction
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
# it "should have valid company and project after save", :failing_1 => true do
|
422
|
+
# form.save
|
423
|
+
# # form.tasks.first.task.destroy
|
424
|
+
# form.company.should be_valid
|
425
|
+
# form.project.valid?
|
426
|
+
# puts "Errors = #{form.project.errors.inspect}"
|
427
|
+
# form.project.should be_valid
|
428
|
+
# end
|
429
|
+
|
430
|
+
# it "should not raise error on 'save!'" do
|
431
|
+
# expect{ form.save! }.to_not raise_error
|
432
|
+
# end
|
433
|
+
end
|
434
|
+
|
435
|
+
context "with invalid, marked for destruction nested model" do
|
436
|
+
let(:attributes) do {
|
437
|
+
:company_name => "dummycorp",
|
438
|
+
:project_name => "railsapp",
|
439
|
+
"due_date(1i)" => "2014",
|
440
|
+
"due_date(2i)" => "10",
|
441
|
+
"due_date(3i)" => "30",
|
442
|
+
:tasks_attributes => {
|
443
|
+
"0" => {
|
444
|
+
:name => "task_1",
|
445
|
+
"start_date(1i)" => "2012",
|
446
|
+
"start_date(2i)" => "1",
|
447
|
+
"start_date(3i)" => "2",
|
448
|
+
},
|
449
|
+
"1" => {
|
450
|
+
:name => "task_2",
|
451
|
+
"end_date(1i)" => "2011",
|
452
|
+
"end_date(2i)" => "12",
|
453
|
+
"end_date(3i)" => "15",
|
454
|
+
:_destroy => "1"
|
455
|
+
}
|
456
|
+
} }
|
457
|
+
end
|
458
|
+
|
459
|
+
before(:each) do
|
460
|
+
form.fill(attributes)
|
461
|
+
end
|
462
|
+
|
463
|
+
it { is pending }
|
464
|
+
# it "should return true on 'save'" do
|
465
|
+
# form.save.should be_true
|
466
|
+
# end
|
467
|
+
|
468
|
+
# it "should have valid company and project after save", :failing_1 => true do
|
469
|
+
# form.save
|
470
|
+
# # form.tasks.first.task.destroy
|
471
|
+
# form.company.should be_valid
|
472
|
+
# form.project.valid?
|
473
|
+
# puts "Errors = #{form.project.errors.inspect}"
|
474
|
+
# form.project.should be_valid
|
475
|
+
# end
|
476
|
+
|
477
|
+
# it "should not raise error on 'save!'" do
|
478
|
+
# expect{ form.save! }.to_not raise_error
|
479
|
+
# end
|
480
|
+
end
|
481
|
+
|
381
482
|
context "with valid attributes" do
|
382
483
|
let(:attributes) do {
|
383
484
|
:company_name => "dummycorp",
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Full Behavior', :js => true do
|
4
|
+
include Capybara::DSL
|
5
|
+
|
6
|
+
[:jquery].each do |js_framework|
|
7
|
+
|
8
|
+
url = case js_framework
|
9
|
+
when :jquery then '/projects/new'
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with #{js_framework}" do
|
13
|
+
context 'adding/removing fields' do
|
14
|
+
it 'adds fields and increments count' do
|
15
|
+
visit url
|
16
|
+
click_link 'Add new task'
|
17
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(1)
|
18
|
+
click_link 'Add new task'
|
19
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(2)
|
20
|
+
click_link 'Add new task'
|
21
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(3)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'removes fields' do
|
25
|
+
visit url
|
26
|
+
click_link 'Add new task'
|
27
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(1)
|
28
|
+
click_link 'Remove'
|
29
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(0)
|
30
|
+
click_link 'Add new task'
|
31
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(1)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'after application submission' do
|
36
|
+
it 'emits general remove event' do
|
37
|
+
visit url
|
38
|
+
click_link 'Add new task'
|
39
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(1)
|
40
|
+
click_link 'Remove'
|
41
|
+
all("input[id^='project_tasks_attributes_']", visible: true).count.should eq(0)
|
42
|
+
click_button 'Submit'
|
43
|
+
page.should have_selector(:xpath, "//input[@id='project_tasks_attributes_0__destroy' and @value='1']", visible: false)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -3,9 +3,15 @@ class ProjectsController < ApplicationController
|
|
3
3
|
@project = Project.new
|
4
4
|
@form = project_form(@project)
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
|
+
def create
|
8
|
+
@project = Project.new
|
9
|
+
@form = project_form(@project).fill(params[:project])
|
10
|
+
render :action => :new
|
11
|
+
end
|
12
|
+
|
7
13
|
private
|
8
14
|
def project_form(project)
|
9
15
|
ProjectForm.new(:project => project)
|
10
|
-
end
|
16
|
+
end
|
11
17
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
class TaskForm < FreeForm::Form
|
2
2
|
form_models :task
|
3
|
-
property :name, :on => :task
|
3
|
+
property :name, :on => :task
|
4
|
+
allow_destroy_on_save
|
4
5
|
|
5
6
|
has_many :milestones, :class => MilestoneForm, :default_initializer => :milestone_initializer
|
6
7
|
|
7
8
|
def milestone_initializer
|
8
9
|
{ :milestone => Milestone.new }
|
9
10
|
end
|
10
|
-
end
|
11
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= freeform_for @form do |f| -%>
|
1
|
+
<%= freeform_for @form, :url => projects_path, :method => :post do |f| -%>
|
2
2
|
<%= f.text_field :name %>
|
3
3
|
<%= f.fields_for :tasks do |tf| -%>
|
4
4
|
<%= tf.text_field :name %>
|
@@ -10,4 +10,5 @@
|
|
10
10
|
<%= tf.link_to_remove 'Remove' %>
|
11
11
|
<% end -%>
|
12
12
|
<%= f.link_to_add 'Add new task', :tasks %>
|
13
|
+
<%= f.submit "Submit" %>
|
13
14
|
<% end -%>
|
data/spec/dummy/db/test.sqlite3
CHANGED
Binary file
|