heartcombo / simple_form

Forms made easy for Rails! It's tied to a simple DSL, with no opinion on markup.
http://blog.plataformatec.com.br/tag/simple_form
MIT License
8.21k stars 1.31k forks source link

Improve docs on Non Active Record #1839

Closed abinoam closed 2 months ago

abinoam commented 4 months ago

SimpleForm relies on has_attribute? and type_for_attribute to infer attribute type so it may generate the correct input type according to its mapping.

I am currently trying to improve Ransack / SimpleForm integration at PR https://github.com/activerecord-hackery/ransack/pull/1487 It was a little hard to me to guess how this was done internally. I wish I've had seen issue #1666 before.

This PR aims to improve the documentation regarding the use of SimpleForm with non-ActiveRecord objects, making it easier for others to implement and understand.

To ensure the documentation's correctness I am also providing a sample test of the example code.

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails"
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"
  gem "sqlite3"
  gem "simple_form"
  gem "byebug"
end

require "active_record"
require "action_view"
require "minitest/autorun"
require "logger"
require "byebug"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :people, force: true do |t|
    t.string  :name
    t.integer :age
    t.boolean :registered

    t.timestamps
  end
end

class Person < ActiveRecord::Base
end

class NonArPerson < Struct.new(:id, :name, :age, :registered)
  def to_model
    self
  end

  def model_name
    OpenStruct.new(param_key: "person")
  end

  def to_key
    id
  end

  def persisted?
    id.present?
  end

  def has_attribute?(attr_name)
    %w(id name age registered).include?(attr_name.to_s)
  end

  def type_for_attribute(attr_name)
    case attr_name.to_s
      when "id" then OpenStruct.new(type: :integer)
      when "name" then OpenStruct.new(type: :string)
      when "age" then OpenStruct.new(type: :integer)
      when "registered" then OpenStruct.new(type: :boolean)
    end
  end
end

class BugTest < ActionView::TestCase

  def test_ar_on_simple_form
    @person = Person.create!(name: "John", age: 30)

    render inline: <<~ERB, locals: { person: @person }
      <%= simple_form_for person, url: "/people" do |f| %>
        <%= f.input :name %>
        <%= f.input :age %>
        <%= f.input :registered %>
      <% end %>
    ERB

    # Each one with the correct type
    assert_not_empty css_select(".string #person_name")
    assert_not_empty css_select(".integer #person_age")
    assert_not_empty css_select(".boolean #person_registered")

    # Age and registered are not strings
    assert_empty css_select(".string #person_age")
    assert_empty css_select(".string #person_registered")

    # Name and registered are not integers
    assert_empty css_select(".integer #person_name")
    assert_empty css_select(".integer #person_registered")

    # Name and age are not booleans
    assert_empty css_select(".boolean #person_name")
    assert_empty css_select(".boolean #person_age")
  end

  def test_non_ar_on_simple_form
    @person = NonArPerson.new

    render inline: <<~ERB, locals: { person: @person }
      <%= simple_form_for person, url: "/people" do |f| %>
        <%= f.input :name %>
        <%= f.input :age %>
        <%= f.input :registered %>
      <% end %>
    ERB

    # Each one with the correct type
    assert_not_empty css_select(".string #person_name")
    assert_not_empty css_select(".integer #person_age")
    assert_not_empty css_select(".boolean #person_registered")

    # Age and registered are not strings
    assert_empty css_select(".string #person_age")
    assert_empty css_select(".string #person_registered")

    # Name and registered are not integers
    assert_empty css_select(".integer #person_name")
    assert_empty css_select(".integer #person_registered")

    # Name and age are not booleans
    assert_empty css_select(".boolean #person_name")
    assert_empty css_select(".boolean #person_age")
  end
end
nashby commented 2 months ago

@abinoam thanks!