web-dev-qa-db-fra.com

Comment utiliser le type de champ json ActiveRecord

J'ai un modèle Rails qui a une colonne de base de données de type "json":

create_table "games", force: true do |t|
  t.json     "game_board"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

Génial! Maintenant, comment dois-je l'utiliser? Est-ce vraiment aussi simple que traiter le champ comme un Hash ?

self.game_board[:player1] = 1
self.game_board[:cards] = cards.to_hash

Si je devais écrire cela, tout fonctionnerait-il comme prévu, donc dans un futur appel API d'un client, je pourrais le faire?:

self.game_board[:player] # And get back the 1 that I put here before

Et la performance aussi? Est-ce que l'ensemble game_board être désérialisé à chaque fois même si ce champ n'est jamais lu? Le champ sera-t-il réécrit (IOW une écriture de base de données) à chaque fois que je change une partie du "Hash?"

16
Freedom_Ben

Oui, ActiveRecord permet d'utiliser les champs json- de Postgres simplement comme des hachages dans leurs modèles. Cependant, il y a quelques points à considérer:

  1. Le hachage peut être NULL à l'initialisation
    Dans votre create_table migration vous autorisez le champ :game_board être NULL. Ainsi, lors de la première utilisation, le champ :game_board de votre instance de modèle sera NULL et vous devez d'abord initialiser le hachage avant de l'utiliser. (Voir l'exemple ci-dessous)

  2. En JSON, toutes les clés sont des chaînes
    Ainsi, lors de l'enregistrement (et du rechargement), toutes les clés seront transformées en chaînes si vous avez déjà utilisé des symboles ou des nombres. Ainsi, pour éviter tout comportement indésirable, il est recommandé d'utiliser des clés de chaîne, sauf si votre ORM est configuré pour symboliser toutes les clés.


Vos exemples:

self.game_board         ||= {}
self.game_board[:player1] = 1
self.game_board[:cards]   = cards.to_hash

# after reload from database (access via String-key):
self.game_board['player1']  # And retrieve value 1 (that we put here before)


@ Performance:

  1. Oui, chaque fois qu'ActiveRecord lit une entrée de la base de données et crée une instance de modèle, les champs JSON ne sont pas sérialisés dans les hachages. Mais si vous pensez que cela nuit aux performances de votre application, vous devez soit utiliser un champ de texte et sérialiser/désérialiser le JSON/Hashs lorsque vous en avez besoin ou, mieux encore, ne pas utiliser ActiveRecord du tout. En créant des tas de classes et en utilisant des méthodes magiques, ActiveRecord crée tellement de surcharge que vous ne devriez pas vous soucier de la désérialisation de JSON. La commodité a ses coûts.

  2. Oui, chaque fois que vous modifiez une valeur dans le hachage, le champ JSON (entier) est remplacé et mis à jour avec la nouvelle version sérialisée.
    Deux notes à ce sujet:

    • Même dans Postgres lui-même (pas seulement dans ActiveRecord), la possibilité d'effectuer des mises à jour sur certains éléments JSON est jusqu'à présent manquante. Comparez cette question Stackoverflow
    • En général, les champs JSON doivent être utilisés avec une structure fixe ou, au moins, dans des tailles gérables et le type de champ n'est pas censé être un magasin de documents comme par exemple. dans MongoDB. Comparez la documentation Postgres
26
Andreas Rayo Kniep

Juste pour clarifier davantage - lorsque vous enregistrez l'objet JSON dans un attribut de votre instance de modèle assurez-vous de l'enregistrer en tant que hachage.

Active Record ne se plaindra pas si vous oubliez d'analyser une chaîne JSON :

  game = Game.create(game_board: '"key":"value"')

Lorsque vous récupérez une chaîne à partir d'un attribut json, il ne se plaindra pas et renverra simplement la chaîne.

  game.game_board
  => '"key":"value"'

Ainsi, game.game_board['key'] Entraînerait une erreur car vous essayez de traiter une chaîne comme un hachage.

Assurez-vous donc d'utiliser JSON.parse(string) avant d'enregistrer.

  game = Game.create(game_board: JSON.parse('"key":"value"'))

Alors maintenant, vous avez le comportement attendu

game.game_board['key']
=> 'value'

Probablement pas utile dans ce cas, mais j'ai rencontré ce problème lors de l'enregistrement d'une charge utile JSON à partir d'une API avec laquelle j'étais en cours d'intégration. Quoi qu'il en soit, j'espère que cela vous aidera.

0
Dylan Pierce