Как украсить хэш — объект из стороннего API- Rails

#ruby-on-rails #api #decorator

Вопрос:

Я использую внешний сторонний API в своем приложении rails, и мне интересно, как мне лучше всего обработать ответ, чтобы я мог добавить функциональность к каждому объекту в представлении.

У меня есть окно поиска, которое запускает вызов внешнего API музея. У меня есть объект службы, обрабатывающий вызов и анализирующий ответ, поэтому я могу обеспечить согласованную структуру. Затем я представляю возвращенные товары в списке, чтобы отдельные товары можно было импортировать в приложение.

Я хочу добавить уменьшенное изображение для каждого элемента в списке, где идентификатор изображения существует в ответе API, или добавить заполнитель, где его нет. Я справляюсь с этим с некоторой логикой в представлении, которое я хотел бы извлечь.

    <% @mus_objects.each do |ob| %>
      <div class="row mt-5">
        <div class="col-lg-6">
          <% if ob['_primaryImageId'] %>
            <%= image_tag("https://iiif_image_server_example.com/#{ob['_primaryImageId']}/full/!140,140/0/default.jpg") %>
          <% else %>
            <%= image_tag("https://placehold.it/140x140?text=No Image") %>
          <% end %>
          <%= ob["_primaryTitle"] %>
          <%= link_to 'Import', new_admin_museum_object_path(ob) %>
        </div>
      </div>
    <% end %> 
 

Это мой код контроллера:

 class Admin::MuseumApiController < ApplicationController

  def search
    @search_term = params[:search_term]
    if params[:searchtype] == 'search_term'
      response = MuseumApiManager::FetchBySearchTerm.call(@search_term)
    elsif params[:searchtype] == 'systemNumber'
      response = MuseumApiManager::FetchBySystemNumber.call(@search_term)
    end
    if response amp;amp; response.success?
      @mus_objects = response["records"]
    end
  end

end
 

Ответ на мой вызов объекта службы выглядит следующим образом:

 #<OpenStruct success?=true, records=[{"systemNumber"=>"O123", "objectType"=>"Bottle", "_primaryTitle"=>"Bottle", "_primaryImageId"=>'1234'}]>
 

Таким образом, мои @mus_objects, используемые представлением, представляют собой массив хэшей.
Я не уверен, каков наилучший подход для решения этой проблемы. Я думаю, что что-то вроде декоратора было бы уместно, чтобы я мог вместо этого позвонить ob.thumbnail_url в пределах представления, но декоратору требуется модель.

**** Экспериментируйте с ответом, предложенным с помощью ActiveModel ****

Я создал PORO, как было предложено, и привел методы поиска здесь, хотя, пока я его тестирую, я оставил логику в объекте ServiceObject.

 class MuseumApiObject
  include ActiveModel::Model

  def self.fetch_by_search_term(search_term)
    response = MuseumApiManager::FetchBySearchTerm.call(search_term)
    response["records"]
  end

  def thumbnail_url
    if _primaryImageId
      "https://iiif_example.com/#{_primaryImageId}/full/!140,140/0/default.jpg"
    else
      'https://placehold.it/140x140?text=No Image'
    end
  end
end
 

теперь в контроллере я делаю это:

 class Admin::MuseumApiController < ApplicationController

  def search
    @search_term = params[:search_term]
    @mus_objects = MuseumApiObject.fetch_by_search_term(@search_term)
  end

end
 

Теперь это все еще просто создает массив хэшей. Как я могу создать экземпляр этого массива хэшей для загрузки объектов MuseumApiObjects? Я попытался использовать create с массивом, но он не существует.

Ответ №1:

Я предпочитаю создавать объекты модели (не ActiveRecord) для инкапсуляции логики домена в моем коде (вместо служб). Это можно сделать, включив ActiveModel::Model в ПОРОс.

Эта стратегия позволяет мне сохранить всю мою бизнес-логику в моих моделях, и теперь не все модели должны иметь связанную таблицу. В качестве плюса я все еще получаю все преимущества, такие как проверки и обратные вызовы, и я также могу писать свои тестовые примеры, как и для любой другой модели.

В вашем конкретном случае использования я создам MuseumObject класс модели (который также может включать логику взаимодействия с API), а также thumbnail_url метод.

 class MuseumObject
  include ActiveModel::Model

  attr_accessor :system_number, :object_type, primary_title, primary_image_id

  def initialize(attributes)
    self.system_number = attributes['systemNumber']
    ...
  end

  def self.search
    ...
  end

  def thumbnail_url
    ...
  end
end
 

Изменить:
Вам нужно будет инициализировать объект с помощью хэша. Вы можете определить attr_accessor s для всех ваших полей, а затем вы можете просто инициализировать объекты с помощью хэшей.

Скажем, например, если ваш массив хэшей хранится в museum_hashes переменной, вы можете преобразовать его в массив объектов вашей модели следующим образом,

 museum_hashes.map { |hash| MuseumObject.new(hash) }
 

Комментарии:

1. Спасибо, что познакомили меня с ActiveModel. Я добавил свои последующие примечания к исходному вопросу, пока пытаюсь заставить это работать — не могли бы вы помочь?

2. Добавил более подробную информацию к ответу, пожалуйста, посмотрите, поможет ли это.