Как динамически добавлять свойства к представителю ROAR?

Я использую ROAR для реализации API для приложения рельсов. Это приложение имеет дело с заявками, которые могут иметь такие атрибуты, как тема и описание, но также могут иметь определенные пользователем атрибуты. Для простоты предположим, что билет выглядит так:

class Ticket
  attr_accessor :subject, :description

  def custom_attributes
    # in reality these attributes depend on the current ticket instance
    # they are not hard-coded into the class
    [['priority', 'high'], ['Operating System', 'Ubuntu']]
  end
end

Требуемый вывод JSON для такого билета выглядит следующим образом:

{
  "subject": "Foo",
  "description": "Bar",
  "customField1": "high",
  "customField2": "Ubuntu"
}

Теперь вы можете уже увидеть проблему. Все свойства являются непосредственными потомками корневого объекта, это означает, что я не могу записать это как представитель:

class TicketRepresenter
  property :subject
  property :description

  # Need to iterate over instance members on the class level here...
end

Есть ли какой-то механик, который ROAR предлагает для этого? Например, обратный вызов, который выполняется в контексте фактического экземпляра, например,

def call_me_on_write
  represented.custom_attributes.each do |attribute|
    add_property('customField1', attribute[1])
  end
end

Есть ли что-то подобное в ROAR, что я упустил для этого?

Я искал в обеих документах ROAR и в документах представимых, но не смог найти ничего.

отказ

Я попытался упростить реальные обстоятельства, чтобы сделать вопрос более читабельным. Если вы считаете, что важная информация отсутствует, пожалуйста, сообщите мне. Я с радостью предоставлю более подробную информацию.

Вне сферы

Пожалуйста, не обсуждайте, является ли выбранный формат JSON хорошей или плохой идеей, я хочу оценить, поддержит ли ROAR его.

2 ответа

Решение

Я закончил динамически создавать классы из моего основного представителя:

class TicketRepresenter
  property :subject
  property :description

  def self.create(ticket, context = {})
    klass = Class.new(TicketRepresenter) # create a subclass of my representer
    ticket.custom_attributes.each do |attribute|
      # for each custom attribute in the actual instance insert a property into the created class
      property "customField#{attribute.id}".to_sym
               getter: -> (*) { attribute.value }
    end

    # return an instance of the class created above
    klass.new(ticket, context)
  end
end

По сути, это означает, что фактический класс-представитель, используемый для создания JSON, отличается для каждого Ticket,

Если вы хотите прочитать Ticket Вернувшись из JSON, необходимо правильно инициализировать представительство, чтобы созданный класс представления знал о ваших настраиваемых полях, а также определял установщики.

Теперь вам нужно будет условно позвонить по новой create метод вместо new, Если вам нужно, чтобы ваш представитель был создан ROAR (например, для коллекции), вы можете использовать механизм создания полиморфных объектов ROAR.

Примечание. Приведенный выше код не совсем подходит для примера пользовательских атрибутов, опубликованных в моем вопросе, но вы должны понять (в примере атрибут не имеет таких членов, как id а также value, но был список, состоящий из ключа и значения).

Я считаю, что лучший подход для решения этой проблемы - использовать Роара. writer:, Он полностью передает вам управление выводом, передавая несколько значений, которые он называет опциями, в предоставленную лямбду.

Например:

property :desired_property_name, writer: -> (represented:, doc:, **) do
  doc[:desired_key] = represented.desired_value
end

Существует множество применений, которые не описаны в github readme, но описаны на веб-сайте Trailblazer. Этот, в частности, можно найти по адресу http://trailblazer.to/gems/representable/3.0/function-api.html.

Ура!

Другие вопросы по тегам