Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move convert_to_model call from form_for into form_with #44468

Merged
merged 1 commit into from
May 25, 2022

Conversation

seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Feb 17, 2022

Summary

Ensure models passed to form_with attempt to call to_model.

Now that form_for is implemented in terms of form_with, this commit
also removes the convert_to_model call from the form_for implementation.

To exercise this behavior, change existing form_with test coverage.

Prior to this change, a call to form_with made with a model: argument
that responds to to_model would not incorporate the instance's persistence
state into the form's HTTP verb. After this change, the persistence state
inferred from the model: argument's to_model call is incorporated into
the <form> element's [method] attribute.

This is a separate follow-up change proposed in rails/rails#44328.
The original change to restore old behavior deliberately excluded
applying the same logic to form_with, since it would be a breaking
change from how form_with behaved previously.

This commit proposed making that breaking change.

@rails-bot rails-bot bot added the actionview label Feb 17, 2022
@seanpdoyle seanpdoyle changed the title Form with to model method form_with: Attempt to call to_model on model: Feb 17, 2022
@seanpdoyle
Copy link
Contributor Author

seanpdoyle commented Feb 17, 2022

This is cut off of #44328, and includes its first commit (44eaa25).

When reviewing this branch's changes, skip to the second commit.

This has been rebased.

@seanpdoyle seanpdoyle force-pushed the form-with-to-model-method branch 2 times, most recently from c337d32 to ea1ea3f Compare February 17, 2022 21:27
Copy link
Contributor

@etiennebarrie etiennebarrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you describe a bit the breaking change?

If I understand correctly, an object needs to implements to_model to cause a change in behavior. If it does implement it and returns self like described in the docs, there's no change with this PR.
If the object implements to_model, but points to another object that conforms to the Active Model API, the form is broken in the sense that it will use the object for the action attribute, but the form's method will use the passed object persisted? (allowing for objects that don't implement it). So you can have an object that has a to_model that points to an active model that's persisted, but have a form with a POST because the object passed in doesn't implement it.

So it feels more like a bugfix than a breaking change? Maybe I'm missing some use cases?

@@ -438,7 +438,7 @@ def form_for(record, options = {}, &block)
model = nil
object_name = record
else
model = convert_to_model(record)
model = record
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really need model anymore, we could pass record to form_with directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping to avoid mixing too many refactoring changes with changes to the behavior. Inlining apply_form_for_options! feels reasonable, but might even be better suited as a follow-up PR.

While the parallel declarations might be confusing, it's a pattern that pre-dates this change so I felt like it was worth sticking with.

If it's decided that this isn't a breaking change, and that it's a worthwhile bugfix, I'd feel more comfortable broadening the scope to include some variable clean up. What do you think?

@seanpdoyle seanpdoyle force-pushed the form-with-to-model-method branch 2 times, most recently from 677b5fd to 24321f0 Compare May 18, 2022 18:18
@etiennebarrie
Copy link
Contributor

cc @gmcgibbon @byroot I think this is reasonable, I have a hard time finding an incompatibility that this would cause. If you implement to_model today it's only called in some places, e.g. the submit button will use the right wording (update vs create), but the action will be of the object passed to form_with, not the one from to_model. But if you implement the Active Model API like persisted?, but with a to_model that points to another object, it's kind of weird?

Also weird is that since we will call to_model on the result of to_model (in tag helpers like submit), if that points somewhere else that will be incompatible. But that sounds really like a weird edge case.

@byroot
Copy link
Member

byroot commented May 24, 2022

I like the general idea, but I'm a bit confused as I don't see any call to to_model in that diff.

Also such new feature needs a CHANGELOG.

@seanpdoyle
Copy link
Contributor Author

I like the general idea, but I'm a bit confused as I don't see any call to to_model in that diff.

@byroot In order to shrink the diff back to its original size, I've reverted the refactor commit. With those incidental changes reverted, the focus of the diff involves the timing of calls to convert_to_model, which is where the call to to_model occurs.

Also such new feature needs a CHANGELOG.

The original change this is a follow-up to included a CHANGELOG entry:

Ensure models passed to form_for attempt to call to_model.

This PR was split in response to #44328 (comment), though I'm not quite sure how to most clearly characterize the (breaking) change into a new CHANGELOG entry.

@byroot
Copy link
Member

byroot commented May 24, 2022

@seanpdoyle ah, I see. Can you rework the PR title and description then, cause it's very hard to review in its current form.

@etiennebarrie
Copy link
Contributor

It's the convert_to_model call in form_with.

@seanpdoyle seanpdoyle changed the title form_with: Attempt to call to_model on model: Move convert_to_model call from form_for into form_with May 24, 2022
@seanpdoyle
Copy link
Contributor Author

@byroot I've renamed the PR title and tried to distill the change into the PR description.

Copy link
Member

@gmcgibbon gmcgibbon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this changes the behaviour of form_with, I think we should still add a changelog. Besides that this LGTM 👍

Ensure models passed to `form_with` attempt to call `to_model`.

Now that `form_for` is implemented in terms of `form_with`, this commit
also removes the `convert_to_model` call from the `form_for` implementation.

To exercise this behavior, change existing `form_with` test coverage.

Prior to this change, a call to `form_with` made with a `model:` argument
that responds to `to_model` would not incorporate the instance's persistence
state into the form's HTTP verb. After this change, the persistence state
inferred from the `model:` argument's `to_model` call is incorporated into
the `<form>` element's `[method]` attribute.

This is a separate follow-up change proposed in [rails#44328][].
The original change to restore old behavior _deliberately_ excluded
applying the same logic to `form_with`, since it would be a breaking
change from how `form_with` behaved previously.

This commit proposed making that breaking change.

[rails#44328]: rails#44328 (comment)
@seanpdoyle
Copy link
Contributor Author

@gmcgibbon I've added a CHANGELOG entry.

@byroot byroot merged commit 3404606 into rails:main May 25, 2022
@seanpdoyle seanpdoyle deleted the form-with-to-model-method branch May 25, 2022 14:27
inulty-dfe added a commit to DFE-Digital/apply-for-teacher-training that referenced this pull request Mar 12, 2024
  Upgrading to Rails 7.1 causes the RejectionReasons model to be passed
  as the `object_name` when rendering errors. This model doesn't hold
  any errors when it's passed to the form builder.

  This is because `form_with` now calls `#to_model` on the object passed
  to the form whereas it didn't in Rails 7.

  It's better that we don't depend on the internals of Rails for this
  feature.

  **Rails 7..7.1 fixes for `#to_model`**
  rails/rails#43421
  rails/rails#44326
  rails/rails#44468
inulty-dfe added a commit to DFE-Digital/apply-for-teacher-training that referenced this pull request Mar 12, 2024
  Upgrading to Rails 7.1 causes the RejectionReasons model to be passed
  as the `object_name` when rendering errors. This model doesn't hold
  any errors when it's passed to the form builder.

  This is because `form_with` now calls `#to_model` on the object passed
  to the form whereas it didn't in Rails 7.

  It's better that we don't depend on the internals of Rails for this
  feature.

  **Rails 7..7.1 fixes for `#to_model`**
  rails/rails#43421
  rails/rails#44326
  rails/rails#44468
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants