Giving super powers to Rails nested forms with Vue.js — Part 2

After my first attempt to use VueJS with Rails nested forms I ended up with mix feelings. I really liked the code, but the exponential fields bug made me very discouraged.

The improvements in code clarity was so great that it didn't let me give up, so I keep thinking about the integration problems.

The first thing I noticed was the way I was rendering the form. It rendered all the fields, with it's errors, and this was being used as the template in VueJS. This caused the exponential bug: VueJS data multiplied by the rendered fields. So the first lesson was:

  • Always render the form in a way that can be used as a template by VueJS

I made some little changes to the build_nested_form method in my controller to follow the first lesson:

I exposed the method as a helper and also added the ignore_params. Now I construct my form with = simple_form_for build_nested_form(true), url: nested_form_good_path do |f|

This is like always render the form as in the new action. This worked very well, except for the errors. When I add a category/deadline, I don't want it to have the error messages. This lead to the second lesson:

  • Avoid errors information outputed by form helpers

This means that your HTML must have the markup to add error classes, like has-error and also the VueJS markup to iterate over the errors of a specific field. For example, this is the code to generate the category input field:

I'm using the compact_input_group wrapper as an inheritance from my real application. It's original function was make the error message more compact. Now I'm using it to not add the has-error class in the template. It’s messing the VueJS magic :/

But all this didn't come easy. I'm generating the errors with errors= #{@nested_form.errors.to_json}and if you take a look at the generated HTML you will get:

It's far from ideal to mark proper fields with it's errors, but fortunately Rails 5 merged a new feature for has_many options, it's called index_errors. I configured it globally with ActiveRecord::Base.index_nested_attributes_errors = trueand get this cool output:

I don't program much in JavaScript, so it take me while to realize how to put these errors inside my nested form JSON representation. I find a really simple solution, and I guess it's secure, using eval:

I'm also finishing a pull-request to active_type, so it also support index_errors.

You can see the live example in https://vuejs-nested-form.herokuapp.com and dig into it's code https://github.com/cerdiogenes/vuejs-nested-form. The good part also show my initial motivation to use VueJS: keep a column in sync.

Conclusion

Want to use VueJS in dynamic nested forms? Follow these rules:

  1. Always render a form that can be used as a template (probably this is the same form used in the new action)
  2. Use your own markup and VueJS magic for errors
  3. Use index_errors in your relationships

Have fun!