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

时间:2009-06-16 17:48:33

标签: ruby-on-rails forms serialization

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

class Widget < ActiveRecord::Base
  serialize :options
end

选项字段可以包含可变数据信息。例如,以下是fixtures文件中一条记录的选项字段:

  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中编辑这样的序列化哈希字段的最佳/正确方法是什么?

8 个答案:

答案 0 :(得分:57)

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

关于这个的好处是它将options数组的组件公开为属性,这允许你像这样使用Rails表单助手:

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

答案 1 :(得分:38)

嗯,我有同样的问题,并试图不过度设计它。问题是,尽管您可以将序列化哈希传递给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

度过美好的一天

答案 2 :(得分:29)

emh几乎就在那里。我认为Rails会将值返回到表单字段,但事实并非如此。所以你可以在“:value =&gt;”中手动将它放在那里每个字段的参数。它看起来不太光滑,但它确实有效。

这是从上到下:

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”中添加的任何字段都会显示在序列化哈希中。您可以随意添加或删除字段。它们将作为属性传递给“options”哈希并存储为YAML。

答案 3 :(得分:4)

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:

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.

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.

答案 4 :(得分:3)

不需要setter / getters,我刚才在模型中定义:

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}

我的上一期是如何让用户添加新的键/值对。

答案 5 :(得分:2)

我会建议一些简单的事情,因为所有的时间,当用户保存表格时你会得到字符串。因此,您可以在过滤之前使用例如并解析这些数据:

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

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

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

我遇到了同样的问题,经过一些研究,我发现了一种使用Rails的docs的解决方案,使序列化列的键可以作为属性来访问。

以此,我们可以访问序列化列的“嵌套”属性……

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