Как отредактировать сериализованное поле Rails в форме?
У меня есть модель данных в моем проекте 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?
Ответы
Ответ 1
Если вы знаете, какие ключи опций будут заранее, вы можете объявить для них специальные геттеры и сеттеры следующим образом:
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
Ответ 2
Ну, у меня была такая же проблема, и я старался не переучивать ее. Проблема в том, что, хотя вы можете передать сериализованный хеш в поля_for, поля для функции будут думать, это хэш-вариант (а не ваш объект) и установить для объекта формы значение nil. Это означает, что, хотя вы можете редактировать значения, они не появятся после редактирования. Это может быть ошибка или неожиданное поведение рельсов и, возможно, исправление в будущем.
Однако на данный момент довольно легко заставить его работать (хотя мне потребовалось все утро, чтобы понять).
Вы можете оставить модель как есть, и в представлении вам нужно указать поля для объекта как открытой структуры. Это правильно установит объект записи (так что f2.object вернет ваши параметры), а во-вторых, позволяет конструктору text_field получить доступ к значению из вашего объекта /params.
Так как я включил "|| {}", он также будет работать с новыми/создаваемыми формами.
= 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
Отличный день
Ответ 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", будет отображаться в сериализованном хэше. Вы можете добавлять или удалять поля по своему усмотрению. Они будут переданы как атрибуты хеша "options" и сохранены как YAML.
Ответ 4
Не требуется 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
Я боролся с очень похожей проблемой. Решения, которые я нашел здесь, были очень полезны для меня. Спасибо @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, чтобы отображать только хэш, если он не пуст, а затем отображать только пары ключ/значение, где значение не равно нулю.
Ответ 6
Я пытаюсь сделать что-то подобное, и я нашел такие работы:
<%= 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
Я предлагаю что-то простое, потому что все время, когда пользователь будет сохранять форму, вы получите строку. Таким образом, вы можете использовать, например, перед фильтром и анализировать такие данные:
before_save do
widget.options = YAML.parse(widget.options).to_ruby
end
Конечно, вы должны добавить проверку, если это правильный YAML.
Но он должен работать.