From 7f03fe867cfaf4003841f76e1fb606e183972d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Ju=C3=A1rez?= Date: Mon, 1 Apr 2024 21:54:20 -0600 Subject: [PATCH 1/3] Add support to the `input` tag --- lib/action_view/helpers/dynamic_form.rb | 42 +++++-------------------- test/dynamic_form_test.rb | 18 ++++++----- 2 files changed, 19 insertions(+), 41 deletions(-) diff --git a/lib/action_view/helpers/dynamic_form.rb b/lib/action_view/helpers/dynamic_form.rb index ecc171a..a73073f 100644 --- a/lib/action_view/helpers/dynamic_form.rb +++ b/lib/action_view/helpers/dynamic_form.rb @@ -11,14 +11,18 @@ module Helpers # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form. # In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html module DynamicForm + DEFAULT_FIELD_OPTION_MAXLENGTH = 30 # Returns a default input tag for the type of object returned by the method. For example, if @post # has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World": # # input("post", "title") - # # => - def input(record_name, method, options = {}) - raise_broken_code_error - InstanceTag.new(record_name, method, self).to_tag(options) + # # => + # + # input("post", "title", "maxlength" => 10) + # # => + def input(object_name, method, options = {}) + options["maxlength"] = DEFAULT_FIELD_OPTION_MAXLENGTH unless options.key?("maxlength") + Tags::TextField.new(object_name, method, self, options).render end # Returns an entire form with all needed input tags for a specified Active Record object. For example, if @post @@ -262,32 +266,6 @@ def default_input_block Proc.new { |record, column| %(


#{input(record, column.name)}

) } end - module InstanceTagMethods - def to_tag(options = {}) - case column_type - when :string - field_type = @method_name.include?("password") ? "password" : "text" - to_input_field_tag(field_type, options) - when :text - to_text_area_tag(options) - when :integer, :float, :decimal - to_input_field_tag("text", options) - when :date - to_date_select_tag(options) - when :datetime, :timestamp - to_datetime_select_tag(options) - when :time - to_time_select_tag(options) - when :boolean - to_boolean_select_tag(options).html_safe - end - end - - def column_type - object.send(:column_for_attribute, @method_name).type - end - end - module FormBuilderMethods def error_message_on(method, *args) @template.error_message_on(@object || @object_name, method, *args) @@ -299,10 +277,6 @@ def error_messages(options = {}) end end - class InstanceTag - include DynamicForm::InstanceTagMethods - end - class FormBuilder include DynamicForm::FormBuilderMethods end diff --git a/test/dynamic_form_test.rb b/test/dynamic_form_test.rb index 0a5ec72..e2e8dca 100644 --- a/test/dynamic_form_test.rb +++ b/test/dynamic_form_test.rb @@ -130,11 +130,15 @@ def url_for(options) end def test_generic_input_tag - assert_raise(BrokenFeatureError) do assert_dom_equal( - %(), input("post", "title") + %(), input("post", "title") + ) + end + + def test_input_tag_with_maxlength + assert_dom_equal( + %(), input("post", "title", "maxlength" => 10) ) - end end def test_text_area_with_errors @@ -225,10 +229,10 @@ def inner_test_form_with_method_option def test_form_with_action_option assert_raise(BrokenFeatureError) do - output_buffer << form("post", :action => "sign") - assert_select "form[action=sign]" do |form| - assert_select "input[type=submit][value=Sign]" - end + output_buffer << form("post", :action => "sign") + assert_select "form[action=sign]" do |form| + assert_select "input[type=submit][value=Sign]" + end end end From 11663fe1f6c109cdec50e5139d7a782caa765f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20Ju=C3=A1rez?= Date: Tue, 2 Apr 2024 22:04:16 -0600 Subject: [PATCH 2/3] Add support for extra column types --- lib/action_view/helpers/dynamic_form.rb | 60 ++++++++++++++++++- test/dynamic_form_test.rb | 76 ++++++++++++++++++++++--- 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/lib/action_view/helpers/dynamic_form.rb b/lib/action_view/helpers/dynamic_form.rb index a73073f..59d838f 100644 --- a/lib/action_view/helpers/dynamic_form.rb +++ b/lib/action_view/helpers/dynamic_form.rb @@ -11,7 +11,6 @@ module Helpers # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form. # In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html module DynamicForm - DEFAULT_FIELD_OPTION_MAXLENGTH = 30 # Returns a default input tag for the type of object returned by the method. For example, if @post # has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World": # @@ -21,8 +20,7 @@ module DynamicForm # input("post", "title", "maxlength" => 10) # # => def input(object_name, method, options = {}) - options["maxlength"] = DEFAULT_FIELD_OPTION_MAXLENGTH unless options.key?("maxlength") - Tags::TextField.new(object_name, method, self, options).render + InputBuilder.new(object_name, method, self, options).to_tag end # Returns an entire form with all needed input tags for a specified Active Record object. For example, if @post @@ -266,6 +264,58 @@ def default_input_block Proc.new { |record, column| %(


#{input(record, column.name)}

) } end + + module InputBuilderMethods + DEFAULT_MAXLENGTH = 30 + + def initialize(object_name, method, context, options) + @object_name = object_name + @context = context + @method = method + @options = options + end + + def to_tag + case column_type + when :string + @options["type"] = @method.include?("password") ? "password" : "text" + Tags::TextField.new(*generic_args).render + when :text + Tags::TextArea.new(*generic_args).render + when :integer, :float, :decimal + Tags::TextField.new(*generic_args).render + when :date + Tags::DateField.new(*generic_args).render + when :datetime, :timestamp + Tags::DatetimeLocalField.new(*generic_args).render + when :time + Tags::TimeField.new(*generic_args).render + when :boolean + Tags::CheckBox.new(@object_name, @method, @context, "1", "0", options_with_default).render + end + end + + private + + def options_with_default + @options.tap do |options| + options["maxlength"] = DEFAULT_MAXLENGTH unless @options.key?("maxlength") + end + end + + def generic_args + [ @object_name, @method, @context, options_with_default ] + end + + def object + @context.instance_variable_get("@#{@object_name}") + end + + def column_type + object.send(:column_for_attribute, @method).type + end + end + module FormBuilderMethods def error_message_on(method, *args) @template.error_message_on(@object || @object_name, method, *args) @@ -277,6 +327,10 @@ def error_messages(options = {}) end end + class InputBuilder + include DynamicForm::InputBuilderMethods + end + class FormBuilder include DynamicForm::FormBuilderMethods end diff --git a/test/dynamic_form_test.rb b/test/dynamic_form_test.rb index e2e8dca..c917253 100644 --- a/test/dynamic_form_test.rb +++ b/test/dynamic_form_test.rb @@ -10,12 +10,12 @@ def form_for(*) end silence_warnings do - class Post < Struct.new(:title, :author_name, :body, :secret, :written_on) + class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :published_on, :started_at, :published) extend ActiveModel::Naming include ActiveModel::Conversion end - class User < Struct.new(:email) + class User < Struct.new(:email, :password) extend ActiveModel::Naming include ActiveModel::Conversion end @@ -78,14 +78,27 @@ def @post.column_for_attribute(attr_name) end silence_warnings do - def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end + def Post.content_columns() + [ + Column.new(:string, "title", "Title"), + Column.new(:text, "body", "Body"), + Column.new(:integer, "secret", "Secret"), + Column.new(:date, "written_on", "Written On"), + Column.new(:datetime, "published_on", "Published On"), + Column.new(:time, "started_at", "Started At"), + Column.new(:boolean, "published", "Published") + ] + end end - @post.title = "Hello World" - @post.author_name = "" - @post.body = "Back to the hill and over it again!" - @post.secret = 1 - @post.written_on = Date.new(2004, 6, 15) + @post.title = "Hello World" + @post.author_name = "" + @post.body = "Back to the hill and over it again!" + @post.secret = 1 + @post.written_on = Date.new(2004, 6, 15) + @post.published_on = Date.new(2005, 8, 23) + @post.started_at = Time.new(2024, 4, 2, 21, 44, 10) + @post.published = true end def setup_user @@ -107,10 +120,11 @@ def @user.column_for_attribute(attr_name) end silence_warnings do - def User.content_columns() [ Column.new(:string, "email", "Email") ] end + def User.content_columns() [ Column.new(:string, "email", "Email"), Column.new(:string, "password", "Password") ] end end @user.email = "" + @user.password = "password" end def protect_against_forgery? @@ -141,6 +155,50 @@ def test_input_tag_with_maxlength ) end + def test_input_for_password + assert_dom_equal( + %(), input("user", "password") + ) + end + + def test_input_for_text + def @post.errors; end + + assert_dom_equal( + %(), input("post", "body") + ) + end + + def test_input_tag_for_integer + assert_dom_equal( + %(), input("post", "secret") + ) + end + + def test_input_tag_for_date + assert_dom_equal( + %(), input("post", "written_on") + ) + end + + def test_input_tag_for_datetime + assert_dom_equal( + %(), input("post", "published_on") + ) + end + + def test_input_tag_for_time + assert_dom_equal( + %(), input("post", "started_at") + ) + end + + def test_input_tag_for_boolean + assert_dom_equal( + %(), input("post", "published") + ) + end + def test_text_area_with_errors expected_dom = %(