From 459cbf295175b8d9ae1b2e560a465d547eb88083 Mon Sep 17 00:00:00 2001 From: pvincent Date: Fri, 6 Mar 2026 03:44:51 +0000 Subject: [PATCH] formtastic --- Gemfile | 1 + Gemfile.lock | 4 + Rakefile | 2 +- app/assets/stylesheets/formtastic.css | 289 ++++++++++++++++++++++ app/controllers/posts_controller.rb | 59 +++++ app/helpers/posts_helper.rb | 2 + app/models/post.rb | 2 + app/views/posts/_form.html.erb | 5 + app/views/posts/_post.html.erb | 2 + app/views/posts/edit.html.erb | 10 + app/views/posts/index.html.erb | 43 ++++ app/views/posts/new.html.erb | 9 + app/views/posts/show.html.erb | 15 ++ config/initializers/formtastic.rb | 118 +++++++++ config/routes.rb | 1 + db/migrate/20260303141001_create_posts.rb | 11 + db/schema.rb | 21 ++ test/controllers/posts_controller_test.rb | 48 ++++ test/fixtures/posts.yml | 11 + test/models/post_test.rb | 7 + 20 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 app/assets/stylesheets/formtastic.css create mode 100644 app/controllers/posts_controller.rb create mode 100644 app/helpers/posts_helper.rb create mode 100644 app/models/post.rb create mode 100644 app/views/posts/_form.html.erb create mode 100644 app/views/posts/_post.html.erb create mode 100644 app/views/posts/edit.html.erb create mode 100644 app/views/posts/index.html.erb create mode 100644 app/views/posts/new.html.erb create mode 100644 app/views/posts/show.html.erb create mode 100644 config/initializers/formtastic.rb create mode 100644 db/migrate/20260303141001_create_posts.rb create mode 100644 db/schema.rb create mode 100644 test/controllers/posts_controller_test.rb create mode 100644 test/fixtures/posts.yml create mode 100644 test/models/post_test.rb diff --git a/Gemfile b/Gemfile index 89d5bf7..9516146 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' gem 'bootsnap', require: false gem 'dotenv-rails' +gem 'formtastic' gem 'importmap-rails' gem 'propshaft' gem 'puma' diff --git a/Gemfile.lock b/Gemfile.lock index b5b1183..90b2bc8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,8 @@ GEM ffi (1.17.3-x86_64-darwin) ffi (1.17.3-x86_64-linux-gnu) ffi (1.17.3-x86_64-linux-musl) + formtastic (6.0.0) + actionpack (>= 7.2.0) fugit (1.12.1) et-orbi (~> 1.4) raabro (~> 1.4) @@ -366,6 +368,7 @@ DEPENDENCIES debug dotenv-rails error_highlight + formtastic htmlbeautifier importmap-rails propshaft @@ -426,6 +429,7 @@ CHECKSUMS ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5 ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56 + formtastic (6.0.0) sha256=c398906b65978fec3d045d6792f82cf9641f086ac9f17357b2b382f723126165 fugit (1.12.1) sha256=5898f478ede9b415f0804e42b8f3fd53f814bd85eebffceebdbc34e1107aaf68 globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 htmlbeautifier (1.4.3) sha256=b43d08f7e2aa6ae1b5a6f0607b4ed8954c8d4a8e85fd2336f975dda1e4db385b diff --git a/Rakefile b/Rakefile index 9a5ea73..e85f913 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require_relative "config/application" +require_relative 'config/application' Rails.application.load_tasks diff --git a/app/assets/stylesheets/formtastic.css b/app/assets/stylesheets/formtastic.css new file mode 100644 index 0000000..679535b --- /dev/null +++ b/app/assets/stylesheets/formtastic.css @@ -0,0 +1,289 @@ +/* ------------------------------------------------------------------------------------------------- + +It's *strongly* suggested that you don't modify this file. Instead, load a new stylesheet after +this one in your layouts (eg formtastic_changes.css) and override the styles to suit your needs. +This will allow you to update formtastic.css with new releases without clobbering your own changes. + +This stylesheet forms part of the Formtastic Rails gem +(c) Justin French + +--------------------------------------------------------------------------------------------------*/ + +/* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just .formtastic +--------------------------------------------------------------------------------------------------*/ +.formtastic, +.formtastic ul, +.formtastic ol, +.formtastic li, +.formtastic fieldset, +.formtastic legend, +.formtastic input, +.formtastic button, +.formtastic textarea, +.formtastic select, +.formtastic p { + margin:0; + padding:0; +} + +.formtastic fieldset { + border:0; +} + +.formtastic em, +.formtastic strong { + font-style:normal; + font-weight:normal; +} + +.formtastic ol, +.formtastic ul { + list-style:none; +} + +.formtastic abbr, +.formtastic acronym { + border:0; + font-variant:normal; +} + +.formtastic input, +.formtastic button, +.formtastic textarea { + font-family:sans-serif; + font-size:inherit; + font-weight:inherit; +} + +.formtastic input, +.formtastic textarea, +.formtastic select { + font-size:100%; +} + +.formtastic legend { + white-space:normal; + color:#000; +} + + +/* SEMANTIC ERRORS +--------------------------------------------------------------------------------------------------*/ +.formtastic .errors { + color:#cc0000; + margin:0.5em 0 1.5em 25%; + list-style:square; +} + +.formtastic .errors li { + padding:0; + border:none; + display:list-item; +} + + +/* BUTTONS & ACTIONS +--------------------------------------------------------------------------------------------------*/ +.formtastic .buttons, +.formtastic .actions { + overflow:hidden; /* clear containing floats */ + padding-left:25%; +} + +.formtastic .button, +.formtastic .action { + float:left; + padding-right:0.5em; +} + +.formtastic .button_action button { + padding:3px 8px; +} + +.formtastic .link_action a { + display:block; + padding:3px 0; +} + + +/* INPUTS +--------------------------------------------------------------------------------------------------*/ +.formtastic .inputs { + overflow:hidden; /* clear containing floats */ +} + +.formtastic .input { + overflow:hidden; /* clear containing floats */ + padding:0.5em 0; /* padding and negative margin juggling is for Firefox */ + margin-top:-0.5em; + margin-bottom:1em; +} + + +/* LEFT ALIGNED LABELS +--------------------------------------------------------------------------------------------------*/ +.formtastic .input .label { + display:block; + width:25%; + float:left; + padding-top:.2em; +} + +.formtastic .fragments .label, +.formtastic .choices .label { + position:absolute; + width:95%; + left:0px; +} + +.formtastic .fragments .label label, +.formtastic .choices .label label { + position:absolute; +} + +/* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets) +--------------------------------------------------------------------------------------------------*/ +.formtastic .choices { + position:relative; +} + +.formtastic .choices-group { + float:left; + width:74%; + margin:0; + padding:0 0 0 25%; +} + +.formtastic .choice { + padding:0; + border:0; +} + + +/* INLINE HINTS +--------------------------------------------------------------------------------------------------*/ +.formtastic .input .inline-hints { + color:#666; + margin:0.5em 0 0 25%; +} + + +/* INLINE ERRORS +--------------------------------------------------------------------------------------------------*/ +.formtastic .inline-errors { + color:#cc0000; + margin:0.5em 0 0 25%; +} + +.formtastic .errors { + color:#cc0000; + margin:0.5em 0 0 25%; + list-style:square; +} + +.formtastic .errors li { + padding:0; + border:none; + display:list-item; +} + + +/* STRING, NUMERIC, PASSWORD, EMAIL, URL, PHONE, SEARCH (ETC) OVERRIDES +--------------------------------------------------------------------------------------------------*/ +.formtastic .stringish input { + width:72%; +} + +.formtastic .stringish input[size], +.formtastic .stringish input[max] { + width:auto; + max-width:72%; +} + + +/* TEXTAREA OVERRIDES +--------------------------------------------------------------------------------------------------*/ +.formtastic .text textarea { + width:72%; +} + +.formtastic .text textarea[cols] { + width:auto; + max-width:72%; +} + + +/* HIDDEN OVERRIDES +--------------------------------------------------------------------------------------------------*/ +.formtastic .hidden { + display:none; +} + + +/* BOOLEAN LABELS +--------------------------------------------------------------------------------------------------*/ +.formtastic .boolean label { + margin-left:25%; + display:block; +} + + +/* CHOICE GROUPS +--------------------------------------------------------------------------------------------------*/ +.formtastic .choices-group { + margin-bottom:-0.5em; +} + +.formtastic .choice { + margin:0.1em 0 0.5em 0; +} + +.formtastic .choice label { + float:none; + width:100%; + line-height:100%; + padding-top:0; + margin-bottom:0.6em; +} + + +/* ADJUSTMENTS FOR INPUTS INSIDE LABELS (boolean input, radio input, check_boxes input) +--------------------------------------------------------------------------------------------------*/ +.formtastic .choice label input, +.formtastic .boolean label input { + margin:0 0.3em 0 0.1em; + line-height:100%; +} + + +/* FRAGMENTED INPUTS (DATE/TIME/DATETIME) +--------------------------------------------------------------------------------------------------*/ +.formtastic .fragments { + position:relative; +} + +.formtastic .fragments-group { + float:left; + width:74%; + margin:0; + padding:0 0 0 25%; +} + +.formtastic .fragment { + float:left; + width:auto; + margin:0 .3em 0 0; + padding:0; + border:0; +} + +.formtastic .fragment label { + display:none; +} + +.formtastic .fragment label input { + display:inline; + margin:0; + padding:0; +} + diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 0000000..53bb6b9 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,59 @@ +class PostsController < ApplicationController + before_action :set_post, only: %i[show edit update destroy] + + # GET /posts + def index + @posts = Post.all + end + + # GET /posts/1 + def show + end + + # GET /posts/new + def new + @post = Post.new + end + + # GET /posts/1/edit + def edit + end + + # POST /posts + def create + @post = Post.new(post_params) + + if @post.save + redirect_to @post, notice: 'Post was successfully created.' + else + render :new, status: :unprocessable_content + end + end + + # PATCH/PUT /posts/1 + def update + if @post.update(post_params) + redirect_to @post, notice: 'Post was successfully updated.', status: :see_other + else + render :edit, status: :unprocessable_content + end + end + + # DELETE /posts/1 + def destroy + @post.destroy! + redirect_to posts_path, notice: 'Post was successfully destroyed.', status: :see_other + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_post + @post = Post.find(params.expect(:id)) + end + + # Only allow a list of trusted parameters through. + def post_params + params.require(:post).permit(:title, :quantity, :content) + end +end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb new file mode 100644 index 0000000..a7b8cec --- /dev/null +++ b/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 0000000..b2a8b46 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,2 @@ +class Post < ApplicationRecord +end diff --git a/app/views/posts/_form.html.erb b/app/views/posts/_form.html.erb new file mode 100644 index 0000000..5addfa9 --- /dev/null +++ b/app/views/posts/_form.html.erb @@ -0,0 +1,5 @@ +<%= semantic_form_for @post do |f| %> + <%= f.inputs %> + <%= f.semantic_errors :state %> + <%= f.actions %> +<% end %> diff --git a/app/views/posts/_post.html.erb b/app/views/posts/_post.html.erb new file mode 100644 index 0000000..b9bf448 --- /dev/null +++ b/app/views/posts/_post.html.erb @@ -0,0 +1,2 @@ +
+
diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb new file mode 100644 index 0000000..66322d6 --- /dev/null +++ b/app/views/posts/edit.html.erb @@ -0,0 +1,10 @@ +<% content_for :title, "Editing post" %> + +
+

Editing post

+ + <%= render "form", post: @post %> + + <%= link_to "Show this post", @post, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> + <%= link_to "Back to posts", posts_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> +
diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb new file mode 100644 index 0000000..130457e --- /dev/null +++ b/app/views/posts/index.html.erb @@ -0,0 +1,43 @@ +<% content_for :title, "Posts" %> + +
+ <% if notice.present? %> +

+ <%= notice %> +

+ <% end %> + +
+

Posts

+ <%= link_to "New post", new_post_path, class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white block font-medium" %> +
+ +
+ <% if @posts.any? %> + <% @posts.each do |post| %> +
+ <%= render post %> + +
+ <%= link_to "Show", post, class: "btn btn-primary" %> + <%= link_to "Edit", edit_post_path(post), class: "btn btn-secondary" %> + <%= button_to "Destroy", post, method: :delete, class: "btn btn-accent", data: { turbo_confirm: "Are you sure?" } %> +
+
+ <% end %> + <% else %> +

No posts found.

+ <% end %> +
+
diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb new file mode 100644 index 0000000..e97cdac --- /dev/null +++ b/app/views/posts/new.html.erb @@ -0,0 +1,9 @@ +<% content_for :title, "New post" %> + +
+

New post

+ + <%= render "form", post: @post %> + + <%= link_to "Back to posts", posts_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> +
diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb new file mode 100644 index 0000000..b039aab --- /dev/null +++ b/app/views/posts/show.html.erb @@ -0,0 +1,15 @@ +<% content_for :title, "Showing post" %> + +
+ <% if notice.present? %> +

<%= notice %>

+ <% end %> + +

Showing post

+ + <%= render @post %> + + <%= link_to "Edit this post", edit_post_path(@post), class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> + <%= link_to "Back to posts", posts_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %> + <%= button_to "Destroy this post", @post, method: :delete, form_class: "sm:inline-block mt-2 sm:mt-0 sm:ml-2", class: "w-full rounded-md px-3.5 py-2.5 text-white bg-red-600 hover:bg-red-500 font-medium cursor-pointer", data: { turbo_confirm: "Are you sure?" } %> +
diff --git a/config/initializers/formtastic.rb b/config/initializers/formtastic.rb new file mode 100644 index 0000000..0502ffc --- /dev/null +++ b/config/initializers/formtastic.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +# Set the default text field size when input is a string. Default is nil. +# Formtastic::FormBuilder.default_text_field_size = 50 + +# Set the default text area height when input is a text. Default is 20. +# Formtastic::FormBuilder.default_text_area_height = 5 + +# Set the default text area width when input is a text. Default is nil. +# Formtastic::FormBuilder.default_text_area_width = 50 + +# Should all fields be considered "required" by default? +# Defaults to true. +# Formtastic::FormBuilder.all_fields_required_by_default = true + +# Should select fields have a blank option/prompt by default? +# Defaults to true. +# Formtastic::FormBuilder.include_blank_for_select_by_default = true + +# Set the string that will be appended to the labels/fieldsets which are required. +# It accepts string or procs and the default is a localized version of +# '*'. In other words, if you configure formtastic.required +# in your locale, it will replace the abbr title properly. But if you don't want to use +# abbr tag, you can simply give a string as below. +# Formtastic::FormBuilder.required_string = "(required)" + +# Set the string that will be appended to the labels/fieldsets which are optional. +# Defaults to an empty string ("") and also accepts procs (see required_string above). +# Formtastic::FormBuilder.optional_string = "(optional)" + +# Set the way inline errors will be displayed. +# Defaults to :sentence, valid options are :sentence, :list, :first and :none +# Formtastic::FormBuilder.inline_errors = :sentence +# Formtastic uses the following classes as default for hints, inline_errors and error list + +# If you override the class here, please ensure to override it in your stylesheets as well. +# Formtastic::FormBuilder.default_hint_class = "inline-hints" +# Formtastic::FormBuilder.default_inline_error_class = "inline-errors" +# Formtastic::FormBuilder.default_error_list_class = "errors" + +# Set the method to call on label text to transform or format it for human-friendly +# reading when formtastic is used without object. Defaults to :humanize. +# Formtastic::FormBuilder.label_str_method = :humanize + +# Set the array of methods to try calling on parent objects in :select and :radio inputs +# for the text inside each @