Repository: heartcombo/simple_form
Branch: main
Commit: 55bec8024c3b
Files: 122
Total size: 438.9 KB
Directory structure:
gitextract_7k1r7n03/
├── .github/
│ ├── code-scanning.yml
│ └── workflows/
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── ISSUE_TEMPLATE.md
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bin/
│ └── test
├── gemfiles/
│ ├── Gemfile-rails-7-0
│ ├── Gemfile-rails-7-1
│ ├── Gemfile-rails-7-2
│ ├── Gemfile-rails-8-0
│ └── Gemfile-rails-main
├── lib/
│ ├── generators/
│ │ └── simple_form/
│ │ ├── USAGE
│ │ ├── install_generator.rb
│ │ └── templates/
│ │ ├── README
│ │ ├── _form.html.erb
│ │ ├── _form.html.haml
│ │ ├── _form.html.slim
│ │ └── config/
│ │ ├── initializers/
│ │ │ ├── simple_form.rb
│ │ │ ├── simple_form_bootstrap.rb
│ │ │ └── simple_form_foundation.rb
│ │ └── locales/
│ │ └── simple_form.en.yml
│ ├── simple_form/
│ │ ├── action_view_extensions/
│ │ │ ├── builder.rb
│ │ │ └── form_helper.rb
│ │ ├── components/
│ │ │ ├── errors.rb
│ │ │ ├── hints.rb
│ │ │ ├── html5.rb
│ │ │ ├── label_input.rb
│ │ │ ├── labels.rb
│ │ │ ├── maxlength.rb
│ │ │ ├── min_max.rb
│ │ │ ├── minlength.rb
│ │ │ ├── pattern.rb
│ │ │ ├── placeholders.rb
│ │ │ └── readonly.rb
│ │ ├── components.rb
│ │ ├── error_notification.rb
│ │ ├── form_builder.rb
│ │ ├── helpers/
│ │ │ ├── autofocus.rb
│ │ │ ├── disabled.rb
│ │ │ ├── readonly.rb
│ │ │ ├── required.rb
│ │ │ └── validators.rb
│ │ ├── helpers.rb
│ │ ├── inputs/
│ │ │ ├── base.rb
│ │ │ ├── block_input.rb
│ │ │ ├── boolean_input.rb
│ │ │ ├── collection_check_boxes_input.rb
│ │ │ ├── collection_input.rb
│ │ │ ├── collection_radio_buttons_input.rb
│ │ │ ├── collection_select_input.rb
│ │ │ ├── color_input.rb
│ │ │ ├── date_time_input.rb
│ │ │ ├── file_input.rb
│ │ │ ├── grouped_collection_select_input.rb
│ │ │ ├── hidden_input.rb
│ │ │ ├── numeric_input.rb
│ │ │ ├── password_input.rb
│ │ │ ├── priority_input.rb
│ │ │ ├── range_input.rb
│ │ │ ├── rich_text_area_input.rb
│ │ │ ├── string_input.rb
│ │ │ ├── text_input.rb
│ │ │ └── weekday_input.rb
│ │ ├── inputs.rb
│ │ ├── map_type.rb
│ │ ├── railtie.rb
│ │ ├── tags.rb
│ │ ├── version.rb
│ │ ├── wrappers/
│ │ │ ├── builder.rb
│ │ │ ├── leaf.rb
│ │ │ ├── many.rb
│ │ │ ├── root.rb
│ │ │ └── single.rb
│ │ └── wrappers.rb
│ └── simple_form.rb
├── simple_form.gemspec
└── test/
├── action_view_extensions/
│ ├── builder_test.rb
│ └── form_helper_test.rb
├── components/
│ ├── custom_components_test.rb
│ └── label_test.rb
├── form_builder/
│ ├── association_test.rb
│ ├── button_test.rb
│ ├── error_notification_test.rb
│ ├── error_test.rb
│ ├── general_test.rb
│ ├── hint_test.rb
│ ├── input_field_test.rb
│ ├── label_test.rb
│ └── wrapper_test.rb
├── generators/
│ └── simple_form_generator_test.rb
├── inputs/
│ ├── boolean_input_test.rb
│ ├── collection_check_boxes_input_test.rb
│ ├── collection_radio_buttons_input_test.rb
│ ├── collection_select_input_test.rb
│ ├── color_input_test.rb
│ ├── country_input_test.rb
│ ├── datetime_input_test.rb
│ ├── disabled_test.rb
│ ├── discovery_test.rb
│ ├── file_input_test.rb
│ ├── general_test.rb
│ ├── grouped_collection_select_input_test.rb
│ ├── hidden_input_test.rb
│ ├── numeric_input_test.rb
│ ├── readonly_test.rb
│ ├── required_test.rb
│ ├── rich_text_area_input_test.rb
│ ├── string_input_test.rb
│ ├── text_input_test.rb
│ ├── time_zone_input_test.rb
│ └── weekday_input_test.rb
├── simple_form_test.rb
├── support/
│ ├── discovery_inputs.rb
│ ├── misc_helpers.rb
│ ├── mock_controller.rb
│ └── models.rb
└── test_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/code-scanning.yml
================================================
paths-ignore:
- test/**
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
permissions:
contents: read
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
test:
strategy:
fail-fast: false
matrix:
gemfile:
- Gemfile
- gemfiles/Gemfile-rails-main
- gemfiles/Gemfile-rails-8-0
- gemfiles/Gemfile-rails-7-2
- gemfiles/Gemfile-rails-7-1
- gemfiles/Gemfile-rails-7-0
ruby:
- '4.0'
- '3.4'
- '3.3'
- '3.2'
- '3.1'
- '3.0'
- '2.7'
exclude:
- gemfile: Gemfile
ruby: '3.1'
- gemfile: Gemfile
ruby: '3.0'
- gemfile: Gemfile
ruby: '2.7'
- gemfile: gemfiles/Gemfile-rails-main
ruby: '3.2'
- gemfile: gemfiles/Gemfile-rails-main
ruby: '3.1'
- gemfile: gemfiles/Gemfile-rails-main
ruby: '3.0'
- gemfile: gemfiles/Gemfile-rails-main
ruby: '2.7'
- gemfile: gemfiles/Gemfile-rails-8-0
ruby: '3.1'
- gemfile: gemfiles/Gemfile-rails-8-0
ruby: '3.0'
- gemfile: gemfiles/Gemfile-rails-8-0
ruby: '2.7'
- gemfile: gemfiles/Gemfile-rails-7-2
ruby: '3.0'
- gemfile: gemfiles/Gemfile-rails-7-2
ruby: '2.7'
runs-on: ubuntu-latest
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true # runs bundle install and caches installed gems automatically
- run: bundle exec rake
================================================
FILE: .gitignore
================================================
.bundle/
pkg/
rdoc/
gemfiles/*.lock
/.idea/
================================================
FILE: CHANGELOG.md
================================================
## 5.4.1
* Ruby 4.0 support (no changes required)
* Support procs on validators for minlength/maxlength, and improve validators logic across the board to match Rails [#1859](https://github.com/heartcombo/simple_form/pull/1859)
## 5.4.0
* Add support for Ruby 3.4 and Rails 7.2/8.0/8.1. (no changes required)
* Drop support for Rails < 7 and Ruby < 2.7.
* Add `weekday` input. [#1846](https://github.com/heartcombo/simple_form/pull/1846)
* Remove redundant `aria-required` attribute for required fields. [#1823](https://github.com/heartcombo/simple_form/pull/1823)
* Integrate `:rich_text_area` with placeholders [#1842](https://github.com/heartcombo/simple_form/pull/1842)
* Fix encrypted attributes improperly casted (later fixed in Rails) [#1836](https://github.com/heartcombo/simple_form/pull/1836)
* Pass `base` object to `human_attribute_name` in labels [#1812](https://github.com/heartcombo/simple_form/pull/1812)
## 5.3.1
* Revert "Speed up input mapping lookup by avoiding rescuing exceptions" from v5.3.0, it caused a regression on dev/test environments with custom inputs.
* Try a slightly different approach to input lookups, without relying on regexp, to see if that helps with performance as originally intended.
* Add support to Ruby 3.3. (no changes required.)
## 5.3.0
* Add support for Rails 7.1. (no meaningful changes required.)
* Add `SimpleForm.deprecator` to integrate with new application deprecators in Rails 7.1.
* Remove test files from the gem package. [@orien](https://github.com/orien)
* Speed up input mapping lookup by avoiding rescuing exceptions. [@meanphil](https://github.com/meanphil) [@kriom](https://github.com/kriom) [@egeek](https://github.com/egeek)
## 5.2.0
* Add support for Rails 7.0 and Ruby 3.1/3.2 (no changes required)
* Fix escaping issue on boolean input with `include_hidden: false` and custom wrapper.
* Update Bootstrap install generator version 5. [@mhw](https://github.com/mhw)
* Accept proc as `group_method` for grouped collection select
* Honor `include_hidden` option on inline boolean inputs [@yboulkaid](https://github.com/yboulkaid)
* Fix deprecation error when using country_select input.
## 5.1.0
* Remove `I18nCache` module entirely. It was added complexity for very little gain in some translations, and caused extra trouble upgrading to Ruby 3. If you need that level of caching consider looking into I18n caching as a whole.
* Add support for Ruby 3.0, drop support for Ruby < 2.5.
* Add support for Rails 6.1, drop support for Rails < 5.2.
* Move CI to GitHub Actions.
## 5.0.3
### Bug fix
* Fix for ActiveStorage::Attached::Many. [@enriquez](https://github.com/enriquez)
## 5.0.2
### Enhancements
* Remove instruction to use form-inline class. [@goalaleo](https://github.com/goalaleo)
* Added RichTextAreaInput for ActionText. [itsterry](https://github.com/itsterry)
* Skip valid_class check if no class defined. [TALlama](https://github.com/TALlama)
### Bug fix
* Fix 'aria-required' field generated by prompt. [@CarlosAlbertoSantos](https://github.com/CarlosAlbertoSantos)
## 5.0.1
### Bug fix
* Replace `_url` with `remote_url` when trying to guess file inputs [@tegon](https://github.com/tegon). This has the side-effect of changing carrierwave's support from `0.2.1` to `0.2.2`.
## 5.0.0
### Enhancements
* Set multiple attribute for grouped selects also. [@ollym](https://github.com/ollym)
* Removes or renames label classes. [Abduvakilov](https://github.com/Abduvakilov)
* Support to label custom classes for inline collections. [@feliperenan](https://github.com/feliperenan)
* Update bootstrap generator template to match v4.3.x. [@m5o](https://github.com/m5o)
* Allow "required" attribute in generated select elements of PriorityInput. [@mcountis](https://github.com/mcountis)
### Bug fix
* Do not call `#send` in form object to check whether the attribute is a file input. [@tegon](https://github.com/tegon)
## Deprecations
* The config `SimpleForm.file_methods` is deprecated and it has no effect. Simple Form now supports automatically discover of file inputs for the following Gems: activestorage, carrierwave, paperclip, refile and shrine. If you are using a custom method that is not from one of the supported Gems, please change your forms to pass the input type explicitly:
```erb
<%= form.input :avatar, as: :file %>
```
See http://blog.plataformatec.com.br/2019/09/incorrect-access-control-in-simple-form-cve-2019-16676 for more information.
## 4.1.0
### Enhancements
* Guess input type more carefully. [@sringling](https://github.com/sringling)
* Allow custom error on forms without model. [@victorperez](https://github.com/victorperez)
* Do not support Ruby < 2.3 anymore. [@gssbzn](https://github.com/gssbzn)
* Add color input type. [@gssbzn](https://github.com/gssbzn)
### Bug fix
* Improve disabled option to input_field. [@betelgeuse](https://github.com/betelgeuse)
* Memoize `input_html_classes` in `SimpleForm::Inputs::Base`. [@RigoTheDev](https://github.com/RigoTheDev)
* Fix column type citext HTML5 input type bug. [@brucew](https://github.com/brucew)
* Use form attribute in the nested boolean hidden field when it is given. [@feliperenan](https://github.com/feliperenan)
## 4.0.1
### Bug fix
* Do not support Rails 4 anymore. [@rafaelfranca](https://github.com/rafaelfranca)
* Add missing comma. [@vill](https://github.com/vill)
## 4.0.0
### Enhancements
* Add bootstrap v4.1 generator template. [@m5o](https://github.com/m5o)
* Add Rails 5.2 support. [@gobijan](https://github.com/gobijan)
* Add API to register custom components.[@feliperenan](https://github.com/feliperenan)
* Allow custom errors classes to inputs.[@feliperenan](https://github.com/feliperenan)
* Remove support from Rails 4.0, 4.1 and 4.2. [@feliperenan](https://github.com/feliperenan)
* Add support for citext, hstore, json & jsonb column types. [@swrobel](https://github.com/swrobel)
* Add :valid_class on input wrapper when value is present and valid [@aeberlin](https://github.com/aeberlin), [@m5o](https://github.com/m5o)
* Allow :valid_class to inputs when value is present and valid. [@m5o](https://github.com/m5o)
* Allow validation classes on input_field. [@feliperenan](https://github.com/feliperenan)
* Add basic ActiveStorage support. [@murb](https://github.com/murb)
### Bug fix
* Fix horizontal form label position, from right to text-right. [@cavpollo](https://github.com/cavpollo)
* Add base error display alongside existing errors. [@bluefalcon26](https://github.com/bluefalcon26)
* Silent deprecation warning for placeholder_text. [@moofkit](https://github.com/moofkit)
* Use custom i18n scope for label required html. [@tvdeyen](https://github.com/tvdeyen)
## 3.5.1
### Enhancements
* Exclude hidden field when unchecked_value: false. [@fschwahn](https://github.com/fschwahn)
* Add frozen_string_literal magic comment to several files. [@oniofchaos](https://github.com/oniofchaos)
* Try convert @object to model in case we got decorated object [@timurvafin](https://github.com/timurvafin)
- From now, if you are using some object that inherits from `SimpleDelegator`, you must implement
`def to_model; self; end`. Otherwise, *Simple Form* will convert the decorated object to the model
since `SimpleDelegator` will delegate it to the model.
* Code cleanup [@Fornacula](https://github.com/Fornacula)
### Bug fix
* Fix error when the scope from association has parameter. [@feliperenan](https://github.com/feliperenan)
* Only call `where` on associations when they respond to it. [@anicholson](https://github.com/anicholson)
* require 'action_pack' before using it. [@etagwerker](https://github.com/etagwerker)
* Check if Rails.env is defined. [@etagwerker](https://github.com/etagwerker)
* Fix minlength. [@mameier](https://github.com/mameier)
* Make errors_on_attribute return [] when not present. [@redrick](https://github.com/redrick)
* Fix boolean inputs in nested style for label non-string. [@feliperenan](https://github.com/feliperenan)
## 3.5.0
* Updated gem dependency to support Rails 5.1.x.
## 3.4.0
* Removed Ruby 2.4.0 `Integer` unification deprecation warning.
* Removed EOL Ruby 1.9.3 from the build matrix.
* Added `minlength` component.
* `boolean_label_class` can be set on a per-input basis.
## 3.3.1
### Bug fix
* Fix support for symbols when looking up types with `ActiveModel::Type`.
## 3.3.0
### enhancements
* Add the `aria-invalid` attribute on inputs with errors.
* Added support for the new `ActiveModel::Type` API over Active Record's
column objects.
### bug fix
* Fix `merge_wrapper_options` to correctly merge options with duplicated keys. [@herminiotorres](https://github.com/herminiotorres)
Closes [#1278](https://github.com/heartcombo/simple_form/issues/1278).
## 3.2.1
### enhancements
* Updated gem dependency to support Rails 5.0.x.
## 3.2.0
### bug fix
* Improve performance of input generation by disabling support for `_html` translations. This reverts the feature introduced on the 3.1.0 branch
## 3.1.1
### enhancements
* Add the `disabled_class` to the label when the input is disabled. [@rhodrid](https://github.com/rhodrid)
### bug fix
* Make it possible to override `required` value that was previously set in the wrapper. [@nashby](https://github.com/nashby)
* `date/time/datetime` inputs now correctly generate the label `for` attribute when
HTML5 compatibility is explicitly enabled. [@ericsullivan](https://github.com/ericsullivan)
* The datetime, date, and time inputs now have a nice format by default on bootstrap.
[@ulissesalmeida](https://github.com/ulissesalmeida) [@eltonchrls](https://github.com/eltonchrls)
* Now it is possible to set custom input mappings for collections.
Example:
```ruby
# On configuration:
config.input_mappings = { /gender$/ => :check_boxes }
# On form:
f.input :gender, collection: [:male, :female]
```
[strangeworks](https://github.com/strangeworks)
## 3.1.0
### enhancements
* Update foundation generator to version 5. [@jorge-d](https://github.com/jorge-d)
* Add mapping to `uuid` columns.
* Add custom namespaces for custom inputs feature. [@vala](https://github.com/vala)
* Add `:unless_blank` option to the wrapper API. [@IanVaughan](https://github.com/IanVaughan)
* Add support to html markup in the I18n options. [@laurocaetano](https://github.com/laurocaetano)
* Add the `full_error` component. [@laurocaetano](https://github.com/laurocaetano)
* Add support to `scope` to be used on associations. [@laurocaetano](https://github.com/laurocaetano)
* Execute the association `condition` in the object context. [@laurocaetano](https://github.com/laurocaetano)
* Check if the given association responds to `order` before calling it. [@laurocaetano](https://github.com/laurocaetano)
* Add Bootstrap 3 initializer template.
* For radio or checkbox collection always use `:item_wrapper_tag` to wrap the content and add `label` when using `boolean_style` with `:nested` [@kassio](https://github.com/kassio) and [@erichkist](https://github.com/erichkist)
* `input_field` uses the same wrapper as input but only with attribute components. [@nashby](https://github.com/nashby)
* Add wrapper mapping per form basis [@rcillo](https://github.com/rcillo) and [@bernardoamc](https://github.com/bernardoamc)
* Add `for` attribute to `label` when collections are rendered as radio or checkbox [@erichkist](https://github.com/erichkist), [@ulissesalmeida](https://github.com/ulissesalmeida) and [@fabioyamate](https://github.com/fabioyamate)
* Add `include_default_input_wrapper_class` config [@luizcosta](https://github.com/luizcosta)
* Map `datetime`, `date` and `time` input types to their respective HTML5 input tags
when the `:html5` is set to `true` [@volmer](https://github.com/volmer)
* Add `boolean_label_class` config.
* Add `:html` option to include additional attributes on custom wrappers [@remofritzsche](https://github.com/remofritzsche) and [@ulissesalmeida](https://github.com/ulissesalmeida)
* Make possible to use the Wrappers API to define attributes for the components.
See https://github.com/heartcombo/simple_form/pull/997 for more information.
* Put a whitespace before the `inline_label` options of boolean input if it is present.
* Add support to configure the `label_text` proc at the wrapper level. [@NOX73](https://github.com/NOX73)
* `label_text` proc now receive three arguments (label, request, and if the label was explicit). [@timscott](https://github.com/timscott)
* Add I18n support to `:include_blank` and `:prompt` when `:translate` is used as value. [@haines](https://github.com/heartcombo/simple_form/pull/616)
* Add support to define custom error messages for the attributes.
* Add support to change the I18n scope to be used in Simple Form. [@nielsbuus](https://github.com/nielsbuus)
* The default form class can now be overridden with `html: { :class }`. [@rmm5t](https://github.com/rmm5t)
### bug fix
* Fix `full_error` when the attribute is an association. [@mvdamme](https://github.com/jorge-d)
* Fix support to `:namespace` and `:index` options for nested check boxes and radio buttons when the attribute is an association.
* Collection input that uses automatic collection translation properly sets checked values.
Closes [#971](https://github.com/heartcombo/simple_form/issues/971) [@nashby](https://github.com/nashby)
* Collection input generates `required` attribute if it has `prompt` option. [@nashby](https://github.com/nashby)
* Grouped collection uses the first non-empty object to detect label and value methods.
## deprecation
* Methods on custom inputs now accept a required argument with the wrapper options.
See https://github.com/heartcombo/simple_form/pull/997 for more information.
* SimpleForm.form_class is deprecated in favor of SimpleForm.default_form_class.
Future versions of Simple Form will not generate `simple_form` class for the form
element.
See https://github.com/heartcombo/simple_form/pull/1109 for more information.
Please check [v3.0](https://github.com/heartcombo/simple_form/blob/v3.0/CHANGELOG.md) for previous changes.
================================================
FILE: CONTRIBUTING.md
================================================
## Contributing
1. If you have any questions about Simple Form, search the
[Wiki](https://github.com/heartcombo/simple_form/wiki)
or [Stack Overflow](http://stackoverflow.com/questions/tagged/simple_form).
Do not post questions here.
2. If you find a security bug, **DO NOT** submit an issue here.
Please send an e-mail to [heartcombo@googlegroups.com](mailto:heartcombo@googlegroups.com)
instead.
3. Do a small search on the issues tracker before submitting your issue to
see if it was already reported or fixed. In case it was not, create your report
including Rails and Simple Form versions. If you are getting exceptions, please
include the full backtrace.
That's it! The more information you give, the more easy it becomes for us to
track it down and fix it. Ideal scenario would be adding the issue to Simple Form
test suite or to a sample application.
Thanks!
================================================
FILE: Gemfile
================================================
source "https://rubygems.org"
gemspec
gem "activemodel", "~> 8.1.0"
gem "actionpack", "~> 8.1.0"
gem "railties", "~> 8.1.0"
================================================
FILE: ISSUE_TEMPLATE.md
================================================
## Precheck
- Do not use the issues tracker for help or support, try Stack Overflow.
- For bugs, do a quick search and make sure the bug has not yet been reported
- If you found a security bug, do not report it through GitHub. Please send an e-mail to heartcombo@googlegroups.com instead.
- Finally, be nice and have fun!
## Environment
- Ruby **[version]**
- Rails **[version]**
- Simple Form **[version]**
## Current behavior
Include code samples, errors, steps to reproduce the error and stacktraces if appropriate.
Will be even more helpful if you provide a sample application or a test case that reproduces the error.
## Expected behavior
================================================
FILE: MIT-LICENSE
================================================
Copyright (c) 2020-CURRENT Rafael França, Carlos Antonio da Silva
Copyright (c) 2009-2019 Plataformatec
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================

[](https://badge.fury.io/rb/simple_form)
Rails forms made easy.
**Simple Form** aims to be as flexible as possible while helping you with powerful components to create
your forms. The basic goal of **Simple Form** is to not touch your way of defining the layout, letting
you find the better design for your eyes. Most of the DSL was inherited from Formtastic,
which we are thankful for and should make you feel right at home.
INFO: This README refers to **Simple Form** 5.0. For older releases, check the related branch for your version.
## Table of Contents
- [Installation](#installation)
- [Bootstrap](#bootstrap-5)
- [Zurb Foundation 5](#zurb-foundation-5)
- [Country Select](#country-select)
- [Usage](#usage)
- [Stripping away all wrapper divs](#stripping-away-all-wrapper-divs)
- [Collections](#collections)
- [Priority](#priority)
- [Associations](#associations)
- [Buttons](#buttons)
- [Wrapping Rails Form Helpers](#wrapping-rails-form-helpers)
- [Extra helpers](#extra-helpers)
- [Simple Fields For](#simple-fields-for)
- [Collection Radio Buttons](#collection-radio-buttons)
- [Collection Check Boxes](#collection-check-boxes)
- [Available input types and defaults for each column type](#available-input-types-and-defaults-for-each-column-type)
- [Custom inputs](#custom-inputs)
- [Custom form builder](#custom-form-builder)
- [I18n](#i18n)
- [Configuration](#configuration)
- [The wrappers API](#the-wrappers-api)
- [Custom Components](#custom-components)
- [HTML 5 Notice](#html-5-notice)
- [Using non Active Record objects](#using-non-active-record-objects)
- [Information](#information)
- [RDocs](#rdocs)
- [Supported Ruby / Rails versions](#supported-ruby--rails-versions)
- [Bug reports](#bug-reports)
- [Maintainers](#maintainers)
- [License](#license)
## Installation
Add it to your Gemfile:
```ruby
gem 'simple_form'
```
Run the following command to install it:
```console
bundle install
```
Run the generator:
```console
rails generate simple_form:install
```
### Bootstrap 5
**Simple Form** can be easily integrated with [Bootstrap 5](https://getbootstrap.com/).
Use the `bootstrap` option in the install generator, like this:
```console
rails generate simple_form:install --bootstrap
```
This will add an initializer that configures **Simple Form** wrappers for
Bootstrap 5's [form controls](https://getbootstrap.com/docs/5.0/forms/overview/).
You have to be sure that you added a copy of the [Bootstrap](https://getbootstrap.com/)
assets on your application.
For more information see the generator output, our
[example application code](https://github.com/heartcombo/simple_form-bootstrap).
### Zurb Foundation 5
To generate wrappers that are compatible with [Zurb Foundation 5](https://get.foundation/sites/docs-v5/), pass
the `foundation` option to the generator, like this:
```console
rails generate simple_form:install --foundation
```
Please note that the Foundation wrapper does not support the `:hint` option by default. In order to
enable hints, please uncomment the appropriate line in `config/initializers/simple_form_foundation.rb`.
You will need to provide your own CSS styles for hints.
Please see the [instructions on how to install Foundation in a Rails app](https://get.foundation/sites/docs-v5/applications.html).
### Country Select
If you want to use the country select, you will need the
[country_select gem](https://rubygems.org/gems/country_select), add it to your Gemfile:
```ruby
gem 'country_select'
```
If you don't want to use the gem you can easily override this behaviour by mapping the
country inputs to something else, with a line like this in your `simple_form.rb` initializer:
```ruby
config.input_mappings = { /country/ => :string }
```
## Usage
**Simple Form** was designed to be customized as you need to. Basically it's a stack of components that
are invoked to create a complete html input for you, which by default contains label, hints, errors
and the input itself. It does not aim to create a lot of different logic from the default Rails
form helpers, as they do a great job by themselves. Instead, **Simple Form** acts as a DSL and just
maps your input type (retrieved from the column definition in the database) to a specific helper method.
To start using **Simple Form** you just have to use the helper it provides:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username %>
<%= f.input :password %>
<%= f.button :submit %>
<% end %>
```
This will generate an entire form with labels for user name and password as well, and render errors
by default when you render the form with invalid data (after submitting for example).
You can overwrite the default label by passing it to the input method. You can also add a hint,
an error, or even a placeholder. For boolean inputs, you can add an inline label as well:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username, label: 'Your username please', error: 'Username is mandatory, please specify one' %>
<%= f.input :password, hint: 'No special characters.' %>
<%= f.input :email, placeholder: 'user@domain.com' %>
<%= f.input :remember_me, inline_label: 'Yes, remember me' %>
<%= f.button :submit %>
<% end %>
```
In some cases you may want to disable labels, hints or errors. Or you may want to configure the html
of any of them:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username, label_html: { class: 'my_class' }, hint_html: { class: 'hint_class' } %>
<%= f.input :password, hint: false, error_html: { id: 'password_error' } %>
<%= f.input :password_confirmation, label: false %>
<%= f.button :submit %>
<% end %>
```
It is also possible to pass any html attribute straight to the input, by using the `:input_html`
option, for instance:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username, input_html: { class: 'special' } %>
<%= f.input :password, input_html: { maxlength: 20 } %>
<%= f.input :remember_me, input_html: { value: '1' } %>
<%= f.button :submit %>
<% end %>
```
If you want to pass the same options to all inputs in the form (for example, a default class),
you can use the `:defaults` option in `simple_form_for`. Specific options in `input` call will
overwrite the defaults:
```erb
<%= simple_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f| %>
<%= f.input :username, input_html: { class: 'special' } %>
<%= f.input :password, input_html: { maxlength: 20 } %>
<%= f.input :remember_me, input_html: { value: '1' } %>
<%= f.button :submit %>
<% end %>
```
Since **Simple Form** generates a wrapper div around your label and input by default, you can pass
any html attribute to that wrapper as well using the `:wrapper_html` option, like so:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username, wrapper_html: { class: 'username' } %>
<%= f.input :password, wrapper_html: { id: 'password' } %>
<%= f.input :remember_me, wrapper_html: { class: 'options' } %>
<%= f.button :submit %>
<% end %>
```
Required fields are marked with an * prepended to their labels.
By default all inputs are required. When the form object includes `ActiveModel::Validations`
(which, for example, happens with Active Record models), fields are required only when there is `presence` validation.
Otherwise, **Simple Form** will mark fields as optional. For performance reasons, this
detection is skipped on validations that make use of conditional options, such as `:if` and `:unless`.
And of course, the `required` property of any input can be overwritten as needed:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :name, required: false %>
<%= f.input :username %>
<%= f.input :password %>
<%= f.button :submit %>
<% end %>
```
By default, **Simple Form** will look at the column type in the database and use an
appropriate input for the column. For example, a column created with type
`:text` in the database will use a `textarea` input by default. See the section
[Available input types and defaults for each column
type](https://github.com/heartcombo/simple_form#available-input-types-and-defaults-for-each-column-type)
for a complete list of defaults.
**Simple Form** also lets you overwrite the default input type it creates:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username %>
<%= f.input :password %>
<%= f.input :description, as: :text %>
<%= f.input :accepts, as: :radio_buttons %>
<%= f.button :submit %>
<% end %>
```
So instead of a checkbox for the *accepts* attribute, you'll have a pair of radio buttons with yes/no
labels and a textarea instead of a text field for the description. You can also render boolean
attributes using `as: :select` to show a dropdown.
It is also possible to give the `:disabled` option to **Simple Form**, and it'll automatically mark
the wrapper as disabled with a CSS class, so you can style labels, hints and other components inside
the wrapper as well:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :username, disabled: true, hint: 'You cannot change your username.' %>
<%= f.button :submit %>
<% end %>
```
**Simple Form** inputs accept the same options as their corresponding input type helper in Rails:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :date_of_birth, as: :date, start_year: Date.today.year - 90,
end_year: Date.today.year - 12, discard_day: true,
order: [:month, :year] %>
<%= f.input :accepts, as: :boolean, checked_value: 'positive', unchecked_value: 'negative' %>
<%= f.button :submit %>
<% end %>
```
By default, **Simple Form** generates a hidden field to handle the un-checked case for boolean fields.
Passing `unchecked_value: false` in the options for boolean fields will cause this hidden field to be omitted,
following the convention in Rails. You can also specify `include_hidden: false` to skip the hidden field:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :just_the_checked_case, as: :boolean, include_hidden: false %>
<%= f.button :submit %>
<% end %>
```
**Simple Form** also allows you to use label, hint, input_field, error and full_error helpers
(please take a look at the rdocs for each method for more info):
```erb
<%= simple_form_for @user do |f| %>
<%= f.label :username %>
<%= f.input_field :username %>
<%= f.hint 'No special characters, please!' %>
<%= f.error :username, id: 'user_name_error' %>
<%= f.full_error :token %>
<%= f.submit 'Save' %>
<% end %>
```
Any extra option passed to these methods will be rendered as html option.
### Stripping away all wrapper divs
**Simple Form** also allows you to strip away all the div wrappers around the ` ` field that is
generated with the usual `f.input`.
The easiest way to achieve this is to use `f.input_field`.
Example:
```ruby
simple_form_for @user do |f|
f.input_field :name
f.input_field :remember_me, as: :boolean
end
```
```html
```
For check boxes and radio buttons you can remove the label changing `boolean_style` from default value `:nested` to `:inline`.
Example:
```ruby
simple_form_for @user do |f|
f.input_field :name
f.input_field :remember_me, as: :boolean, boolean_style: :inline
end
```
```html
```
To view the actual RDocs for this, check them out here - https://rubydoc.info/github/heartcombo/simple_form/main/SimpleForm/FormBuilder:input_field
### Collections
And what if you want to create a select containing the age from 18 to 60 in your form? You can do it
overriding the `:collection` option:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :user %>
<%= f.input :age, collection: 18..60 %>
<%= f.button :submit %>
<% end %>
```
Collections can be arrays or ranges, and when a `:collection` is given the `:select` input will be
rendered by default, so we don't need to pass the `as: :select` option. Other types of collection
are `:radio_buttons` and `:check_boxes`. Those are added by **Simple Form** to Rails set of form
helpers (read Extra Helpers section below for more information).
Collection inputs accept two other options beside collections:
* *label_method* => the label method to be applied to the collection to retrieve the label (use this
instead of the `text_method` option in `collection_select`)
* *value_method* => the value method to be applied to the collection to retrieve the value
Those methods are useful to manipulate the given collection. Both of these options also accept
lambda/procs in case you want to calculate the value or label in a special way eg. custom
translation. You can also define a `to_label` method on your model as **Simple Form** will search for
and use `:to_label` as a `:label_method` first if it is found.
By default, **Simple Form** will use the first item from an array as the label and the second one as the value.
If you want to change this behavior you must make it explicit, like this:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :gender, as: :radio_buttons, collection: [['0', 'female'], ['1', 'male']], label_method: :second, value_method: :first %>
<% end %>
```
All other options given are sent straight to the underlying Rails helper(s): [`collection_select`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select), [`collection_check_boxes`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_check_boxes), [`collection_radio_buttons`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_radio_buttons). For example, you can pass `prompt` and `selected` as:
```ruby
f.input :age, collection: 18..60, prompt: "Select your age", selected: 21
```
It may also be useful to explicitly pass a value to the optional `:selected` like above, especially if passing a collection of nested objects.
It is also possible to create grouped collection selects, that will use the html *optgroup* tags, like this:
```ruby
f.input :country_id, collection: @continents, as: :grouped_select, group_method: :countries
```
Grouped collection inputs accept the same `:label_method` and `:value_method` options, which will be
used to retrieve label/value attributes for the `option` tags. Besides that, you can give:
* *group_method* => the method to be called on the given collection to generate the options for
each group (required)
* *group_label_method* => the label method to be applied on the given collection to retrieve the label
for the _optgroup_ (**Simple Form** will attempt to guess the best one the same way it does with
`:label_method`)
### Priority
**Simple Form** also supports `:time_zone` and `:country`. When using such helpers, you can give
`:priority` as an option to select which time zones and/or countries should be given higher priority:
```ruby
f.input :residence_country, priority: [ "Brazil" ]
f.input :time_zone, priority: /US/
```
Those values can also be configured with a default value to be used on the site through the
`SimpleForm.country_priority` and `SimpleForm.time_zone_priority` helpers.
Note: While using `country_select` if you want to restrict to only a subset of countries for a specific
drop down then you may use the `:collection` option:
```ruby
f.input :shipping_country, priority: [ "Brazil" ], collection: [ "Australia", "Brazil", "New Zealand"]
```
### Associations
To deal with associations, **Simple Form** can generate select inputs, a series of radios buttons or checkboxes.
Lets see how it works: imagine you have a user model that belongs to a company and `has_and_belongs_to_many`
roles. The structure would be something like:
```ruby
class User < ActiveRecord::Base
belongs_to :company
has_and_belongs_to_many :roles
end
class Company < ActiveRecord::Base
has_many :users
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
```
Now we have the user form:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :name %>
<%= f.association :company %>
<%= f.association :roles %>
<%= f.button :submit %>
<% end %>
```
Simple enough, right? This is going to render a `:select` input for choosing the `:company`, and another
`:select` input with `:multiple` option for the `:roles`. You can, of course, change it to use radio
buttons and checkboxes as well:
```ruby
f.association :company, as: :radio_buttons
f.association :roles, as: :check_boxes
```
The association helper just invokes `input` under the hood, so all options available to `:select`,
`:radio_buttons` and `:check_boxes` are also available to association. Additionally, you can specify
the collection by hand, all together with the prompt:
```ruby
f.association :company, collection: Company.active.order(:name), prompt: "Choose a Company"
```
In case you want to declare different labels and values:
```ruby
f.association :company, label_method: :company_name, value_method: :id, include_blank: false
```
Please note that the association helper is currently only tested with Active Record. It currently
does not work well with Mongoid and depending on the ORM you're using your mileage may vary.
### Buttons
All web forms need buttons, right? **Simple Form** wraps them in the DSL, acting like a proxy:
```erb
<%= simple_form_for @user do |f| %>
<%= f.input :name %>
<%= f.button :submit %>
<% end %>
```
The above will simply call submit. You choose to use it or not, it's just a question of taste.
The button method also accepts optional parameters, that are delegated to the underlying submit call:
```erb
<%= f.button :submit, "Custom Button Text", class: "my-button" %>
```
To create a `` element, use the following syntax:
```erb
<%= f.button :button, "Custom Button Text" %>
<%= f.button :button do %>
Custom Button Text
<% end %>
```
### Wrapping Rails Form Helpers
Say you wanted to use a rails form helper but still wrap it in **Simple Form** goodness? You can, by
calling input with a block like so:
```erb
<%= f.input :role do %>
<%= f.select :role, Role.all.map { |r| [r.name, r.id, { class: r.company.id }] }, include_blank: true %>
<% end %>
```
In the above example, we're taking advantage of Rails 3's select method that allows us to pass in a
hash of additional attributes for each option.
### Extra helpers
**Simple Form** also comes with some extra helpers you can use inside rails default forms without relying
on `simple_form_for` helper. They are listed below.
#### Simple Fields For
Wrapper to use **Simple Form** inside a default rails form. It works in the same way that the `fields_for`
Rails helper, but change the builder to use the `SimpleForm::FormBuilder`.
```ruby
form_for @user do |f|
f.simple_fields_for :posts do |posts_form|
# Here you have all simple_form methods available
posts_form.input :title
end
end
```
#### Collection Radio Buttons
Creates a collection of radio inputs with labels associated (same API as `collection_select`):
```ruby
form_for @user do |f|
f.collection_radio_buttons :options, [[true, 'Yes'], [false, 'No']], :first, :last
end
```
```html
Yes
No
```
#### Collection Check Boxes
Creates a collection of checkboxes with labels associated (same API as `collection_select`):
```ruby
form_for @user do |f|
f.collection_check_boxes :options, [[true, 'Yes'], [false, 'No']], :first, :last
end
```
```html
Yes
No
```
To use this with associations in your model, you can do the following:
```ruby
form_for @user do |f|
f.collection_check_boxes :role_ids, Role.all, :id, :name # using :roles here is not going to work.
end
```
To add a CSS class to the label item, you can use the `item_label_class` option:
```ruby
f.collection_check_boxes :role_ids, Role.all, :id, :name, item_label_class: 'my-custom-class'
```
## Available input types and defaults for each column type
The following table shows the html element you will get for each attribute
according to its database definition. These defaults can be changed by
specifying the helper method in the column `Mapping` as the `as:` option.
Mapping | Generated HTML Element | Database Column Type
--------------- |--------------------------------------|---------------------
`boolean` | `input[type=checkbox]` | `boolean`
`string` | `input[type=text]` | `string`
`citext` | `input[type=text]` | `citext`
`email` | `input[type=email]` | `string` with `name =~ /email/`
`url` | `input[type=url]` | `string` with `name =~ /url/`
`tel` | `input[type=tel]` | `string` with `name =~ /phone/`
`password` | `input[type=password]` | `string` with `name =~ /password/`
`search` | `input[type=search]` | -
`uuid` | `input[type=text]` | `uuid`
`color` | `input[type=color]` | `string`
`text` | `textarea` | `text`
`hstore` | `textarea` | `hstore`
`json` | `textarea` | `json`
`jsonb` | `textarea` | `jsonb`
`file` | `input[type=file]` | `string` responding to file methods
`hidden` | `input[type=hidden]` | -
`integer` | `input[type=number]` | `integer`
`float` | `input[type=number]` | `float`
`decimal` | `input[type=number]` | `decimal`
`range` | `input[type=range]` | -
`datetime` | `datetime select` | `datetime/timestamp`
`date` | `date select` | `date`
`time` | `time select` | `time`
`weekday` | `select` (weekdays as options) | -
`select` | `select` | `belongs_to`/`has_many`/`has_and_belongs_to_many` associations
`radio_buttons` | collection of `input[type=radio]` | `belongs_to` associations
`check_boxes` | collection of `input[type=checkbox]` | `has_many`/`has_and_belongs_to_many` associations
`country` | `select` (countries as options) | `string` with `name =~ /country/`
`time_zone` | `select` (timezones as options) | `string` with `name =~ /time_zone/`
`rich_text_area`| `trix-editor` | -
## Custom inputs
It is very easy to add custom inputs to **Simple Form**. For instance, if you want to add a custom input
that extends the string one, you just need to add this file:
```ruby
# app/inputs/currency_input.rb
class CurrencyInput < SimpleForm::Inputs::Base
def input(wrapper_options)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
"$ #{@builder.text_field(attribute_name, merged_input_options)}".html_safe
end
end
```
And use it in your views:
```ruby
f.input :money, as: :currency
```
Note, you may have to create the `app/inputs/` directory and restart your webserver.
You can also redefine existing **Simple Form** inputs by creating a new class with the same name. For
instance, if you want to wrap date/time/datetime in a div, you can do:
```ruby
# app/inputs/date_time_input.rb
class DateTimeInput < SimpleForm::Inputs::DateTimeInput
def input(wrapper_options)
template.content_tag(:div, super)
end
end
```
Or if you want to add a class to all the select fields you can do:
```ruby
# app/inputs/collection_select_input.rb
class CollectionSelectInput < SimpleForm::Inputs::CollectionSelectInput
def input_html_classes
super.push('chosen')
end
end
```
If needed, you can namespace your custom inputs in a module and tell **Simple Form** to look for
their definitions in this module. This can avoid conflicts with other form libraries (like Formtastic) that look up
the global context to find inputs definition too.
```ruby
# app/inputs/custom_inputs/numeric_input
module CustomInputs
class NumericInput < SimpleForm::Inputs::NumericInput
def input_html_classes
super.push('no-spinner')
end
end
end
```
And in the **SimpleForm** initializer :
```ruby
# config/simple_form.rb
config.custom_inputs_namespaces << "CustomInputs"
```
## Custom form builder
You can create a custom form builder that uses **Simple Form**.
Create a helper method that calls `simple_form_for` with a custom builder:
```ruby
def custom_form_for(object, *args, &block)
options = args.extract_options!
simple_form_for(object, *(args << options.merge(builder: CustomFormBuilder)), &block)
end
```
Create a form builder class that inherits from `SimpleForm::FormBuilder`.
```ruby
class CustomFormBuilder < SimpleForm::FormBuilder
def input(attribute_name, options = {}, &block)
super(attribute_name, options.merge(label: false), &block)
end
end
```
## I18n
**Simple Form** uses all power of I18n API to lookup labels, hints, prompts and placeholders. To customize your
forms you can create a locale file like this:
```yaml
en:
simple_form:
labels:
user:
username: 'User name'
password: 'Password'
hints:
user:
username: 'User name to sign in.'
password: 'No special characters, please.'
placeholders:
user:
username: 'Your username'
password: '****'
include_blanks:
user:
age: 'Rather not say'
prompts:
user:
role: 'Select your role'
```
And your forms will use this information to render the components for you.
**Simple Form** also lets you be more specific, separating lookups through actions.
Let's say you want a different label for new and edit actions, the locale file would
be something like:
```yaml
en:
simple_form:
labels:
user:
username: 'User name'
password: 'Password'
edit:
username: 'Change user name'
password: 'Change password'
```
This way **Simple Form** will figure out the right translation for you, based on the action being
rendered. And to be a little bit DRYer with your locale file, you can specify defaults for all
models under the 'defaults' key:
```yaml
en:
simple_form:
labels:
defaults:
username: 'User name'
password: 'Password'
new:
username: 'Choose a user name'
hints:
defaults:
username: 'User name to sign in.'
password: 'No special characters, please.'
placeholders:
defaults:
username: 'Your username'
password: '****'
```
**Simple Form** will always look for a default attribute translation under the "defaults" key if no
specific is found inside the model key.
In addition, **Simple Form** will fallback to default `human_attribute_name` from Rails when no other
translation is found for labels. Finally, you can also overwrite any label, hint or placeholder
inside your view, just by passing the option manually. This way the I18n lookup will be skipped.
For `:prompt` and `:include_blank` the I18n lookup is optional and to enable it is necessary to pass
`:translate` as value.
```ruby
f.input :role, prompt: :translate
```
**Simple Form** also has support for translating options in collection helpers. For instance, given a
User with a `:role` attribute, you might want to create a select box showing translated labels
that would post either `:admin` or `:editor` as value. With **Simple Form** you could create an input
like this:
```ruby
f.input :role, collection: [:admin, :editor]
```
And **Simple Form** will try a lookup like this in your locale file, to find the right labels to show:
```yaml
en:
simple_form:
options:
user:
role:
admin: 'Administrator'
editor: 'Editor'
```
You can also use the `defaults` key as you would do with labels, hints and placeholders. It is
important to notice that **Simple Form** will only do the lookup for options if you give a collection
composed of symbols only. This is to avoid constant lookups to I18n.
It's also possible to translate buttons, using Rails' built-in I18n support:
```yaml
en:
helpers:
submit:
user:
create: "Add %{model}"
update: "Save Changes"
```
There are other options that can be configured through I18n API, such as required text and boolean.
Be sure to check our locale file or the one copied to your application after you run
`rails generate simple_form:install`.
It should be noted that translations for labels, hints and placeholders for a namespaced model, e.g.
`Admin::User`, should be placed under `admin_user`, not under `admin/user`. This is different from
how translations for namespaced model and attribute names are defined:
```yaml
en:
activerecord:
models:
admin/user: User
attributes:
admin/user:
name: Name
```
They should be placed under `admin/user`. Form labels, hints and placeholders for those attributes,
though, should be placed under `admin_user`:
```yaml
en:
simple_form:
labels:
admin_user:
name: Name
```
This difference exists because **Simple Form** relies on `object_name` provided by Rails'
FormBuilder to determine the translation path for a given object instead of `i18n_key` from the
object itself. Thus, similarly, if a form for an `Admin::User` object is defined by calling
`simple_form_for @admin_user, as: :some_user`, **Simple Form** will look for translations
under `some_user` instead of `admin_user`.
When translating `simple_fields_for` attributes be sure to use the same name you pass to it, e.g. `simple_fields_for :posts` should be placed under `posts` not `post`:
```yaml
en:
simple_form:
labels:
posts:
title: 'Post title'
hints:
posts:
title: 'A good title'
placeholders:
posts:
title: 'Once upon a time...'
```
## Configuration
**Simple Form** has several configuration options. You can read and change them in the initializer
created by **Simple Form**, so if you haven't executed the command below yet, please do:
`rails generate simple_form:install`
### The wrappers API
With **Simple Form** you can configure how your components will be rendered using the wrappers API.
The syntax looks like this:
```ruby
config.wrappers tag: :div, class: :input,
error_class: :field_with_errors,
valid_class: :field_without_errors do |b|
# Form extensions
b.use :html5
b.optional :pattern
b.use :maxlength
b.use :placeholder
b.use :readonly
# Form components
b.use :label_input
b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error }
end
```
The _Form components_ will generate the form tags like labels, inputs, hints or errors contents.
The available components are:
```ruby
:label # The tag alone
:input # The tag alone
:label_input # The and the tags
:hint # The hint for the input
:error # The error for the input
```
The _Form extensions_ are used to generate some attributes or perform some lookups on the model to
add extra information to your components.
You can create new _Form components_ using the wrappers API as in the following example:
```ruby
config.wrappers do |b|
b.use :placeholder
b.use :label_input
b.wrapper tag: :div, class: 'separator' do |component|
component.use :hint, wrap_with: { tag: :span, class: :hint }
component.use :error, wrap_with: { tag: :span, class: :error }
end
end
```
this will wrap the hint and error components within a `div` tag using the class `'separator'`.
You can customize _Form components_ passing options to them:
```ruby
config.wrappers do |b|
b.use :label_input, class: 'label-input-class', error_class: 'is-invalid', valid_class: 'is-valid'
end
```
This sets the input and label classes to `'label-input-class'` and will set the class `'is-invalid'`
if the input has errors and `'is-valid'` if the input is valid.
If you want to customize the custom _Form components_ on demand you can give it a name like this:
```ruby
config.wrappers do |b|
b.use :placeholder
b.use :label_input
b.wrapper :my_wrapper, tag: :div, class: 'separator', html: { id: 'my_wrapper_id' } do |component|
component.use :hint, wrap_with: { tag: :span, class: :hint }
component.use :error, wrap_with: { tag: :span, class: :error }
end
end
```
and now you can pass options to your `input` calls to customize the `:my_wrapper` _Form component_.
```ruby
# Completely turns off the custom wrapper
f.input :name, my_wrapper: false
# Configure the html
f.input :name, my_wrapper_html: { id: 'special_id' }
# Configure the tag
f.input :name, my_wrapper_tag: :p
```
You can also define more than one wrapper and pick one to render in a specific form or input.
To define another wrapper you have to give it a name, as the follow:
```ruby
config.wrappers :small do |b|
b.use :placeholder
b.use :label_input
end
```
and use it in this way:
```ruby
# Specifying to whole form
simple_form_for @user, wrapper: :small do |f|
f.input :name
end
# Specifying to one input
simple_form_for @user do |f|
f.input :name, wrapper: :small
end
```
**Simple Form** also allows you to use optional elements. For instance, let's suppose you want to use
hints or placeholders, but you don't want them to be generated automatically. You can set their
default values to `false` or use the `optional` method. Is preferable to use the `optional` syntax:
```ruby
config.wrappers placeholder: false do |b|
b.use :placeholder
b.use :label_input
b.wrapper tag: :div, class: 'separator' do |component|
component.optional :hint, wrap_with: { tag: :span, class: :hint }
component.use :error, wrap_with: { tag: :span, class: :error }
end
end
```
By setting it as `optional`, a hint will only be generated when `hint: true` is explicitly used.
The same for placeholder.
It is also possible to give the option `:unless_blank` to the wrapper if you want to render it only
when the content is present.
```ruby
b.wrapper tag: :span, class: 'hint', unless_blank: true do |component|
component.optional :hint
end
```
## Custom Components
When you use custom wrappers, you might also be looking for a way to add custom components to your
wrapper. The default components are:
```ruby
:label # The tag alone
:input # The tag alone
:label_input # The and the tags
:hint # The hint for the input
:error # The error for the input
```
A custom component might be interesting for you if your views look something like this:
```erb
<%= simple_form_for @blog do |f| %>
2
<%= f.input :body, as: :text %>
<% end %>
```
A cleaner method to create your views would be:
```erb
<%= simple_form_for @blog, wrapper: :with_numbers do |f| %>
<%= f.input :title, number: 1 %>
<%= f.input :body, as: :text, number: 2 %>
<% end %>
```
To use the number option on the input, first, tells to Simple Form the place where the components
will be:
``` ruby
# config/initializers/simple_form.rb
Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
```
Create a new component within the path specified above:
```ruby
# lib/components/numbers_component.rb
module NumbersComponent
# To avoid deprecation warning, you need to make the wrapper_options explicit
# even when they won't be used.
def number(wrapper_options = nil)
@number ||= begin
options[:number].to_s.html_safe if options[:number].present?
end
end
end
SimpleForm.include_component(NumbersComponent)
```
Finally, add a new wrapper to the config/initializers/simple_form.rb file:
```ruby
config.wrappers :with_numbers, tag: 'div', class: 'row', error_class: 'error' do |b|
b.use :html5
b.use :number, wrap_with: { tag: 'div', class: 'span1 number' }
b.wrapper tag: 'div', class: 'span8' do |ba|
ba.use :placeholder
ba.use :label
ba.use :input
ba.use :error, wrap_with: { tag: 'span', class: 'help-inline' }
ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' }
end
end
```
## HTML 5 Notice
By default, **Simple Form** will generate input field types and attributes that are supported in HTML5,
but are considered invalid HTML for older document types such as HTML4 or XHTML1.0. The HTML5
extensions include the new field types such as email, number, search, url, tel, and the new
attributes such as required, autofocus, maxlength, min, max, step.
Most browsers will not care, but some of the newer ones - in particular Chrome 10+ - use the
required attribute to force a value into an input and will prevent form submission without it.
Depending on the design of the application this may or may not be desired. In many cases it can
break existing UI's.
It is possible to disable all HTML 5 extensions in **Simple Form** by removing the `html5`
component from the wrapper used to render the inputs.
For example, change:
```ruby
config.wrappers tag: :div do |b|
b.use :html5
b.use :label_input
end
```
To:
```ruby
config.wrappers tag: :div do |b|
b.use :label_input
end
```
If you want to have all other HTML 5 features, such as the new field types, you can disable only
the browser validation:
```ruby
SimpleForm.browser_validations = false # default is true
```
This option adds a new `novalidate` property to the form, instructing it to skip all HTML 5
validation. The inputs will still be generated with the required and other attributes, that might
help you to use some generic javascript validation.
You can also add `novalidate` to a specific form by setting the option on the form itself:
```erb
<%= simple_form_for(resource, html: { novalidate: true }) do |form| %>
```
Please notice that none of the configurations above will disable the `placeholder` component,
which is an HTML 5 feature. We believe most of the newest browsers are handling this attribute
just fine, and if they aren't, any plugin you use would take care of applying the placeholder.
In any case, you can disable it if you really want to, by removing the placeholder component
from the components list in the **Simple Form** configuration file.
HTML 5 date / time inputs are not generated by **Simple Form** by default, so using `date`,
`time` or `datetime` will all generate select boxes using normal Rails helpers. We believe
browsers are not totally ready for these yet, but you can easily opt-in on a per-input basis
by passing the html5 option:
```erb
<%= f.input :expires_at, as: :date, html5: true %>
```
## Using non Active Record objects
There are few ways to build forms with objects that don't inherit from Active Record, as
follows:
You can include the module `ActiveModel::Model`.
```ruby
class User
include ActiveModel::Model
attr_accessor :id, :name
end
```
If you are using Presenters or Decorators that inherit from `SimpleDelegator` you can delegate
it to the model.
```ruby
class UserPresenter < SimpleDelegator
# Without that, Simple Form will consider the user model as the object.
def to_model
self
end
end
```
You can define all methods required by the helpers.
```ruby
class User
extend ActiveModel::Naming
attr_accessor :id, :name
def to_model
self
end
def to_key
id
end
def persisted?
false
end
end
```
To have SimpleForm infer the attributes' types, you can provide
`#has_attribute?` and `#type_for_attribute` methods.
The later should return an object that responds to `#type`
with the attribute type. This is useful for generating
the correct input types (eg: checkboxes for booleans).
```ruby
class User < Struct.new(:id, :name, :age, :registered)
def to_model
self
end
def model_name
OpenStruct.new(param_key: "user")
end
def to_key
id
end
def persisted?
id.present?
end
def has_attribute?(attr_name)
%w(id name age registered).include?(attr_name.to_s)
end
def type_for_attribute(attr_name)
case attr_name.to_s
when "id" then OpenStruct.new(type: :integer)
when "name" then OpenStruct.new(type: :string)
when "age" then OpenStruct.new(type: :integer)
when "registered" then OpenStruct.new(type: :boolean)
end
end
end
```
If your object doesn't implement those methods, you must make explicit it when you are
building the form
```ruby
class User
attr_accessor :id, :name
# The only method required to use the f.submit helper.
def persisted?
false
end
end
```
```erb
<%= simple_form_for(@user, as: :user, method: :post, url: users_path) do |f| %>
<%= f.input :name %>
<%= f.submit 'New user' %>
<% end %>
```
## Information
### RDocs
You can view the **Simple Form** documentation in RDoc format here:
https://rubydoc.info/github/heartcombo/simple_form/main/frames
### Supported Ruby / Rails versions
We intend to maintain support for all Ruby / Rails versions that haven't reached end-of-life.
For more information about specific versions please check [Ruby](https://www.ruby-lang.org/en/downloads/branches/)
and [Rails](https://guides.rubyonrails.org/maintenance_policy.html) maintenance policies, and our test matrix.
### Bug reports
If you discover any bugs, feel free to create an issue on GitHub. Please add as much information as
possible to help us in fixing the potential bug. We also encourage you to help even more by forking and
sending us a pull request.
https://github.com/heartcombo/simple_form/issues
If you have discovered a security related bug, please do NOT use the GitHub issue tracker. Send an e-mail to heartcombo.oss@gmail.com.
## License
MIT License.
Copyright 2020-CURRENT Rafael França, Carlos Antonio da Silva.
Copyright 2009-2019 Plataformatec.
The Simple Form logo is licensed under [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/).
================================================
FILE: Rakefile
================================================
# encoding: UTF-8
require 'bundler/gem_tasks'
require 'rake/testtask'
desc 'Default: run unit tests.'
task default: :test
desc 'Test the simple_form plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
begin
require 'rdoc/task'
desc 'Generate documentation for the simple_form plugin.'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'SimpleForm'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.md')
rdoc.rdoc_files.include('lib/**/*.rb')
end
rescue LoadError
puts 'RDoc::Task is not supported on this platform'
end
================================================
FILE: bin/test
================================================
#!/usr/bin/env ruby
$: << File.expand_path(File.expand_path('../../test', __FILE__))
require 'bundler/setup'
require 'rails/test_unit/runner'
require 'rails/test_unit/reporter'
Rails::TestUnitReporter.executable = 'bin/test'
Rails::TestUnit::Runner.parse_options(ARGV)
Rails::TestUnit::Runner.run(ARGV)
================================================
FILE: gemfiles/Gemfile-rails-7-0
================================================
source "https://rubygems.org"
gemspec path: ".."
gem "activemodel", "~> 7.0.0"
gem "actionpack", "~> 7.0.0"
gem "railties", "~> 7.0.0"
================================================
FILE: gemfiles/Gemfile-rails-7-1
================================================
source "https://rubygems.org"
gemspec path: ".."
gem "activemodel", "~> 7.1.0"
gem "actionpack", "~> 7.1.0"
gem "railties", "~> 7.1.0"
================================================
FILE: gemfiles/Gemfile-rails-7-2
================================================
source "https://rubygems.org"
gemspec path: ".."
gem "activemodel", "~> 7.2.0"
gem "actionpack", "~> 7.2.0"
gem "railties", "~> 7.2.0"
================================================
FILE: gemfiles/Gemfile-rails-8-0
================================================
source "https://rubygems.org"
gemspec path: ".."
gem "activemodel", "~> 8.0.0"
gem "actionpack", "~> 8.0.0"
gem "railties", "~> 8.0.0"
================================================
FILE: gemfiles/Gemfile-rails-main
================================================
source "https://rubygems.org"
gemspec path: ".."
gem "railties", github: "rails/rails", branch: "main"
gem "activemodel", github: "rails/rails", branch: "main"
gem "actionpack", github: "rails/rails", branch: "main"
gem "tzinfo"
================================================
FILE: lib/generators/simple_form/USAGE
================================================
To copy a SimpleForm initializer to your Rails App, with some configuration values, just do:
rails generate simple_form:install
================================================
FILE: lib/generators/simple_form/install_generator.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Generators
class InstallGenerator < Rails::Generators::Base
desc "Copy SimpleForm default files"
source_root File.expand_path('../templates', __FILE__)
class_option :template_engine, desc: 'Template engine to be invoked (erb, haml or slim).'
class_option :bootstrap, type: :boolean, desc: 'Add the Bootstrap 5 wrappers to the SimpleForm initializer.'
class_option :foundation, type: :boolean, desc: 'Add the Zurb Foundation 5 wrappers to the SimpleForm initializer.'
def info_bootstrap
return if options.bootstrap? || options.foundation?
puts "SimpleForm supports Bootstrap 5 and Zurb Foundation 5. If you want "\
"a configuration that is compatible with one of these frameworks, then please " \
"re-run this generator with --bootstrap or --foundation as an option."
end
def copy_config
template "config/initializers/simple_form.rb"
if options[:bootstrap]
template "config/initializers/simple_form_bootstrap.rb"
elsif options[:foundation]
template "config/initializers/simple_form_foundation.rb"
end
directory 'config/locales'
end
def copy_scaffold_template
engine = options[:template_engine]
copy_file "_form.html.#{engine}", "lib/templates/#{engine}/scaffold/_form.html.#{engine}"
end
def show_readme
if behavior == :invoke && options.bootstrap?
readme "README"
end
end
end
end
end
================================================
FILE: lib/generators/simple_form/templates/README
================================================
===============================================================================
Be sure to have a copy of the Bootstrap stylesheet available on your
application, you can get it on http://getbootstrap.com/.
For usage examples and documentation, see the example app:
https://github.com/heartcombo/simple_form-bootstrap
===============================================================================
================================================
FILE: lib/generators/simple_form/templates/_form.html.erb
================================================
<%# frozen_string_literal: true %>
<%%= simple_form_for(@<%= singular_table_name %>) do |f| %>
<%%= f.error_notification %>
<%%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<%- attributes.each do |attribute| -%>
<%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %>
<%- end -%>
<%%= f.button :submit %>
<%% end %>
================================================
FILE: lib/generators/simple_form/templates/_form.html.haml
================================================
-# frozen_string_literal: true
= simple_form_for(@<%= singular_table_name %>) do |f|
= f.error_notification
= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?
.form-inputs
<%- attributes.each do |attribute| -%>
= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>
<%- end -%>
.form-actions
= f.button :submit
================================================
FILE: lib/generators/simple_form/templates/_form.html.slim
================================================
= simple_form_for(@<%= singular_table_name %>) do |f|
= f.error_notification
= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?
.form-inputs
<%- attributes.each do |attribute| -%>
= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>
<%- end -%>
.form-actions
= f.button :submit
================================================
FILE: lib/generators/simple_form/templates/config/initializers/simple_form.rb
================================================
# frozen_string_literal: true
#
# Uncomment this and change the path if necessary to include your own
# components.
# See https://github.com/heartcombo/simple_form#custom-components to know
# more about custom components.
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
#
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Wrappers are used by the form builder to generate a
# complete input. You can remove any component from the
# wrapper, change the order or even add your own to the
# stack. The options given below are used to wrap the
# whole input.
config.wrappers :default, class: :input,
hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|
## Extensions enabled by default
# Any of these extensions can be disabled for a
# given input by passing: `f.input EXTENSION_NAME => false`.
# You can make any of these extensions optional by
# renaming `b.use` to `b.optional`.
# Determines whether to use HTML5 (:email, :url, ...)
# and required attributes
b.use :html5
# Calculates placeholders automatically from I18n
# You can also pass a string as f.input placeholder: "Placeholder"
b.use :placeholder
## Optional extensions
# They are disabled unless you pass `f.input EXTENSION_NAME => true`
# to the input. If so, they will retrieve the values from the model
# if any exists. If you want to enable any of those
# extensions by default, you can change `b.optional` to `b.use`.
# Calculates maxlength from length validations for string inputs
# and/or database column lengths
b.optional :maxlength
# Calculate minlength from length validations for string inputs
b.optional :minlength
# Calculates pattern from format validations for string inputs
b.optional :pattern
# Calculates min and max from length validations for numeric inputs
b.optional :min_max
# Calculates readonly automatically from readonly attributes
b.optional :readonly
## Inputs
# b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :label_input
b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error }
## full_messages_for
# If you want to display the full error message for the attribute, you can
# use the component :full_error, like:
#
# b.use :full_error, wrap_with: { tag: :span, class: :error }
end
# The default wrapper to be used by the FormBuilder.
config.default_wrapper = :default
# Define the way to render check boxes / radio buttons with labels.
# Defaults to :nested for bootstrap config.
# inline: input + label
# nested: label > input
config.boolean_style = :nested
# Default class for buttons
config.button_class = 'btn'
# Method used to tidy up errors. Specify any Rails Array method.
# :first lists the first message for each field.
# Use :to_sentence to list all errors for each field.
# config.error_method = :first
# Default tag used for error notification helper.
config.error_notification_tag = :div
# CSS class to add for error notification helper.
config.error_notification_class = 'error_notification'
# Series of attempts to detect a default label method for collection.
# config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
# Series of attempts to detect a default value method for collection.
# config.collection_value_methods = [ :id, :to_s ]
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
# config.collection_wrapper_tag = nil
# You can define the class to use on all collection wrappers. Defaulting to none.
# config.collection_wrapper_class = nil
# You can wrap each item in a collection of radio/check boxes with a tag,
# defaulting to :span.
# config.item_wrapper_tag = :span
# You can define a class to use in all item wrappers. Defaulting to none.
# config.item_wrapper_class = nil
# How the label text should be generated altogether with the required text.
# config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" }
# You can define the class to use on all labels. Default is nil.
# config.label_class = nil
# You can define the default class to be used on forms. Can be overridden
# with `html: { :class }`. Defaulting to none.
# config.default_form_class = nil
# You can define which elements should obtain additional classes
# config.generate_additional_classes_for = [:wrapper, :label, :input]
# Whether attributes are required by default (or not). Default is true.
# config.required_by_default = true
# Tell browsers whether to use the native HTML5 validations (novalidate form option).
# These validations are enabled in SimpleForm's internal config but disabled by default
# in this configuration, which is recommended due to some quirks from different browsers.
# To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations,
# change this configuration to true.
config.browser_validations = false
# Custom mappings for input types. This should be a hash containing a regexp
# to match as key, and the input type that will be used when the field name
# matches the regexp as value.
# config.input_mappings = { /count/ => :integer }
# Custom wrappers for input types. This should be a hash containing an input
# type as key and the wrapper that will be used for all inputs with specified type.
# config.wrapper_mappings = { string: :prepend }
# Namespaces where SimpleForm should look for custom input classes that
# override default inputs.
# config.custom_inputs_namespaces << "CustomInputs"
# Default priority for time_zone inputs.
# config.time_zone_priority = nil
# Default priority for country inputs.
# config.country_priority = nil
# When false, do not use translations for labels.
# config.translate_labels = true
# Automatically discover new inputs in Rails' autoload path.
# config.inputs_discovery = true
# Cache SimpleForm inputs discovery
# config.cache_discovery = !Rails.env.development?
# Default class for inputs
# config.input_class = nil
# Define the default class of the input wrapper of the boolean input.
config.boolean_label_class = 'checkbox'
# Defines if the default input wrapper class should be included in radio
# collection wrappers.
# config.include_default_input_wrapper_class = true
# Defines which i18n scope will be used in Simple Form.
# config.i18n_scope = 'simple_form'
# Defines validation classes to the input_field. By default it's nil.
# config.input_field_valid_class = 'is-valid'
# config.input_field_error_class = 'is-invalid'
end
================================================
FILE: lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb
================================================
# frozen_string_literal: true
# These defaults are defined and maintained by the community at
# https://github.com/heartcombo/simple_form-bootstrap
# Please submit feedback, changes and tests only there.
# Uncomment this and change the path if necessary to include your own
# components.
# See https://github.com/heartcombo/simple_form#custom-components
# to know more about custom components.
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Default class for buttons
config.button_class = 'btn'
# Define the default class of the input wrapper of the boolean input.
config.boolean_label_class = 'form-check-label'
# How the label text should be generated altogether with the required text.
config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" }
# Define the way to render check boxes / radio buttons with labels.
config.boolean_style = :inline
# You can wrap each item in a collection of radio/check boxes with a tag
config.item_wrapper_tag = :div
# Defines if the default input wrapper class should be included in radio
# collection wrappers.
config.include_default_input_wrapper_class = false
# CSS class to add for error notification helper.
config.error_notification_class = 'alert alert-danger'
# Method used to tidy up errors. Specify any Rails Array method.
# :first lists the first message for each field.
# :to_sentence to list all errors for each field.
config.error_method = :to_sentence
# add validation classes to `input_field`
config.input_field_error_class = 'is-invalid'
config.input_field_valid_class = 'is-valid'
# vertical forms
#
# vertical default_wrapper
config.wrappers :vertical_form, class: 'mb-3' 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: 'form-label'
b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :full_error, wrap_with: { class: 'invalid-feedback' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# vertical input for boolean
config.wrappers :vertical_boolean, tag: 'fieldset', class: 'mb-3' do |b|
b.use :html5
b.optional :readonly
b.wrapper :form_check_wrapper, class: 'form-check' do |bb|
bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
bb.use :label, class: 'form-check-label'
bb.use :full_error, wrap_with: { class: 'invalid-feedback' }
bb.use :hint, wrap_with: { class: 'form-text' }
end
end
# vertical input for radio buttons and check boxes
config.wrappers :vertical_collection, item_wrapper_class: 'form-check', item_label_class: 'form-check-label', tag: 'fieldset', class: 'mb-3' do |b|
b.use :html5
b.optional :readonly
b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
ba.use :label_text
end
b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :full_error, wrap_with: { class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# vertical input for inline radio buttons and check boxes
config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', item_label_class: 'form-check-label', tag: 'fieldset', class: 'mb-3' do |b|
b.use :html5
b.optional :readonly
b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|
ba.use :label_text
end
b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :full_error, wrap_with: { class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# vertical file input
config.wrappers :vertical_file, class: 'mb-3' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :readonly
b.use :label, class: 'form-label'
b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :full_error, wrap_with: { class: 'invalid-feedback' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# vertical select input
config.wrappers :vertical_select, class: 'mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'form-label'
b.use :input, class: 'form-select', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :full_error, wrap_with: { class: 'invalid-feedback' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# vertical multi select
config.wrappers :vertical_multi_select, class: 'mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'form-label'
b.wrapper class: 'd-flex flex-row justify-content-between align-items-center' do |ba|
ba.use :input, class: 'form-select mx-1', error_class: 'is-invalid', valid_class: 'is-valid'
end
b.use :full_error, wrap_with: { class: 'invalid-feedback d-block' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# vertical range input
config.wrappers :vertical_range, class: 'mb-3' do |b|
b.use :html5
b.use :placeholder
b.optional :readonly
b.optional :step
b.use :label, class: 'form-label'
b.use :input, class: 'form-range', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :full_error, wrap_with: { class: 'invalid-feedback' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# horizontal forms
#
# horizontal default_wrapper
config.wrappers :horizontal_form, class: 'row mb-3' 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 col-form-label'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# horizontal input for boolean
config.wrappers :horizontal_boolean, class: 'row mb-3' do |b|
b.use :html5
b.optional :readonly
b.wrapper :grid_wrapper, class: 'col-sm-9 offset-sm-3' do |wr|
wr.wrapper :form_check_wrapper, class: 'form-check' do |bb|
bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
bb.use :label, class: 'form-check-label'
bb.use :full_error, wrap_with: { class: 'invalid-feedback' }
bb.use :hint, wrap_with: { class: 'form-text' }
end
end
end
# horizontal input for radio buttons and check boxes
config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', item_label_class: 'form-check-label', class: 'row mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label pt-0'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# horizontal input for inline radio buttons and check boxes
config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', item_label_class: 'form-check-label', class: 'row mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label pt-0'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# horizontal file input
config.wrappers :horizontal_file, class: 'row mb-3' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# horizontal select input
config.wrappers :horizontal_select, class: 'row mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-select', error_class: 'is-invalid', valid_class: 'is-valid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# horizontal multi select
config.wrappers :horizontal_multi_select, class: 'row mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.wrapper class: 'd-flex flex-row justify-content-between align-items-center' do |bb|
bb.use :input, class: 'form-select mx-1', error_class: 'is-invalid', valid_class: 'is-valid'
end
ba.use :full_error, wrap_with: { class: 'invalid-feedback d-block' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# horizontal range input
config.wrappers :horizontal_range, class: 'row mb-3' do |b|
b.use :html5
b.use :placeholder
b.optional :readonly
b.optional :step
b.use :label, class: 'col-sm-3 col-form-label pt-0'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-range', error_class: 'is-invalid', valid_class: 'is-valid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
# inline forms
#
# inline default_wrapper
config.wrappers :inline_form, class: 'col-12' 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: 'visually-hidden'
b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :error, wrap_with: { class: 'invalid-feedback' }
b.optional :hint, wrap_with: { class: 'form-text' }
end
# inline input for boolean
config.wrappers :inline_boolean, class: 'col-12' do |b|
b.use :html5
b.optional :readonly
b.wrapper :form_check_wrapper, class: 'form-check' do |bb|
bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
bb.use :label, class: 'form-check-label'
bb.use :error, wrap_with: { class: 'invalid-feedback' }
bb.optional :hint, wrap_with: { class: 'form-text' }
end
end
# bootstrap custom forms
#
# custom input switch for boolean
config.wrappers :custom_boolean_switch, class: 'mb-3' do |b|
b.use :html5
b.optional :readonly
b.wrapper :form_check_wrapper, tag: 'div', class: 'form-check form-switch' do |bb|
bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'
bb.use :label, class: 'form-check-label'
bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
bb.use :hint, wrap_with: { class: 'form-text' }
end
end
# Input Group - custom component
# see example app and config at https://github.com/heartcombo/simple_form-bootstrap
config.wrappers :input_group, class: 'mb-3' 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: 'form-label'
b.wrapper :input_group_tag, class: 'input-group' do |ba|
ba.optional :prepend
ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
ba.optional :append
ba.use :full_error, wrap_with: { class: 'invalid-feedback' }
end
b.use :hint, wrap_with: { class: 'form-text' }
end
# Floating Labels form
#
# floating labels default_wrapper
config.wrappers :floating_labels_form, class: 'form-floating mb-3' 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 :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :label
b.use :full_error, wrap_with: { class: 'invalid-feedback' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# custom multi select
config.wrappers :floating_labels_select, class: 'form-floating mb-3' do |b|
b.use :html5
b.optional :readonly
b.use :input, class: 'form-select', error_class: 'is-invalid', valid_class: 'is-valid'
b.use :label
b.use :full_error, wrap_with: { class: 'invalid-feedback' }
b.use :hint, wrap_with: { class: 'form-text' }
end
# The default wrapper to be used by the FormBuilder.
config.default_wrapper = :vertical_form
# Custom wrappers for input types. This should be a hash containing an input
# type as key and the wrapper that will be used for all inputs with specified type.
config.wrapper_mappings = {
boolean: :vertical_boolean,
check_boxes: :vertical_collection,
date: :vertical_multi_select,
datetime: :vertical_multi_select,
file: :vertical_file,
radio_buttons: :vertical_collection,
range: :vertical_range,
time: :vertical_multi_select,
select: :vertical_select
}
end
================================================
FILE: lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb
================================================
# frozen_string_literal: true
#
# Uncomment this and change the path if necessary to include your own
# components.
# See https://github.com/heartcombo/simple_form#custom-components to know
# more about custom components.
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
#
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Don't forget to edit this file to adapt it to your needs (specially
# all the grid-related classes)
#
# Please note that hints are commented out by default since Foundation
# doesn't provide styles for hints. You will need to provide your own CSS styles for hints.
# Uncomment them to enable hints.
config.wrappers :vertical_form, class: :input, hint_class: :field_with_hint, error_class: :error, valid_class: :valid 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_input
b.use :error, wrap_with: { tag: :small, class: :error }
# b.use :hint, wrap_with: { tag: :span, class: :hint }
end
config.wrappers :horizontal_form, tag: 'div', class: 'row', hint_class: :field_with_hint, error_class: :error, valid_class: :valid 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.wrapper :label_wrapper, tag: :div, class: 'small-3 columns' do |ba|
ba.use :label, class: 'text-right inline'
end
b.wrapper :right_input_wrapper, tag: :div, class: 'small-9 columns' do |ba|
ba.use :input
ba.use :error, wrap_with: { tag: :small, class: :error }
# ba.use :hint, wrap_with: { tag: :span, class: :hint }
end
end
config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'row' do |b|
b.use :html5
b.optional :readonly
b.wrapper :container_wrapper, tag: 'div', class: 'small-offset-3 small-9 columns' do |ba|
ba.wrapper tag: 'label', class: 'checkbox' do |bb|
bb.use :input
bb.use :label_text
end
ba.use :error, wrap_with: { tag: :small, class: :error }
# ba.use :hint, wrap_with: { tag: :span, class: :hint }
end
end
# Foundation does not provide a way to handle inline forms
# This wrapper can be used to create an inline form
# by hiding that labels on every screen sizes ('hidden-for-small-up').
#
# Note that you need to adapt this wrapper to your needs. If you need a 4
# columns form then change the wrapper class to 'small-3', if you need
# only two use 'small-6' and so on.
config.wrappers :inline_form, tag: 'div', class: 'column small-4', hint_class: :field_with_hint, error_class: :error, valid_class: :valid 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: 'hidden-for-small-up'
b.use :input
b.use :error, wrap_with: { tag: :small, class: :error }
# b.use :hint, wrap_with: { tag: :span, class: :hint }
end
# Examples of use:
# - wrapper_html: {class: 'row'}, custom_wrapper_html: {class: 'column small-12'}
# - custom_wrapper_html: {class: 'column small-3 end'}
config.wrappers :customizable_wrapper, tag: 'div', error_class: :error, valid_class: :valid do |b|
b.use :html5
b.optional :readonly
b.wrapper :custom_wrapper, tag: :div do |ba|
ba.use :label_input
end
b.use :error, wrap_with: { tag: :small, class: :error }
# b.use :hint, wrap_with: { tag: :span, class: :hint }
end
# CSS class for buttons
config.button_class = 'button'
# Set this to div to make the checkbox and radio properly work
# otherwise simple_form adds a label tag instead of a div around
# the nested label
config.item_wrapper_tag = :div
# CSS class to add for error notification helper.
config.error_notification_class = 'alert-box alert'
# The default wrapper to be used by the FormBuilder.
config.default_wrapper = :vertical_form
# Defines validation classes to the input_field. By default it's nil.
# config.input_field_valid_class = 'is-valid'
# config.input_field_error_class = 'is-invalid'
end
================================================
FILE: lib/generators/simple_form/templates/config/locales/simple_form.en.yml
================================================
en:
simple_form:
"yes": 'Yes'
"no": 'No'
required:
text: 'required'
mark: '*'
# You can uncomment the line below if you need to overwrite the whole required html.
# When using html, text and mark won't be used.
# html: '* '
error_notification:
default_message: "Please review the problems below:"
# Examples
# labels:
# defaults:
# password: 'Password'
# user:
# new:
# email: 'E-mail to sign in.'
# edit:
# email: 'E-mail.'
# hints:
# defaults:
# username: 'User name to sign in.'
# password: 'No special characters, please.'
# include_blanks:
# defaults:
# age: 'Rather not say'
# prompts:
# defaults:
# age: 'Select your age'
================================================
FILE: lib/simple_form/action_view_extensions/builder.rb
================================================
# frozen_string_literal: true
module SimpleForm
module ActionViewExtensions
# A collection of methods required by simple_form but added to rails default form.
# This means that you can use such methods outside simple_form context.
module Builder
# Wrapper for using SimpleForm inside a default rails form.
# Example:
#
# form_for @user do |f|
# f.simple_fields_for :posts do |posts_form|
# # Here you have all simple_form methods available
# posts_form.input :title
# end
# end
def simple_fields_for(*args, &block)
options = args.extract_options!
options[:wrapper] = self.options[:wrapper] if options[:wrapper].nil?
options[:defaults] ||= self.options[:defaults]
options[:wrapper_mappings] ||= self.options[:wrapper_mappings]
if self.class < ActionView::Helpers::FormBuilder
options[:builder] ||= self.class
else
options[:builder] ||= SimpleForm::FormBuilder
end
fields_for(*args, options, &block)
end
end
end
end
module ActionView::Helpers
class FormBuilder
include SimpleForm::ActionViewExtensions::Builder
end
end
================================================
FILE: lib/simple_form/action_view_extensions/form_helper.rb
================================================
# frozen_string_literal: true
module SimpleForm
module ActionViewExtensions
# This module creates SimpleForm wrappers around default form_for and fields_for.
#
# Example:
#
# simple_form_for @user do |f|
# f.input :name, hint: 'My hint'
# end
#
module FormHelper
def simple_form_for(record, options = {}, &block)
options[:builder] ||= SimpleForm::FormBuilder
options[:html] ||= {}
unless options[:html].key?(:novalidate)
options[:html][:novalidate] = !SimpleForm.browser_validations
end
if options[:html].key?(:class)
options[:html][:class] = [SimpleForm.form_class, options[:html][:class]].compact
else
options[:html][:class] = [SimpleForm.form_class, SimpleForm.default_form_class, simple_form_css_class(record, options)].compact
end
with_simple_form_field_error_proc do
form_for(record, options, &block)
end
end
def simple_fields_for(record_name, record_object = nil, options = {}, &block)
options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
options[:builder] ||= SimpleForm::FormBuilder
with_simple_form_field_error_proc do
fields_for(record_name, record_object, options, &block)
end
end
private
def with_simple_form_field_error_proc
default_field_error_proc = ::ActionView::Base.field_error_proc
begin
::ActionView::Base.field_error_proc = SimpleForm.field_error_proc
yield
ensure
::ActionView::Base.field_error_proc = default_field_error_proc
end
end
def simple_form_css_class(record, options)
html_options = options[:html]
as = options[:as]
if html_options.key?(:class)
html_options[:class]
elsif record.is_a?(String) || record.is_a?(Symbol)
as || record
else
record = record.last if record.is_a?(Array)
action = record.respond_to?(:persisted?) && record.persisted? ? :edit : :new
as ? "#{action}_#{as}" : dom_class(record, action)
end
end
end
end
end
ActiveSupport.on_load(:action_view) do
include SimpleForm::ActionViewExtensions::FormHelper
end
================================================
FILE: lib/simple_form/components/errors.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
module Errors
def error(wrapper_options = nil)
error_text if has_errors?
end
def full_error(wrapper_options = nil)
full_error_text if options[:error] != false && has_errors?
end
def has_errors?
object_with_errors? || !object && has_custom_error?
end
def has_value?
object && object.respond_to?(attribute_name) && object.send(attribute_name).present?
end
def valid?
!has_errors? && has_value?
end
protected
def error_text
text = has_custom_error? ? options[:error] : errors.send(error_method)
"#{html_escape(options[:error_prefix])} #{html_escape(text)}".lstrip.html_safe
end
def full_error_text
has_custom_error? ? options[:error] : full_errors.send(error_method)
end
def object_with_errors?
object && object.respond_to?(:errors) && errors.present?
end
def error_method
options[:error_method] || SimpleForm.error_method
end
def errors
@errors ||= (errors_on_attribute + errors_on_association).compact
end
def full_errors
@full_errors ||= (full_errors_on_attribute + full_errors_on_association).compact
end
def errors_on_attribute
object.errors[attribute_name] || []
end
def full_errors_on_attribute
object.errors.full_messages_for(attribute_name)
end
def errors_on_association
reflection ? object.errors[reflection.name] : []
end
def full_errors_on_association
reflection ? object.errors.full_messages_for(reflection.name) : []
end
def has_custom_error?
options[:error].is_a?(String)
end
end
end
end
================================================
FILE: lib/simple_form/components/hints.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
# Needs to be enabled in order to do automatic lookups.
module Hints
def hint(wrapper_options = nil)
@hint ||= begin
hint = options[:hint]
if hint.is_a?(String)
html_escape(hint)
else
content = translate_from_namespace(:hints)
content.html_safe if content
end
end
end
def has_hint?
options[:hint] != false && hint.present?
end
end
end
end
================================================
FILE: lib/simple_form/components/html5.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
module HTML5
def initialize(*)
@html5 = false
end
def html5(wrapper_options = nil)
@html5 = true
input_html_options[:required] = input_html_required_option
input_html_options[:'aria-invalid'] = has_errors? || nil
nil
end
def html5?
@html5
end
def input_html_required_option
!options[:required].nil? ? required_field? : has_required?
end
def has_required?
# We need to check browser_validations because
# some browsers are still checking required even
# if novalidate was given.
required_field? && SimpleForm.browser_validations
end
end
end
end
================================================
FILE: lib/simple_form/components/label_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
module LabelInput
extend ActiveSupport::Concern
included do
include SimpleForm::Components::Labels
end
def label_input(wrapper_options = nil)
if options[:label] == false
deprecated_component(:input, wrapper_options)
else
deprecated_component(:label, wrapper_options) + deprecated_component(:input, wrapper_options)
end
end
private
def deprecated_component(namespace, wrapper_options)
method = method(namespace)
if method.arity.zero?
SimpleForm.deprecator.warn(SimpleForm::CUSTOM_INPUT_DEPRECATION_WARN % { name: namespace })
method.call
else
method.call(wrapper_options)
end
end
end
end
end
================================================
FILE: lib/simple_form/components/labels.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
module Labels
extend ActiveSupport::Concern
module ClassMethods #:nodoc:
def translate_required_html
I18n.t(:"required.html", scope: i18n_scope, default:
%(#{translate_required_mark} )
)
end
def translate_required_text
I18n.t(:"required.text", scope: i18n_scope, default: 'required')
end
def translate_required_mark
I18n.t(:"required.mark", scope: i18n_scope, default: '*')
end
private
def i18n_scope
SimpleForm.i18n_scope
end
end
def label(wrapper_options = nil)
label_options = merge_wrapper_options(label_html_options, wrapper_options)
if generate_label_for_attribute?
@builder.label(label_target, label_text, label_options)
else
template.label_tag(nil, label_text, label_options)
end
end
def label_text(wrapper_options = nil)
label_text = options[:label_text] || SimpleForm.label_text
label_text.call(html_escape(raw_label_text), required_label_text, options[:label].present?).strip.html_safe
end
def label_target
attribute_name
end
def label_html_options
label_html_classes = SimpleForm.additional_classes_for(:label) {
[input_type, required_class, disabled_class, SimpleForm.label_class].compact
}
label_options = html_options_for(:label, label_html_classes)
if options.key?(:input_html) && options[:input_html].key?(:id)
label_options[:for] = options[:input_html][:id]
end
label_options
end
protected
def raw_label_text #:nodoc:
options[:label] || label_translation
end
# Default required text when attribute is required.
def required_label_text #:nodoc:
required_field? ? self.class.translate_required_html.dup : ''
end
# First check labels translation and then human attribute name.
def label_translation #:nodoc:
if SimpleForm.translate_labels && (translated_label = translate_from_namespace(:labels))
translated_label
elsif object.class.respond_to?(:human_attribute_name)
object.class.human_attribute_name(reflection_or_attribute_name.to_s, { base: object })
else
attribute_name.to_s.humanize
end
end
def generate_label_for_attribute?
true
end
end
end
end
================================================
FILE: lib/simple_form/components/maxlength.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
# Needs to be enabled in order to do automatic lookups.
module Maxlength
def maxlength(wrapper_options = nil)
input_html_options[:maxlength] ||= maximum_length_from_validation || limit
nil
end
private
def maximum_length_from_validation
maxlength = options[:maxlength]
if maxlength.is_a?(String) || maxlength.is_a?(Integer)
maxlength
else
length_validator = find_length_validator
maximum_length_value_from(length_validator)
end
end
def find_length_validator
find_validator(:length)
end
def maximum_length_value_from(length_validator)
if length_validator
value = length_validator.options[:is] || length_validator.options[:maximum]
resolve_validator_value(value)
end
end
end
end
end
================================================
FILE: lib/simple_form/components/min_max.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
module MinMax
def min_max(wrapper_options = nil)
if numeric_validator = find_numericality_validator
validator_options = numeric_validator.options
input_html_options[:min] ||= minimum_value(validator_options)
input_html_options[:max] ||= maximum_value(validator_options)
end
nil
end
private
def integer?
input_type == :integer
end
def minimum_value(validator_options)
if integer? && validator_options.key?(:greater_than)
resolve_validator_value(validator_options[:greater_than]) + 1
else
resolve_validator_value(validator_options[:greater_than_or_equal_to])
end
end
def maximum_value(validator_options)
if integer? && validator_options.key?(:less_than)
resolve_validator_value(validator_options[:less_than]) - 1
else
resolve_validator_value(validator_options[:less_than_or_equal_to])
end
end
def find_numericality_validator
find_validator(:numericality)
end
end
end
end
================================================
FILE: lib/simple_form/components/minlength.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
# Needs to be enabled in order to do automatic lookups.
module Minlength
def minlength(wrapper_options = nil)
input_html_options[:minlength] ||= minimum_length_from_validation
nil
end
private
def minimum_length_from_validation
minlength = options[:minlength]
if minlength.is_a?(String) || minlength.is_a?(Integer)
minlength
else
length_validator = find_length_validator
minimum_length_value_from(length_validator)
end
end
def find_length_validator
find_validator(:length)
end
def minimum_length_value_from(length_validator)
if length_validator
value = length_validator.options[:is] || length_validator.options[:minimum]
resolve_validator_value(value)
end
end
end
end
end
================================================
FILE: lib/simple_form/components/pattern.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
# Needs to be enabled in order to do automatic lookups.
module Pattern
def pattern(wrapper_options = nil)
input_html_options[:pattern] ||= pattern_source
nil
end
private
def pattern_source
pattern = options[:pattern]
if pattern.is_a?(String)
pattern
elsif (pattern_validator = find_pattern_validator) && (with = pattern_validator.options[:with])
resolve_validator_value(with).source
end
end
def find_pattern_validator
find_validator(:format)
end
end
end
end
================================================
FILE: lib/simple_form/components/placeholders.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
# Needs to be enabled in order to do automatic lookups.
module Placeholders
def placeholder(wrapper_options = nil)
input_html_options[:placeholder] ||= placeholder_text
nil
end
def placeholder_text(wrapper_options = nil)
placeholder = options[:placeholder]
placeholder.is_a?(String) ? placeholder : translate_from_namespace(:placeholders)
end
end
end
end
================================================
FILE: lib/simple_form/components/readonly.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Components
# Needs to be enabled in order to do automatic lookups.
module Readonly
def readonly(wrapper_options = nil)
if readonly_attribute? && !has_readonly?
input_html_options[:readonly] ||= true
input_html_classes << :readonly
end
nil
end
private
def readonly_attribute?
object.class.respond_to?(:readonly_attributes) &&
object.persisted? &&
object.class.readonly_attributes.include?(attribute_name.to_s)
end
end
end
end
================================================
FILE: lib/simple_form/components.rb
================================================
# frozen_string_literal: true
module SimpleForm
# Components are a special type of helpers that can work on their own.
# For example, by using a component, it will automatically change the
# output under given circumstances without user input. For example,
# the disabled helper always need a disabled: true option given
# to the input in order to be enabled. On the other hand, things like
# hints can generate output automatically by doing I18n lookups.
module Components
extend ActiveSupport::Autoload
autoload :Errors
autoload :Hints
autoload :HTML5
autoload :LabelInput
autoload :Labels
autoload :MinMax
autoload :Maxlength
autoload :Minlength
autoload :Pattern
autoload :Placeholders
autoload :Readonly
end
end
================================================
FILE: lib/simple_form/error_notification.rb
================================================
# frozen_string_literal: true
module SimpleForm
class ErrorNotification
delegate :object, :object_name, :template, to: :@builder
def initialize(builder, options)
@builder = builder
@message = options.delete(:message)
@options = options
end
def render
if has_errors?
template.content_tag(error_notification_tag, error_message, html_options)
end
end
protected
def errors
object.errors
end
def has_errors?
object && object.respond_to?(:errors) && errors.present?
end
def error_message
(@message || translate_error_notification).html_safe
end
def error_notification_tag
SimpleForm.error_notification_tag
end
def html_options
@options[:class] = "#{SimpleForm.error_notification_class} #{@options[:class]}".strip
@options
end
def translate_error_notification
lookups = []
lookups << :"#{object_name}"
lookups << :default_message
lookups << "Please review the problems below:"
I18n.t(lookups.shift, scope: :"simple_form.error_notification", default: lookups)
end
end
end
================================================
FILE: lib/simple_form/form_builder.rb
================================================
# frozen_string_literal: true
require 'active_support/core_ext/object/deep_dup'
require 'simple_form/map_type'
require 'simple_form/tags'
module SimpleForm
class FormBuilder < ActionView::Helpers::FormBuilder
attr_reader :template, :object_name, :object, :wrapper
# When action is create or update, we still should use new and edit
ACTIONS = {
'create' => 'new',
'update' => 'edit'
}
ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
extend MapType
include SimpleForm::Inputs
map_type :text, :hstore, :json, :jsonb, to: SimpleForm::Inputs::TextInput
map_type :file, to: SimpleForm::Inputs::FileInput
map_type :string, :email, :search, :tel, :url, :uuid, :citext, to: SimpleForm::Inputs::StringInput
map_type :password, to: SimpleForm::Inputs::PasswordInput
map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
map_type :range, to: SimpleForm::Inputs::RangeInput
map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
map_type :rich_text_area, to: SimpleForm::Inputs::RichTextAreaInput
map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
map_type :boolean, to: SimpleForm::Inputs::BooleanInput
map_type :hidden, to: SimpleForm::Inputs::HiddenInput
def self.discovery_cache
@discovery_cache ||= {}
end
def initialize(*) #:nodoc:
super
@object = convert_to_model(@object)
@defaults = options[:defaults]
@wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
end
# Basic input helper, combines all components in the stack to generate
# input html based on options the user define and some guesses through
# database column information. By default a call to input will generate
# label + input + hint (when defined) + errors (when exists), and all can
# be configured inside a wrapper html.
#
# If a block is given, the contents of the block will replace the input
# field that would otherwise be generated automatically. The content will
# be given a label and wrapper div to make it consistent with the other
# elements in the form.
#
# == Examples
#
# # Imagine @user has error "can't be blank" on name
# simple_form_for @user do |f|
# f.input :name, hint: 'My hint'
# end
#
# This is the output html (only the input portion, not the form):
#
#
# * Super User Name!
#
#
# My hint
# can't be blank
#
# Each database type will render a default input, based on some mappings and
# heuristic to determine which is the best option.
#
# You have some options for the input to enable/disable some functions:
#
# as: allows you to define the input type you want, for instance you
# can use it to generate a text field for a date column.
#
# required: defines whether this attribute is required or not. True
# by default.
#
# The fact SimpleForm is built in components allow the interface to be unified.
# So, for instance, if you need to disable :hint for a given input, you can pass
# hint: false. The same works for :error, :label and :wrapper.
#
# Besides the html for any component can be changed. So, if you want to change
# the label html you just need to give a hash to :label_html. To configure the
# input html, supply :input_html instead and so on.
#
# == Options
#
# Some inputs, as datetime, time and select allow you to give extra options, like
# prompt and/or include blank. Such options are given in plainly:
#
# f.input :created_at, include_blank: true
#
# == Collection
#
# When playing with collections (:radio_buttons, :check_boxes and :select
# inputs), you have three extra options:
#
# collection: use to determine the collection to generate the radio or select
#
# label_method: the method to apply on the array collection to get the label
#
# value_method: the method to apply on the array collection to get the value
#
# == Priority
#
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
#
def input(attribute_name, options = {}, &block)
options = @defaults.deep_dup.deep_merge(options) if @defaults
input = find_input(attribute_name, options, &block)
wrapper = find_wrapper(input.input_type, options)
wrapper.render input
end
alias :attribute :input
# Creates a input tag for the given attribute. All the given options
# are sent as :input_html.
#
# == Examples
#
# simple_form_for @user do |f|
# f.input_field :name
# end
#
# This is the output html (only the input portion, not the form):
#
#
#
# It also support validation classes once it is configured.
#
# # config/initializers/simple_form.rb
# SimpleForm.setup do |config|
# config.input_field_valid_class = 'is-valid'
# config.input_field_error_class = 'is-invalid'
# end
#
# simple_form_for @user do |f|
# f.input_field :name
# end
#
# When the validation happens, the input will be rendered with
# the class configured according to the validation:
#
# - when the input is valid:
#
#
#
# - when the input is invalid:
#
#
#
def input_field(attribute_name, options = {})
components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
options = options.dup
options[:input_html] = options.except(:as, :boolean_style, :collection, :disabled, :label_method, :value_method, :prompt, *components)
options = @defaults.deep_dup.deep_merge(options) if @defaults
input = find_input(attribute_name, options)
wrapper = find_wrapper(input.input_type, options)
components = build_input_field_components(components.push(:input))
SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
end
# Helper for dealing with association selects/radios, generating the
# collection automatically. It's just a wrapper to input, so all options
# supported in input are also supported by association. Some extra options
# can also be given:
#
# == Examples
#
# simple_form_for @user do |f|
# f.association :company # Company.all
# end
#
# f.association :company, collection: Company.all(order: 'name')
# # Same as using :order option, but overriding collection
#
# == Block
#
# When a block is given, association simple behaves as a proxy to
# simple_fields_for:
#
# f.association :company do |c|
# c.input :name
# c.input :type
# end
#
# From the options above, only :collection can also be supplied.
#
# Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
#
def association(association, options = {}, &block)
options = options.dup
return simple_fields_for(*[association,
options.delete(:collection), options].compact, &block) if block_given?
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
reflection = find_association_reflection(association)
raise "Association #{association.inspect} not found" unless reflection
options[:as] ||= :select
options[:collection] ||= fetch_association_collection(reflection, options)
attribute = build_association_attribute(reflection, association, options)
input(attribute, options.merge(reflection: reflection))
end
# Creates a button:
#
# form_for @user do |f|
# f.button :submit
# end
#
# It just acts as a proxy to method name given. We also alias original Rails
# button implementation (3.2 forward (to delegate to the original when
# calling `f.button :button`.
#
alias_method :button_button, :button
def button(type, *args, &block)
options = args.extract_options!.dup
options[:class] = [SimpleForm.button_class, options[:class]].compact
args << options
if respond_to?(:"#{type}_button")
send(:"#{type}_button", *args, &block)
else
send(type, *args, &block)
end
end
# Creates an error tag based on the given attribute, only when the attribute
# contains errors. All the given options are sent as :error_html.
#
# == Examples
#
# f.error :name
# f.error :name, id: "cool_error"
#
def error(attribute_name, options = {})
options = options.dup
options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
wrapper.find(:error).
render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
end
# Return the error but also considering its name. This is used
# when errors for a hidden field need to be shown.
#
# == Examples
#
# f.full_error :token #=> Token is invalid
#
def full_error(attribute_name, options = {})
options = options.dup
options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
object.class.human_attribute_name(attribute_name.to_s, { base: object })
else
attribute_name.to_s.humanize
end
error(attribute_name, options)
end
# Creates a hint tag for the given attribute. Accepts a symbol indicating
# an attribute for I18n lookup or a string. All the given options are sent
# as :hint_html.
#
# == Examples
#
# f.hint :name # Do I18n lookup
# f.hint :name, id: "cool_hint"
# f.hint "Don't forget to accept this"
#
def hint(attribute_name, options = {})
options = options.dup
options[:hint_html] = options.except(:hint_tag, :hint)
if attribute_name.is_a?(String)
options[:hint] = attribute_name
attribute_name, column, input_type = nil, nil, nil
else
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
end
wrapper.find(:hint).
render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
end
# Creates a default label tag for the given attribute. You can give a label
# through the :label option or using i18n. All the given options are sent
# as :label_html.
#
# == Examples
#
# f.label :name # Do I18n lookup
# f.label :name, "Name" # Same behavior as Rails, do not add required tag
# f.label :name, label: "Name" # Same as above, but adds required tag
#
# f.label :name, required: false
# f.label :name, id: "cool_label"
#
def label(attribute_name, *args)
return super if args.first.is_a?(String) || block_given?
options = args.extract_options!.dup
options[:label_html] = options.except(:label, :label_text, :required, :as)
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
end
# Creates an error notification message that only appears when the form object
# has some error. You can give a specific message with the :message option,
# otherwise it will look for a message using I18n. All other options given are
# passed straight as html options to the html tag.
#
# == Examples
#
# f.error_notification
# f.error_notification message: 'Something went wrong'
# f.error_notification id: 'user_error_message', class: 'form_error'
#
def error_notification(options = {})
SimpleForm::ErrorNotification.new(self, options).render
end
# Create a collection of radio inputs for the attribute. Basically this
# helper will create a radio input associated with a label for each
# text/value option in the collection, using value_method and text_method
# to convert these text/value. You can give a symbol or a proc to both
# value_method and text_method, that will be evaluated for each item in
# the collection.
#
# == Examples
#
# form_for @user do |f|
# f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
# end
#
#
# Yes
#
# No
#
# It is also possible to give a block that should generate the radio +
# label. To wrap the radio with the label, for instance:
#
# form_for @user do |f|
# f.collection_radio_buttons(
# :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
# ) do |b|
# b.label { b.radio_button + b.text }
# end
# end
#
# == Options
#
# Collection radio accepts some extra options:
#
# * checked => the value that should be checked initially.
#
# * disabled => the value or values that should be disabled. Accepts a single
# item or an array of items.
#
# * collection_wrapper_tag => the tag to wrap the entire collection.
#
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
#
# * item_wrapper_tag => the tag to wrap each item in the collection.
#
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
#
# * a block => to generate the label + radio or any other component.
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
SimpleForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
end
# Creates a collection of check boxes for each item in the collection,
# associated with a clickable label. Use value_method and text_method to
# convert items in the collection for use as text/value in check boxes.
# You can give a symbol or a proc to both value_method and text_method,
# that will be evaluated for each item in the collection.
#
# == Examples
#
# form_for @user do |f|
# f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
# end
#
#
#
# Yes
#
#
# No
#
# It is also possible to give a block that should generate the check box +
# label. To wrap the check box with the label, for instance:
#
# form_for @user do |f|
# f.collection_check_boxes(
# :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
# ) do |b|
# b.label { b.check_box + b.text }
# end
# end
#
# == Options
#
# Collection check box accepts some extra options:
#
# * checked => the value or values that should be checked initially. Accepts
# a single item or an array of items. It overrides existing associations.
#
# * disabled => the value or values that should be disabled. Accepts a single
# item or an array of items.
#
# * collection_wrapper_tag => the tag to wrap the entire collection.
#
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag. This option
# is ignored if the :collection_wrapper_tag option is blank.
#
# * item_wrapper_tag => the tag to wrap each item in the collection.
#
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
#
# * a block => to generate the label + check box or any other component.
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
SimpleForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
end
# Extract the model names from the object_name mess, ignoring numeric and
# explicit child indexes.
#
# Example:
#
# route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
# ["route", "blocks", "blocks_learning_object", "foo"]
#
def lookup_model_names #:nodoc:
@lookup_model_names ||= begin
child_index = options[:child_index]
names = object_name.to_s.scan(/(?!\d)\w+/).flatten
names.delete(child_index) if child_index
names.each { |name| name.gsub!('_attributes', '') }
names.freeze
end
end
# The action to be used in lookup.
def lookup_action #:nodoc:
@lookup_action ||= begin
action = template.controller && template.controller.action_name
return unless action
action = action.to_s
ACTIONS[action] || action
end
end
private
def fetch_association_collection(reflection, options)
options.fetch(:collection) do
relation = reflection.klass.all
if reflection.respond_to?(:scope) && reflection.scope
if reflection.scope.parameters.any?
relation = reflection.klass.instance_exec(object, &reflection.scope)
else
relation = reflection.klass.instance_exec(&reflection.scope)
end
else
order = reflection.options[:order]
conditions = reflection.options[:conditions]
conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
relation = relation.where(conditions) if relation.respond_to?(:where) && conditions.present?
relation = relation.order(order) if relation.respond_to?(:order)
end
relation
end
end
def build_association_attribute(reflection, association, options)
case reflection.macro
when :belongs_to
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
when :has_one
raise ArgumentError, ":has_one associations are not supported by f.association"
else
if options[:as] == :select || options[:as] == :grouped_select
html_options = options[:input_html] ||= {}
html_options[:multiple] = true unless html_options.key?(:multiple)
end
# Force the association to be preloaded for performance.
if options[:preload] != false && object.respond_to?(association)
target = object.send(association)
target.to_a if target.respond_to?(:to_a)
end
:"#{reflection.name.to_s.singularize}_ids"
end
end
# Find an input based on the attribute name.
def find_input(attribute_name, options = {}, &block)
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
if block_given?
SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
else
find_mapping(input_type).new(self, attribute_name, column, input_type, options)
end
end
# Attempt to guess the better input type given the defined options. By
# default always fallback to the user :as option, or to a :select when a
# collection is given.
def default_input_type(attribute_name, column, options)
return options[:as].to_sym if options[:as]
custom_type = find_custom_type(attribute_name.to_s) and return custom_type
return :select if options[:collection]
input_type = column.try(:type)
case input_type
when :timestamp
:datetime
when :string, :citext, nil
case attribute_name.to_s
when /(?:\b|\W|_)password(?:\b|\W|_)/ then :password
when /(?:\b|\W|_)time_zone(?:\b|\W|_)/ then :time_zone
when /(?:\b|\W|_)country(?:\b|\W|_)/ then :country
when /(?:\b|\W|_)email(?:\b|\W|_)/ then :email
when /(?:\b|\W|_)phone(?:\b|\W|_)/ then :tel
when /(?:\b|\W|_)url(?:\b|\W|_)/ then :url
else
file_method?(attribute_name) ? :file : (input_type || :string)
end
else
input_type
end
end
def find_custom_type(attribute_name)
SimpleForm.input_mappings.find { |match, type|
attribute_name =~ match
}.try(:last) if SimpleForm.input_mappings
end
# Internal: Try to discover whether an attribute corresponds to a file or not.
#
# Most upload Gems add some kind of attributes to the ActiveRecord's model they are included in.
# This method tries to guess if an attribute belongs to some of these Gems by checking the presence
# of their methods using `#respond_to?`.
#
# Note: This does not support multiple file upload inputs, as this is very application-specific.
#
# The order here was chosen based on the popularity of Gems:
#
# - `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0`
# - `remote_#{attribute_name}_url` - Refile >= `0.3.0` and CarrierWave >= `0.2.2`
# - `#{attribute_name}_attacher` - Refile >= `0.4.0` and Shrine >= `0.9.0`
# - `#{attribute_name}_file_name` - Paperclip ~> `2.0` (added for backwards compatibility)
#
# Returns a Boolean.
def file_method?(attribute_name)
@object.respond_to?("#{attribute_name}_attachment") ||
@object.respond_to?("#{attribute_name}_attachments") ||
@object.respond_to?("remote_#{attribute_name}_url") ||
@object.respond_to?("#{attribute_name}_attacher") ||
@object.respond_to?("#{attribute_name}_file_name")
end
def find_attribute_column(attribute_name)
if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
detected_type = @object.type_for_attribute(attribute_name.to_s)
# Some attributes like ActiveRecord::Encryption::EncryptedAttribute are detected
# as different type, in that case we need to use the original type
detected_type.respond_to?(:cast_type) ? detected_type.cast_type : detected_type
elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
@object.column_for_attribute(attribute_name)
end
end
def find_association_reflection(association)
if @object.class.respond_to?(:reflect_on_association)
@object.class.reflect_on_association(association)
end
end
# Attempts to find a mapping. It follows the following rules:
#
# 1) It tries to find a registered mapping, if succeeds:
# a) Try to find an alternative with the same name in the Object scope
# b) Or use the found mapping
# 2) If not, fallbacks to #{input_type}Input
# 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
def find_mapping(input_type)
discovery_cache[input_type] ||=
if mapping = self.class.mappings[input_type]
mapping_override(mapping) || mapping
else
camelized = "#{input_type.to_s.camelize}Input"
attempt_mapping_with_custom_namespace(camelized) ||
attempt_mapping(camelized, Object) ||
attempt_mapping(camelized, self.class) ||
raise("No input found for #{input_type}")
end
end
# Attempts to find a wrapper mapping. It follows the following rules:
#
# 1) It tries to find a wrapper for the current form
# 2) If not, it tries to find a config
def find_wrapper_mapping(input_type)
if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
options[:wrapper_mappings][input_type]
else
SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
end
end
def find_wrapper(input_type, options)
if name = options[:wrapper] || find_wrapper_mapping(input_type)
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
else
wrapper
end
end
# If cache_discovery is enabled, use the class level cache that persists
# between requests, otherwise use the instance one.
def discovery_cache
if SimpleForm.cache_discovery
self.class.discovery_cache
else
@discovery_cache ||= {}
end
end
def mapping_override(klass)
name = klass.name
if name =~ /^SimpleForm::Inputs/
input_name = name.split("::").last
attempt_mapping_with_custom_namespace(input_name) ||
attempt_mapping(input_name, Object)
end
end
def attempt_mapping(mapping, at)
return if SimpleForm.inputs_discovery == false && at == Object
begin
at.const_get(mapping)
rescue NameError => e
raise unless e.message.include?(mapping)
end
end
def attempt_mapping_with_custom_namespace(input_name)
SimpleForm.custom_inputs_namespaces.each do |namespace|
if (mapping = attempt_mapping(input_name, namespace.constantize))
return mapping
end
end
nil
end
def build_input_field_components(components)
components.map do |component|
if component == :input
SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
else
SimpleForm::Wrappers::Leaf.new(component)
end
end
end
def build_input_field_options
input_field_options = {}
valid_class = SimpleForm.input_field_valid_class
error_class = SimpleForm.input_field_error_class
if error_class.present?
input_field_options[:error_class] = error_class
end
if valid_class.present?
input_field_options[:valid_class] = valid_class
end
input_field_options
end
end
end
================================================
FILE: lib/simple_form/helpers/autofocus.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Helpers
module Autofocus
private
def has_autofocus?
options[:autofocus] == true
end
end
end
end
================================================
FILE: lib/simple_form/helpers/disabled.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Helpers
module Disabled
private
def has_disabled?
options[:disabled] == true
end
def disabled_class
:disabled if has_disabled?
end
end
end
end
================================================
FILE: lib/simple_form/helpers/readonly.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Helpers
module Readonly
private
def readonly_class
:readonly if has_readonly?
end
def has_readonly?
options[:readonly] == true
end
end
end
end
================================================
FILE: lib/simple_form/helpers/required.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Helpers
module Required
private
def required_field?
@required
end
def calculate_required
if !options[:required].nil?
options[:required]
elsif has_validators?
required_by_validators?
else
required_by_default?
end
end
def required_by_validators?
(attribute_validators + reflection_validators).any? { |v| v.kind == :presence && valid_validator?(v) }
end
def required_by_default?
SimpleForm.required_by_default
end
# Do not use has_required? because we want to add the class
# regardless of the required option.
def required_class
required_field? ? :required : :optional
end
end
end
end
================================================
FILE: lib/simple_form/helpers/validators.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Helpers
module Validators
def has_validators?
@has_validators ||= attribute_name && object.class.respond_to?(:validators_on)
end
private
def attribute_validators
object.class.validators_on(attribute_name)
end
def reflection_validators
reflection ? object.class.validators_on(reflection.name) : []
end
def valid_validator?(validator)
!conditional_validators?(validator) && action_validator_match?(validator)
end
def conditional_validators?(validator)
validator.options.include?(:if) || validator.options.include?(:unless)
end
def action_validator_match?(validator)
return true unless validator.options.include?(:on)
case validator.options[:on]
when :save
true
when :create
!object.persisted?
when :update
object.persisted?
end
end
def find_validator(kind)
attribute_validators.find { |v| v.kind == kind } if has_validators?
end
# Implements `ActiveModel::Validations::ResolveValue`, introduced by Rails 7.1.
# https://github.com/rails/rails/blob/v7.1.0/activemodel/lib/active_model/validations/resolve_value.rb
def resolve_validator_value(value)
case value
when Proc
if value.arity == 0
value.call
else
value.call(object)
end
when Symbol
object.send(value)
else
if value.respond_to?(:call)
value.call(object)
else
value
end
end
end
end
end
end
================================================
FILE: lib/simple_form/helpers.rb
================================================
# frozen_string_literal: true
module SimpleForm
# Helpers are made of several helpers that cannot be turned on automatically.
# For instance, disabled cannot be turned on automatically, it requires the
# user to explicitly pass the option disabled: true so it may work.
module Helpers
autoload :Autofocus, 'simple_form/helpers/autofocus'
autoload :Disabled, 'simple_form/helpers/disabled'
autoload :Readonly, 'simple_form/helpers/readonly'
autoload :Required, 'simple_form/helpers/required'
autoload :Validators, 'simple_form/helpers/validators'
end
end
================================================
FILE: lib/simple_form/inputs/base.rb
================================================
# frozen_string_literal: true
require 'active_support/core_ext/string/output_safety'
require 'action_view/helpers'
module SimpleForm
module Inputs
class Base
include ERB::Util
include ActionView::Helpers::TranslationHelper
include SimpleForm::Helpers::Autofocus
include SimpleForm::Helpers::Disabled
include SimpleForm::Helpers::Readonly
include SimpleForm::Helpers::Required
include SimpleForm::Helpers::Validators
include SimpleForm::Components::Errors
include SimpleForm::Components::Hints
include SimpleForm::Components::HTML5
include SimpleForm::Components::LabelInput
include SimpleForm::Components::Maxlength
include SimpleForm::Components::Minlength
include SimpleForm::Components::MinMax
include SimpleForm::Components::Pattern
include SimpleForm::Components::Placeholders
include SimpleForm::Components::Readonly
attr_reader :attribute_name, :column, :input_type, :reflection,
:options, :input_html_options, :input_html_classes, :html_classes
delegate :template, :object, :object_name, :lookup_model_names, :lookup_action, to: :@builder
class_attribute :default_options
self.default_options = {}
def self.enable(*keys)
options = self.default_options.dup
keys.each { |key| options.delete(key) }
self.default_options = options
end
def self.disable(*keys)
options = self.default_options.dup
keys.each { |key| options[key] = false }
self.default_options = options
end
# Always enabled.
enable :hint
# Usually disabled, needs to be enabled explicitly passing true as option.
disable :maxlength, :minlength, :placeholder, :pattern, :min_max
def initialize(builder, attribute_name, column, input_type, options = {})
super
options = options.dup
@builder = builder
@attribute_name = attribute_name
@column = column
@input_type = input_type
@reflection = options.delete(:reflection)
@options = options.reverse_merge!(self.class.default_options)
@required = calculate_required
# Notice that html_options_for receives a reference to input_html_classes.
# This means that classes added dynamically to input_html_classes will
# still propagate to input_html_options.
@html_classes = SimpleForm.additional_classes_for(:input) { additional_classes }
@input_html_classes = @html_classes.dup
input_html_classes = self.input_html_classes
if SimpleForm.input_class && input_html_classes.any?
input_html_classes << SimpleForm.input_class
end
@input_html_options = html_options_for(:input, input_html_classes).tap do |o|
o[:readonly] = true if has_readonly?
o[:disabled] = true if has_disabled?
o[:autofocus] = true if has_autofocus?
end
end
def input(wrapper_options = nil)
raise NotImplementedError
end
def input_options
options
end
def additional_classes
@additional_classes ||= [input_type, required_class, readonly_class, disabled_class].compact
end
def input_class
"#{lookup_model_names.join('_')}_#{reflection_or_attribute_name}"
end
private
def limit
if column
decimal_or_float? ? decimal_limit : column_limit
end
end
def column_limit
column.limit
end
# Add one for decimal point
def decimal_limit
column_limit && (column_limit + 1)
end
def decimal_or_float?
column.type == :float || column.type == :decimal
end
def nested_boolean_style?
options.fetch(:boolean_style, SimpleForm.boolean_style) == :nested
end
# Find reflection name when available, otherwise use attribute
def reflection_or_attribute_name
@reflection_or_attribute_name ||= reflection ? reflection.name : attribute_name
end
# Retrieve options for the given namespace from the options hash
def html_options_for(namespace, css_classes)
html_options = options[:"#{namespace}_html"]
html_options = html_options ? html_options.dup : {}
css_classes << html_options[:class] if html_options.key?(:class)
html_options[:class] = css_classes unless css_classes.empty?
html_options
end
# Lookup translations for the given namespace using I18n, based on object name,
# actual action and attribute name. Lookup priority as follows:
#
# simple_form.{namespace}.{model}.{action}.{attribute}
# simple_form.{namespace}.{model}.{attribute}
# simple_form.{namespace}.defaults.{attribute}
#
# Namespace is used for :labels and :hints.
#
# Model is the actual object name, for a @user object you'll have :user.
# Action is the action being rendered, usually :new or :edit.
# And attribute is the attribute itself, :name for example.
#
# The lookup for nested attributes is also done in a nested format using
# both model and nested object names, such as follow:
#
# simple_form.{namespace}.{model}.{nested}.{action}.{attribute}
# simple_form.{namespace}.{model}.{nested}.{attribute}
# simple_form.{namespace}.{nested}.{action}.{attribute}
# simple_form.{namespace}.{nested}.{attribute}
# simple_form.{namespace}.defaults.{attribute}
#
# Example:
#
# simple_form:
# labels:
# user:
# new:
# email: 'E-mail para efetuar o sign in.'
# edit:
# email: 'E-mail.'
#
# Take a look at our locale example file.
def translate_from_namespace(namespace, default = '')
model_names = lookup_model_names.dup
lookups = []
while !model_names.empty?
joined_model_names = model_names.join(".")
model_names.shift
lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}"
lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
end
lookups << :"defaults.#{lookup_action}.#{reflection_or_attribute_name}"
lookups << :"defaults.#{reflection_or_attribute_name}"
lookups << default
I18n.t(lookups.shift, scope: :"#{i18n_scope}.#{namespace}", default: lookups).presence
end
def merge_wrapper_options(options, wrapper_options)
if wrapper_options
wrapper_options = set_input_classes(wrapper_options)
wrapper_options.merge(options) do |key, oldval, newval|
case key.to_s
when "class"
Array(oldval) + Array(newval)
when "data", "aria"
oldval.merge(newval)
else
newval
end
end
else
options
end
end
def set_input_classes(wrapper_options)
wrapper_options = wrapper_options.dup
error_class = wrapper_options.delete(:error_class)
valid_class = wrapper_options.delete(:valid_class)
if error_class.present? && has_errors?
wrapper_options[:class] = "#{wrapper_options[:class]} #{error_class}"
end
if valid_class.present? && valid?
wrapper_options[:class] = "#{wrapper_options[:class]} #{valid_class}"
end
wrapper_options
end
def i18n_scope
SimpleForm.i18n_scope
end
end
end
end
================================================
FILE: lib/simple_form/inputs/block_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class BlockInput < Base
def initialize(*args, &block)
super
@block = block
end
def input(wrapper_options = nil)
template.capture(&@block)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/boolean_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class BooleanInput < Base
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
if nested_boolean_style?
build_hidden_field_for_checkbox +
template.label_tag(nil, class: boolean_label_class) {
build_check_box_without_hidden_field(merged_input_options) +
inline_label
}
else
if include_hidden?
build_check_box(unchecked_value, merged_input_options)
else
build_check_box_without_hidden_field(merged_input_options)
end
end
end
def label_input(wrapper_options = nil)
if options[:label] == false || inline_label?
input(wrapper_options)
elsif nested_boolean_style?
html_options = label_html_options.dup
html_options[:class] ||= []
html_options[:class].push(boolean_label_class) if boolean_label_class
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
build_hidden_field_for_checkbox +
@builder.label(label_target, html_options) {
build_check_box_without_hidden_field(merged_input_options) + label_text
}
else
input(wrapper_options) + label(wrapper_options)
end
end
private
def boolean_label_class
options[:boolean_label_class] || SimpleForm.boolean_label_class
end
# Build a checkbox tag using default unchecked value. This allows us to
# reuse the method for nested boolean style, but with no unchecked value,
# which won't generate the hidden checkbox. This is the default functionality
# in Rails > 3.2.1, and is backported in SimpleForm AV helpers.
def build_check_box(unchecked_value, options)
@builder.check_box(attribute_name, options, checked_value, unchecked_value)
end
# Build a checkbox without generating the hidden field. See
# #build_hidden_field_for_checkbox for more info.
def build_check_box_without_hidden_field(options)
build_check_box(nil, options)
end
# Create a hidden field for the current checkbox, so we can simulate Rails
# functionality with hidden + checkbox, but under a nested context, where
# we need the hidden field to be *outside* the label (otherwise it
# generates invalid html - html5 only).
def build_hidden_field_for_checkbox
return "".html_safe if !include_hidden? || !unchecked_value
options = { value: unchecked_value, id: nil, disabled: input_html_options[:disabled] }
options[:name] = input_html_options[:name] if input_html_options.key?(:name)
options[:form] = input_html_options[:form] if input_html_options.key?(:form)
@builder.hidden_field(attribute_name, options)
end
def inline_label?
nested_boolean_style? && options[:inline_label]
end
def inline_label
inline_option = options[:inline_label]
if inline_option
label = inline_option == true ? label_text : html_escape(inline_option)
" #{label}".html_safe
end
end
# Booleans are not required by default because in most of the cases
# it makes no sense marking them as required. The only exception is
# Terms of Use usually presented at most sites sign up screen.
def required_by_default?
false
end
def include_hidden?
options.fetch(:include_hidden, true)
end
def checked_value
options.fetch(:checked_value, '1')
end
def unchecked_value
options.fetch(:unchecked_value, '0')
end
end
end
end
================================================
FILE: lib/simple_form/inputs/collection_check_boxes_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class CollectionCheckBoxesInput < CollectionRadioButtonsInput
protected
# Checkbox components do not use the required html tag.
# More info: https://github.com/heartcombo/simple_form/issues/340#issuecomment-2871956
def has_required?
false
end
def build_nested_boolean_style_item_tag(collection_builder)
collection_builder.check_box + collection_builder.text.to_s
end
def item_wrapper_class
"checkbox"
end
end
end
end
================================================
FILE: lib/simple_form/inputs/collection_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class CollectionInput < Base
BASIC_OBJECT_CLASSES = [String, Integer, Float, NilClass, Symbol, TrueClass, FalseClass]
BASIC_OBJECT_CLASSES.push(Fixnum, Bignum) unless 1.class == Integer
# Default boolean collection for use with selects/radios when no
# collection is given. Always fallback to this boolean collection.
# Texts can be translated using i18n in "simple_form.yes" and
# "simple_form.no" keys. See the example locale file.
def self.boolean_collection
[ [I18n.t(:"simple_form.yes", default: 'Yes'), true],
[I18n.t(:"simple_form.no", default: 'No'), false] ]
end
def input(wrapper_options = nil)
raise NotImplementedError,
"input should be implemented by classes inheriting from CollectionInput"
end
def input_options
options = super
options[:include_blank] = true unless skip_include_blank?
translate_option options, :prompt
translate_option options, :include_blank
options
end
private
def collection
@collection ||= begin
collection = options.delete(:collection) || self.class.boolean_collection
collection.respond_to?(:call) ? collection.call : collection.to_a
end
end
def has_required?
super && (input_options[:include_blank] || input_options[:prompt].present? || multiple?)
end
# Check if :include_blank must be included by default.
def skip_include_blank?
(options.keys & %i[prompt include_blank default selected]).any? || multiple?
end
def multiple?
!!options[:input_html].try(:[], :multiple)
end
# Detect the right method to find the label and value for a collection.
# If no label or value method are defined, will attempt to find them based
# on default label and value methods that can be configured through
# SimpleForm.collection_label_methods and
# SimpleForm.collection_value_methods.
def detect_collection_methods
label, value = options.delete(:label_method), options.delete(:value_method)
unless label && value
common_method_for = detect_common_display_methods
label ||= common_method_for[:label]
value ||= common_method_for[:value]
end
[label, value]
end
def detect_common_display_methods(collection_classes = detect_collection_classes)
collection_translated = translate_collection if collection_classes == [Symbol]
if collection_translated || collection_classes.include?(Array)
{ label: :first, value: :second }
elsif collection_includes_basic_objects?(collection_classes)
{ label: :to_s, value: :to_s }
else
detect_method_from_class(collection_classes)
end
end
def detect_method_from_class(collection_classes)
sample = collection.first || collection.last
{ label: SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) },
value: SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) } }
end
def detect_collection_classes(some_collection = collection)
some_collection.map(&:class).uniq
end
def collection_includes_basic_objects?(collection_classes)
(collection_classes & BASIC_OBJECT_CLASSES).any?
end
def translate_collection
if translated_collection = translate_from_namespace(:options)
@collection = collection.map do |key|
html_key = "#{key}_html".to_sym
if translated_collection[html_key]
[translated_collection[html_key].html_safe || key, key.to_s]
else
[translated_collection[key] || key, key.to_s]
end
end
true
end
end
def translate_option(options, key)
if options[key] == :translate
namespace = key.to_s.pluralize
options[key] = translate_from_namespace(namespace, true)
end
end
end
end
end
================================================
FILE: lib/simple_form/inputs/collection_radio_buttons_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class CollectionRadioButtonsInput < CollectionInput
def input(wrapper_options = nil)
label_method, value_method = detect_collection_methods
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.send(:"collection_#{input_type}",
attribute_name, collection, value_method, label_method,
input_options, merged_input_options,
&collection_block_for_nested_boolean_style
)
end
def input_options
options = super
apply_default_collection_options!(options)
options
end
protected
def apply_default_collection_options!(options)
options[:item_wrapper_tag] ||= options.fetch(:item_wrapper_tag, SimpleForm.item_wrapper_tag)
options[:item_wrapper_class] = [
item_wrapper_class, options[:item_wrapper_class], SimpleForm.item_wrapper_class
].compact.presence if SimpleForm.include_default_input_wrapper_class
options[:collection_wrapper_tag] ||= options.fetch(:collection_wrapper_tag, SimpleForm.collection_wrapper_tag)
options[:collection_wrapper_class] = [
options[:collection_wrapper_class], SimpleForm.collection_wrapper_class
].compact.presence
end
def collection_block_for_nested_boolean_style
return unless nested_boolean_style?
proc { |builder| build_nested_boolean_style_item_tag(builder) }
end
def build_nested_boolean_style_item_tag(collection_builder)
collection_builder.radio_button + collection_builder.text.to_s
end
def item_wrapper_class
"radio"
end
# Do not attempt to generate label[for] attributes by default, unless an
# explicit html option is given. This avoids generating labels pointing to
# non existent fields.
def generate_label_for_attribute?
false
end
end
end
end
================================================
FILE: lib/simple_form/inputs/collection_select_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class CollectionSelectInput < CollectionInput
def input(wrapper_options = nil)
label_method, value_method = detect_collection_methods
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.collection_select(
attribute_name, collection, value_method, label_method,
input_options, merged_input_options
)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/color_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class ColorInput < Base
def input(wrapper_options = nil)
input_html_options[:type] ||= "color" if html5?
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.text_field(attribute_name, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/date_time_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class DateTimeInput < Base
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
if use_html5_inputs?
@builder.send(:"#{input_type}_field", attribute_name, merged_input_options)
else
@builder.send(:"#{input_type}_select", attribute_name, input_options, merged_input_options)
end
end
private
def label_target
if use_html5_inputs?
attribute_name
else
position = case input_type
when :date, :datetime
date_order = input_options[:order] || I18n.t('date.order')
date_order.first.to_sym
else
:hour
end
position = ActionView::Helpers::DateTimeSelector::POSITION[position]
"#{attribute_name}_#{position}i"
end
end
def use_html5_inputs?
input_options[:html5]
end
end
end
end
================================================
FILE: lib/simple_form/inputs/file_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class FileInput < Base
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.file_field(attribute_name, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/grouped_collection_select_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class GroupedCollectionSelectInput < CollectionInput
def input(wrapper_options = nil)
label_method, value_method = detect_collection_methods
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.grouped_collection_select(attribute_name, grouped_collection,
group_method, group_label_method, value_method, label_method,
input_options, merged_input_options)
end
private
def grouped_collection
@grouped_collection ||= begin
grouped_collection = options.delete(:collection)
grouped_collection.respond_to?(:call) ? grouped_collection.call : grouped_collection.to_a
end
end
# Sample collection
def collection
@collection ||= grouped_collection.map { |collection| group_method.respond_to?(:call) ? group_method.call(collection) : collection.try(:send, group_method) }.detect(&:present?) || []
end
def group_method
@group_method ||= options.delete(:group_method)
end
def group_label_method
label = options.delete(:group_label_method)
unless label
common_method_for = detect_common_display_methods(detect_collection_classes(grouped_collection))
label = common_method_for[:label]
end
label
end
def detect_method_from_class(collection_classes)
return {} if collection_classes.empty?
sample = collection_classes.first
{ label: SimpleForm.collection_label_methods.find { |m| sample.instance_methods.include?(m) },
value: SimpleForm.collection_value_methods.find { |m| sample.instance_methods.include?(m) } }
end
end
end
end
================================================
FILE: lib/simple_form/inputs/hidden_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class HiddenInput < Base
disable :label, :errors, :hint, :required
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.hidden_field(attribute_name, merged_input_options)
end
private
def required_class
nil
end
end
end
end
================================================
FILE: lib/simple_form/inputs/numeric_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class NumericInput < Base
enable :placeholder, :min_max
def input(wrapper_options = nil)
input_html_classes.unshift("numeric")
if html5?
input_html_options[:type] ||= "number"
input_html_options[:step] ||= integer? ? 1 : "any"
end
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.text_field(attribute_name, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/password_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class PasswordInput < Base
enable :placeholder, :maxlength, :minlength
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.password_field(attribute_name, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/priority_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class PriorityInput < CollectionSelectInput
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
send(:"#{input_type}_input", merged_input_options)
end
def input_priority
options[:priority] || SimpleForm.send(:"#{input_type}_priority")
end
protected
def country_input(merged_input_options)
@builder.send(:country_select,
attribute_name,
input_options.merge(priority_countries: input_priority),
merged_input_options)
end
def time_zone_input(merged_input_options)
@builder.send(:time_zone_select,
attribute_name,
input_priority,
input_options,
merged_input_options)
end
def skip_include_blank?
super || input_priority.present?
end
end
end
end
================================================
FILE: lib/simple_form/inputs/range_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class RangeInput < NumericInput
def input(wrapper_options = nil)
if html5?
input_html_options[:type] ||= "range"
input_html_options[:step] ||= 1
end
super
end
end
end
end
================================================
FILE: lib/simple_form/inputs/rich_text_area_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class RichTextAreaInput < Base
enable :placeholder
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.rich_text_area(attribute_name, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/string_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class StringInput < Base
enable :placeholder, :maxlength, :minlength, :pattern
def input(wrapper_options = nil)
unless string?
input_html_classes.unshift("string")
input_html_options[:type] ||= input_type if html5?
end
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.text_field(attribute_name, merged_input_options)
end
private
def string?
input_type == :string || input_type == :citext
end
end
end
end
================================================
FILE: lib/simple_form/inputs/text_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class TextInput < Base
enable :placeholder, :maxlength, :minlength
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.text_area(attribute_name, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs/weekday_input.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
class WeekdayInput < CollectionSelectInput
enable :placeholder
def input(wrapper_options = nil)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.weekday_select(attribute_name, input_options, merged_input_options)
end
end
end
end
================================================
FILE: lib/simple_form/inputs.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Inputs
extend ActiveSupport::Autoload
autoload :Base
autoload :BlockInput
autoload :BooleanInput
autoload :CollectionCheckBoxesInput
autoload :CollectionInput
autoload :CollectionRadioButtonsInput
autoload :CollectionSelectInput
autoload :ColorInput
autoload :DateTimeInput
autoload :FileInput
autoload :GroupedCollectionSelectInput
autoload :HiddenInput
autoload :NumericInput
autoload :PasswordInput
autoload :PriorityInput
autoload :RangeInput
autoload :RichTextAreaInput
autoload :StringInput
autoload :TextInput
autoload :WeekdayInput
end
end
================================================
FILE: lib/simple_form/map_type.rb
================================================
# frozen_string_literal: true
require 'active_support/core_ext/class/attribute'
module SimpleForm
module MapType
def self.extended(base)
base.class_attribute :mappings
base.mappings = {}
end
def map_type(*types)
map_to = types.extract_options![:to]
raise ArgumentError, "You need to give :to as option to map_type" unless map_to
self.mappings = mappings.merge types.each_with_object({}) { |t, m| m[t] = map_to }
end
end
end
================================================
FILE: lib/simple_form/railtie.rb
================================================
# frozen_string_literal: true
require 'rails/railtie'
module SimpleForm
class Railtie < Rails::Railtie
config.eager_load_namespaces << SimpleForm
config.after_initialize do
unless SimpleForm.configured?
warn '[Simple Form] Simple Form is not configured in the application and will use the default values.' +
' Use `rails generate simple_form:install` to generate the Simple Form configuration.'
end
end
initializer "simple_form.deprecator" do |app|
app.deprecators[:simple_form] = SimpleForm.deprecator if app.respond_to?(:deprecators)
end
end
end
================================================
FILE: lib/simple_form/tags.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Tags
module CollectionExtensions
private
def render_collection
item_wrapper_tag = @options.fetch(:item_wrapper_tag, :span)
item_wrapper_class = @options[:item_wrapper_class]
@collection.map do |item|
value = value_for_collection(item, @value_method)
text = value_for_collection(item, @text_method)
default_html_options = default_html_options_for_collection(item, value)
additional_html_options = option_html_attributes(item)
rendered_item = yield item, value, text, default_html_options.merge(additional_html_options)
if @options.fetch(:boolean_style, SimpleForm.boolean_style) == :nested
label_options = default_html_options.slice(:index, :namespace)
label_options['class'] = @options[:item_label_class]
rendered_item = @template_object.label(@object_name, sanitize_attribute_name(value), rendered_item, label_options)
end
item_wrapper_tag ? @template_object.content_tag(item_wrapper_tag, rendered_item, class: item_wrapper_class) : rendered_item
end.join.html_safe
end
def wrap_rendered_collection(collection)
wrapper_tag = @options[:collection_wrapper_tag]
if wrapper_tag
wrapper_class = @options[:collection_wrapper_class]
@template_object.content_tag(wrapper_tag, collection, class: wrapper_class)
else
collection
end
end
end
class CollectionRadioButtons < ActionView::Helpers::Tags::CollectionRadioButtons
include CollectionExtensions
def render
wrap_rendered_collection(super)
end
private
def render_component(builder)
label_class = "#{@options[:item_label_class]} collection_radio_buttons".strip
builder.radio_button + builder.label(class: label_class)
end
end
class CollectionCheckBoxes < ActionView::Helpers::Tags::CollectionCheckBoxes
include CollectionExtensions
def render
wrap_rendered_collection(super)
end
private
def render_component(builder)
label_class = "#{@options[:item_label_class]} collection_check_boxes".strip
builder.check_box + builder.label(class: label_class)
end
end
end
end
================================================
FILE: lib/simple_form/version.rb
================================================
# frozen_string_literal: true
module SimpleForm
VERSION = "5.4.1".freeze
end
================================================
FILE: lib/simple_form/wrappers/builder.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Wrappers
# Provides the builder syntax for components. The builder provides
# three methods `use`, `optional` and `wrapper` and they allow the following invocations:
#
# config.wrappers do |b|
# # Use a single component
# b.use :html5
#
# # Use the component, but do not automatically lookup. It will only be triggered when
# # :placeholder is explicitly set.
# b.optional :placeholder
#
# # Use a component with specific wrapper options
# b.use :error, wrap_with: { tag: "span", class: "error" }
#
# # Use a set of components by wrapping them in a tag+class.
# b.wrapper tag: "div", class: "another" do |ba|
# ba.use :label
# ba.use :input
# end
#
# # Use a set of components by wrapping them in a tag+class.
# # This wrapper is identified by :label_input, which means it can
# # be turned off on demand with `f.input :name, label_input: false`
# b.wrapper :label_input, tag: "div", class: "another" do |ba|
# ba.use :label
# ba.use :input
# end
# end
#
# The builder also accepts default options at the root level. This is usually
# used if you want a component to be disabled by default:
#
# config.wrappers hint: false do |b|
# b.use :hint
# b.use :label_input
# end
#
# In the example above, hint defaults to false, which means it won't automatically
# do the lookup anymore. It will only be triggered when :hint is explicitly set.
class Builder
def initialize(options)
@options = options
@components = []
end
def use(name, options = {})
if options && wrapper = options[:wrap_with]
@components << Single.new(name, wrapper, options.except(:wrap_with))
else
@components << Leaf.new(name, options)
end
end
def optional(name, options = {}, &block)
@options[name] = false
use(name, options)
end
def wrapper(name, options = nil)
if block_given?
name, options = nil, name if name.is_a?(Hash)
builder = self.class.new(@options)
options ||= {}
options[:tag] = :div if options[:tag].nil?
yield builder
@components << Many.new(name, builder.to_a, options)
else
raise ArgumentError, "A block is required as argument to wrapper"
end
end
def to_a
@components
end
end
end
end
================================================
FILE: lib/simple_form/wrappers/leaf.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Wrappers
class Leaf
attr_reader :namespace
def initialize(namespace, options = {})
@namespace = namespace
@options = options
end
def render(input)
method = input.method(@namespace)
if method.arity.zero?
SimpleForm.deprecator.warn(SimpleForm::CUSTOM_INPUT_DEPRECATION_WARN % { name: @namespace })
method.call
else
method.call(@options)
end
end
def find(name)
self if @namespace == name
end
end
end
end
================================================
FILE: lib/simple_form/wrappers/many.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Wrappers
# A wrapper is an object that holds several components and render them.
# A component may be any object that responds to `render`.
# This API allows inputs/components to be easily wrapped, removing the
# need to modify the code only to wrap input in an extra tag.
#
# `Many` represents a wrapper around several components at the same time.
# It may optionally receive a namespace, allowing it to be configured
# on demand on input generation.
class Many
attr_reader :namespace, :defaults, :components
def initialize(namespace, components, defaults = {})
@namespace = namespace
@components = components
@defaults = defaults
@defaults[:tag] = :div unless @defaults.key?(:tag)
@defaults[:class] = Array(@defaults[:class])
end
def render(input)
content = "".html_safe
options = input.options
components.each do |component|
next if options[component.namespace] == false
rendered = component.render(input)
content.safe_concat rendered.to_s if rendered
end
wrap(input, options, content)
end
def find(name)
return self if namespace == name
@components.each do |c|
if c.is_a?(Symbol)
return nil if c == namespace
elsif value = c.find(name)
return value
end
end
nil
end
private
def wrap(input, options, content)
return content if options[namespace] == false
return if defaults[:unless_blank] && content.empty?
tag = (namespace && options[:"#{namespace}_tag"]) || @defaults[:tag]
return content unless tag
klass = html_classes(input, options)
opts = html_options(options)
opts[:class] = (klass << opts[:class]).join(' ').strip unless klass.empty?
input.template.content_tag(tag, content, opts)
end
def html_options(options)
(@defaults[:html] || {}).merge(options[:"#{namespace}_html"] || {})
end
def html_classes(input, options)
@defaults[:class].dup
end
end
end
end
================================================
FILE: lib/simple_form/wrappers/root.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Wrappers
# `Root` is the root wrapper for all components. It is special cased to
# always have a namespace and to add special html classes.
class Root < Many
attr_reader :options
def initialize(*args)
super(:wrapper, *args)
@options = @defaults.except(:tag, :class, :error_class, :hint_class)
end
def render(input)
input.options.reverse_merge!(@options)
super
end
# Provide a fallback if name cannot be found.
def find(name)
super || SimpleForm::Wrappers::Many.new(name, [Leaf.new(name)])
end
private
def html_classes(input, options)
css = options[:wrapper_class] ? Array(options[:wrapper_class]) : @defaults[:class]
css += SimpleForm.additional_classes_for(:wrapper) do
input.additional_classes + [input.input_class]
end
css << html_class(:error_class, options) { input.has_errors? }
css << html_class(:hint_class, options) { input.has_hint? }
css << html_class(:valid_class, options) { input.valid? }
css.compact
end
def html_class(key, options)
css = (options[:"wrapper_#{key}"] || @defaults[key])
css if css && yield
end
end
end
end
================================================
FILE: lib/simple_form/wrappers/single.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Wrappers
# `Single` is an optimization for a wrapper that has only one component.
class Single < Many
def initialize(name, wrapper_options = {}, options = {})
@component = Leaf.new(name, options)
super(name, [@component], wrapper_options)
end
def render(input)
options = input.options
if options[namespace] != false
content = @component.render(input)
wrap(input, options, content) if content
end
end
private
def html_options(options)
%i[label input].include?(namespace) ? {} : super
end
end
end
end
================================================
FILE: lib/simple_form/wrappers.rb
================================================
# frozen_string_literal: true
module SimpleForm
module Wrappers
autoload :Builder, 'simple_form/wrappers/builder'
autoload :Many, 'simple_form/wrappers/many'
autoload :Root, 'simple_form/wrappers/root'
autoload :Single, 'simple_form/wrappers/single'
autoload :Leaf, 'simple_form/wrappers/leaf'
end
end
================================================
FILE: lib/simple_form.rb
================================================
# frozen_string_literal: true
require 'action_view'
require 'action_pack'
require 'simple_form/action_view_extensions/form_helper'
require 'simple_form/action_view_extensions/builder'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/reverse_merge'
module SimpleForm
extend ActiveSupport::Autoload
autoload :Helpers
autoload :Wrappers
eager_autoload do
autoload :Components
autoload :ErrorNotification
autoload :FormBuilder
autoload :Inputs
end
def self.eager_load!
super
SimpleForm::Inputs.eager_load!
SimpleForm::Components.eager_load!
end
CUSTOM_INPUT_DEPRECATION_WARN = <<-WARN
%{name} method now accepts a `wrapper_options` argument. The method definition without the argument is deprecated and will be removed in the next Simple Form version. Change your code from:
def %{name}
to
def %{name}(wrapper_options)
See https://github.com/heartcombo/simple_form/pull/997 for more information.
WARN
FILE_METHODS_DEPRECATION_WARN = <<-WARN
[SIMPLE_FORM] SimpleForm.file_methods is deprecated and has no effect.
Since version 5, Simple Form now supports automatically discover of file inputs for the following Gems: activestorage, carrierwave, paperclip, refile and shrine.
If you are using a custom method that is not from one of the supported Gems, please change your forms to pass the input type explicitly:
<%= form.input :avatar, as: :file %>
See http://blog.plataformatec.com.br/2019/09/incorrect-access-control-in-simple-form-cve-2019-16676 for more information.
WARN
@@configured = false
def self.configured? #:nodoc:
@@configured
end
def self.deprecator
@deprecator ||= ActiveSupport::Deprecation.new("5.3", "SimpleForm")
end
## CONFIGURATION OPTIONS
# Method used to tidy up errors.
mattr_accessor :error_method
@@error_method = :first
# Default tag used for error notification helper.
mattr_accessor :error_notification_tag
@@error_notification_tag = :p
# CSS class to add for error notification helper.
mattr_accessor :error_notification_class
@@error_notification_class = :error_notification
# Series of attempts to detect a default label method for collection.
mattr_accessor :collection_label_methods
@@collection_label_methods = %i[to_label name title to_s]
# Series of attempts to detect a default value method for collection.
mattr_accessor :collection_value_methods
@@collection_value_methods = %i[id to_s]
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
mattr_accessor :collection_wrapper_tag
@@collection_wrapper_tag = nil
# You can define the class to use on all collection wrappers, defaulting to none.
mattr_accessor :collection_wrapper_class
@@collection_wrapper_class = nil
# You can wrap each item in a collection of radio/check boxes with a tag,
# defaulting to span. Please note that when using :boolean_style = :nested,
# SimpleForm will force this option to be a :label.
mattr_accessor :item_wrapper_tag
@@item_wrapper_tag = :span
# You can define the class to use on all item wrappers, defaulting to none.
mattr_accessor :item_wrapper_class
@@item_wrapper_class = nil
# How the label text should be generated altogether with the required text.
mattr_accessor :label_text
@@label_text = ->(label, required, explicit_label) { "#{required} #{label}" }
# You can define the class to be used on all labels. Defaults to none.
mattr_accessor :label_class
@@label_class = nil
# Define the way to render check boxes / radio buttons with labels.
# inline: input + label (default)
# nested: label > input
mattr_accessor :boolean_style
@@boolean_style = :inline
# DEPRECATED: You can define the class to be used on all forms. Default is
# simple_form.
mattr_reader :form_class
@@form_class = :simple_form
# You can define the default class to be used on all forms. Can be overridden
# with `html: { :class }`. Defaults to none.
mattr_accessor :default_form_class
@@default_form_class = nil
# You can define which elements should obtain additional classes.
mattr_accessor :generate_additional_classes_for
@@generate_additional_classes_for = %i[wrapper label input]
# Whether attributes are required by default or not.
mattr_accessor :required_by_default
@@required_by_default = true
# Tell browsers whether to use default HTML5 validations (novalidate option).
mattr_accessor :browser_validations
@@browser_validations = true
# Custom mappings for input types. This should be a hash containing a regexp
# to match as key, and the input type that will be used when the field name
# matches the regexp as value, such as { /count/ => :integer }.
mattr_accessor :input_mappings
@@input_mappings = nil
# Custom wrappers for input types. This should be a hash containing an input
# type as key and the wrapper that will be used for all inputs with specified type.
# e.g { string: :string_wrapper, boolean: :boolean_wrapper }
# You can also set a wrapper mapping per form basis.
# e.g simple_form_for(@foo, wrapper_mappings: { check_boxes: :bootstrap_checkbox })
mattr_accessor :wrapper_mappings
@@wrapper_mappings = nil
# Namespaces where SimpleForm should look for custom input classes that override
# default inputs. Namespaces are given as string to allow lazy loading inputs.
# e.g. config.custom_inputs_namespaces << "CustomInputs"
# will try to find CustomInputs::NumericInput when an :integer
# field is called.
mattr_accessor :custom_inputs_namespaces
@@custom_inputs_namespaces = []
# Default priority for time_zone inputs.
mattr_accessor :time_zone_priority
@@time_zone_priority = nil
# Default priority for country inputs.
mattr_accessor :country_priority
@@country_priority = nil
# When off, do not use translations in labels. Disabling translation in
# hints and placeholders can be done manually in the wrapper API.
mattr_accessor :translate_labels
@@translate_labels = true
# Automatically discover new inputs in Rails' autoload path.
mattr_accessor :inputs_discovery
@@inputs_discovery = true
# Cache SimpleForm inputs discovery.
mattr_accessor :cache_discovery
@@cache_discovery = defined?(Rails.env) && !Rails.env.development?
# Adds a class to each generated button, mostly for compatibility.
mattr_accessor :button_class
@@button_class = 'button'
# Override the default ActiveModelHelper behaviour of wrapping the input.
# This gets taken care of semantically by adding an error class to the wrapper tag
# containing the input.
mattr_accessor :field_error_proc
@@field_error_proc = proc do |html_tag, instance_tag|
html_tag
end
# Adds a class to each generated inputs
mattr_accessor :input_class
@@input_class = nil
# Defines if an input wrapper class should be included or not
mattr_accessor :include_default_input_wrapper_class
@@include_default_input_wrapper_class = true
# Define the default class of the input wrapper of the boolean input.
mattr_accessor :boolean_label_class
@@boolean_label_class = 'checkbox'
## WRAPPER CONFIGURATION
# The default wrapper to be used by the FormBuilder.
mattr_accessor :default_wrapper
@@default_wrapper = :default
@@wrappers = {} #:nodoc:
mattr_accessor :i18n_scope
@@i18n_scope = 'simple_form'
mattr_accessor :input_field_error_class
@@input_field_error_class = nil
mattr_accessor :input_field_valid_class
@@input_field_valid_class = nil
# Retrieves a given wrapper
def self.wrapper(name)
@@wrappers[name.to_s] or raise WrapperNotFound, "Couldn't find wrapper with name #{name}"
end
# Raised when fails to find a given wrapper name
class WrapperNotFound < StandardError
end
# Define a new wrapper using SimpleForm::Wrappers::Builder
# and store it in the given name.
def self.wrappers(*args, &block)
if block_given?
options = args.extract_options!
name = args.first || :default
@@wrappers[name.to_s] = build(options, &block)
else
@@wrappers
end
end
# Builds a new wrapper using SimpleForm::Wrappers::Builder.
def self.build(options = {})
options[:tag] = :div if options[:tag].nil?
builder = SimpleForm::Wrappers::Builder.new(options)
yield builder
SimpleForm::Wrappers::Root.new(builder.to_a, options)
end
wrappers class: :input, hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|
b.use :html5
b.use :min_max
b.use :maxlength
b.use :minlength
b.use :placeholder
b.optional :pattern
b.optional :readonly
b.use :label_input
b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error }
end
def self.additional_classes_for(component)
generate_additional_classes_for.include?(component) ? yield : []
end
## SETUP
def self.default_input_size=(*)
SimpleForm.deprecator.warn "[SIMPLE_FORM] SimpleForm.default_input_size= is deprecated and has no effect", caller
end
def self.form_class=(value)
SimpleForm.deprecator.warn "[SIMPLE_FORM] SimpleForm.form_class= is deprecated and will be removed in 4.x. Use SimpleForm.default_form_class= instead", caller
@@form_class = value
end
def self.file_methods=(file_methods)
SimpleForm.deprecator.warn(FILE_METHODS_DEPRECATION_WARN, caller)
@@file_methods = file_methods
end
def self.file_methods
SimpleForm.deprecator.warn(FILE_METHODS_DEPRECATION_WARN, caller)
@@file_methods
end
# Default way to setup Simple Form. Run rails generate simple_form:install
# to create a fresh initializer with all configuration values.
def self.setup
@@configured = true
yield self
end
# Includes a component to be used by Simple Form. Methods defined in a
# component will be exposed to be used in the wrapper as Simple::Components
#
# Examples
#
# # The application needs to tell where the components will be.
# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
#
# # Create a custom component in the path specified above.
# # lib/components/input_group_component.rb
# module InputGroupComponent
# def prepend
# ...
# end
#
# def append
# ...
# end
# end
#
# SimpleForm.setup do |config|
# # Create a wrapper using the custom component.
# config.wrappers :input_group, tag: :div, error_class: :error do |b|
# b.use :label
# b.optional :prepend
# b.use :input
# b.use :append
# end
# end
#
# # Using the custom component in the form.
# <%= simple_form_for @blog, wrapper: input_group do |f| %>
# <%= f.input :title, prepend: true %>
# <% end %>
#
def self.include_component(component)
if Module === component
SimpleForm::Inputs::Base.include(component)
else
raise TypeError, "SimpleForm.include_component expects a module but got: #{component.class}"
end
end
end
require 'simple_form/railtie' if defined?(Rails)
================================================
FILE: simple_form.gemspec
================================================
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "simple_form/version"
Gem::Specification.new do |s|
s.name = "simple_form"
s.version = SimpleForm::VERSION.dup
s.platform = Gem::Platform::RUBY
s.summary = "Forms made easy!"
s.email = "heartcombo.oss@gmail.com"
s.homepage = "https://github.com/heartcombo/simple_form"
s.description = "Forms made easy!"
s.authors = ['José Valim', 'Carlos Antonio', 'Rafael França']
s.license = "MIT"
s.metadata = {
"homepage_uri" => "https://github.com/heartcombo/simple_form",
"documentation_uri" => "https://rubydoc.info/github/heartcombo/simple_form",
"changelog_uri" => "https://github.com/heartcombo/simple_form/blob/main/CHANGELOG.md",
"source_code_uri" => "https://github.com/heartcombo/simple_form",
"bug_tracker_uri" => "https://github.com/heartcombo/simple_form/issues",
"wiki_uri" => "https://github.com/heartcombo/simple_form/wiki"
}
s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*"]
s.require_paths = ["lib"]
s.required_ruby_version = ">= 2.7.0"
s.add_dependency "activemodel", ">= 7.0"
s.add_dependency "actionpack", ">= 7.0"
s.add_development_dependency "country_select"
s.add_development_dependency "minitest", "< 6"
s.add_development_dependency "rake"
s.add_development_dependency "rdoc"
end
================================================
FILE: test/action_view_extensions/builder_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class BuilderTest < ActionView::TestCase
def with_custom_form_for(object, *args, &block)
with_concat_custom_form_for(object) do |f|
assert f.instance_of?(CustomFormBuilder)
yield f
end
end
def with_collection_radio_buttons(object, attribute, collection, value_method, text_method, options = {}, html_options = {}, &block)
with_concat_form_for(object) do |f|
f.collection_radio_buttons attribute, collection, value_method, text_method, options, html_options, &block
end
end
def with_collection_check_boxes(object, attribute, collection, value_method, text_method, options = {}, html_options = {}, &block)
with_concat_form_for(object) do |f|
f.collection_check_boxes attribute, collection, value_method, text_method, options, html_options, &block
end
end
# COLLECTION RADIO
test "collection radio accepts a collection and generate inputs from value method" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s
assert_select 'form input[type=radio][value=true]#user_active_true'
assert_select 'form input[type=radio][value=false]#user_active_false'
end
test "collection radio accepts a collection and generate inputs from label method" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s
assert_select 'form label.collection_radio_buttons[for=user_active_true]', 'true'
assert_select 'form label.collection_radio_buttons[for=user_active_false]', 'false'
end
test "collection radio handles camelized collection values for labels correctly" do
with_collection_radio_buttons @user, :active, %w[Yes No], :to_s, :to_s
assert_select 'form label.collection_radio_buttons[for=user_active_yes]', 'Yes'
assert_select 'form label.collection_radio_buttons[for=user_active_no]', 'No'
end
test "collection radio sanitizes collection values for labels correctly" do
with_collection_radio_buttons @user, :name, ['$0.99', '$1.99'], :to_s, :to_s
assert_select 'label.collection_radio_buttons[for=user_name_0_99]', '$0.99'
assert_select 'label.collection_radio_buttons[for=user_name_1_99]', '$1.99'
end
test "collection radio checks the correct value to local variables" do
user = User.build(active: false)
with_collection_radio_buttons user, :active, [true, false], :to_s, :to_s
assert_select 'form input[type=radio][value=true]'
assert_select 'form input[type=radio][value=false][checked=checked]'
end
test "collection radio accepts checked item" do
with_collection_radio_buttons @user, :active, [[1, true], [0, false]], :last, :first, checked: true
assert_select 'form input[type=radio][value=true][checked=checked]'
assert_no_select 'form input[type=radio][value=false][checked=checked]'
end
test "collection radio accepts checked item which has a value of false" do
with_collection_radio_buttons @user, :active, [[1, true], [0, false]], :last, :first, checked: false
assert_no_select 'form input[type=radio][value=true][checked=checked]'
assert_select 'form input[type=radio][value=false][checked=checked]'
end
test "collection radio accepts multiple disabled items" do
collection = [[1, true], [0, false], [2, 'other']]
with_collection_radio_buttons @user, :active, collection, :last, :first, disabled: [true, false]
assert_select 'form input[type=radio][value=true][disabled=disabled]'
assert_select 'form input[type=radio][value=false][disabled=disabled]'
assert_no_select 'form input[type=radio][value=other][disabled=disabled]'
end
test "collection radio accepts single disable item" do
collection = [[1, true], [0, false]]
with_collection_radio_buttons @user, :active, collection, :last, :first, disabled: true
assert_select 'form input[type=radio][value=true][disabled=disabled]'
assert_no_select 'form input[type=radio][value=false][disabled=disabled]'
end
test "collection radio accepts html options as input" do
collection = [[1, true], [0, false]]
with_collection_radio_buttons @user, :active, collection, :last, :first, {}, class: 'special-radio'
assert_select 'form input[type=radio][value=true].special-radio#user_active_true'
assert_select 'form input[type=radio][value=false].special-radio#user_active_false'
end
test "collection radio wraps the collection in the given collection wrapper tag" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s, collection_wrapper_tag: :ul
assert_select 'form ul input[type=radio]', count: 2
end
test "collection radio does not render any wrapper tag by default" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form ul'
end
test "collection radio does not wrap the collection when given falsy values" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s, collection_wrapper_tag: false
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form ul'
end
test "collection radio uses the given class for collection wrapper tag" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s,
collection_wrapper_tag: :ul, collection_wrapper_class: "items-list"
assert_select 'form ul.items-list input[type=radio]', count: 2
end
test "collection radio uses no class for collection wrapper tag when no wrapper tag is given" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s,
collection_wrapper_class: "items-list"
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form ul'
assert_no_select '.items-list'
end
test "collection radio uses no class for collection wrapper tag by default" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s, collection_wrapper_tag: :ul
assert_select 'form ul'
assert_no_select 'form ul[class]'
end
test "collection radio wrap items in a span tag by default" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s
assert_select 'form span input[type=radio][value=true]#user_active_true + label'
assert_select 'form span input[type=radio][value=false]#user_active_false + label'
end
test "collection radio wraps each item in the given item wrapper tag" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s, item_wrapper_tag: :li
assert_select 'form li input[type=radio]', count: 2
end
test "collection radio does not wrap each item when given explicitly falsy value" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s, item_wrapper_tag: false
assert_select 'form input[type=radio]'
assert_no_select 'form span input[type=radio]'
end
test "collection radio uses the given class for item wrapper tag" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s,
item_wrapper_tag: :li, item_wrapper_class: "inline"
assert_select "form li.inline input[type=radio]", count: 2
end
test "collection radio uses no class for item wrapper tag when no wrapper tag is given" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s,
item_wrapper_tag: nil, item_wrapper_class: "inline"
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form li'
assert_no_select '.inline'
end
test "collection radio uses no class for item wrapper tag by default" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s,
item_wrapper_tag: :li
assert_select "form li", count: 2
assert_no_select "form li[class]"
end
test "collection radio does not wrap input inside the label" do
with_collection_radio_buttons @user, :active, [true, false], :to_s, :to_s
assert_select 'form input[type=radio] + label'
assert_no_select 'form label input'
end
test "collection radio accepts a block to render the label as radio button wrapper" do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
b.label { b.radio_button }
end
assert_select 'label[for=user_active_true] > input#user_active_true[type=radio]'
assert_select 'label[for=user_active_false] > input#user_active_false[type=radio]'
end
test "collection radio accepts a block to change the order of label and radio button" do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
b.label + b.radio_button
end
assert_select 'label[for=user_active_true] + input#user_active_true[type=radio]'
assert_select 'label[for=user_active_false] + input#user_active_false[type=radio]'
end
test "collection radio with block helpers accept extra html options" do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
b.label(class: "radio_button") + b.radio_button(class: "radio_button")
end
assert_select 'label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]'
assert_select 'label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]'
end
test "collection radio with block helpers allows access to current text and value" do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
b.label(:"data-value" => b.value) { b.radio_button + b.text }
end
assert_select 'label[for=user_active_true][data-value=true]', 'true' do
assert_select 'input#user_active_true[type=radio]'
end
assert_select 'label[for=user_active_false][data-value=false]', 'false' do
assert_select 'input#user_active_false[type=radio]'
end
end
test "collection radio with block helpers allows access to the current object item in the collection to access extra properties" do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
b.label(class: b.object) { b.radio_button + b.text }
end
assert_select 'label.true[for=user_active_true]', 'true' do
assert_select 'input#user_active_true[type=radio]'
end
assert_select 'label.false[for=user_active_false]', 'false' do
assert_select 'input#user_active_false[type=radio]'
end
end
test "collection radio with block helpers does not leak the template" do
with_concat_form_for(@user) do |f|
collection_input = f.collection_radio_buttons :active, [true, false], :to_s, :to_s do |b|
b.label(class: b.object) { b.radio_button + b.text }
end
concat collection_input
concat f.hidden_field :name
end
assert_select 'label.true[for=user_active_true]', text: 'true', count: 1 do
assert_select 'input#user_active_true[type=radio]'
end
assert_select 'label.false[for=user_active_false]', text: 'false', count: 1 do
assert_select 'input#user_active_false[type=radio]'
end
end
# COLLECTION CHECK BOX
test "collection check box accepts a collection and generate a series of checkboxes for value method" do
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
with_collection_check_boxes @user, :tag_ids, collection, :id, :name
assert_select 'form input#user_tag_ids_1[type=checkbox][value="1"]'
assert_select 'form input#user_tag_ids_2[type=checkbox][value="2"]'
end
test "collection check box generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection" do
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
with_collection_check_boxes @user, :tag_ids, collection, :id, :name
assert_select "form input[type=hidden][name='user[tag_ids][]'][value='']", count: 1
end
test "collection check box accepts a collection and generate a series of checkboxes with labels for label method" do
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
with_collection_check_boxes @user, :tag_ids, collection, :id, :name
assert_select 'form label.collection_check_boxes[for=user_tag_ids_1]', 'Tag 1'
assert_select 'form label.collection_check_boxes[for=user_tag_ids_2]', 'Tag 2'
end
test "collection check box handles camelized collection values for labels correctly" do
with_collection_check_boxes @user, :active, %w[Yes No], :to_s, :to_s
assert_select 'form label.collection_check_boxes[for=user_active_yes]', 'Yes'
assert_select 'form label.collection_check_boxes[for=user_active_no]', 'No'
end
test "collection check box sanitizes collection values for labels correctly" do
with_collection_check_boxes @user, :name, ['$0.99', '$1.99'], :to_s, :to_s
assert_select 'label.collection_check_boxes[for=user_name_0_99]', '$0.99'
assert_select 'label.collection_check_boxes[for=user_name_1_99]', '$1.99'
end
test "collection check box checks the correct value to local variables" do
user = User.build(tag_ids: [1, 3])
collection = (1..3).map { |i| [i, "Tag #{i}"] }
with_collection_check_boxes user, :tag_ids, collection, :first, :last
assert_select 'form input[type=checkbox][value="1"][checked=checked]'
assert_select 'form input[type=checkbox][value="3"][checked=checked]'
assert_no_select 'form input[type=checkbox][value="2"][checked=checked]'
end
test "collection check box accepts selected values as :checked option" do
collection = (1..3).map { |i| [i, "Tag #{i}"] }
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, checked: [1, 3]
assert_select 'form input[type=checkbox][value="1"][checked=checked]'
assert_select 'form input[type=checkbox][value="3"][checked=checked]'
assert_no_select 'form input[type=checkbox][value="2"][checked=checked]'
end
test "collection check boxes accepts selected string values as :checked option" do
collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: %w[1 3]
assert_select 'input[type=checkbox][value="1"][checked=checked]'
assert_select 'input[type=checkbox][value="3"][checked=checked]'
assert_no_select 'input[type=checkbox][value="2"][checked=checked]'
end
test "collection check box accepts a single checked value" do
collection = (1..3).map { |i| [i, "Tag #{i}"] }
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, checked: 3
assert_select 'form input[type=checkbox][value="3"][checked=checked]'
assert_no_select 'form input[type=checkbox][value="1"][checked=checked]'
assert_no_select 'form input[type=checkbox][value="2"][checked=checked]'
end
test "collection check box accepts selected values as :checked option and override the model values" do
collection = (1..3).map { |i| [i, "Tag #{i}"] }
@user.tag_ids = [2]
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, checked: [1, 3]
assert_select 'form input[type=checkbox][value="1"][checked=checked]'
assert_select 'form input[type=checkbox][value="3"][checked=checked]'
assert_no_select 'form input[type=checkbox][value="2"][checked=checked]'
end
test "collection check box accepts multiple disabled items" do
collection = (1..3).map { |i| [i, "Tag #{i}"] }
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, disabled: [1, 3]
assert_select 'form input[type=checkbox][value="1"][disabled=disabled]'
assert_select 'form input[type=checkbox][value="3"][disabled=disabled]'
assert_no_select 'form input[type=checkbox][value="2"][disabled=disabled]'
end
test "collection check box accepts single disable item" do
collection = (1..3).map { |i| [i, "Tag #{i}"] }
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, disabled: 1
assert_select 'form input[type=checkbox][value="1"][disabled=disabled]'
assert_no_select 'form input[type=checkbox][value="3"][disabled=disabled]'
assert_no_select 'form input[type=checkbox][value="2"][disabled=disabled]'
end
test "collection check box accepts a proc to disabled items" do
collection = (1..3).map { |i| [i, "Tag #{i}"] }
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, disabled: proc { |i| i.first == 1 }
assert_select 'form input[type=checkbox][value="1"][disabled=disabled]'
assert_no_select 'form input[type=checkbox][value="3"][disabled=disabled]'
assert_no_select 'form input[type=checkbox][value="2"][disabled=disabled]'
end
test "collection check box accepts html options" do
collection = [[1, 'Tag 1'], [2, 'Tag 2']]
with_collection_check_boxes @user, :tag_ids, collection, :first, :last, {}, class: 'check'
assert_select 'form input.check[type=checkbox][value="1"]'
assert_select 'form input.check[type=checkbox][value="2"]'
end
test "collection check box with fields for" do
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
with_concat_form_for(@user) do |f|
f.fields_for(:post) do |p|
p.collection_check_boxes :tag_ids, collection, :id, :name
end
end
assert_select 'form input#user_post_tag_ids_1[type=checkbox][value="1"]'
assert_select 'form input#user_post_tag_ids_2[type=checkbox][value="2"]'
assert_select 'form label.collection_check_boxes[for=user_post_tag_ids_1]', 'Tag 1'
assert_select 'form label.collection_check_boxes[for=user_post_tag_ids_2]', 'Tag 2'
end
test "collection check boxes wraps the collection in the given collection wrapper tag" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, collection_wrapper_tag: :ul
assert_select 'form ul input[type=checkbox]', count: 2
end
test "collection check boxes does not render any wrapper tag by default" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form ul'
end
test "collection check boxes does not wrap the collection when given falsy values" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, collection_wrapper_tag: false
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form ul'
end
test "collection check boxes uses the given class for collection wrapper tag" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s,
collection_wrapper_tag: :ul, collection_wrapper_class: "items-list"
assert_select 'form ul.items-list input[type=checkbox]', count: 2
end
test "collection check boxes uses no class for collection wrapper tag when no wrapper tag is given" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s,
collection_wrapper_class: "items-list"
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form ul'
assert_no_select '.items-list'
end
test "collection check boxes uses no class for collection wrapper tag by default" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, collection_wrapper_tag: :ul
assert_select 'form ul'
assert_no_select 'form ul[class]'
end
test "collection check boxes wrap items in a span tag by default" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
assert_select 'form span input[type=checkbox]', count: 2
end
test "collection check boxes wraps each item in the given item wrapper tag" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, item_wrapper_tag: :li
assert_select 'form li input[type=checkbox]', count: 2
end
test "collection check boxes does not wrap each item when given explicitly falsy value" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, item_wrapper_tag: false
assert_select 'form input[type=checkbox]'
assert_no_select 'form span input[type=checkbox]'
end
test "collection check boxes uses the given class for item wrapper tag" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s,
item_wrapper_tag: :li, item_wrapper_class: "inline"
assert_select "form li.inline input[type=checkbox]", count: 2
end
test "collection check boxes uses no class for item wrapper tag when no wrapper tag is given" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s,
item_wrapper_tag: nil, item_wrapper_class: "inline"
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form li'
assert_no_select '.inline'
end
test "collection check boxes uses no class for item wrapper tag by default" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s,
item_wrapper_tag: :li
assert_select "form li", count: 2
assert_no_select "form li[class]"
end
test "collection check box does not wrap input inside the label" do
with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
assert_select 'form input[type=checkbox] + label'
assert_no_select 'form label input'
end
test "collection check boxes accepts a block to render the label as check box wrapper" do
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
b.label { b.check_box }
end
assert_select 'label[for=user_active_true] > input#user_active_true[type=checkbox]'
assert_select 'label[for=user_active_false] > input#user_active_false[type=checkbox]'
end
test "collection check boxes accepts a block to change the order of label and check box" do
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
b.label + b.check_box
end
assert_select 'label[for=user_active_true] + input#user_active_true[type=checkbox]'
assert_select 'label[for=user_active_false] + input#user_active_false[type=checkbox]'
end
test "collection check boxes with block helpers accept extra html options" do
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
b.label(class: "check_box") + b.check_box(class: "check_box")
end
assert_select 'label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]'
assert_select 'label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]'
end
test "collection check boxes with block helpers allows access to current text and value" do
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
b.label(:"data-value" => b.value) { b.check_box + b.text }
end
assert_select 'label[for=user_active_true][data-value=true]', 'true' do
assert_select 'input#user_active_true[type=checkbox]'
end
assert_select 'label[for=user_active_false][data-value=false]', 'false' do
assert_select 'input#user_active_false[type=checkbox]'
end
end
test "collection check boxes with block helpers allows access to the current object item in the collection to access extra properties" do
with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
b.label(class: b.object) { b.check_box + b.text }
end
assert_select 'label.true[for=user_active_true]', 'true' do
assert_select 'input#user_active_true[type=checkbox]'
end
assert_select 'label.false[for=user_active_false]', 'false' do
assert_select 'input#user_active_false[type=checkbox]'
end
end
test "collection check boxes with block helpers does not leak the template" do
with_concat_form_for(@user) do |f|
collection_input = f.collection_check_boxes :active, [true, false], :to_s, :to_s do |b|
b.label(class: b.object) { b.check_box + b.text }
end
concat collection_input
concat f.hidden_field :name
end
assert_select 'label.true[for=user_active_true]', text: 'true', count: 1 do
assert_select 'input#user_active_true[type=checkbox]'
end
assert_select 'label.false[for=user_active_false]', text: 'false', count: 1 do
assert_select 'input#user_active_false[type=checkbox]'
end
end
# SIMPLE FIELDS
test "simple fields for is available and yields an instance of FormBuilder" do
with_concat_form_for(@user) do |f|
f.simple_fields_for(:posts) do |posts_form|
assert posts_form.instance_of?(SimpleForm::FormBuilder)
end
end
end
test "fields for with a hash like model yields an instance of FormBuilder" do
with_concat_form_for(:user) do |f|
f.simple_fields_for(:author, HashBackedAuthor.new) do |author|
assert author.instance_of?(SimpleForm::FormBuilder)
author.input :name
end
end
assert_select "input[name='user[author][name]'][value='hash backed author']"
end
test "fields for yields an instance of CustomBuilder if main builder is a CustomBuilder" do
with_custom_form_for(:user) do |f|
f.simple_fields_for(:company) do |company|
assert company.instance_of?(CustomFormBuilder)
end
end
end
test "fields for yields an instance of FormBuilder if it was set in options" do
with_custom_form_for(:user) do |f|
f.simple_fields_for(:company, builder: SimpleForm::FormBuilder) do |company|
assert company.instance_of?(SimpleForm::FormBuilder)
end
end
end
test "fields inherits wrapper option from the parent form" do
swap_wrapper :another do
simple_form_for(:user, wrapper: :another) do |f|
f.simple_fields_for(:company) do |company|
assert_equal :another, company.options[:wrapper]
end
end
end
end
test "fields overrides wrapper option from the parent form" do
swap_wrapper :another do
simple_form_for(:user, wrapper: :another) do |f|
f.simple_fields_for(:company, wrapper: false) do |company|
assert_equal false, company.options[:wrapper]
end
end
end
end
end
================================================
FILE: test/action_view_extensions/form_helper_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class FormHelperTest < ActionView::TestCase
test 'SimpleForm for yields an instance of FormBuilder' do
simple_form_for :user do |f|
assert f.instance_of?(SimpleForm::FormBuilder)
end
end
test 'SimpleForm adds default class to form' do
with_concat_form_for(:user)
assert_select 'form.simple_form'
end
test 'SimpleForm allows overriding default form class' do
swap SimpleForm, default_form_class: "my_custom_class" do
with_concat_form_for :user, html: { class: "override_class" }
assert_no_select 'form.my_custom_class'
assert_select 'form.override_class'
end
end
# Remove this test when SimpleForm.form_class is removed in 4.x
test 'SimpleForm allows overriding default form class, but not form class' do
SimpleForm.deprecator.silence do
swap SimpleForm, form_class: "fixed_class", default_form_class: "my_custom_class" do
with_concat_form_for :user, html: { class: "override_class" }
assert_no_select 'form.my_custom_class'
assert_select 'form.fixed_class.override_class'
end
end
end
test 'SimpleForm uses default browser validations by default' do
with_concat_form_for(:user)
assert_no_select 'form[novalidate]'
end
test 'SimpleForm does not use default browser validations if specified in the configuration options' do
swap SimpleForm, browser_validations: false do
with_concat_form_for(:user)
assert_select 'form[novalidate="novalidate"]'
end
end
test 'disabled browser validations overrides default configuration' do
with_concat_form_for(:user, html: { novalidate: true })
assert_select 'form[novalidate="novalidate"]'
end
test 'enabled browser validations overrides disabled configuration' do
swap SimpleForm, browser_validations: false do
with_concat_form_for(:user, html: { novalidate: false })
assert_no_select 'form[novalidate]'
end
end
test 'SimpleForm adds object name as css class to form when object is not present' do
with_concat_form_for(:user, html: { novalidate: true })
assert_select 'form.simple_form.user'
end
test 'SimpleForm adds :as option as css class to form when object is not present' do
with_concat_form_for(:user, as: 'superuser')
assert_select 'form.simple_form.superuser'
end
test 'SimpleForm adds object class name with new prefix as css class to form if record is not persisted' do
@user.new_record!
with_concat_form_for(@user)
assert_select 'form.simple_form.new_user'
end
test 'SimpleForm adds :as option with new prefix as css class to form if record is not persisted' do
@user.new_record!
with_concat_form_for(@user, as: 'superuser')
assert_select 'form.simple_form.new_superuser'
end
test 'SimpleForm adds edit class prefix as css class to form if record is persisted' do
with_concat_form_for(@user)
assert_select 'form.simple_form.edit_user'
end
test 'SimpleForm adds :as options with edit prefix as css class to form if record is persisted' do
with_concat_form_for(@user, as: 'superuser')
assert_select 'form.simple_form.edit_superuser'
end
test 'SimpleForm adds last object name as css class to form when there is array of objects' do
with_concat_form_for([Company.new, @user])
assert_select 'form.simple_form.edit_user'
end
test 'SimpleForm does not add object class to form if css_class is specified' do
with_concat_form_for(:user, html: { class: nil })
assert_no_select 'form.user'
end
test 'SimpleForm adds custom class to form if css_class is specified' do
with_concat_form_for(:user, html: { class: 'my_class' })
assert_select 'form.my_class'
end
test 'passes options to SimpleForm' do
with_concat_form_for(:user, url: '/account', html: { id: 'my_form' })
assert_select 'form#my_form'
assert_select 'form[action="/account"]'
end
test 'form_for yields an instance of FormBuilder' do
with_concat_form_for(:user) do |f|
assert f.instance_of?(SimpleForm::FormBuilder)
end
end
test 'fields_for with a hash like model yields an instance of FormBuilder' do
with_concat_fields_for(:author, HashBackedAuthor.new) do |f|
assert f.instance_of?(SimpleForm::FormBuilder)
f.input :name
end
assert_select "input[name='author[name]'][value='hash backed author']"
end
test 'custom error proc is not destructive' do
swap_field_error_proc do
result = nil
simple_form_for :user do |f|
result = simple_fields_for 'address' do
'hello'
end
end
assert_equal 'hello', result
end
end
test 'custom error proc survives an exception' do
swap_field_error_proc do
begin
simple_form_for :user do |f|
simple_fields_for 'address' do
raise 'an exception'
end
end
rescue StandardError
end
end
end
test 'SimpleForm for swaps default action view field_error_proc' do
expected_error_proc = -> {}
swap SimpleForm, field_error_proc: expected_error_proc do
simple_form_for :user do |f|
assert_equal expected_error_proc, ::ActionView::Base.field_error_proc
end
end
end
private
def swap_field_error_proc(expected_error_proc = -> {})
swap ActionView::Base, field_error_proc: expected_error_proc do
yield
assert_equal expected_error_proc, ActionView::Base.field_error_proc
end
end
end
================================================
FILE: test/components/custom_components_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
# Module that represents a custom component.
module Numbers
def number(wrapper_options = nil)
@number ||= options[:number].to_s.html_safe
end
end
# Module that represents a custom component.
module InputGroup
def prepend(wrapper_options = nil)
span_tag = content_tag(:span, options[:prepend], class: 'input-group-text')
template.content_tag(:div, span_tag, class: 'input-group-prepend')
end
def append(wrapper_options = nil)
span_tag = content_tag(:span, options[:append], class: 'input-group-text')
template.content_tag(:div, span_tag, class: 'input-group-append')
end
end
class CustomComponentsTest < ActionView::TestCase
test 'includes the custom components' do
SimpleForm.include_component Numbers
custom_wrapper = SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :number, wrap_with: { tag: 'div', class: 'number' }
end
with_form_for @user, :name, number: 1, wrapper: custom_wrapper
assert_select 'div.number', text: '1'
end
test 'includes custom components and use it as optional in the wrapper' do
SimpleForm.include_component InputGroup
custom_wrapper = SimpleForm.build tag: :div, class: 'custom_wrapper' do |b|
b.use :label
b.optional :prepend
b.use :input
b.use :append
end
with_form_for @user, :name, prepend: true, wrapper: custom_wrapper
assert_select 'div.input-group-prepend > span.input-group-text'
assert_select 'div.input-group-append > span.input-group-text'
end
test 'raises a TypeError when the component is not a Module' do
component = 'MyComponent'
exception = assert_raises TypeError do
SimpleForm.include_component(component)
end
assert_equal exception.message, "SimpleForm.include_component expects a module but got: String"
end
end
================================================
FILE: test/components/label_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
# Isolated tests for label without triggering f.label.
class IsolatedLabelTest < ActionView::TestCase
def with_label_for(object, attribute_name, type, options = {})
with_concat_form_for(object) do |f|
options[:reflection] = Association.new(Company, :company, {}) if options.delete(:setup_association)
SimpleForm::Inputs::Base.new(f, attribute_name, nil, type, options).label
end
end
test 'label generates a default humanized description' do
with_label_for @user, :name, :string
assert_select 'label[for=user_name]', /Name/
end
test 'label allows a customized description' do
with_label_for @user, :name, :string, label: 'My label!'
assert_select 'label[for=user_name]', /My label!/
end
test 'label uses human attribute name from object when available' do
with_label_for @user, :description, :text
assert_select 'label[for=user_description]', /User Description!/
end
test 'label uses human_attribute_name and passed object as an option to it' do
with_label_for @user, :status, :text
assert_select 'label[for=user_status]', /\[#{@user.id}\] User Status!/
end
test 'label uses human attribute name based on association name' do
with_label_for @user, :company_id, :string, setup_association: true
assert_select 'label', /Company Human Name!/
end
test 'label uses i18n based on model, action, and attribute to lookup translation' do
@controller.action_name = "new"
store_translations(:en, simple_form: { labels: { user: {
new: { description: 'Nova descrição' }
} } }) do
with_label_for @user, :description, :text
assert_select 'label[for=user_description]', /Nova descrição/
end
end
test 'label fallbacks to new when action is create' do
@controller.action_name = "create"
store_translations(:en, simple_form: { labels: { user: {
new: { description: 'Nova descrição' }
} } }) do
with_label_for @user, :description, :text
assert_select 'label[for=user_description]', /Nova descrição/
end
end
test 'label does not explode while looking for i18n translation when action is not set' do
def @controller.action_name; nil; end
assert_nothing_raised do
with_label_for @user, :description, :text
end
assert_select 'label[for=user_description]'
end
test 'label uses i18n based on model and attribute to lookup translation' do
store_translations(:en, simple_form: { labels: { user: {
description: 'Descrição'
} } }) do
with_label_for @user, :description, :text
assert_select 'label[for=user_description]', /Descrição/
end
end
test 'label uses i18n under defaults to lookup translation' do
store_translations(:en, simple_form: { labels: { defaults: { age: 'Idade' } } }) do
with_label_for @user, :age, :integer
assert_select 'label[for=user_age]', /Idade/
end
end
test 'label does not use i18n label if translate is false' do
swap SimpleForm, translate_labels: false do
store_translations(:en, simple_form: { labels: { defaults: { age: 'Idade' } } }) do
with_label_for @user, :age, :integer
assert_select 'label[for=user_age]', /Age/
end
end
end
test 'label uses i18n with lookup for association name' do
store_translations(:en, simple_form: { labels: {
user: { company: 'My company!' }
} }) do
with_label_for @user, :company_id, :string, setup_association: true
assert_select 'label[for=user_company_id]', /My company!/
end
end
test 'label uses i18n under defaults namespace to lookup for association name' do
store_translations(:en, simple_form: { labels: {
defaults: { company: 'Plataformatec' }
} }) do
with_label_for @user, :company, :string, setup_association: true
assert_select 'form label', /Plataformatec/
end
end
test 'label does correct i18n lookup for nested models with nested translation' do
@user.company = Company.new(1, 'Empresa')
store_translations(:en, simple_form: { labels: {
user: { name: 'Usuario', company: { name: 'Nome da empresa' } }
} }) do
with_concat_form_for @user do |f|
concat f.input :name
concat(f.simple_fields_for(:company) do |company_form|
concat(company_form.input :name)
end)
end
assert_select 'label[for=user_name]', /Usuario/
assert_select 'label[for=user_company_attributes_name]', /Nome da empresa/
end
end
test 'label does correct i18n lookup for nested models with no nested translation' do
@user.company = Company.new(1, 'Empresa')
store_translations(:en, simple_form: { labels: {
user: { name: 'Usuario' },
company: { name: 'Nome da empresa' }
} }) do
with_concat_form_for @user do |f|
concat f.input :name
concat(f.simple_fields_for(:company) do |company_form|
concat(company_form.input :name)
end)
end
assert_select 'label[for=user_name]', /Usuario/
assert_select 'label[for=user_company_attributes_name]', /Nome da empresa/
end
end
test 'label does correct i18n lookup for nested has_many models with no nested translation' do
@user.tags = [Tag.new(1, 'Empresa')]
store_translations(:en, simple_form: { labels: {
user: { name: 'Usuario' },
tags: { name: 'Nome da empresa' }
} }) do
with_concat_form_for @user do |f|
concat f.input :name
concat(f.simple_fields_for(:tags, child_index: "new_index") do |tags_form|
concat(tags_form.input :name)
end)
end
assert_select 'label[for=user_name]', /Usuario/
assert_select 'label[for=user_tags_attributes_new_index_name]', /Nome da empresa/
end
end
test 'label has css class from type' do
with_label_for @user, :name, :string
assert_select 'label.string'
with_label_for @user, :description, :text
assert_select 'label.text'
with_label_for @user, :age, :integer
assert_select 'label.integer'
with_label_for @user, :born_at, :date
assert_select 'label.date'
with_label_for @user, :created_at, :datetime
assert_select 'label.datetime'
end
test 'label does not have css class from type when generate_additional_classes_for does not include :label' do
swap SimpleForm, generate_additional_classes_for: %i[wrapper input] do
with_label_for @user, :name, :string
assert_no_select 'label.string'
with_label_for @user, :description, :text
assert_no_select 'label.text'
with_label_for @user, :age, :integer
assert_no_select 'label.integer'
with_label_for @user, :born_at, :date
assert_no_select 'label.date'
with_label_for @user, :created_at, :datetime
assert_no_select 'label.datetime'
end
end
test 'label does not generate empty css class' do
swap SimpleForm, generate_additional_classes_for: %i[wrapper input] do
with_label_for @user, :name, :string
assert_no_select 'label[class]'
end
end
test 'label obtains required from ActiveModel::Validations when it is included' do
with_label_for @validating_user, :name, :string
assert_select 'label.required'
with_label_for @validating_user, :status, :string
assert_select 'label.optional'
end
test 'label does not obtain required from ActiveModel::Validations when generate_additional_classes_for does not include :label' do
swap SimpleForm, generate_additional_classes_for: %i[wrapper input] do
with_label_for @validating_user, :name, :string
assert_no_select 'label.required'
with_label_for @validating_user, :status, :string
assert_no_select 'label.optional'
end
end
test 'label allows overriding required when ActiveModel::Validations is included' do
with_label_for @validating_user, :name, :string, required: false
assert_select 'label.optional'
with_label_for @validating_user, :status, :string, required: true
assert_select 'label.required'
end
test 'label is required by default when ActiveModel::Validations is not included' do
with_label_for @user, :name, :string
assert_select 'label.required'
end
test 'label is able to disable required when ActiveModel::Validations is not included' do
with_label_for @user, :name, :string, required: false
assert_no_select 'label.required'
end
test 'label adds required text when required' do
with_label_for @user, :name, :string
assert_select 'label.required abbr[title=required]', '*'
end
test 'label does not have required text in no required inputs' do
with_label_for @user, :name, :string, required: false
assert_no_select 'form label abbr'
end
test 'label uses i18n to find required text' do
store_translations(:en, simple_form: { required: { text: 'campo requerido' } }) do
with_label_for @user, :name, :string
assert_select 'form label abbr[title="campo requerido"]', '*'
end
end
test 'label uses custom i18n scope to find required text' do
store_translations(:en, my_scope: { required: { text: 'Pflichtfeld' } }) do
swap SimpleForm, i18n_scope: :my_scope do
with_label_for @user, :name, :string
assert_select 'form label abbr[title="Pflichtfeld"]', '*'
end
end
end
test 'label uses i18n to find required mark' do
store_translations(:en, simple_form: { required: { mark: '*-*' } }) do
with_label_for @user, :name, :string
assert_select 'form label abbr', '*-*'
end
end
test 'label uses custom i18n scope to find required mark' do
store_translations(:en, my_scope: { required: { mark: '!!' } }) do
swap SimpleForm, i18n_scope: :my_scope do
with_label_for @user, :name, :string
assert_select 'form label abbr', '!!'
end
end
end
test 'label uses i18n to find required string tag' do
store_translations(:en, simple_form: { required: { html: '* ' } }) do
with_label_for @user, :name, :string
assert_no_select 'form label abbr'
assert_select 'form label span.required[title=requerido]', '*'
end
end
test 'label uses custom i18n scope to find required string tag' do
store_translations(:en, my_scope: { required: { html: '!! ' } }) do
swap SimpleForm, i18n_scope: :my_scope do
with_label_for @user, :name, :string
assert_no_select 'form label abbr'
assert_select 'form label span.mandatory[title=Pflichtfeld]', '!!'
end
end
end
test 'label allows overwriting input id' do
with_label_for @user, :name, :string, input_html: { id: 'my_new_id' }
assert_select 'label[for=my_new_id]'
end
test 'label allows overwriting of for attribute' do
with_label_for @user, :name, :string, label_html: { for: 'my_new_id' }
assert_select 'label[for=my_new_id]'
end
test 'label allows overwriting of for attribute with input_html not containing id' do
with_label_for @user, :name, :string, label_html: { for: 'my_new_id' }, input_html: { class: 'foo' }
assert_select 'label[for=my_new_id]'
end
test 'label uses default input id when it was not overridden' do
with_label_for @user, :name, :string, input_html: { class: 'my_new_id' }
assert_select 'label[for=user_name]'
end
test 'label is generated properly when object is not present' do
with_label_for :project, :name, :string
assert_select 'label[for=project_name]', /Name/
end
test 'label includes for attribute for select collection' do
with_label_for @user, :sex, :select, collection: %i[male female]
assert_select 'label[for=user_sex]'
end
test 'label uses i18n properly when object is not present' do
store_translations(:en, simple_form: { labels: {
project: { name: 'Nome' }
} }) do
with_label_for :project, :name, :string
assert_select 'label[for=project_name]', /Nome/
end
end
test 'label adds required by default when object is not present' do
with_label_for :project, :name, :string
assert_select 'label.required[for=project_name]'
with_label_for :project, :description, :string, required: false
assert_no_select 'label.required[for=project_description]'
end
test 'label adds chosen label class' do
swap SimpleForm, label_class: :my_custom_class do
with_label_for @user, :name, :string
assert_select 'label.my_custom_class'
end
end
test 'label strips extra classes even when label_class is nil' do
swap SimpleForm, label_class: nil do
with_label_for @user, :name, :string
assert_select "label[class='string required']"
assert_no_select "label[class='string required ']"
assert_no_select "label[class=' string required']"
end
end
end
================================================
FILE: test/form_builder/association_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class AssociationTest < ActionView::TestCase
def with_association_for(object, *args)
with_concat_form_for(object) do |f|
f.association(*args)
end
end
test 'builder does not allow creating an association input when no object exists' do
assert_raise ArgumentError do
with_association_for :post, :author
end
end
test 'builder association works with decorated object responsive to #to_model' do
assert_nothing_raised do
with_association_for @decorated_user, :company
end
end
test 'builder association with a block calls simple_fields_for' do
simple_form_for @user do |f|
f.association :posts do |posts_form|
assert posts_form.instance_of?(SimpleForm::FormBuilder)
end
end
end
test 'builder association forwards collection to simple_fields_for' do
calls = 0
simple_form_for @user do |f|
f.association :company, collection: Company.all do |c|
calls += 1
end
end
assert_equal 3, calls
end
test 'builder association marks input as required based on both association and attribute' do
swap SimpleForm, required_by_default: false do
with_association_for @validating_user, :company, collection: []
assert_select 'label.required'
end
end
test 'builder preloads collection association' do
value = @user.tags = Minitest::Mock.new
value.expect(:to_a, value)
with_association_for @user, :tags
assert_select 'form select.select#user_tag_ids'
assert_select 'form select option[value="1"]', 'Tag 1'
assert_select 'form select option[value="2"]', 'Tag 2'
assert_select 'form select option[value="3"]', 'Tag 3'
value.verify
end
test 'builder does not preload collection association if preload is false' do
value = @user.tags = Minitest::Mock.new
value.expect(:to_a, nil)
with_association_for @user, :tags, preload: false
assert_select 'form select.select#user_tag_ids'
assert_select 'form select option[value="1"]', 'Tag 1'
assert_select 'form select option[value="2"]', 'Tag 2'
assert_select 'form select option[value="3"]', 'Tag 3'
assert_raises MockExpectationError do
value.verify
end
end
test 'builder does not preload non-collection association' do
value = @user.company = Minitest::Mock.new
value.expect(:to_a, nil)
with_association_for @user, :company
assert_select 'form select.select#user_company_id'
assert_select 'form select option[value="1"]', 'Company 1'
assert_select 'form select option[value="2"]', 'Company 2'
assert_select 'form select option[value="3"]', 'Company 3'
assert_raises MockExpectationError do
value.verify
end
end
# ASSOCIATIONS - BELONGS TO
test 'builder creates a select for belongs_to associations' do
with_association_for @user, :company
assert_select 'form select.select#user_company_id'
assert_select 'form select option[value="1"]', 'Company 1'
assert_select 'form select option[value="2"]', 'Company 2'
assert_select 'form select option[value="3"]', 'Company 3'
end
test 'builder creates blank select if collection is nil' do
with_association_for @user, :company, collection: nil
assert_select 'form select.select#user_company_id'
assert_no_select 'form select option[value="1"]', 'Company 1'
end
test 'builder allows collection radio for belongs_to associations' do
with_association_for @user, :company, as: :radio_buttons
assert_select 'form input.radio_buttons#user_company_id_1'
assert_select 'form input.radio_buttons#user_company_id_2'
assert_select 'form input.radio_buttons#user_company_id_3'
end
test 'builder allows collection to have a proc as a condition' do
with_association_for @user, :extra_special_company
assert_select 'form select.select#user_extra_special_company_id'
assert_select 'form select option[value="1"]'
assert_no_select 'form select option[value="2"]'
assert_no_select 'form select option[value="3"]'
end
test 'builder allows collection to have a scope' do
with_association_for @user, :special_pictures
assert_select 'form select.select#user_special_picture_ids'
assert_select 'form select option[value="3"]', '3'
assert_no_select 'form select option[value="1"]'
assert_no_select 'form select option[value="2"]'
end
test 'builder allows collection to have a scope with parameter' do
with_association_for @user, :special_tags
assert_select 'form select.select#user_special_tag_ids'
assert_select 'form select[multiple=multiple]'
assert_select 'form select option[value="1"]', 'Tag 1'
assert_no_select 'form select option[value="2"]'
assert_no_select 'form select option[value="3"]'
end
test 'builder marks the record which already belongs to the user' do
@user.company_id = 2
with_association_for @user, :company, as: :radio_buttons
assert_no_select 'form input.radio_buttons#user_company_id_1[checked=checked]'
assert_select 'form input.radio_buttons#user_company_id_2[checked=checked]'
assert_no_select 'form input.radio_buttons#user_company_id_3[checked=checked]'
end
# ASSOCIATIONS - FINDERS
test 'builder uses reflection conditions to find collection' do
with_association_for @user, :special_company
assert_select 'form select.select#user_special_company_id'
assert_select 'form select option[value="1"]'
assert_no_select 'form select option[value="2"]'
assert_no_select 'form select option[value="3"]'
end
test 'builder allows overriding collection to association input' do
with_association_for @user, :company, include_blank: false,
collection: [Company.new(999, 'Teste')]
assert_select 'form select.select#user_company_id'
assert_no_select 'form select option[value="1"]'
assert_select 'form select option[value="999"]', 'Teste'
assert_select 'form select option', count: 1
end
# ASSOCIATIONS - has_*
test 'builder does not allow has_one associations' do
assert_raise ArgumentError do
with_association_for @user, :first_company, as: :radio_buttons
end
end
test 'builder does not call where if the given association does not respond to it' do
with_association_for @user, :friends
assert_select 'form select.select#user_friend_ids'
assert_select 'form select[multiple=multiple]'
assert_select 'form select option[value="1"]', 'Friend 1'
assert_select 'form select option[value="2"]', 'Friend 2'
assert_select 'form select option[value="3"]', 'Friend 3'
end
test 'builder does not call order if the given association does not respond to it' do
with_association_for @user, :pictures
assert_select 'form select.select#user_picture_ids'
assert_select 'form select[multiple=multiple]'
assert_select 'form select option[value="1"]', 'Picture 1'
assert_select 'form select option[value="2"]', 'Picture 2'
assert_select 'form select option[value="3"]', 'Picture 3'
end
test 'builder creates a select with multiple options for collection associations' do
with_association_for @user, :tags
assert_select 'form select.select#user_tag_ids'
assert_select 'form select[multiple=multiple]'
assert_select 'form select option[value="1"]', 'Tag 1'
assert_select 'form select option[value="2"]', 'Tag 2'
assert_select 'form select option[value="3"]', 'Tag 3'
end
test 'builder allows size to be overwritten for collection associations' do
with_association_for @user, :tags, input_html: { size: 10 }
assert_select 'form select[multiple=multiple][size="10"]'
end
test 'builder marks all selected records which already belongs to user' do
@user.tag_ids = [1, 2]
with_association_for @user, :tags
assert_select 'form select option[value="1"][selected=selected]'
assert_select 'form select option[value="2"][selected=selected]'
assert_no_select 'form select option[value="3"][selected=selected]'
end
test 'builder allows a collection of check boxes for collection associations' do
@user.tag_ids = [1, 2]
with_association_for @user, :tags, as: :check_boxes
assert_select 'form input#user_tag_ids_1[type=checkbox]'
assert_select 'form input#user_tag_ids_2[type=checkbox]'
assert_select 'form input#user_tag_ids_3[type=checkbox]'
end
test 'builder marks all selected records for collection boxes' do
@user.tag_ids = [1, 2]
with_association_for @user, :tags, as: :check_boxes
assert_select 'form input[type=checkbox][value="1"][checked=checked]'
assert_select 'form input[type=checkbox][value="2"][checked=checked]'
assert_no_select 'form input[type=checkbox][value="3"][checked=checked]'
end
test 'builder with collection support giving collection and item wrapper tags' do
with_association_for @user, :tags, as: :check_boxes,
collection_wrapper_tag: :ul, item_wrapper_tag: :li
assert_select 'form ul', count: 1
assert_select 'form ul li', count: 3
end
test 'builder with collection support does not change the options hash' do
options = { as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li }
with_association_for @user, :tags, options
assert_select 'form ul', count: 1
assert_select 'form ul li', count: 3
assert_equal({ as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li },
options)
end
test 'builder with group select considers multiple select by default' do
with_association_for @user, :tags, as: :grouped_select, group_method: :group_method
assert_select 'select[multiple="multiple"].grouped_select'
end
end
================================================
FILE: test/form_builder/button_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class ButtonTest < ActionView::TestCase
def with_button_for(object, *args)
with_concat_form_for(object) do |f|
f.button(*args)
end
end
test 'builder creates buttons' do
with_button_for :post, :submit
assert_select 'form input.button[type=submit][value="Save Post"]'
end
test 'builder creates buttons with options' do
with_button_for :post, :submit, class: 'my_button'
assert_select 'form input.button.my_button[type=submit][value="Save Post"]'
end
test 'builder does not modify the options hash' do
options = { class: 'my_button' }
with_button_for :post, :submit, options
assert_select 'form input.button.my_button[type=submit][value="Save Post"]'
assert_equal({ class: 'my_button' }, options)
end
test 'builder creates buttons for records' do
@user.new_record!
with_button_for @user, :submit
assert_select 'form input.button[type=submit][value="Create User"]'
end
test "builder uses the default class from the configuration" do
swap SimpleForm, button_class: 'btn' do
with_button_for :post, :submit
assert_select 'form input.btn[type=submit][value="Save Post"]'
end
end
if ActionView::Helpers::FormBuilder.method_defined?(:button)
test "allows to use Rails button helper when available" do
with_button_for :post, :button, 'Save!'
assert_select 'form button.button[type=submit]', 'Save!'
end
end
end
================================================
FILE: test/form_builder/error_notification_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
# Tests for f.error_notification
class ErrorNotificationTest < ActionView::TestCase
def with_error_notification_for(object, options = {}, &block)
with_concat_form_for(object) do |f|
f.error_notification(options)
end
end
test 'error notification is not generated when the object has no error' do
assert @validating_user.valid?
with_error_notification_for @validating_user
assert_no_select 'p.error_notification'
end
test 'error notification is not generated for forms without objects' do
with_error_notification_for :user
assert_no_select 'p.error_notification'
end
test 'error notification is generated when the object has some error' do
with_error_notification_for @user
assert_select 'p.error_notification', 'Please review the problems below:'
end
test 'error notification uses I18n based on model to generate the notification message' do
store_translations(:en, simple_form: { error_notification: { user:
'Alguns erros foram encontrados para o usuário:'
} }) do
with_error_notification_for @user
assert_select 'p.error_notification', 'Alguns erros foram encontrados para o usuário:'
end
end
test 'error notification uses I18n fallbacking to default message' do
store_translations(:en, simple_form: { error_notification: {
default_message: 'Opa! Alguns erros foram encontrados, poderia verificar?'
} }) do
with_error_notification_for @user
assert_select 'p.error_notification', 'Opa! Alguns erros foram encontrados, poderia verificar?'
end
end
test 'error notification allows passing the notification message' do
with_error_notification_for @user, message: 'Erro encontrado ao criar usuario'
assert_select 'p.error_notification', 'Erro encontrado ao criar usuario'
end
test 'error notification accepts other html options' do
with_error_notification_for @user, id: 'user_error_message', class: 'form_error'
assert_select 'p#user_error_message.form_error.error_notification'
end
test 'error notification allows configuring the wrapper element' do
swap SimpleForm, error_notification_tag: :div do
with_error_notification_for @user
assert_select 'div.error_notification'
end
end
test 'error notification can contain HTML tags' do
with_error_notification_for @user, message: 'Erro encontrado ao criar usuário '
assert_select 'p.error_notification', 'Erro encontrado ao criar usuário'
assert_select 'p.error_notification b', 'usuário'
end
test 'error notification uses I18n based on model to generate the notification message and accepts HTML' do
store_translations(:en, simple_form: { error_notification: { user:
'Alguns erros foram encontrados para o usuário :'
} }) do
with_error_notification_for @user
assert_select 'p.error_notification', 'Alguns erros foram encontrados para o usuário:'
assert_select 'p.error_notification b', 'usuário'
end
end
end
================================================
FILE: test/form_builder/error_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
# Tests for f.error and f.full_error
class ErrorTest < ActionView::TestCase
def with_error_for(object, *args)
with_concat_form_for(object) do |f|
f.error(*args)
end
end
def with_full_error_for(object, *args)
with_concat_form_for(object) do |f|
f.full_error(*args)
end
end
test 'error does not generate content for attribute without errors' do
with_error_for @user, :active
assert_no_select 'span.error'
end
test 'error does not generate messages when object is not present' do
with_error_for :project, :name
assert_no_select 'span.error'
end
test "error does not generate messages when object doesn't respond to errors method" do
@user.instance_eval { undef errors }
with_error_for @user, :name
assert_no_select 'span.error'
end
test 'error generates messages for attribute with single error' do
with_error_for @user, :name
assert_select 'span.error', "cannot be blank"
end
test 'error generates messages with decorated object responsive to #to_model' do
with_error_for @decorated_user, :name
assert_select 'span.error', "cannot be blank"
end
test 'error generates messages for attribute with one error when using first' do
swap SimpleForm, error_method: :first do
with_error_for @user, :age
assert_select 'span.error', 'is not a number'
end
end
test 'error generates messages for attribute with several errors when using to_sentence' do
swap SimpleForm, error_method: :to_sentence do
with_error_for @user, :age
assert_select 'span.error', 'is not a number and must be greater than 18'
end
end
test 'error is able to pass html options' do
with_error_for @user, :name, id: 'error', class: 'yay'
assert_select 'span#error.error.yay'
end
test 'error does not modify the options hash' do
options = { id: 'error', class: 'yay' }
with_error_for @user, :name, options
assert_select 'span#error.error.yay'
assert_equal({ id: 'error', class: 'yay' }, options)
end
test 'error finds errors on attribute and association' do
with_error_for @user, :company_id, as: :select,
error_method: :to_sentence, reflection: Association.new(Company, :company, {})
assert_select 'span.error', 'must be valid and company must be present'
end
test 'error generates an error tag with a clean HTML' do
with_error_for @user, :name
assert_no_select 'span.error[error_html]'
end
test 'error generates an error tag with a clean HTML when errors options are present' do
with_error_for @user, :name, error_tag: :p, error_prefix: 'Name', error_method: :first
assert_no_select 'p.error[error_html]'
assert_no_select 'p.error[error_tag]'
assert_no_select 'p.error[error_prefix]'
assert_no_select 'p.error[error_method]'
end
test 'error escapes error prefix text' do
with_error_for @user, :name, error_prefix: 'Name '
assert_no_select 'span.error b'
end
test 'error escapes error text' do
@user.errors.add(:action, 'must not contain markup ')
with_error_for @user, :action
assert_select 'span.error'
assert_no_select 'span.error b', 'markup'
end
test 'error generates an error message with raw HTML tags' do
with_error_for @user, :name, error_prefix: 'Name '.html_safe
assert_select 'span.error', "Name cannot be blank"
assert_select 'span.error b', "Name"
end
test 'error adds aria-invalid attribute to inputs' do
with_form_for @user, :name, error: true
assert_select "input#user_name[name='user[name]'][aria-invalid='true']"
with_form_for @user, :name, as: :text, error: true
assert_select "textarea#user_name[name='user[name]'][aria-invalid='true']"
@user.errors.add(:active, 'must select one')
with_form_for @user, :active, as: :radio_buttons
assert_select "input#user_active_true[type=radio][name='user[active]'][aria-invalid='true']"
assert_select "input#user_active_false[type=radio][name='user[active]'][aria-invalid='true']"
with_form_for @user, :active, as: :check_boxes
assert_select "input#user_active_true[type=checkbox][aria-invalid='true']"
assert_select "input#user_active_false[type=checkbox][aria-invalid='true']"
with_form_for @user, :company_id, as: :select, error: true
assert_select "select#user_company_id[aria-invalid='true']"
@user.errors.add(:password, 'must not be blank')
with_form_for @user, :password
assert_select "input#user_password[type=password][aria-invalid='true']"
end
# FULL ERRORS
test 'full error generates a full error tag for the attribute' do
with_full_error_for @user, :name
assert_select 'span.error', "Super User Name! cannot be blank"
end
test 'full error generates a full error tag with a clean HTML' do
with_full_error_for @user, :name
assert_no_select 'span.error[error_html]'
end
test 'full error allows passing options to full error tag' do
with_full_error_for @user, :name, id: 'name_error', error_prefix: "Your name"
assert_select 'span.error#name_error', "Your name cannot be blank"
end
test 'full error does not modify the options hash' do
options = { id: 'name_error' }
with_full_error_for @user, :name, options
assert_select 'span.error#name_error', "Super User Name! cannot be blank"
assert_equal({ id: 'name_error' }, options)
end
test 'full error escapes error text' do
@user.errors.add(:action, 'must not contain markup ')
with_full_error_for @user, :action
assert_select 'span.error'
assert_no_select 'span.error b', 'markup'
end
test 'full error uses human_attribute_name and passed object as an option to it' do
@user.errors.add(:status, 'error')
with_full_error_for @user, :status
assert_select 'span.error', "\[#{@user.id}\] User Status! error"
end
# CUSTOM WRAPPERS
test 'error with custom wrappers works' do
swap_wrapper do
with_error_for @user, :name
assert_select 'span.omg_error', "cannot be blank"
end
end
# FULL_ERROR_WRAPPER
test 'full error finds errors on association' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :company_id, as: :select
assert_select 'span.error', 'Company must be valid'
end
end
test 'full error finds errors on association with reflection' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :company_id, as: :select,
reflection: Association.new(Company, :company, {})
assert_select 'span.error', 'Company must be valid'
end
end
test 'full error can be disabled' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :company_id, as: :select, full_error: false
assert_no_select 'span.error'
end
end
test 'full error can be disabled setting error to false' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :company_id, as: :select, error: false
assert_no_select 'span.error'
end
end
# CUSTOM ERRORS
test 'input with custom error works' do
error_text = "Super User Name! cannot be blank"
with_form_for @user, :name, error: error_text
assert_select 'span.error', error_text
end
test 'input with error option as true does not use custom error' do
with_form_for @user, :name, error: true
assert_select 'span.error', "cannot be blank"
end
test 'input with custom error does not generate the error if there is no error on the attribute' do
with_form_for @user, :active, error: "Super User Active! cannot be blank"
assert_no_select 'span.error'
end
test 'input with custom error works when form does not use a model' do
with_form_for :user, :active, error: "Super User Active! cannot be blank"
assert_select 'span.error'
end
test 'input with custom error works when using full_error component' do
swap_wrapper :default, custom_wrapper_with_full_error do
error_text = "Super User Name! cannot be blank"
with_form_for @user, :name, error: error_text
assert_select 'span.error', error_text
end
end
test 'input with custom error escapes the error text' do
with_form_for @user, :name, error: 'error must not contain markup '
assert_select 'span.error'
assert_no_select 'span.error b', 'markup'
end
test 'input with custom error does not escape the error text if it is safe' do
with_form_for @user, :name, error: 'error must contain markup '.html_safe
assert_select 'span.error'
assert_select 'span.error b', 'markup'
end
test 'input with custom error escapes the error text using full_error component' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :name, error: 'error must not contain markup '
assert_select 'span.error'
assert_no_select 'span.error b', 'markup'
end
end
test 'input with custom error does not escape the error text if it is safe using full_error component' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :name, error: 'error must contain markup '.html_safe
assert_select 'span.error'
assert_select 'span.error b', 'markup'
end
end
test 'input with custom error when using full_error component does not generate the error if there is no error on the attribute' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :active, error: "Super User Active! can't be blank"
assert_no_select 'span.error'
end
end
end
================================================
FILE: test/form_builder/general_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class FormBuilderTest < ActionView::TestCase
def with_custom_form_for(object, *args, &block)
with_concat_custom_form_for(object) do |f|
f.input(*args, &block)
end
end
test 'nested simple fields yields an instance of FormBuilder' do
simple_form_for :user do |f|
f.simple_fields_for :posts do |posts_form|
assert posts_form.instance_of?(SimpleForm::FormBuilder)
end
end
end
test 'builder input is html safe' do
simple_form_for @user do |f|
assert f.input(:name).html_safe?
end
end
test 'builder works without controller' do
stub_any_instance ActionView::TestCase, :controller, nil do
simple_form_for @user do |f|
assert f.input(:name)
end
end
end
test 'builder works with decorated object responsive to #to_model' do
assert_nothing_raised do
with_form_for @decorated_user, :name
end
end
test 'builder input allows a block to configure input' do
with_form_for @user, :name do
text_field_tag :foo, :bar, id: :cool
end
assert_no_select 'input.string'
assert_select 'input#cool'
end
test 'builder allows adding custom input mappings for default input types' do
swap SimpleForm, input_mappings: { /count$/ => :integer } do
with_form_for @user, :post_count
assert_no_select 'form input#user_post_count.string'
assert_select 'form input#user_post_count.numeric.integer'
end
end
test 'builder does not override custom input mappings for custom collection' do
swap SimpleForm, input_mappings: { /gender$/ => :check_boxes } do
with_concat_form_for @user do |f|
f.input :gender, collection: %i[male female]
end
assert_no_select 'select option', 'Male'
assert_select 'input[type=checkbox][value=male]'
end
end
test 'builder allows to skip input_type class' do
swap SimpleForm, generate_additional_classes_for: %i[label wrapper] do
with_form_for @user, :post_count
assert_no_select "form input#user_post_count.integer"
assert_select "form input#user_post_count"
end
end
test 'builder allows to add additional classes only for wrapper' do
swap SimpleForm, generate_additional_classes_for: [:wrapper] do
with_form_for @user, :post_count
assert_no_select "form input#user_post_count.string"
assert_no_select "form label#user_post_count.string"
assert_select "form div.input.string"
end
end
test 'builder allows adding custom input mappings for integer input types' do
swap SimpleForm, input_mappings: { /lock_version/ => :hidden } do
with_form_for @user, :lock_version
assert_no_select 'form input#user_lock_version.integer'
assert_select 'form input#user_lock_version.hidden'
end
end
test 'builder uses the first matching custom input map when more than one matches' do
swap SimpleForm, input_mappings: { /count$/ => :integer, /^post_/ => :password } do
with_form_for @user, :post_count
assert_no_select 'form input#user_post_count.password'
assert_select 'form input#user_post_count.numeric.integer'
end
end
test 'builder uses the custom map only for matched attributes' do
swap SimpleForm, input_mappings: { /lock_version/ => :hidden } do
with_form_for @user, :post_count
assert_no_select 'form input#user_post_count.hidden'
assert_select 'form input#user_post_count.string'
end
end
test 'builder allow to use numbers in the model name' do
user = UserNumber1And2.build(tags: [Tag.new(nil, 'Tag1')])
with_concat_form_for(user, url: '/') do |f|
f.simple_fields_for(:tags) do |tags|
tags.input :name
end
end
assert_select 'form .user_number1_and2_tags_name'
assert_no_select 'form .user_number1_and2_tags_1_name'
end
# INPUT TYPES
test 'builder generates text fields for string columns' do
with_form_for @user, :name
assert_select 'form input#user_name.string'
end
test 'builder generates text areas for text columns' do
with_form_for @user, :description
assert_no_select 'form input#user_description.string'
assert_select 'form textarea#user_description.text'
end
test 'builder generates text areas for text columns when hinted' do
with_form_for @user, :description, as: :text
assert_no_select 'form input#user_description.string'
assert_select 'form textarea#user_description.text'
end
test 'builder generates text field for text columns when hinted' do
with_form_for @user, :description, as: :string
assert_no_select 'form textarea#user_description.text'
assert_select 'form input#user_description.string'
end
test 'builder generates text areas for hstore columns' do
with_form_for @user, :hstore
assert_no_select 'form input#user_hstore.string'
assert_select 'form textarea#user_hstore.text'
end
test 'builder generates text areas for json columns' do
with_form_for @user, :json
assert_no_select 'form input#user_json.string'
assert_select 'form textarea#user_json.text'
end
test 'builder generates text areas for jsonb columns' do
with_form_for @user, :jsonb
assert_no_select 'form input#user_jsonb.string'
assert_select 'form textarea#user_jsonb.text'
end
test 'builder generates a checkbox for boolean columns' do
with_form_for @user, :active
assert_select 'form input[type=checkbox]#user_active.boolean'
end
test 'builder uses integer text field for integer columns' do
with_form_for @user, :age
assert_select 'form input#user_age.numeric.integer'
end
test 'builder generates decimal text field for decimal columns' do
with_form_for @user, :credit_limit
assert_select 'form input#user_credit_limit.numeric.decimal'
end
test 'builder generates uuid fields for uuid columns' do
with_form_for @user, :uuid
if defined? ActiveModel::Type
assert_select 'form input#user_uuid.string.string'
else
assert_select 'form input#user_uuid.string.uuid'
end
end
test 'builder generates string fields for citext columns' do
with_form_for @user, :citext
assert_select 'form input#user_citext.string'
end
test 'builder generates password fields for columns that matches password' do
with_form_for @user, :password
assert_select 'form input#user_password.password'
end
test 'builder generates country fields for columns that matches country' do
with_form_for @user, :residence_country
assert_select 'form select#user_residence_country.country'
end
test 'builder generates time_zone fields for columns that matches time_zone' do
with_form_for @user, :time_zone
assert_select 'form select#user_time_zone.time_zone'
end
test 'builder generates email fields for columns that matches email' do
with_form_for @user, :email
assert_select 'form input#user_email.string.email'
end
test 'builder generates tel fields for columns that matches phone' do
with_form_for @user, :phone_number
assert_select 'form input#user_phone_number.string.tel'
end
test 'builder generates url fields for columns that matches url' do
with_form_for @user, :url
assert_select 'form input#user_url.string.url'
end
test 'builder generates date select for date columns' do
with_form_for @user, :born_at
assert_select 'form select#user_born_at_1i.date'
end
test 'builder generates time select for time columns' do
with_form_for @user, :delivery_time
assert_select 'form select#user_delivery_time_4i.time'
end
test 'builder generates datetime select for datetime columns' do
with_form_for @user, :created_at
assert_select 'form select#user_created_at_1i.datetime'
end
test 'builder generates datetime select for timestamp columns' do
with_form_for @user, :updated_at
assert_select 'form select#user_updated_at_1i.datetime'
end
test 'builder generates file input for ActiveStorage >= 5.2 and Refile >= 0.2.0 <= 0.4.0' do
with_form_for UserWithAttachment.build, :avatar
assert_select 'form input#user_with_attachment_avatar.file'
end
test 'builder generates file input for ActiveStorage::Attached::Many' do
with_form_for UserWithAttachment.build, :avatars
assert_select 'form input#user_with_attachment_avatars.file'
end
test 'builder generates file input for Refile >= 0.3.0 and CarrierWave >= 0.2.2' do
with_form_for UserWithAttachment.build, :cover
assert_select 'form input#user_with_attachment_cover.file'
end
test 'builder generates file input for Refile >= 0.4.0 and Shrine >= 0.9.0' do
with_form_for UserWithAttachment.build, :profile_image
assert_select 'form input#user_with_attachment_profile_image.file'
end
test 'builder generates file input for Paperclip ~> 2.0' do
with_form_for UserWithAttachment.build, :portrait
assert_select 'form input#user_with_attachment_portrait.file'
end
test 'build generates select if a collection is given' do
with_form_for @user, :age, collection: 1..60
assert_select 'form select#user_age.select'
end
test 'builder does not generate url fields for columns that contain only the letters url' do
with_form_for @user, :hourly
assert_no_select 'form input#user_url.string.url'
assert_select 'form input#user_hourly.string'
end
test 'builder allows overriding default input type for text' do
with_form_for @user, :name, as: :text
assert_no_select 'form input#user_name'
assert_select 'form textarea#user_name.text'
end
test 'builder allows overriding default input type for radio_buttons' do
with_form_for @user, :active, as: :radio_buttons
assert_no_select 'form input[type=checkbox]'
assert_select 'form input.radio_buttons[type=radio]', count: 2
end
test 'builder allows overriding default input type for string' do
with_form_for @user, :born_at, as: :string
assert_no_select 'form select'
assert_select 'form input#user_born_at.string'
end
# COMMON OPTIONS
# Remove this test when SimpleForm.form_class is removed in 4.x
test 'builder adds chosen form class' do
SimpleForm.deprecator.silence do
swap SimpleForm, form_class: :my_custom_class do
with_form_for @user, :name
assert_select 'form.my_custom_class'
end
end
end
# Remove this test when SimpleForm.form_class is removed in 4.x
test 'builder adds chosen form class and default form class' do
SimpleForm.deprecator.silence do
swap SimpleForm, form_class: "my_custom_class", default_form_class: "my_default_class" do
with_form_for @user, :name
assert_select 'form.my_custom_class.my_default_class'
end
end
end
test 'builder adds default form class' do
swap SimpleForm, default_form_class: "default_class" do
with_form_for @user, :name
assert_select 'form.default_class'
end
end
test 'builder allows passing options to input' do
with_form_for @user, :name, input_html: { class: 'my_input', id: 'my_input' }
assert_select 'form input#my_input.my_input.string'
end
test 'builder does not propagate input options to wrapper' do
with_form_for @user, :name, input_html: { class: 'my_input', id: 'my_input' }
assert_no_select 'form div.input.my_input.string'
assert_select 'form input#my_input.my_input.string'
end
test 'builder does not propagate input options to wrapper with custom wrapper' do
swap_wrapper :default, custom_wrapper_with_wrapped_input do
with_form_for @user, :name, input_html: { class: 'my_input' }
assert_no_select 'form div.input.my_input'
assert_select 'form input.my_input.string'
end
end
test 'builder does not propagate label options to wrapper with custom wrapper' do
swap_wrapper :default, custom_wrapper_with_wrapped_label do
with_form_for @user, :name, label_html: { class: 'my_label' }
assert_no_select 'form div.label.my_label'
assert_select 'form label.my_label.string'
end
end
test 'builder generates an input with label' do
with_form_for @user, :name
assert_select 'form label.string[for=user_name]', /Name/
end
test 'builder is able to disable the label for an input' do
with_form_for @user, :name, label: false
assert_no_select 'form label'
end
test 'builder is able to disable the label for an input and return a html safe string' do
with_form_for @user, :name, label: false, wrapper: custom_wrapper_with_wrapped_label_input
assert_select 'form input#user_name'
end
test 'builder uses custom label' do
with_form_for @user, :name, label: 'Yay!'
assert_select 'form label', /Yay!/
end
test 'builder passes options to label' do
with_form_for @user, :name, label_html: { id: "cool" }
assert_select 'form label#cool', /Name/
end
test 'builder does not generate hints for an input' do
with_form_for @user, :name
assert_no_select 'span.hint'
end
test 'builder is able to add a hint for an input' do
with_form_for @user, :name, hint: 'test'
assert_select 'span.hint', 'test'
end
test 'builder is able to disable a hint even if it exists in i18n' do
store_translations(:en, simple_form: { hints: { name: 'Hint test' } }) do
stub_any_instance(SimpleForm::Inputs::Base, :hint, -> { raise 'Never' }) do
with_form_for @user, :name, hint: false
assert_no_select 'span.hint'
end
end
end
test 'builder passes options to hint' do
with_form_for @user, :name, hint: 'test', hint_html: { id: "cool" }
assert_select 'span.hint#cool', 'test'
end
test 'builder generates errors for attribute without errors' do
with_form_for @user, :credit_limit
assert_no_select 'span.errors'
end
test 'builder generates errors for attribute with errors' do
with_form_for @user, :name
assert_select 'span.error', "cannot be blank"
end
test 'builder is able to disable showing errors for an input' do
with_form_for @user, :name, error: false
assert_no_select 'span.error'
end
test 'builder passes options to errors' do
with_form_for @user, :name, error_html: { id: "cool" }
assert_select 'span.error#cool', "cannot be blank"
end
test 'placeholder does not be generated when set to false' do
store_translations(:en, simple_form: { placeholders: { user: {
name: 'Name goes here'
} } }) do
with_form_for @user, :name, placeholder: false
assert_no_select 'input[placeholder]'
end
end
# DEFAULT OPTIONS
%i[input input_field].each do |method|
test "builder receives a default argument and pass it to the inputs when calling '#{method}'" do
with_concat_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f|
f.public_send(method, :name)
end
assert_select 'input.default_class'
end
test "builder receives a default argument and pass it to the inputs without changing the defaults when calling '#{method}'" do
with_concat_form_for @user, defaults: { input_html: { class: 'default_class', id: 'default_id' } } do |f|
concat(f.public_send(method, :name))
concat(f.public_send(method, :credit_limit))
end
assert_select "input.string.default_class[name='user[name]']"
assert_no_select "input.string[name='user[credit_limit]']"
end
test "builder receives a default argument and pass it to the inputs and nested form when calling '#{method}'" do
@user.company = Company.new(1, 'Empresa')
with_concat_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f|
concat(f.public_send(method, :name))
concat(f.simple_fields_for(:company) do |company_form|
concat(company_form.public_send(method, :name))
end)
end
assert_select "input.string.default_class[name='user[name]']"
assert_select "input.string.default_class[name='user[company_attributes][name]']"
end
end
test "builder receives a default argument and pass it to the inputs when calling 'input', respecting the specific options" do
with_concat_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f|
f.input :name, input_html: { id: 'specific_id' }
end
assert_select 'input.default_class#specific_id'
end
test "builder receives a default argument and pass it to the inputs when calling 'input_field', respecting the specific options" do
with_concat_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f|
f.input_field :name, id: 'specific_id'
end
assert_select 'input.default_class#specific_id'
end
test "builder receives a default argument and pass it to the inputs when calling 'input', overwriting the defaults with specific options" do
with_concat_form_for @user, defaults: { input_html: { class: 'default_class', id: 'default_id' } } do |f|
f.input :name, input_html: { id: 'specific_id' }
end
assert_select 'input.default_class#specific_id'
end
test "builder receives a default argument and pass it to the inputs when calling 'input_field', overwriting the defaults with specific options" do
with_concat_form_for @user, defaults: { input_html: { class: 'default_class', id: 'default_id' } } do |f|
f.input_field :name, id: 'specific_id'
end
assert_select 'input.default_class#specific_id'
end
# WITHOUT OBJECT
test 'builder generates properly when object is not present' do
with_form_for :project, :name
assert_select 'form input.string#project_name'
end
test 'builder generates password fields based on attribute name when object is not present' do
with_form_for :project, :password_confirmation
assert_select 'form input[type=password].password#project_password_confirmation'
end
test 'builder generates text fields by default for all attributes when object is not present' do
with_form_for :project, :created_at
assert_select 'form input.string#project_created_at'
with_form_for :project, :budget
assert_select 'form input.string#project_budget'
end
test 'builder allows overriding input type when object is not present' do
with_form_for :project, :created_at, as: :datetime
assert_select 'form select.datetime#project_created_at_1i'
with_form_for :project, :budget, as: :decimal
assert_select 'form input.decimal#project_budget'
end
# CUSTOM FORM BUILDER
test 'custom builder inherits mappings' do
with_custom_form_for @user, :email
assert_select 'form input[type=email]#user_email.custom'
end
test 'form with CustomMapTypeFormBuilder uses custom map type builder' do
with_concat_custom_mapping_form_for(:user) do |user|
assert user.instance_of?(CustomMapTypeFormBuilder)
end
end
test 'form with CustomMapTypeFormBuilder uses custom mapping' do
with_concat_custom_mapping_form_for(:user) do |user|
assert_equal SimpleForm::Inputs::StringInput, user.class.mappings[:custom_type]
end
end
test 'form without CustomMapTypeFormBuilder does not use custom mapping' do
with_concat_form_for(:user) do |user|
assert_nil user.class.mappings[:custom_type]
end
end
end
================================================
FILE: test/form_builder/hint_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
# Tests for f.hint
class HintTest < ActionView::TestCase
def with_hint_for(object, *args)
with_concat_form_for(object) do |f|
f.hint(*args)
end
end
test 'hint does not be generated by default' do
with_hint_for @user, :name
assert_no_select 'span.hint'
end
test 'hint is generated with optional text' do
with_hint_for @user, :name, hint: 'Use with care...'
assert_select 'span.hint', 'Use with care...'
end
test 'hint is generated with decorated object responsive to #to_model' do
with_hint_for @decorated_user, :name, hint: 'Use with care...'
assert_select 'span.hint', 'Use with care...'
end
test 'hint does not modify the options hash' do
options = { hint: 'Use with care...' }
with_hint_for @user, :name, options
assert_select 'span.hint', 'Use with care...'
assert_equal({ hint: 'Use with care...' }, options)
end
test 'hint is generated cleanly with optional text' do
with_hint_for @user, :name, hint: 'Use with care...', hint_tag: :span
assert_no_select 'span.hint[hint]'
assert_no_select 'span.hint[hint_tag]'
assert_no_select 'span.hint[hint_html]'
end
test 'hint uses the current component tag set' do
with_hint_for @user, :name, hint: 'Use with care...', hint_tag: :p
assert_select 'p.hint', 'Use with care...'
end
test 'hint is able to pass html options' do
with_hint_for @user, :name, hint: 'Yay!', id: 'hint', class: 'yay'
assert_select 'span#hint.hint.yay'
end
test 'hint is output as html_safe' do
with_hint_for @user, :name, hint: 'Bold and not...'.html_safe
assert_select 'span.hint', 'Bold and not...'
assert_select 'span.hint b', 'Bold'
end
test 'builder escapes hint text' do
with_hint_for @user, :name, hint: ''
assert_no_select 'span.hint script'
end
# Without attribute name
test 'hint without attribute name' do
with_hint_for @validating_user, 'Hello World!'
assert_select 'span.hint', 'Hello World!'
end
test 'hint without attribute name generates component tag with a clean HTML' do
with_hint_for @validating_user, 'Hello World!'
assert_no_select 'span.hint[hint]'
assert_no_select 'span.hint[hint_html]'
end
test 'hint without attribute name uses the current component tag set' do
with_hint_for @user, 'Hello World!', hint_tag: :p
assert_no_select 'p.hint[hint]'
assert_no_select 'p.hint[hint_html]'
assert_no_select 'p.hint[hint_tag]'
end
test 'hint without attribute name is able to pass html options' do
with_hint_for @user, 'Yay', id: 'hint', class: 'yay'
assert_select 'span#hint.hint.yay', 'Yay'
end
# I18n
test 'hint uses i18n based on model, action, and attribute to lookup translation' do
store_translations(:en, simple_form: { hints: { user: {
edit: { name: 'Content of this input will be truncated...' }
} } }) do
with_hint_for @user, :name
assert_select 'span.hint', 'Content of this input will be truncated...'
end
end
test 'hint uses i18n with model and attribute to lookup translation' do
store_translations(:en, simple_form: { hints: { user: {
name: 'Content of this input will be capitalized...'
} } }) do
with_hint_for @user, :name
assert_select 'span.hint', 'Content of this input will be capitalized...'
end
end
test 'hint uses i18n under defaults namespace to lookup translation' do
store_translations(:en, simple_form: {
hints: { defaults: { name: 'Content of this input will be downcased...' } }
}) do
with_hint_for @user, :name
assert_select 'span.hint', 'Content of this input will be downcased...'
end
end
test 'hint uses i18n with lookup for association name' do
store_translations(:en, simple_form: { hints: {
user: { company: 'My company!' }
} } ) do
with_hint_for @user, :company_id, as: :string, reflection: Association.new(Company, :company, {})
assert_select 'span.hint', /My company!/
end
end
test 'hint outputs translations as html_safe' do
store_translations(:en, simple_form: { hints: { user: {
edit: { name: 'This is bold and this is not...' }
} } }) do
with_hint_for @user, :name
assert_select 'span.hint', 'This is bold and this is not...'
end
end
# No object
test 'hint generates properly when object is not present' do
with_hint_for :project, :name, hint: 'Test without object'
assert_select 'span.hint', 'Test without object'
end
# Custom wrappers
test 'hint with custom wrappers works' do
swap_wrapper do
with_hint_for @user, :name, hint: "cannot be blank"
assert_select 'div.omg_hint', "cannot be blank"
end
end
end
================================================
FILE: test/form_builder/input_field_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
# Tests for f.input_field
class InputFieldTest < ActionView::TestCase
test "builder input_field only renders the input tag, nothing else" do
with_input_field_for @user, :name
assert_select 'form > input.required.string'
assert_no_select 'div.string'
assert_no_select 'label'
assert_no_select '.hint'
end
test 'builder input_field allows overriding default input type' do
with_input_field_for @user, :name, as: :text
assert_no_select 'input#user_name'
assert_select 'textarea#user_name.text'
end
test 'builder input_field generates input type based on column type' do
with_input_field_for @user, :age
assert_select 'input[type=number].integer#user_age'
end
test 'builder input_field is able to disable any component' do
with_input_field_for @user, :age, html5: false
assert_no_select 'input[html5=false]#user_age'
assert_select 'input[type=text].integer#user_age'
end
test 'builder input_field allows passing options to input tag' do
with_input_field_for @user, :name, id: 'name_input', class: 'name'
assert_select 'input.string.name#name_input'
end
test 'builder input_field does not modify the options hash' do
options = { id: 'name_input', class: 'name' }
with_input_field_for @user, :name, options
assert_select 'input.string.name#name_input'
assert_equal({ id: 'name_input', class: 'name' }, options)
end
test 'builder input_field generates an input tag with a clean HTML' do
with_input_field_for @user, :name, as: :integer, class: 'name'
assert_no_select 'input.integer[input_html]'
assert_no_select 'input.integer[as]'
end
test 'builder input_field uses i18n to translate placeholder text' do
store_translations(:en, simple_form: { placeholders: { user: {
name: 'Name goes here'
} } }) do
with_input_field_for @user, :name
assert_select 'input.string[placeholder="Name goes here"]'
end
end
test 'builder input_field uses min_max component' do
with_input_field_for @other_validating_user, :age, as: :integer
assert_select 'input[min="18"]'
end
test 'builder input_field does not use pattern component by default' do
with_input_field_for @other_validating_user, :country, as: :string
assert_no_select 'input[pattern="\w+"]'
end
test 'builder input_field infers pattern from attributes' do
with_input_field_for @other_validating_user, :country, as: :string, pattern: true
assert_select "input:match('pattern', ?)", /\w+/
end
test 'builder input_field accepts custom pattern' do
with_input_field_for @other_validating_user, :country, as: :string, pattern: '\d+'
assert_select "input:match('pattern', ?)", /\\d+/
end
test 'builder input_field uses readonly component' do
with_input_field_for @other_validating_user, :age, as: :integer, readonly: true
assert_select 'input.integer.readonly[readonly]'
end
test 'builder input_field uses maxlength component' do
with_input_field_for @validating_user, :name, as: :string
assert_select 'input.string[maxlength="25"]'
end
test 'builder input_field uses minlength component' do
with_input_field_for @validating_user, :name, as: :string
assert_select 'input.string[minlength="5"]'
end
test 'builder collection input_field generates input tag with a clean HTML' do
with_input_field_for @user, :status, collection: %w[Open Closed],
class: 'status', label_method: :to_s, value_method: :to_s
assert_no_select 'select.status[input_html]'
assert_no_select 'select.status[collection]'
assert_no_select 'select.status[label_method]'
assert_no_select 'select.status[value_method]'
end
test 'build input_field does not treat "boolean_style" as a HTML attribute' do
with_input_field_for @user, :active, boolean_style: :nested
assert_no_select 'input.boolean[boolean_style]'
end
test 'build input_field does not treat "prompt" as a HTML attribute' do
with_input_field_for @user, :attempts, collection: [1,2,3,4,5], prompt: :translate
assert_no_select 'select[prompt]'
end
test 'build input_field without pattern component use the pattern string' do
swap_wrapper :default, custom_wrapper_with_html5_components do
with_input_field_for @user, :name, pattern: '\w+'
assert_select "input:match('pattern', ?)", /\w+/
end
end
test 'build input_field without placeholder component use the placeholder string' do
swap_wrapper :default, custom_wrapper_with_html5_components do
with_input_field_for @user, :name, placeholder: 'Placeholder'
assert_select 'input[placeholder="Placeholder"]'
end
end
test 'build input_field without maxlength component use the maxlength string' do
swap_wrapper :default, custom_wrapper_with_html5_components do
with_input_field_for @user, :name, maxlength: 5
assert_select 'input[maxlength="5"]'
end
end
test 'build input_field without minlength component use the minlength string' do
swap_wrapper :default, custom_wrapper_with_html5_components do
with_input_field_for @user, :name, minlength: 5
assert_select 'input[minlength="5"]'
end
end
test 'build input_field without readonly component use the readonly string' do
swap_wrapper :default, custom_wrapper_with_html5_components do
with_input_field_for @user, :name, readonly: true
assert_select 'input[readonly="readonly"]'
end
end
test 'adds valid class to input_field when it is configured' do
swap SimpleForm, input_field_valid_class: 'is-valid' do
@user.instance_eval { undef errors }
with_input_field_for @user, :name
assert_select 'input.string.required.is-valid'
end
end
test 'adds error class to input_field when it is configured' do
swap SimpleForm, input_field_error_class: 'is-invalid' do
with_input_field_for @user, :name
assert_select 'input.string.required.is-invalid'
end
end
test 'does not add validation classes to input_field when it is not configured' do
swap SimpleForm, input_field_error_class: nil, input_field_valid_class: nil do
with_input_field_for @user, :name
assert_select 'input.string.required'
end
end
end
================================================
FILE: test/form_builder/label_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class LabelTest < ActionView::TestCase
def with_label_for(object, *args, &block)
with_concat_form_for(object) do |f|
f.label(*args, &block)
end
end
test 'builder generates a label for the attribute' do
with_label_for @user, :name
assert_select 'label.string[for=user_name]', /Name/
end
test 'builder generates a label for the attribute with decorated object responsive to #to_model' do
with_label_for @decorated_user, :name
assert_select 'label.string[for=user_name]', /Name/
end
test 'builder generates a label for the boolean attribute' do
with_label_for @user, :name, as: :boolean
assert_select 'label.boolean[for=user_name]', /Name/
assert_no_select 'label[as=boolean]'
end
test 'builder generates a label component tag with a clean HTML' do
with_label_for @user, :name
assert_no_select 'label.string[label_html]'
end
test 'builder adds a required class to label if the attribute is required' do
with_label_for @validating_user, :name
assert_select 'label.string.required[for=validating_user_name]', /Name/
end
test 'builder adds a disabled class to label if the attribute is disabled' do
with_label_for @validating_user, :name, disabled: true
assert_select 'label.string.disabled[for=validating_user_name]', /Name/
end
test 'builder does not add a disabled class to label if the attribute is not disabled' do
with_label_for @validating_user, :name, disabled: false
assert_no_select 'label.string.disabled[for=validating_user_name]', /Name/
end
test 'builder escapes label text' do
with_label_for @user, :name, label: '', required: false
assert_no_select 'label.string script'
end
test 'builder does not escape label text if it is safe' do
with_label_for @user, :name, label: ''.html_safe, required: false
assert_select 'label.string script', "alert(1337)"
end
test 'builder allows passing options to label tag' do
with_label_for @user, :name, label: 'My label', id: 'name_label'
assert_select 'label.string#name_label', /My label/
end
test 'builder label generates label tag with clean HTML' do
with_label_for @user, :name, label: 'My label', required: true, id: 'name_label'
assert_select 'label.string#name_label', /My label/
assert_no_select 'label[label]'
assert_no_select 'label[required]'
end
test 'builder does not modify the options hash' do
options = { label: 'My label', id: 'name_label' }
with_label_for @user, :name, options
assert_select 'label.string#name_label', /My label/
assert_equal({ label: 'My label', id: 'name_label' }, options)
end
test 'builder fallbacks to default label when string is given' do
with_label_for @user, :name, 'Nome do usuário'
assert_select 'label', 'Nome do usuário'
assert_no_select 'label.string'
end
test 'builder fallbacks to default label when block is given' do
with_label_for @user, :name do
'Nome do usuário'
end
assert_select 'label', 'Nome do usuário'
assert_no_select 'label.string'
end
test 'builder allows label order to be changed' do
swap SimpleForm, label_text: proc { |l, r| "#{l}:" } do
with_label_for @user, :age
assert_select 'label.integer[for=user_age]', "Age:"
end
end
test 'configuration allow set label text for wrappers' do
swap_wrapper :default, custom_wrapper_with_label_text do
with_concat_form_for(@user) do |f|
concat f.input :age
end
assert_select "label.integer[for=user_age]", "**Age**"
end
end
test 'configuration allow set rewritten label tag for wrappers' do
swap_wrapper :default, custom_wrapper_with_custom_label_component do
with_concat_form_for(@user) do |f|
concat f.input :age
end
assert_select "span.integer.user_age", /Age/
end
end
test 'builder allows custom formatting when label is explicitly specified' do
swap SimpleForm, label_text: ->(l, r, explicit_label) { explicit_label ? l : "#{l.titleize}:" } do
with_label_for @user, :time_zone, 'What is your home time zone?'
assert_select 'label[for=user_time_zone]', 'What is your home time zone?'
end
end
test 'builder allows custom formatting when label is generated' do
swap SimpleForm, label_text: ->(l, r, explicit_label) { explicit_label ? l : "#{l.titleize}:" } do
with_label_for @user, :time_zone
assert_select 'label[for=user_time_zone]', 'Time Zone:'
end
end
test 'builder allows label specific `label_text` option' do
with_label_for @user, :time_zone, label_text: ->(l, _, _) { "#{l.titleize}:" }
assert_no_select 'label[label_text]'
assert_select 'label[for=user_time_zone]', 'Time Zone:'
end
end
================================================
FILE: test/form_builder/wrapper_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class WrapperTest < ActionView::TestCase
test 'wrapper does not have error class for attribute without errors' do
with_form_for @user, :active
assert_no_select 'div.field_with_errors'
end
test 'wrapper does not have error class when object is not present' do
with_form_for :project, :name
assert_no_select 'div.field_with_errors'
end
test 'wrapper adds the attribute name class' do
with_form_for @user, :name
assert_select 'div.user_name'
end
test 'wrapper adds the attribute name class for nested forms' do
@user.company = Company.new(1, 'Empresa')
with_concat_form_for @user do |f|
concat(f.simple_fields_for(:company) do |company_form|
concat(company_form.input :name)
end)
end
assert_select 'div.user_company_name'
end
test 'wrapper adds the association name class' do
with_form_for @user, :company
assert_select 'div.user_company'
end
test 'wrapper adds error class for attribute with errors' do
with_form_for @user, :name
assert_select 'div.field_with_errors'
end
test 'wrapper adds error class to input for attribute with errors' do
with_form_for @user, :name, wrapper: custom_wrapper_with_input_error_class
assert_select 'div.field_with_errors'
assert_select 'input.is-invalid'
end
test 'wrapper does not add error class to input when the attribute is valid' do
with_form_for @user, :phone_number, wrapper: custom_wrapper_with_input_error_class
assert_no_select 'div.field_with_errors'
assert_no_select 'input.is-invalid'
end
test 'wrapper adds valid class for present attribute without errors' do
@user.instance_eval { undef errors }
with_form_for @user, :name, wrapper: custom_wrapper_with_input_valid_class
assert_select 'div.field_without_errors'
assert_select 'input.is-valid'
assert_no_select 'div.field_with_errors'
assert_no_select 'input.is-invalid'
end
test 'wrapper does not determine if valid class is needed when it is set to nil' do
@user.instance_eval { undef errors }
with_form_for @user, :name, wrapper: custom_wrapper_with_input_valid_class(valid_class: nil)
assert_no_select 'div.field_without_errors'
end
test 'wrapper adds hint class for attribute with a hint' do
with_form_for @user, :name, hint: 'hint'
assert_select 'div.field_with_hint'
end
test 'wrapper does not have disabled class by default' do
with_form_for @user, :active
assert_no_select 'div.disabled'
end
test 'wrapper has disabled class when input is disabled' do
with_form_for @user, :active, disabled: true
assert_select 'div.disabled'
end
test 'wrapper supports no wrapping when wrapper is false' do
with_form_for @user, :name, wrapper: false
assert_select 'form > label[for=user_name]'
assert_select 'form > input#user_name.string'
end
test 'wrapper supports no wrapping when wrapper tag is false' do
with_form_for @user, :name, wrapper: custom_wrapper_without_top_level
assert_select 'form > label[for=user_name]'
assert_select 'form > input#user_name.string'
end
test 'wrapper wraps tag adds required/optional css classes' do
with_form_for @user, :name
assert_select 'form div.input.required.string'
with_form_for @user, :age, required: false
assert_select 'form div.input.optional.integer'
end
test 'wrapper allows custom options to be given' do
with_form_for @user, :name, wrapper_html: { id: "super_cool", class: 'yay' }
assert_select 'form #super_cool.required.string.yay'
end
test 'wrapper allows tag to be given on demand' do
with_form_for @user, :name, wrapper_tag: :b
assert_select 'form b.required.string'
end
test 'wrapper allows wrapper class to be given on demand' do
with_form_for @user, :name, wrapper_class: :wrapper
assert_select 'form div.wrapper.required.string'
end
test 'wrapper skips additional classes when configured' do
swap SimpleForm, generate_additional_classes_for: %i[input label] do
with_form_for @user, :name, wrapper_class: :wrapper
assert_select 'form div.wrapper'
assert_no_select 'div.required'
assert_no_select 'div.string'
assert_no_select 'div.user_name'
end
end
test 'wrapper does not generate empty css class' do
swap SimpleForm, generate_additional_classes_for: %i[input label] do
swap_wrapper :default, custom_wrapper_without_class do
with_form_for @user, :name
assert_no_select 'div#custom_wrapper_without_class[class]'
end
end
end
# Custom wrapper test
test 'custom wrappers works' do
swap_wrapper do
with_form_for @user, :name, hint: "cool"
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
assert_no_select "section.custom_wrapper div.another_wrapper span.omg_error"
assert_select "section.custom_wrapper div.error_wrapper span.omg_error"
assert_select "section.custom_wrapper > div.omg_hint", "cool"
end
end
test 'custom wrappers can be turned off' do
swap_wrapper do
with_form_for @user, :name, another: false
assert_no_select "section.custom_wrapper div.another_wrapper label"
assert_no_select "section.custom_wrapper div.another_wrapper input.string"
assert_select "section.custom_wrapper div.error_wrapper span.omg_error"
end
end
test 'custom wrappers can have additional attributes' do
swap_wrapper :default, custom_wrapper_with_additional_attributes do
with_form_for @user, :name
assert_select "div.custom_wrapper[title='some title'][data-wrapper='test']"
end
end
test 'custom wrappers can have full error message on attributes' do
swap_wrapper :default, custom_wrapper_with_full_error do
with_form_for @user, :name
assert_select 'span.error', "Super User Name! cannot be blank"
end
end
test 'custom wrappers on a form basis' do
swap_wrapper :another do
with_concat_form_for(@user) do |f|
f.input :name
end
assert_no_select "section.custom_wrapper div.another_wrapper label"
assert_no_select "section.custom_wrapper div.another_wrapper input.string"
with_concat_form_for(@user, wrapper: :another) do |f|
f.input :name
end
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
end
end
test 'custom wrappers on input basis' do
swap_wrapper :another do
with_form_for @user, :name
assert_no_select "section.custom_wrapper div.another_wrapper label"
assert_no_select "section.custom_wrapper div.another_wrapper input.string"
output_buffer.to_s.replace ""
with_form_for @user, :name, wrapper: :another
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
output_buffer.to_s.replace ""
end
with_form_for @user, :name, wrapper: custom_wrapper
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
end
test 'access wrappers with indifferent access' do
swap_wrapper :another do
with_form_for @user, :name, wrapper: "another"
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
end
end
test 'does not duplicate label classes for different inputs' do
swap_wrapper :default, custom_wrapper_with_label_html_option do
with_concat_form_for(@user) do |f|
concat f.input :name, required: false
concat f.input :email, as: :email, required: true
end
assert_select "label.string.optional.extra-label-class[for='user_name']"
assert_select "label.email.required.extra-label-class[for='user_email']"
assert_no_select "label.string.optional.extra-label-class[for='user_email']"
end
end
test 'raise error when wrapper not found' do
assert_raise SimpleForm::WrapperNotFound do
with_form_for @user, :name, wrapper: :not_found
end
end
test 'uses wrapper for specified in config mapping' do
swap_wrapper :another do
swap SimpleForm, wrapper_mappings: { string: :another } do
with_form_for @user, :name
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
end
end
end
test 'uses custom wrapper mapping per form basis' do
swap_wrapper :another do
with_concat_form_for @user, wrapper_mappings: { string: :another } do |f|
concat f.input :name
end
end
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
end
test 'simple_fields_form reuses custom wrapper mapping per form basis' do
@user.company = Company.new(1, 'Empresa')
swap_wrapper :another do
with_concat_form_for @user, wrapper_mappings: { string: :another } do |f|
concat(f.simple_fields_for(:company) do |company_form|
concat(company_form.input(:name))
end)
end
end
assert_select "section.custom_wrapper div.another_wrapper label"
assert_select "section.custom_wrapper div.another_wrapper input.string"
end
test "input attributes class will merge with wrapper_options' classes" do
swap_wrapper :default, custom_wrapper_with_input_class do
with_concat_form_for @user do |f|
concat f.input :name, input_html: { class: 'another-class' }
end
end
assert_select "div.custom_wrapper input.string.inline-class.another-class"
end
test "input with data attributes will merge with wrapper_options' data" do
swap_wrapper :default, custom_wrapper_with_input_data_modal do
with_concat_form_for @user do |f|
concat f.input :name, input_html: { data: { modal: 'another-data', target: 'merge-data' } }
end
end
assert_select "input[data-wrapper='data-wrapper'][data-modal='another-data'][data-target='merge-data']"
end
test "input with aria attributes will merge with wrapper_options' aria" do
swap_wrapper :default, custom_wrapper_with_input_aria_modal do
with_concat_form_for @user do |f|
concat f.input :name, input_html: { aria: { modal: 'another-aria', target: 'merge-aria' } }
end
end
assert_select "input[aria-wrapper='aria-wrapper'][aria-modal='another-aria'][aria-target='merge-aria']"
end
test 'input accepts attributes in the DSL' do
swap_wrapper :default, custom_wrapper_with_input_class do
with_concat_form_for @user do |f|
concat f.input :name
end
end
assert_select "div.custom_wrapper input.string.inline-class"
end
test 'label accepts attributes in the DSL' do
swap_wrapper :default, custom_wrapper_with_label_class do
with_concat_form_for @user do |f|
concat f.input :name
end
end
assert_select "div.custom_wrapper label.string.inline-class"
end
test 'label_input accepts attributes in the DSL' do
swap_wrapper :default, custom_wrapper_with_label_input_class do
with_concat_form_for @user do |f|
concat f.input :name
end
end
assert_select "div.custom_wrapper label.string.inline-class"
assert_select "div.custom_wrapper input.string.inline-class"
end
test 'input accepts data attributes in the DSL' do
swap_wrapper :default, custom_wrapper_with_input_attributes do
with_concat_form_for @user do |f|
concat f.input :name
end
end
assert_select "div.custom_wrapper input.string[data-modal=true]"
end
test 'inline wrapper displays when there is content' do
swap_wrapper :default, custom_wrapper_with_wrapped_optional_component do
with_form_for @user, :name, hint: "cannot be blank"
assert_select 'section.custom_wrapper div.no_output_wrapper p.omg_hint', "cannot be blank"
assert_select 'p.omg_hint'
end
end
test 'inline wrapper does not display when there is no content' do
swap_wrapper :default, custom_wrapper_with_wrapped_optional_component do
with_form_for @user, :name
assert_select 'section.custom_wrapper div.no_output_wrapper'
assert_no_select 'p.omg_hint'
end
end
test 'optional wrapper does not display when there is content' do
swap_wrapper :default, custom_wrapper_with_unless_blank do
with_form_for @user, :name, hint: "can't be blank"
assert_select 'section.custom_wrapper div.no_output_wrapper'
assert_select 'div.no_output_wrapper'
assert_select 'p.omg_hint'
end
end
test 'optional wrapper does not display when there is no content' do
swap_wrapper :default, custom_wrapper_with_unless_blank do
with_form_for @user, :name
assert_no_select 'section.custom_wrapper div.no_output_wrapper'
assert_no_select 'div.no_output_wrapper'
assert_no_select 'p.omg_hint'
end
end
end
================================================
FILE: test/generators/simple_form_generator_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class SimpleFormGeneratorTest < Rails::Generators::TestCase
tests SimpleForm::Generators::InstallGenerator
destination File.expand_path('../../tmp', __FILE__)
setup :prepare_destination
teardown { rm_rf(destination_root) }
test 'generates example locale file' do
run_generator
assert_file 'config/locales/simple_form.en.yml'
end
test 'generates the simple_form initializer' do
run_generator
assert_file 'config/initializers/simple_form.rb',
/config\.default_wrapper = :default/, /config\.boolean_style = :nested/
end
test 'generates the simple_form initializer with the bootstrap wrappers' do
run_generator %w[--bootstrap]
assert_file 'config/initializers/simple_form.rb',
/config\.default_wrapper = :default/, /config\.boolean_style = :nested/
assert_file 'config/initializers/simple_form_bootstrap.rb', /config\.wrappers :vertical_form/,
/config\.wrappers :horizontal_form/, /config\.default_wrapper = :vertical_form/
end
test 'generates the simple_form initializer with the foundation wrappers' do
run_generator %w[--foundation]
assert_file 'config/initializers/simple_form.rb',
/config\.default_wrapper = :default/, /config\.boolean_style = :nested/
assert_file 'config/initializers/simple_form_foundation.rb', /config\.wrappers :vertical_form/,
/config\.default_wrapper = :vertical_form/, /config\.item_wrapper_tag = :div/
end
%w[erb haml slim].each do |engine|
test "generates the scaffold template when using #{engine}" do
run_generator ['-e', engine]
assert_file "lib/templates/#{engine}/scaffold/_form.html.#{engine}"
end
end
end
================================================
FILE: test/inputs/boolean_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class BooleanInputTest < ActionView::TestCase
test 'input generates a checkbox by default for boolean attributes' do
with_input_for @user, :active, :boolean
assert_select 'input[type=checkbox].boolean#user_active'
assert_select 'label.boolean.optional', 'Active'
end
test 'input does not generate the label with the checkbox when label option is false' do
with_input_for @user, :active, :boolean, label: false
assert_select 'input[type=checkbox].boolean#user_active'
assert_no_select 'label'
end
test 'input uses custom checked value' do
@user.action = 'on'
with_input_for @user, :action, :boolean, checked_value: 'on', unchecked_value: 'off'
assert_select 'input[type=checkbox][value=on][checked=checked]'
end
test 'input uses custom unchecked value' do
@user.action = 'off'
with_input_for @user, :action, :boolean, checked_value: 'on', unchecked_value: 'off'
assert_select 'input[type=checkbox][value=on]'
assert_no_select 'input[checked=checked][value=on]'
end
test 'input generates hidden input with custom unchecked value' do
with_input_for @user, :action, :boolean, checked_value: 'on', unchecked_value: 'off'
assert_select 'input[type=hidden][value=off]'
end
test 'input allows skipping hidden input when setting :include_hidden to false' do
with_input_for @user, :active, :boolean, include_hidden: false
assert_no_select "input[type=hidden][name='user[active]']"
end
test 'input uses inline boolean style by default' do
with_input_for @user, :active, :boolean
assert_select 'input.boolean + label.boolean.optional'
assert_no_select 'label > input'
end
test 'input allows changing default boolean style config to nested, generating a default label and a manual hidden field for checkbox' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean
assert_select 'label[for=user_active]', 'Active'
assert_select 'label.boolean > input.boolean'
assert_no_select 'input[type=checkbox] + label'
end
end
test 'input boolean with nested allows :inline_label' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, inline_label: 'I am so inline.'
assert_select 'label.checkbox', text: ' I am so inline.'
end
end
test 'input boolean with nested escapes :inline_label with HTML' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, inline_label: 'I am so inline. '
assert_no_select 'label.checkbox b'
end
end
test 'input boolean with nested allows :inline_label with HTML when safe' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, inline_label: 'I am so inline. '.html_safe
assert_select 'label.checkbox b', text: 'I am so inline.'
end
end
test 'input boolean with nested style creates an inline label using the default label text when inline_label option set to true' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, inline_label: true
assert_select 'label.checkbox', text: ' Active'
end
end
test 'input boolean with nested style creates an inline label using the label text when inline_label option set to true' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, inline_label: true, label_text: proc { 'New Active' }
assert_select 'label.checkbox', text: ' New Active'
end
end
test 'input boolean with nested style creates an inline label using the label html when inline_label option set to true' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, inline_label: true, label_text: proc { 'New Active ' }
assert_select 'label.checkbox', text: ' New Active'
end
end
test 'input boolean with nested generates a manual hidden field for checkbox outside the label, to recreate Rails functionality with valid html5' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean
assert_select "input[type=hidden][name='user[active]'] + label.boolean > input.boolean"
assert_no_select 'input[type=checkbox] + label'
end
end
test 'input boolean with nested generates a disabled hidden field for checkbox outside the label, if the field is disabled' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, disabled: true
assert_select "input[type=hidden][name='user[active]'][disabled] + label.boolean > input.boolean[disabled]"
end
end
test 'input boolean with nested generates a disabled hidden field with the form attribute when it is given' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, input_html: { form: 'form_id' }
assert_select "input[type=hidden][form=form_id]+ label.boolean > input.boolean"
end
end
test 'input accepts changing boolean style to nested through given options' do
with_input_for @user, :active, :boolean, boolean_style: :nested
assert_select 'label[for=user_active]', 'Active'
assert_select 'label.boolean > input.boolean'
assert_no_select 'input[type=checkbox] + label'
end
test 'input accepts changing boolean style to inline through given options, when default is nested' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, boolean_style: :inline
assert_select 'label[for=user_active]', 'Active'
assert_select 'input.boolean + label.boolean'
assert_no_select 'label > input'
end
end
test 'input with nested style allows disabling label' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, label: false
assert_select 'input.boolean'
assert_no_select 'label.boolean'
end
end
test 'input with nested style allows customizing input_html' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, input_html: { name: 'active_user' }
assert_select "input[type=hidden][name=active_user] + label.boolean > input.boolean[name=active_user]"
end
end
test 'input with nested style allows disabling hidden field' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, include_hidden: false
assert_select "label.boolean > input.boolean"
assert_no_select "input[type=hidden] + label.boolean"
end
end
test 'input with nested style and with single wrapper allows disabling hidden field' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, include_hidden: false, wrapper: custom_wrapper_with_wrapped_label_input
assert_select "label.boolean > input.boolean"
assert_no_select "input[type=hidden] + label.boolean"
end
end
test 'input with nested style does not include hidden field when unchecked_value is false' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean, unchecked_value: false
assert_select "label.boolean > input.boolean"
assert_no_select "input[type=hidden] + label.boolean"
end
end
test 'input boolean works using :input only in wrapper config (no label_input)' do
swap_wrapper do
with_input_for @user, :active, :boolean
assert_select 'label.boolean + input[type=hidden] + input.boolean'
assert_no_select 'label.checkbox'
end
end
test 'input boolean with nested style works using :input only in wrapper config (no label_input), adding the extra "checkbox" label wrapper' do
swap_wrapper do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean
assert_select 'label.boolean + input[type=hidden] + label.checkbox > input.boolean'
end
end
end
test 'input boolean allows specifying boolean_label_class on a per-input basis' do
swap_wrapper do
swap SimpleForm, boolean_style: :nested, boolean_label_class: 'foo' do
with_input_for @user, :active, :boolean, boolean_label_class: 'baz'
assert_select 'label.boolean + input[type=hidden] + label.baz > input.boolean'
end
end
end
test 'input boolean with nested style works using :input only in wrapper config (no label_input), adding the extra label wrapper with custom class' do
swap_wrapper do
swap SimpleForm, boolean_style: :nested, boolean_label_class: 'foo' do
with_input_for @user, :active, :boolean
assert_select 'label.boolean + input[type=hidden] + label.foo > input.boolean'
end
end
end
test 'input boolean with nested style works using :label_input in wrapper config, adding "checkbox" class to label' do
swap_wrapper :default, self.custom_wrapper_without_top_level do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :boolean
assert_select 'input[type=hidden] + label.boolean.checkbox > input.boolean'
end
end
end
test 'input boolean with nested style works using :label_input in wrapper config, adding custom class to label' do
swap_wrapper :default, self.custom_wrapper_without_top_level do
swap SimpleForm, boolean_style: :nested, boolean_label_class: 'foo' do
with_input_for @user, :active, :boolean
assert_select 'input[type=hidden] + label.boolean.foo > input.boolean'
end
end
end
test 'input boolean without additional classes adds "checkbox" class to label' do
swap_wrapper :default, self.custom_wrapper_without_top_level do
swap SimpleForm, boolean_style: :nested, generate_additional_classes_for: [:input] do
with_input_for @user, :active, :boolean
assert_select 'label'
assert_select 'label.checkbox'
assert_no_select 'label.boolean'
end
end
end
test 'input boolean works with wrapper config defining a class for the input' do
swap_wrapper :default, self.custom_wrapper_with_input_class do
with_input_for @user, :active, :boolean
assert_select 'input.boolean.inline-class'
end
end
end
================================================
FILE: test/inputs/collection_check_boxes_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class CollectionCheckBoxesInputTest < ActionView::TestCase
test 'input check boxes does not include for attribute by default' do
with_input_for @user, :gender, :check_boxes, collection: %i[male female]
assert_select 'label'
assert_no_select 'label[for=user_gender]'
end
test 'input check boxes includes for attribute when giving as html option' do
with_input_for @user, :gender, :check_boxes, collection: %i[male female], label_html: { for: 'gender' }
assert_select 'label[for=gender]'
end
test 'collection input with check_boxes type does not generate required html attribute' do
with_input_for @user, :name, :check_boxes, collection: %w[Jose Carlos]
assert_select 'input.required'
assert_no_select 'input[required]'
end
test 'input does automatic collection translation for check_box types using defaults key' do
store_translations(:en, simple_form: { options: { defaults: {
gender: { male: 'Male', female: 'Female' }
} } } ) do
with_input_for @user, :gender, :check_boxes, collection: %i[male female]
assert_select 'input[type=checkbox][value=male]'
assert_select 'input[type=checkbox][value=female]'
assert_select 'label.collection_check_boxes', 'Male'
assert_select 'label.collection_check_boxes', 'Female'
end
end
test 'input does automatic collection translation for check_box types using specific object key' do
store_translations(:en, simple_form: { options: { user: {
gender: { male: 'Male', female: 'Female' }
} } } ) do
with_input_for @user, :gender, :check_boxes, collection: %i[male female]
assert_select 'input[type=checkbox][value=male]'
assert_select 'input[type=checkbox][value=female]'
assert_select 'label.collection_check_boxes', 'Male'
assert_select 'label.collection_check_boxes', 'Female'
end
end
test 'input that uses automatic collection translation for check_boxes properly sets checked values' do
store_translations(:en, simple_form: { options: { defaults: {
gender: { male: 'Male', female: 'Female' }
} } } ) do
@user.gender = 'male'
with_input_for @user, :gender, :check_boxes, collection: %i[male female]
assert_select 'input[type=checkbox][value=male][checked=checked]'
assert_select 'input[type=checkbox][value=female]'
assert_select 'label.collection_check_boxes', 'Male'
assert_select 'label.collection_check_boxes', 'Female'
end
end
test 'input check boxes does not wrap the collection by default' do
with_input_for @user, :active, :check_boxes
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form ul'
end
test 'input check boxes accepts html options as the last element of collection' do
with_input_for @user, :name, :check_boxes, collection: [['Jose', 'jose', class: 'foo']]
assert_select 'input.foo[type=checkbox][value=jose]'
end
test 'input check boxes wraps the collection in the configured collection wrapper tag' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :check_boxes
assert_select 'form ul input[type=checkbox]', count: 2
end
end
test 'input check boxes does not wrap the collection when configured with falsy values' do
swap SimpleForm, collection_wrapper_tag: false do
with_input_for @user, :active, :check_boxes
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form ul'
end
end
test 'input check boxes allows overriding the collection wrapper tag at input level' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :check_boxes, collection_wrapper_tag: :section
assert_select 'form section input[type=checkbox]', count: 2
assert_no_select 'form ul'
end
end
test 'input check boxes allows disabling the collection wrapper tag at input level' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :check_boxes, collection_wrapper_tag: false
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form ul'
end
end
test 'input check boxes renders the wrapper tag with the configured wrapper class' do
swap SimpleForm, collection_wrapper_tag: :ul, collection_wrapper_class: 'inputs-list' do
with_input_for @user, :active, :check_boxes
assert_select 'form ul.inputs-list input[type=checkbox]', count: 2
end
end
test 'input check boxes allows giving wrapper class at input level only' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :check_boxes, collection_wrapper_class: 'items-list'
assert_select 'form ul.items-list input[type=checkbox]', count: 2
end
end
test 'input check boxes uses both configured and given wrapper classes for wrapper tag' do
swap SimpleForm, collection_wrapper_tag: :ul, collection_wrapper_class: 'inputs-list' do
with_input_for @user, :active, :check_boxes, collection_wrapper_class: 'items-list'
assert_select 'form ul.inputs-list.items-list input[type=checkbox]', count: 2
end
end
test 'input check boxes wraps each item in the configured item wrapper tag' do
swap SimpleForm, item_wrapper_tag: :li do
with_input_for @user, :active, :check_boxes
assert_select 'form li input[type=checkbox]', count: 2
end
end
test 'input check boxes does not wrap items when configured with falsy values' do
swap SimpleForm, item_wrapper_tag: false do
with_input_for @user, :active, :check_boxes
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form li'
end
end
test 'input check boxes allows overriding the item wrapper tag at input level' do
swap SimpleForm, item_wrapper_tag: :li do
with_input_for @user, :active, :check_boxes, item_wrapper_tag: :dl
assert_select 'form dl input[type=checkbox]', count: 2
assert_no_select 'form li'
end
end
test 'input check boxes allows disabling the item wrapper tag at input level' do
swap SimpleForm, item_wrapper_tag: :ul do
with_input_for @user, :active, :check_boxes, item_wrapper_tag: false
assert_select 'form input[type=checkbox]', count: 2
assert_no_select 'form li'
end
end
test 'input check boxes wraps items in a span tag by default' do
with_input_for @user, :active, :check_boxes
assert_select 'form span input[type=checkbox]', count: 2
end
test 'input check boxes renders the item wrapper tag with a default class "checkbox"' do
with_input_for @user, :active, :check_boxes, item_wrapper_tag: :li
assert_select 'form li.checkbox input[type=checkbox]', count: 2
end
test 'input check boxes renders the item wrapper tag with the configured item wrapper class' do
swap SimpleForm, item_wrapper_tag: :li, item_wrapper_class: 'item' do
with_input_for @user, :active, :check_boxes
assert_select 'form li.checkbox.item input[type=checkbox]', count: 2
end
end
test 'input check boxes allows giving item wrapper class at input level only' do
swap SimpleForm, item_wrapper_tag: :li do
with_input_for @user, :active, :check_boxes, item_wrapper_class: 'item'
assert_select 'form li.checkbox.item input[type=checkbox]', count: 2
end
end
test 'input check boxes uses both configured and given item wrapper classes for item wrapper tag' do
swap SimpleForm, item_wrapper_tag: :li, item_wrapper_class: 'item' do
with_input_for @user, :active, :check_boxes, item_wrapper_class: 'inline'
assert_select 'form li.checkbox.item.inline input[type=checkbox]', count: 2
end
end
test 'input check boxes respects the nested boolean style config, generating nested label > input' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :check_boxes
assert_select 'span.checkbox > label > input#user_active_true[type=checkbox]'
assert_select 'span.checkbox > label', 'Yes'
assert_select 'span.checkbox > label > input#user_active_false[type=checkbox]'
assert_select 'span.checkbox > label', 'No'
assert_no_select 'label.collection_radio_buttons'
end
end
test 'input check boxes with nested style does not overrides configured item wrapper tag' do
swap SimpleForm, boolean_style: :nested, item_wrapper_tag: :li do
with_input_for @user, :active, :check_boxes
assert_select 'li.checkbox > label > input'
end
end
test 'input check boxes with nested style does not overrides given item wrapper tag' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :check_boxes, item_wrapper_tag: :li
assert_select 'li.checkbox > label > input'
end
end
test 'input check boxes with nested style accepts giving extra wrapper classes' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :check_boxes, item_wrapper_class: "inline"
assert_select 'span.checkbox.inline > label > input'
end
end
test 'input check boxes with nested style renders item labels with specified class' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :check_boxes, item_label_class: "test"
assert_select 'span.checkbox > label.test > input'
end
end
test 'input check boxes with nested style and falsey input wrapper renders item labels with specified class' do
swap SimpleForm, boolean_style: :nested, item_wrapper_tag: false do
with_input_for @user, :active, :check_boxes, item_label_class: "checkbox-inline"
assert_select 'label.checkbox-inline > input'
assert_no_select 'span.checkbox'
end
end
test 'input check boxes wrapper class are not included when set to falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :check_boxes, collection: %i[male female]
assert_no_select 'label.checkbox'
end
end
test 'input check boxes custom wrapper class is included when include input wrapper class is falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :check_boxes, collection: %i[male female], item_wrapper_class: 'custom'
assert_no_select 'label.checkbox'
assert_select 'span.custom'
end
end
test 'input check boxes with nested style and namespace uses the right for attribute' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_concat_form_for @user, namespace: :foo do |f|
concat f.input :gender, as: :check_boxes, collection: %i[male female]
end
assert_select 'label[for=foo_user_gender_male]'
assert_select 'label[for=foo_user_gender_female]'
end
end
test 'input check boxes with nested style and index uses the right for attribute' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_concat_form_for @user, index: 1 do |f|
concat f.input :gender, as: :check_boxes, collection: %i[male female]
end
assert_select 'label[for=user_1_gender_male]'
assert_select 'label[for=user_1_gender_female]'
end
end
test 'input check boxes with nested style accepts non-string attribute as label' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :amount,
:check_boxes,
collection: { 100 => 'hundred', 200 => 'two_hundred' },
label_method: :first,
value_method: :second
assert_select 'input[type=checkbox][value=hundred]'
assert_select 'input[type=checkbox][value=two_hundred]'
assert_select 'span.checkbox > label', '100'
assert_select 'span.checkbox > label', '200'
end
end
test 'input check boxes with inline style support label custom classes' do
swap SimpleForm, boolean_style: :inline do
with_input_for @user, :gender, :check_boxes, collection: %i[male female], item_label_class: 'beautiful-label'
assert_select 'label.beautiful-label', count: 2
end
end
end
================================================
FILE: test/inputs/collection_radio_buttons_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class CollectionRadioButtonsInputTest < ActionView::TestCase
test 'input generates boolean radio buttons by default for radio types' do
with_input_for @user, :active, :radio_buttons
assert_select 'input[type=radio][value=true].radio_buttons#user_active_true'
assert_select 'input[type=radio][value=false].radio_buttons#user_active_false'
end
test 'input as radio generates internal labels by default' do
with_input_for @user, :active, :radio_buttons
assert_select 'label[for=user_active_true]', 'Yes'
assert_select 'label[for=user_active_false]', 'No'
end
test 'input as radio generates internal labels with accurate `for` values with nested boolean style' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons
assert_select 'label[for=user_active_true]', 'Yes'
assert_select 'label[for=user_active_false]', 'No'
end
end
test 'nested label does not duplicate input id' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons, id: 'nested_id'
assert_select 'input#user_active_true'
assert_no_select 'label#user_active_true'
end
end
test 'input as radio uses i18n to translate internal labels' do
store_translations(:en, simple_form: { yes: 'Sim', no: 'Não' }) do
with_input_for @user, :active, :radio_buttons
assert_select 'label[for=user_active_true]', 'Sim'
assert_select 'label[for=user_active_false]', 'Não'
end
end
test 'input radio does not include for attribute by default' do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female]
assert_select 'label'
assert_no_select 'label[for=user_gender]'
end
test 'input radio includes for attribute when giving as html option' do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female], label_html: { for: 'gender' }
assert_select 'label[for=gender]'
end
test 'input marks the checked value when using boolean and radios' do
@user.active = false
with_input_for @user, :active, :radio_buttons
assert_no_select 'input[type=radio][value=true][checked]'
assert_select 'input[type=radio][value=false][checked]'
end
test 'input allows overriding collection for radio types' do
with_input_for @user, :name, :radio_buttons, collection: %w[Jose Carlos]
assert_select 'input[type=radio][value=Jose]'
assert_select 'input[type=radio][value=Carlos]'
assert_select 'label.collection_radio_buttons[for=user_name_jose]', 'Jose'
assert_select 'label.collection_radio_buttons[for=user_name_carlos]', 'Carlos'
end
test 'input does automatic collection translation for radio types using defaults key' do
store_translations(:en, simple_form: { options: { defaults: {
gender: { male: 'Male', female: 'Female' }
} } } ) do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female]
assert_select 'input[type=radio][value=male]'
assert_select 'input[type=radio][value=female]'
assert_select 'label.collection_radio_buttons[for=user_gender_male]', 'Male'
assert_select 'label.collection_radio_buttons[for=user_gender_female]', 'Female'
end
end
test 'input does automatic collection translation for radio types using specific object key' do
store_translations(:en, simple_form: { options: { user: {
gender: { male: 'Male', female: 'Female' }
} } } ) do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female]
assert_select 'input[type=radio][value=male]'
assert_select 'input[type=radio][value=female]'
assert_select 'label.collection_radio_buttons[for=user_gender_male]', 'Male'
assert_select 'label.collection_radio_buttons[for=user_gender_female]', 'Female'
end
end
test 'input does automatic collection translation and preserve html markup' do
swap SimpleForm, boolean_style: :nested do
store_translations(:en, simple_form: { options: { user: {
gender: { male_html: 'Male ', female_html: 'Female ' }
} } } ) do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female]
assert_select 'input[type=radio][value=male]'
assert_select 'input[type=radio][value=female]'
assert_select 'label[for=user_gender_male] strong', 'Male'
assert_select 'label[for=user_gender_female] strong', 'Female'
end
end
end
test 'input does automatic collection translation with keys prefixed with _html and a string value' do
swap SimpleForm, boolean_style: :nested do
store_translations(:en, simple_form: { options: { user: {
gender: { male_html: 'Male', female_html: 'Female' }
} } } ) do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female]
assert_select 'input[type=radio][value=male]'
assert_select 'input[type=radio][value=female]'
assert_select 'label[for=user_gender_male]', 'Male'
assert_select 'label[for=user_gender_female]', 'Female'
end
end
end
test 'input marks the current radio value by default' do
@user.name = "Carlos"
with_input_for @user, :name, :radio_buttons, collection: %w[Jose Carlos]
assert_select 'input[type=radio][value=Carlos][checked=checked]'
end
test 'input accepts html options as the last element of collection' do
with_input_for @user, :name, :radio_buttons, collection: [['Jose', 'jose', class: 'foo']]
assert_select 'input.foo[type=radio][value=jose]'
end
test 'input allows using a collection with text/value arrays' do
with_input_for @user, :name, :radio_buttons, collection: [%w[Jose jose], %w[Carlos carlos]]
assert_select 'input[type=radio][value=jose]'
assert_select 'input[type=radio][value=carlos]'
assert_select 'label.collection_radio_buttons', 'Jose'
assert_select 'label.collection_radio_buttons', 'Carlos'
end
test 'input allows using a collection with a Proc' do
with_input_for @user, :name, :radio_buttons, collection: proc { %w[Jose Carlos] }
assert_select 'label.collection_radio_buttons', 'Jose'
assert_select 'label.collection_radio_buttons', 'Carlos'
end
test 'input allows overriding only label method for collections' do
with_input_for @user, :name, :radio_buttons,
collection: %w[Jose Carlos],
label_method: :upcase
assert_select 'label.collection_radio_buttons', 'JOSE'
assert_select 'label.collection_radio_buttons', 'CARLOS'
end
test 'input allows overriding only value method for collections' do
with_input_for @user, :name, :radio_buttons,
collection: %w[Jose Carlos],
value_method: :upcase
assert_select 'input[type=radio][value=JOSE]'
assert_select 'input[type=radio][value=CARLOS]'
end
test 'input allows overriding label and value method for collections' do
with_input_for @user, :name, :radio_buttons,
collection: %w[Jose Carlos],
label_method: :upcase,
value_method: :downcase
assert_select 'input[type=radio][value=jose]'
assert_select 'input[type=radio][value=carlos]'
assert_select 'label.collection_radio_buttons', 'JOSE'
assert_select 'label.collection_radio_buttons', 'CARLOS'
end
test 'input allows overriding label and value method using a lambda for collections' do
with_input_for @user, :name, :radio_buttons,
collection: %w[Jose Carlos],
label_method: ->(i) { i.upcase },
value_method: ->(i) { i.downcase }
assert_select 'input[type=radio][value=jose]'
assert_select 'input[type=radio][value=carlos]'
assert_select 'label.collection_radio_buttons', 'JOSE'
assert_select 'label.collection_radio_buttons', 'CARLOS'
end
test 'collection input with radio type generates required html attribute' do
with_input_for @user, :name, :radio_buttons, collection: %w[Jose Carlos]
assert_select 'input[type=radio].required'
assert_select 'input[type=radio][required]'
end
test 'input radio does not wrap the collection by default' do
with_input_for @user, :active, :radio_buttons
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form ul'
end
test 'input radio wraps the collection in the configured collection wrapper tag' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :radio_buttons
assert_select 'form ul input[type=radio]', count: 2
end
end
test 'input radio does not wrap the collection when configured with falsy values' do
swap SimpleForm, collection_wrapper_tag: false do
with_input_for @user, :active, :radio_buttons
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form ul'
end
end
test 'input radio allows overriding the collection wrapper tag at input level' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :radio_buttons, collection_wrapper_tag: :section
assert_select 'form section input[type=radio]', count: 2
assert_no_select 'form ul'
end
end
test 'input radio allows disabling the collection wrapper tag at input level' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :radio_buttons, collection_wrapper_tag: false
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form ul'
end
end
test 'input radio renders the wrapper tag with the configured wrapper class' do
swap SimpleForm, collection_wrapper_tag: :ul, collection_wrapper_class: 'inputs-list' do
with_input_for @user, :active, :radio_buttons
assert_select 'form ul.inputs-list input[type=radio]', count: 2
end
end
test 'input radio allows giving wrapper class at input level only' do
swap SimpleForm, collection_wrapper_tag: :ul do
with_input_for @user, :active, :radio_buttons, collection_wrapper_class: 'items-list'
assert_select 'form ul.items-list input[type=radio]', count: 2
end
end
test 'input radio uses both configured and given wrapper classes for wrapper tag' do
swap SimpleForm, collection_wrapper_tag: :ul, collection_wrapper_class: 'inputs-list' do
with_input_for @user, :active, :radio_buttons, collection_wrapper_class: 'items-list'
assert_select 'form ul.inputs-list.items-list input[type=radio]', count: 2
end
end
test 'input radio wraps each item in the configured item wrapper tag' do
swap SimpleForm, item_wrapper_tag: :li do
with_input_for @user, :active, :radio_buttons
assert_select 'form li input[type=radio]', count: 2
end
end
test 'input radio does not wrap items when configured with falsy values' do
swap SimpleForm, item_wrapper_tag: false do
with_input_for @user, :active, :radio_buttons
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form li'
end
end
test 'input radio allows overriding the item wrapper tag at input level' do
swap SimpleForm, item_wrapper_tag: :li do
with_input_for @user, :active, :radio_buttons, item_wrapper_tag: :dl
assert_select 'form dl input[type=radio]', count: 2
assert_no_select 'form li'
end
end
test 'input radio allows disabling the item wrapper tag at input level' do
swap SimpleForm, item_wrapper_tag: :ul do
with_input_for @user, :active, :radio_buttons, item_wrapper_tag: false
assert_select 'form input[type=radio]', count: 2
assert_no_select 'form li'
end
end
test 'input radio wraps items in a span tag by default' do
with_input_for @user, :active, :radio_buttons
assert_select 'form span input[type=radio]', count: 2
end
test 'input radio renders the item wrapper tag with a default class "radio"' do
with_input_for @user, :active, :radio_buttons, item_wrapper_tag: :li
assert_select 'form li.radio input[type=radio]', count: 2
end
test 'input radio renders the item wrapper tag with the configured item wrapper class' do
swap SimpleForm, item_wrapper_tag: :li, item_wrapper_class: 'item' do
with_input_for @user, :active, :radio_buttons
assert_select 'form li.radio.item input[type=radio]', count: 2
end
end
test 'input radio allows giving item wrapper class at input level only' do
swap SimpleForm, item_wrapper_tag: :li do
with_input_for @user, :active, :radio_buttons, item_wrapper_class: 'item'
assert_select 'form li.radio.item input[type=radio]', count: 2
end
end
test 'input radio uses both configured and given item wrapper classes for item wrapper tag' do
swap SimpleForm, item_wrapper_tag: :li, item_wrapper_class: 'item' do
with_input_for @user, :active, :radio_buttons, item_wrapper_class: 'inline'
assert_select 'form li.radio.item.inline input[type=radio]', count: 2
end
end
test 'input radio respects the nested boolean style config, generating nested label > input' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons
assert_select 'span.radio > label > input#user_active_true[type=radio]'
assert_select 'span.radio > label', 'Yes'
assert_select 'span.radio > label > input#user_active_false[type=radio]'
assert_select 'span.radio > label', 'No'
assert_no_select 'label.collection_radio_buttons'
end
end
test 'input radio with nested style does not overrides configured item wrapper tag' do
swap SimpleForm, boolean_style: :nested, item_wrapper_tag: :li do
with_input_for @user, :active, :radio_buttons
assert_select 'li.radio > label > input'
end
end
test 'input radio with nested style does not overrides given item wrapper tag' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons, item_wrapper_tag: :li
assert_select 'li.radio > label > input'
end
end
test 'input radio with nested style accepts giving extra wrapper classes' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons, item_wrapper_class: "inline"
assert_select 'span.radio.inline > label > input'
end
end
test 'input radio with nested style renders item labels with specified class' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons, item_label_class: "test"
assert_select 'span.radio > label.test > input'
end
end
test 'input radio with nested style and falsey input wrapper renders item labels with specified class' do
swap SimpleForm, boolean_style: :nested, item_wrapper_tag: false do
with_input_for @user, :active, :radio_buttons, item_label_class: "radio-inline"
assert_select 'label.radio-inline > input'
assert_no_select 'span.radio'
end
end
test 'input radio wrapper class are not included when set to falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female]
assert_no_select 'label.radio'
end
end
test 'input radio custom wrapper class is included when include input wrapper class is falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female], item_wrapper_class: 'custom'
assert_no_select 'label.radio'
assert_select 'span.custom'
end
end
test 'input radio with nested style and namespace uses the right for attribute' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_concat_form_for @user, namespace: :foo do |f|
concat f.input :gender, as: :radio_buttons, collection: %i[male female]
end
assert_select 'label[for=foo_user_gender_male]'
assert_select 'label[for=foo_user_gender_female]'
end
end
test 'input radio with nested style and index uses the right for attribute' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_concat_form_for @user, index: 1 do |f|
concat f.input :gender, as: :radio_buttons, collection: %i[male female]
end
assert_select 'label[for=user_1_gender_male]'
assert_select 'label[for=user_1_gender_female]'
end
end
test 'input radio with nested style accetps non-string attribute as label' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :amount,
:radio_buttons,
collection: { 100 => 'hundred', 200 => 'two_hundred' },
label_method: :first,
value_method: :second
assert_select 'input[type=radio][value=hundred]'
assert_select 'input[type=radio][value=two_hundred]'
assert_select 'span.radio > label', '100'
assert_select 'span.radio > label', '200'
end
end
test 'input check boxes with inline style support label custom classes' do
swap SimpleForm, boolean_style: :inline do
with_input_for @user, :gender, :radio_buttons, collection: %i[male female], item_label_class: 'beautiful-label'
assert_select 'label.beautiful-label', count: 2
end
end
end
================================================
FILE: test/inputs/collection_select_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class CollectionSelectInputTest < ActionView::TestCase
test 'input generates a boolean select with options by default for select types' do
with_input_for @user, :active, :select
assert_select 'select.select#user_active'
assert_select 'select option[value=true]', 'Yes'
assert_select 'select option[value=false]', 'No'
end
test 'input as select uses i18n to translate select boolean options' do
store_translations(:en, simple_form: { yes: 'Sim', no: 'Não' }) do
with_input_for @user, :active, :select
assert_select 'select option[value=true]', 'Sim'
assert_select 'select option[value=false]', 'Não'
end
end
test 'input allows overriding collection for select types' do
with_input_for @user, :name, :select, collection: %w[Jose Carlos]
assert_select 'select.select#user_name'
assert_select 'select option', 'Jose'
assert_select 'select option', 'Carlos'
end
test 'input does automatic collection translation for select types using defaults key' do
store_translations(:en, simple_form: { options: { defaults: {
gender: { male: 'Male', female: 'Female' }
} } }) do
with_input_for @user, :gender, :select, collection: %i[male female]
assert_select 'select.select#user_gender'
assert_select 'select option', 'Male'
assert_select 'select option', 'Female'
end
end
test 'input does automatic collection translation for select types using specific object key' do
store_translations(:en, simple_form: { options: { user: {
gender: { male: 'Male', female: 'Female' }
} } }) do
with_input_for @user, :gender, :select, collection: %i[male female]
assert_select 'select.select#user_gender'
assert_select 'select option', 'Male'
assert_select 'select option', 'Female'
end
end
test 'input marks the selected value by default' do
@user.name = "Carlos"
with_input_for @user, :name, :select, collection: %w[Jose Carlos]
assert_select 'select option[selected=selected]', 'Carlos'
end
test 'input accepts html options as the last element of collection' do
with_input_for @user, :name, :select, collection: [['Jose', class: 'foo']]
assert_select 'select.select#user_name'
assert_select 'select option.foo', 'Jose'
end
test 'input marks the selected value also when using integers' do
@user.age = 18
with_input_for @user, :age, :select, collection: 18..60
assert_select 'select option[selected=selected]', '18'
end
test 'input marks the selected value when using booleans and select' do
@user.active = false
with_input_for @user, :active, :select
assert_no_select 'select option[selected][value=true]', 'Yes'
assert_select 'select option[selected][value=false]', 'No'
end
test 'input sets the correct value when using a collection that includes floats' do
with_input_for @user, :age, :select, collection: [2.0, 2.5, 3.0, 3.5, 4.0, 4.5]
assert_select 'select option[value="2.0"]'
assert_select 'select option[value="2.5"]'
end
test 'input sets the correct values when using a collection that uses mixed values' do
with_input_for @user, :age, :select, collection: ["Hello Kitty", 2, 4.5, :johnny, nil, true, false]
assert_select 'select option[value="Hello Kitty"]'
assert_select 'select option[value="2"]'
assert_select 'select option[value="4.5"]'
assert_select 'select option[value="johnny"]'
assert_select 'select option[value=""]'
assert_select 'select option[value="true"]'
assert_select 'select option[value="false"]'
end
test 'input includes a blank option even if :include_blank is set to false if the collection includes a nil value' do
with_input_for @user, :age, :select, collection: [nil], include_blank: false
assert_select 'select option[value=""]'
end
test 'input automatically sets include blank' do
with_input_for @user, :age, :select, collection: 18..30
assert_select 'select option[value=""]', ''
end
test 'input translates include blank when set to :translate' do
store_translations(:en, simple_form: { include_blanks: { user: {
age: 'Rather not say'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, include_blank: :translate
assert_select 'select option[value=""]', 'Rather not say'
end
end
test 'input translates include blank with a default' do
store_translations(:en, simple_form: { include_blanks: { defaults: {
age: 'Rather not say'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, include_blank: :translate
assert_select 'select option[value=""]', 'Rather not say'
end
end
test 'input does not translate include blank when set to a string' do
store_translations(:en, simple_form: { include_blanks: { user: {
age: 'Rather not say'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, include_blank: 'Young at heart'
assert_select 'select option[value=""]', 'Young at heart'
end
end
test 'input does not translate include blank when automatically set' do
store_translations(:en, simple_form: { include_blanks: { user: {
age: 'Rather not say'
} } }) do
with_input_for @user, :age, :select, collection: 18..30
assert_select 'select option[value=""]', ''
end
end
test 'input does not translate include blank when set to true' do
store_translations(:en, simple_form: { include_blanks: { user: {
age: 'Rather not say'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, include_blank: true
assert_select 'select option[value=""]', ''
end
end
test 'input does not translate include blank when set to false' do
store_translations(:en, simple_form: { include_blanks: { user: {
age: 'Rather not say'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, include_blank: false
assert_no_select 'select option[value=""]'
end
end
test 'input does not set include blank if otherwise is told' do
with_input_for @user, :age, :select, collection: 18..30, include_blank: false
assert_no_select 'select option[value=""]'
end
test 'input does not set include blank if prompt is given' do
with_input_for @user, :age, :select, collection: 18..30, prompt: "Please select foo"
assert_no_select 'select option[value=""]', ''
end
test 'input does not set include blank if multiple is given' do
with_input_for @user, :age, :select, collection: 18..30, input_html: { multiple: true }
assert_no_select 'select option[value=""]', ''
end
test 'input translates prompt when set to :translate' do
store_translations(:en, simple_form: { prompts: { user: {
age: 'Select age:'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, prompt: :translate
assert_select 'select option[value=""]', 'Select age:'
end
end
test 'input translates prompt with a default' do
store_translations(:en, simple_form: { prompts: { defaults: {
age: 'Select age:'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, prompt: :translate
assert_select 'select option[value=""]', 'Select age:'
end
end
test 'input does not translate prompt when set to a string' do
store_translations(:en, simple_form: { prompts: { user: {
age: 'Select age:'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, prompt: 'Do it:'
assert_select 'select option[value=""]', 'Do it:'
end
end
test 'input does not translate prompt when set to false' do
store_translations(:en, simple_form: { prompts: { user: {
age: 'Select age:'
} } }) do
with_input_for @user, :age, :select, collection: 18..30, prompt: false
assert_no_select 'select option[value=""]'
end
end
test 'input uses Rails prompt translation as a fallback' do
store_translations(:en, helpers: { select: {
prompt: 'Select value:'
} }) do
with_input_for @user, :age, :select, collection: 18..30, prompt: :translate
assert_select 'select option[value=""]', "Select value:"
end
end
test 'input detects label and value on collections' do
users = [User.build(id: 1, name: "Jose"), User.build(id: 2, name: "Carlos")]
with_input_for @user, :description, :select, collection: users
assert_select 'select option[value="1"]', 'Jose'
assert_select 'select option[value="2"]', 'Carlos'
end
test 'input disables the entire select and no specific options when given `true`' do
with_input_for @user, :description, :select, collection: %w[Jose Carlos], disabled: true
assert_no_select 'select option[value=Jose][disabled=disabled]', 'Jose'
assert_no_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
assert_select 'select[disabled=disabled]'
assert_select 'div.disabled'
end
test 'input allows disabling only a single given option' do
with_input_for @user, :description, :select, collection: %w[Jose Carlos], disabled: 'Jose'
assert_select 'select option[value=Jose][disabled=disabled]', 'Jose'
assert_no_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
assert_no_select 'select[disabled=disabled]'
assert_no_select 'div.disabled'
end
test 'input allows disabling multiple given options' do
with_input_for @user, :description, :select, collection: %w[Jose Carlos], disabled: %w[Jose Carlos]
assert_select 'select option[value=Jose][disabled=disabled]', 'Jose'
assert_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
assert_no_select 'select[disabled=disabled]'
assert_no_select 'div.disabled'
end
test 'input allows overriding label and value method using a lambda for collection selects' do
with_input_for @user, :name, :select,
collection: %w[Jose Carlos],
label_method: ->(i) { i.upcase },
value_method: ->(i) { i.downcase }
assert_select 'select option[value=jose]', "JOSE"
assert_select 'select option[value=carlos]', "CARLOS"
end
test 'input allows overriding only label but not value method using a lambda for collection select' do
with_input_for @user, :name, :select,
collection: %w[Jose Carlos],
label_method: ->(i) { i.upcase }
assert_select 'select option[value=Jose]', "JOSE"
assert_select 'select option[value=Carlos]', "CARLOS"
end
test 'input allows overriding only value but not label method using a lambda for collection select' do
with_input_for @user, :name, :select,
collection: %w[Jose Carlos],
value_method: ->(i) { i.downcase }
assert_select 'select option[value=jose]', "Jose"
assert_select 'select option[value=carlos]', "Carlos"
end
test 'input allows symbols for collections' do
with_input_for @user, :name, :select, collection: %i[jose carlos]
assert_select 'select.select#user_name'
assert_select 'select option[value=jose]', 'jose'
assert_select 'select option[value=carlos]', 'carlos'
end
test 'collection input with select type generates required html attribute only with blank option' do
with_input_for @user, :name, :select, include_blank: true, collection: %w[Jose Carlos]
assert_select 'select.required'
assert_select 'select[required]'
end
test 'collection input with select type generates required html attribute only with blank option or prompt' do
with_input_for @user, :name, :select, prompt: 'Name...', collection: %w[Jose Carlos]
assert_select 'select.required'
assert_select 'select[required]'
end
test "collection input generated aria-label should contain 'true'" do
with_input_for @user, :age, :select, collection: 18..30, prompt: "Please select foo"
assert_select 'select.required'
end
test 'collection input with select type does not generate required html attribute without blank option' do
with_input_for @user, :name, :select, include_blank: false, collection: %w[Jose Carlos]
assert_select 'select.required'
assert_no_select 'select[required]'
end
test 'collection input with select type with multiple attribute generates required html attribute without blank option' do
with_input_for @user, :name, :select, include_blank: false, input_html: { multiple: true }, collection: %w[Jose Carlos]
assert_select 'select.required'
assert_select 'select[required]'
end
test 'collection input with select type with multiple attribute generates required html attribute with blank option' do
with_input_for @user, :name, :select, include_blank: true, input_html: { multiple: true }, collection: %w[Jose Carlos]
assert_select 'select.required'
assert_select 'select[required]'
end
test 'input allows disabled options with a lambda for collection select' do
with_input_for @user, :name, :select, collection: %w[Carlos Antonio],
disabled: ->(x) { x == "Carlos" }
assert_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
assert_select 'select option[value=Antonio]', 'Antonio'
assert_no_select 'select option[value=Antonio][disabled]'
end
test 'input allows disabled and label method with lambdas for collection select' do
with_input_for @user, :name, :select, collection: %w[Carlos Antonio],
disabled: ->(x) { x == "Carlos" }, label_method: ->(x) { x.upcase }
assert_select 'select option[value=Carlos][disabled=disabled]', 'CARLOS'
assert_select 'select option[value=Antonio]', 'ANTONIO'
assert_no_select 'select option[value=Antonio][disabled]'
end
test 'input allows a non lambda disabled option with lambda label method for collections' do
with_input_for @user, :name, :select, collection: %w[Carlos Antonio],
disabled: "Carlos", label_method: ->(x) { x.upcase }
assert_select 'select option[value=Carlos][disabled=disabled]', 'CARLOS'
assert_select 'select option[value=Antonio]', 'ANTONIO'
assert_no_select 'select option[value=Antonio][disabled]'
end
test 'input allows selected and label method with lambdas for collection select' do
with_input_for @user, :name, :select, collection: %w[Carlos Antonio],
selected: ->(x) { x == "Carlos" }, label_method: ->(x) { x.upcase }
assert_select 'select option[value=Carlos][selected=selected]', 'CARLOS'
assert_select 'select option[value=Antonio]', 'ANTONIO'
assert_no_select 'select option[value=Antonio][selected]'
end
test 'input allows a non lambda selected option with lambda label method for collection select' do
with_input_for @user, :name, :select, collection: %w[Carlos Antonio],
selected: "Carlos", label_method: ->(x) { x.upcase }
assert_select 'select option[value=Carlos][selected=selected]', 'CARLOS'
assert_select 'select option[value=Antonio]', 'ANTONIO'
assert_no_select 'select option[value=Antonio][selected]'
end
test 'input does not override default selection through attribute value with label method as lambda for collection select' do
@user.name = "Carlos"
with_input_for @user, :name, :select, collection: %w[Carlos Antonio],
label_method: ->(x) { x.upcase }
assert_select 'select option[value=Carlos][selected=selected]', 'CARLOS'
assert_select 'select option[value=Antonio]', 'ANTONIO'
assert_no_select 'select option[value=Antonio][selected]'
end
end
================================================
FILE: test/inputs/color_input_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class ColorInputTest < ActionView::TestCase
test 'input generates a color field' do
with_input_for @user, :favorite_color, :color
assert_select 'input[type=color].color#user_favorite_color'
end
end
================================================
FILE: test/inputs/country_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class CountryInputTest < ActionView::TestCase
COUNTRY_SELECT_SEPARATOR =
if Gem::Version.new(CountrySelect::VERSION) >= Gem::Version.new("11.0.0")
"hr"
else
'option[value="---------------"][disabled=disabled]'
end
test 'input generates a country select field' do
with_input_for @user, :country, :country
assert_select 'select#user_country'
assert_select 'select option[value=BR]', 'Brazil'
assert_no_select 'select option[value=""][disabled=disabled]'
assert_no_select "select #{COUNTRY_SELECT_SEPARATOR}"
end
test 'input generates a country select with SimpleForm default' do
swap SimpleForm, country_priority: [ 'Brazil' ] do
with_input_for @user, :country, :country
assert_select %(select option[value="BR"] + #{COUNTRY_SELECT_SEPARATOR})
end
end
test 'input generates a country select using options priority' do
with_input_for @user, :country, :country, priority: [ 'Ukraine' ]
assert_select %(select option[value="UA"] + #{COUNTRY_SELECT_SEPARATOR})
end
test 'input does generate select element with required html attribute' do
with_input_for @user, :country, :country
assert_select 'select.required'
assert_select 'select[required]'
end
end
================================================
FILE: test/inputs/datetime_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
# Tests for datetime, date and time inputs when HTML5 compatibility is enabled in the wrapper.
class DateTimeInputWithHtml5Test < ActionView::TestCase
test 'input generates a datetime input for datetime attributes if HTML5 compatibility is explicitly enbled' do
with_input_for @user, :created_at, :datetime, html5: true
assert_select 'input[type="datetime-local"]'
end
test 'input generates a datetime select for datetime attributes' do
with_input_for @user, :created_at, :datetime
assert_select 'select.datetime'
end
test 'input generates a date input for date attributes if HTML5 compatibility is explicitly enbled' do
with_input_for @user, :born_at, :date, html5: true
assert_select 'input[type="date"]'
end
test 'input generates a date select for date attributes' do
with_input_for @user, :born_at, :date
assert_select 'select.date'
end
test 'input generates a time input for time attributes if HTML5 compatibility is explicitly enbled' do
with_input_for @user, :delivery_time, :time, html5: true
assert_select 'input[type="time"]'
end
test 'input generates a time select for time attributes' do
with_input_for @user, :delivery_time, :time
assert_select 'select.time'
end
test 'input generates required html attribute' do
with_input_for @user, :delivery_time, :time, required: true, html5: true
assert_select 'input.required'
assert_select 'input[required]'
end
end
# Tests for datetime, date and time inputs when HTML5 compatibility is enabled in the wrapper.
class DateTimeInputWithoutHtml5Test < ActionView::TestCase
test 'input generates a datetime select by default for datetime attributes' do
swap_wrapper do
with_input_for @user, :created_at, :datetime
1.upto(5) do |i|
assert_select "form select.datetime#user_created_at_#{i}i"
end
end
end
test 'input is able to pass options to datetime select' do
with_input_for @user, :created_at, :datetime, html5: false,
disabled: true, prompt: { year: 'ano', month: 'mês', day: 'dia' }
assert_select 'select.datetime[disabled=disabled]'
assert_select 'select.datetime option', 'ano'
assert_select 'select.datetime option', 'mês'
assert_select 'select.datetime option', 'dia'
end
test 'input generates a datetime input for datetime attributes if HTML5 compatibility is explicitly enabled' do
swap_wrapper do
with_input_for @user, :created_at, :datetime, html5: true
assert_select 'input[type="datetime-local"]'
end
end
test 'input generates a date select for date attributes' do
swap_wrapper do
with_input_for @user, :born_at, :date
assert_select 'select.date#user_born_at_1i'
assert_select 'select.date#user_born_at_2i'
assert_select 'select.date#user_born_at_3i'
assert_no_select 'select.date#user_born_at_4i'
end
end
test 'input is able to pass options to date select' do
with_input_for @user, :born_at, :date, as: :date, html5: false,
disabled: true, prompt: { year: 'ano', month: 'mês', day: 'dia' }
assert_select 'select.date[disabled=disabled]'
assert_select 'select.date option', 'ano'
assert_select 'select.date option', 'mês'
assert_select 'select.date option', 'dia'
end
test 'input is able to pass :default to date select' do
with_input_for @user, :born_at, :date, default: Date.today, html5: false
assert_select "select.date option[value='#{Date.today.year}'][selected=selected]"
end
test 'input generates a date input for date attributes if HTML5 compatibility is explicitly enabled' do
swap_wrapper do
with_input_for @user, :born_at, :date, html5: true
assert_select 'input[type="date"]'
end
end
test 'input generates a time select for time attributes' do
swap_wrapper do
with_input_for @user, :delivery_time, :time
assert_select 'input[type=hidden]#user_delivery_time_1i'
assert_select 'input[type=hidden]#user_delivery_time_2i'
assert_select 'input[type=hidden]#user_delivery_time_3i'
assert_select 'select.time#user_delivery_time_4i'
assert_select 'select.time#user_delivery_time_5i'
end
end
test 'input is able to pass options to time select' do
with_input_for @user, :delivery_time, :time, required: true, html5: false,
disabled: true, prompt: { hour: 'hora', minute: 'minuto' }
assert_select 'select.time[disabled=disabled]'
assert_select 'select.time option', 'hora'
assert_select 'select.time option', 'minuto'
end
test 'input generates a time input for time attributes if HTML5 compatibility is explicitly enabled' do
swap_wrapper do
with_input_for @user, :delivery_time, :time, html5: true
assert_select 'input[type="time"]'
end
end
test 'label uses i18n to get target for date input type' do
store_translations(:en, date: { order: %w[month day year] }) do
with_input_for :project, :created_at, :date, html5: false
assert_select 'label[for=project_created_at_2i]'
end
end
test 'label uses i18n to get target for datetime input type' do
store_translations(:en, date: { order: %w[month day year] }) do
with_input_for :project, :created_at, :datetime, html5: false
assert_select 'label[for=project_created_at_2i]'
end
end
test 'label uses order to get target when date input type' do
with_input_for :project, :created_at, :date, order: %w[month year day], html5: false
assert_select 'label[for=project_created_at_2i]'
end
test 'label uses order to get target when datetime input type' do
with_input_for :project, :created_at, :datetime, order: %w[month year day], html5: false
assert_select 'label[for=project_created_at_2i]'
end
test 'label points to first option when time input type' do
with_input_for :project, :created_at, :time, html5: false
assert_select 'label[for=project_created_at_4i]'
end
test 'label points to attribute name if HTML5 compatibility is explicitly enabled' do
with_input_for :project, :created_at, :date, html5: true
assert_select 'label[for=project_created_at]'
end
end
================================================
FILE: test/inputs/disabled_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class DisabledTest < ActionView::TestCase
test 'string input is disabled when disabled option is true' do
with_input_for @user, :name, :string, disabled: true
assert_select 'input.string.disabled[disabled]'
end
test 'text input is disabled when disabled option is true' do
with_input_for @user, :description, :text, disabled: true
assert_select 'textarea.text.disabled[disabled]'
end
test 'numeric input is disabled when disabled option is true' do
with_input_for @user, :age, :integer, disabled: true
assert_select 'input.integer.disabled[disabled]'
end
test 'date input is disabled when disabled option is true' do
with_input_for @user, :born_at, :date, disabled: true
assert_select 'select.date.disabled[disabled]'
end
test 'datetime input is disabled when disabled option is true' do
with_input_for @user, :created_at, :datetime, disabled: true
assert_select 'select.datetime.disabled[disabled]'
end
test 'string input does not be disabled when disabled option is false' do
with_input_for @user, :name, :string, disabled: false
assert_no_select 'input.string.disabled[disabled]'
end
test 'text input does not be disabled when disabled option is false' do
with_input_for @user, :description, :text, disabled: false
assert_no_select 'textarea.text.disabled[disabled]'
end
test 'numeric input does not be disabled when disabled option is false' do
with_input_for @user, :age, :integer, disabled: false
assert_no_select 'input.integer.disabled[disabled]'
end
test 'date input does not be disabled when disabled option is false' do
with_input_for @user, :born_at, :date, disabled: false
assert_no_select 'select.date.disabled[disabled]'
end
test 'datetime input does not be disabled when disabled option is false' do
with_input_for @user, :created_at, :datetime, disabled: false
assert_no_select 'select.datetime.disabled[disabled]'
end
test 'string input does not be disabled when disabled option is not present' do
with_input_for @user, :name, :string
assert_no_select 'input.string.disabled[disabled]'
end
test 'text input does not be disabled when disabled option is not present' do
with_input_for @user, :description, :text
assert_no_select 'textarea.text.disabled[disabled]'
end
test 'numeric input does not be disabled when disabled option is not present' do
with_input_for @user, :age, :integer
assert_no_select 'input.integer.disabled[disabled]'
end
test 'date input does not be disabled when disabled option is not present' do
with_input_for @user, :born_at, :date
assert_no_select 'select.date.disabled[disabled]'
end
test 'datetime input does not be disabled when disabled option is not present' do
with_input_for @user, :created_at, :datetime
assert_no_select 'select.datetime.disabled[disabled]'
end
test 'input_field collection allows disabled select' do
with_input_field_for @user, :description, collection: ['foo', 'bar'], disabled: true
assert_select 'select[disabled]'
assert_no_select 'option[disabled]'
end
test 'input_field collection allows individual disabled options' do
with_input_field_for @user, :description, collection: ['foo', 'bar'], disabled: 'bar'
assert_no_select 'select[disabled]'
assert_no_select 'option[disabled][value=foo]'
assert_select 'option[disabled][value=bar]'
end
end
================================================
FILE: test/inputs/discovery_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class DiscoveryTest < ActionView::TestCase
# Setup new inputs and remove them after the test.
def discovery(value = false)
swap SimpleForm, cache_discovery: value do
begin
load "support/discovery_inputs.rb"
yield
ensure
SimpleForm::FormBuilder.discovery_cache.clear
Object.send :remove_const, :StringInput
Object.send :remove_const, :NumericInput
Object.send :remove_const, :CustomizedInput
Object.send :remove_const, :DeprecatedInput
Object.send :remove_const, :CollectionSelectInput
Object.send :remove_const, :FileInput
CustomInputs.send :remove_const, :CustomizedInput
CustomInputs.send :remove_const, :PasswordInput
CustomInputs.send :remove_const, :NumericInput
end
end
end
test 'builder does not discover new inputs if cached' do
with_form_for @user, :name
assert_select 'form input#user_name.string'
discovery(true) do
with_form_for @user, :name
assert_no_select 'form section input#user_name.string'
end
end
test 'builder discovers new inputs' do
discovery do
with_form_for @user, :name, as: :customized
assert_select 'form section input#user_name.string'
end
end
test 'builder does not discover new inputs if discovery is off' do
with_form_for @user, :name
assert_select 'form input#user_name.string'
swap SimpleForm, inputs_discovery: false do
discovery do
with_form_for @user, :name
assert_no_select 'form section input#user_name.string'
end
end
end
test 'builder discovers new inputs from mappings if not cached' do
discovery do
with_form_for @user, :name
assert_select 'form section input#user_name.string'
end
end
test 'builder discovers new inputs from internal fallbacks if not cached' do
discovery do
with_form_for @user, :age
assert_select 'form section input#user_age.numeric.integer'
end
end
test 'builder discovers new mapped inputs from configured namespaces if not cached' do
discovery do
swap SimpleForm, custom_inputs_namespaces: ['CustomInputs'] do
with_form_for @user, :password
assert_select 'form input#user_password.password-custom-input'
end
end
end
test 'builder discovers new mapped inputs from configured namespaces before the ones from top level namespace' do
discovery do
swap SimpleForm, custom_inputs_namespaces: ['CustomInputs'] do
with_form_for @user, :age
assert_select 'form input#user_age.numeric-custom-input'
end
end
end
test 'builder discovers new custom inputs from configured namespace before the ones from top level namespace' do
discovery do
swap SimpleForm, custom_inputs_namespaces: ['CustomInputs'] do
with_form_for @user, :name, as: 'customized'
assert_select 'form input#user_name.customized-namespace-custom-input'
end
end
end
test 'raises error when configured namespace does not exists' do
discovery do
swap SimpleForm, custom_inputs_namespaces: ['InvalidNamespace'] do
assert_raise NameError do
with_form_for @user, :age
end
end
end
end
test 'new inputs can override the input_html_options' do
discovery do
with_form_for @user, :active, as: :select
assert_select 'form select#user_active.select.chosen'
end
end
test 'does not duplicate the html classes giving a extra class' do
discovery do
swap SimpleForm, input_class: 'custom-default-input-class' do
with_form_for @user, :active, as: :select
assert_select 'form select#user_active.select' do
# Make sure class list contains 'chosen' only once.
assert_select ":match('class', ?)", /^(?!.*\bchosen\b.*\bchosen\b).*\bchosen\b.*$/
end
end
end
end
test 'new inputs can override the default input_html_classes' do
discovery do
with_form_for @user, :avatar, as: :file
assert_no_select 'form input[type=file]#user_avatar.file.file-upload'
assert_select 'form input[type=file]#user_avatar.file-upload'
end
end
test 'inputs method without wrapper_options are deprecated' do
discovery do
assert_deprecated('input method now accepts a `wrapper_options` argument.', SimpleForm.deprecator) do
with_form_for @user, :name, as: :deprecated
end
assert_select 'form section input#user_name.string'
end
end
end
================================================
FILE: test/inputs/file_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class FileInputTest < ActionView::TestCase
test 'input generates a file field' do
with_input_for @user, :name, :file
assert_select 'input#user_name[type=file]'
end
test "input generates a file field that doesn't accept placeholder" do
store_translations(:en, simple_form: { placeholders: { user: { name: "text" } } }) do
with_input_for @user, :name, :file
assert_no_select 'input[placeholder]'
end
end
end
================================================
FILE: test/inputs/general_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class InputTest < ActionView::TestCase
test 'input generates css class based on default input type' do
with_input_for @user, :name, :string
assert_select 'input.string'
with_input_for @user, :description, :text
assert_select 'textarea.text'
with_input_for @user, :age, :integer
assert_select 'input.integer'
with_input_for @user, :born_at, :date
assert_select 'select.date'
with_input_for @user, :created_at, :datetime
assert_select 'select.datetime'
end
test 'string input generates autofocus attribute when autofocus option is true' do
with_input_for @user, :name, :string, autofocus: true
assert_select 'input.string[autofocus]'
end
test 'input accepts input_class configuration' do
swap SimpleForm, input_class: :xlarge do
with_input_for @user, :name, :string
assert_select 'input.xlarge'
assert_no_select 'div.xlarge'
end
end
test 'input does not add input_class when configured to not generate additional classes for input' do
swap SimpleForm, input_class: 'xlarge', generate_additional_classes_for: [:wrapper] do
with_input_for @user, :name, :string
assert_select 'input'
assert_no_select '.xlarge'
end
end
test 'text input generates autofocus attribute when autofocus option is true' do
with_input_for @user, :description, :text, autofocus: true
assert_select 'textarea.text[autofocus]'
end
test 'numeric input generates autofocus attribute when autofocus option is true' do
with_input_for @user, :age, :integer, autofocus: true
assert_select 'input.integer[autofocus]'
end
test 'date input generates autofocus attribute when autofocus option is true' do
with_input_for @user, :born_at, :date, autofocus: true
assert_select 'select.date[autofocus]'
end
test 'datetime input generates autofocus attribute when autofocus option is true' do
with_input_for @user, :created_at, :datetime, autofocus: true
assert_select 'select.datetime[autofocus]'
end
test 'string input generates autofocus attribute when autofocus option is false' do
with_input_for @user, :name, :string, autofocus: false
assert_no_select 'input.string[autofocus]'
end
test 'text input generates autofocus attribute when autofocus option is false' do
with_input_for @user, :description, :text, autofocus: false
assert_no_select 'textarea.text[autofocus]'
end
test 'numeric input generates autofocus attribute when autofocus option is false' do
with_input_for @user, :age, :integer, autofocus: false
assert_no_select 'input.integer[autofocus]'
end
test 'date input generates autofocus attribute when autofocus option is false' do
with_input_for @user, :born_at, :date, autofocus: false
assert_no_select 'select.date[autofocus]'
end
test 'datetime input generates autofocus attribute when autofocus option is false' do
with_input_for @user, :created_at, :datetime, autofocus: false
assert_no_select 'select.datetime[autofocus]'
end
test 'string input generates autofocus attribute when autofocus option is not present' do
with_input_for @user, :name, :string
assert_no_select 'input.string[autofocus]'
end
test 'text input generates autofocus attribute when autofocus option is not present' do
with_input_for @user, :description, :text
assert_no_select 'textarea.text[autofocus]'
end
test 'numeric input generates autofocus attribute when autofocus option is not present' do
with_input_for @user, :age, :integer
assert_no_select 'input.integer[autofocus]'
end
test 'date input generates autofocus attribute when autofocus option is not present' do
with_input_for @user, :born_at, :date
assert_no_select 'select.date[autofocus]'
end
test 'datetime input generates autofocus attribute when autofocus option is not present' do
with_input_for @user, :created_at, :datetime
assert_no_select 'select.datetime[autofocus]'
end
# With no object
test 'input is generated properly when object is not present' do
with_input_for :project, :name, :string
assert_select 'input.string.required#project_name'
end
test 'input as radio is generated properly when object is not present ' do
with_input_for :project, :name, :radio_buttons
assert_select 'input.radio_buttons#project_name_true'
assert_select 'input.radio_buttons#project_name_false'
end
test 'input as select with collection is generated properly when object is not present' do
with_input_for :project, :name, :select, collection: %w[Jose Carlos]
assert_select 'select.select#project_name'
end
test 'input does not generate empty css class' do
swap SimpleForm, generate_additional_classes_for: %i[wrapper label] do
with_input_for :project, :name, :string
assert_no_select 'input#project_name[class]'
end
end
end
================================================
FILE: test/inputs/grouped_collection_select_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class GroupedCollectionSelectInputTest < ActionView::TestCase
test 'grouped collection accepts array collection form' do
with_input_for @user, :tag_ids, :grouped_select,
collection: [['Authors', %w[Jose Carlos]], ['General', %w[Bob John]]],
group_method: :last
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option', 'Jose'
assert_select 'option', 'Carlos'
end
assert_select 'optgroup[label=General]' do
assert_select 'option', 'Bob'
assert_select 'option', 'John'
end
end
end
test 'grouped collection accepts empty array collection form' do
with_input_for @user, :tag_ids, :grouped_select,
collection: [],
group_method: :last
assert_select 'select.grouped_select#user_tag_ids'
end
test 'grouped collection accepts proc as collection' do
with_input_for @user, :tag_ids, :grouped_select,
collection: proc { [['Authors', %w[Jose Carlos]], ['General', %w[Bob John]]] },
group_method: :last
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option', 'Jose'
assert_select 'option', 'Carlos'
end
assert_select 'optgroup[label=General]' do
assert_select 'option', 'Bob'
assert_select 'option', 'John'
end
end
end
test 'grouped collection accepts hash collection form' do
with_input_for @user, :tag_ids, :grouped_select,
collection: { Authors: %w[Jose Carlos], General: %w[Bob John] },
group_method: :last
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option', 'Jose'
assert_select 'option', 'Carlos'
end
assert_select 'optgroup[label=General]' do
assert_select 'option', 'Bob'
assert_select 'option', 'John'
end
end
end
test 'grouped collection allows overriding group_method using a lambda' do
with_input_for @user, :tag_ids, :grouped_select,
collection: { Authors: %w[Jose Carlos] },
group_method: ->(i) { i.last }
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option[value=Jose]', 'Jose'
assert_select 'option[value=Carlos]', 'Carlos'
end
end
end
test 'grouped collection accepts group_label_method option' do
with_input_for @user, :tag_ids, :grouped_select,
collection: { %w[Jose Carlos] => 'Authors' },
group_method: :first,
group_label_method: :last
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option', 'Jose'
assert_select 'option', 'Carlos'
end
end
end
test 'grouped collection finds default label methods on the group objects' do
option_list = %w[Jose Carlos]
GroupedClass = Struct.new(:to_label, :options)
group = GroupedClass.new("Authors", option_list)
with_input_for @user, :tag_ids, :grouped_select,
collection: [group],
group_method: :options
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option', 'Jose'
assert_select 'option', 'Carlos'
end
end
end
test 'grouped collections finds the default label method from the first non-empty object' do
Agent = Struct.new(:id, :name)
agents = [["First", []], ["Second", [Agent.new(7, 'Bond'), Agent.new(47, 'Hitman')]]]
with_input_for @user, :tag_ids, :grouped_select,
collection: agents,
group_label_method: :first,
group_method: :last,
include_blank: false
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Second]' do
assert_select 'option[value="7"]', 'Bond'
assert_select 'option[value="47"]', 'Hitman'
end
end
end
test 'grouped collection accepts label and value methods options' do
with_input_for @user, :tag_ids, :grouped_select,
collection: { Authors: %w[Jose Carlos] },
group_method: :last,
label_method: :upcase,
value_method: :downcase
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option[value=jose]', 'JOSE'
assert_select 'option[value=carlos]', 'CARLOS'
end
end
end
test 'grouped collection allows overriding label and value methods using a lambda' do
with_input_for @user, :tag_ids, :grouped_select,
collection: { Authors: %w[Jose Carlos] },
group_method: :last,
label_method: ->(i) { i.upcase },
value_method: ->(i) { i.downcase }
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option[value=jose]', 'JOSE'
assert_select 'option[value=carlos]', 'CARLOS'
end
end
end
test 'grouped collection with associations' do
tag_groups = [
TagGroup.new(1, "Group of Tags", [Tag.new(1, "Tag 1"), Tag.new(2, "Tag 2")]),
TagGroup.new(2, "Other group", [Tag.new(3, "Tag 3"), Tag.new(4, "Tag 4")])
]
with_input_for @user, :tag_ids, :grouped_select,
collection: tag_groups, group_method: :tags
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label="Group of Tags"]' do
assert_select 'option[value="1"]', 'Tag 1'
assert_select 'option[value="2"]', 'Tag 2'
end
assert_select 'optgroup[label="Other group"]' do
assert_select 'option[value="3"]', 'Tag 3'
assert_select 'option[value="4"]', 'Tag 4'
end
end
end
test 'grouped collection accepts html options as the last element of collection' do
with_input_for @user, :tag_ids, :grouped_select,
collection: [['Authors', [['Jose', 'jose', class: 'foo'], ['Carlos', 'carlos', class: 'bar']]]],
group_method: :last
assert_select 'select.grouped_select#user_tag_ids' do
assert_select 'optgroup[label=Authors]' do
assert_select 'option.foo', 'Jose'
assert_select 'option.bar', 'Carlos'
end
end
end
end
================================================
FILE: test/inputs/hidden_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class HiddenInputTest < ActionView::TestCase
test 'input generates a hidden field' do
with_input_for @user, :name, :hidden
assert_no_select 'input[type=text]'
assert_select 'input#user_name[type=hidden]'
end
test 'hint does not be generated for hidden fields' do
store_translations(:en, simple_form: { hints: { user: { name: "text" } } }) do
with_input_for @user, :name, :hidden
assert_no_select 'span.hint'
end
end
test 'label does not be generated for hidden inputs' do
with_input_for @user, :name, :hidden
assert_no_select 'label'
end
test 'required/optional options does not be generated for hidden inputs' do
with_input_for @user, :name, :hidden
assert_no_select 'input.required'
assert_no_select 'input[required]'
assert_no_select 'input.optional'
assert_select 'input.hidden#user_name'
end
end
================================================
FILE: test/inputs/numeric_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class NumericInputTest < ActionView::TestCase
test 'input generates an integer text field for integer attributes ' do
with_input_for @user, :age, :integer
assert_select 'input[type=number].integer#user_age'
end
test 'input generates a float text field for float attributes ' do
with_input_for @user, :age, :float
assert_select 'input[type=number].float#user_age'
end
test 'input generates a decimal text field for decimal attributes ' do
with_input_for @user, :age, :decimal
assert_select 'input[type=number].decimal#user_age'
end
test 'input does not generate min attribute by default' do
with_input_for @user, :age, :integer
assert_no_select 'input[min]'
end
test 'input does not generate max attribute by default' do
with_input_for @user, :age, :integer
assert_no_select 'input[max]'
end
test 'input infers min value from integer attributes with greater than validation' do
with_input_for @other_validating_user, :age, :float
assert_no_select 'input[min]'
with_input_for @other_validating_user, :age, :integer
assert_select 'input[min="18"]'
end
test 'input infers min value from integer attributes with greater than validation using symbol' do
with_input_for @validating_user, :amount, :float
assert_no_select 'input[min]'
with_input_for @validating_user, :amount, :integer
assert_select 'input[min="11"]'
end
test 'input infers min value from integer attributes with greater than or equal to validation using symbol' do
with_input_for @validating_user, :attempts, :float
assert_select 'input[min="1"]'
with_input_for @validating_user, :attempts, :integer
assert_select 'input[min="1"]'
end
test 'input infers min value from integer attributes with greater than validation using proc' do
with_input_for @other_validating_user, :amount, :float
assert_no_select 'input[min]'
with_input_for @other_validating_user, :amount, :integer
assert_select 'input[min="20"]'
end
test 'input infers min value from integer attributes with greater than or equal to validation using proc' do
with_input_for @other_validating_user, :attempts, :float
assert_select 'input[min="19"]'
with_input_for @other_validating_user, :attempts, :integer
assert_select 'input[min="19"]'
end
test 'input infers max value from attributes with less than validation' do
with_input_for @other_validating_user, :age, :float
assert_no_select 'input[max]'
with_input_for @other_validating_user, :age, :integer
assert_select 'input[max="99"]'
end
test 'input infers max value from attributes with less than validation using symbol' do
with_input_for @validating_user, :amount, :float
assert_no_select 'input[max]'
with_input_for @validating_user, :amount, :integer
assert_select 'input[max="99"]'
end
test 'input infers max value from attributes with less than or equal to validation using symbol' do
with_input_for @validating_user, :attempts, :float
assert_select 'input[max="100"]'
with_input_for @validating_user, :attempts, :integer
assert_select 'input[max="100"]'
end
test 'input infers max value from attributes with less than validation using proc' do
with_input_for @other_validating_user, :amount, :float
assert_no_select 'input[max]'
with_input_for @other_validating_user, :amount, :integer
assert_select 'input[max="118"]'
end
test 'input infers max value from attributes with less than or equal to validation using proc' do
with_input_for @other_validating_user, :attempts, :float
assert_select 'input[max="119"]'
with_input_for @other_validating_user, :attempts, :integer
assert_select 'input[max="119"]'
end
test 'input has step value of any except for integer attribute' do
with_input_for @validating_user, :age, :float
assert_select 'input[step="any"]'
with_input_for @validating_user, :age, :integer
assert_select 'input[step="1"]'
with_input_for @validating_user, :age, :integer, as: :decimal, input_html: { step: 0.5 }
assert_select 'input[step="0.5"]'
end
test 'numeric input does not generate placeholder by default' do
with_input_for @user, :age, :integer
assert_no_select 'input[placeholder]'
end
test 'numeric input accepts the placeholder option' do
with_input_for @user, :age, :integer, placeholder: 'Put in your age'
assert_select 'input.integer[placeholder="Put in your age"]'
end
test 'numeric input uses i18n to translate placeholder text' do
store_translations(:en, simple_form: { placeholders: { user: {
age: 'Age goes here'
} } }) do
with_input_for @user, :age, :integer
assert_select 'input.integer[placeholder="Age goes here"]'
end
end
# Numeric input but HTML5 disabled
test 'when not using HTML5 input does not generate field with type number and use text instead' do
swap_wrapper do
with_input_for @user, :age, :integer
assert_no_select "input[type=number]"
assert_no_select "input#user_age[text]"
end
end
test 'when not using HTML5 input does not use min or max or step attributes' do
swap_wrapper do
with_input_for @validating_user, :age, :integer
assert_no_select "input[type=number]"
assert_no_select "input[min]"
assert_no_select "input[max]"
assert_no_select "input[step]"
end
end
%i[integer float decimal].each do |type|
test "#{type} input infers min value from attributes with greater than or equal validation" do
with_input_for @validating_user, :age, type
assert_select 'input[min="18"]'
end
test "#{type} input infers the max value from attributes with less than or equal to validation" do
with_input_for @validating_user, :age, type
assert_select 'input[max="99"]'
end
end
test 'min_max does not emit max value as bare string' do
with_input_for @other_validating_user, :age, :integer
assert_select 'input[max]'
assert_no_select 'div', %r{^99}
end
end
================================================
FILE: test/inputs/readonly_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class ReadonlyTest < ActionView::TestCase
test 'string input generates readonly elements when readonly option is true' do
with_input_for @user, :name, :string, readonly: true
assert_select 'input.string.readonly[readonly]'
end
test 'text input generates readonly elements when readonly option is true' do
with_input_for @user, :description, :text, readonly: true
assert_select 'textarea.text.readonly[readonly]'
end
test 'numeric input generates readonly elements when readonly option is true' do
with_input_for @user, :age, :integer, readonly: true
assert_select 'input.integer.readonly[readonly]'
end
test 'date input generates readonly elements when readonly option is true' do
with_input_for @user, :born_at, :date, readonly: true
assert_select 'select.date.readonly[readonly]'
end
test 'datetime input generates readonly elements when readonly option is true' do
with_input_for @user, :created_at, :datetime, readonly: true
assert_select 'select.datetime.readonly[readonly]'
end
test 'string input generates readonly elements when readonly option is false' do
with_input_for @user, :name, :string, readonly: false
assert_no_select 'input.string.readonly[readonly]'
end
test 'text input generates readonly elements when readonly option is false' do
with_input_for @user, :description, :text, readonly: false
assert_no_select 'textarea.text.readonly[readonly]'
end
test 'numeric input generates readonly elements when readonly option is false' do
with_input_for @user, :age, :integer, readonly: false
assert_no_select 'input.integer.readonly[readonly]'
end
test 'date input generates readonly elements when readonly option is false' do
with_input_for @user, :born_at, :date, readonly: false
assert_no_select 'select.date.readonly[readonly]'
end
test 'datetime input generates readonly elements when readonly option is false' do
with_input_for @user, :created_at, :datetime, readonly: false
assert_no_select 'select.datetime.readonly[readonly]'
end
test 'string input generates readonly elements when readonly option is not present' do
with_input_for @user, :name, :string
assert_no_select 'input.string.readonly[readonly]'
end
test 'text input generates readonly elements when readonly option is not present' do
with_input_for @user, :description, :text
assert_no_select 'textarea.text.readonly[readonly]'
end
test 'numeric input generates readonly elements when readonly option is not present' do
with_input_for @user, :age, :integer
assert_no_select 'input.integer.readonly[readonly]'
end
test 'date input generates readonly elements when readonly option is not present' do
with_input_for @user, :born_at, :date
assert_no_select 'select.date.readonly[readonly]'
end
test 'datetime input generates readonly elements when readonly option is not present' do
with_input_for @user, :created_at, :datetime
assert_no_select 'select.datetime.readonly[readonly]'
end
test 'input generates readonly attribute when the field is readonly and the object is persisted' do
with_input_for @user, :credit_card, :string, readonly: :lookup
assert_select 'input.string.readonly[readonly]'
end
test 'input does not generate readonly attribute when the field is readonly and the object is not persisted' do
@user.new_record!
with_input_for @user, :credit_card, :string, readonly: :lookup
assert_no_select 'input.string.readonly[readonly]'
end
test 'input does not generate readonly attribute when the field is not readonly and the object is persisted' do
with_input_for @user, :name, :string
assert_no_select 'input.string.readonly[readonly]'
end
test 'input does not generate readonly attribute when the component is not used' do
swap_wrapper do
with_input_for @user, :credit_card, :string
assert_no_select 'input.string.readonly[readonly]'
end
end
end
================================================
FILE: test/inputs/required_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class RequiredTest < ActionView::TestCase
# REQUIRED AND PRESENCE VALIDATION
test 'builder input obtains required from ActiveModel::Validations when it is included' do
with_form_for @validating_user, :name
assert_select 'input.required[required]#validating_user_name'
with_form_for @validating_user, :status
assert_select 'input.optional#validating_user_status'
end
test 'builder input allows overriding required when ActiveModel::Validations is included' do
with_form_for @validating_user, :name, required: false
assert_select 'input.optional#validating_user_name'
with_form_for @validating_user, :status, required: true
assert_select 'input.required[required]#validating_user_status'
end
test 'builder input is required by default when ActiveModel::Validations is not included' do
with_form_for @user, :name
assert_select 'input.required[required]#user_name'
end
test 'builder input does not be required by default when ActiveModel::Validations is not included if option is set to false' do
swap SimpleForm, required_by_default: false do
with_form_for @user, :name
assert_select 'input.optional#user_name'
assert_no_select 'input[required]'
end
end
test 'when not using browser validations, input does not generate required html attribute' do
swap SimpleForm, browser_validations: false do
with_input_for @user, :name, :string
assert_select 'input[type=text].required'
assert_no_select 'input[type=text][required]'
end
end
test 'when not using browser validations, when required option is set to false, input does not generate required html attribute' do
swap SimpleForm, browser_validations: false do
with_input_for @user, :name, :string, required: false
assert_no_select 'input[type=text].required'
assert_no_select 'input[type=text][required]'
end
end
test 'when not using browser validations, when required option is set to true, input generates required html attribute' do
swap SimpleForm, browser_validations: false do
with_input_for @user, :name, :string, required: true
assert_select 'input[type=text].required'
assert_select 'input[type=text][required]'
end
end
test 'when not using browser validations, when required option is true in the wrapper, input does not generate required html attribute' do
swap SimpleForm, browser_validations: false do
swap_wrapper :default, self.custom_wrapper_with_required_input do
with_concat_form_for(@user) do |f|
concat f.input :name
end
assert_select 'input[type=text].required'
assert_no_select 'input[type=text][required]'
end
end
end
test 'builder input allows disabling required when ActiveModel::Validations is not included' do
with_form_for @user, :name, required: false
assert_no_select 'input.required'
assert_no_select 'input[required]'
assert_select 'input.optional#user_name'
end
test 'when not the required component the input does not have the required attribute but has the required class' do
swap_wrapper do
with_input_for @user, :name, :string
assert_select 'input[type=text].required'
assert_no_select 'input[type=text][required]'
end
end
# VALIDATORS :if :unless
test 'builder input does not be required when ActiveModel::Validations is included and if option is present' do
with_form_for @validating_user, :age
assert_no_select 'input.required'
assert_no_select 'input[required]'
assert_select 'input.optional#validating_user_age'
end
test 'builder input does not be required when ActiveModel::Validations is included and unless option is present' do
with_form_for @validating_user, :amount
assert_no_select 'input.required'
assert_no_select 'input[required]'
assert_select 'input.optional#validating_user_amount'
end
# VALIDATORS :on
test 'builder input is required when validation is on create and is not persisted' do
@validating_user.new_record!
with_form_for @validating_user, :action
assert_select 'input.required'
assert_select 'input[required]'
assert_select 'input.required[required]#validating_user_action'
end
test 'builder input does not be required when validation is on create and is persisted' do
with_form_for @validating_user, :action
assert_no_select 'input.required'
assert_no_select 'input[required]'
assert_select 'input.optional#validating_user_action'
end
test 'builder input is required when validation is on save' do
with_form_for @validating_user, :credit_limit
assert_select 'input.required'
assert_select 'input[required]'
assert_select 'input.required[required]#validating_user_credit_limit'
@validating_user.new_record!
with_form_for @validating_user, :credit_limit
assert_select 'input.required'
assert_select 'input[required]'
assert_select 'input.required[required]#validating_user_credit_limit'
end
test 'builder input is required when validation is on update and is persisted' do
with_form_for @validating_user, :phone_number
assert_select 'input.required'
assert_select 'input[required]'
assert_select 'input.required[required]#validating_user_phone_number'
end
test 'builder input does not be required when validation is on update and is not persisted' do
@validating_user.new_record!
with_form_for @validating_user, :phone_number
assert_no_select 'input.required'
assert_no_select 'input[required]'
assert_select 'input.optional#validating_user_phone_number'
end
test 'builder input does not generate required html attribute when option is set to false when it is set to true in wrapper' do
swap SimpleForm, browser_validations: true do
swap_wrapper :default, self.custom_wrapper_with_required_input do
with_concat_form_for(@user) do |f|
concat f.input :name, required: false
end
assert_no_select 'input[type=text][required]'
end
end
end
end
================================================
FILE: test/inputs/rich_text_area_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class RichTextAreaInputTest < ActionView::TestCase
test 'input generates a text area for text attributes' do
with_input_for @user, :description, :text
assert_select 'textarea.text#user_description'
end
test 'input generates a text area for text attributes that accept placeholder' do
with_input_for @user, :description, :text, placeholder: 'Put in some text'
assert_select 'textarea.text[placeholder="Put in some text"]'
end
end
================================================
FILE: test/inputs/string_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class StringInputTest < ActionView::TestCase
test 'input maps text field to string attribute' do
with_input_for @user, :name, :string
assert_select "input#user_name[type=text][name='user[name]'][value='New in SimpleForm!']"
end
test 'input maps text field to citext attribute' do
with_input_for @user, :name, :citext
assert_select "input#user_name[type=text][name='user[name]'][value='New in SimpleForm!']"
end
test 'input generates a password field for password attributes' do
with_input_for @user, :password, :password
assert_select "input#user_password.password[type=password][name='user[password]']"
end
test 'input gets maxlength from column definition for string attributes' do
with_input_for @user, :name, :string
assert_select 'input.string[maxlength="100"]'
end
test 'input does not get maxlength from column without size definition for string attributes' do
with_input_for @user, :action, :string
assert_no_select 'input.string[maxlength]'
end
test 'input gets maxlength from column definition for password attributes' do
with_input_for @user, :password, :password
assert_select 'input.password[type=password][maxlength="100"]'
end
test 'input infers maxlength from validation when present' do
with_input_for @validating_user, :name, :string
assert_select 'input.string[maxlength="25"]'
end
test 'input infers minlength from validation when present' do
with_input_for @validating_user, :name, :string
assert_select 'input.string[minlength="5"]'
end
test 'input infers maxlength from validation proc when present' do
with_input_for @validating_user, :username, :string
assert_select 'input.string[maxlength="30"]'
end
test 'input infers minlength from validation proc when present' do
with_input_for @validating_user, :username, :string
assert_select 'input.string[minlength="10"]'
end
test 'input gets maxlength from validation when :is option present' do
with_input_for @validating_user, :home_picture, :string
assert_select 'input.string[maxlength="12"]'
end
test 'input gets minlength from validation when :is option present' do
with_input_for @validating_user, :home_picture, :string
assert_select 'input.string[minlength="12"]'
end
test 'input maxlength is the column limit plus one to make room for decimal point' do
with_input_for @user, :credit_limit, :string
assert_select 'input.string[maxlength="16"]'
end
test 'input does not generate placeholder by default' do
with_input_for @user, :name, :string
assert_no_select 'input[placeholder]'
end
test 'input accepts the placeholder option' do
with_input_for @user, :name, :string, placeholder: 'Put in some text'
assert_select 'input.string[placeholder="Put in some text"]'
end
test 'input generates a password field for password attributes that accept placeholder' do
with_input_for @user, :password, :password, placeholder: 'Password Confirmation'
assert_select 'input[type=password].password[placeholder="Password Confirmation"]#user_password'
end
test 'input does not infer pattern from attributes by default' do
with_input_for @other_validating_user, :country, :string
assert_no_select 'input[pattern="\w+"]'
end
test 'input infers pattern from attributes' do
with_input_for @other_validating_user, :country, :string, pattern: true
assert_select "input:match('pattern', ?)", /\w+/
end
test 'input infers pattern from attributes using proc' do
with_input_for @other_validating_user, :name, :string, pattern: true
assert_select "input:match('pattern', ?)", /\w+/
end
test 'input does not infer pattern from attributes if root default is false' do
swap_wrapper do
with_input_for @other_validating_user, :country, :string
assert_no_select 'input[pattern="\w+"]'
end
end
test 'input uses given pattern from attributes' do
with_input_for @other_validating_user, :country, :string, input_html: { pattern: "\\d+" }
assert_select "input:match('pattern', ?)", /\\d+/
end
test 'input does not use pattern if model has :without validation option' do
with_input_for @other_validating_user, :description, :string, pattern: true
assert_no_select 'input[pattern="\d+"]'
end
test 'input uses i18n to translate placeholder text' do
store_translations(:en, simple_form: { placeholders: { user: {
name: 'Name goes here'
} } }) do
with_input_for @user, :name, :string
assert_select 'input.string[placeholder="Name goes here"]'
end
end
test 'input uses custom i18n scope to translate placeholder text' do
store_translations(:en, my_scope: { placeholders: { user: {
name: 'Name goes here'
} } }) do
swap SimpleForm, i18n_scope: :my_scope do
with_input_for @user, :name, :string
assert_select 'input.string[placeholder="Name goes here"]'
end
end
end
%i[email url search tel].each do |type|
test "input allows type #{type}" do
with_input_for @user, :name, type
assert_select "input.string.#{type}"
assert_select "input[type=#{type}]"
end
test "input does not allow type #{type} if HTML5 compatibility is disabled" do
swap_wrapper do
with_input_for @user, :name, type
assert_select "input[type=text]"
assert_no_select "input[type=#{type}]"
end
end
end
test 'input strips extra spaces from class html attribute with default classes' do
with_input_for @user, :name, :string
assert_select "input[class='string required']"
assert_no_select "input[class='string required ']"
assert_no_select "input[class=' string required']"
end
test 'input strips extra spaces from class html attribute when giving a custom class' do
with_input_for @user, :name, :string, input_html: { class: "my_input" }
assert_select "input[class='string required my_input']"
assert_no_select "input[class='string required my_input ']"
assert_no_select "input[class=' string required my_input']"
end
end
================================================
FILE: test/inputs/text_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class TextInputTest < ActionView::TestCase
test 'input generates a text area for text attributes' do
with_input_for @user, :description, :text
assert_select 'textarea.text#user_description'
end
test 'input generates a text area for text attributes that accept placeholder' do
with_input_for @user, :description, :text, placeholder: 'Put in some text'
assert_select 'textarea.text[placeholder="Put in some text"]'
end
test 'input generates a placeholder from the translations' do
store_translations(:en, simple_form: { placeholders: { user: { name: "placeholder from i18n en.simple_form.placeholders.user.name" } } }) do
with_input_for @user, :name, :text
assert_select 'textarea.text[placeholder="placeholder from i18n en.simple_form.placeholders.user.name"]'
end
end
test 'input gets maxlength from column definition for text attributes' do
with_input_for @user, :description, :text
assert_select 'textarea.text[maxlength="200"]'
end
test 'input infers maxlength column definition from validation when present for text attributes' do
with_input_for @validating_user, :description, :text
assert_select 'textarea.text[maxlength="50"]'
end
test 'input infers minlength column definition from validation when present for text attributes' do
with_input_for @validating_user, :description, :text
assert_select 'textarea.text[minlength="15"]'
end
end
================================================
FILE: test/inputs/time_zone_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class TimeZoneInputTest < ActionView::TestCase
test 'input generates a time zone select field' do
with_input_for @user, :time_zone, :time_zone
assert_select 'select#user_time_zone'
assert_select 'select option[value=Brasilia]', '(GMT-03:00) Brasilia'
assert_no_select 'select option[value=""][disabled=disabled]'
end
test 'input generates a time zone select field with default' do
with_input_for @user, :time_zone, :time_zone, default: 'Brasilia'
assert_select 'select option[value=Brasilia][selected=selected]'
assert_no_select 'select option[value=""]'
end
test 'input generates a time zone select using options priority' do
with_input_for @user, :time_zone, :time_zone, priority: /Brasilia/
assert_select 'select option[value=""][disabled=disabled]'
assert_no_select 'select option[value=""]', /^$/
end
test 'input does generate select element with required html attribute' do
with_input_for @user, :time_zone, :time_zone
assert_select 'select.required'
assert_select 'select[required]'
end
end
================================================
FILE: test/inputs/weekday_input_test.rb
================================================
# frozen_string_literal: true
# encoding: UTF-8
require 'test_helper'
class WeekdayInputTest < ActionView::TestCase
test 'input generates a weekday select' do
with_input_for @user, :born_at, :weekday
assert_select 'select.weekday#user_born_at'
end
test 'input generates a weekday select that accepts placeholder' do
with_input_for @user, :born_at, :weekday, placeholder: 'Put in a weekday'
assert_select 'select.weekday[placeholder="Put in a weekday"]'
end
end
================================================
FILE: test/simple_form_test.rb
================================================
# frozen_string_literal: true
require 'test_helper'
class SimpleFormTest < ActiveSupport::TestCase
test 'setup block yields self' do
SimpleForm.setup do |config|
assert_equal SimpleForm, config
end
end
test 'setup block configure Simple Form' do
SimpleForm.setup do |config|
assert_equal SimpleForm, config
end
assert_equal true, SimpleForm.configured?
end
end
================================================
FILE: test/support/discovery_inputs.rb
================================================
# frozen_string_literal: true
class StringInput < SimpleForm::Inputs::StringInput
def input(wrapper_options = nil)
"".html_safe
end
end
class NumericInput < SimpleForm::Inputs::NumericInput
def input(wrapper_options = nil)
"".html_safe
end
end
class CustomizedInput < SimpleForm::Inputs::StringInput
def input(wrapper_options = nil)
"".html_safe
end
def input_method
:text_field
end
end
class DeprecatedInput < SimpleForm::Inputs::StringInput
def input
"".html_safe
end
def input_method
:text_field
end
end
class CollectionSelectInput < SimpleForm::Inputs::CollectionSelectInput
def input_html_classes
super.push('chosen')
end
end
class FileInput < SimpleForm::Inputs::FileInput
def input_html_classes
super.delete_if { |html_class| html_class == :file }
super.push('file-upload')
end
end
module CustomInputs
class CustomizedInput < SimpleForm::Inputs::StringInput
def input_html_classes
super.push('customized-namespace-custom-input')
end
end
class PasswordInput < SimpleForm::Inputs::PasswordInput
def input_html_classes
super.push('password-custom-input')
end
end
class NumericInput < SimpleForm::Inputs::PasswordInput
def input_html_classes
super.push('numeric-custom-input')
end
end
end
================================================
FILE: test/support/misc_helpers.rb
================================================
# frozen_string_literal: true
module MiscHelpers
def store_translations(locale, translations, &block)
I18n.backend.store_translations locale, translations
yield
ensure
I18n.reload!
I18n.backend.send :init_translations
end
def assert_no_select(selector, value = nil)
assert_select(selector, text: value, count: 0)
end
def swap(object, new_values)
old_values = {}
new_values.each do |key, value|
old_values[key] = object.send key
object.send :"#{key}=", value
end
yield
ensure
old_values.each do |key, value|
object.send :"#{key}=", value
end
end
def stub_any_instance(klass, method, value)
klass.class_eval do
alias_method :"new_#{method}", method
define_method(method) do
if value.respond_to?(:call)
value.call
else
value
end
end
end
yield
ensure
klass.class_eval do
undef_method method
alias_method method, :"new_#{method}"
undef_method :"new_#{method}"
end
end
def swap_wrapper(name = :default, wrapper = custom_wrapper)
old = SimpleForm.wrappers[name.to_s]
SimpleForm.wrappers[name.to_s] = wrapper
yield
ensure
SimpleForm.wrappers[name.to_s] = old
end
def custom_wrapper
SimpleForm.build tag: :section, class: "custom_wrapper", pattern: false do |b|
b.use :pattern
b.wrapper :another, class: "another_wrapper" do |ba|
ba.use :label
ba.use :input
end
b.wrapper :error_wrapper, tag: :div, class: "error_wrapper" do |be|
be.use :error, wrap_with: { tag: :span, class: "omg_error" }
end
b.use :hint, wrap_with: { class: "omg_hint" }
end
end
def custom_wrapper_with_wrapped_optional_component
SimpleForm.build tag: :section, class: "custom_wrapper" do |b|
b.wrapper tag: :div, class: 'no_output_wrapper' do |ba|
ba.optional :hint, wrap_with: { tag: :p, class: 'omg_hint' }
end
end
end
def custom_wrapper_with_unless_blank
SimpleForm.build tag: :section, class: "custom_wrapper" do |b|
b.wrapper tag: :div, class: 'no_output_wrapper', unless_blank: true do |ba|
ba.optional :hint, wrap_with: { tag: :p, class: 'omg_hint' }
end
end
end
def custom_wrapper_with_input_class
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :label
b.use :input, class: 'inline-class'
end
end
def custom_wrapper_with_input_data_modal
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :label
b.use :input, data: { modal: 'data-modal', wrapper: 'data-wrapper' }
end
end
def custom_wrapper_with_input_aria_modal
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :label
b.use :input, aria: { modal: 'aria-modal', wrapper: 'aria-wrapper' }
end
end
def custom_wrapper_with_label_class
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :label, class: 'inline-class'
b.use :input
end
end
def custom_wrapper_with_input_attributes
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :input, data: { modal: true }
end
end
def custom_wrapper_with_label_input_class
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.use :label_input, class: 'inline-class'
end
end
def custom_wrapper_with_wrapped_input
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.wrapper tag: :div, class: 'elem' do |component|
component.use :label
component.use :input, wrap_with: { tag: :div, class: 'input' }
end
end
end
def custom_wrapper_with_wrapped_label
SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
b.wrapper tag: :div, class: 'elem' do |component|
component.use :label, wrap_with: { tag: :div, class: 'label' }
component.use :input
end
end
end
def custom_wrapper_without_top_level
SimpleForm.build tag: false, class: 'custom_wrapper_without_top_level' do |b|
b.use :label_input
b.use :hint, wrap_with: { tag: :span, class: :hint }
b.use :error, wrap_with: { tag: :span, class: :error }
end
end
def custom_wrapper_without_class
SimpleForm.build tag: :div, wrapper_html: { id: 'custom_wrapper_without_class' } do |b|
b.use :label_input
end
end
def custom_wrapper_with_label_html_option
SimpleForm.build tag: :div, class: "custom_wrapper", label_html: { class: 'extra-label-class' } do |b|
b.use :label_input
end
end
def custom_wrapper_with_wrapped_label_input
SimpleForm.build tag: :section, class: "custom_wrapper", pattern: false do |b|
b.use :label_input, wrap_with: { tag: :div, class: :field }
end
end
def custom_wrapper_with_additional_attributes
SimpleForm.build tag: :div, class: 'custom_wrapper', html: { data: { wrapper: :test }, title: 'some title' } do |b|
b.use :label_input
end
end
def custom_wrapper_with_full_error
SimpleForm.build tag: :div, class: 'custom_wrapper' do |b|
b.use :full_error, wrap_with: { tag: :span, class: :error }
end
end
def custom_wrapper_with_label_text
SimpleForm.build label_text: proc { |label, required| "**#{label}**" } do |b|
b.use :label_input
end
end
def custom_wrapper_with_custom_label_component
SimpleForm.build tag: :span, class: 'custom_wrapper' do |b|
b.use :label_text
end
end
def custom_wrapper_with_html5_components
SimpleForm.build tag: :span, class: 'custom_wrapper' do |b|
b.use :label_text
end
end
def custom_wrapper_with_required_input
SimpleForm.build tag: :span, class: 'custom_wrapper' do |b|
b.use :html5
b.use :input, required: true
end
end
def custom_wrapper_with_input_error_class
SimpleForm.build tag: :div, class: "custom_wrapper", error_class: :field_with_errors do |b|
b.use :label
b.use :input, class: 'inline-class', error_class: 'is-invalid'
end
end
def custom_wrapper_with_input_valid_class(valid_class: :field_without_errors)
SimpleForm.build tag: :div, class: "custom_wrapper", valid_class: valid_class do |b|
b.use :label
b.use :input, class: 'inline-class', valid_class: 'is-valid'
end
end
def custom_form_for(object, *args, &block)
simple_form_for(object, *args, { builder: CustomFormBuilder }, &block)
end
def custom_mapping_form_for(object, *args, &block)
simple_form_for(object, *args, { builder: CustomMapTypeFormBuilder }, &block)
end
def with_concat_form_for(*args, &block)
concat simple_form_for(*args, &(block || proc {}))
end
def with_concat_fields_for(*args, &block)
concat simple_fields_for(*args, &block)
end
def with_concat_custom_form_for(*args, &block)
concat custom_form_for(*args, &block)
end
def with_concat_custom_mapping_form_for(*args, &block)
concat custom_mapping_form_for(*args, &block)
end
def with_form_for(object, *args, &block)
with_concat_form_for(object) do |f|
f.input(*args, &block)
end
end
def with_input_for(object, attribute_name, type, options = {})
with_concat_form_for(object) do |f|
f.input(attribute_name, options.merge(as: type))
end
end
def with_input_field_for(object, *args)
with_concat_form_for(object) do |f|
f.input_field(*args)
end
end
end
class CustomFormBuilder < SimpleForm::FormBuilder
def input(attribute_name, *args, &block)
super(attribute_name, *args, { input_html: { class: 'custom' } }, &block)
end
end
class CustomMapTypeFormBuilder < SimpleForm::FormBuilder
map_type :custom_type, to: SimpleForm::Inputs::StringInput
end
================================================
FILE: test/support/mock_controller.rb
================================================
# frozen_string_literal: true
class MockController
attr_writer :action_name
def _routes
self
end
def action_name
defined?(@action_name) ? @action_name : "edit"
end
def url_for(*)
"http://example.com"
end
def url_options
{}
end
def polymorphic_mappings(*); {}; end
def hash_for_user_path(*); end
def hash_for_validating_user_path(*); end
def hash_for_other_validating_user_path(*); end
def hash_for_users_path(*); end
end
================================================
FILE: test/support/models.rb
================================================
# frozen_string_literal: true
Association = Struct.new(:klass, :name, :macro, :scope, :options)
Column = Struct.new(:name, :type, :limit) do
end
Relation = Struct.new(:records) do
delegate :each, to: :records
def where(conditions = nil)
self.class.new conditions ? [records.first] : records
end
def order(conditions = nil)
self.class.new conditions ? records.last : records
end
alias_method :to_a, :records
alias_method :to_ary, :records
end
Decorator = Struct.new(:object) do
def to_model
object
end
end
Picture = Struct.new(:id, :name) do
extend ActiveModel::Naming
include ActiveModel::Conversion
def self.where(conditions = nil)
if conditions.is_a?(Hash) && conditions[:name]
all.to_a.last
else
all
end
end
def self.all
Relation.new((1..3).map { |i| new(i, "#{name} #{i}") })
end
end
Company = Struct.new(:id, :name) do
extend ActiveModel::Naming
include ActiveModel::Conversion
class << self
delegate :order, :where, to: :_relation
end
def self._relation
all
end
def self.all
Relation.new((1..3).map { |i| new(i, "#{name} #{i}") })
end
def persisted?
true
end
end
Friend = Struct.new(:id, :name) do
extend ActiveModel::Naming
include ActiveModel::Conversion
def self.all
(1..3).map { |i| new(i, "#{name} #{i}") }
end
def persisted?
true
end
end
class Tag < Company
def group_method
["category-1"]
end
end
TagGroup = Struct.new(:id, :name, :tags)
class User
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :name, :username, :company, :company_id, :time_zone, :active, :age,
:description, :created_at, :updated_at, :credit_limit, :password, :url,
:delivery_time, :born_at, :special_company_id, :country, :tags, :tag_ids,
:avatar, :home_picture, :email, :status, :residence_country, :phone_number,
:post_count, :lock_version, :amount, :attempts, :action, :credit_card, :gender,
:extra_special_company_id, :pictures, :picture_ids, :special_pictures,
:special_picture_ids, :uuid, :friends, :friend_ids, :special_tags, :special_tag_ids,
:citext, :hstore, :json, :jsonb, :hourly, :favorite_color
def self.build(extra_attributes = {})
attributes = {
id: 1,
name: 'New in SimpleForm!',
description: 'Hello!',
created_at: Time.now
}.merge! extra_attributes
new attributes
end
def initialize(options = {})
@new_record = false
options.each do |key, value|
send("#{key}=", value)
end if options
end
def new_record!
@new_record = true
end
def persisted?
!@new_record
end
def company_attributes=(*)
end
def tags_attributes=(*)
end
def column_for_attribute(attribute)
column_type, limit = case attribute.to_sym
when :name, :status, :password then [:string, 100]
when :description then [:text, 200]
when :age then :integer
when :credit_limit then [:decimal, 15]
when :active then :boolean
when :born_at then :date
when :delivery_time then :time
when :created_at then :datetime
when :updated_at then :timestamp
when :lock_version then :integer
when :home_picture then :string
when :amount then :integer
when :attempts then :integer
when :action then :string
when :credit_card then :string
else attribute.to_sym
end
Column.new(attribute, column_type, limit)
end
begin
require 'active_model/type'
begin
ActiveModel::Type.lookup(:text)
rescue ArgumentError # :text is no longer an ActiveModel::Type
# But we don't want our tests to depend on ActiveRecord
class ::ActiveModel::Type::Text < ActiveModel::Type::String
def type; :text; end
end
ActiveModel::Type.register(:text, ActiveModel::Type::Text)
end
def type_for_attribute(attribute)
column_type, limit = case attribute
when 'name', 'status', 'password' then [:string, 100]
when 'description' then [:text, 200]
when 'age' then :integer
when 'credit_limit' then [:decimal, 15]
when 'active' then :boolean
when 'born_at' then :date
when 'delivery_time' then :time
when 'created_at' then :datetime
when 'updated_at' then :datetime
when 'lock_version' then :integer
when 'home_picture' then :string
when 'amount' then :integer
when 'attempts' then :integer
when 'action' then :string
when 'credit_card' then :string
when 'uuid' then :string
when 'citext' then :string
when 'hstore' then [:text, 200]
when 'json' then [:text, 200]
when 'jsonb' then [:text, 200]
end
ActiveModel::Type.lookup(column_type, limit: limit)
end
rescue LoadError
end
def has_attribute?(attribute)
case attribute.to_sym
when :name, :status, :password, :description, :age,
:credit_limit, :active, :born_at, :delivery_time,
:created_at, :updated_at, :lock_version, :home_picture,
:amount, :attempts, :action, :credit_card, :uuid,
:citext, :hstore, :json, :jsonb then true
else false
end
end
def self.human_attribute_name(attribute, options = {})
case attribute
when 'name', :name
'Super User Name!'
when 'description'
'User Description!'
when 'status'
"[#{options[:base].id}] User Status!"
when 'company'
'Company Human Name!'
else
attribute.to_s.humanize
end
end
def self.reflect_on_association(association)
case association
when :company
Association.new(Company, association, :belongs_to, nil, {})
when :tags
Association.new(Tag, association, :has_many, nil, {})
when :special_tags
Association.new(Tag, association, :has_many, ->(user) { where(id: user.id) }, {})
when :first_company
Association.new(Company, association, :has_one, nil, {})
when :special_company
Association.new(Company, association, :belongs_to, nil, conditions: { id: 1 })
when :extra_special_company
Association.new(Company, association, :belongs_to, nil, conditions: proc { { id: self.id } })
when :pictures
Association.new(Picture, association, :has_many, nil, {})
when :special_pictures
Association.new(Picture, association, :has_many, proc { where(name: self.name) }, {})
when :friends
Association.new(Friend, association, :has_many, nil, {})
end
end
def errors
@errors ||= begin
errors = ActiveModel::Errors.new(self)
errors.add(:name, "cannot be blank")
errors.add(:description, 'must be longer than 15 characters')
errors.add(:age, 'is not a number')
errors.add(:age, 'must be greater than 18')
errors.add(:company, 'company must be present')
errors.add(:company_id, 'must be valid')
errors
end
end
def self.readonly_attributes
["credit_card"]
end
end
class ValidatingUser < User
include ActiveModel::Validations
validates :name, presence: true
validates :username, presence: true
validates :company, presence: true
validates :age, presence: true, if: proc { |user| user.name }
validates :amount, presence: true, unless: proc { |user| user.age }
validates :action, presence: true, on: :create
validates :credit_limit, presence: true, on: :save
validates :phone_number, presence: true, on: :update
validates_numericality_of :age,
greater_than_or_equal_to: 18,
less_than_or_equal_to: 99,
only_integer: true
validates_numericality_of :amount,
greater_than: :min_amount,
less_than: :max_amount,
only_integer: true
validates_numericality_of :attempts,
greater_than_or_equal_to: :min_attempts,
less_than_or_equal_to: :max_attempts,
only_integer: true
validates_length_of :name, maximum: 25, minimum: 5
if ActiveModel.gem_version >= Gem::Version::new("7.1.0")
validates_length_of :username, maximum: -> { 30 }, minimum: -> { 10 }
else
validates_length_of :username, maximum: ->(_) { 30 }, minimum: ->(_) { 10 }
end
validates_length_of :description, in: 15..50
validates_length_of :home_picture, is: 12
def min_amount
10
end
def max_amount
100
end
def min_attempts
1
end
def max_attempts
100
end
end
class OtherValidatingUser < User
include ActiveModel::Validations
validates_numericality_of :age,
greater_than: 17,
less_than: 100,
only_integer: true
validates_numericality_of :amount,
greater_than: proc { |user| user.age },
less_than: proc { |user| user.age + 100 },
only_integer: true
validates_numericality_of :attempts,
greater_than_or_equal_to: proc { |user| user.age },
less_than_or_equal_to: proc { |user| user.age + 100 },
only_integer: true
validates_format_of :country, with: /\w+/
validates_format_of :name, with: proc { /\w+/ }
validates_format_of :description, without: /\d+/
end
class HashBackedAuthor < Hash
extend ActiveModel::Naming
include ActiveModel::Conversion
def persisted?; false; end
def name
'hash backed author'
end
end
class UserNumber1And2 < User
end
class UserWithAttachment < User
def avatar_attachment
OpenStruct.new
end
def avatars_attachments
OpenStruct.new
end
def remote_cover_url
"/uploads/cover.png"
end
def profile_image_attacher
OpenStruct.new
end
def portrait_file_name
"portrait.png"
end
end
================================================
FILE: test/test_helper.rb
================================================
# frozen_string_literal: true
require 'minitest/autorun'
require 'active_model'
require 'action_controller'
require 'action_view'
ActionView::RoutingUrlFor.include ActionDispatch::Routing::UrlFor
require 'action_view/template'
require 'action_view/test_case'
module Rails
def self.env
ActiveSupport::StringInquirer.new("test")
end
end
$:.unshift File.expand_path("../../lib", __FILE__)
require 'simple_form'
require "rails/generators/test_case"
require 'generators/simple_form/install_generator'
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
require file unless file.end_with?('discovery_inputs.rb')
end
I18n.default_locale = :en
require 'country_select'
Rails::Dom::Testing::Assertions::SelectorAssertions::HTMLSelector::NO_STRIP << "label"
if ActiveSupport::TestCase.respond_to?(:test_order=)
ActiveSupport::TestCase.test_order = :random
end
require "rails/test_unit/line_filtering"
class ActionView::TestCase
include MiscHelpers
include SimpleForm::ActionViewExtensions::FormHelper
extend Rails::LineFiltering
setup :set_controller
setup :setup_users
def set_controller
@controller = MockController.new
end
def setup_users(extra_attributes = {})
@user = User.build(extra_attributes)
@decorated_user = Decorator.new(@user)
@validating_user = ValidatingUser.build({
name: 'Tester McTesterson',
username: 'mctesterson',
description: 'A test user of the most distinguished caliber',
home_picture: 'Home picture',
age: 19,
amount: 15,
attempts: 1,
company: [1]
}.merge!(extra_attributes))
@other_validating_user = OtherValidatingUser.build({
age: 19,
company: 1
}.merge!(extra_attributes))
end
def protect_against_forgery?
false
end
def user_path(*args)
'/users'
end
def company_user_path(*args)
'/company/users'
end
alias :users_path :user_path
alias :super_user_path :user_path
alias :validating_user_path :user_path
alias :validating_users_path :user_path
alias :other_validating_user_path :user_path
alias :user_with_attachment_path :user_path
end