diff --git a/.circleci/config.yml b/.circleci/config.yml index 244a44ca2..4a7f1666d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ version: 2 jobs: build: docker: - - image: circleci/ruby:2.7.4-node-browsers + - image: cimg/ruby:3.1.1-browsers environment: RAILS_ENV: test PGHOST: 127.0.0.1 @@ -41,6 +41,13 @@ jobs: - run: bundle exec rake db:create - run: bundle exec rake db:schema:load + # Install chrome driver + - run: + name: Setup Chrome + command: | + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt install ./google-chrome-stable_current_amd64.deb + # run tests! - run: name: run tests diff --git a/.env.test b/.env.test index e5233cb0b..62f89850c 100644 --- a/.env.test +++ b/.env.test @@ -28,3 +28,6 @@ TOUCHPOINTS_EMAIL_SENDER=from@example.gov TOUCHPOINTS_SUPPORT=support@example.gov TOUCHPOINTS_TEAM=team@example.gov TOUCHPOINTS_WEB_DOMAIN=127.0.0.1 + +DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL=true + diff --git a/Dockerfile b/Dockerfile index ec8db40ee..3faf819d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,20 @@ -# gets the docker image of ruby 2.5 and lets us build on top of that -FROM ruby:2.6.5-slim +FROM ruby:3.1.1-slim -# install rails dependencies -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs postgresql-client && apt-get install -y git +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ + postgresql-client \ + nodejs \ + git \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# create a folder /FlexUOMConverter in the docker container and go into that folder -RUN mkdir /touchpoints -WORKDIR /touchpoints +RUN gem update --system +RUN gem install bundler:2.3.8 -# Copy the Gemfile and Gemfile.lock from app root directory into the /myapp/ folder in the docker container -COPY Gemfile /touchpoints/Gemfile +WORKDIR /usr/src/app -# Run bundle install to install gems inside the gemfile -RUN bundle install +ENTRYPOINT ["./entrypoint.sh"] +EXPOSE 3002 -# Copy the whole app -COPY . /touchpoints - -RUN cd /touchpoints - -# Expose port 3000 to the Docker host, so we can access it -# from the outside. -EXPOSE 3000 - -# Configure an entry point, so we don't need to specify -# "bundle exec" for each of our commands. -ENTRYPOINT ["bundle", "exec"] - -# The main command to run when the container starts. Also -# tell the Rails dev server to bind to all interfaces by -# default. -CMD ["rails", "server", "-b", "0.0.0.0"] +CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"] \ No newline at end of file diff --git a/Gemfile b/Gemfile index 3cbae613b..ed11f51ba 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,56 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.7.4' +ruby '3.1.1' + +# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" +gem "rails", "~> 7.0.2", ">= 7.0.2.2" + +# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] +gem "sprockets-rails" + +# Use postgresql as the database for Active Record +gem "pg", "~> 1.3" + +# Use the Puma web server [https://github.com/puma/puma] +gem "puma", "~> 5.6" + +# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] +gem "importmap-rails" + +# Hotwire"s SPA-like page accelerator [https://turbo.hotwired.dev] +gem "turbo-rails" + +# Hotwire"s modest JavaScript framework [https://stimulus.hotwired.dev] +gem "stimulus-rails" + +# Build JSON APIs with ease [https://github.com/rails/jbuilder] +gem "jbuilder" + +# Use Redis adapter to run Action Cable in production +gem "redis", "~> 4.6" + +# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] +# gem "kredis" + +# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] +# gem "bcrypt", "~> 3.1.7" + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby] + +# Reduces boot times through caching; required in config/boot.rb +gem "bootsnap", require: false + +# Use Sass to process CSS +# gem "sassc-rails", "~> 2.1" + +# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] +gem "image_processing", "~> 1.12" + +gem "brakeman" +gem "bundler-audit" +gem "rubocop-rails" gem 'active_model_serializers', '>= 0.10.13' gem 'acts-as-list' @@ -19,16 +68,12 @@ gem 'mini_magick' gem 'newrelic_rpm' gem 'omniauth-github' gem 'omniauth_login_dot_gov', git: 'https://github.com/18F/omniauth_login_dot_gov.git', branch: 'main' -gem 'rails', '>= 6.1.4.4' -gem 'pg', '~> 1.3.1' -gem 'puma', '~> 5.6.2' gem 'rack-cors', '>= 1.1.1', require: 'rack/cors' gem 'sass-rails', '>= 6.0.0' gem 'sidekiq' gem 'uglifier' gem 'json-jwt' # Use Redis to cache Touchpoints in all envs -gem 'redis' gem 'redis-namespace' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -37,7 +82,7 @@ gem 'aasm', '~> 5.2.0' gem 'whenever', require: false gem 'logstop' gem 'paper_trail' -gem 'acts-as-taggable-on', '~> 8.0' +gem 'acts-as-taggable-on' gem "rolify" group :development, :test do @@ -66,5 +111,3 @@ group :test do gem 'webdrivers', '>= 5.0.0' end -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock index d224106c6..2f4785716 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,40 +18,47 @@ GEM aasm-diagram (0.1.3) aasm (~> 5.0, >= 4.12) ruby-graphviz (~> 1.2) - actioncable (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) + actioncable (7.0.2.3) + actionpack (= 7.0.2.3) + activesupport (= 7.0.2.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.4.4) - actionpack (= 6.1.4.4) - activejob (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) + actionmailbox (7.0.2.3) + actionpack (= 7.0.2.3) + activejob (= 7.0.2.3) + activerecord (= 7.0.2.3) + activestorage (= 7.0.2.3) + activesupport (= 7.0.2.3) mail (>= 2.7.1) - actionmailer (6.1.4.4) - actionpack (= 6.1.4.4) - actionview (= 6.1.4.4) - activejob (= 6.1.4.4) - activesupport (= 6.1.4.4) + net-imap + net-pop + net-smtp + actionmailer (7.0.2.3) + actionpack (= 7.0.2.3) + actionview (= 7.0.2.3) + activejob (= 7.0.2.3) + activesupport (= 7.0.2.3) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.4.4) - actionview (= 6.1.4.4) - activesupport (= 6.1.4.4) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.2.3) + actionview (= 7.0.2.3) + activesupport (= 7.0.2.3) + rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.4.4) - actionpack (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) + actiontext (7.0.2.3) + actionpack (= 7.0.2.3) + activerecord (= 7.0.2.3) + activestorage (= 7.0.2.3) + activesupport (= 7.0.2.3) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.4.4) - activesupport (= 6.1.4.4) + actionview (7.0.2.3) + activesupport (= 7.0.2.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -61,38 +68,38 @@ GEM activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.4.4) - activesupport (= 6.1.4.4) + activejob (7.0.2.3) + activesupport (= 7.0.2.3) globalid (>= 0.3.6) - activemodel (6.1.4.4) - activesupport (= 6.1.4.4) - activerecord (6.1.4.4) - activemodel (= 6.1.4.4) - activesupport (= 6.1.4.4) - activestorage (6.1.4.4) - actionpack (= 6.1.4.4) - activejob (= 6.1.4.4) - activerecord (= 6.1.4.4) - activesupport (= 6.1.4.4) - marcel (~> 1.0.0) + activemodel (7.0.2.3) + activesupport (= 7.0.2.3) + activerecord (7.0.2.3) + activemodel (= 7.0.2.3) + activesupport (= 7.0.2.3) + activestorage (7.0.2.3) + actionpack (= 7.0.2.3) + activejob (= 7.0.2.3) + activerecord (= 7.0.2.3) + activesupport (= 7.0.2.3) + marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.4.4) + activesupport (7.0.2.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) acts-as-list (0.1.2) - acts-as-taggable-on (8.1.0) - activerecord (>= 5.0, < 6.2) + acts-as-taggable-on (9.0.1) + activerecord (>= 6.0, < 7.1) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) aes_key_wrap (1.1.0) + ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.564.0) + aws-partitions (1.568.0) aws-record (2.7.0) aws-sdk-dynamodb (~> 1.18) - aws-sdk-core (3.129.0) + aws-sdk-core (3.130.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -118,10 +125,16 @@ GEM rack (~> 2) aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) - bcrypt (3.1.16) + bcrypt (3.1.17) bindata (2.4.10) bindex (0.8.1) + bootsnap (1.11.1) + msgpack (~> 1.2) + brakeman (5.2.1) builder (3.2.4) + bundler-audit (0.9.0.1) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) capybara (3.36.0) addressable matrix @@ -164,13 +177,14 @@ GEM responders warden (~> 1.2.3) diff-lcs (1.5.0) + digest (3.1.0) docile (1.4.0) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) erubi (1.10.0) - excon (0.91.0) + excon (0.92.0) execjs (2.8.1) factory_bot (6.2.0) activesupport (>= 5.0.0) @@ -204,6 +218,13 @@ GEM image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) + importmap-rails (1.0.3) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + io-wait (0.2.1) + jbuilder (2.11.5) + actionview (>= 5.0.0) + activesupport (>= 5.0.0) jmespath (1.6.1) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) @@ -231,12 +252,11 @@ GEM kaminari-core (1.2.2) kramdown (2.3.1) rexml - listen (3.1.5) + listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) logstop (0.2.8) - loofah (2.14.0) + loofah (2.15.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -249,16 +269,28 @@ GEM mime-types-data (3.2022.0105) mini_magick (4.11.0) mini_mime (1.1.2) - mini_portile2 (2.8.0) minitest (5.15.0) + msgpack (1.4.5) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) + net-imap (0.2.3) + digest + net-protocol + strscan + net-pop (0.1.1) + digest + net-protocol + timeout + net-protocol (0.1.2) + io-wait + timeout + net-smtp (0.3.1) + digest + net-protocol + timeout newrelic_rpm (8.5.0) nio4r (2.5.8) - nokogiri (1.13.3) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) nokogiri (1.13.3-x86_64-darwin) racc (~> 1.4) oauth2 (1.4.9) @@ -278,10 +310,13 @@ GEM oauth2 (~> 1.4) omniauth (>= 1.9, < 3) orm_adapter (0.5.0) - paper_trail (12.2.0) + paper_trail (12.3.0) activerecord (>= 5.2) request_store (~> 1.1) - pg (1.3.3) + parallel (1.21.0) + parser (3.1.1.0) + ast (~> 2.4.1) + pg (1.3.4) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) @@ -296,21 +331,20 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.4.4) - actioncable (= 6.1.4.4) - actionmailbox (= 6.1.4.4) - actionmailer (= 6.1.4.4) - actionpack (= 6.1.4.4) - actiontext (= 6.1.4.4) - actionview (= 6.1.4.4) - activejob (= 6.1.4.4) - activemodel (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) + rails (7.0.2.3) + actioncable (= 7.0.2.3) + actionmailbox (= 7.0.2.3) + actionmailer (= 7.0.2.3) + actionpack (= 7.0.2.3) + actiontext (= 7.0.2.3) + actionview (= 7.0.2.3) + activejob (= 7.0.2.3) + activemodel (= 7.0.2.3) + activerecord (= 7.0.2.3) + activestorage (= 7.0.2.3) + activesupport (= 7.0.2.3) bundler (>= 1.15.0) - railties (= 6.1.4.4) - sprockets-rails (>= 2.0.0) + railties (= 7.0.2.3) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -320,18 +354,20 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.4.2) loofah (~> 2.3) - railties (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) + railties (7.0.2.3) + actionpack (= 7.0.2.3) + activesupport (= 7.0.2.3) method_source - rake (>= 0.13) + rake (>= 12.2) thor (~> 1.0) + zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.0.6) rb-fsevent (0.11.1) rb-inotify (0.10.1) ffi (~> 1.0) redis (4.6.0) - redis-namespace (1.8.1) + redis-namespace (1.8.2) redis (>= 3.0.4) regexp_parser (2.2.1) request_store (1.5.1) @@ -360,11 +396,26 @@ GEM rspec-support (3.11.0) rspec_junit_formatter (0.5.1) rspec-core (>= 2, < 4, != 2.12.0) + rubocop (1.26.0) + parallel (~> 1.10) + parser (>= 3.1.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.16.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.16.0) + parser (>= 3.1.1.0) + rubocop-rails (2.14.1) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.7.0, < 2.0) ruby-graphviz (1.2.5) rexml + ruby-progressbar (1.11.0) ruby-vips (2.1.4) ffi (~> 1.12) - ruby_dep (1.5.0) rubyzip (1.3.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -398,12 +449,20 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) ssrf_filter (1.0.7) + stimulus-rails (1.0.4) + railties (>= 6.0.0) + strscan (3.0.1) thor (1.2.1) tilt (2.0.10) + timeout (0.2.0) + turbo-rails (1.0.1) + actionpack (>= 6.0.0) + railties (>= 6.0.0) tzinfo (2.0.4) concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unicode-display_width (2.1.0) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.0) @@ -425,17 +484,18 @@ GEM zeitwerk (2.5.4) PLATFORMS - ruby x86_64-darwin-19 - x86_64-darwin-20 DEPENDENCIES aasm (~> 5.2.0) aasm-diagram active_model_serializers (>= 0.10.13) acts-as-list - acts-as-taggable-on (~> 8.0) + acts-as-taggable-on aws-sdk-rails (>= 3.6.1) + bootsnap + brakeman + bundler-audit capybara (>= 3.35.3) caracal (>= 1.4.1) carrierwave (>= 2.2.1) @@ -444,6 +504,9 @@ DEPENDENCIES dotenv-rails (>= 2.7.6) factory_bot_rails (>= 6.2.0) fog-aws (>= 3.12.0) + image_processing (~> 1.12) + importmap-rails + jbuilder jquery-rails (>= 4.4.0) jquery-ui-rails (>= 6.0.1) json-jwt @@ -457,21 +520,25 @@ DEPENDENCIES omniauth-github omniauth_login_dot_gov! paper_trail - pg (~> 1.3.1) + pg (~> 1.3) pry - puma (~> 5.6.2) + puma (~> 5.6) rack-cors (>= 1.1.1) - rails (>= 6.1.4.4) + rails (~> 7.0.2, >= 7.0.2.2) rails-controller-testing (>= 1.0.5) - redis + redis (~> 4.6) redis-namespace rolify rspec-rails (>= 5.0.2) rspec_junit_formatter + rubocop-rails sass-rails (>= 6.0.0) selenium-webdriver sidekiq simplecov + sprockets-rails + stimulus-rails + turbo-rails tzinfo-data uglifier web-console (>= 4.1.0) @@ -479,7 +546,7 @@ DEPENDENCIES whenever RUBY VERSION - ruby 2.7.4p191 + ruby 3.1.1p18 BUNDLED WITH - 2.2.24 + 2.3.7 diff --git a/README.md b/README.md index 893c36d5e..5e41266cc 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,29 @@ The Touchpoints team tracks work in a [backlog](https://en.wikipedia.org/wiki/Ka Issues and ideas are also noted in GitHub [Issues](https://github.com/gsa/touchpoints/issues). +## Docker development + +Setup + +1. install Docker +2. clone repo +3. copy .env.sample .env and update vars + +To build a development environment + +1. docker-compose build +2. docker-compose run webapp rails db:create +3. docker-compose run webapp rails db:setup +4. docker-compose up +5. Navigate to http://lvh.me:3002/admin + +To start/stop after building +1. docker-compose up +2. docker-compose down + +To run tests (TBD -- this currently does not work with selenium web driver) +1. docker-compose run webapp rspec + ## License See [LICENSE](LICENSE.md) diff --git a/app/models/submission.rb b/app/models/submission.rb index 566f4fc05..d94d9c956 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -57,7 +57,7 @@ def validate_custom_form # Run Custom Validations questions.each do |question| if question.is_required && !answered_questions[question.answer_field] - errors.messages[question.answer_field] << "is required" + errors.add(question.answer_field.to_sym, :blank, message: "is required") end if question.character_limit.present? && answered_questions[question.answer_field] && answered_questions[question.answer_field].length > question.character_limit diff --git a/bin/rails b/bin/rails index 6fb4e4051..efc037749 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,4 @@ #!/usr/bin/env ruby -APP_PATH = File.expand_path('../config/application', __dir__) +APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../config/boot" require "rails/commands" diff --git a/bin/setup b/bin/setup index 583be4a57..ec47b79b3 100755 --- a/bin/setup +++ b/bin/setup @@ -2,7 +2,7 @@ require "fileutils" # path to your application root. -APP_ROOT = File.expand_path('..', __dir__) +APP_ROOT = File.expand_path("..", __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -13,24 +13,21 @@ FileUtils.chdir APP_ROOT do # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') - - # Install JavaScript dependencies - # system! 'bin/yarn' + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" - # unless File.exist?('config/database.yml') - # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" # end puts "\n== Preparing database ==" - system! 'bin/rails db:prepare' + system! "bin/rails db:prepare" puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' + system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" - system! 'bin/rails restart' + system! "bin/rails restart" end diff --git a/config/database.yml b/config/database.yml index 7d938cdcc..9fa30a00a 100644 --- a/config/database.yml +++ b/config/database.yml @@ -23,9 +23,6 @@ default: &default development: <<: *default - # Uncomment host and username if running docker containers ( docker-compose ) - #host: postgres - #username: postgres database: touchpoints_development # The specified database role being used to connect to postgres. diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb new file mode 100644 index 000000000..a579326e2 --- /dev/null +++ b/config/initializers/new_framework_defaults_7_0.rb @@ -0,0 +1,117 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 7.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `7.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +# `button_to` view helper will render `