├── .editorconfig ├── .gitattributes ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app └── models │ └── form_core │ ├── application_record.rb │ ├── field.rb │ └── form.rb ├── bin └── rails ├── db └── migrate │ ├── 20170430190404_create_forms.rb │ └── 20170430191336_create_fields.rb ├── form_core.gemspec ├── lib ├── form_core.rb ├── form_core │ ├── coder.rb │ ├── coders │ │ ├── hash_coder.rb │ │ └── yaml_coder.rb │ ├── concerns │ │ └── models │ │ │ ├── field.rb │ │ │ └── form.rb │ ├── engine.rb │ ├── errors.rb │ ├── version.rb │ └── virtual_model.rb └── tasks │ └── form_core_tasks.rake └── test ├── dummy ├── Rakefile ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ ├── application.es6 │ │ │ └── direct_uploads.es6 │ │ └── stylesheets │ │ │ ├── application.scss │ │ │ └── direct_uploads.css │ ├── channels │ │ └── application_cable │ │ │ ├── channel.rb │ │ │ └── connection.rb │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── dictionaries_controller.rb │ │ ├── fields │ │ │ ├── application_controller.rb │ │ │ ├── choices_controller.rb │ │ │ ├── data_source_options_controller.rb │ │ │ ├── options_controller.rb │ │ │ └── validations_controller.rb │ │ ├── forms │ │ │ ├── application_controller.rb │ │ │ ├── fields_controller.rb │ │ │ ├── loads_controller.rb │ │ │ ├── previews_controller.rb │ │ │ └── sections_controller.rb │ │ ├── forms_controller.rb │ │ ├── nested_forms │ │ │ ├── application_controller.rb │ │ │ └── fields_controller.rb │ │ └── time_zones_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ ├── fields_helper.rb │ │ └── forms_helper.rb │ ├── jobs │ │ └── application_job.rb │ ├── lib │ │ └── .keep │ ├── models │ │ ├── application_record.rb │ │ ├── choice.rb │ │ ├── concerns │ │ │ ├── .keep │ │ │ ├── acts_as_default_value.rb │ │ │ ├── enum_attribute_localizable.rb │ │ │ ├── fields │ │ │ │ ├── fake.rb │ │ │ │ ├── helper.rb │ │ │ │ ├── options │ │ │ │ │ └── data_source.rb │ │ │ │ └── validations │ │ │ │ │ ├── acceptance.rb │ │ │ │ │ ├── confirmation.rb │ │ │ │ │ ├── exclusion.rb │ │ │ │ │ ├── format.rb │ │ │ │ │ ├── inclusion.rb │ │ │ │ │ ├── length.rb │ │ │ │ │ ├── numericality.rb │ │ │ │ │ └── presence.rb │ │ │ ├── form_core │ │ │ │ ├── active_storage_bridge │ │ │ │ │ └── attached │ │ │ │ │ │ └── macros.rb │ │ │ │ └── acts_as_default_value.rb │ │ │ └── forms │ │ │ │ └── fake.rb │ │ ├── data_source.rb │ │ ├── data_sources.rb │ │ ├── data_sources │ │ │ ├── dictionary.rb │ │ │ └── empty.rb │ │ ├── dictionary.rb │ │ ├── field.rb │ │ ├── field_options.rb │ │ ├── fields.rb │ │ ├── fields │ │ │ ├── attachment_field.rb │ │ │ ├── boolean_field.rb │ │ │ ├── choice_field.rb │ │ │ ├── date_field.rb │ │ │ ├── date_range_field.rb │ │ │ ├── datetime_field.rb │ │ │ ├── datetime_range_field.rb │ │ │ ├── decimal_field.rb │ │ │ ├── decimal_range_field.rb │ │ │ ├── embeds │ │ │ │ ├── date_range.rb │ │ │ │ ├── datetime_range.rb │ │ │ │ ├── decimal_range.rb │ │ │ │ └── integer_range.rb │ │ │ ├── integer_field.rb │ │ │ ├── integer_range_field.rb │ │ │ ├── multiple_attachment_field.rb │ │ │ ├── multiple_choice_field.rb │ │ │ ├── multiple_nested_form_field.rb │ │ │ ├── multiple_resource_field.rb │ │ │ ├── multiple_resource_select_field.rb │ │ │ ├── multiple_select_field.rb │ │ │ ├── nested_form_field.rb │ │ │ ├── options │ │ │ │ ├── date_field.rb │ │ │ │ ├── date_range_field.rb │ │ │ │ ├── datetime_field.rb │ │ │ │ ├── datetime_range_field.rb │ │ │ │ ├── decimal_field.rb │ │ │ │ ├── decimal_range_field.rb │ │ │ │ ├── integer_field.rb │ │ │ │ ├── integer_range_field.rb │ │ │ │ ├── multiple_resource_field.rb │ │ │ │ ├── multiple_resource_select_field.rb │ │ │ │ ├── multiple_select_field.rb │ │ │ │ ├── resource_field.rb │ │ │ │ ├── resource_select_field.rb │ │ │ │ ├── select_field.rb │ │ │ │ └── text_field.rb │ │ │ ├── resource_field.rb │ │ │ ├── resource_select_field.rb │ │ │ ├── select_field.rb │ │ │ ├── text_field.rb │ │ │ └── validations │ │ │ │ ├── attachment_field.rb │ │ │ │ ├── boolean_field.rb │ │ │ │ ├── choice_field.rb │ │ │ │ ├── date_field.rb │ │ │ │ ├── date_range_field.rb │ │ │ │ ├── datetime_field.rb │ │ │ │ ├── datetime_range_field.rb │ │ │ │ ├── decimal_field.rb │ │ │ │ ├── decimal_range_field.rb │ │ │ │ ├── integer_field.rb │ │ │ │ ├── integer_range_field.rb │ │ │ │ ├── multiple_attachment_field.rb │ │ │ │ ├── multiple_choice_field.rb │ │ │ │ ├── multiple_nested_form_field.rb │ │ │ │ ├── multiple_resource_field.rb │ │ │ │ ├── multiple_resource_select_field.rb │ │ │ │ ├── multiple_select_field.rb │ │ │ │ ├── nested_form_field.rb │ │ │ │ ├── resource_field.rb │ │ │ │ ├── resource_select_field.rb │ │ │ │ ├── select_field.rb │ │ │ │ └── text_field.rb │ │ ├── form.rb │ │ ├── metal_form.rb │ │ ├── nested_form.rb │ │ ├── non_configurable.rb │ │ ├── section.rb │ │ └── virtual_model.rb │ ├── overrides │ │ └── action_view │ │ │ └── helpers │ │ │ └── tags │ │ │ ├── date_field_override.rb │ │ │ └── datetime_local_field_override.rb │ ├── presenters │ │ ├── application_presenter.rb │ │ ├── concerns │ │ │ └── fields │ │ │ │ └── presenter_for_number_field.rb │ │ └── fields │ │ │ ├── attachment_field_presenter.rb │ │ │ ├── boolean_field_presenter.rb │ │ │ ├── choice_field_presenter.rb │ │ │ ├── composite_field_presenter.rb │ │ │ ├── date_field_presenter.rb │ │ │ ├── date_range_field_presenter.rb │ │ │ ├── datetime_field_presenter.rb │ │ │ ├── datetime_range_field_presenter.rb │ │ │ ├── decimal_field_presenter.rb │ │ │ ├── decimal_range_field_presenter.rb │ │ │ ├── field_presenter.rb │ │ │ ├── integer_field_presenter.rb │ │ │ ├── integer_range_field_presenter.rb │ │ │ ├── multiple_attachment_field_presenter.rb │ │ │ ├── multiple_choice_field_presenter.rb │ │ │ ├── multiple_nested_form_field_presenter.rb │ │ │ ├── multiple_resource_field_presenter.rb │ │ │ ├── multiple_resource_select_field_presenter.rb │ │ │ ├── multiple_select_field_presenter.rb │ │ │ ├── nested_form_field_presenter.rb │ │ │ ├── resource_field_presenter.rb │ │ │ ├── resource_select_field_presenter.rb │ │ │ ├── select_field_presenter.rb │ │ │ └── text_field_presenter.rb │ └── views │ │ ├── _form_core │ │ ├── data_source_options │ │ │ ├── _dictionary.html.erb │ │ │ └── _empty.html.erb │ │ ├── field_options │ │ │ ├── _date_field.html.erb │ │ │ ├── _date_range_field.html.erb │ │ │ ├── _datetime_field.html.erb │ │ │ ├── _datetime_range_field.html.erb │ │ │ ├── _decimal_field.html.erb │ │ │ ├── _decimal_range_field.html.erb │ │ │ ├── _integer_field.html.erb │ │ │ ├── _integer_range_field.html.erb │ │ │ ├── _multiple_resource_field.html.erb │ │ │ ├── _multiple_resource_select_field.html.erb │ │ │ ├── _multiple_select_field.html.erb │ │ │ ├── _resource_field.html.erb │ │ │ ├── _resource_select_field.html.erb │ │ │ ├── _select_field.html.erb │ │ │ └── _text_field.html.erb │ │ ├── fields │ │ │ ├── _attachment_field.html.erb │ │ │ ├── _boolean_field.html.erb │ │ │ ├── _choice_field.html.erb │ │ │ ├── _date_field.html.erb │ │ │ ├── _date_range_field.html.erb │ │ │ ├── _datetime_field.html.erb │ │ │ ├── _datetime_range_field.html.erb │ │ │ ├── _decimal_field.html.erb │ │ │ ├── _decimal_range_field.html.erb │ │ │ ├── _integer_field.html.erb │ │ │ ├── _integer_range_field.html.erb │ │ │ ├── _multiple_attachment_field.html.erb │ │ │ ├── _multiple_choice_field.html.erb │ │ │ ├── _multiple_nested_form_field.html.erb │ │ │ ├── _multiple_resource_field.html.erb │ │ │ ├── _multiple_resource_select_field.html.erb │ │ │ ├── _multiple_select_field.html.erb │ │ │ ├── _nested_form.html.erb │ │ │ ├── _nested_form_field.html.erb │ │ │ ├── _resource_field.html.erb │ │ │ ├── _resource_select_field.html.erb │ │ │ ├── _select_field.html.erb │ │ │ └── _text_field.html.erb │ │ ├── preview │ │ │ ├── _form.html.erb │ │ │ └── _nested_form.html.erb │ │ ├── render │ │ │ └── _form.html.erb │ │ └── validations │ │ │ ├── _acceptance.html.erb │ │ │ ├── _confirmation.html.erb │ │ │ ├── _exclusion.html.erb │ │ │ ├── _format.html.erb │ │ │ ├── _inclusion.html.erb │ │ │ ├── _length.html.erb │ │ │ ├── _numericality.html.erb │ │ │ └── _presence.html.erb │ │ ├── dictionaries │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── new.html.erb │ │ ├── fields │ │ ├── choices │ │ │ ├── _form.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── index.html.erb │ │ │ └── new.html.erb │ │ ├── data_source_options │ │ │ ├── _form.html.erb │ │ │ └── edit.html.erb │ │ ├── options │ │ │ ├── _form.html.erb │ │ │ └── edit.html.erb │ │ └── validations │ │ │ ├── _form.html.erb │ │ │ └── edit.html.erb │ │ ├── forms │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── fields │ │ │ ├── _form.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── index.html.erb │ │ │ └── new.html.erb │ │ ├── index.html.erb │ │ ├── loads │ │ │ ├── create.html.erb │ │ │ └── show.html.erb │ │ ├── new.html.erb │ │ ├── previews │ │ │ ├── create.html.erb │ │ │ └── show.html.erb │ │ └── sections │ │ │ ├── _form.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── index.html.erb │ │ │ └── new.html.erb │ │ ├── layouts │ │ ├── _alert.html.erb │ │ ├── _footer.html.erb │ │ ├── _nav.html.erb │ │ ├── _notice.html.erb │ │ ├── application.html.erb │ │ └── forms.html.erb │ │ └── nested_forms │ │ └── fields │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── new.html.erb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── update ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── actionview.rb │ │ ├── application_controller_renderer.rb │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── content_security_policy.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── form_core.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── validates_timeliness.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ ├── en.yml │ │ ├── form_core.en.yml │ │ └── validates_timeliness.en.yml │ ├── puma.rb │ ├── routes.rb │ ├── secrets.yml │ ├── spring.rb │ └── storage.yml ├── db │ ├── migrate │ │ ├── 20170504211819_add_columns_to_forms.rb │ │ ├── 20170504214301_create_sections.rb │ │ ├── 20170504225506_add_columns_to_fields.rb │ │ ├── 20170515212355_create_dictionaries.rb │ │ ├── 20170613184106_add_attachable_to_forms.rb │ │ ├── 20180120200440_add_position_to_fields.rb │ │ ├── 20180120201200_add_position_to_sections.rb │ │ ├── 20180220232604_create_choices.rb │ │ ├── 20180228201120_add_position_to_choices.rb │ │ └── 20210108214331_create_active_storage_tables.active_storage.rb │ └── schema.rb ├── lib │ ├── assets │ │ └── .keep │ └── monkey_patches │ │ ├── active_support.rb │ │ └── active_support │ │ └── concern+prependable.rb ├── log │ └── .keep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ └── favicon.ico ├── storage │ └── .keep └── test │ ├── application_system_test_case.rb │ ├── controllers │ └── .keep │ ├── fixtures │ └── .keep │ ├── models │ └── .keep │ └── system │ └── .keep ├── fixtures └── form_core │ ├── fields.yml │ └── forms.yml ├── form_core_test.rb ├── integration └── navigation_test.rb ├── models └── form_core │ ├── field_test.rb │ └── form_test.rb └── test_helper.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | end_of_line = lf 13 | 14 | [*.md] 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rb diff=ruby 2 | *.gemspec diff=ruby 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | .bundle/ 9 | 10 | # Ignore the default SQLite database. 11 | test/dummy/db/*.sqlite3 12 | test/dummy/db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | log/*.log 16 | !test/dummy/log/.keep 17 | !test/dummy/tmp/.keep 18 | test/dummy/log/*.log 19 | test/dummy/tmp/ 20 | 21 | # Ignore uploaded files in development 22 | !test/dummy/storage/.keep 23 | test/dummy/storage/* 24 | 25 | pkg/ 26 | .byebug_history 27 | 28 | node_modules/ 29 | test/dummy/public/packs 30 | test/dummy/node_modules/ 31 | yarn-error.log 32 | 33 | *.gem 34 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.0.3 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | # Declare your gem"s dependencies in form_core.gemspec. 7 | # Bundler will treat runtime dependencies like base dependencies, and 8 | # development dependencies will be added by default to the :development group. 9 | gemspec 10 | 11 | gem "activeentity", path: "../activeentity" 12 | 13 | # Declare any dependencies that are still in development here instead of in 14 | # your gemspec. These might include edge Rails or gems from your path or 15 | # Git. Remember to move these dependencies to your gemspec before releasing 16 | # your gem to rubygems.org. 17 | 18 | gem "rails", "~> 7.0.0" 19 | gem "sqlite3" 20 | 21 | # To support ES6 22 | gem "sprockets", "~> 4.0" 23 | # Support ES6 24 | gem "babel-transpiler" 25 | # Use CoffeeScript for .coffee assets and views 26 | # gem "coffee-rails", "~> 4.2" 27 | # Use SCSS for stylesheets 28 | gem "sassc-rails" 29 | # Use Uglifier as compressor for JavaScript assets 30 | gem "uglifier", ">= 1.3.0" 31 | 32 | gem "bulma-rails" 33 | gem "jquery-rails" 34 | gem "selectize-rails" 35 | gem "turbolinks" 36 | 37 | # Use Puma as the app server 38 | gem "puma" 39 | 40 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 41 | gem "listen", ">= 3.2" 42 | gem "web-console", group: :development 43 | # Call "byebug" anywhere in the code to stop execution and get a debugger console 44 | gem "byebug", platforms: %i[mri mingw x64_mingw] 45 | 46 | gem "timeliness-i18n" 47 | gem "validates_timeliness", "~> 5.0.0.alpha1" 48 | 49 | gem "acts_as_list" 50 | gem "cocoon" 51 | 52 | gem "rubocop" 53 | gem "rubocop-performance" 54 | gem "rubocop-rails" 55 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Jun Jiang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Form Core 2 | ==== 3 | 4 | A Rails engine providing ability to generate dynamic form. 5 | 6 | ## Requirements 7 | 8 | ### 0.1 branch 9 | 10 | - MRI 2.5+ 11 | - Rails 6.0+ 12 | 13 | ### 0.0.1 branch 14 | 15 | - MRI 2.3+ 16 | - Rails 5.0+ 17 | 18 | ## Usage 19 | 20 | See demo for now. 21 | 22 | ## Installation 23 | 24 | Add this line to your Gemfile: 25 | 26 | ```ruby 27 | gem 'form_core' 28 | ``` 29 | 30 | Or you may want to include the gem directly from GitHub: 31 | 32 | ```ruby 33 | gem 'form_core', github: 'rails-engine/form_core' 34 | ``` 35 | 36 | And then execute: 37 | 38 | ```sh 39 | $ bundle 40 | ``` 41 | 42 | Copy migrations 43 | 44 | ```sh 45 | $ bin/rails form_core:install:migrations 46 | ``` 47 | 48 | Then do migrate 49 | 50 | ```sh 51 | $ bin/rails db:migrate 52 | ``` 53 | 54 | ## Demo 55 | 56 | Clone the repository. 57 | 58 | ```sh 59 | $ git clone https://github.com/rails-engine/form_core.git 60 | ``` 61 | 62 | Change directory 63 | 64 | ```sh 65 | $ cd form_core 66 | ``` 67 | 68 | Run bundler 69 | 70 | ```sh 71 | $ bundle install 72 | ``` 73 | 74 | Preparing database 75 | 76 | ```sh 77 | $ bin/rails db:migrate 78 | ``` 79 | 80 | Start the Rails server 81 | 82 | ```sh 83 | $ bin/rails s 84 | ``` 85 | 86 | Open your browser, and visit `http://localhost:3000` 87 | 88 | ## Contributing 89 | 90 | Bug report or pull request are welcome. 91 | 92 | ### Make a pull request 93 | 94 | 1. Fork it 95 | 2. Create your feature branch (`git checkout -b my-new-feature`) 96 | 3. Commit your changes (`git commit -am 'Add some feature'`) 97 | 4. Push to the branch (`git push origin my-new-feature`) 98 | 5. Create new Pull Request 99 | 100 | Please write unit test with your code if necessary. 101 | 102 | ## License 103 | 104 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 105 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require "bundler/setup" 5 | rescue LoadError 6 | puts "You must `gem install bundler` and `bundle install` to run rake tasks" 7 | end 8 | 9 | require "rdoc/task" 10 | 11 | RDoc::Task.new(:rdoc) do |rdoc| 12 | rdoc.rdoc_dir = "rdoc" 13 | rdoc.title = "FormCore" 14 | rdoc.options << "--line-numbers" 15 | rdoc.rdoc_files.include("README.md") 16 | rdoc.rdoc_files.include("lib/**/*.rb") 17 | end 18 | 19 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 20 | load "rails/tasks/engine.rake" 21 | 22 | load "rails/tasks/statistics.rake" 23 | 24 | require "bundler/gem_tasks" 25 | 26 | require "rake/testtask" 27 | 28 | Rake::TestTask.new(:test) do |t| 29 | t.libs << "test" 30 | t.pattern = "test/**/*_test.rb" 31 | t.verbose = false 32 | end 33 | 34 | task default: :test 35 | -------------------------------------------------------------------------------- /app/models/form_core/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | class ApplicationRecord < ActiveRecord::Base 5 | self.abstract_class = true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/form_core/field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | class Field < ApplicationRecord 5 | include FormCore::Concerns::Models::Field 6 | 7 | self.table_name = "fields" 8 | 9 | belongs_to :form, touch: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/form_core/form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | class Form < ApplicationRecord 5 | include FormCore::Concerns::Models::Form 6 | 7 | self.table_name = "forms" 8 | 9 | has_many :fields, dependent: :destroy 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # This command will automatically be run when you run "rails" with Rails gems 5 | # installed from the root of your application. 6 | 7 | ENGINE_ROOT = File.expand_path("../..", __FILE__) 8 | ENGINE_PATH = File.expand_path("../../lib/form_core/engine", __FILE__) 9 | APP_PATH = File.expand_path("../../test/dummy/config/application", __FILE__) 10 | 11 | # Set up gems listed in the Gemfile. 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) 13 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 14 | 15 | require "rails/all" 16 | require "rails/engine/commands" 17 | -------------------------------------------------------------------------------- /db/migrate/20170430190404_create_forms.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateForms < ActiveRecord::Migration[5.1] 4 | def change 5 | create_table :forms do |t| 6 | t.string :type, null: false, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20170430191336_create_fields.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateFields < ActiveRecord::Migration[5.1] 4 | def change 5 | create_table :fields do |t| 6 | t.string :name, null: false 7 | t.integer :accessibility, null: false 8 | t.text :validations 9 | t.text :options 10 | t.string :type, null: false, index: true 11 | t.references :form, foreign_key: true 12 | 13 | t.timestamps 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /form_core.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push File.expand_path("lib", __dir__) 4 | 5 | # Maintain your gem's version: 6 | require "form_core/version" 7 | 8 | # Describe your gem and declare its dependencies: 9 | Gem::Specification.new do |s| 10 | s.name = "form_core" 11 | s.version = FormCore::VERSION 12 | s.authors = ["jasl"] 13 | s.email = ["jasl9187@hotmail.com"] 14 | s.homepage = "https://github.com/rails-engine/form_core" 15 | s.summary = "A Rails engine providing ability to generate dynamic form." 16 | s.description = <<-TEXT.lstrip 17 | A Rails engine providing ability to generate dynamic form. 18 | It's would make such as dynamic fields of model or questionnaire easily. 19 | TEXT 20 | s.license = "MIT" 21 | 22 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 23 | 24 | #s.add_dependency "activeentity", ">= 6.1.0" 25 | s.add_dependency "rails", ">= 6.1", "< 8" 26 | end 27 | -------------------------------------------------------------------------------- /lib/form_core.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "form_core/engine" 4 | require "form_core/errors" 5 | 6 | require "form_core/coder" 7 | require "form_core/coders/hash_coder" 8 | require "form_core/coders/yaml_coder" 9 | 10 | require "form_core/virtual_model" 11 | require "form_core/concerns/models/form" 12 | require "form_core/concerns/models/field" 13 | 14 | module FormCore 15 | class << self 16 | def virtual_model_class 17 | @virtual_model_class ||= VirtualModel 18 | end 19 | 20 | def virtual_model_class=(klass) 21 | raise ArgumentError, "#{klass} should be sub-class of #{VirtualModel}." unless klass && klass < VirtualModel 22 | 23 | @reserved_names = nil 24 | @virtual_model_class = klass 25 | end 26 | 27 | def reserved_names 28 | @reserved_names ||= Set.new( 29 | %i[def class module private public protected allocate new parent superclass] + 30 | virtual_model_class.instance_methods(true) 31 | ) 32 | end 33 | 34 | def virtual_model_coder_class 35 | @virtual_model_coder_class ||= HashCoder 36 | end 37 | 38 | def virtual_model_coder_class=(klass) 39 | raise ArgumentError, "#{klass} should be sub-class of #{Coder}." unless klass && klass < Coder 40 | 41 | @virtual_model_coder_class = klass 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/form_core/coder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | class Coder 5 | cattr_accessor :strict 6 | 7 | attr_reader :object_class 8 | 9 | def initialize(object_class) 10 | @object_class = object_class 11 | end 12 | 13 | def strict? 14 | Coder.strict 15 | end 16 | 17 | def dump(_obj) 18 | raise NotImplementedError 19 | end 20 | 21 | def load(_src) 22 | raise NotImplementedError 23 | end 24 | 25 | private 26 | 27 | def new_or_raise_decoding_error 28 | if strict? 29 | raise DecodingDataCorrupted 30 | else 31 | object_class.new 32 | end 33 | end 34 | 35 | def valid_attribute_names 36 | object_class.attribute_names + object_class._embeds_reflections.keys 37 | end 38 | 39 | def valid_attributes(hash) 40 | hash.slice(*valid_attribute_names) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/form_core/coders/hash_coder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "yaml" 4 | 5 | module FormCore 6 | class HashCoder < FormCore::Coder # :nodoc: 7 | def dump(obj) 8 | obj&.serializable_hash || {} 9 | end 10 | 11 | def load(hash) 12 | return new_or_raise_decoding_error if hash.nil? || !hash.respond_to?(:to_h) 13 | 14 | object_class.new valid_attributes(hash) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/form_core/coders/yaml_coder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "yaml" 4 | 5 | module FormCore 6 | class YAMLCoder < FormCore::Coder # :nodoc: 7 | cattr_accessor :safe_mode 8 | 9 | def self.whitelist_classes 10 | @whitelist_classes ||= [] 11 | end 12 | 13 | def safe_mode? 14 | YAMLCoder.safe_mode 15 | end 16 | 17 | def dump(obj) 18 | return YAML.dump({}) unless obj 19 | 20 | YAML.dump obj.serializable_hash 21 | end 22 | 23 | def load(yaml) 24 | return object_class.new if yaml.blank? 25 | 26 | return new_or_raise_decoding_error unless yaml.is_a?(String) && /^---/.match?(yaml) 27 | 28 | decoded = 29 | if safe_mode? 30 | YAML.safe_load(yaml, YAMLCoder.whitelist_classes) 31 | else 32 | YAML.safe_load(yaml) 33 | end 34 | return new_or_raise_decoding_error unless decoded.is_a? Hash 35 | 36 | object_class.new valid_attributes(decoded) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/form_core/concerns/models/form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore::Concerns 4 | module Models 5 | module Form 6 | extend ActiveSupport::Concern 7 | 8 | def to_virtual_model(model_name: "Form", 9 | fields_scope: proc { |fields| fields }, 10 | overrides: {}) 11 | model = FormCore.virtual_model_class.build model_name 12 | 13 | append_to_virtual_model(model, fields_scope: fields_scope, overrides: overrides) 14 | end 15 | 16 | def append_to_virtual_model(model, 17 | fields_scope: proc { |fields| fields }, 18 | overrides: {}) 19 | check_model_validity! model 20 | 21 | global_overrides = overrides.fetch(:_global, {}) 22 | fields_scope.call(fields).each do |f| 23 | f.interpret_to model, overrides: global_overrides.merge(overrides.fetch(f.name, {})) 24 | end 25 | 26 | model 27 | end 28 | 29 | private 30 | 31 | def check_model_validity!(model) 32 | unless model.is_a?(Class) && model < ::FormCore::VirtualModel 33 | raise ArgumentError, "#{model} must be a #{::FormCore::VirtualModel}'s subclass" 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/form_core/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | class Engine < ::Rails::Engine 5 | isolate_namespace FormCore 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/form_core/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | # = Form Core Errors 5 | # 6 | # Generic Form Core exception class. 7 | class FormCoreError < StandardError 8 | end 9 | 10 | class DecodingDataCorrupted < FormCoreError 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/form_core/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormCore 4 | VERSION = "0.1.5" 5 | end 6 | -------------------------------------------------------------------------------- /lib/tasks/form_core_tasks.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # desc "Explaining what the task does" 3 | # task :form_core do 4 | # # Task goes here 5 | # end 6 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative "config/application" 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.es6: -------------------------------------------------------------------------------- 1 | //= require rails-ujs 2 | //= require turbolinks 3 | //= require jquery3 4 | 5 | //= require selectize 6 | //= require cocoon 7 | //= require activestorage 8 | //= require direct_uploads 9 | 10 | // Cheat form https://github.com/jgthms/bulma/blob/master/docs/_javascript/main.js 11 | document.addEventListener('DOMContentLoaded', () => { 12 | 13 | // Dropdowns 14 | 15 | const $dropdowns = getAll('.dropdown:not(.is-hoverable)'); 16 | 17 | if ($dropdowns.length > 0) { 18 | $dropdowns.forEach($el => { 19 | $el.addEventListener('click', event => { 20 | event.stopPropagation(); 21 | $el.classList.toggle('is-active'); 22 | }); 23 | }); 24 | 25 | document.addEventListener('click', event => { 26 | closeDropdowns(); 27 | }); 28 | } 29 | 30 | function closeDropdowns() { 31 | $dropdowns.forEach($el => { 32 | $el.classList.remove('is-active'); 33 | }); 34 | } 35 | 36 | // Functions 37 | 38 | function getAll(selector) { 39 | return Array.prototype.slice.call(document.querySelectorAll(selector), 0); 40 | } 41 | 42 | // Utils 43 | 44 | function removeFromArray(array, value) { 45 | if (array.includes(value)) { 46 | const value_index = array.indexOf(value); 47 | array.splice(value_index, 1); 48 | } 49 | 50 | return array; 51 | } 52 | 53 | Array.prototype.diff = function (a) { 54 | return this.filter(function (i) { 55 | return a.indexOf(i) < 0; 56 | }); 57 | }; 58 | }); 59 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/direct_uploads.es6: -------------------------------------------------------------------------------- 1 | addEventListener("direct-upload:initialize", event => { 2 | const { target, detail } = event 3 | const { id, file } = detail 4 | target.insertAdjacentHTML("beforebegin", ` 5 |
6 |
7 | ${file.name} 8 |
9 | `) 10 | }) 11 | 12 | addEventListener("direct-upload:start", event => { 13 | const { id } = event.detail 14 | const element = document.getElementById(`direct-upload-${id}`) 15 | element.classList.remove("direct-upload--pending") 16 | }) 17 | 18 | addEventListener("direct-upload:progress", event => { 19 | const { id, progress } = event.detail 20 | const progressElement = document.getElementById(`direct-upload-progress-${id}`) 21 | progressElement.style.width = `${progress}%` 22 | }) 23 | 24 | addEventListener("direct-upload:error", event => { 25 | event.preventDefault() 26 | const { id, error } = event.detail 27 | const element = document.getElementById(`direct-upload-${id}`) 28 | element.classList.add("direct-upload--error") 29 | element.setAttribute("title", error) 30 | }) 31 | 32 | addEventListener("direct-upload:end", event => { 33 | const { id } = event.detail 34 | const element = document.getElementById(`direct-upload-${id}`) 35 | element.classList.add("direct-upload--complete") 36 | }) 37 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "selectize"; 2 | @import "bulma"; 3 | @import "direct_uploads"; 4 | 5 | body { 6 | display: flex; 7 | min-height: 100vh; 8 | flex-direction: column; 9 | 10 | & nav.nav { 11 | a.brand { 12 | padding: 0.5rem 0.75em 0.5em 0; 13 | } 14 | } 15 | 16 | & .main { 17 | flex: 1; 18 | } 19 | 20 | & footer.footer { 21 | padding: 3rem 1.5rem 3rem; 22 | } 23 | } 24 | 25 | .section-header { 26 | background-color:#fafafa; 27 | } 28 | 29 | .flash { 30 | overflow: hidden; 31 | top: 0; 32 | left: 0; 33 | right: 0; 34 | line-height: 2.5; 35 | background: $info; 36 | color: $text-invert; 37 | text-align: center; 38 | } 39 | 40 | #alert { 41 | background: $danger; 42 | } 43 | 44 | .nested_form_field { 45 | .collection { 46 | .nested_form { 47 | border: 1px solid $green; 48 | padding: 0.5em; 49 | margin-left: 0.75em; 50 | margin-bottom: 0.75rem; 51 | } 52 | } 53 | } 54 | 55 | .content { 56 | .nested-content { 57 | margin-left: 0.75em; 58 | margin-bottom: 1em; 59 | } 60 | } 61 | 62 | .message-body.content { 63 | > :first-child { 64 | margin-top: 0; 65 | } 66 | } 67 | 68 | .field { 69 | flex: 1; 70 | } 71 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/direct_uploads.css: -------------------------------------------------------------------------------- 1 | .direct-upload { 2 | display: inline-block; 3 | position: relative; 4 | padding: 2px 4px; 5 | margin: 0 3px 3px 0; 6 | border: 1px solid rgba(0, 0, 0, 0.3); 7 | border-radius: 3px; 8 | font-size: 11px; 9 | line-height: 13px; 10 | } 11 | 12 | .direct-upload--pending { 13 | opacity: 0.6; 14 | } 15 | 16 | .direct-upload__progress { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | bottom: 0; 21 | opacity: 0.2; 22 | background: #0076ff; 23 | transition: width 120ms ease-out, opacity 60ms 60ms ease-in; 24 | transform: translate3d(0, 0, 0); 25 | } 26 | 27 | .direct-upload--complete .direct-upload__progress { 28 | opacity: 0.4; 29 | } 30 | 31 | .direct-upload--error { 32 | border-color: red; 33 | } 34 | 35 | input[type=file][data-direct-upload-url][disabled] { 36 | display: none; 37 | } 38 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | around_action :set_time_zone 5 | 6 | helper_method :current_time_zone 7 | 8 | private 9 | 10 | def set_time_zone(&block) 11 | Time.use_zone(current_time_zone, &block) 12 | end 13 | 14 | def current_time_zone 15 | @_current_time_zone ||= 16 | if session[:current_time_zone].present? 17 | ActiveSupport::TimeZone[session[:current_time_zone]] || ActiveSupport::TimeZone["UTC"] 18 | else 19 | ActiveSupport::TimeZone["UTC"] 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/controllers/dictionaries_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DictionariesController < ApplicationController 4 | before_action :set_dictionary, only: %i[edit update destroy] 5 | 6 | # GET /dictionaries/dictionaries 7 | def index 8 | @dictionaries = Dictionary.all 9 | end 10 | 11 | # GET /dictionaries/dictionaries/new 12 | def new 13 | @dictionary = Dictionary.new 14 | end 15 | 16 | # GET /dictionaries/dictionaries/1/edit 17 | def edit; end 18 | 19 | # POST /dictionaries/dictionaries 20 | def create 21 | @dictionary = Dictionary.new(dictionary_params) 22 | 23 | if @dictionary.save 24 | redirect_to dictionaries_url, notice: "dictionary was successfully created." 25 | else 26 | render :new 27 | end 28 | end 29 | 30 | # PATCH/PUT /dictionaries/dictionaries/1 31 | def update 32 | if @dictionary.update(dictionary_params) 33 | redirect_to dictionaries_url, notice: "dictionary was successfully updated." 34 | else 35 | render :edit 36 | end 37 | end 38 | 39 | # DELETE /dictionaries/dictionaries/1 40 | def destroy 41 | @dictionary.destroy 42 | redirect_to dictionaries_url, notice: "dictionary was successfully destroyed." 43 | end 44 | 45 | private 46 | 47 | # Use callbacks to share common setup or constraints between actions. 48 | def set_dictionary 49 | @dictionary = Dictionary.find(params[:id]) 50 | end 51 | 52 | # Only allow a trusted parameter "white list" through. 53 | def dictionary_params 54 | params.fetch(:dictionary, {}).permit(:scope, :value) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/fields/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Fields::ApplicationController < ApplicationController 4 | before_action :set_field 5 | 6 | protected 7 | 8 | # Use callbacks to share common setup or constraints between actions. 9 | def set_field 10 | @field = Field.find(params[:field_id]) 11 | end 12 | 13 | def fields_url 14 | form = @field.form 15 | 16 | case form 17 | when Form 18 | form_fields_url(form) 19 | when NestedForm 20 | nested_form_fields_url(form) 21 | else 22 | raise "Unknown form: #{form.class}" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/fields/choices_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Fields::ChoicesController < Fields::ApplicationController 4 | before_action :require_attach_choices! 5 | before_action :set_choice, only: %i[edit update destroy] 6 | 7 | def new 8 | @choice = @field.choices.build 9 | end 10 | 11 | def create 12 | @choice = @field.choices.build choice_params 13 | if @choice.save 14 | redirect_to field_choices_url(@field) 15 | else 16 | render :new 17 | end 18 | end 19 | 20 | def edit; end 21 | 22 | def update 23 | if @choice.update choice_params 24 | redirect_to field_choices_url(@field) 25 | else 26 | render :edit 27 | end 28 | end 29 | 30 | def destroy 31 | @choice.destroy 32 | redirect_to field_choices_url(@field) 33 | end 34 | 35 | private 36 | 37 | def require_attach_choices! 38 | redirect_to fields_url unless @field.attached_choices? 39 | end 40 | 41 | def choice_params 42 | params.require(:choice).permit(:label) 43 | end 44 | 45 | def set_choice 46 | @choice = @field.choices.find(params[:id]) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/fields/data_source_options_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Fields::DataSourceOptionsController < Fields::ApplicationController 4 | before_action :require_data_source_options 5 | before_action :set_options 6 | 7 | def edit; end 8 | 9 | def update 10 | @options.assign_attributes(options_params) 11 | if @options.valid? && @field.save(validate: false) 12 | redirect_to fields_url, notice: "Field was successfully updated." 13 | else 14 | render :edit 15 | end 16 | end 17 | 18 | private 19 | 20 | def require_data_source_options 21 | redirect_to fields_url unless @field.respond_to?(:data_source) 22 | end 23 | 24 | def set_options 25 | @options = @field.data_source 26 | end 27 | 28 | def options_params 29 | params.fetch(:options, {}).permit! 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/fields/options_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Fields::OptionsController < Fields::ApplicationController 4 | before_action :set_options 5 | 6 | def edit; end 7 | 8 | def update 9 | @options.assign_attributes(options_params) 10 | if @options.valid? && @field.save(validate: false) 11 | redirect_to fields_url, notice: "Field was successfully updated." 12 | else 13 | render :edit 14 | end 15 | end 16 | 17 | private 18 | 19 | def set_options 20 | @options = @field.options 21 | end 22 | 23 | def options_params 24 | params.fetch(:options, {}).permit! 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/fields/validations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Fields::ValidationsController < Fields::ApplicationController 4 | before_action :set_validations 5 | 6 | def edit; end 7 | 8 | def update 9 | @validations.assign_attributes(validations_params) 10 | if @validations.valid? && @field.save(validate: false) 11 | redirect_to fields_url, notice: "Field was successfully updated." 12 | else 13 | render :edit 14 | end 15 | end 16 | 17 | private 18 | 19 | def set_validations 20 | @validations = @field.validations 21 | end 22 | 23 | def validations_params 24 | params.fetch(:validations, {}).permit! 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/forms/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Forms::ApplicationController < ApplicationController 4 | layout "forms" 5 | 6 | before_action :set_form 7 | 8 | protected 9 | 10 | # Use callbacks to share common setup or constraints between actions. 11 | def set_form 12 | @form = Form.find(params[:form_id]) 13 | end 14 | 15 | def set_form_with_eager_load_fields_and_sections 16 | @form = Form.includes(:sections, fields: [:choices]).find(params[:form_id]) 17 | 18 | grouped_fields = @form.fields.group_by(&:section_id) 19 | @form.sections.each do |section| 20 | association = section.fields.instance_variable_get(:@association) 21 | association.target.concat grouped_fields.fetch(section.id, []).sort_by(&:position) 22 | association.loaded! 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/forms/fields_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Forms::FieldsController < Forms::ApplicationController 4 | before_action :set_field, only: %i[show edit update destroy] 5 | 6 | # GET /forms/1/fields 7 | def index 8 | @fields = @form.fields.includes(:section).all 9 | end 10 | 11 | # GET /forms/fields/new 12 | def new 13 | @field = @form.fields.build 14 | end 15 | 16 | # GET /forms/1/fields/1/edit 17 | def edit; end 18 | 19 | # POST /forms/1/fields 20 | def create 21 | @field = @form.fields.build(field_params) 22 | 23 | if @field.save 24 | redirect_to form_fields_url(@form), notice: "Field was successfully created." 25 | else 26 | render :new 27 | end 28 | end 29 | 30 | # PATCH/PUT /forms/1/fields/1 31 | def update 32 | if @field.update(field_params) 33 | redirect_to form_fields_url(@form), notice: "Field was successfully updated." 34 | else 35 | render :edit 36 | end 37 | end 38 | 39 | # DELETE /forms/1/fields/1 40 | def destroy 41 | @field.destroy 42 | redirect_to form_fields_url(@form), notice: "Field was successfully destroyed." 43 | end 44 | 45 | private 46 | 47 | # Use callbacks to share common setup or constraints between actions. 48 | def set_field 49 | @field = @form.fields.find(params[:id]) 50 | end 51 | 52 | # Only allow a trusted parameter "white list" through. 53 | def field_params 54 | params.fetch(:field, {}).permit(:name, :label, :hint, :section_id, :accessibility, :type) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/forms/loads_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Forms::LoadsController < Forms::ApplicationController 4 | skip_before_action :set_form, except: [:show] 5 | before_action :set_form_with_eager_load_fields_and_sections, except: [:show] 6 | before_action :set_preview, except: [:show] 7 | 8 | def show; end 9 | 10 | def create 11 | @instance = @preview.load(serialized_form_data) 12 | render :create 13 | end 14 | 15 | private 16 | 17 | def set_preview 18 | @preview = @form.to_virtual_model 19 | end 20 | 21 | def serialized_form_data 22 | params.fetch(:serialized, "") 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/forms/previews_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Forms::PreviewsController < Forms::ApplicationController 4 | skip_before_action :set_form 5 | before_action :set_form_with_eager_load_fields_and_sections 6 | before_action :set_preview 7 | 8 | def show 9 | @instance = @preview.new 10 | end 11 | 12 | def create 13 | @instance = @preview.new(preview_params) 14 | if @instance.valid? 15 | render :create 16 | else 17 | render :show 18 | end 19 | end 20 | 21 | private 22 | 23 | def set_preview 24 | @preview = @form.to_virtual_model 25 | end 26 | 27 | def preview_params 28 | params.fetch(:preview, {}).permit! 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/forms/sections_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Forms::SectionsController < Forms::ApplicationController 4 | before_action :set_section, only: %i[show edit update destroy] 5 | 6 | # GET /forms/1/sections 7 | def index 8 | @sections = @form.sections.all 9 | end 10 | 11 | # GET /forms/sections/new 12 | def new 13 | @section = @form.sections.build 14 | end 15 | 16 | # GET /forms/1/sections/1/edit 17 | def edit; end 18 | 19 | # POST /forms/1/sections 20 | def create 21 | @section = @form.sections.build(section_params) 22 | 23 | if @section.save 24 | redirect_to form_sections_url(@form), notice: "Section was successfully created." 25 | else 26 | render :new 27 | end 28 | end 29 | 30 | # PATCH/PUT /forms/1/sections/1 31 | def update 32 | if @section.update(section_params) 33 | redirect_to form_sections_url(@form), notice: "Section was successfully updated." 34 | else 35 | render :edit 36 | end 37 | end 38 | 39 | # DELETE /forms/1/sections/1 40 | def destroy 41 | @section.destroy 42 | redirect_to form_sections_url(@form), notice: "Section was successfully destroyed." 43 | end 44 | 45 | private 46 | 47 | # Use callbacks to share common setup or constraints between actions. 48 | def set_section 49 | @section = @form.sections.find(params[:id]) 50 | end 51 | 52 | # Only allow a trusted parameter "white list" through. 53 | def section_params 54 | params.fetch(:section, {}).permit(:title, :headless) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/forms_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class FormsController < ApplicationController 4 | layout "application" 5 | 6 | before_action :set_form, only: %i[edit update destroy] 7 | 8 | # GET /forms 9 | def index 10 | @forms = Form.all 11 | end 12 | 13 | # GET /forms/new 14 | def new 15 | @form = Form.new 16 | end 17 | 18 | # GET /forms/1/edit 19 | def edit; end 20 | 21 | # POST /forms 22 | def create 23 | @form = Form.new(form_params) 24 | 25 | if @form.save 26 | redirect_to form_fields_url(@form), notice: "Form was successfully created." 27 | else 28 | render :new 29 | end 30 | end 31 | 32 | # PATCH/PUT /forms/1 33 | def update 34 | if @form.update(form_params) 35 | redirect_to form_fields_url(@form), notice: "Form was successfully updated." 36 | else 37 | render :edit 38 | end 39 | end 40 | 41 | # DELETE /forms/1 42 | def destroy 43 | @form.destroy 44 | redirect_to forms_url, notice: "Form was successfully destroyed." 45 | end 46 | 47 | def random 48 | @form = Form.create_random_form! 49 | 50 | redirect_to form_fields_url(@form), notice: "Form was successfully generated." 51 | end 52 | 53 | private 54 | 55 | # Use callbacks to share common setup or constraints between actions. 56 | def set_form 57 | @form = Form.find(params[:id]) 58 | end 59 | 60 | # Only allow a trusted parameter "white list" through. 61 | def form_params 62 | params.fetch(:form, {}).permit(:title, :description) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/nested_forms/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NestedForms::ApplicationController < ApplicationController 4 | before_action :set_nested_form 5 | 6 | protected 7 | 8 | # Use callbacks to share common setup or constraints between actions. 9 | def set_nested_form 10 | @nested_form = NestedForm.find(params[:nested_form_id]) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/nested_forms/fields_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NestedForms::FieldsController < NestedForms::ApplicationController 4 | before_action :set_field, only: %i[show edit update destroy] 5 | 6 | def index 7 | @fields = @nested_form.fields.includes(:section).all 8 | end 9 | 10 | def new 11 | @field = @nested_form.fields.build 12 | end 13 | 14 | def edit; end 15 | 16 | def create 17 | @field = @nested_form.fields.build(field_params) 18 | 19 | if @field.save 20 | redirect_to nested_form_fields_url(@nested_form), notice: "Field was successfully created." 21 | else 22 | render :new 23 | end 24 | end 25 | 26 | def update 27 | if @field.update(field_params) 28 | redirect_to nested_form_fields_url(@nested_form), notice: "Field was successfully updated." 29 | else 30 | render :edit 31 | end 32 | end 33 | 34 | def destroy 35 | @field.destroy 36 | redirect_to nested_form_fields_url(@nested_form), notice: "Field was successfully destroyed." 37 | end 38 | 39 | private 40 | 41 | # Use callbacks to share common setup or constraints between actions. 42 | def set_field 43 | @field = @nested_form.fields.find(params[:id]) 44 | end 45 | 46 | # Only allow a trusted parameter "white list" through. 47 | def field_params 48 | params.fetch(:field, {}).permit(:name, :label, :hint, :accessibility, :type) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/time_zones_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TimeZonesController < ApplicationController 4 | def update 5 | @time_zone = ActiveSupport::TimeZone[params[:time_zone]] 6 | return render :not_found unless @time_zone 7 | 8 | session[:current_time_zone] = params[:time_zone] 9 | 10 | redirect_back fallback_location: root_url 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | def options_for_enum_select(klass, attribute, selected = nil) 5 | container = klass.public_send(attribute.to_s.pluralize).map do |k, v| 6 | v ||= k 7 | [klass.human_enum_value(attribute, k), v] 8 | end 9 | 10 | options_for_select(container, selected) 11 | end 12 | 13 | def present(model, options = {}) 14 | klass = options.delete(:presenter_class) || "#{model.class}Presenter".constantize 15 | presenter = klass.new(model, self, options) 16 | 17 | yield(presenter) if block_given? 18 | 19 | presenter 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/fields_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FieldsHelper 4 | def options_for_field_types(selected: nil) 5 | options_for_select(Field.descendants.map { |klass| [klass.model_name.human, klass.to_s] }, selected) 6 | end 7 | 8 | def options_for_data_source_types(selected: nil) 9 | options_for_select(DataSource.descendants.map { |klass| [klass.model_name.human, klass.to_s] }, selected) 10 | end 11 | 12 | def field_label(form, field_name:) 13 | field_name = field_name.to_s.split(".").first.to_sym 14 | 15 | form.fields.select do |field| 16 | field.name == field_name 17 | end.first&.label 18 | end 19 | 20 | def fields_path 21 | form = @field.form 22 | 23 | case form 24 | when Form 25 | form_fields_path(form) 26 | when NestedForm 27 | nested_form_fields_path(form) 28 | else 29 | raise "Unknown form: #{form.class}" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/forms_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FormsHelper 4 | def smart_form_fields_path(form) 5 | case form 6 | when Form 7 | form_fields_path(form) 8 | when NestedForm 9 | nested_form_fields_path(form) 10 | else 11 | raise "Unknown form: #{form.class}" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/lib/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/app/lib/.keep -------------------------------------------------------------------------------- /test/dummy/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | 6 | include ActsAsDefaultValue 7 | include EnumAttributeLocalizable 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/choice.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Choice < ApplicationRecord 4 | belongs_to :field 5 | 6 | validates :label, 7 | presence: true 8 | 9 | acts_as_list scope: [:field_id] 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/enum_attribute_localizable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EnumAttributeLocalizable 4 | extend ActiveSupport::Concern 5 | 6 | module ClassMethods 7 | def human_enum_value(attribute, value, options = {}) 8 | parts = attribute.to_s.split(".") 9 | attribute = parts.pop.pluralize 10 | attributes_scope = "#{i18n_scope}.attributes" 11 | 12 | if parts.any? 13 | namespace = parts.join("/") 14 | defaults = lookup_ancestors.map do |klass| 15 | :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}.#{value}" 16 | end 17 | defaults << :"#{attributes_scope}.#{namespace}.#{attribute}.#{value}" 18 | else 19 | defaults = lookup_ancestors.map do |klass| 20 | :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}.#{value}" 21 | end 22 | end 23 | 24 | defaults << :"attributes.#{attribute}.#{value}" 25 | defaults << options.delete(:default) if options[:default] 26 | defaults << value.to_s.humanize 27 | 28 | options[:default] = defaults 29 | I18n.translate(defaults.shift, **options) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/fake.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Fake 5 | extend ActiveSupport::Concern 6 | 7 | module ClassMethods 8 | def configure_fake_options_to(_field); end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Helper 5 | extend ActiveSupport::Concern 6 | 7 | def options_configurable? 8 | options.is_a?(FieldOptions) && options.attributes.any? 9 | end 10 | 11 | def validations_configurable? 12 | validations.is_a?(FieldOptions) && validations.attributes.any? 13 | end 14 | 15 | def attached_choices? 16 | false 17 | end 18 | 19 | def attached_data_source? 20 | false 21 | end 22 | 23 | def attached_nested_form? 24 | false 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/options/data_source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Options::DataSource 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | attribute :data_source_type, :string, default: DataSources::Empty.to_s 9 | 10 | ::DataSource.descendants.each do |klass| 11 | key = :"#{klass.type_key}_data_source" 12 | embeds_one key, class_name: klass.to_s 13 | accepts_nested_attributes_for key 14 | end 15 | 16 | validates :data_source_type, 17 | inclusion: { in: ->(_) { ::DataSource.descendants.map(&:to_s) } }, 18 | allow_blank: false 19 | end 20 | 21 | def data_source 22 | send(:"#{data_source_class.type_key}_data_source") || send(:"build_#{data_source_class.type_key}_data_source") 23 | end 24 | 25 | def data_source_class 26 | data_source_type.safe_constantize || ::DataSources::Empty 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/validations/acceptance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Validations::Acceptance 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | attribute :acceptance, :boolean, default: false 9 | end 10 | 11 | def interpret_to(model, field_name, _accessibility, _options = {}) 12 | super 13 | return unless acceptance 14 | 15 | model.validates field_name, acceptance: true 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/validations/confirmation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Validations::Confirmation 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | attribute :confirmation, :boolean, default: false 9 | end 10 | 11 | def interpret_to(model, field_name, _accessibility, _options = {}) 12 | super 13 | return unless confirmation 14 | 15 | model.validates field_name, confirmation: true 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/validations/exclusion.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Validations::Exclusion 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | embeds_one :exclusion, class_name: "Fields::Validations::Exclusion::ExclusionOptions" 9 | accepts_nested_attributes_for :exclusion 10 | 11 | after_initialize do 12 | build_exclusion unless exclusion 13 | end 14 | end 15 | 16 | def interpret_to(model, field_name, accessibility, options = {}) 17 | super 18 | exclusion&.interpret_to model, field_name, accessibility, options 19 | end 20 | 21 | class ExclusionOptions < FieldOptions 22 | attribute :message, :string, default: "" 23 | attribute :in, :string, default: [], array: true 24 | 25 | def interpret_to(model, field_name, _accessibility, _options = {}) 26 | return if self.in.empty? 27 | 28 | options = { in: self.in } 29 | options[:message] = message if message.present? 30 | 31 | model.validates field_name, exclusion: options, allow_blank: true 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/validations/format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Validations::Format 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | embeds_one :format, class_name: "Fields::Validations::Format::FormatOptions" 9 | accepts_nested_attributes_for :format 10 | 11 | after_initialize do 12 | build_format unless format 13 | end 14 | end 15 | 16 | def interpret_to(model, field_name, accessibility, options = {}) 17 | super 18 | format&.interpret_to model, field_name, accessibility, options 19 | end 20 | 21 | class FormatOptions < FieldOptions 22 | attribute :with, :string, default: "" 23 | attribute :message, :string, default: "" 24 | 25 | validate do 26 | Regexp.new(with) if with.present? 27 | rescue RegexpError 28 | errors.add :with, :invalid 29 | end 30 | 31 | def interpret_to(model, field_name, _accessibility, _options = {}) 32 | return if with.blank? 33 | 34 | with = Regexp.new(self.with) 35 | 36 | options = { with: with } 37 | options[:message] = message if message.present? 38 | 39 | model.validates field_name, format: options, allow_blank: true 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/validations/inclusion.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Validations::Inclusion 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | embeds_one :inclusion, class_name: "Fields::Validations::Inclusion::InclusionOptions" 9 | accepts_nested_attributes_for :inclusion 10 | 11 | after_initialize do 12 | build_inclusion unless inclusion 13 | end 14 | end 15 | 16 | def interpret_to(model, field_name, accessibility, options = {}) 17 | super 18 | inclusion&.interpret_to model, field_name, accessibility, options 19 | end 20 | 21 | class InclusionOptions < FieldOptions 22 | attribute :message, :string, default: "" 23 | attribute :in, :string, default: [], array: true 24 | 25 | def interpret_to(model, field_name, _accessibility, _options = {}) 26 | return if self.in.empty? 27 | 28 | options = { in: self.in } 29 | options[:message] = message if message.present? 30 | 31 | model.validates field_name, inclusion: options, allow_blank: true 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/fields/validations/presence.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Validations::Presence 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | attribute :presence, :boolean, default: false 9 | end 10 | 11 | def interpret_to(model, field_name, _accessibility, _options = {}) 12 | super 13 | return unless presence 14 | 15 | model.validates field_name, presence: true 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/dummy/app/models/data_source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DataSource < FieldOptions 4 | def type_key 5 | self.class.type_key 6 | end 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | def foreign_field_name_suffix 13 | "_id" 14 | end 15 | 16 | def foreign_field_name(field_name) 17 | "#{field_name}#{foreign_field_name_suffix}" 18 | end 19 | 20 | def value_method 21 | :id 22 | end 23 | 24 | def text_method 25 | raise NotImplementedError 26 | end 27 | 28 | def value_for_preview_method 29 | raise NotImplementedError 30 | end 31 | 32 | def scoped_condition 33 | serializable_hash 34 | end 35 | 36 | def scoped_records(**additional_condition) 37 | condition = scoped_condition.merge(additional_condition) 38 | self.class.scoped_records(condition) 39 | end 40 | 41 | def interpret_to(model, field_name, accessibility, _options = {}); end 42 | 43 | class << self 44 | def type_key 45 | model_name.name.split("::").last.underscore 46 | end 47 | 48 | def scoped_records(_condition) 49 | raise NotImplementedError 50 | end 51 | end 52 | end 53 | 54 | require_dependency "data_sources" 55 | -------------------------------------------------------------------------------- /test/dummy/app/models/data_sources.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DataSources 4 | %w[empty dictionary].each do |type| 5 | require_dependency "data_sources/#{type}" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/data_sources/dictionary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DataSources 4 | class Dictionary < DataSource 5 | def text_method 6 | :value 7 | end 8 | 9 | def value_for_preview_method 10 | :value 11 | end 12 | 13 | attribute :scope, :string, default: "" 14 | 15 | validates :scope, 16 | format: { with: ::Dictionary::SCOPE_REGEX }, 17 | allow_blank: true 18 | 19 | class << self 20 | def scoped_records(condition) 21 | return ::Dictionary.none if condition.blank? 22 | 23 | ::Dictionary.where(condition) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/models/data_sources/empty.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DataSources 4 | class Empty < DataSource 5 | def foreign_field_name_suffix 6 | "" 7 | end 8 | 9 | def foreign_field_name(field_name) 10 | field_name 11 | end 12 | 13 | def text_method 14 | nil 15 | end 16 | 17 | def interpret_to(_model, _field_name, _accessibility, _options = {}); end 18 | 19 | class << self 20 | def scoped_records(*) 21 | ApplicationRecord.none 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy/app/models/dictionary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Dictionary < ApplicationRecord 4 | SCOPE_REGEX = /\A([a-z_][a-z_0-9]*\.)*[a-z_][a-z_0-9]*\z/.freeze 5 | 6 | attribute :value, :string 7 | validates :value, 8 | presence: true, uniqueness: { scope: :scope } 9 | 10 | validates :scope, 11 | presence: true, 12 | format: { with: SCOPE_REGEX } 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | %w[ 5 | text boolean decimal integer 6 | date datetime 7 | choice multiple_choice 8 | select multiple_select 9 | integer_range decimal_range date_range datetime_range 10 | nested_form multiple_nested_form 11 | attachment multiple_attachment 12 | resource_select multiple_resource_select 13 | resource multiple_resource 14 | ].each do |type| 15 | require_dependency "fields/#{type}_field" 16 | end 17 | 18 | MAP = Hash[*Field.descendants.map { |f| [f.type_key, f] }.flatten] 19 | end 20 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/attachment_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class AttachmentField < Field 5 | serialize :validations, Validations::AttachmentField 6 | serialize :options, NonConfigurable 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | def interpret_to(model, overrides: {}) 13 | check_model_validity!(model) 14 | 15 | accessibility = overrides.fetch(:accessibility, self.accessibility) 16 | return model if accessibility == :hidden 17 | 18 | model.attribute name, stored_type 19 | model.has_one_attached name 20 | model.attr_readonly name if accessibility == :readonly 21 | 22 | interpret_validations_to model, accessibility, overrides 23 | interpret_extra_to model, accessibility, overrides 24 | 25 | model 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/boolean_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class BooleanField < Field 5 | serialize :validations, Validations::BooleanField 6 | serialize :options, NonConfigurable 7 | 8 | def stored_type 9 | :boolean 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/choice_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class ChoiceField < Field 5 | serialize :validations, Validations::ChoiceField 6 | serialize :options, NonConfigurable 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | def attached_choices? 13 | true 14 | end 15 | 16 | protected 17 | 18 | def interpret_extra_to(model, accessibility, overrides = {}) 19 | super 20 | return if accessibility != :read_and_write 21 | 22 | choice_ids = choices.pluck(:id) 23 | return if choice_ids.empty? 24 | 25 | model.validates name, inclusion: { in: choice_ids }, allow_blank: true 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/date_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DateField < Field 5 | serialize :validations, Validations::DateField 6 | serialize :options, Options::DateField 7 | 8 | def stored_type 9 | :datetime 10 | end 11 | 12 | protected 13 | 14 | def interpret_extra_to(model, accessibility, overrides = {}) 15 | super 16 | 17 | model.class_eval <<-CODE, __FILE__, __LINE__ + 1 18 | def #{name}=(val) 19 | super(val.try(:in_time_zone)&.utc) 20 | end 21 | CODE 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/date_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DateRangeField < Field 5 | serialize :validations, Validations::DateRangeField 6 | serialize :options, Options::DateRangeField 7 | 8 | def interpret_to(model, overrides: {}) 9 | check_model_validity!(model) 10 | 11 | accessibility = overrides.fetch(:accessibility, self.accessibility) 12 | return model if accessibility == :hidden 13 | 14 | nested_model = Class.new(::Fields::Embeds::DateRange) 15 | 16 | model.nested_models[name] = nested_model 17 | 18 | model.embeds_one name, anonymous_class: nested_model, validate: true 19 | model.accepts_nested_attributes_for name, reject_if: :all_blank 20 | 21 | interpret_validations_to model, accessibility, overrides 22 | interpret_extra_to model, accessibility, overrides 23 | 24 | model 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/datetime_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DatetimeField < Field 5 | serialize :validations, Validations::DatetimeField 6 | serialize :options, Options::DatetimeField 7 | 8 | def stored_type 9 | :datetime 10 | end 11 | 12 | protected 13 | 14 | def interpret_extra_to(model, accessibility, overrides = {}) 15 | super 16 | 17 | model.class_eval <<-CODE, __FILE__, __LINE__ + 1 18 | def #{name}=(val) 19 | super(val.try(:in_time_zone)&.utc) 20 | end 21 | CODE 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/datetime_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DatetimeRangeField < Field 5 | serialize :validations, Validations::DatetimeRangeField 6 | serialize :options, Options::DatetimeRangeField 7 | 8 | def interpret_to(model, overrides: {}) 9 | check_model_validity!(model) 10 | 11 | accessibility = overrides.fetch(:accessibility, self.accessibility) 12 | return model if accessibility == :hidden 13 | 14 | nested_model = Class.new(::Fields::Embeds::DatetimeRange) 15 | 16 | model.nested_models[name] = nested_model 17 | 18 | model.embeds_one name, anonymous_class: nested_model, validate: true 19 | model.accepts_nested_attributes_for name, reject_if: :all_blank 20 | 21 | interpret_validations_to model, accessibility, overrides 22 | interpret_extra_to model, accessibility, overrides 23 | 24 | model 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/decimal_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DecimalField < Field 5 | serialize :validations, Validations::DecimalField 6 | serialize :options, Options::DecimalField 7 | 8 | def stored_type 9 | :decimal 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/decimal_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DecimalRangeField < Field 5 | serialize :validations, Validations::DecimalRangeField 6 | serialize :options, Options::DecimalRangeField 7 | 8 | def interpret_to(model, overrides: {}) 9 | check_model_validity!(model) 10 | 11 | accessibility = overrides.fetch(:accessibility, self.accessibility) 12 | return model if accessibility == :hidden 13 | 14 | nested_model = Class.new(::Fields::Embeds::DecimalRange) 15 | 16 | model.nested_models[name] = nested_model 17 | 18 | model.embeds_one name, anonymous_class: nested_model, validate: true 19 | model.accepts_nested_attributes_for name, reject_if: :all_blank 20 | 21 | interpret_validations_to model, accessibility, overrides 22 | interpret_extra_to model, accessibility, overrides 23 | 24 | model 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/embeds/date_range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Embeds 5 | class DateRange < VirtualModel 6 | attribute :begin, :datetime 7 | attribute :end, :datetime 8 | 9 | validates :begin, 10 | presence: true 11 | 12 | validates :end, 13 | timeliness: { 14 | after: :begin, 15 | type: :date 16 | }, 17 | allow_blank: true, 18 | if: -> { read_attribute(:begin).present? } 19 | 20 | def begin=(val) 21 | super(val.try(:in_time_zone)&.utc) 22 | end 23 | 24 | def end=(val) 25 | super(val.try(:in_time_zone)&.utc) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/embeds/datetime_range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Embeds 5 | class DatetimeRange < VirtualModel 6 | attribute :begin, :datetime 7 | attribute :end, :datetime 8 | 9 | validates :begin, 10 | presence: true 11 | 12 | validates :end, 13 | timeliness: { 14 | after: :begin, 15 | type: :datetime 16 | }, 17 | allow_blank: true, 18 | if: -> { read_attribute(:begin).present? } 19 | 20 | def begin=(val) 21 | super(val.try(:in_time_zone)&.utc) 22 | end 23 | 24 | def end=(val) 25 | super(val.try(:in_time_zone)&.utc) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/embeds/decimal_range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Embeds 5 | class DecimalRange < VirtualModel 6 | attribute :begin, :decimal 7 | attribute :end, :decimal 8 | 9 | validates :begin, :end, 10 | presence: true, 11 | numericality: { only_integer: false } 12 | 13 | validates :end, 14 | numericality: { 15 | greater_than: :begin 16 | }, 17 | allow_blank: true, 18 | if: -> { read_attribute(:begin).present? } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/embeds/integer_range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module Embeds 5 | class IntegerRange < VirtualModel 6 | attribute :begin, :integer 7 | attribute :end, :integer 8 | 9 | validates :begin, :end, 10 | presence: true, 11 | numericality: { only_integer: true } 12 | 13 | validates :end, 14 | numericality: { 15 | greater_than: :begin 16 | }, 17 | allow_blank: true, 18 | if: -> { read_attribute(:begin).present? } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/integer_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class IntegerField < Field 5 | serialize :validations, Validations::IntegerField 6 | serialize :options, Options::IntegerField 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | protected 13 | 14 | def interpret_extra_to(model, accessibility, _overrides = {}) 15 | return if accessibility != :read_and_write 16 | 17 | model.validates name, numericality: { only_integer: true }, allow_blank: true 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/integer_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class IntegerRangeField < Field 5 | serialize :validations, Validations::IntegerRangeField 6 | serialize :options, Options::IntegerRangeField 7 | 8 | def interpret_to(model, overrides: {}) 9 | check_model_validity!(model) 10 | 11 | accessibility = overrides.fetch(:accessibility, self.accessibility) 12 | return model if accessibility == :hidden 13 | 14 | nested_model = Class.new(::Fields::Embeds::IntegerRange) 15 | 16 | model.nested_models[name] = nested_model 17 | 18 | model.embeds_one name, anonymous_class: nested_model, validate: true 19 | model.accepts_nested_attributes_for name, reject_if: :all_blank 20 | 21 | interpret_validations_to model, accessibility, overrides 22 | interpret_extra_to model, accessibility, overrides 23 | 24 | model 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/multiple_attachment_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleAttachmentField < Field 5 | serialize :validations, Validations::MultipleAttachmentField 6 | serialize :options, NonConfigurable 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | def interpret_to(model, overrides: {}) 13 | check_model_validity!(model) 14 | 15 | accessibility = overrides.fetch(:accessibility, self.accessibility) 16 | return model if accessibility == :hidden 17 | 18 | model.attribute name, stored_type, default: [], array_without_blank: true 19 | model.has_many_attached name 20 | model.attr_readonly name if accessibility == :readonly 21 | 22 | interpret_validations_to model, accessibility, overrides 23 | interpret_extra_to model, accessibility, overrides 24 | 25 | model 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/multiple_choice_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleChoiceField < Field 5 | serialize :validations, Validations::MultipleChoiceField 6 | serialize :options, NonConfigurable 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | def attached_choices? 13 | true 14 | end 15 | 16 | def interpret_to(model, overrides: {}) 17 | check_model_validity!(model) 18 | 19 | accessibility = overrides.fetch(:accessibility, self.accessibility) 20 | return model if accessibility == :hidden 21 | 22 | model.attribute name, stored_type, default: [], array_without_blank: true 23 | model.attr_readonly name if accessibility == :readonly 24 | 25 | interpret_validations_to model, accessibility, overrides 26 | interpret_extra_to model, accessibility, overrides 27 | 28 | model 29 | end 30 | 31 | protected 32 | 33 | def interpret_extra_to(model, accessibility, overrides = {}) 34 | super 35 | return if accessibility != :read_and_write 36 | 37 | choice_ids = choices.pluck(:id) 38 | return if choice_ids.empty? 39 | 40 | model.validates name, subset: { in: choice_ids }, allow_blank: true 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/multiple_nested_form_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleNestedFormField < Field 5 | after_create do 6 | build_nested_form.save! 7 | end 8 | 9 | serialize :validations, Validations::MultipleNestedFormField 10 | serialize :options, NonConfigurable 11 | 12 | def attached_nested_form? 13 | true 14 | end 15 | 16 | def interpret_to(model, overrides: {}) 17 | check_model_validity!(model) 18 | 19 | accessibility = overrides.fetch(:accessibility, self.accessibility) 20 | return model if accessibility == :hidden 21 | 22 | overrides[:name] = name 23 | 24 | nested_model = nested_form.to_virtual_model(overrides: { _global: { accessibility: accessibility } }) 25 | 26 | model.nested_models[name] = nested_model 27 | 28 | model.embeds_many name, anonymous_class: nested_model, validate: true 29 | model.accepts_nested_attributes_for name, reject_if: :all_blank 30 | model.attr_readonly name if accessibility == :readonly 31 | 32 | interpret_validations_to model, accessibility, overrides 33 | interpret_extra_to model, accessibility, overrides 34 | 35 | model 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/multiple_resource_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleResourceField < Field 5 | serialize :validations, Validations::MultipleResourceField 6 | serialize :options, Options::MultipleResourceField 7 | 8 | def stored_type 9 | :string 10 | end 11 | 12 | delegate :data_source, to: :options 13 | 14 | def collection 15 | data_source.scoped_records 16 | end 17 | 18 | def attached_data_source? 19 | true 20 | end 21 | 22 | def interpret_to(model, overrides: {}) 23 | check_model_validity!(model) 24 | 25 | accessibility = overrides.fetch(:accessibility, self.accessibility) 26 | return model if accessibility == :hidden 27 | 28 | model.attribute name, stored_type, default: [], array_without_blank: true 29 | model.attr_readonly name if accessibility == :readonly 30 | 31 | interpret_validations_to model, accessibility, overrides 32 | interpret_extra_to model, accessibility, overrides 33 | 34 | model 35 | end 36 | 37 | protected 38 | 39 | def interpret_extra_to(model, accessibility, overrides = {}) 40 | super 41 | return if accessibility != :read_and_write 42 | 43 | # TODO: performance 44 | # model.validates name, subset: {in: collection}, allow_blank: true 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/multiple_resource_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleResourceSelectField < Field 5 | serialize :validations, Validations::MultipleResourceSelectField 6 | serialize :options, Options::MultipleResourceSelectField 7 | 8 | def stored_type 9 | :string 10 | end 11 | 12 | delegate :data_source, to: :options 13 | 14 | def collection 15 | data_source.scoped_records 16 | end 17 | 18 | def attached_data_source? 19 | true 20 | end 21 | 22 | def interpret_to(model, overrides: {}) 23 | check_model_validity!(model) 24 | 25 | accessibility = overrides.fetch(:accessibility, self.accessibility) 26 | return model if accessibility == :hidden 27 | 28 | model.attribute name, stored_type, default: [], array_without_blank: true 29 | model.attr_readonly name if accessibility == :readonly 30 | 31 | interpret_validations_to model, accessibility, overrides 32 | interpret_extra_to model, accessibility, overrides 33 | 34 | model 35 | end 36 | 37 | protected 38 | 39 | def interpret_extra_to(model, accessibility, overrides = {}) 40 | super 41 | return if accessibility != :read_and_write || !options.strict_select 42 | 43 | # TODO: performance 44 | # model.validates name, subset: {in: collection}, allow_blank: true 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/multiple_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleSelectField < Field 5 | serialize :validations, Validations::MultipleSelectField 6 | serialize :options, Options::MultipleSelectField 7 | 8 | def stored_type 9 | :string 10 | end 11 | 12 | def attached_choices? 13 | true 14 | end 15 | 16 | def interpret_to(model, overrides: {}) 17 | check_model_validity!(model) 18 | 19 | accessibility = overrides.fetch(:accessibility, self.accessibility) 20 | return model if accessibility == :hidden 21 | 22 | model.attribute name, stored_type, default: [], array_without_blank: true 23 | model.attr_readonly name if accessibility == :readonly 24 | 25 | interpret_validations_to model, accessibility, overrides 26 | interpret_extra_to model, accessibility, overrides 27 | 28 | model 29 | end 30 | 31 | protected 32 | 33 | def interpret_extra_to(model, accessibility, overrides = {}) 34 | super 35 | return if accessibility != :read_and_write || !options.strict_select 36 | 37 | model.validates name, subset: { in: choices.pluck(:label) }, allow_blank: true 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/nested_form_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class NestedFormField < Field 5 | after_create do 6 | build_nested_form.save! 7 | end 8 | 9 | serialize :validations, Validations::NestedFormField 10 | serialize :options, NonConfigurable 11 | 12 | def attached_nested_form? 13 | true 14 | end 15 | 16 | def interpret_to(model, overrides: {}) 17 | check_model_validity!(model) 18 | 19 | accessibility = overrides.fetch(:accessibility, self.accessibility) 20 | return model if accessibility == :hidden 21 | 22 | nested_model = nested_form.to_virtual_model(overrides: { _global: { accessibility: accessibility } }) 23 | 24 | model.nested_models[name] = nested_model 25 | 26 | model.embeds_one name, anonymous_class: nested_model, validate: true 27 | model.accepts_nested_attributes_for name, reject_if: :all_blank 28 | model.attr_readonly name if accessibility == :readonly 29 | 30 | interpret_validations_to model, accessibility, overrides 31 | interpret_extra_to model, accessibility, overrides 32 | 33 | model 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/decimal_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class DecimalField < FieldOptions 5 | attribute :step, :decimal, default: 0.01 6 | 7 | validates :step, 8 | numericality: { 9 | greater_than_or_equal_to: 0.0 10 | } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/integer_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class IntegerField < FieldOptions 5 | attribute :step, :integer, default: 0 6 | 7 | validates :step, 8 | numericality: { 9 | only_integer: true, 10 | greater_than_or_equal_to: 0 11 | } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/multiple_resource_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class MultipleResourceField < FieldOptions 5 | include Fields::Options::DataSource 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/multiple_resource_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class MultipleResourceSelectField < FieldOptions 5 | include Fields::Options::DataSource 6 | 7 | attribute :strict_select, :boolean, default: true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/multiple_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class MultipleSelectField < FieldOptions 5 | attribute :strict_select, :boolean, default: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/resource_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class ResourceField < FieldOptions 5 | include Fields::Options::DataSource 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/resource_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class ResourceSelectField < FieldOptions 5 | include Fields::Options::DataSource 6 | 7 | attribute :strict_select, :boolean, default: true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class SelectField < FieldOptions 5 | attribute :strict_select, :boolean, default: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/options/text_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Options 4 | class TextField < FieldOptions 5 | attribute :multiline, :boolean, default: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/resource_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class ResourceField < Field 5 | serialize :options, Options::ResourceField 6 | serialize :validations, Validations::ResourceField 7 | 8 | def stored_type 9 | :integer 10 | end 11 | 12 | delegate :data_source, to: :options 13 | 14 | def collection 15 | data_source.scoped_records 16 | end 17 | 18 | def attached_data_source? 19 | true 20 | end 21 | 22 | protected 23 | 24 | def interpret_extra_to(model, accessibility, overrides = {}) 25 | super 26 | return if accessibility != :read_and_write 27 | 28 | # TODO: performance 29 | # model.validates name, inclusion: {in: collection}, allow_blank: true 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/resource_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class ResourceSelectField < Field 5 | serialize :options, Options::ResourceSelectField 6 | serialize :validations, Validations::ResourceSelectField 7 | 8 | def stored_type 9 | :string 10 | end 11 | 12 | delegate :data_source, to: :options 13 | 14 | def collection 15 | data_source.scoped_records 16 | end 17 | 18 | def attached_data_source? 19 | true 20 | end 21 | 22 | protected 23 | 24 | def interpret_extra_to(model, accessibility, overrides = {}) 25 | super 26 | return if accessibility != :read_and_write || !options.strict_select 27 | 28 | # TODO: performance 29 | # model.validates name, inclusion: {in: collection}, allow_blank: true 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class SelectField < Field 5 | serialize :validations, Validations::SelectField 6 | serialize :options, Options::SelectField 7 | 8 | def stored_type 9 | :string 10 | end 11 | 12 | def attached_choices? 13 | true 14 | end 15 | 16 | protected 17 | 18 | def interpret_extra_to(model, accessibility, overrides = {}) 19 | super 20 | return if accessibility != :read_and_write || !options.strict_select 21 | 22 | model.validates name, inclusion: { in: choices.pluck(:label) }, allow_blank: true 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/text_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class TextField < Field 5 | serialize :validations, Validations::TextField 6 | serialize :options, Options::TextField 7 | 8 | def stored_type 9 | :string 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/attachment_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class AttachmentField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/boolean_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class BooleanField < FieldOptions 5 | prepend Fields::Validations::Acceptance 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/choice_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class ChoiceField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/date_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class DateField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/date_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class DateRangeField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/datetime_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class DatetimeField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/datetime_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class DatetimeRangeField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/decimal_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class DecimalField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Numericality 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/decimal_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class DecimalRangeField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/integer_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class IntegerField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Numericality 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/integer_range_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class IntegerRangeField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/multiple_attachment_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class MultipleAttachmentField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/multiple_choice_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class MultipleChoiceField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/multiple_nested_form_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class MultipleNestedFormField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/multiple_resource_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class MultipleResourceField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/multiple_resource_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class MultipleResourceSelectField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/multiple_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class MultipleSelectField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/nested_form_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class NestedFormField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/resource_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class ResourceField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/resource_select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class ResourceSelectField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/select_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class SelectField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/fields/validations/text_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields::Validations 4 | class TextField < FieldOptions 5 | prepend Fields::Validations::Presence 6 | prepend Fields::Validations::Length 7 | prepend Fields::Validations::Format 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/models/form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Form < MetalForm 4 | has_many :sections, -> { order(position: :asc) }, dependent: :destroy 5 | 6 | validates :title, 7 | presence: true 8 | 9 | after_create :auto_create_default_section 10 | 11 | private 12 | 13 | def auto_create_default_section 14 | sections.create! title: I18n.t("defaults.section.title"), headless: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/dummy/app/models/metal_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MetalForm < ApplicationRecord 4 | include FormCore::Concerns::Models::Form 5 | 6 | self.table_name = "forms" 7 | 8 | has_many :fields, foreign_key: "form_id", dependent: :destroy 9 | 10 | include Forms::Fake 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/models/nested_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NestedForm < MetalForm 4 | belongs_to :attachable, polymorphic: true, touch: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/non_configurable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class NonConfigurable < FieldOptions 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/section.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Section < ApplicationRecord 4 | belongs_to :form, touch: true 5 | 6 | has_many :fields, -> { order(position: :asc) }, dependent: :nullify 7 | 8 | acts_as_list scope: [:form_id] 9 | 10 | validates :title, 11 | presence: true 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/models/virtual_model.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VirtualModel < FormCore::VirtualModel 4 | include FormCore::ActiveStorageBridge::Attached::Macros 5 | include FormCore::ActsAsDefaultValue 6 | 7 | include EnumAttributeLocalizable 8 | 9 | # self.inheritance_column = nil 10 | 11 | def persisted? 12 | false 13 | end 14 | 15 | class << self 16 | def nested_models 17 | @nested_models ||= {} 18 | end 19 | 20 | def attr_readonly?(attr_name) 21 | readonly_attributes.include? attr_name.to_s 22 | end 23 | 24 | def metadata 25 | @metadata ||= {} 26 | end 27 | 28 | def _embeds_reflections 29 | _reflections.select { |_, v| v.is_a? ActiveEntity::Reflection::EmbeddedAssociationReflection } 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/dummy/app/overrides/action_view/helpers/tags/date_field_override.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActionView::Helpers::Tags::DateField.class_eval do 4 | def format_date(value) 5 | value&.in_time_zone&.strftime("%Y-%m-%d") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/overrides/action_view/helpers/tags/datetime_local_field_override.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActionView::Helpers::Tags::DatetimeLocalField.class_eval do 4 | def format_date(value) 5 | value&.in_time_zone&.strftime("%Y-%m-%dT%T") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/application_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationPresenter < SimpleDelegator 4 | def initialize(model, view, options = {}) 5 | super(model) 6 | 7 | @model = model 8 | @view = view 9 | @options = options 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/concerns/fields/presenter_for_number_field.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | module PresenterForNumberField 5 | extend ActiveSupport::Concern 6 | 7 | def min 8 | return if @model.validations.numericality.lower_bound_check_disabled? 9 | 10 | min = @model.validations.numericality.lower_bound_value 11 | integer_only? ? min.to_i : min 12 | end 13 | 14 | def max 15 | return if @model.validations.numericality.upper_bound_check_disabled? 16 | 17 | max = @model.validations.numericality.upper_bound_value 18 | integer_only? ? max.to_i : max 19 | end 20 | 21 | def step 22 | step = @model.options.step 23 | return if step.zero? 24 | 25 | integer_only? ? step.to_i : step 26 | end 27 | 28 | def integer_only? 29 | false 30 | end 31 | 32 | def to_builder_options 33 | { min: min, max: max, step: step, required: required? }.reject { |_, v| v.blank? } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/attachment_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class AttachmentFieldPresenter < FieldPresenter 5 | def value_for_preview 6 | id = value 7 | return if id.blank? 8 | 9 | blob = ActiveStorage::Blob.find_by id: id 10 | return unless blob 11 | 12 | @view.link_to blob.filename, @view.rails_blob_path(blob, disposition: "attachment") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/boolean_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class BooleanFieldPresenter < FieldPresenter 5 | def required 6 | @model.validations&.acceptance 7 | end 8 | 9 | def value_for_preview 10 | super ? I18n.t("values.true") : I18n.t("values.false") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/choice_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class ChoiceFieldPresenter < FieldPresenter 5 | def value_for_preview 6 | id = value 7 | return if id.blank? 8 | 9 | if choices.loaded? 10 | choices.target.find { |choice| choice.id == id }&.label 11 | else 12 | choices.find_by(id: id)&.label 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/composite_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class CompositeFieldPresenter < FieldPresenter 5 | def value 6 | target&.send(@model.name) 7 | end 8 | 9 | def value_for_preview 10 | target&.send(@model.name) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/decimal_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class DecimalFieldPresenter < FieldPresenter 5 | include Fields::PresenterForNumberField 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Fields::FieldPresenter < ApplicationPresenter 4 | def required 5 | @model.validations&.presence 6 | end 7 | alias required? required 8 | 9 | def target 10 | @options[:target] 11 | end 12 | 13 | def value 14 | target&.read_attribute(@model.name) 15 | end 16 | 17 | def value_for_preview 18 | target&.read_attribute(@model.name) 19 | end 20 | 21 | def access_readonly? 22 | target.class.attr_readonly?(@model.name) 23 | end 24 | 25 | def access_hidden? 26 | target.class.attribute_names.exclude?(@model.name.to_s) && target.class._reflections.keys.exclude?(@model.name.to_s) 27 | end 28 | 29 | def access_read_and_write? 30 | !access_readonly? && 31 | (target.class.attribute_names.include?(@model.name.to_s) || target.class._reflections.key?(@model.name.to_s)) 32 | end 33 | 34 | def id 35 | "form_field_#{@model.id}" 36 | end 37 | 38 | def nested_form_field? 39 | false 40 | end 41 | 42 | def multiple_nested_form? 43 | false 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/integer_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class IntegerFieldPresenter < FieldPresenter 5 | include Fields::PresenterForNumberField 6 | 7 | def integer_only? 8 | true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/multiple_attachment_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleAttachmentFieldPresenter < FieldPresenter 5 | def value_for_preview 6 | ids = value 7 | return if ids.blank? 8 | 9 | blobs = ActiveStorage::Blob.where(id: ids) 10 | return if blobs.blank? 11 | 12 | blobs.map do |blob| 13 | @view.link_to blob.filename, @view.rails_blob_path(blob, disposition: "attachment") 14 | end.join(", ").html_safe 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/multiple_choice_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleChoiceFieldPresenter < FieldPresenter 5 | def value_for_preview 6 | ids = value 7 | return if ids.blank? 8 | 9 | if choices.loaded? 10 | choices.target.select { |choice| ids.include?(choice.id) }.map(&:label) 11 | else 12 | choices.where(id: ids).map(&:label) 13 | end.join(", ") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/multiple_nested_form_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleNestedFormFieldPresenter < CompositeFieldPresenter 5 | def multiple_nested_form? 6 | true 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/multiple_resource_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleResourceFieldPresenter < FieldPresenter 5 | MAX_HARD_CODE_ITEMS_SIZE = 20 6 | 7 | def include_blank? 8 | required? 9 | end 10 | 11 | def value_for_preview 12 | return unless @model.collection 13 | return if @model.collection.none? 14 | 15 | collection 16 | .where(@model.data_source.value_method => value) 17 | .map(&@model.data_source.text_method) 18 | .join(", ") 19 | end 20 | 21 | def collection 22 | return unless @model.collection 23 | 24 | # TODO: limit 100 for performance 25 | @model.collection.limit(100) 26 | end 27 | 28 | def options_for_select 29 | return @view.options_for_select([]) unless @model.collection 30 | 31 | @view.options_from_collection_for_select( 32 | collection, @model.data_source.value_method, @model.data_source.text_method, value 33 | ) 34 | end 35 | 36 | def max_items_size 37 | size = @model.validations.length.maximum 38 | size.positive? ? size : MAX_HARD_CODE_ITEMS_SIZE 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/multiple_resource_select_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleResourceSelectFieldPresenter < FieldPresenter 5 | MAX_HARD_CODE_ITEMS_SIZE = 20 6 | 7 | def include_blank? 8 | required? 9 | end 10 | 11 | def value_for_preview 12 | super&.join(", ") 13 | end 14 | 15 | def can_custom_value? 16 | !@model.options.strict_select 17 | end 18 | 19 | def collection 20 | values = 21 | if @model.collection 22 | # TODO: limit 100 for performance 23 | @model.collection.limit(100).map(&@model.data_source.text_method) 24 | else 25 | [] 26 | end 27 | 28 | if can_custom_value? && value.present? 29 | ([value] + values).uniq 30 | else 31 | values 32 | end 33 | end 34 | 35 | def options_for_select 36 | @view.options_for_select(collection, value) 37 | end 38 | 39 | def max_items_size 40 | size = @model.validations.length.maximum 41 | size.positive? ? size : MAX_HARD_CODE_ITEMS_SIZE 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/multiple_select_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class MultipleSelectFieldPresenter < FieldPresenter 5 | MAX_HARD_CODE_ITEMS_SIZE = 20 6 | 7 | def include_blank? 8 | required? 9 | end 10 | 11 | def value_for_preview 12 | super&.join(", ") 13 | end 14 | 15 | def can_custom_value? 16 | !@model.options.strict_select 17 | end 18 | 19 | def collection 20 | collection = @model.choices.map(&:label) 21 | if can_custom_value? && value.present? 22 | (value + collection).uniq 23 | else 24 | collection 25 | end 26 | end 27 | 28 | def options_for_select 29 | @view.options_for_select(collection, value) 30 | end 31 | 32 | def max_items_size 33 | size = @model.validations.length.maximum 34 | size.positive? ? size : MAX_HARD_CODE_ITEMS_SIZE 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/nested_form_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class NestedFormFieldPresenter < CompositeFieldPresenter 5 | def nested_form_field? 6 | true 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/resource_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class ResourceFieldPresenter < FieldPresenter 5 | def value_for_preview 6 | return unless @model.collection 7 | return if @model.collection.none? 8 | 9 | id = value 10 | return if id.blank? 11 | 12 | collection 13 | .where(@model.data_source.value_method => id) 14 | .first 15 | &.send(@model.data_source.text_method) 16 | end 17 | 18 | def include_blank? 19 | required? 20 | end 21 | 22 | def collection 23 | return unless @model.collection 24 | 25 | # TODO: limit 100 for performance 26 | @model.collection.limit(100) 27 | end 28 | 29 | def options_for_select 30 | return @view.options_for_select([]) unless @model.collection 31 | 32 | @view.options_from_collection_for_select( 33 | collection, @model.data_source.value_method, @model.data_source.text_method, value 34 | ) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/resource_select_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class ResourceSelectFieldPresenter < FieldPresenter 5 | def include_blank? 6 | required? 7 | end 8 | 9 | def can_custom_value? 10 | !@model.options.strict_select 11 | end 12 | 13 | def collection 14 | values = 15 | if @model.collection 16 | # TODO: limit 100 for performance 17 | @model.collection.limit(100).map(&@model.data_source.text_method) 18 | else 19 | [] 20 | end 21 | 22 | if can_custom_value? && value.present? 23 | ([value] + values).uniq 24 | else 25 | values 26 | end 27 | end 28 | 29 | def options_for_select 30 | @view.options_for_select(collection, value) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/select_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class SelectFieldPresenter < FieldPresenter 5 | def include_blank? 6 | required? 7 | end 8 | 9 | def can_custom_value? 10 | !@model.options.strict_select 11 | end 12 | 13 | def collection 14 | collection = @model.choices.map(&:label) 15 | if can_custom_value? && value.present? 16 | ([value] + collection).uniq 17 | else 18 | collection 19 | end 20 | end 21 | 22 | def options_for_select 23 | @view.options_for_select(collection, value) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy/app/presenters/fields/text_field_presenter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Fields 4 | class TextFieldPresenter < FieldPresenter 5 | def multiline 6 | @model.options.multiline 7 | end 8 | alias multiline? multiline 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/data_source_options/_dictionary.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :scope %> 3 |
4 | <%= f.text_field :scope, class: "input", required: true %> 5 |
6 |

7 | The dictionary's scope you've created, go to <%= link_to "Dictionaries", dictionaries_path, target: "_blank" %> to check this out. 8 |

9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/data_source_options/_empty.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/app/views/_form_core/data_source_options/_empty.html.erb -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_decimal_field.html.erb: -------------------------------------------------------------------------------- 1 |

Decimal field

2 | 3 |
4 | <%= f.label :step, class: "label" %> 5 |
6 | <%= f.number_field :step, class: "input", min: 0, step: 0.01 %> 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_integer_field.html.erb: -------------------------------------------------------------------------------- 1 |

Integer field

2 | 3 |
4 | <%= f.label :step, class: "label" %> 5 |
6 | <%= f.number_field :step, class: "input", min: 0, step: 1 %> 7 |
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_multiple_resource_field.html.erb: -------------------------------------------------------------------------------- 1 |

Multiple resource field

2 | 3 |
4 | <%= f.label :data_source_type, class: "label" %> 5 |
6 | 7 | <%= f.select :data_source_type, options_for_data_source_types(selected: options.data_source_type) %> 8 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_multiple_resource_select_field.html.erb: -------------------------------------------------------------------------------- 1 |

Multiple resource Select field

2 | 3 |
4 | <%= f.label :data_source_type, class: "label" %> 5 |
6 | 7 | <%= f.select :data_source_type, options_for_data_source_types(selected: options.data_source_type) %> 8 | 9 |
10 |
11 | 12 |
13 |
14 | 18 |
19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_multiple_select_field.html.erb: -------------------------------------------------------------------------------- 1 |

Select field

2 | 3 |
4 |
5 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_resource_field.html.erb: -------------------------------------------------------------------------------- 1 |

Resource field

2 | 3 |
4 | <%= f.label :data_source_type, class: "label" %> 5 |
6 | 7 | <%= f.select :data_source_type, options_for_data_source_types(selected: options.data_source_type) %> 8 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_resource_select_field.html.erb: -------------------------------------------------------------------------------- 1 |

Resource Select field

2 | 3 |
4 | <%= f.label :data_source_type, class: "label" %> 5 |
6 | 7 | <%= f.select :data_source_type, options_for_data_source_types(selected: options.data_source_type) %> 8 | 9 |
10 |
11 | 12 |
13 |
14 | 18 |
19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_select_field.html.erb: -------------------------------------------------------------------------------- 1 |

Select field

2 | 3 |
4 |
5 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/field_options/_text_field.html.erb: -------------------------------------------------------------------------------- 1 |

Text field

2 | 3 |
4 |
5 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_attachment_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.file_field field.name, direct_upload: true %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_boolean_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 | <% if field.hint.present? %> 9 |

<%= field.hint %>

10 | <% end %> 11 |
12 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_choice_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.collection_radio_buttons field.name, field.choices, :id, :label do |m| %> 5 | 9 | <% end %> 10 |
11 | <% if field.hint.present? %> 12 |

<%= field.hint %>

13 | <% end %> 14 |
15 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_date_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.date_field field.name, field.field_options.merge(class: 'input', id: field.id, required: field.required, disabled: field.access_readonly?) %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_date_range_field.html.erb: -------------------------------------------------------------------------------- 1 | <% f.object.send("build_#{field.name}") unless f.object.send(field.name) %> 2 | 3 |
4 | <%= f.label field.name, field.label, class: 'label' %> 5 |
6 | <%= f.fields_for field.name do |ff| %> 7 |
8 |

9 | <%= ff.date_field :begin, class: 'input', id: "#{field.id}_begin", required: field.required, **field.begin_options %> 10 |

11 |

12 | 13 | ~ 14 | 15 |

16 |

17 | <%= ff.date_field :end, class: 'input', id: "#{field.id}_end", **field.end_options %> 18 |

19 |
20 | <% end %> 21 |
22 | <% if field.hint.present? %> 23 |

<%= field.hint %>

24 | <% end %> 25 |
26 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_datetime_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.datetime_field field.name, field.field_options.merge(class: 'input', id: field.id, required: field.required, disabled: field.access_readonly?) %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_datetime_range_field.html.erb: -------------------------------------------------------------------------------- 1 | <% f.object.send("build_#{field.name}") unless f.object.send(field.name) %> 2 | 3 |
4 | <%= f.label field.name, field.label, class: 'label' %> 5 |
6 | <%= f.fields_for field.name do |ff| %> 7 |
8 |

9 | <%= ff.datetime_field :begin, class: 'input', id: "#{field.id}_begin", required: field.required, **field.begin_options %> 10 |

11 |

12 | 13 | ~ 14 | 15 |

16 |

17 | <%= ff.datetime_field :end, class: 'input', id: "#{field.id}_end", **field.end_options %> 18 |

19 |
20 | <% end %> 21 |
22 | <% if field.hint.present? %> 23 |

<%= field.hint %>

24 | <% end %> 25 |
26 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_decimal_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.number_field field.name, class: 'input', id: field.id, required: field.required, disabled: field.access_readonly?, **field.to_builder_options %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_decimal_range_field.html.erb: -------------------------------------------------------------------------------- 1 | <% f.object.send("build_#{field.name}") unless f.object.send(field.name) %> 2 | 3 |
4 | <%= f.label field.name, field.label, class: 'label' %> 5 |
6 | <%= f.fields_for field.name do |ff| %> 7 |
8 |

9 | <%= ff.number_field :begin, class: 'input', id: "#{field.id}_begin", required: field.required, **field.begin_options %> 10 |

11 |

12 | 13 | ~ 14 | 15 |

16 |

17 | <%= ff.number_field :end, class: 'input', id: "#{field.id}_end", required: field.required, **field.end_options %> 18 |

19 |
20 | <% end %> 21 |
22 | <% if field.hint.present? %> 23 |

<%= field.hint %>

24 | <% end %> 25 |
26 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_integer_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.number_field field.name, class: 'input', id: field.id, required: field.required, disabled: field.access_readonly?, **field.to_builder_options %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_integer_range_field.html.erb: -------------------------------------------------------------------------------- 1 | <% f.object.send("build_#{field.name}") unless f.object.send(field.name) %> 2 | 3 |
4 | <%= f.label field.name, field.label, class: 'label' %> 5 |
6 | <%= f.fields_for field.name do |ff| %> 7 |
8 |

9 | <%= ff.number_field :begin, class: 'input', id: "#{field.id}_begin", required: field.required, **field.begin_options %> 10 |

11 |

12 | 13 | ~ 14 | 15 |

16 |

17 | <%= ff.number_field :end, class: 'input', id: "#{field.id}_end", required: field.required, **field.end_options %> 18 |

19 |
20 | <% end %> 21 |
22 | <% if field.hint.present? %> 23 |

<%= field.hint %>

24 | <% end %> 25 |
26 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_multiple_attachment_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.file_field field.name, multiple: true, direct_upload: true %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_multiple_choice_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.collection_check_boxes field.name, field.choices, :id, :label do |m| %> 5 | 9 | <% end %> 10 |
11 | <% if field.hint.present? %> 12 |

<%= field.hint %>

13 | <% end %> 14 |
15 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_multiple_nested_form_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= field.label %>

3 | 4 | <%= tag.div id: field.name, class: "collection" do %> 5 | <%= f.fields_for field.name do |ff| %> 6 | <%= render "_form_core/fields/nested_form", f: ff, field: field, form: field.nested_form, nesting: false %> 7 | <% end %> 8 | 9 | 22 | <% end %> 23 |
24 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_multiple_resource_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.select field.name, field.options_for_select, {include_blank: field.include_blank?}, id: field.id, class: field.id, required: field.required, disabled: field.access_readonly?, multiple: true %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | 11 | 25 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_multiple_resource_select_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.select field.name, field.options_for_select, {include_blank: field.include_blank?}, id: field.id, class: field.id, required: field.required, disabled: field.access_readonly?, multiple: true %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | 11 | 27 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_multiple_select_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.select field.name, field.options_for_select, {include_blank: field.include_blank?}, id: field.id, class: field.id, required: field.required, disabled: field.access_readonly?, multiple: true %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | 11 | 27 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_nested_form.html.erb: -------------------------------------------------------------------------------- 1 | <% nesting ||= false %> 2 | 3 |
4 | <% if f.object.errors.any? %> 5 |
6 |
7 |

8 | <%= pluralize(f.object.errors.count, "error") %> prohibited this form from being submitted: 9 |

10 |
11 |
12 |
    13 | <% f.object.errors.messages.each do |name, messages| %> 14 | <% messages.each do |message| %> 15 |
  • 16 | <%= "#{form.fields.select { |field| field.name == name}.first&.label } #{message}" %> 17 |
  • 18 | <% end %> 19 | <% end %> 20 |
21 |
22 |
23 | <% end %> 24 | 25 | <% form.fields.each do |field| %> 26 | <% field = present(field, target: f.object) %> 27 | <% unless field.access_hidden? %> 28 | <%= render "_form_core/fields/#{field.type_key}", f: f, field: field, nesting: nesting %> 29 | <% end %> 30 | <% end %> 31 | 32 | <% if field.access_read_and_write? %> 33 |
34 |
35 | <%= link_to_remove_association "Remove", f, class: "button is-small is-danger", wrapper_class: "nested_form" %> 36 |
37 |
38 | <% end %> 39 |
40 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_nested_form_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= field.label %>

3 | 4 | <%= tag.div id: field.name, class: "collection" do %> 5 | <%= f.fields_for field.name do |ff| %> 6 | <%= render "_form_core/fields/nested_form", f: ff, field: field, form: field.nested_form, nesting: false %> 7 | <% end %> 8 | 9 | 23 | <% end %> 24 |
25 | 26 | <% if field.access_read_and_write? %> 27 | 42 | <% end %> 43 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_resource_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.select field.name, field.options_for_select, {include_blank: field.include_blank?}, id: field.id, class: field.id, required: field.required, disabled: field.access_readonly? %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | 11 | 21 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_resource_select_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.select field.name, field.options_for_select, {include_blank: field.include_blank?}, id: field.id, class: field.id, required: field.required, disabled: field.access_readonly? %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | 11 | 25 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_select_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <%= f.select field.name, field.options_for_select, {include_blank: field.include_blank?}, id: field.id, class: field.id, required: field.required, disabled: field.access_readonly? %> 5 |
6 | <% if field.hint.present? %> 7 |

<%= field.hint %>

8 | <% end %> 9 |
10 | 11 | 25 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/fields/_text_field.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label field.name, field.label, class: 'label' %> 3 |
4 | <% if field.options.multiline %> 5 | <%= f.text_area field.name, class: 'textarea', id: field.id, required: field.required, disabled: field.access_readonly? %> 6 | <% else %> 7 | <%= f.text_field field.name, class: 'input', id: field.id, required: field.required, disabled: field.access_readonly? %> 8 | <% end %> 9 |
10 | <% if field.hint.present? %> 11 |

<%= field.hint %>

12 | <% end %> 13 |
14 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/preview/_form.html.erb: -------------------------------------------------------------------------------- 1 | <% title_tag ||= :h3 %> 2 | <% form.sections.each do |section| %> 3 | <% fields = section.fields.map { |field| present(field, target: instance) }.reject(&:access_hidden?) %> 4 | <% next if fields.empty? %> 5 | 6 | <% unless section.headless? %> 7 | <%= content_tag title_tag, section.title %> 8 | <% end %> 9 | 10 | <% fields.each do |field| %> 11 | <% if field.nested_form_field? %> 12 | <% next unless field.value_for_preview %> 13 | 14 |

<%= field.label %>:

15 | <%= render "_form_core/preview/nested_form", form: field.nested_form, instance: field.value_for_preview %> 16 | <% elsif field.multiple_nested_form? %> 17 | <% next if field.value_for_preview.empty? %> 18 | 19 |

<%= field.label %>:

20 | <%= field.value_for_preview.map do |nested_instance| %> 21 | <% render "_form_core/preview/nested_form", form: field.nested_form, instance: nested_instance %> 22 | <% end.join("
").html_safe %> 23 | <% else %> 24 |

<%= field.label %>: <%= field.value_for_preview %>

25 | <% end %> 26 | <% end %> 27 | <% end %> 28 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/preview/_nested_form.html.erb: -------------------------------------------------------------------------------- 1 | <% fields = form.fields.map { |field| present(field, target: instance) }.reject(&:access_hidden?) %> 2 | <% return if fields.empty? %> 3 | 4 |
5 | <% fields.each do |field| %> 6 | <% if field.nested_form_field? %> 7 | <% next unless field.value_for_preview %> 8 | 9 |

<%= field.label %>:

10 | <%= render "_form_core/preview/nested_form", form: field.nested_form, instance: field.value_for_preview %> 11 | <% elsif field.multiple_nested_form? %> 12 | <% next if field.value_for_preview.empty? %> 13 | 14 |

<%= field.label %>:

15 | <%= field.value_for_preview.map do |nested_instance| %> 16 | <% render "_form_core/preview/nested_form", form: field.nested_form, instance: nested_instance %> 17 | <% end.join("
").html_safe %> 18 | <% else %> 19 |

<%= field.label %>: <%= field.value_for_preview %>

20 | <% end %> 21 | <% end %> 22 |
23 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/render/_form.html.erb: -------------------------------------------------------------------------------- 1 | <% options ||= {} %> 2 | <%= form_with(model: instance, **options) do |f| %> 3 | <% if instance.errors.any? %> 4 |
5 |
6 |

7 | <%= pluralize(instance.errors.count, "error") %> prohibited this form from being submitted: 8 |

9 |
10 |
11 | 18 |
19 |
20 | <% end %> 21 | 22 | <% form.sections.each do |section| %> 23 | <% fields = section.fields.map { |field| present(field, target: instance) }.reject(&:access_hidden?) %> 24 | <% next if fields.empty? %> 25 | 26 | <% unless section.headless? %> 27 |

<%= section.title %>

28 | <% end %> 29 | 30 | <% fields.each do |field| %> 31 | <%= render "_form_core/fields/#{field.type_key}", f: f, field: field %> 32 | <% end %> 33 | <% end %> 34 | 35 |
36 |
37 | <%= f.submit "Submit", class: "button is-primary" %> 38 |
39 |
40 | <%= link_to "Back", url_for(:back), class: "button is-link" %> 41 |
42 |
43 | <% end %> 44 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_acceptance.html.erb: -------------------------------------------------------------------------------- 1 |

Acceptance

2 | 3 |
4 |
5 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_confirmation.html.erb: -------------------------------------------------------------------------------- 1 |

Confirmation

2 | 3 |
4 |
5 | 9 |
10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_exclusion.html.erb: -------------------------------------------------------------------------------- 1 |

Exclusion

2 |
3 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_format.html.erb: -------------------------------------------------------------------------------- 1 | <% validations.build_format unless validations.format %> 2 | <% format = validations.format %> 3 | 4 |

Format

5 | 6 | <%= f.fields_for :format, format do |ff| %> 7 | <% if format.errors.any? %> 8 |
9 |
10 |

11 | <%= pluralize(format.errors.count, "error") %> prohibited this form from being saved: 12 |

13 |
14 |
15 | <% format.errors.full_messages.each do |message| %> 16 |
  • <%= message %>
  • 17 | <% end %> 18 |
    19 |
    20 | <% end %> 21 | 22 |
    23 | <%= ff.label :with %> 24 |
    25 | <%= ff.text_field :with, class: "input" %> 26 |
    27 |
    28 | 29 |
    30 | <%= ff.label :message %> 31 |
    32 | <%= ff.text_field :message, class: "input" %> 33 |
    34 |
    35 | <% end %> 36 | 37 |
    38 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_inclusion.html.erb: -------------------------------------------------------------------------------- 1 |

    Inclusion

    2 |
    3 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_length.html.erb: -------------------------------------------------------------------------------- 1 | <% validations.build_length unless validations.length %> 2 | <% length = validations.length %> 3 | 4 |

    Length

    5 | 6 | <%= f.fields_for :length, length do |ff| %> 7 | <% if length.errors.any? %> 8 |
    9 |
    10 |

    11 | <%= pluralize(length.errors.count, "error") %> prohibited this form from being saved: 12 |

    13 |
    14 |
    15 | <% length.errors.full_messages.each do |message| %> 16 |
  • <%= message %>
  • 17 | <% end %> 18 |
    19 |
    20 | <% end %> 21 | 22 |
    23 | <%= ff.label :minimum %> 24 |
    25 | <%= ff.number_field :minimum, min: 0, step: 1, class: 'input' %> 26 |
    27 |
    28 | 29 |
    30 | <%= ff.label :maximum %> 31 |
    32 | <%= ff.number_field :maximum, min: 0, step: 1, class: 'input' %> 33 |
    34 |
    35 | 36 |
    37 | <%= ff.label :is %> 38 |
    39 | <%= ff.number_field :is, min: 0, step: 1, class: 'input' %> 40 |
    41 |
    42 | <% end %> 43 | 44 |
    45 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_numericality.html.erb: -------------------------------------------------------------------------------- 1 | <% validations.build_numericality unless validations.numericality %> 2 | <% numericality = validations.numericality %> 3 | 4 |

    Numericality

    5 | 6 | <%= f.fields_for :numericality, numericality do |ff| %> 7 | <% if numericality.errors.any? %> 8 |
    9 |
    10 |

    11 | <%= pluralize(numericality.errors.count, "error") %> prohibited this form from being saved: 12 |

    13 |
    14 |
    15 | <% numericality.errors.full_messages.each do |message| %> 16 |
  • <%= message %>
  • 17 | <% end %> 18 |
    19 |
    20 | <% end %> 21 | 22 |
    23 |
    24 | 25 | <%= ff.select :lower_bound_check, options_for_enum_select(numericality.class, :lower_bound_check, numericality.lower_bound_check), {}, class: "select" %> 26 | 27 |
    28 |
    29 | <%= ff.number_field :lower_bound_value, step: 0.01, class: "input" %> 30 |
    31 |
    32 | 33 |
    34 |
    35 | 36 | <%= ff.select :upper_bound_check, options_for_enum_select(numericality.class, :upper_bound_check, numericality.upper_bound_check), {}, class: "select" %> 37 | 38 |
    39 |
    40 | <%= ff.number_field :upper_bound_value, step: 0.01, class: "input" %> 41 |
    42 |
    43 | <% end %> 44 | 45 |
    46 | -------------------------------------------------------------------------------- /test/dummy/app/views/_form_core/validations/_presence.html.erb: -------------------------------------------------------------------------------- 1 |

    Presence

    2 | 3 |
    4 |
    5 | 9 |
    10 |
    11 | 12 |
    13 | -------------------------------------------------------------------------------- /test/dummy/app/views/dictionaries/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: dictionary, local: true) do |f| %> 2 | <% if dictionary.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(dictionary.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 |
    20 | <%= f.label :scope, class: 'label' %> 21 |
    22 | <%= f.text_field :scope, class: 'input', required: true %> 23 |
    24 |

    25 | The value must follow the pattern <%= Dictionary::SCOPE_REGEX.source %>. 26 |

    27 |
    28 | 29 |
    30 | <%= f.label :value, class: 'label' %> 31 |
    32 | <%= f.text_field :value, class: 'input', required: true %> 33 |
    34 |
    35 | 36 |
    37 |
    38 | <%= f.submit class: 'button is-primary' %> 39 |
    40 |
    41 | <%= link_to 'Back', url_for(:back), class: 'button is-link' %> 42 |
    43 |
    44 | <% end %> 45 | -------------------------------------------------------------------------------- /test/dummy/app/views/dictionaries/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing dictionary

    4 | <%= render 'form', dictionary: @dictionary %> 5 |
    6 |
    7 | 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/dictionaries/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 | <%= link_to 'New', new_dictionary_path, class: 'button is-primary' %> 5 |

    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% @dictionaries.each do |dictionary| %> 17 | 18 | 21 | 24 | 27 | 31 | 32 | <% end %> 33 | 34 |
    IdScopeValue
    19 | <%= dictionary.id %> 20 | 22 | <%= dictionary.scope %> 23 | 25 | <%= dictionary.value %> 26 | 28 | <%= link_to 'Edit', edit_dictionary_path(dictionary) %> | 29 | <%= link_to 'Destroy', dictionary, method: :delete, data: { confirm: 'Are you sure?' } %> 30 |
    35 |
    36 |
    37 | 38 | -------------------------------------------------------------------------------- /test/dummy/app/views/dictionaries/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    New dictionary

    4 | <%= render 'form', dictionary: @dictionary %> 5 |
    6 |
    7 | 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/choices/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: [field.becomes(Field), choice], local: true) do |f| %> 2 | <% if choice.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(choice.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 |
    20 | <%= f.label :label, class: 'label' %> 21 |
    22 | <%= f.text_field :label, class: 'input' %> 23 |
    24 |
    25 | 26 |
    27 |
    28 | <%= f.submit class: 'button is-primary' %> 29 |
    30 |
    31 | <%= link_to 'Back', url_for(:back), class: 'button is-link' %> 32 |
    33 |
    34 | <% end %> 35 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/choices/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing choice

    4 | <%= render "form", field: @field, choice: @choice %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/choices/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 | <%= link_to "New choice", new_field_choice_path(@field), class: "button is-primary" %> 5 | <%= link_to "Back", fields_path, class: "button is-link" %> 6 |

    7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <% @field.choices.each do |choice| %> 16 | 17 | 18 | 22 | 23 | <% end %> 24 | 25 |
    Label
    <%= choice.label %> 19 | <%= link_to "Edit", edit_field_choice_path(@field, choice) %> | 20 | <%= link_to "Destroy", field_choice_path(@field, choice), method: :delete, data: {confirm: "Are you sure?"} %> 21 |
    26 |
    27 |
    28 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/choices/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    New choice

    4 | <%= render "form", field: @field, choice: @choice %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/data_source_options/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: options, scope: :options, url: field_data_source_options_path(field), method: :patch, local: true) do |f| %> 2 | <% if options.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(options.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 | <%= render "_form_core/data_source_options/#{options.type_key}", f: f %> 20 | 21 |
    22 |
    23 | <%= f.submit "Update", class: 'button is-primary' %> 24 |
    25 |
    26 | <%= link_to 'Back', url_for(:back), class: 'button is-link' %> 27 |
    28 |
    29 | <% end %> 30 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/data_source_options/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing data source options

    4 | <%= render "form", field: @field, options: @options %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/options/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: options, scope: :options, url: field_options_path(field), method: :patch, local: true) do |f| %> 2 | <% if options.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(options.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 | <%= render "_form_core/field_options/#{field.type_key}", f: f, options: options %> 20 | 21 |
    22 |
    23 | <%= f.submit "Update", class: 'button is-primary' %> 24 |
    25 |
    26 | <%= link_to 'Back', url_for(:back), class: 'button is-link' %> 27 |
    28 |
    29 | <% end %> 30 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/options/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing options

    4 | <%= render "form", field: @field, options: @options %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/validations/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: validations, scope: :validations, url: field_validations_path(field), method: :patch, local: true) do |f| %> 2 | <% if validations.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(validations.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 | <% validations.class.attribute_names.each do |attribute_name| %> 20 | <%= render "_form_core/validations/#{attribute_name}", f: f, validations: validations %> 21 | <% end %> 22 | <% validations.class.reflections.keys.each do |attribute_name| %> 23 | <%= render "_form_core/validations/#{attribute_name}", f: f, validations: validations %> 24 | <% end %> 25 | 26 |
    27 |
    28 | <%= f.submit "Update", class: "button is-primary" %> 29 |
    30 |
    31 | <%= link_to "Back", url_for(:back), class: "button is-link" %> 32 |
    33 |
    34 | <% end %> 35 | -------------------------------------------------------------------------------- /test/dummy/app/views/fields/validations/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing validations

    4 | <%= render "form", field: @field, validations: @validations %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: form, local: true) do |f| %> 2 | <% if form.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(form.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 |
    20 | <%= f.label :title, class: "label" %> 21 |
    22 | <%= f.text_field :title, class: "input", placeholder: "Title" %> 23 |
    24 |
    25 | 26 |
    27 | <%= f.label :description, class: "label" %> 28 |
    29 | <%= f.text_area :description, class: "textarea", placeholder: "Description" %> 30 |
    31 |
    32 | 33 |
    34 |
    35 | <%= f.submit class: "button is-primary" %> 36 |
    37 |
    38 | <%= link_to "Back", url_for(:back), class: "button is-link" %> 39 |
    40 |
    41 | <% end %> 42 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing form

    4 | <%= render "form", form: @form %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/fields/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing field

    4 | <%= render "form", form: @form, field: @field %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/fields/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    New field

    4 | <%= render "form", form: @form, field: @field %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 | <%= link_to "New Form", new_form_path, class: "button is-primary" %> 5 | <%= link_to "Generate form", random_forms_path(@form), method: :post, class: "button is-link" %> 6 |

    7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% @forms.each do |form| %> 18 | 19 | 20 | 21 | 22 | 27 | 28 | <% end %> 29 | 30 |
    TitleDescriptionUpdated at
    <%= form.title %><%= form.description %><%= form.updated_at %> 23 | <%= link_to "Show", form_fields_path(form) %> | 24 | <%= link_to "Edit", edit_form_path(form) %> | 25 | <%= link_to "Destroy", form, method: :delete, data: { confirm: "Are you sure?" } %> 26 |
    31 |
    32 |
    33 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/loads/create.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= render "_form_core/render/form", 4 | form: @form, instance: @instance, 5 | options: {scope: :preview, url: form_preview_path(@form), local: true} %> 6 |
    7 |
    8 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/loads/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= form_with url: form_load_path(@form), method: :post, local: true do |f| %> 4 |
    5 |
    6 | <%= f.text_area :serialized, class: "textarea", rows: 20 %> 7 |
    8 |
    9 | 10 |
    11 |
    12 | <%= f.submit "Load from YAML", class: "button is-primary" %> 13 |
    14 |
    15 | <%= link_to "Back", url_for(:back), class: "button is-link" %> 16 |
    17 |
    18 | <% end %> 19 |
    20 |
    21 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    New form

    4 | <%= render "form", form: @form %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/previews/create.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | <%= render "_form_core/preview/form", form: @form, instance: @instance %> 5 |
    6 |
    7 |
    8 | 9 |
    10 |
    11 |

    Serialized result

    12 |

    13 | You can copy the YAML and paste into <%= link_to 'load page', form_load_path(@form) %> to validate deserialization. 14 |

    15 | 16 |
    17 | 18 | <%= @instance.dump.gsub("\n", "
    ").gsub(" ", " ").html_safe %> 19 |
    20 |
    21 |
    22 |
    23 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/previews/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= render "_form_core/render/form", 4 | form: @form, instance: @instance, 5 | options: {scope: :preview, url: form_preview_path(@form), local: true} %> 6 |
    7 |
    8 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/sections/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: [form, section], local: true) do |f| %> 2 | <% if section.errors.any? %> 3 |
    4 |
    5 |

    6 | <%= pluralize(section.errors.count, "error") %> prohibited this form from being saved: 7 |

    8 |
    9 |
    10 | 15 |
    16 |
    17 | <% end %> 18 | 19 |
    20 | <%= f.label :title, class: "label" %> 21 |
    22 | <%= f.text_field :title, class: "input", placeholder: "Title" %> 23 |
    24 |
    25 | 26 |
    27 |
    28 | 32 |
    33 |
    34 | 35 |
    36 |
    37 | <%= f.submit class: "button is-primary" %> 38 |
    39 |
    40 | <%= link_to "Back", url_for(:back), class: "button is-link" %> 41 |
    42 |
    43 | <% end %> 44 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/sections/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing section

    4 | <%= render "form", form: @form, section: @section %> 5 |
    6 |
    7 | 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/sections/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 | <%= link_to "New Section", new_form_section_path(@form), class: "button is-primary" %> 5 |

    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @sections.each do |section| %> 15 | 16 | 17 | 21 | 22 | <% end %> 23 | 24 |
    Title
    <%= section.title %> 18 | <%= link_to "Edit", edit_form_section_path(@form, section) %> | 19 | <%= link_to "Destroy", [@form, section], method: :delete, data: {confirm: "Are you sure?" } %> 20 |
    25 |
    26 |
    27 | -------------------------------------------------------------------------------- /test/dummy/app/views/forms/sections/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    New section

    4 | <%= render "form", form: @form, section: @section %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/_alert.html.erb: -------------------------------------------------------------------------------- 1 | <% return if alert.blank? %> 2 |
    3 |

    <%= alert %>

    4 |
    5 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/_nav.html.erb: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/_notice.html.erb: -------------------------------------------------------------------------------- 1 | <% return if notice.blank? %> 2 |
    3 |

    <%= notice %>

    4 |
    5 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Form Core 5 | 6 | <%= csrf_meta_tags %> 7 | 8 | 9 | 10 | 11 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" %> 12 | <%= javascript_include_tag "application", "data-turbolinks-track": "reload" %> 13 | 14 | 15 | <%= render "layouts/notice" %> 16 | <%= render "layouts/alert" %> 17 | <%= render "layouts/nav" %> 18 | 19 |
    20 | <%= content_for?(:content) ? yield(:content) : yield %> 21 |
    22 | 23 | <%= render "layouts/footer" %> 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/forms.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :content do %> 2 |
    3 |
    4 |
    5 |

    6 | <%= @form.title %> 7 |

    8 |

    9 | <%= @form.description %> 10 |

    11 |
    12 |
    13 | 14 |
    15 |
    16 | 32 |
    33 |
    34 |
    35 | 36 | <%= yield %> 37 | <% end %> 38 | 39 | <%= render template: "layouts/application" %> 40 | -------------------------------------------------------------------------------- /test/dummy/app/views/nested_forms/fields/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing field

    4 | <%= render "form", form: @nested_form, field: @field %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/app/views/nested_forms/fields/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    New field

    4 | <%= render "form", form: @nested_form, field: @field %> 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | APP_PATH = File.expand_path("../config/application", __dir__) 5 | require_relative "../config/boot" 6 | require "rails/commands" 7 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require_relative "../config/boot" 5 | require "rake" 6 | Rake.application.run 7 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "pathname" 5 | require "fileutils" 6 | include FileUtils 7 | 8 | # path to your application root. 9 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__) 10 | 11 | def system!(*args) 12 | system(*args) || abort("\n== Command #{args} failed ==") 13 | end 14 | 15 | chdir APP_ROOT do 16 | # This script is a starting point to setup your application. 17 | # Add necessary setup steps to this file. 18 | 19 | puts "== Installing dependencies ==" 20 | system! "gem install bundler --conservative" 21 | system("bundle check") || system!("bundle install") 22 | 23 | # Install JavaScript dependencies if using Yarn 24 | system("bin/yarn") 25 | 26 | # puts "\n== Copying sample files ==" 27 | # unless File.exist?('config/database.yml') 28 | # cp 'config/database.yml.sample', 'config/database.yml' 29 | # end 30 | 31 | puts "\n== Preparing database ==" 32 | system! "bin/rails db:setup" 33 | 34 | puts "\n== Removing old logs and tempfiles ==" 35 | system! "bin/rails log:clear tmp:clear" 36 | 37 | puts "\n== Restarting application server ==" 38 | system! "bin/rails restart" 39 | end 40 | -------------------------------------------------------------------------------- /test/dummy/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "pathname" 5 | require "fileutils" 6 | include FileUtils 7 | 8 | # path to your application root. 9 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__) 10 | 11 | def system!(*args) 12 | system(*args) || abort("\n== Command #{args} failed ==") 13 | end 14 | 15 | chdir APP_ROOT do 16 | # This script is a way to update your development environment automatically. 17 | # Add necessary update steps to this file. 18 | 19 | puts "== Installing dependencies ==" 20 | system! "gem install bundler --conservative" 21 | system("bundle check") || system!("bundle install") 22 | 23 | puts "\n== Updating database ==" 24 | system! "bin/rails db:migrate" 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! "bin/rails log:clear tmp:clear" 28 | 29 | puts "\n== Restarting application server ==" 30 | system! "bin/rails restart" 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative "config/environment" 6 | 7 | run Rails.application 8 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "boot" 4 | 5 | require "rails" 6 | # Pick the frameworks you want: 7 | require "active_model/railtie" 8 | # require "active_job/railtie" 9 | require "active_record/railtie" 10 | require "active_storage/engine" 11 | require "action_controller/railtie" 12 | # require "action_mailer/railtie" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "sprockets/railtie" 16 | require "rails/test_unit/railtie" 17 | 18 | Bundler.require(*Rails.groups) 19 | require "form_core" 20 | 21 | Dir[Pathname.new(File.dirname(__FILE__)).realpath.parent.join("lib", "monkey_patches", "*.rb")].map do |file| 22 | require file 23 | end 24 | 25 | module Dummy 26 | class Application < Rails::Application 27 | # Initialize configuration defaults for originally generated Rails version. 28 | config.load_defaults 7.0 29 | 30 | # Settings in config/environments/* take precedence over those specified here. 31 | # Application configuration should go into files in config/initializers 32 | # -- all .rb files in that directory are automatically loaded. 33 | 34 | Rails.autoloaders.main.ignore(Rails.root.join("app", "overrides").to_s) 35 | 36 | config.to_prepare do 37 | Dir.glob(Rails.root + "app/overrides/**/*_override*.rb").each do |c| 38 | require(c) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 5 | 6 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 7 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 8 | -------------------------------------------------------------------------------- /test/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative "application" 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # The test environment is used exclusively to run your application's 7 | # test suite. You never need to work with it otherwise. Remember that 8 | # your test database is "scratch space" for the test suite and is wiped 9 | # and recreated between test runs. Don't rely on the data there! 10 | config.cache_classes = true 11 | 12 | # Do not eager load code on boot. This avoids loading your whole application 13 | # just for the purpose of running a single test. If you are using a tool that 14 | # preloads Rails for running tests, you may have to set it to true. 15 | config.eager_load = false 16 | 17 | # Configure public file server for tests with Cache-Control for performance. 18 | config.public_file_server.enabled = true 19 | config.public_file_server.headers = { 20 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 21 | } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | 27 | # Raise exceptions instead of rendering exception templates. 28 | config.action_dispatch.show_exceptions = false 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | # Store uploaded files on the local file system in a temporary directory 34 | config.active_storage.service = :test 35 | 36 | # Print deprecation notices to the stderr. 37 | config.active_support.deprecation = :stderr 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/actionview.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActionView::Base.field_error_proc = proc do |html_tag, _instance| 4 | if /<(input|label|textarea|select)/.match?(html_tag) 5 | html_field = Nokogiri::HTML::DocumentFragment.parse(html_tag) 6 | html_field.children.add_class "is-danger" 7 | html_field.to_s.html_safe 8 | else 9 | html_tag.html_safe 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: "example.org", 6 | # https: false 7 | # ) 8 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 8 | # Rails.backtrace_cleaner.remove_silencers! 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide content security policy 6 | # For further information see the following documentation 7 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 8 | 9 | # Rails.application.config.content_security_policy do |policy| 10 | # policy.default_src :self, :https 11 | # policy.font_src :self, :https, :data 12 | # policy.img_src :self, :https, :data 13 | # policy.object_src :none 14 | # policy.script_src :self, :https 15 | # policy.style_src :self, :https 16 | 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | 21 | # If you are using UJS then enable automatic nonce generation 22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 23 | 24 | # Report CSP violations to a specified URI 25 | # For further information see the following documentation: 26 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 27 | # Rails.application.config.content_security_policy_report_only = true 28 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid fields are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += [:password] 7 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/form_core.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.configuration.to_prepare do 4 | FormCore.virtual_model_class = ::VirtualModel 5 | FormCore.virtual_model_coder_class = FormCore::YAMLCoder 6 | 7 | FormCore::YAMLCoder.safe_mode = true 8 | FormCore::YAMLCoder.whitelist_classes.concat [BigDecimal, Date, Time] 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new inflection rules using the following format. Inflections 5 | # are locale specific, and you may define rules for as many different 6 | # locales as you wish. All of these examples are active by default: 7 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 8 | # inflect.plural /^(ox)$/i, '\1en' 9 | # inflect.singular /^(ox)en/i, '\1' 10 | # inflect.irregular "person", "people" 11 | # inflect.uncountable %w( fish sheep ) 12 | # end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym "RESTful" 17 | # end 18 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/validates_timeliness.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ValidatesTimeliness.setup do |config| 4 | # Extend ORM/ODMs for full support (:active_record included). 5 | config.extend_orms = [:active_record] 6 | # 7 | # Default timezone 8 | # config.default_timezone = :utc 9 | # 10 | # Set the dummy date part for a time type values. 11 | # config.dummy_date_for_time_type = [ 2000, 1, 1 ] 12 | # 13 | # Ignore errors when restriction options are evaluated 14 | # config.ignore_restriction_errors = false 15 | # 16 | # Re-display invalid values in date/time selects 17 | # config.enable_date_time_select_extension! 18 | # 19 | # Handle multiparameter date/time values strictly 20 | # config.enable_multiparameter_extension! 21 | # 22 | # Shorthand date and time symbols for restrictions 23 | # config.restriction_shorthand_symbols.update( 24 | # :now => lambda { Time.current }, 25 | # :today => lambda { Date.current } 26 | # ) 27 | # 28 | # Use the plugin date/time parser which is stricter and extendable 29 | # config.use_plugin_parser = false 30 | # 31 | # Add one or more formats making them valid. e.g. add_formats(:date, 'd(st|rd|th) of mmm, yyyy') 32 | # config.parser.add_formats() 33 | # 34 | # Remove one or more formats making them invalid. e.g. remove_formats(:date, 'dd/mm/yyy') 35 | # config.parser.remove_formats() 36 | # 37 | # Change the ambiguous year threshold when parsing a 2 digit year 38 | # config.parser.ambiguous_year_threshold = 30 39 | # 40 | # Treat ambiguous dates, such as 01/02/1950, as a Non-US date. 41 | # config.parser.remove_us_formats 42 | end 43 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | defaults: 34 | section: 35 | title: Default 36 | values: 37 | 'true': 'Yes' 38 | 'false': 'No' 39 | -------------------------------------------------------------------------------- /test/dummy/config/locales/validates_timeliness.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | messages: 4 | invalid_date: "is not a valid date" 5 | invalid_time: "is not a valid time" 6 | invalid_datetime: "is not a valid datetime" 7 | is_at: "must be at %{restriction}" 8 | before: "must be before %{restriction}" 9 | on_or_before: "must be on or before %{restriction}" 10 | after: "must be after %{restriction}" 11 | on_or_after: "must be on or after %{restriction}" 12 | validates_timeliness: 13 | error_value_formats: 14 | date: '%Y-%m-%d' 15 | time: '%H:%M:%S' 16 | datetime: '%Y-%m-%d %H:%M:%S' 17 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 10 | threads threads_count, threads_count 11 | 12 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 13 | # 14 | port ENV.fetch("PORT") { 3000 } 15 | 16 | # Specifies the `environment` that Puma will run in. 17 | # 18 | environment ENV.fetch("RAILS_ENV") { "development" } 19 | 20 | # Specifies the number of `workers` to boot in clustered mode. 21 | # Workers are forked webserver processes. If using threads and workers together 22 | # the concurrency of the application would be max `threads` * `workers`. 23 | # Workers do not work on JRuby or Windows (both of which do not support 24 | # processes). 25 | # 26 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 27 | 28 | # Use the `preload_app!` method when specifying a `workers` number. 29 | # This directive tells Puma to first boot the application and load code 30 | # before forking the application. This takes advantage of Copy On Write 31 | # process behavior so workers use less memory. 32 | # 33 | # preload_app! 34 | 35 | # Allow puma to be restarted by `rails restart` command. 36 | plugin :tmp_restart 37 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | resources :dictionaries, except: %i[show] 5 | 6 | resources :forms, except: %i[show] do 7 | collection do 8 | post "random" 9 | end 10 | 11 | scope module: :forms do 12 | resource :preview, only: %i[show create] 13 | resource :load, only: %i[show create] 14 | resources :sections, except: %i[show] 15 | resources :fields, except: %i[show] 16 | end 17 | end 18 | 19 | resources :fields, only: %i[] do 20 | scope module: :fields do 21 | resource :validations, only: %i[edit update] 22 | resource :options, only: %i[edit update] 23 | resource :data_source_options, only: %i[edit update] 24 | resources :choices, except: %i[show] 25 | end 26 | end 27 | 28 | resources :nested_forms, only: %i[] do 29 | scope module: :nested_forms do 30 | resources :fields, except: %i[show] 31 | end 32 | end 33 | 34 | root to: "forms#index" 35 | 36 | resource :time_zone, only: [:update] 37 | end 38 | -------------------------------------------------------------------------------- /test/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: aacd284148dbbd47e20f36637c777a3537a02b671c16ef6c7516409f776821a7c5bd938c4c4a948b4dbc3953163a6b74da2b6bce97d4f264f64190df0600571d 22 | 23 | test: 24 | secret_key_base: ddc01f0a4c5c2b000a0e14fa7385f972e1a48f8bdb524fc7d14b2c839ff9c403fb1a43078b07fa47600ab3672d378ce59310591fd79879a81f4db385b44a38d8 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /test/dummy/config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | %w[ 4 | .ruby-version 5 | .rbenv-vars 6 | tmp/restart.txt 7 | tmp/caching-dev.txt 8 | ].each { |path| Spring.watch(path) } 9 | -------------------------------------------------------------------------------- /test/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20170504211819_add_columns_to_forms.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddColumnsToForms < ActiveRecord::Migration[6.0] 4 | def change 5 | change_table :forms do |t| 6 | t.string :title, default: "" 7 | t.text :description, default: "" 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20170504214301_create_sections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateSections < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :sections do |t| 6 | t.string :title, default: "" 7 | t.boolean :headless, null: false, default: false 8 | t.references :form, foreign_key: true 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20170504225506_add_columns_to_fields.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddColumnsToFields < ActiveRecord::Migration[6.0] 4 | def change 5 | change_table :fields do |t| 6 | t.string :label, default: "" 7 | t.string :hint, default: "" 8 | t.references :section, foreign_key: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20170515212355_create_dictionaries.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateDictionaries < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :dictionaries do |t| 6 | t.string :value, null: false, default: "", index: true 7 | t.string :scope, null: false, default: "", index: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20170613184106_add_attachable_to_forms.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddAttachableToForms < ActiveRecord::Migration[6.0] 4 | def change 5 | change_table :forms do |t| 6 | t.references :attachable, polymorphic: true, index: true 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20180120200440_add_position_to_fields.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddPositionToFields < ActiveRecord::Migration[6.0] 4 | def change 5 | change_table :fields do |t| 6 | t.integer :position 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20180120201200_add_position_to_sections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddPositionToSections < ActiveRecord::Migration[6.0] 4 | def change 5 | change_table :sections do |t| 6 | t.integer :position 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20180220232604_create_choices.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateChoices < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :choices do |t| 6 | t.text :label, null: false 7 | t.references :field, foreign_key: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20180228201120_add_position_to_choices.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AddPositionToChoices < ActiveRecord::Migration[6.0] 4 | def change 5 | change_table :choices do |t| 6 | t.integer :position 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20210108214331_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from active_storage (originally 20170806125915) 4 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 5 | def change 6 | create_table :active_storage_blobs do |t| 7 | t.string :key, null: false 8 | t.string :filename, null: false 9 | t.string :content_type 10 | t.text :metadata 11 | t.string :service_name, null: false 12 | t.bigint :byte_size, null: false 13 | t.string :checksum, null: false 14 | t.datetime :created_at, null: false 15 | 16 | t.index [ :key ], unique: true 17 | end 18 | 19 | create_table :active_storage_attachments do |t| 20 | t.string :name, null: false 21 | t.references :record, null: false, polymorphic: true, index: false 22 | t.references :blob, null: false 23 | 24 | t.datetime :created_at, null: false 25 | 26 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true 27 | t.foreign_key :active_storage_blobs, column: :blob_id 28 | end 29 | 30 | create_table :active_storage_variant_records do |t| 31 | t.belongs_to :blob, null: false, index: false 32 | t.string :variation_digest, null: false 33 | 34 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true 35 | t.foreign_key :active_storage_blobs, column: :blob_id 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /test/dummy/lib/monkey_patches/active_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "active_support/concern+prependable" 4 | -------------------------------------------------------------------------------- /test/dummy/lib/monkey_patches/active_support/concern+prependable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Cheated from https://gitlab.com/gitlab-org/gitlab-ee/blob/master/config/initializers/0_as_concern.rb 4 | # This module is based on: https://gist.github.com/bcardarella/5735987 5 | 6 | module Prependable 7 | def prepend_features(base) 8 | if base.instance_variable_defined?(:@_dependencies) 9 | base.instance_variable_get(:@_dependencies) << self 10 | false 11 | else 12 | return false if base < self 13 | 14 | super 15 | base.singleton_class.send(:prepend, const_get("ClassMethods")) if const_defined?(:ClassMethods) 16 | @_dependencies.each { |dep| base.send(:prepend, dep) } 17 | base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) 18 | end 19 | end 20 | end 21 | 22 | module ActiveSupport 23 | module Concern 24 | prepend Prependable 25 | 26 | alias prepended included 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/log/.keep -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

    Maybe you tried to change something you didn't have access to.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    We're sorry, but something went wrong.

    62 |
    63 |

    If you are the application owner check the logs for more information.

    64 |
    65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/public/apple-touch-icon.png -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/public/favicon.ico -------------------------------------------------------------------------------- /test/dummy/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/storage/.keep -------------------------------------------------------------------------------- /test/dummy/test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 6 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/test/controllers/.keep -------------------------------------------------------------------------------- /test/dummy/test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/test/fixtures/.keep -------------------------------------------------------------------------------- /test/dummy/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/test/models/.keep -------------------------------------------------------------------------------- /test/dummy/test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-engine/form_core/b7fca1b1a184700adf9801c57dcd4dfeef5bc586/test/dummy/test/system/.keep -------------------------------------------------------------------------------- /test/fixtures/form_core/fields.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | label: MyString 6 | hint: MyString 7 | prompt: MyString 8 | accessibility: 0 9 | validations: 10 | options: 11 | section: one 12 | 13 | two: 14 | name: MyString 15 | label: MyString 16 | hint: MyString 17 | prompt: MyString 18 | accessibility: 0 19 | validations: 20 | options: 21 | section: two 22 | -------------------------------------------------------------------------------- /test/fixtures/form_core/forms.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | title: MyString 5 | description: MyText 6 | 7 | two: 8 | title: MyString 9 | description: MyText 10 | -------------------------------------------------------------------------------- /test/form_core_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class FormCore::Test < ActiveSupport::TestCase 6 | test "truth" do 7 | assert_kind_of Module, FormCore 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/integration/navigation_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class NavigationTest < ActionDispatch::IntegrationTest 6 | # test "the truth" do 7 | # assert true 8 | # end 9 | end 10 | -------------------------------------------------------------------------------- /test/models/form_core/field_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module FormCore 6 | class FieldTest < ActiveSupport::TestCase 7 | # test "the truth" do 8 | # assert true 9 | # end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/models/form_core/form_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module FormCore 6 | class FormTest < ActiveSupport::TestCase 7 | # test "the truth" do 8 | # assert true 9 | # end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path("../test/dummy/config/environment.rb", __dir__) 4 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)] 5 | ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__) 6 | require "rails/test_help" 7 | 8 | # Filter out Minitest backtrace while allowing backtrace from other libraries 9 | # to be shown. 10 | Minitest.backtrace_filter = Minitest::BacktraceFilter.new 11 | 12 | # Load fixtures from the engine 13 | if ActiveSupport::TestCase.respond_to?(:fixture_path=) 14 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) 15 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path 16 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" 17 | ActiveSupport::TestCase.fixtures :all 18 | end 19 | --------------------------------------------------------------------------------