将多输入虚拟属性移动到SimpleForm自定义输入组件

boo*_*sey 13 ruby-on-rails simple-form

高度以英寸为单位存储在数据库中.

但是,英尺和英寸需要以下列形式输入:

Height: [_______] feet [_______] inches
Run Code Online (Sandbox Code Playgroud)

所以我使用了虚拟属性,并使其正常工作.这是我的模型的简化版本:

class Client < ActiveRecord::Base
  attr_accessible :name, :height_feet, :height_inches

  before_save :combine_height

    def height_feet
      height.floor/12 if height
    end

    def height_feet=(feet)
      @feet = feet
    end

    def height_inches
      if height && height%12 != 0
        height%12
      end
    end

    def height_inches=(inches) #on save?
      @inches = inches
    end

  def combine_height
    self.height = @feet.to_d*12 + @inches.to_d #if @feet.present?
  end

end
Run Code Online (Sandbox Code Playgroud)

_form部分使用simple_form:

<%= simple_form_for(@client) do |f| %>
  <ul>
    <%= f.error_notification %>
    <%= f.input :name %>
    <%= f.input :weight %>
    <li>
      <%= f.input :height_feet, :label => 'Height', :wrapper => false %>
      <span>feet</span>
      <%= f.input :height_inches, :label => false, :wrapper => false %>
      <span>inches</span>
    </li>
    <%= f.error :base %>
  </ul>
    <%= f.button :submit %>
<% end %>
Run Code Online (Sandbox Code Playgroud)

这有效.不过这是不理想的.

我想干这个并创建一个自定义输入组件,这样我就可以用<%= f.input :height, as: :feet_and_inch %>- 以及因此任何其他输入遵循相同模式的高度添加到表单中<%= f.input :wingspan, as: :feet_and_inch %>.

我已经尝试过自定义组件,但我无法获得两个输入显示 - 而且我不确定哪里是将'转换'逻辑从英尺和英寸放到英寸的最佳位置(同样从英寸返回到英尺和英寸).

Jiř*_*šil 6

据我所知,除了渲染到自定义输入之外,你无法真正移动任何东西.提交表单后不会调用SimpleForm,因此无法以任何方式真正干扰值.我也喜欢错误,因为我过去也需要它.无论如何,这是一个将转换逻辑保留在模型中的版本.

自定义SimpleForm输入:

# app/inputs/feet_and_inch_input.rb
class FeetAndInchInput < SimpleForm::Inputs::Base
  def input
    output           = ""
    label            = @options.fetch(:label) { @attribute_name.to_s.capitalize }

    feet_attribute   = "#{attribute_name}_feet".to_sym
    inches_attribute = "#{attribute_name}_inches".to_sym

    output << @builder.input(feet_attribute, wrapper: false, label: label)
    output << template.content_tag(:span, " feet ")
    output << @builder.input(inches_attribute, wrapper: false, label: false)
    output << template.content_tag(:span, " inches ")

    output.html_safe
  end

  def label
    ""
  end
end
Run Code Online (Sandbox Code Playgroud)

表格.请注意,我没有将<li>标签放在自定义输入中,我认为这样更灵活,但随时可以更改它.

# app/views/clients/_form.html.erb
<li>
  <%= f.input :height, as: :feet_and_inch %>
</li>
Run Code Online (Sandbox Code Playgroud)

所有这一切都依赖于一个事实,对于每一个height属性,你也有height_feetheight_inches属性.

现在对于模型,我不确定这是否可行,也许有人可能会提出更好的解决方案,但在这里:

class Client < ActiveRecord::Base
  attr_accessible :name

  ["height", "weight"].each do |attribute|
    attr_accessible "#{attribute}_feet".to_sym
    attr_accessible "#{attribute}_inches".to_sym

    before_save do
      feet  = instance_variable_get("@#{attribute}_feet_ins_var").to_d
      inches = instance_variable_get("@#{attribute}_inches_ins_var").to_d

      self.send("#{attribute}=", feet*12 + inches)
    end

    define_method "#{attribute}_feet" do
      value = self.send(attribute)
      value.floor / 12 if value
    end

    define_method "#{attribute}_feet=" do |feet|
      instance_variable_set("@#{attribute}_feet_ins_var", feet)
    end

    define_method "#{attribute}_inches=" do |inches|
      instance_variable_set("@#{attribute}_inches_ins_var", inches)
    end

    define_method "#{attribute}_inches" do
      value = self.send(attribute)
      value % 12 if value && value % 12 != 0
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

它基本上是相同的,但动态定义方法.您可以在顶部看到您希望生成这些方法的属性列表.

请注意,所有这些都没有经过彻底的测试,可能会杀死你的猫,但希望可以给你一些想法.