data_mapper, attr_accessor и сериализация только сериализуют свойства, а не атрибуты 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 ответа

Решение

Благодаря Мэтту я немного покопался и нашел параметр:method для метода to_json в dm-serializer. Их метод 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

Работает, как и ожидалось, с новым параметром option.

То, что я мог бы сделать, чтобы выборочно получить нужные атрибуты без необходимости выполнять дополнительную работу, - просто передать имена атрибутов в параметр:method.

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

Или, так как у меня уже был мой Base учебный класс:

p ps.to_json :methods => Person.attributes

Теперь мне просто нужно выяснить, как я хочу поддерживать коллекции.

Datamapper имеет свою собственную библиотеку сериализации, dm-serializer, что обеспечивает to_json метод для любого ресурса Datamapper. Если вам требуется Datamapper с require 'data_mapper' в вашем коде вы используете data_mapper meta-gem, который требует dm-serializer как часть его настройки.

to_json метод предоставлен dm-serializer только сериализует свойства Datamapper вашего объекта (то есть те, которые вы указали с property а не "нормальные" свойства (которые вы определили с attr_accessor). Вот почему вы получаете id а также first_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 метод, который вы могли бы использовать для создания комбинированного to_json метод, например:

def to_json
  as_json.merge({:last_name => last_name}).to_json
end
Другие вопросы по тегам