2009-06-16 10 views
50

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

class Widget < ActiveRecord::Base 
    serialize :options 
end 

Le champ d'options peut avoir d'informations de données variables. Par exemple, voici le champ d'options pour un enregistrement à partir 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 quelle est la bonne façon de laisser un utilisateur de modifier cette information en vue de formulaire standard?

Si vous utilisez simplement un champ de zone de texte simple pour le champ d'options, vous obtiendriez juste une représentation de vidage de yaml et ces données seraient juste renvoyées comme une chaîne.

Quelle est la meilleure/bonne façon d'éditer un champ de hachage sérialisé comme ceci dans Rails?

+0

Quel type d'interface souhaitez-vous? Devrait-il y avoir des champs pour chaque attribut? Savez-vous ce que tous les attributs sont à l'avant? –

+0

Tous les attributs ne seront pas connus à l'avance. Certains sont standards et seront toujours présents mais le reste peut être défini par l'utilisateur. Il s'agit d'une interface admin uniquement, donc je fais confiance à l'utilisateur dans une plus grande mesure que la normale. En fait, je viens d'utiliser une zone de texte et laisser l'utilisateur entrer la clé: paires de valeurs en utilisant le balisage YAML et cela a bien fonctionné à travers – cpjolicoeur

Répondre

55

Si vous savez ce que les touches d'options vont être à l'avance, vous pouvez déclarer getters spéciaux et setters 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 chose à ce sujet est qu'il expose les éléments du les options tableau comme attributs, ce qui vous permet d'utiliser les rails forment des aides comme ceci:

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

Merci, mais je ne connais pas toutes les valeurs d'option à l'avance. Certains d'entre eux seront toujours présents, mais le reste peut être défini par l'utilisateur. – cpjolicoeur

+0

Oui, ce serait génial de créer ces accesseurs dynamiquement – Artur79

+1

son bon à ajouter à la fin de l'eval: attr_accessible nom_méthode – Artur79

1

Je suis en train de faire quelque chose de semblable et je trouve ce genre de travaux:

<%= 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 de formulaire ne sont pas remplis lorsque le formulaire est rendu. lorsque le formulaire est soumis les valeurs sont par très bien et je peux faire:

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

donc sa « moitié » de travail ...

28

EMH est presque là. Je pense que Rails renverrait les valeurs aux champs de formulaire mais ce n'est pas le cas. Vous pouvez donc le mettre manuellement dans le paramètre ": value =>" pour chaque champ. Il n'a pas l'air lisse, mais ça marche.

Ici, il est 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 %> 

Tous les champs que vous ajoutez dans le « fields_for » sera affiché 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.

+0

J'essaie de faire comme vous l'avez dit, mais cela ne semble pas fonctionner pour moi: http://stackoverflow.com/questions/6621341 –

1

Je vais suggérer quelque chose de simple, parce que tout le temps, lorsque l'utilisateur va enregistrer le formulaire Vous obtiendrez chaîne. Vous pouvez donc utiliser par exemple avant filtre et analyser ces données comme:

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

bien sûr, vous devez ajouter la validation si cela est YAML correcte. Mais ça devrait marcher.

3

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

serialize :content_hash, Hash 

Puis dans la vue, je fais (avec simple_form, mais similaire à la vanille 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 Le dernier problème est de savoir comment laisser l'utilisateur ajouter une nouvelle paire clé/valeur.

+0

Principalement travaillé, sauf que j'ai affaire à un hachage profond et seulement le niveau racine se termine comme un hachage avec cela. – cpuguy83

35

Eh bien, j'ai eu le même problème, et j'ai essayé de ne pas trop le concevoir. Le problème est que, bien que vous puissiez passer le hash sérialisé à fields_for, les champs de la fonction vont penser, c'est un hachage d'option (et non votre objet), et définir l'objet de formulaire à zéro. Cela signifie que, bien que vous puissiez modifier les valeurs, elles n'apparaîtront pas après l'édition. Ce pourrait être un bug ou un comportement inattendu de rails et peut-être fixé à l'avenir.

Cependant, pour l'instant, il est assez facile de le faire fonctionner (bien qu'il m'ait fallu toute la matinée pour comprendre).

Vous pouvez laisser votre modèle tel quel et dans la vue, vous devez donner des champs pour l'objet en tant que structure ouverte. Cela définira correctement l'objet record (ainsi f2.object renverra vos options) et deuxièmement, il laisse le constructeur text_field accéder à la valeur de votre objet/params.

Depuis que j'ai inclus "|| {}", il fonctionnera aussi avec de nouveaux/créer des formes.

= 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 

ont un grand jour

+1

Bien joué. Cette astuce m'a fait gagner du temps ce soir. Je ne pensais pas que le fait que l'objet était un hachage signifierait qu'il serait interprété comme un hachage d'options, mais si c'est effectivement ce qui se passe, cela a beaucoup de sens. – Legion

+1

J'aime vraiment vraiment cette solution, j'ai ajouté ceci à mon modèle pour le compléter: 'def options OpenStruct.new self [: options] || {} end', donc vous obtenez l'OpenStruct toujours lors de la lecture de l'attribut .. –

+2

Je pense qu'il y a une petite erreur: vous devriez appeler 'text_field' sur' f2', pas 'f' – Sidhannowe

4

J'ai été aux prises avec un problème 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 dépassées. Voici ce qui a fonctionné pour moi avec Rails 5 et rubis 2.3:

Dans 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 que je devais mettre à jour les forts paramètres comme ceci:

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

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

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

+0

Cela fonctionne pour stocker des valeurs, mais lorsque vous ouvrez le formulaire 'edit', vous ne voyez pas les valeurs que vous avez enregistrées –

+0

@YaroShm Cela fait un moment que j'ai visité ce problème. Si je me souviens bien les paramètres forts étaient un peu compliqués et les mises à jour des rails au cours de la dernière année ont peut-être changé la syntaxe nécessaire. Il faudra probablement un débogage minutieux et peut-être un peu de chance pour le faire fonctionner. Quand vous le faites, s'il vous plaît poster l'astuce ici. Cela aidera d'autres développeurs qui trouveront ce post dans le futur. Bonne chance! – JDenman6

Questions connexes