What was the reason behind this reversion?
I've been using the feature and it works great for my needs.
Best,
Zack Chandler
http://depixelate.com
Got it - I figured it was prep time for 2.2... I for one would like
to see this reapplied and iterated on after 2.2 is tagged as it really
helps out with multi-model forms.
Best,
Zack
The plugin exists of 3 parts:
- AutosaveAssociation, which as the name implies automatically saves
associations, not only on create as is the default.
- NestedParams, which is my take on this problem. It creates/updates/
destroys associations. This uses AutosaveAssociation.
- NestedParamsFormBuilder, which is a subclass of FormBuilder that
adds code to #fields_for for handling NestedParams enabled models.
Obviously this is a wip, but it might be interesting for the
discussion as well.
Cheers,
Eloy
I'm not particularly attached to using two different faux accessors
for create vs update. It's nice and clean, but really any way that
lets you tell create vs update is fine by me.
--josh
--
Josh Susser
http://blog.hasmanythrough.com
{
:tasks_params =>
{
'1' => { :id => '3', :name => 'Foo' },
'2' => { :id => '17', :_delete => '1', :name => 'Bob' },
'3' => { :name => 'Bar' },
'4' => { :name => 'Baz' }
}
}
The presence of a :_delete param is used to indicate that an old
record should be deleted. This can be done simply with a checkbox or
a JavaScript control. I think this works much better than trying to
delete records that just happen to be missing from the hash, since
that can get ugly if you are paginating a bunch of records or
otherwise subsetting e the list.
And I do think the _params (or _attributes) suffix on the name is
important.
Anyway, I'll try and get my proposed patch mashed around for this new
arrangement, and see if we can get a good form_for mod to support this
as well.
--josh
I really can't think of a common use case for this scenario.
It seems to me that when you're paginating a form, or any other edge
case,
you shouldn't rely on any naive solution, but rather solve it yourself
in your controller/model.
This is why I personally feel that destroying a record should be off
by default.
But for simple common use cases, where you might have removed
a child from the DOM, the naive/simple approach I took in my plugin is
good enough IMO.
>> Using the presence of the :id attribute to distinguish old vs new
>> records seems ok, but it might get wonky for edge cases that use a
>> different primary key. I think I do like that approach. The thing
>> that is important that some of the gisted approaches miss is being
>> able to order new records in the form. It can get seriously
>> confusing
>> for users if data shifts around form one place to another if they
>> failed a validation and need to correct something.
This is a very valid point which I have fixed in:
http://github.com/alloy/complex-form-examples/commit/4c740d5d8f5f99c0bfa5c3f38162ef26329c7cf0
Thanks for bringing that up.
>> I'm not particularly attached to using two different faux accessors
>> for create vs update. It's nice and clean, but really any way that
>> lets you tell create vs update is fine by me.
I'm sorry, but my English is not good enough to follow this bit.
Could you maybe explain it some more or in other words?
Eloy
This isn't a naive solution, but one that worked well for us in a
rather complicated workflow. Making the controller do extra stuff to
handle deletes outside of the save transaction is annoying and
problematic. Letting the model handle deletes as part of the same
operation means the entire change can be handled in one transaction,
which simplifies things a lot. We found that using a _delete field
worked best for us. Noticing that all fields were blank was a lot of
work for both the user and our code, and didn't work at all if the
model included radio buttons or select boxes without the blank option.
>>> Using the presence of the :id attribute to distinguish old vs new
>>> records seems ok, but it might get wonky for edge cases that use a
>>> different primary key. I think I do like that approach. The thing
>>> that is important that some of the gisted approaches miss is being
>>> able to order new records in the form. It can get seriously
>>> confusing
>>> for users if data shifts around form one place to another if they
>>> failed a validation and need to correct something.
>
> This is a very valid point which I have fixed in:
> http://github.com/alloy/complex-form-examples/commit/4c740d5d8f5f99c0bfa5c3f38162ef26329c7cf0
> Thanks for bringing that up.
>>> I'm not particularly attached to using two different faux accessors
>>> for create vs update. It's nice and clean, but really any way that
>>> lets you tell create vs update is fine by me.
>
> I'm sorry, but my English is not good enough to follow this bit.
> Could you maybe explain it some more or in other words?
Sorry about that, I was using non-standard jargon. I use "faux
accessor" to describe a model method that pretends to be an attribute
accessor for the purpose of using a form to edit something that isn't
a primitive attribute. And in my proposed patch for parameterized
associations I use two different faux accessors for the API, e.g.
create_tasks_params and update_tasks_params.
I was referring to my implementation as being naive, not yours :)
> Making the controller do extra stuff to
> handle deletes outside of the save transaction is annoying and
> problematic. Letting the model handle deletes as part of the same
> operation means the entire change can be handled in one transaction,
> which simplifies things a lot. We found that using a _delete field
> worked best for us.
Indeed I would solve this in the model as well.
I still don't see the pagination etc as a common use case.
However, adding the option to check for the presence of "_delete" =>
"1",
seems an easy/clear enough option as well.
> Noticing that all fields were blank was a lot of
> work for both the user and our code, and didn't work at all if the
> model included radio buttons or select boxes without the blank option.
Just to be sure that we are talking about the same, and not different
approaches from the gist :)
My implementation has 2 distinct options;
- :reject_empty : Which doesn't create new records for empty hashes.
- :destroy_missing : Which destroys records if they're missing in the
attributes hash.
Both of these are off by default.
> Sorry about that, I was using non-standard jargon. I use "faux
> accessor" to describe a model method that pretends to be an attribute
> accessor for the purpose of using a form to edit something that isn't
> a primitive attribute. And in my proposed patch for parameterized
> associations I use two different faux accessors for the API, e.g.
> create_tasks_params and update_tasks_params.
Thanks for the clarification. I agree.
As I always prefer comparing implementation over discussion,
I will take some time one of the next few days to turn my plugin into
a patch.
I will also include the option to mark a record to be destroyed with
"_delete".
Eloy
Thanks for clarifying.
> BTW Eloy, I saw your idea of using the timestamp when adding new task
> records (through javascript). Genius!
Thanks :)
I agree. I think I already support what you mean.
But maybe it would be an idea if you could send me a test which tests
the difference
in behaviour between a Hash and a Array so I can verify?
Cheers,
Eloy
Indeed. That's actually what he opted for with that comment, if I
understood correctly.
> Like Eloy's implementation, in my project I specify whether or not I
> want to destroy records that are missing from the hash, by
> a :destroy_missing option on the has_many declaration. But I
> definitely agree that the best implementation would be to match what
> ActiveRecord already does for tasks= when taking an array of objects
> rather than a params hash -- which is, according to the docs:
> "Replaces the collection's content by deleting and adding objects as
> appropriate."
Agreed, it seems to follow the principle of least surprise.
However, I wonder if that might have to do with the fact that
using a has_many writer method is usually not used in combination with
mass assignment....?
I guess for me it just doesn't feel right if by default stuff will get
deleted,
where one might expect that you are simply updating the attributes.
But this may very well just be me being "paranoia" :)
Comments?
> Re. Eloy's :reject_empty option: I never create records if all the
> attributes are blank (i.e. the user presses the "add another task"
> button but leaves the task completely blank). Is there really a use
> case for that? In fact, if a user deletes all the content from the
> fields of an existing task, my implementation would delete that
> record.
Using the :reject_empty option as a default might indeed be sensible.
Deleting records for which we get a empty hash however is a different
case.
The problem is that you might not always be sending
all attributes of a record. Thus assuming that a empty hash also means
that
all the attributes of a record are empty is a bit dangerous.
There are ways to solve this of course, but that is not something I
would
like to have by default.
Eloy
This is why I don't like overloading the comments= setter to handle
hashes, but prefer having a comments_params= setter instead. The
basic setter deals with models, the params setter is the convenience
interface for controllers to use for multi-model form support. It's a
much simpler implementation too.
I've already spammed the list with this before, but I believe my plugin
(which screws with UrlEncodedParameterParser) could solve some of the
issues you guys are running into with this.
http://www.vector-seven.com/git/rails/plugins/form_collections.git/
It's not very well named, but it effectively uses Arrays instead of
Hashes when numeric indices are used in the form. It also simplifies the
implementation of UrlEncodedPairParser at the expense of slightly
modified semantics in the parameter parsing (i.e. yes, it breaks a few
tests -- some of which I'm convinced were not correct to begin with).
With such a patch in play, ordering issues would be resolved (thanks to
the use of Arrays) and updates could be detected by effectively checking
if the :id attribute is set on the nested form attributes. In the "Add
Comment" case mentioned later in this discussion, I would assume one
would be doing a many_assoc#<< rather than a many_assoc#= ...
The only thing really missing from the plugin is a way to handle sparse
arrays. i.e. Assigning values only to indices 1, 200 and 999 of an array
should not yield an array of 1000 elements, 997 of which are nil. This
should be fairly trivial.
Cheers,
Tom
The reason I had used the association setter is because in the case
of a has_one association things like fields_for would work out of
the box and I only needed to create a special fields_for for a
has_many association.
Eloy
--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscribe@googlegroups.com.
To post to this group, send email to rubyonrails-core@googlegroups.com.
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.