data_mapper,attr_accessor,&序列化仅序列化属性而不是attr_accessor属性

时间:2012-08-19 04:48:52

标签: ruby sinatra ruby-datamapper attr-accessor

我正在使用data_mapper / sinatra并尝试使用attr_accessor创建一些属性。以下示例代码:

require 'json'
class Person
  include DataMapper::Resource

  property :id,          Serial
  property :first_name,  String
  attr_accessor  :last_name
end

ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json

生成此输出:

"{\"id\":null,\"first_name\":\"Mike\"}"

显然我希望它能给我名字和姓氏属性。关于如何让人们按照预期的方式工作以使我的json拥有所有属性的任何想法?

另外,请随意解释为什么我的期望(我得到所有属性)不正确。我猜一些内部属性列表没有将attr_accessor实例变量添加到它或其他东西。但即便如此,为什么呢?

2 个答案:

答案 0 :(得分:2)

Datamapper拥有自己的序列化库dm-serializer,可为任何Datamapper资源提供to_json方法。如果您的代码中需要带有require 'data_mapper'的Datamapper,那么您使用data_mapper meta-gem requires dm-serializer as part of it’s set up。{/ p>

to_json提供的dm-serializer方法仅序列化对象的Datamapper属性(即您使用property指定的属性),而不是“正常”属性(即您用attr_accessor定义。这就是您获得idfirst_name但不是last_name的原因。

为了避免使用dm-serializer,您需要明确要求所需的库,而不是依赖data_mapper。您至少需要dm-core和其他人。

“普通”json库在对象的默认to_json调用中不包含任何属性,它只使用对象to_s方法。因此,在这种情况下,如果您将require 'data_mapper'替换为require 'dm-core',您将获得类似"\"#<Person:0x000001013a0320>\""的内容。

要创建自己对象的json表示,您需要创建自己的to_json方法。一个简单的例子就是在json中对你想要的属性进行硬编码:

def to_json
  {:id => id, :first_name => first_name, :last_name => last_name}.to_json
end

您可以创建一个方法来查看对象的属性和属性,并从中创建适当的json,而不是以这种方式对其进行硬编码。

请注意,如果您创建自己的to_json方法,仍然可以致电require 'data_mapper',则to_json将替换dm-serializer提供的方法。实际上,dm-serializer还添加了as_json method,您可以使用它来创建合并的to_json方法,例如:

def to_json
  as_json.merge({:last_name => last_name}).to_json
end

答案 1 :(得分:1)

感谢Matt我做了一些挖掘并找到了dm-serializer的to_json方法的方法参数。他们的to_json方法相当不错,基本上只是as_json辅助方法的包装器,所以我通过添加几行来覆盖它:

  if options[:include_attributes]
    options[:methods] = [] if options[:methods].nil?
    options[:methods].concat(model.attributes).uniq!
  end

完成的方法覆盖如下所示:

module DataMapper
  module Serializer

    def to_json(*args)
      options = args.first
      options = {} unless options.kind_of?(Hash)

      if options[:include_attributes]
        options[:methods] = [] if options[:methods].nil?
        options[:methods].concat(model.attributes).uniq!
      end

      result = as_json(options)

      # default to making JSON
      if options.fetch(:to_json, true)
        MultiJson.dump(result)
      else
        result
      end
    end

  end
end

这与我添加到我用于模型的基本模块中的属性方法一起使用。相关部分如下:

module Base

  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods

    def attr_accessor(*vars)
      @attributes ||= []
      @attributes.concat vars
      super(*vars)
    end

    def attributes
      @attributes || []
    end
  end

  def attributes
    self.class.attributes
  end

end

现在我原来的例子:

require 'json'
class Person
  include DataMapper::Resource
  include Base

  property :id,          Serial
  property :first_name,  String
  attr_accessor  :last_name
end

ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json :include_attributes => true

使用新选项参数按预期工作。

我可以做的就是有选择地获取我想要的属性而不必做额外的工作就是将属性名称传递给:methods param。

p ps.to_json :methods => [:last_name]

或者,因为我已经有了Base课程:

p ps.to_json :methods => Person.attributes

现在我只需要弄清楚我想如何支持集合。