Utilisation de la sérialisation de Rails pour enregistrer le hachage dans la base de données

135

J'essaie d'enregistrer un identifiant de mappage de hachage pour un certain nombre de tentatives dans mon application rails. Ma migration vers la base de données pour accueillir cette nouvelle colonne:

class AddMultiWrongToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :multi_wrong, :string
  end

  def self.down
    remove_column :users, :multi_wrong
  end
end

Dans mon modèle, j'ai:

class User < ActiveRecord::Base 
 serialize :multi_wrong, Hash
end

Mais quand j'utilise la console rails pour tester cela en faisant:

user = User.create()
user.multi_wrong = {"test"=>"123"}
user.save

La sortie est fausse. Qu'est-ce qui ne va pas ici?

cmwright
la source
4
Y a-t-il quelque chose dans user.errors après avoir tenté de sauvegarder l'enregistrement?
Martijn le
1
À l'avenir, vous pouvez utiliser la méthode bang (save!) Pour lever une exception et afficher un message d'erreur.
leishman
La meilleure réponse utilise maintenant une colonne JSON stackoverflow.com/a/21397522/1536309
Blair Anderson

Réponses:

174

Le type de colonne est incorrect. Vous devez utiliser Text au lieu de String. Par conséquent, votre migration doit être:

 def self.up
   add_column :users, :multi_wrong, :text
 end

Ensuite, Rails le convertira correctement en YAML pour vous (et effectuera une sérialisation appropriée). Les champs de chaînes sont limités en taille et ne contiennent que des valeurs particulièrement petites.

Benjamin Tan Wei Hao
la source
1
@BenjaminTan quelle est la raison derrière cela, pourquoi est-ce que je ne peux pas stocker le hachage dans le type de données «chaîne».
Lohith MV
8
Parce que dans la base de données, String a une longueur fixe de 255 (je pense). Mais si vous deviez sérialiser un hachage de taille comparative, cela dépasserait facilement la longueur. Même cas pour les tableaux. Le texte permet des longueurs beaucoup plus grandes.
Benjamin Tan Wei Hao
72

ACTUALISÉ:

La mise en œuvre exacte dépendra de votre base de données, mais PostgreSQL a maintenant jsonet jsonbcolonnes qui peuvent stocker nativement vos données de hachage / objet et vous permettent de requête contre le JSON avec ActiveRecord !

changez votre migration et vous avez terminé.

class Migration0001
  def change
    add_column :users, :location_data, :json, default: {}
  end
end

ORIGINAL:

Pour plus de détails: rails docs && apidock

Assurez-vous que votre colonne est :textet non:string

Migration:

$ rails g migration add_location_data_to_users location_data:text

devrait créer:

class Migration0001
  def change
    add_column :users, :location_data, :text
  end
end

Votre classe ressemblerait à:

class User < ActiveRecord::Base
  serialize :location_data
end

Actions disponibles:

b = User.new
b.location_data = [1,2,{foot: 3, bart: "noodles"}]
b.save

Plus génial?!

utiliser postgresql hstore

class AddHstore < ActiveRecord::Migration  
  def up
    enable_extension :hstore
  end

  def down
    disable_extension :hstore
  end
end 

class Migration0001
  def change
    add_column :users, :location_data, :hstore
  end
end

Avec hstore, vous pouvez définir des attributs sur le champ sérialisé

class User < ActiveRecord::Base  
  # setup hstore
  store_accessor :location_data, :city, :state
end
Blair Anderson
la source
2
Vraiment génial! Merci!
Alexander Gorg
18

Rails 4 a une nouvelle fonctionnalité appelée Store , vous pouvez donc facilement l'utiliser pour résoudre votre problème. Vous pouvez définir un accesseur pour celui-ci et il est recommandé de déclarer la colonne de base de données utilisée pour le magasin sérialisé sous forme de texte, il y a donc beaucoup de place. L'exemple original:

class User < ActiveRecord::Base
  store :settings, accessors: [ :color, :homepage ], coder: JSON
end

u = User.new(color: 'black', homepage: '37signals.com')
u.color                          # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor

# There is no difference between strings and symbols for accessing custom attributes
u.settings[:country]  # => 'Denmark'
u.settings['country'] # => 'Denmark'
Aboozar Rajabi
la source