【问题标题】:ActiveRecord serialize using JSON instead of YAMLActiveRecord 使用 JSON 而不是 YAML 进行序列化
【发布时间】:2011-01-06 00:36:18
【问题描述】:

我有一个使用序列化列的模型:

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

有没有办法让这个序列化使用 JSON 而不是 YAML?

【问题讨论】:

标签: ruby-on-rails json activerecord yaml serialization


【解决方案1】:

更新

请参阅下面 mid 的高评价答案以获得更合适的 Rails >= 3.1 答案。对于 Rails

这可能就是你要找的东西。

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:

它不是很干,但我已经尽力了。如果有人能在User 模型中修复after_find,那就太好了。

【讨论】:

  • 你不明白。他希望 :options 属性被序列化为 json 数据,而不是 yaml。
  • Ja,使用序列化,选项字段中的值作为 YAML 序列化值存储在数据库中。我希望它是 JSON。
  • “加密/解密”是误导性名称,因为不涉及加密。应该是“编码/解码”。现在无论如何都要使用下面提到的 Rails 3.1 解决方案。
【解决方案2】:

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 属性。 (尚未完全测试,但似乎可以正常工作)

【讨论】:

    【解决方案3】:

    我编写了自己的 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
    

    由于loaddump 是实例方法,它需要一个实例作为模型定义中serialize 的第二个参数传递。这是一个例子:

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

    我尝试创建一个新实例、加载一个实例并在 IRB 中转储一个实例,一切似乎都正常工作。我也为此写了blog post

    【讨论】:

    • 我真的是 Rails 新手。我会将 JSONColumn 类放在哪里?我会把它包括在哪里?谢谢!
    • 有很多方法可以做到,但这里有一种方法:你可以把它放在lib/json_column.rb中,然后将require 'json_column'添加到config/environment.rb
    • 谢谢。很高兴您可以设置默认值。我用它来创建一个类似于集合(无序数组)的列:序列化:something,JsonSerializer.new(Set.new)
    • @Venkat D.,如果您命名类以使其与文件名匹配,那么您甚至不需要明确要求该文件,因为它会在您第一次引用常量时自动加载。我把它叫做 JsonSerializer 并把它放在 lib/json_serializer.rb 中,所以我什至不需要'json_serializer'。 (只需确保 lib/ 在 config/application.rb 中的 autoload_paths 中:config.autoload_paths = %W( #{config.root}/lib ))
    • @TylerRick 这可能是最好的答案。随意写一个基于我的新答案。无需给我归因(或者更确切地说,您可以随心所欲地简单而弱地给出它)。我特此发布我的答案以及它链接到公共领域的博客文章。
    【解决方案4】:

    在 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

    rails 附带的默认 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

    【讨论】:

    【解决方案5】:

    在 Rails 3.1 中你可以

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

    希望有帮助

    【讨论】:

    • 我认为这是因为[:dump, :load].all {|s| ::JSON.respond_to? s},就像默认的YAMLColumn 一样。在控制器中,JSON == ::JSONJSON != ::ActiveSupport::JSON
    • 这在 Rails 3.2.1 中有效,但在 3.2.2 中停止:请参阅 github.com/rails/rails/issues/5797 希望得到修复,因为它非常方便!
    • 为了澄清@BenAtkin 所说的,如果您传递给序列化调用的类响应.dump(obj).load(obj),它将被用作编码器/序列化器。否则,默认的 YAMLColumn 类将用作编码器/序列化器。转储和加载方法也可以直接在模型类中实现,您不必必须使用专用的单独类进行编码。
    • 这可能会导致错误:NoMethodError: undefined method `read' for nil:NilClass
    • 所以,这确实比标准 YAML (ar4.1.4) 更快
    【解决方案6】:

    Aleran,你在 Rails 3 中使用过这种方法吗?我遇到了同样的问题,当我遇到 Michael Rykov 的这篇文章时,我正朝着连载的方向前进,但是不可能在他的博客上发表评论,或者至少在那篇文章上是不可能的。据我了解,他是说您不需要定义设置类,但是当我尝试这样做时,它一直告诉我设置未定义。所以我只是想知道你是否使用过它,还有什么应该描述的?谢谢。

    【讨论】:

    • 当然你需要设置类。但是您可能可以直接处理 JSON 类。 :constructor => JSON.method(:parse), :converter => JSON.method(:parse), :mapping => %w(my_attribute to_json)。不过没试过。
    【解决方案7】:

    更简单的解决方案是使用composed_of,如this blog post by Michael Rykov 中所述。我喜欢这个解决方案,因为它需要使用更少的回调。

    这里是它的要点:

    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
    

    【讨论】:

    【解决方案8】:

    我的要求在这个阶段不需要大量的代码重用,所以我的提炼代码是上述答案的变体:

      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  
    

    干杯,最后很容易!

    【讨论】:

    • 这仅适用于某些数据类型。例如input = 'foo'; serialized = input.to_json; JSON.parse(serialized) #=> JSON::ParserError: 757: unexpected token at '"foo"' from /Users/username/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/ json-1.8.3/lib/json/common.rb:155:in `parse' makandracards.com/makandra/… 一个很好的解决方法是使用 Ruby 的 Marshal 库。它比 JSON 或 YAML 更快、更可靠。
    猜你喜欢
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多