Класс Set и уникальные коллекции объектов


На одном проекте мне понадобилось получать список форматов для баннеров из одной рекламной платформы. Задача с виду простая, но возникла одна проблема - список форматов приходит не уникальный. Для решения этой проблемы я решил использовать встроенный в Ruby класс Set и немного DDD.

Итак, по порядку.

Список форматов мы будем получать по API, для этого создадим сервис:

# app/services/my_target_service.rb
class MyTargetService
  API_URL = 'https://example.com'

  def self.banner_formats
    response = RestClient.get("#{API_URL}/api/banner_formats.json")
    JSON.parse(response, symbolize_names: true)
  end
end

В ответ нам приходит массив примерно следующего вида:

[
  {
    :id => 1,
    :status => 'active',
    :name => 'Format 1',
    :description => '...',
    :width => 240,
    :height => 400
  },
  {
    :id => 1,
    :status => 'active',
    :name => 'Format 1',
    :description => '...',
    :width => 240,
    :height => 400
  },
  {
    :id => 2,
    :status => 'inactive',
    :name => 'Format 2',
    :description => '...',
    :width => 1080,
    :height => 607
  }
]

Как видно из примера, форматы в списке повторяются. Воспользуемся классом Set, чтобы сделать этот список уникальным:

# app/services/my_target_service.rb
require 'set'

class MyTargetService
  API_URL = 'https://example.com'

  def self.banner_formats
    response = RestClient.get("#{API_URL}/api/banner_formats.json")
    formats = JSON.parse(response, symbolize_names: true)

    banner_formats = Set.new
    formats.each do |format|
      banner_formats << format
    end

    banner_formats
  end
end

Для удобства работы, обернем каждый формат в отдельную сущность. Для этого создадим класс BannerFormatEntity:

# app/entities/banner_format_entity.rb
class BannerFormatEntity
  attr_reader :format

  def initialize(format)
    @format = format
  end

  def id
    format[:id]
  end

  def name
    format[:name]
  end

  def description
    format[:description]
  end

  def size
    "#{format[:width]}x#{format[:height]}"
  end
  
  def active?
    format[:status] == 'active'
  end
end

Теперь обновим наш сервис, чтобы использовать класс BannerFormatEntity. Так же добавим условие, чтобы в списке были только активные форматы:

# app/services/my_target_service.rb
require 'set'

class MyTargetService
  API_URL = 'https://example.com'

  def self.banner_formats
    response = RestClient.get("#{API_URL}/api/banner_formats.json")
    formats = JSON.parse(response, symbolize_names: true)

    banner_formats = Set.new
    formats.each do |format|
      banner_format = BannerFormatEntity.new(format)
      banner_formats << banner_format if banner_format.active?
    end

    banner_formats
  end
end

Остается последний штрих. Для того чтобы Set мог определять уникальность наших объектов, необходимо определить в классе BannerFormatEntity 2 метода: Object#eql? и Object#hash.

# app/entities/banner_format_entity.rb
class BannerFormatEntity
  attr_reader :format

  def initialize(format)
    @format = format
  end

  def id
    format[:id]
  end

  def name
    format[:name]
  end

  def description
    format[:description]
  end

  def size
    "#{format[:width]}x#{format[:height]}"
  end

  def active?
    format[:status] == 'active'
  end
  
  def eql?(other)
    id == other.id
  end
  
  def hash
    id.hash
  end
end

Вот таким простым и элегантным способом, мы решили нашу задачу с получением уникального списка форматов для баннеров. Сервис MyTargetService отправляет запрос к API рекламной площадки и возвращает уникальную коллекцию объектов BannerFormatEntity.

Похожие записи

Автоматическая проверка кода с помощью Vexor

Пошаговая инструкция, что для этого нужно сделать.

Управление зависимостями через Homebrew

Управление внешними зависимостями проекта c помощью Homebrew Bundle.

Настройка Passenger для работы с Action Cable

Решаем проблему работы WebSocket-сервера через Phusion Passenger.

Настройка Rails-сервера на DigitalOcean

Настройка боевого Rails-сервера на DigitalOcean. Шаг за шагом.

Резервное копирование GitLab с помощью rsync

Мини-проект для автоматического резервного копирования GitLab c помощью Ruby и rsync.

Настройка Rails-окружения на OS X Yosemite

Полноценное рабочее окружение на OS X 10.10 Yosemite: Ruby, Homebrew, Oh My ZSH, rbenv и многое другое.