Ruby-on-rails 如何编辑表单中的 Rails 序列化字段?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1002963/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 21:22:06  来源:igfitidea点击:

How to edit a Rails serialized field in a form?

ruby-on-railsformsserialization

提问by cpjolicoeur

I have a data model in my Rails project that has a serialized field:

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

class Widget < ActiveRecord::Base
  serialize :options
end

The options field can have variable data info. For example, here is the options field for one record from the fixtures file:

选项字段可以具有可变数据信息。例如,这里是设备文件中一条记录的选项字段:

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

My question is what is the proper way to let a user edit this info in a standard form view?

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

If you just use a simple text area field for the options field, you would just get a yaml dump representation and that data would just be sent back as a string.

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

What is the best/proper way to edit a serialized hash field like this in Rails?

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

回答by austinfromboston

If you know what the option keys are going to be in advance, you can declare special getters and setters for them like so:

如果您事先知道选项键将是什么,您可以为它们声明特殊的 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

The nice thing about this is that it exposes the components of the options array as attributes, which allows you to use the Rails form helpers like so:

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

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

回答by Christian Butzke

Well, I had the same problem, and tried not to over-engineer it. The problem is, that although you can pass the serialized hash to fields_for, the fields for function will think, it is an option hash (and not your object), and set the form object to nil. This means, that although you can edit the values, they will not appear after editing. It might be a bug or unexpected behavior of rails and maybe fixed in the future.

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

However, for now, it is quite easy to get it working (though it took me the whole morning to figure out).

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

You can leave you model as is and in the view you need to give fields for the object as an open struct. That will properly set the record object (so f2.object will return your options) and secondly it lets the text_field builder access the value from your object/params.

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

Since I included " || {}", it will work with new/create forms, too.

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

= 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

Have a great day

祝你有美好的一天

回答by sbzoom

emh is almost there. I would think that Rails would return the values to the form fields but it does not. So you can just put it in there manually in the ":value =>" parameter for each field. It doesn't look slick, but it works.

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

Here it is from top to bottom:

这是从上到下:

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 %>

Any field you add in the "fields_for" will show up in the serialized hash. You can add or remove fields at will. They will be passed as attributes to the "options" hash and stored as YAML.

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

回答by JDenman6

I've been struggling with a very similar problem. The solutions I found here were very helpful to me. Thank you @austinfromboston, @Christian-Butske, @sbzoom, and everyone else. However, I think these answers might be slightly out-of-date. Here's what worked for me with Rails 5 and ruby 2.3:

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

In the form:

在形式:

<%= 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 %>

and then in the controller I had to update the strong parameters like so:

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

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

It seems to be important that the serialized hash parameter comes at the end of the list of parameters. Otherwise, Rails will expect the next parameter to also be a serialized hash.

序列化哈希参数位于参数列表的末尾似乎很重要。否则,Rails 会期望下一个参数也是一个序列化的哈希值。

In the view I used some simple if/then logic to only display the hash if it is not empty and then to only display key/value pairs where the value was not nil.

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

回答by R4ttlesnake

I was facing the same issue, after some research i found a solution using Rails' store_accessorto make keys of a serialized column accessible as attributes.

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

With this we can access "nested" attributes of a serialized column …

有了这个,我们可以访问序列化列的“嵌套”属性……

# 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 %>

回答by gamov

No need setter/getters, I just defined in the model:

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

serialize :content_hash, Hash

Then in the view, I do (with simple_form, but similar with vanilla Rails):

然后在视图中,我这样做(使用 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}

My last issue is how to let the user add a new key/value pair.

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

回答by mtfk

I will suggest something simple, because all the time, when user will save form You will get string. So You can use for example before filter and parse those data like that:

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

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

of course You should add validation if this is correct YAML. But it should works.

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

回答by emh

I'm trying to do something similar and I found this sort of works:

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

<%= 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 %>

except that the form fields aren't populated when the form is rendered. when the form is submitted the values come through just fine and i can do:

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

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

so its "half" working...

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