web-dev-qa-db-fra.com

ActiveRecord sérialiser en utilisant JSON au lieu de YAML

J'ai un modèle qui utilise une colonne sérialisée:

class Form < ActiveRecord::Base
  serialize :options, Hash
end

Existe-t-il un moyen de faire en sorte que cette sérialisation utilise JSON au lieu de YAML?

54
Toby Hede

Dans Rails 3.1, vous pouvez simplement

class Form < ActiveRecord::Base
  serialize :column, JSON
end

J'espère que cela pourra aider

156
Justas L.

Dans Rails 3.1, vous pouvez utiliser des codeurs personnalisés avec serialize.

class ColorCoder
  # Called to deserialize data to Ruby object.
  def load(data)
  end

  # Called to convert from Ruby object to serialized data.
  def dump(obj)
  end
end

class Fruits < ActiveRecord::Base
  serialize :color, ColorCoder.new
end

J'espère que cela t'aides.

Références:

Définition de serialize: https://github.com/Rails/rails/blob/master/activerecord/lib/active_record/base.rb#L556

Le codeur YAML par défaut fourni avec Rails: https://github.com/Rails/rails/blob/master/activerecord/lib/active_record/coders/yaml_column.rb

Et c’est là que l’appel à la load se produit: https://github.com/Rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/read.rb#L132 =

58
balu

Mettre à jour

Voir la réponse mieux notée ci-dessous pour une réponse Rails> = 3.1 bien plus appropriée. C'est une excellente réponse pour Rails <3.1. 

C'est probablement ce que vous recherchez.

Form.find(:first).to_json

Mettre à jour

1) Installez 'json' gem :

gem install json

2) Créer une classe JsonWrapper

# lib/json_wrapper.rb

require 'json'
class JsonWrapper
  def initialize(attribute)
    @attribute = attribute.to_s
  end

  def before_save(record)
    record.send("#{@attribute}=", JsonWrapper.encrypt(record.send("#{@attribute}")))
  end

  def after_save(record)
    record.send("#{@attribute}=", JsonWrapper.decrypt(record.send("#{@attribute}")))
  end

  def self.encrypt(value)
    value.to_json
  end

  def self.decrypt(value)
    JSON.parse(value) rescue value
  end
end

3) Ajouter des rappels de modèle:

#app/models/user.rb

class User < ActiveRecord::Base
    before_save      JsonWrapper.new( :name )
    after_save       JsonWrapper.new( :name )

    def after_find
      self.name = JsonWrapper.decrypt self.name
    end
end

4) Testez-le!

User.create :name => {"a"=>"b", "c"=>["d", "e"]}

PS:

Ce n'est pas tout à fait sec, mais j'ai fait de mon mieux. Si quelqu'un peut corriger after_find dans le modèle User, ce sera génial.

11
St.Woland

Mes exigences n'ayant pas nécessité beaucoup de réutilisation de code à ce stade, mon code distillé est une variante de la réponse ci-dessus:

  require "json/ext"

  before_save :json_serialize  
  after_save  :json_deserialize


  def json_serialize    
    self.options = self.options.to_json
  end

  def json_deserialize    
    self.options = JSON.parse(options)
  end

  def after_find 
    json_deserialize        
  end  

Salut, assez facile à la fin!

8
Toby Hede

La méthode serialize :attr, JSON using composed_of fonctionne comme ceci:

  composed_of :auth,
              :class_name => 'ActiveSupport::JSON',
              :mapping => %w(url to_json),
              :constructor => Proc.new { |url| ActiveSupport::JSON.decode(url) }

où url est l'attribut à sérialiser à l'aide de json et auth est la nouvelle méthode disponible sur votre modèle qui enregistre sa valeur au format json dans l'attribut url. (pas encore complètement testé mais semble fonctionner)

3
caribu

J'ai écrit mon propre codeur YAML, qui prend un défaut. Voici la classe:

class JSONColumn
  def initialize(default={})
    @default = default
  end

  # this might be the database default and we should plan for empty strings or nils
  def load(s)
    s.present? ? JSON.load(s) : @default.clone
  end

  # this should only be nil or an object that serializes to JSON (like a hash or array)
  def dump(o)
    JSON.dump(o || @default)
  end
end

Étant donné que load et dump sont des méthodes d'instance, il est nécessaire de transmettre une instance en tant que second argument à serialize dans la définition du modèle. En voici un exemple:

class Person < ActiveRecord::Base
  validate :name, :pets, :presence => true
  serialize :pets, JSONColumn.new([])
end

J'ai essayé de créer une nouvelle instance, de charger une instance et de transférer une instance dans IRB, et tout semblait bien fonctionner. J'ai aussi écrit un blog post à ce sujet.

3
Benjamin Atkin

Une solution plus simple consiste à utiliser composed_of comme décrit dans cet article de blog de Michael Rykov . J'aime cette solution car elle nécessite moins de rappels.

En voici l'essentiel:

composed_of :settings, :class_name => 'Settings', :mapping => %w(settings to_json),
                       :constructor => Settings.method(:from_json),
                       :converter   => Settings.method(:from_json)

after_validation do |u|
  u.settings = u.settings if u.settings.dirty? # Force to serialize
end
1
Aleran

Aleran, avez-vous utilisé cette méthode avec Rails 3? J'ai un peu le même problème et je me dirigeais vers une publication en série lorsque j'ai lu ce billet de Michael Rykov, mais il est impossible de commenter sur son blog, ou du moins sur ce billet. Si je comprends bien, il dit qu'il n'est pas nécessaire de définir la classe Settings. Toutefois, lorsque j'essaie, il ne cesse de me dire que Setting n'est pas défini. Je me demandais donc simplement si vous l'aviez utilisé et quoi de plus aurait dû être décrit? Merci.

0
Andrew Lank