如何在表单中编辑 Rails 序列化字段?

Posted

技术标签:

【中文标题】如何在表单中编辑 Rails 序列化字段?【英文标题】:How to edit a Rails serialized field in a form? 【发布时间】:2010-11-03 10:18:39 【问题描述】:

我的 Rails 项目中有一个具有序列化字段的数据模型:

class Widget < ActiveRecord::Base
  serialize :options
end

选项字段可以包含可变数据信息。例如,这里是来自夹具文件的一条记录的选项字段:

  options:
    query_id: 2 
    axis_y: 'percent'
    axis_x: 'text'
    units: '%'
    css_class: 'occupancy'
    dom_hook: '#average-occupancy-by-day'
    table_scale: 1

我的问题是让用户在标准表单视图中编辑此信息的正确方法是什么?

如果您只是为选项字段使用一个简单的文本区域字段,您只会得到一个 yaml 转储表示,并且该数据将作为字符串发送回。

在 Rails 中编辑这样的序列化哈希字段的最佳/正确方法是什么?

【问题讨论】:

你想要什么样的界面?每个属性都应该有字段吗?你知道前面所有的属性是什么吗? 所有属性都不会预先知道。有些是标准的并且将始终存在,但其余的可以由用户定义。这是一个仅限管理员的界面,因此我对用户输入的信任程度比平时要大得多。我实际上只是使用了一个 textarea 框,让用户使用 YAML 标记输入键:值对,它一直运行良好 【参考方案1】:

如果您提前知道选项键是什么,您可以为它们声明特殊的 getter 和 setter,如下所示:

class Widget < ActiveRecord::Base
  serialize :options

  def self.serialized_attr_accessor(*args)
    args.each do |method_name|
      eval "
        def #method_name
          (self.options || )[:#method_name]
        end
        def #method_name=(value)
          self.options ||= 
          self.options[:#method_name] = value
        end
        attr_accessible :#method_name
      "
    end
  end

  serialized_attr_accessor :query_id, :axis_y, :axis_x, :units
end

这样做的好处是它将选项数组的组件公开为属性,这允许您像这样使用 Rails 表单助手:

#haml
- form_for @widget do |f|
  = f.text_field :axis_y
  = f.text_field :axis_x
  = f.text_field :unit

【讨论】:

谢谢,但我不提前知道所有选项值。其中一些将始终存在,但其余的可以由用户定义。 是的,动态创建这些访问器会很棒 在 eval 的末尾添加很好:attr_accessible method_name 解决方案是否也在保存时更新哈希值?甚至不是所有的键都在形式?它不会删除现有的记录吗?【参考方案2】:

好吧,我也遇到了同样的问题,并尽量不要过度设计它。问题是,尽管您可以将序列化哈希传递给 fields_for,但函数的字段会认为它是一个选项哈希(而不是您的对象),并将表单对象设置为 nil。这意味着,尽管您可以编辑这些值,但它们在编辑后不会出现。这可能是 Rails 的错误或意外行为,将来可能会修复。

不过,就目前而言,让它工作起来很容易(尽管我花了整个上午才弄明白)。

你可以让你的模型保持原样,在视图中你需要为对象提供字段作为一个开放的结构。这将正确设置记录对象(因此 f2.object 将返回您的选项),其次它允许 text_field 构建器从您的对象/参数访问值。

由于我包含了“|| ”,因此它也适用于新/创建表单。

= form_for @widget do |f|
  = f.fields_for :options, OpenStruct.new(f.object.options || ) do |f2|
    = f2.text_field :axis_y
    = f2.text_field :axis_x
    = f2.text_field :unit

祝你有美好的一天

【讨论】:

干得好。这个提示为我节省了今晚的时间。我没有想到对象是一个散列这一事实意味着它会被解释为一个选项散列,但如果这确实是发生的事情,那就很有意义了。 我真的很喜欢这个解决方案,我已经将它添加到我的模型中以完成它:def options OpenStruct.new self[:options] || end,所以你在读取属性时总是会得到 OpenStruct.. 我觉得有个小错误:你应该在f2上调用text_field,而不是f 感谢 Sidhannowe 的提示,我已修复它^_^ 解决方案是否也在保存时更新hash值?【参考方案3】:

emh 快到了。我认为 Rails 会将值返回到表单字段,但事实并非如此。因此,您可以手动将其放入每个字段的 ":value =>" 参数中。它看起来并不光滑,但它确实有效。

这里是从上到下:

class Widget < ActiveRecord::Base
    serialize :options, Hash
end

<%= form_for :widget, @widget, :url => :action => "update", :html => :method => :put do |f| %>
<%= f.error_messages %>
    <%= f.fields_for :options do |o| %>
        <%= o.text_field :axis_x, :size => 10, :value => @widget.options["axis_x"] %>
        <%= o.text_field :axis_y, :size => 10, :value => @widget.options["axis_y"] %>
    <% end %>
<% end %>

您在“fields_for”中添加的任何字段都将显示在序列化哈希中。您可以随意添加或删除字段。它们将作为属性传递给“选项”哈希并存储为 YAML。

【讨论】:

我正在尝试按照你说的做,但它似乎对我不起作用:***.com/questions/6621341【参考方案4】:

我一直在努力解决一个非常相似的问题。我在这里找到的解决方案对我很有帮助。谢谢@austinfromboston、@Christian-Butske、@sbzoom 和其他所有人。但是,我认为这些答案可能有点过时了。以下是 Rails 5 和 ruby​​ 2.3 对我有用的方法:

形式:

<%= f.label :options %>
<%= f.fields_for :options do |o| %>
  <%= o.label :axis_y %>
  <%= o.text_field :axis_y %>
  <%= o.label :axis_x %>
  <%= o.text_field :axis_x %>
  ...
<% end %>

然后在控制器中我必须像这样更新强参数:

def widget_params
    params.require(:widget).permit(:any, :regular, :parameters, :options => [:axis_y, :axis_x, ...])
end

序列化的散列参数出现在参数列表的末尾似乎很重要。否则,Rails 会期望下一个参数也是一个序列化的散列。

在视图中,我使用了一些简单的 if/then 逻辑,仅在哈希不为空时显示哈希,然后仅显示值不为 nil 的键/值对。

【讨论】:

这适用于存储值,但是当您打开edit 表单时,您看不到您保存的值 @YaroShm 我已经有一段时间没有访问这个问题了。我记得强参数有点棘手,过去一年对 rails 的更新可能已经改变了必要的语法。它可能需要一些仔细的调试,并且可能需要一点幸运才能让它工作。当你这样做时,请在此处发布技巧。它将帮助将来找到此帖子的其他开发人员。祝你好运!【参考方案5】:

我遇到了同样的问题,经过一番研究后,我找到了一个解决方案,使用 Rails 的store_accessor 使序列化列的键可作为属性访问。

通过这个我们可以访问序列化列的“嵌套”属性……

# post.rb
class Post < ApplicationRecord
  serialize :options
  store_accessor :options, :value1, :value2, :value3
end

# set / get values
post = Post.new
post.value1 = "foo"
post.value1
#=> "foo"
post.options['value1']
#=> "foo"

# strong parameters in posts_controller.rb
params.require(:post).permit(:value1, :value2, :value3)

# form.html.erb
<%= form_with model: @post, local: true do |f| %>
  <%= f.label :value1 %>
  <%= f.text_field :value1 %>
  # …
<% end %>

【讨论】:

太棒了,这正是我一直在寻找的。现在,根据文档,您可以使用 store :options, :value1, :value2 api.rubyonrails.org/classes/ActiveRecord/Store.html【参考方案6】:

不需要setter/getter,我只是在模型中定义的:

serialize :content_hash, Hash

然后在视图中,我这样做(使用 simple_form,但与 vanilla Rails 类似):

  = f.simple_fields_for :content_hash do |chf|
    - @model_instance.content_hash.each_pair do |k,v|
      =chf.input k.to_sym, :as => :string, :input_html => :value => v

我的最后一个问题是如何让用户添加一个新的键/值对。

【讨论】:

大部分工作,除了我正在处理一个深度哈希并且只有根级别最终作为一个哈希。【参考方案7】:

我会建议一些简单的东西,因为一直以来,当用户保存表单时,你会得到字符串。因此,您可以在过滤和解析这些数据之前使用例如:

before_save do
  widget.options = YAML.parse(widget.options).to_ruby
end 

当然,如果这是正确的 YAML,您应该添加验证。 但它应该有效。

【讨论】:

【参考方案8】:

我正在尝试做类似的事情,我发现了这种作品:

<%= form_for @search do |f| %>
    <%= f.fields_for :params, @search.params do |p| %>
        <%= p.select "property_id", [[ "All", 0 ]] + PropertyType.all.collect  |pt| [ pt.value, pt.id ]  %>

        <%= p.text_field :min_square_footage, :size => 10, :placeholder => "Min" %>
        <%= p.text_field :max_square_footage, :size => 10, :placeholder => "Max" %>
    <% end %>
<% end %>

除了在呈现表单时不填充表单字段。提交表单后,值就可以通过了,我可以这样做:

@search = Search.new(params[:search])

所以它的“一半”工作......

【讨论】:

以上是关于如何在表单中编辑 Rails 序列化字段?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ActiveAdmin 表单块中使用 rails 6 ActionText 字段

Rails cocoon嵌套字段,在编辑表单上呈现每个字段旁边的现有图像

如何使用相同的表单在 ruby​​ on rails 中创建和编辑

仅当用户选择rails表单中的特定选项时,如何显示字段

如何使用用户定义的字段数制作表单? - Ruby on Rails

Rails 表单,如何使用 jquery 设置隐藏字段的值