From e662c704ac8ae305a4725670d14b9a0f962f6c9e Mon Sep 17 00:00:00 2001 From: taitus Date: Mon, 14 Apr 2025 14:36:35 +0200 Subject: [PATCH] Vendor Foundation form builder to remove gem dependency The "foundation_rails_helper" gem is no longer maintained and is incompatible with Rails 7.1. To avoid blocking the upgrade, we've vendored the vendor/foundation_rails_helper/form_builder.rb as a copy of the original FormBuilder class. To mantain compatibility with auto_labels and button_class variables, that are used in the original builder, we are overwriting in the foundation form builder initializer. The gem has been removed from the Gemfile and replaced with this vendored fallback. This workaround is safe to remove once legacy Foundation CSS support is dropped. All vendored code retains the original MIT license and attribution. --- Gemfile | 1 - Gemfile.lock | 6 - .../initializers/foundation_form_builder.rb | 16 ++ vendor/foundation_rails_helper/LICENSE | 22 ++ .../foundation_rails_helper/form_builder.rb | 245 ++++++++++++++++++ 5 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 config/initializers/foundation_form_builder.rb create mode 100644 vendor/foundation_rails_helper/LICENSE create mode 100644 vendor/foundation_rails_helper/form_builder.rb diff --git a/Gemfile b/Gemfile index 0083da136..64b916032 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,6 @@ gem "devise-security", "~> 0.18.0" gem "exiftool_vendored", "~> 12.97.0" gem "file_validators", "~> 3.0.0" gem "font-awesome-sass", "~> 5.15.1" # Remember to update vendor/assets/images/fontawesome when updating this gem -gem "foundation_rails_helper", "~> 4.0.1" gem "globalize", "~> 7.0.0" gem "globalize-accessors", "~> 0.3.0" gem "graphiql-rails", "~> 1.8.0" diff --git a/Gemfile.lock b/Gemfile.lock index 2e2a62192..e9b07d302 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -239,11 +239,6 @@ GEM mime-types (>= 1.0) font-awesome-sass (5.15.1) sassc (>= 1.11) - foundation_rails_helper (4.0.1) - actionpack (>= 4.1, < 7.1) - activemodel (>= 4.1, < 7.1) - activesupport (>= 4.1, < 7.1) - railties (>= 4.1, < 7.1) gitlab (4.20.1) httparty (~> 0.20) terminal-table (>= 1.5.1) @@ -753,7 +748,6 @@ DEPENDENCIES faraday-retry (~> 2.2.1) file_validators (~> 3.0.0) font-awesome-sass (~> 5.15.1) - foundation_rails_helper (~> 4.0.1) globalize (~> 7.0.0) globalize-accessors (~> 0.3.0) graphiql-rails (~> 1.8.0) diff --git a/config/initializers/foundation_form_builder.rb b/config/initializers/foundation_form_builder.rb new file mode 100644 index 000000000..fc66bcfff --- /dev/null +++ b/config/initializers/foundation_form_builder.rb @@ -0,0 +1,16 @@ +require Rails.root.join("vendor/foundation_rails_helper/form_builder.rb") + +class FoundationRailsHelper::FormBuilder + def column_classes(...) + "" + end + + def auto_labels + true + end + + def submit(value = nil, options = {}) + options[:class] ||= "success button" + super + end +end diff --git a/vendor/foundation_rails_helper/LICENSE b/vendor/foundation_rails_helper/LICENSE new file mode 100644 index 000000000..426855387 --- /dev/null +++ b/vendor/foundation_rails_helper/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2015 Sébastien Gruhier + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/foundation_rails_helper/form_builder.rb b/vendor/foundation_rails_helper/form_builder.rb new file mode 100644 index 000000000..f4b6c076b --- /dev/null +++ b/vendor/foundation_rails_helper/form_builder.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true + +# Copyright (c) 2015 Sébastien Gruhier (http://xilinus.com/) +# MIT License +# Vendored copy from https://github.com/sgruhier/foundation_rails_helper/blob/master/lib/foundation_rails_helper/form_builder.rb +# +# This file was vendored to work around the Rails 7.1 incompatibility, +# since the `foundation_rails_helper` gem is no longer maintained. +# +# See LICENSE in vendor/foundation_rails_helper/LICENSE + +require 'action_view/helpers' + +module FoundationRailsHelper + class FormBuilder < ActionView::Helpers::FormBuilder + include ActionView::Helpers::TagHelper + include ActionView::Helpers::OutputSafetyHelper + %w(file_field email_field text_field text_area telephone_field phone_field + url_field number_field date_field datetime_field datetime_local_field + month_field week_field time_field range_field search_field color_field) + .each do |method_name| + define_method(method_name) do |*args| + attribute = args[0] + options = args[1] || {} + field(attribute, options) do |opts| + super(attribute, opts) + end + end + end + + def label(attribute, text = nil, options = {}) + if error?(attribute) + options[:class] ||= '' + options[:class] += ' is-invalid-label' + end + + super(attribute, (text || '').html_safe, options) + end + + # rubocop:disable LineLength + def check_box(attribute, options = {}, checked_value = '1', unchecked_value = '0') + custom_label(attribute, options[:label], options[:label_options]) do + options.delete(:label) + options.delete(:label_options) + super(attribute, options, checked_value, unchecked_value) + end + error_and_help_text(attribute, options) + end + # rubocop:enable LineLength + + def radio_button(attribute, tag_value, options = {}) + options[:label_options] ||= {} + label_options = options.delete(:label_options).merge!(value: tag_value) + label_text = options.delete(:label) + l = label(attribute, label_text, label_options) unless label_text == false + r = @template.radio_button(@object_name, attribute, tag_value, + objectify_options(options)) + + "#{r}#{l}".html_safe + end + + def password_field(attribute, options = {}) + field attribute, options do |opts| + super(attribute, opts.merge(autocomplete: :off)) + end + end + + def datetime_select(attribute, options = {}, html_options = {}) + field attribute, options, html_options do |html_opts| + super(attribute, options, html_opts.merge(autocomplete: :off)) + end + end + + def date_select(attribute, options = {}, html_options = {}) + field attribute, options, html_options do |html_opts| + super(attribute, options, html_opts.merge(autocomplete: :off)) + end + end + + # rubocop:disable LineLength + def time_zone_select(attribute, priorities = nil, options = {}, html_options = {}) + field attribute, options, html_options do |html_opts| + super(attribute, priorities, options, + html_opts.merge(autocomplete: :off)) + end + end + # rubocop:enable LineLength + + def select(attribute, choices, options = {}, html_options = {}) + field attribute, options, html_options do |html_opts| + html_options[:autocomplete] ||= :off + super(attribute, choices, options, html_opts) + end + end + + # rubocop:disable LineLength, ParameterLists + def collection_select(attribute, collection, value_method, text_method, options = {}, html_options = {}) + field attribute, options, html_options do |html_opts| + html_options[:autocomplete] ||= :off + super(attribute, collection, value_method, text_method, options, + html_opts) + end + end + + def grouped_collection_select(attribute, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + field attribute, options, html_options do |html_opts| + html_options[:autocomplete] ||= :off + super(attribute, collection, group_method, group_label_method, + option_key_method, option_value_method, options, html_opts) + end + end + # rubocop:enable LineLength, ParameterLists + + def autocomplete(attribute, url, options = {}) + field attribute, options do |opts| + opts.merge!(update_elements: opts[:update_elements], + min_length: 0, value: object.send(attribute)) + autocomplete_field(attribute, url, opts) + end + end + + def submit(value = nil, options = {}) + options[:class] ||= FoundationRailsHelper.configuration.button_class + super(value, options) + end + + def error_for(attribute, options = {}) + return unless error?(attribute) + + class_name = 'form-error is-visible' + class_name += " #{options[:class]}" if options[:class] + + error_messages = object.errors[attribute].join(', ') + error_messages = error_messages.html_safe if options[:html_safe_errors] + content_tag(:small, error_messages, + class: class_name.sub('is-invalid-input', '')) + end + + private + + def error?(attribute) + object.respond_to?(:errors) && !object.errors[attribute].blank? + end + + def default_label_text(object, attribute) + if object.class.respond_to?(:human_attribute_name) + return object.class.human_attribute_name(attribute) + end + + attribute.to_s.humanize + end + + def custom_label(attribute, text, options) + return block_given? ? yield.html_safe : ''.html_safe if text == false + + text = default_label_text(object, attribute) if text.nil? || text == true + text = safe_join([yield, text.html_safe]) if block_given? + label(attribute, text, options || {}) + end + + def column_classes(options) + classes = SizeClassCalculator.new(options).classes + classes + ' columns' + end + + def tag_from_options(name, options) + return ''.html_safe unless options && options[:value].present? + + content_tag(:div, + content_tag(:span, options[:value], class: name), + class: column_classes(options).to_s) + end + + def decrement_input_size(input, column, options) + return unless options.present? && options.key?(column) + + input.send("#{column}=", + (input.send(column) - options.fetch(column).to_i)) + input.send('changed?=', true) + end + + def calculate_input_size(prefix_options, postfix_options) + input_size = + OpenStruct.new(changed?: false, small: 12, medium: 12, large: 12) + %w(small medium large).each do |size| + decrement_input_size(input_size, size.to_sym, prefix_options) + decrement_input_size(input_size, size.to_sym, postfix_options) + end + + input_size + end + + def wrap_prefix_and_postfix(block, prefix_options, postfix_options) + prefix = tag_from_options('prefix', prefix_options) + postfix = tag_from_options('postfix', postfix_options) + + input_size = calculate_input_size(prefix_options, postfix_options) + klass = column_classes(input_size.marshal_dump).to_s + input = content_tag(:div, block, class: klass) + + return block unless input_size.changed? + content_tag(:div, prefix + input + postfix, class: 'row collapse') + end + + def error_and_help_text(attribute, options = {}) + html = '' + if options[:help_text] + html += content_tag(:p, options[:help_text], class: 'help-text') + end + html += error_for(attribute, options) || '' + html.html_safe + end + + def field_label(attribute, options) + return ''.html_safe unless auto_labels || options[:label] + custom_label(attribute, options[:label], options[:label_options]) + end + + def field(attribute, options, html_options = nil) + html = field_label(attribute, options) + class_options = html_options || options + + if error?(attribute) + class_options[:class] = class_options[:class].to_s + class_options[:class] += ' is-invalid-input' + end + + options.delete(:label) + options.delete(:label_options) + help_text = options.delete(:help_text) + prefix = options.delete(:prefix) + postfix = options.delete(:postfix) + + html += wrap_prefix_and_postfix(yield(class_options), prefix, postfix) + html + error_and_help_text(attribute, options.merge(help_text: help_text)) + end + + def auto_labels + if @options[:auto_labels].nil? + FoundationRailsHelper.configuration.auto_labels + else + @options[:auto_labels] + end + end + end +end