Added option :force_non_association_create
.
This option will allow users to use `link_to_add_association` in the nested fields loop, since it will not add the new object to the association. Fixes #66. By default we will still create the object in the association, as I believe that is the cleanest.
This commit is contained in:
parent
1c82d74acf
commit
ed8154d2fa
@ -217,6 +217,7 @@ It takes four parameters:
|
|||||||
- `render_options` : options passed through to the form-builder function (e.g. `simple_fields_for`, `semantic_fields_for` or `fields_for`).
|
- `render_options` : options passed through to the form-builder function (e.g. `simple_fields_for`, `semantic_fields_for` or `fields_for`).
|
||||||
If it contains a `:locals` option containing a hash, that is handed to the partial.
|
If it contains a `:locals` option containing a hash, that is handed to the partial.
|
||||||
- `wrap_object` : a proc that will allow to wrap your object, especially useful if you are using decorators (e.g. draper). See example lower.
|
- `wrap_object` : a proc that will allow to wrap your object, especially useful if you are using decorators (e.g. draper). See example lower.
|
||||||
|
- `force_non_association_create`: if true, it will _not_ create the new object using the association (see lower)
|
||||||
|
|
||||||
Optionally you could also leave out the name and supply a block that is captured to give the name (if you want to do something more complicated).
|
Optionally you could also leave out the name and supply a block that is captured to give the name (if you want to do something more complicated).
|
||||||
|
|
||||||
@ -288,6 +289,22 @@ link_to_add_association('add something', @form_obj, :comments,
|
|||||||
:wrap_object => Proc.new { |comment| comment.name = current_user.name; comment })
|
:wrap_object => Proc.new { |comment| comment.name = current_user.name; comment })
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### :force_non_association_create
|
||||||
|
|
||||||
|
In normal cases we create a new nested object using the association relation itself. This is the cleanest way to create
|
||||||
|
a new nested object. But this has a side-effect: for each call of `link_to_add_association` a new element is added to the association.
|
||||||
|
|
||||||
|
In most cases this is not a problem, but if you want to render a `link_to_add_association` for each nested element this will result
|
||||||
|
in an infinite loop.
|
||||||
|
|
||||||
|
To resolve this, specify that `:force_non_association_create` should be `true`, as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
link_to_add_association('add something', @form_obj, :comments, :force_non_association_create => true)
|
||||||
|
```
|
||||||
|
|
||||||
|
By default `:force_non_association_create` is `false`.
|
||||||
|
|
||||||
> A cleaner option would be to call a function that performs this initialisation and returns `self` at the end.
|
> A cleaner option would be to call a function that performs this initialisation and returns `self` at the end.
|
||||||
|
|
||||||
### link_to_remove_association
|
### link_to_remove_association
|
||||||
|
@ -50,6 +50,8 @@ module Cocoon
|
|||||||
# - *:render_options* : options passed to `simple_fields_for, semantic_fields_for or fields_for`
|
# - *:render_options* : options passed to `simple_fields_for, semantic_fields_for or fields_for`
|
||||||
# - *:locals* : the locals hash in the :render_options is handed to the partial
|
# - *:locals* : the locals hash in the :render_options is handed to the partial
|
||||||
# - *:partial* : explicitly override the default partial name
|
# - *:partial* : explicitly override the default partial name
|
||||||
|
# - *:wrap_object : !!! document more here !!!
|
||||||
|
# - *!!!add some option to build in collection or not!!!*
|
||||||
# - *&block*: see <tt>link_to</tt>
|
# - *&block*: see <tt>link_to</tt>
|
||||||
|
|
||||||
def link_to_add_association(*args, &block)
|
def link_to_add_association(*args, &block)
|
||||||
@ -68,16 +70,14 @@ module Cocoon
|
|||||||
render_options ||= {}
|
render_options ||= {}
|
||||||
override_partial = html_options.delete(:partial)
|
override_partial = html_options.delete(:partial)
|
||||||
wrap_object = html_options.delete(:wrap_object)
|
wrap_object = html_options.delete(:wrap_object)
|
||||||
|
force_non_association_create = html_options.delete(:force_non_association_create) || false
|
||||||
|
|
||||||
html_options[:class] = [html_options[:class], "add_fields"].compact.join(' ')
|
html_options[:class] = [html_options[:class], "add_fields"].compact.join(' ')
|
||||||
html_options[:'data-association'] = association.to_s.singularize
|
html_options[:'data-association'] = association.to_s.singularize
|
||||||
html_options[:'data-associations'] = association.to_s.pluralize
|
html_options[:'data-associations'] = association.to_s.pluralize
|
||||||
|
|
||||||
if wrap_object.respond_to?(:call)
|
new_object = create_object(f, association, force_non_association_create)
|
||||||
new_object = wrap_object.call(create_object(f, association))
|
new_object = wrap_object.call(new_object) if wrap_object.respond_to?(:call)
|
||||||
else
|
|
||||||
new_object = create_object(f, association)
|
|
||||||
end
|
|
||||||
|
|
||||||
html_options[:'data-association-insertion-template'] = CGI.escapeHTML(render_association(association, f, new_object, render_options, override_partial)).html_safe
|
html_options[:'data-association-insertion-template'] = CGI.escapeHTML(render_association(association, f, new_object, render_options, override_partial)).html_safe
|
||||||
|
|
||||||
@ -89,10 +89,10 @@ module Cocoon
|
|||||||
# `` has_many :admin_comments, class_name: "Comment", conditions: { author: "Admin" }
|
# `` has_many :admin_comments, class_name: "Comment", conditions: { author: "Admin" }
|
||||||
# will create new Comment with author "Admin"
|
# will create new Comment with author "Admin"
|
||||||
|
|
||||||
def create_object(f, association)
|
def create_object(f, association, force_non_association_create=false)
|
||||||
assoc = f.object.class.reflect_on_association(association)
|
assoc = f.object.class.reflect_on_association(association)
|
||||||
|
|
||||||
assoc ? create_object_on_association(f, association, assoc) : create_object_on_non_association(f, association)
|
assoc ? create_object_on_association(f, association, assoc, force_non_association_create) : create_object_on_non_association(f, association)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_partial_path(partial, association)
|
def get_partial_path(partial, association)
|
||||||
@ -107,13 +107,12 @@ module Cocoon
|
|||||||
raise "Association #{association} doesn't exist on #{f.object.class}"
|
raise "Association #{association} doesn't exist on #{f.object.class}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_object_on_association(f, association, instance)
|
def create_object_on_association(f, association, instance, force_non_association_create)
|
||||||
if instance.class.name == "Mongoid::Relations::Metadata"
|
if instance.class.name == "Mongoid::Relations::Metadata" || force_non_association_create
|
||||||
conditions = instance.respond_to?(:conditions) ? instance.conditions.flatten : []
|
create_object_with_conditions(instance)
|
||||||
instance.klass.new(*conditions)
|
|
||||||
else
|
else
|
||||||
# assume ActiveRecord or compatible
|
# assume ActiveRecord or compatible
|
||||||
if instance.collection?
|
if instance.collection?
|
||||||
f.object.send(association).build
|
f.object.send(association).build
|
||||||
else
|
else
|
||||||
f.object.send("build_#{association}")
|
f.object.send("build_#{association}")
|
||||||
@ -121,5 +120,10 @@ module Cocoon
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_object_with_conditions(instance)
|
||||||
|
conditions = instance.respond_to?(:conditions) ? instance.conditions.flatten : []
|
||||||
|
instance.klass.new(*conditions)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,11 +10,15 @@ describe Cocoon do
|
|||||||
it { should respond_to(:link_to_add_association) }
|
it { should respond_to(:link_to_add_association) }
|
||||||
it { should respond_to(:link_to_remove_association) }
|
it { should respond_to(:link_to_remove_association) }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@tester = TestClass.new
|
||||||
|
@post = Post.new
|
||||||
|
@form_obj = stub(:object => @post, :object_name => @post.class.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
context "link_to_add_association" do
|
context "link_to_add_association" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@tester = TestClass.new
|
|
||||||
@post = Post.new
|
|
||||||
@form_obj = stub(:object => @post)
|
|
||||||
@tester.stub(:render_association).and_return('form<tag>')
|
@tester.stub(:render_association).and_return('form<tag>')
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -41,6 +45,25 @@ describe Cocoon do
|
|||||||
@tester.should_receive(:render_association).with(anything(), anything(), kind_of(CommentDecorator), anything(), anything()).and_return('partiallll')
|
@tester.should_receive(:render_association).with(anything(), anything(), kind_of(CommentDecorator), anything(), anything()).and_return('partiallll')
|
||||||
@tester.link_to_add_association('add something', @form_obj, :comments, :wrap_object => Proc.new {|comment| CommentDecorator.new(comment) })
|
@tester.link_to_add_association('add something', @form_obj, :comments, :wrap_object => Proc.new {|comment| CommentDecorator.new(comment) })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "force non association create" do
|
||||||
|
it "default it uses the association" do
|
||||||
|
@tester.should_receive(:create_object).with(anything, :comments , false)
|
||||||
|
result = @tester.link_to_add_association('add something', @form_obj, :comments)
|
||||||
|
result.to_s.should == '<a href="#" class="add_fields" data-association-insertion-template="form<tag>" data-association="comment" data-associations="comments">add something</a>'
|
||||||
|
end
|
||||||
|
it "specifying false is the same as default: create object on association" do
|
||||||
|
@tester.should_receive(:create_object).with(anything, :comments , false)
|
||||||
|
result = @tester.link_to_add_association('add something', @form_obj, :comments, :force_non_association_create => false)
|
||||||
|
result.to_s.should == '<a href="#" class="add_fields" data-association-insertion-template="form<tag>" data-association="comment" data-associations="comments">add something</a>'
|
||||||
|
end
|
||||||
|
it "specifying true will not create objects on association but using the conditions" do
|
||||||
|
@tester.should_receive(:create_object).with(anything, :comments , true)
|
||||||
|
result = @tester.link_to_add_association('add something', @form_obj, :comments, :force_non_association_create => true)
|
||||||
|
result.to_s.should == '<a href="#" class="add_fields" data-association-insertion-template="form<tag>" data-association="comment" data-associations="comments">add something</a>'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a block" do
|
context "with a block" do
|
||||||
@ -146,12 +169,6 @@ describe Cocoon do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "link_to_remove_association" do
|
context "link_to_remove_association" do
|
||||||
before(:each) do
|
|
||||||
@tester = TestClass.new
|
|
||||||
@post = Post.new
|
|
||||||
@form_obj = stub(:object => @post, :object_name => @post.class.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
context "without a block" do
|
context "without a block" do
|
||||||
it "accepts a name" do
|
it "accepts a name" do
|
||||||
result = @tester.link_to_remove_association('remove something', @form_obj)
|
result = @tester.link_to_remove_association('remove something', @form_obj)
|
||||||
@ -180,44 +197,50 @@ describe Cocoon do
|
|||||||
result.to_s.should == "<input id=\"Post__destroy\" name=\"Post[_destroy]\" type=\"hidden\" /><a href=\"#\" class=\"add_some_class remove_fields dynamic\" data-something=\"bla\">remove some long name</a>"
|
result.to_s.should == "<input id=\"Post__destroy\" name=\"Post[_destroy]\" type=\"hidden\" /><a href=\"#\" class=\"add_some_class remove_fields dynamic\" data-something=\"bla\">remove some long name</a>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "create_object" do
|
context "create_object" do
|
||||||
it "should create correct association with conditions" do
|
it "creates correct association with conditions" do
|
||||||
result = @tester.create_object(@form_obj, :admin_comments)
|
@tester.should_not_receive(:create_object_with_conditions)
|
||||||
result.author.should == "Admin"
|
result = @tester.create_object(@form_obj, :admin_comments)
|
||||||
end
|
result.author.should == "Admin"
|
||||||
|
|
||||||
it "should create correct association for belongs_to associations" do
|
|
||||||
result = @tester.create_object(stub(:object => Comment.new), :post)
|
|
||||||
result.should be_a Post
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should raise error if cannot reflect on association" do
|
|
||||||
expect { @tester.create_object(stub(:object => Comment.new), :not_existing) }.to raise_error /association/i
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should create an association if object responds to 'build_association' as singular" do
|
|
||||||
object = Comment.new
|
|
||||||
object.should_receive(:build_custom_item).and_return 'custom'
|
|
||||||
@tester.create_object(stub(:object => object), :custom_item).should == 'custom'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should create an association if object responds to 'build_association' as plural" do
|
|
||||||
object = Comment.new
|
|
||||||
object.should_receive(:build_custom_item).and_return 'custom'
|
|
||||||
@tester.create_object(stub(:object => object), :custom_items).should == 'custom'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "get_partial_path" do
|
it "creates correct association for belongs_to associations" do
|
||||||
it "generates the default partial name if no partial given" do
|
result = @tester.create_object(stub(:object => Comment.new), :post)
|
||||||
result = @tester.get_partial_path(nil, :admin_comments)
|
result.should be_a Post
|
||||||
result.should == "admin_comment_fields"
|
end
|
||||||
end
|
|
||||||
it "uses the given partial name" do
|
it "raises an error if cannot reflect on association" do
|
||||||
result = @tester.get_partial_path("comment_fields", :admin_comments)
|
expect { @tester.create_object(stub(:object => Comment.new), :not_existing) }.to raise_error /association/i
|
||||||
result.should == "comment_fields"
|
end
|
||||||
end
|
|
||||||
|
it "creates an association if object responds to 'build_association' as singular" do
|
||||||
|
object = Comment.new
|
||||||
|
object.should_receive(:build_custom_item).and_return 'custom'
|
||||||
|
@tester.create_object(stub(:object => object), :custom_item).should == 'custom'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates an association if object responds to 'build_association' as plural" do
|
||||||
|
object = Comment.new
|
||||||
|
object.should_receive(:build_custom_item).and_return 'custom'
|
||||||
|
@tester.create_object(stub(:object => object), :custom_items).should == 'custom'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can create using only conditions not the association" do
|
||||||
|
@tester.should_receive(:create_object_with_conditions).and_return('flappie')
|
||||||
|
@tester.create_object(@form_obj, :comments, true).should == 'flappie'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "get_partial_path" do
|
||||||
|
it "generates the default partial name if no partial given" do
|
||||||
|
result = @tester.get_partial_path(nil, :admin_comments)
|
||||||
|
result.should == "admin_comment_fields"
|
||||||
|
end
|
||||||
|
it "uses the given partial name" do
|
||||||
|
result = @tester.get_partial_path("comment_fields", :admin_comments)
|
||||||
|
result.should == "comment_fields"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user