从具有简单形式的散列动态创建输入

Creating inputs dynamically from a hash with simple form

我有一个 Configuration 模型,它在 JSONB 列中存储 settings

# Stores site wide configuration settings
# This is used in conjunction with PaperTrail to make each change traceable
class Configuration < ActiveRecord::Base
  has_paper_trail
  validates_uniqueness_of :name
  after_initialize :default_values if :new_record?

  private
  def default_values
    self.settings = {
        site_title: 'Portfolio Site'
    }
  end
end

模型设置了某些默认属性,例如site_title。我想使用设置哈希在我的表单中动态创建输入。我试过:

= simple_form_for(@configuration) do |f|
  = f.input :name

  = simple_fields_for :settings do
    - @configuration.settings.each do |k,v|
      = f.input k

  = f.submit

这给出 undefined method 'site_title' 因为它不是模型属性。我可以在指定键和值绑定时使用 simple_forms input 方法吗?

看起来您不能将 simple_form 用于非属性输入。不过这会起作用:

= simple_form_for(@configuration) do |f|
  = f.input :name

  - @configuration.settings.each do |k,v|
    = text_field_tag "configurations[settings][#{k}]", v 

  = f.submit

您可能会扩展默认设置的值,以包含您希望通过 array 呈现的输入类型。

它实际上就像为散列创建一个代理对象或装饰器一样简单——它就像一个模型。

class SettingsDecorator

  INPUT_TYPES = {
      some_number: :integer
  }

  # @see http://api.rubyonrails.org/classes/ActiveModel/Naming.html
  MODEL_NAME = ActiveModel::Name.new(self.class, nil, 'settings')

  # @see http://api.rubyonrails.org/classes/ActiveModel/Naming.html
  def model_name
    MODEL_NAME
  end

  def initialize(hash)
    @object = hash.symbolize_keys
  end

  def method_missing(method, *args, &block)
    if @object.key? method
       @object[method]
    elsif @object.respond_to? method
       @object.send(method, *args, &block)
    end
  end

  def has_attribute? attr
    @object.key? attr
  end

  def input_type? key
    INPUT_TYPES[key]
  end
end

简单表单尝试查找每个输入的列,但如果对象不响应 column_for_attribute.

,则会优雅地回退到 :text

然后我可以像这样使用它:

= simple_form_for(@configuration) do |f|
  = f.input :name
  - settings = SettingsDecorator.new(@configuration.settings)
  = simple_fields_for settings do |sf|
    - settings.keys.each do |key|
      = sf.input key, as: settings.input_type?(key)
  = f.submit