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

Support for Rails 5.1 form_with #369

Merged
merged 73 commits into from
Jan 26, 2018

Conversation

lcreid
Copy link
Contributor

@lcreid lcreid commented Jan 9, 2018

This PR adds a new helper bootstrap_form_with to support the Rails 5.1 form_with helper (#326).

This PR introduces a new helper bootstrap_form_with that functions in a very similar way to bootstrap_form_for and bootstrap_form_tag. The user can choose to use whichever helper for a given file, although they must be using Rails 5.1 to use bootstrap_form_with, because it calls Rails' form_with. Rails 5.0 is still supported.

I would like to continue to improve the test coverage, but I believe it's good enough for people to start experimenting with.

The code changes to the actual helpers are not extensive, but testing is a challenge. form_with adds to the variety of expected outputs that our tests have to handle. Some examples:

  • Prior to Rails 5, tabs were output in alphabetical order. After, they weren't
  • Some helpers starting with Rails 5 produced more output, e.g. an empty hidden input on file tags (The genesis of assert_equivalent_xml is found in Rewrite tests to work on Rails 4.0, 4.1, 4.2, and 5.0 #273 and Testing multiple versions of Rails #278, which specifically addressed these first two issues.)
  • Rails 5.1. stopped generating default DOM ids in the form helpers, and stopped putting the new_class HTML class on forms. There may be other differences that I've forgotten already
  • Rails 5.2 returns to putting default DOM ids, but still doesn't put the HTML class on forms
  • form_with does submits via unobtrusive Javascript, so form tags are marked with data-remote="true".

https://m.patrikonrails.com/rails-5-1s-form-with-vs-old-form-helpers-3a5f72a8c78a is an easy-to-read summary of differences between form_for and form_with.

Given the variety of expected outputs from different combinations of versions and helpers (form_for vs. form_with), it was decided to make this PR do the minimum to add bootstrap_form_with. Additional testing may be added in future PRs.

Fixes #326.

desheikh and others added 22 commits February 15, 2017 14:44
)

* Support :prepend and :append for the `select` helper
Fixes bootstrap-ruby#147

* Bootstrap 4 has renamed `control-label` to `form-control-label`
The main changes were around the fact that `form_with` doesn't
automatically add class and id attributes in many places that the
other form builders did.

Added a `for:` to the label options if an ID was specified for
the input element, so that the label would get the correct value
for the `for` attribute.

Sub-classed the form builder to override methods to make `form_with`
work without the default Rails-generated DOM IDs.
The `bootstrap_form_with` helper uses Ruby 2+ syntax. I put a test
for the Rails version, so the helper is only parse if running
Rails 5.1+.
Remove confusing comment.

Remove some more unnecessary diffs.
README.md Outdated
</label>
</div>
<input type="submit" name="commit" value="Log In" class="btn btn-secondary" data-disable-with="Log In" />
</form>
Copy link
Contributor

Choose a reason for hiding this comment

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

This is duplication. Should be sufficient to to say that bootstrap_form_with is the same thing as bootstrap_form_for and the only difference being that it takes same arguments as form_with

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Stand-alone" bootstrap_form_with documentation should be easier to update when bootstrap_form_for goes away. Also, users often appreciate examples they can just cut and paste.

README.md Outdated
</form>
```

#### Important Differences Between `form_with` and `form_for`
Copy link
Contributor

Choose a reason for hiding this comment

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

Link to Rails documentation would be enough

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some users may appreciate a summary of the differences/tricks about using form_with versus form_for. If everyone things this is overly redundant or pedantic, by all means I'll remove it.

README.md Outdated
the same way `form_for` and `form_tag` would do by default,
you need to specify `local: true` as an option to `form_with`.

##### No Default DOM IDs
Copy link
Contributor

Choose a reason for hiding this comment

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

That's out-of-date and incorrect.

Edit: Correct for 5.1, not correct for 5.2 (rails/rails@d3893ec) unless it gets back-ported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching this. I saw that the Rails PR had been merged, but in my rush to submit this PR, I didn't fix the README.

Copy link
Contributor Author

@lcreid lcreid Jan 11, 2018

Choose a reason for hiding this comment

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

I've fixed up the README. I need to test against Rails 5.2. Will I be able to do that after #366 is merged, or do I need to figure out how to test against Rails 5.2 myself?

Copy link
Contributor

Choose a reason for hiding this comment

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

You can just temporarily add gem "rails", "5.2.0.beta3" in Gemfile to test it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, @GBH . I could only find beta2 on rubygems.org, but that's good enough for me for now.

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 need to slow down and read stuff instead of jumping to conclusions. The commit @GBH referenced (rails/rails@d3893ec) is not the commit I thought it was. I will have to rethink this. The commit makes Rails 5.2+ easier to work with, but I'll have to think about how to document Rails 5.1 and form_with.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like every form_with test may have to have a 5.1 and 5.2+ version. There's already too much duplication to test form_with and form_for. I'll think about this, but if anyone has any suggestions, I'd love to hear them.

tl;dr of the problem: form_with in Rails 5.1 doesn't generate any DOM IDs in the HTML. It looks like form_with in Rails 5.2 is going back to generating DOM IDs in the HTML by default.

Copy link
Contributor

Choose a reason for hiding this comment

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

The thing is, you don't need to duplicate every single test there is. It's enough to make one for textfield and assume that all others work the same.

# expected = %{<div class="form-group"><label class="form-control-label" for="user_misc">Misc</label><div class="radio"><label for="user_misc_address_1"><input id="user_misc_address_1" name="user[misc]" type="radio" value="address_1" /> Foo</label></div><div class="radio"><label for="user_misc_address_2"><input id="user_misc_address_2" name="user[misc]" type="radio" value="address_2" /> Bar</label></div></div>}
#
# assert_equivalent_xml expected, @form_with_builder.collection_radio_buttons(:misc, collection, lambda { |a| "address_#{a.id}" }, :street)
# end
Copy link
Contributor

Choose a reason for hiding this comment

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

commented out tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching this. I will uncomment, but I will also have to figure out what to do about the DOM IDs mentioned above.

@lcreid lcreid changed the title [WIP] Support for Rails 5.1 form_with Support for Rails 5.1 form_with Jan 21, 2018
@mattbrictson
Copy link
Contributor

@lcreid I understand what you are aiming for with the testing approach, but it is a lot to do in a single PR along with all the other form_with stuff. I think I'd rather start by:

  1. Introducing bootstrap_form_with
  2. Adding your documentation to the README
  3. Include a bare-minimum test for Rails 5.1 (e.g.bootstrap_form_with with a single text field)
  4. Likewise, make a similar test for Rails 5.2 (perfect DRY-ness is not a goal in this first pass)

I'm comfortable hacking with git, so I am happy to patch together a new branch that contains the existing work you've done for all of those and retains your authorship of the commits.

I would argue that this first pass might almost be "good enough" to ship for 4.0.0 alpha; we'll probably need a follow-up PR to add fields_with. Then we can iterate on testing, CONTRIBUTING.md additions, etc. with future PRs.

@lcreid
Copy link
Contributor Author

lcreid commented Jan 21, 2018

@mattbrictson that's exactly my thinking. Sorry if my comments are a bit all over the place. In fact, I'd argue that this PR does what you're suggesting, without any more hacking on your part. I'm working on resolving the conflicts from this morning's merges. I had a small problem that I think is due to the Vagrant box I'm using for this work. (I could push something right now, but I'm not 100 percent sure it would pass Travis). I'll ping you shortly with an update on the status.

@lcreid
Copy link
Contributor Author

lcreid commented Jan 21, 2018

Sorry. Every time I pull in a merge, I've got to deal with a broken test. In order to do so properly, I need a bit more time. I hope to push something by end of day Pacific Time.

@lcreid
Copy link
Contributor Author

lcreid commented Jan 21, 2018

I pushed a conflict-free version, but it should fail on Travis. I expect one failure on all versions of Rails, and an additional failure only on Rails 5.1.4. @mattbrictson I'd appreciate your help, and I will try to look at this later today.

@lcreid
Copy link
Contributor Author

lcreid commented Jan 21, 2018

I had a chance to check, and those are the failures I expected. I'll be back on this later tonight.

@lcreid
Copy link
Contributor Author

lcreid commented Jan 23, 2018

This is ready for review prior to merge. As discussed above, I will continue to work on enhancing the testing of form_with for separate pull requests.

Copy link
Contributor

@mattbrictson mattbrictson left a comment

Choose a reason for hiding this comment

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

I am slowly trying to unpack this PR. Apologies for the delay.

I want to make sure the PR is as narrowly-focused as possible, meaning the code changes are the minimum required to support form_with scenarios and don't have any effects on existing form_for usage. The fact that form_builder.rb has modifications has me concerned about unintended consequences.

It seems that most of the changes to form_builder.rb are code formatting, comments, or syntax tweaks. Otherwise there are only two minor behavioral changes that I can see. Could you help me understand what they do?

@@ -314,6 +325,7 @@ def required_attribute?(obj, attribute)
def form_group_builder(method, options, html_options = nil)
options.symbolize_keys!
html_options.symbolize_keys! if html_options
options[:id] = html_options[:id] if html_options && @options[:skip_default_ids]
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you explain why this line is needed?

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'm pretty sure this is part my attempt to handle what is more elegantly handled in #357. The (minimized) tests pass without this line, so I'll take it out.

# The options argument is a small subset of the options that might have
# been passed to generate_label's caller, and definitely doesn't include
# :id.
options[:for] = id if acts_like_form_tag || id
Copy link
Contributor

Choose a reason for hiding this comment

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

I am also having trouble figuring out this part. If id is present, then it will be assigned regardless of whether acts_like_form_tag is true or false. Why is the acts_like_form_tag check needed at all, then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was to address part of the consequence of no default IDs in Rails 5.1 form_with. I'll take this out, and let it be handled by #357. And yes, the acts_like_form_tag becomes redundant when I added || id. "Doh!" I don't know what I was thinking. :-)

Gemfile Outdated
@@ -11,7 +11,7 @@ end

group :test do
# can relax version requirement for Rails 5.2.beta3+
gem "minitest", "~> 5.10.3"
gem "minitest", "5.10.3"
Copy link
Contributor

Choose a reason for hiding this comment

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

I could be wrong, but since 5.10.3 is the only version that matches the ~> 5.10.3 constraint, this change has no actual effect, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry. This was part of some flailing I did around the Minitest versions, and I shouldn't have let it slip through to the PR. :-(

README.md Outdated
@@ -13,7 +13,7 @@ Bootstrap v4-style forms into your Rails application.
## Requirements

* Ruby 2.3+
* Rails 5.0+
* Rails 5.0+ (Rails 5.1+ for `bootstrap_form_with`)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

README.md Outdated
@@ -40,6 +40,138 @@ Then require the CSS in your `application.css` file:

## Usage

### Rails >= 5.1
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not think we should go as far as recommend form_with for Rails 5.1. IMHO the form_with released in 5.1 is fundamentally broken as it does not include IDs. I personally would wait until 5.2 to make the leap from form_for to form_with.

Perhaps we could re-title this section with a caveat:

### `bootstrap_form_with` (Rails 5.1+)

Note that `form_with` in Rails 5.1 does not add IDs to form elements and labels by default, which are both important to Bootstrap markup. This behavior is corrected in Rails 5.2.

And place it after the section where we describe:

### `bootstrap_form_for` (Rails 5.0+)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I wrote the README relatively early in my work on this PR, and once I actually started to use form_with in my own app I was feeling that form_with in 5.1 wasn't really ready for prime time.

<input type="submit" name="commit" value="Log In" class="btn btn-secondary" data-disable-with="Log In" />
</form>
```

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is sufficient to stop here, with a note that says that both model: and url: styles are supported. We don't need to reproduce all the Rails documentation. (Perhaps a link to the appropriate section of the Rails doc would be nice.) In particular, I would remove these sections as well:

  • Important Differences Between form_with and form_for
  • Ajax by Default
  • No Default DOM IDs
  • Use fields Instead Of fields_for In Nested Forms
  • No Default Classes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Back in September, when I was first trying my fork with bootstrap_form_with, this section brought together the things I was learning about form_with versus form_for. But now I'm sure I can find a good link to differences, so I'll take this section out and replace it with a link or two to other documentation.

README.md Outdated
may be deprecated and then removed in future versions of Rails.
`bootstrap_form` will continue to support
`bootstrap_form_for` and `bootstrap_form_tag`
as long as Rails supports `form_for` and `form_tag`.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a great clarification 👍

Could you remove the hard line wrap?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry. An old nroff habit.

Copy link
Contributor

@mattbrictson mattbrictson left a comment

Choose a reason for hiding this comment

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

Sorry for asking you to trim so much of this PR, but I think getting the essence of it merged is the best path forward. We can iterate with small, targeted PRs from here and hopefully cut down on future review time.

assert_equivalent_xml expected, actual
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

I see what you are going for with this test. As Bootstrap and Rails change, the examples in the README will become out of date, which potentially misleads or confusion people learning about the gem. It also reflects poorly on the project if the README is inaccurate.

However I'm not convinced this is the best solution. I'd like to remove this from this PR and save it for future consideration.

@@ -14,7 +14,7 @@ class BootstrapRadioButtonTest < ActionView::TestCase
</label>
</div>
HTML
assert_equivalent_xml expected, @builder.radio_button(:misc, '1', label: 'This is a radio button')
assert_with_builder_radio(expected, :misc, '1', label: 'This is a radio button')
Copy link
Contributor

Choose a reason for hiding this comment

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

The fact that this test file has to change at all as a result of adding form_with indicates to me that there is a little too much going on in this PR. Adding one new feature (bootstrap_form_with) should mean adding one new set of tests. It should not require existing tests to be rewritten. I would prefer to see these tests left alone and the assert_with_builder_radio and assert_with_builder helpers excluded from this PR.

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'll remove all testing for bootstrap_form_with and fields other than the two cases you mentioned above. I'll continue to work on my testing branch and see if I still feel there's a need for more testing.

I've come around to the idea of making this PR minimal, so that's what I'll do. FWIW, the reasons why I wrote more extensive tests are:

  • If form_with is someday going to replace form_for and form_tag, then I thought it would be better write a complete set of bootstrap_form_with tests while I had my head in the problem, rather than leaving it to someone else, five years from now, to figure out how to migrate the bootstrap_form_for tests to bootstrap_form_with
  • Back in August I understood very little of this gem, and wanted to make sure I wasn't breaking anything
  • When I tried to use bootstrap_form_with in my app, lots of stuff broke. Most of it turned out to be due to the differences between form_for and form_with, but I didn't figure that out until after I wrote some tests for bootstrap_form_with
  • General distrust of myself any time I think that, "I only need to test this one little difference". :-)

And I really appreciate the feedback, so keep the comments coming!

# This test demonstrates that the Rails `fields` method passes its options
# to the builder, so there is no need to write a `bootstrap_form` helper
# for the `fields` method. If this test fails, it probably means you have to
# write a `fields` helper in `lib/bootstrap_form/form_builder.rb`.
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems this test was written to investigate Rails behavior, and is now no longer needed. Would you agree?

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 had the same thought, but then I thought it would also serve to test the assumption going forward and give us a warning if something changes in a future Rails version that the assumption is no longer valid. I think there's value to leave it in (perhaps with a better comment to make the intent clearer), but if you want it removed I'll certainly do so.

@@ -201,6 +202,17 @@ class BootstrapFieldsTest < ActionView::TestCase
</form>
HTML
assert_equivalent_xml expected, output

## TODO: DRY up the tests for the _with version
if Gem::Version.new(::Rails::VERSION::STRING).release >= Gem::Version.new('5.1.0')
Copy link
Contributor

Choose a reason for hiding this comment

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

Unless we have reason to believe that fields works fundamentally differently than fields_for, then I am not sure there is much value for the new tests in this file.

if Gem::Version.new(::Rails::VERSION::STRING).release >= Gem::Version.new('5.1.0')
# TODO: Could argue that we should test without `local: true`
assert_equivalent_xml remove_default_ids_for_rails_5_1(expected.gsub(/ class="new_user" id="new_user"/, "")),
bootstrap_form_with(model: @user, local: true) { |f| nil }
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is really the only test we need for this PR. But I would split it into 2 tests: one for Rails 5.1, and a second for Rails 5.2+. I prefer it be written as two separate, new tests (i.e. not bundled under test "default-style forms"). And they should each have their own expected HTML rather than mutating a single expectation with remove_default_ids_for_rails_5_1. I believe repeating the HTML makes it much more explicit as to what is being tested and what is expected, which is very important value for tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I'll do that. (And see my next comment.)

@mattbrictson
Copy link
Contributor

Thanks! I know this PR has been a lot of work so it is great to finally hit the merge button. I am going to squash it down to one commit and then merge.

@lcreid
Copy link
Contributor Author

lcreid commented Jan 26, 2018

Thanks. It was a great learning experience for me. I really appreciate your help and patience with me while I ground away on this.

@mattbrictson mattbrictson merged commit c2090f5 into bootstrap-ruby:master Jan 26, 2018
lcreid added a commit to lcreid/rails-bootstrap-forms that referenced this pull request Jun 4, 2018
* has-error is now has-danger

* inputs with errors should have form-control-feedback

* control-label is now form-control-label

* btn-default is now btn-secondary

* form-horizontal is no longer a required class

* input field should have form-control-danger on error

* checkbox is now form-check

* checkbox input should have form-check-input

* non inline checkbox labels should have form-check-label

* Support :prepend and :append for the `select` helper (bootstrap-ruby#327)

* Support :prepend and :append for the `select` helper
Fixes bootstrap-ruby#147

* Bootstrap 4 has renamed `control-label` to `form-control-label`

* `form_with` mostly working.

The main changes were around the fact that `form_with` doesn't
automatically add class and id attributes in many places that the
other form builders did.

Added a `for:` to the label options if an ID was specified for
the input element, so that the label would get the correct value
for the `for` attribute.

Sub-classed the form builder to override methods to make `form_with`
work without the default Rails-generated DOM IDs.

* Make it parse under Ruby 1.9.2

The `bootstrap_form_with` helper uses Ruby 2+ syntax. I put a test
for the Rails version, so the helper is only parse if running
Rails 5.1+.

* Try another way to fix Ruby 1.9.2 parse problem.

* Try Ruby 1.9.2 syntax yet again.

* Close, but going to try keying on Rails ID generation.

* Using the underlying option for id generation works.

* Use fields instead of fields_for.

* Finally remove FormBuilderFormWith.

* Remove some unnecessary diffs.

Remove confusing comment.

Remove some more unnecessary diffs.

* Fix select test case for :prepend, :append.

* Fix test case that hadn't been converted to form_with.

* Documentation tests.
Rails 5.2 not tested yet.

* Fix out-of-date HTML in test case.

* Uncomment and fix Rails 5.1 collection_radio_button tests.

* Fix README to remove false statements about 5.2.

* heredoc

* bootstrap_form_with_test working with 5.1 and 5.2.

* Handle Rails 5.1 and 5.2+.

* Use heredoc.strip.

* Comments and more heredoc formatting.

* Partially updated test cases.

* Don't add class="" to label if no classes specified.

* Intermediate check-in.

* Intermediate.

* Intermediate.

* Intermediate.

* Most of custom IDs working.

* Uncomment test cases.

* Resolve conflicts.

* Saving work with tests red and debugging.

* Commit before saving to future branch.

* Delete redundant test files.

* Remove puts and TODOs.

* Tests passing up to Rails 5.1.

* Some tests for form_with. All pass.

* Documentation tested.

* Update Rails 5.1 examples in documentation.

* Pin minitest to exactly 5.10.1 for Rails 5.0.

* Fence off one form_with test.

* Add tests for fields method. Pass when they shouldn't.

* Go back to using minitest 5.10.3 to do bisect.

* Test only form_for in one case.
The time zone text strings have an "&" in them. When it's converted to
XML and then back to a string, the "&" gets lost.

* Test and show that bootstrap_form fields helper not needed

* Remove unwanted tests.

* Feedback from review.
Removed unneeded documentation.
Moved `bootstrap_form_with` to after `bootstrap_form_for`.

* Minimize test cases.

* Fix mistaken commit of version.

* Remove code per review.

* Remove duplicate CHANGELOG entry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants