web-dev-qa-db-fra.com

Comment modifier un Rails champ sérialisé dans un formulaire?

J'ai un modèle de données dans mon projet Rails qui a un champ sérialisé:

class Widget < ActiveRecord::Base
  serialize :options
end

Le champ d'options peut avoir des informations de données variables. Par exemple, voici le champ d'options pour un enregistrement du fichier fixtures:

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

Ma question est la suivante: comment permettre à un utilisateur de modifier ces informations dans une vue de formulaire standard?

Si vous utilisez simplement un champ de zone de texte simple pour le champ d'options, vous obtiendrez simplement une représentation de vidage yaml et ces données seront simplement renvoyées sous forme de chaîne.

Quelle est la meilleure/bonne façon de modifier un champ de hachage sérialisé comme celui-ci dans Rails?

56
cpjolicoeur

Si vous savez à l'avance quelles seront les touches d'options, vous pouvez déclarer des getters et des setters spéciaux pour eux comme ceci:

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

La bonne chose à ce sujet est qu'il expose les composants du tableau d'options en tant qu'attributs, ce qui vous permet d'utiliser les aides de formulaire Rails comme ceci:

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

Eh bien, j'ai eu le même problème et j'ai essayé de ne pas le sur-concevoir. Le problème est que, bien que vous puissiez passer le hachage sérialisé à fields_for, les champs de la fonction penseront, c'est un hachage optionnel (et non votre objet) et définissez l'objet de formulaire sur nil. Cela signifie que bien que vous puissiez modifier les valeurs, elles n'apparaîtront pas après la modification. Il peut s'agir d'un bug ou d'un comportement inattendu de Rails et peut-être corrigé dans le futur.

Cependant, pour l'instant, il est assez facile de le faire fonctionner (même s'il m'a fallu toute la matinée pour le comprendre).

Vous pouvez laisser votre modèle tel quel et dans la vue, vous devez donner aux champs de l'objet une structure ouverte. Cela définira correctement l'objet d'enregistrement (donc f2.object renverra vos options) et deuxièmement, cela permet au générateur text_field d'accéder à la valeur à partir de votre objet/paramètres.

Comme j'ai inclus "|| {}", cela fonctionnera également avec les nouveaux formulaires/create.

= 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

Passez une bonne journée

38
Christian Butzke

emh est presque là. Je pense que Rails renverrait les valeurs dans les champs du formulaire, mais ce n'est pas le cas. Vous pouvez donc simplement les insérer manuellement dans le paramètre ": value =>" pour chaque champ. Il ne semble pas lisse, mais cela fonctionne.

Le voici de haut en bas:

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

Tout champ que vous ajoutez dans "fields_for" apparaîtra dans le hachage sérialisé. Vous pouvez ajouter ou supprimer des champs à volonté. Ils seront passés en tant qu'attributs au hachage "options" et stockés en tant que YAML.

29
sbzoom

Je me bats avec un problème très similaire. Les solutions que j'ai trouvées ici m'ont été très utiles. Merci @austinfromboston, @ Christian-Butske, @sbzoom et tout le monde. Cependant, je pense que ces réponses pourraient être légèrement obsolètes. Voici ce qui a fonctionné pour moi avec Rails 5 et Ruby 2.3:

Sous la forme:

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

puis dans le contrôleur, j'ai dû mettre à jour les paramètres forts comme ceci:

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

Il semble important que le paramètre de hachage sérialisé arrive à la fin de la liste des paramètres. Sinon, Rails s'attendra à ce que le paramètre suivant soit également un hachage sérialisé.

Dans la vue, j'ai utilisé une logique simple si/alors pour afficher uniquement le hachage s'il n'est pas vide et pour afficher uniquement les paires clé/valeur où la valeur n'était pas nulle.

4
JDenman6

Pas besoin de setter/getters, je viens de définir dans le modèle:

serialize :content_hash, Hash

Ensuite, dans la vue, je fais (avec simple_form, mais similaire avec 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}

Mon dernier problème est de savoir comment laisser l'utilisateur ajouter une nouvelle paire clé/valeur.

3
gamov

Je proposerai quelque chose de simple, car tout le temps, lorsque l'utilisateur enregistrera le formulaire, vous obtiendrez une chaîne. Vous pouvez donc utiliser par exemple avant de filtrer et d'analyser ces données comme ça:

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

bien sûr, vous devez ajouter la validation si c'est correct YAML. Mais ça devrait marcher.

2
mtfk

J'étais confronté au même problème, après quelques recherches, j'ai trouvé une solution utilisant Rails ' store_accessor pour rendre les clés d'une colonne sérialisée accessibles en tant qu'attributs.

Avec cela, nous pouvons accéder aux attributs "imbriqués" d'une colonne sérialisée…

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

J'essaie de faire quelque chose de similaire et j'ai trouvé ce genre d'œuvres:

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

sauf que les champs du formulaire ne sont pas remplis lors du rendu du formulaire. lorsque le formulaire est soumis, les valeurs se présentent très bien et je peux faire:

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

donc sa "moitié" fonctionne ...

1
emh