ActiveRecord сериализуется с использованием JSON вместо YAML - PullRequest
55 голосов
/ 17 января 2010

У меня есть модель, которая использует сериализованный столбец:

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

Есть ли способ заставить эту сериализацию использовать JSON вместо YAML?

Ответы [ 8 ]

156 голосов
/ 07 августа 2011

В Rails 3.1 вы можете просто

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

Надеюсь, что поможет

58 голосов
/ 12 мая 2011

В Rails 3.1 вы можете использовать собственные кодеры с 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

Надеюсь, это поможет.

Ссылки:

Определение serialize: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rb#L556

Кодер YAML по умолчанию, который поставляется с рельсами: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/coders/yaml_column.rb

И вот где происходит звонок на load: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods/read.rb#L132

11 голосов
/ 17 января 2010

Обновление

См. Высокоуровневый ответ середины ниже для более подходящего ответа Rails> = 3.1. Это отличный ответ для Rails <3.1. </p>

Вероятно, это то, что вы ищете.

Form.find(:first).to_json

Обновление

1) Установить 'json' gem :

gem install json

2) Создать класс 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) Добавить модель обратного вызова:

#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) Проверьте это!

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

PS:

Это не совсем СУХО, но я сделал все возможное. Если кто-то сможет исправить after_find в User модели, это будет здорово.

8 голосов
/ 19 января 2010

На этом этапе мои требования не требовали многократного повторного использования кода, поэтому мой перегруженный код представляет собой вариант ответа выше:

  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  

Ура, довольно легко в конце!

3 голосов
/ 08 ноября 2011

Я написал свой собственный YAML-кодер, который принимает значение по умолчанию. Вот этот класс:

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

Поскольку load и dump являются методами экземпляра, требуется, чтобы экземпляр был передан в качестве второго аргумента serialize в определении модели. Вот пример этого:

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

Я попытался создать новый экземпляр, загрузить экземпляр и выгрузить экземпляр в IRB, и все, казалось, работало правильно. Об этом я тоже написал в блоге .

3 голосов
/ 22 мая 2011

Метод serialize :attr, JSON с использованием composed_of работает следующим образом:

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

, где url - это атрибут, который будет сериализован с использованием json, а auth - новый метод, доступный в вашей модели, который сохраняет свое значение вФормат JSON для атрибута URL.(еще не полностью протестирован, но, похоже, работает)

1 голос
/ 28 января 2011

Более простое решение - использовать composed_of, как описано в этом сообщении в блоге Михаила Рыкова .Мне нравится это решение, потому что оно требует использования меньшего количества обратных вызовов.

Вот суть этого:

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
0 голосов
/ 15 апреля 2011

Aleran, вы использовали этот метод с Rails 3?У меня несколько возникла та же проблема, и я шел к сериализации, когда натолкнулся на этот пост Михаила Рыкова, но комментировать его блог невозможно или, по крайней мере, этот пост.Насколько я понимаю, он говорит, что вам не нужно определять класс настроек, однако, когда я пытаюсь это сделать, он говорит мне, что настройка не определена.Так что мне просто интересно, использовали ли вы это и что еще нужно было описать?Спасибо.

...