Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the input tag #4

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 52 additions & 24 deletions lib/action_view/helpers/dynamic_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ module DynamicForm
# has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World":
#
# input("post", "title")
# # => <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
def input(record_name, method, options = {})
raise_broken_code_error
InstanceTag.new(record_name, method, self).to_tag(options)
Copy link
Author

@okcomputer93 okcomputer93 Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was inspired by the deprecated class InstanceTag and relied on methods such as to_input_field_tag that are no longer available. Instead I'm using Tags::TextField or similars.

# # => <input id="post_title" name="post[title]" size="30" maxlength="30" type="text" value="Hello World" />
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find more value in adding the maxlength attribute as well, in addition to the size.

#
# input("post", "title", "maxlength" => 10)
# # => <input id="post_title" name="post[title]" size="10" maxlength="10" type="text" value="Hello World" />
def input(object_name, method, options = {})
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 <tt>@post</tt>
Expand Down Expand Up @@ -262,29 +264,55 @@ def default_input_block
Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
end

module InstanceTagMethods
def to_tag(options = {})

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
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
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).render
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, setting the maxlength attribute for a checkbox input doesn't make much sense

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense for this tag to be a checkbox input instead of a select, as previously https://api.rubyonrails.org/v3.1.3/classes/ActionView/Helpers/InstanceTag.html#method-i-to_boolean_select_tag

end
end

private

def options_with_default
@options.tap do |options|
options["maxlength"] = DEFAULT_MAXLENGTH unless @options.key?("maxlength")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the render methods sets the size attribute based on the maxlength option https://github.com/rails/rails/blob/main/actionview/lib/action_view/helpers/tags/text_field.rb#L11-L18

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_name).type
object.send(:column_for_attribute, @method).type
end
end

Expand All @@ -299,8 +327,8 @@ def error_messages(options = {})
end
end

class InstanceTag
include DynamicForm::InstanceTagMethods
class InputBuilder
include DynamicForm::InputBuilderMethods
end

class FormBuilder
Expand Down
94 changes: 78 additions & 16 deletions test/dynamic_form_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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?
Expand All @@ -130,11 +144,59 @@ def url_for(options)
end

def test_generic_input_tag
assert_raise(BrokenFeatureError) do
assert_dom_equal(
%(<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />), input("post", "title")
%(<input id="post_title" name="post[title]" maxlength="30" size="30" type="text" value="Hello World" />), input("post", "title")
)
end

def test_input_tag_with_maxlength
assert_dom_equal(
%(<input id="post_title" name="post[title]" maxlength="10" size="10" type="text" value="Hello World" />), input("post", "title", "maxlength" => 10)
)
end

def test_input_for_password
assert_dom_equal(
%(<input maxlength="30" type="password" size="30" value="password" name="user[password]" id="user_password" />), input("user", "password")
)
end

def test_input_for_text
def @post.errors; end

assert_dom_equal(
%(<textarea maxlength="30" name="post[body]" id="post_body">Back to the hill and over it again!</textarea>), input("post", "body")
)
end

def test_input_tag_for_integer
assert_dom_equal(
%(<input maxlength="30" size="30" type="text" value="1" name="post[secret]" id="post_secret" />), input("post", "secret")
)
end

def test_input_tag_for_date
assert_dom_equal(
%(<input maxlength="30" value="2004-06-15" size="30" type="date" name="post[written_on]" id="post_written_on" />), input("post", "written_on")
)
end

def test_input_tag_for_datetime
assert_dom_equal(
%(<input maxlength="30" value="2005-08-23T00:00:00" size="30" type="datetime-local" name="post[published_on]" id="post_published_on" />), input("post", "published_on")
)
end

def test_input_tag_for_time
assert_dom_equal(
%(<input maxlength="30" value="21:44:10.000" size="30" type="time" name="post[started_at]" id="post_started_at" />), input("post", "started_at")
)
end

def test_input_tag_for_boolean
assert_dom_equal(
%(<input name="post[published]" type="hidden" value="0" autocomplete="off" /><input type="checkbox" value="1" checked="checked" name="post[published]" id="post_published" />), input("post", "published")
)
end
end

def test_text_area_with_errors
Expand Down Expand Up @@ -225,10 +287,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

Expand Down