SimpleFormのBootstrap4対応

足跡を残しておく。

# HTMLの構造が違う
https://getbootstrap.com/docs/4.0/components/forms/#server-side
`invalid-feedback` などとりあえず形が違う。

`config/initializers/simple_form_boosstrap.rb` を編集する。自分のところでは下記のようになった。`error_class` の名前を `is-invalid` にしたり、 エラー時のspanのクラスを`invalid-feedback`にしたり。

horizontal_formがデフォルトになっていたり、`:full_error`になっているのはこちらの都合なので、適宜直していただければ。

```rb
# Use this setup block to configure all options available in SimpleForm.
# @see https://suin.io/546
SimpleForm.setup do |config|
  config.error_notification_class = 'alert alert-danger'
  config.button_class = 'btn btn-default'
  config.boolean_label_class = nil

  config.wrappers :vertical_form, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly
    b.use :label, class: 'control-label'

    b.use :input, class: 'form-control'
    b.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
    b.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
  end

  config.wrappers :vertical_file_input, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :readonly
    b.use :label, class: 'control-label'

    b.use :input
    b.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
    b.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
  end

  config.wrappers :vertical_boolean, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.optional :readonly

    b.wrapper tag: 'div', class: 'checkbox' do |ba|
      ba.use :label_input
    end

    b.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
    b.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
  end

  config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.optional :readonly
    b.use :label, class: 'control-label'
    b.use :input
    b.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
    b.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
  end

  config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly
    b.use :label, class: 'col-sm-3 control-label'

    b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
      ba.use :input, class: 'form-control'
      ba.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
      ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
    end
  end

  config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :readonly
    b.use :label, class: 'col-sm-3 control-label'

    b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
      ba.use :input
      ba.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
      ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
    end
  end

  config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.optional :readonly

    b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr|
      wr.wrapper tag: 'div', class: 'checkbox' do |ba|
        ba.use :label_input
      end

      wr.use :error, wrap_with: { tag: 'span', class: 'help-block' }
      wr.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
    end
  end

  config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.optional :readonly

    b.use :label, class: 'col-sm-3 control-label'

    b.wrapper tag: 'div', class: 'col-sm-9' do |ba|
      ba.use :input
      ba.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
      ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
    end
  end

  config.wrappers :inline_form, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly
    b.use :label, class: 'sr-only'

    b.use :input, class: 'form-control'
    b.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
    b.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
  end

  config.wrappers :multi_select, tag: 'div', class: 'form-group row', error_class: 'is-invalid' do |b|
    b.use :html5
    b.optional :readonly
    b.use :label, class: 'control-label col-sm-3'
    b.wrapper tag: 'div', class: 'form-inline col-sm-9' do |ba|
      ba.use :input, class: 'form-control'
      ba.use :full_error, wrap_with: { tag: 'span', class: 'invalid-feedback' }
      ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block' }
    end
  end
  # Wrappers for forms and inputs using the Bootstrap toolkit.
  # Check the Bootstrap docs (http://getbootstrap.com)
  # to learn about the different styles for forms and inputs,
  # buttons and other elements.
  config.default_wrapper = :horizontal_form
  config.wrapper_mappings = {
    check_boxes: :horizontal_radio_and_checkboxes,
    radio_buttons: :horizontal_radio_and_checkboxes,
    file: :horizontal_file_input,
    boolean: :horizontal_boolean,
    datetime: :multi_select,
    date: :multi_select,
    time: :multi_select
  }
end

```

# バリデーションが飾られない

上記をやるだけでは画面にフィードバックも出ないし、色分けもされない。
とりあえず、formに `was-validated`を付けると表示されるようにはなるので、下記のようなヘルパーを書いた。多分SimpleFormがすぐに追随すると思う（これは無駄な努力感）。

```rb
module ApplicationHelper
  def simple_form_ex_for(record, options = {}, &block)
    classes = ['form-horizontal']
    if record.is_a?(Array)
      classes.push('was-validated') if record.last.try(:errors).present?
    else
      classes.push('was-validated') if record.try(:errors).present?
    end

    simple_form_for(record, html: {class: classes.join(' ')}, &block)
  end
end
```

# invalidなのにグリーンの枠
`form-control:valid`は、ブラウザ側のバリデーションのセレクタだが、これが悪さしてしまう様子。よくわからなくて、下記のようなscssを書いて逃げた。

```scss
/* for bootstrap4 validation */
form.was-validated {
  .is-invalid {
    .form-control {
      border-color: #dc3545;
    }
  }
}
```

# おしまいに
やりたいこと出来たのだけど、教材の内容で使おうとしていたのでこれはオーバースペック。おとなしくBootstrap3にすることにした。