web-dev-qa-db-ger.com

ActiveRecord serialisiert mit JSON anstelle von YAML

Ich habe ein Modell, das eine serialisierte Spalte verwendet:

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

Gibt es eine Möglichkeit, dass diese Serialisierung JSON anstelle von YAML verwendet?

54
Toby Hede

In Rails 3.1 kannst du das einfach

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

Hoffentlich hilft das

156
Justas L.

In Rails 3.1 können Sie benutzerdefinierte Codierer mit serialize verwenden.

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

Hoffe das hilft.

Verweise:

Definition von serialize: https://github.com/Rails/rails/blob/master/activerecord/lib/active_record/base.rb#L556

Der Standard-YAML-Codierer, der mit Rails geliefert wird: https://github.com/Rails/rails/blob/master/activerecord/lib/active_record/coders/yaml_column.rb

Und hier geschieht der Aufruf der load: https://github.com/Rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/read.rb#L132

58
balu

Update

In der hoch bewerteten Antwort von mid finden Sie eine wesentlich passendere Antwort für Rails> = 3.1. Dies ist eine großartige Antwort für Rails <3.1. 

Wahrscheinlich ist es das, wonach Sie suchen.

Form.find(:first).to_json

Update

1) Installieren Sie 'json' gem :

gem install json

2) Erstellen Sie eine JsonWrapper-Klasse

# 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) Fügen Sie Modellrückrufe hinzu:

#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) Testen Sie es!

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

PS:

Es ist nicht ganz trocken, aber ich habe mein Bestes gegeben. Wenn jemand after_find im User-Modell korrigieren kann, ist dies großartig.

11
St.Woland

Meine Anforderungen benötigten zu diesem Zeitpunkt nicht viel Wiederverwendung von Code, daher ist mein destillierter Code eine Variation der obigen Antwort:

  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  

Prost, am Ende ganz leicht!

8
Toby Hede

Die serialize :attr, JSON using composed_of-Methode funktioniert folgendermaßen:

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

dabei ist url das mit json zu serialisierende Attribut und auth die neue für Ihr Modell verfügbare Methode, die ihren Wert im json-Format im url-Attribut speichert. (noch nicht vollständig getestet, scheint aber zu funktionieren)

3
caribu

Ich habe meinen eigenen YAML-Codierer geschrieben, der einen Standardwert annimmt. Hier ist die Klasse:

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

Da load und dump Instanzmethoden sind, muss in der Modelldefinition eine Instanz als zweites Argument an serialize übergeben werden. Hier ist ein Beispiel davon:

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

Ich habe versucht, eine neue Instanz zu erstellen, eine Instanz zu laden und eine Instanz in IRB zu sichern, und alles schien richtig zu funktionieren. Ich habe auch einen blog post darüber geschrieben.

3
Benjamin Atkin

Eine einfachere Lösung ist die Verwendung von composed_of wie in diesem Blogpost von Michael Rykov beschrieben. Diese Lösung gefällt mir, da weniger Callbacks erforderlich sind.

Hier ist der Kern davon:

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, haben Sie diese Methode mit Rails 3 verwendet? Ich habe ein bisschen dasselbe Thema und war auf dem Weg in Richtung serialisiert, als ich auf diesen Beitrag von Michael Rykov stieß, aber es ist nicht möglich, seinen Blog zu kommentieren, oder zumindest zu diesem Beitrag. Nach meinem Verständnis sagt er, dass Sie keine Settings-Klasse definieren müssen, aber wenn ich dies versuche, wird mir immer gesagt, dass Setting nicht definiert ist. Ich habe mich nur gefragt, ob Sie es benutzt haben und was noch hätte beschrieben werden sollen. Vielen Dank.

0
Andrew Lank