Как переместить логику с контроллера в модель?

У пула много адресов. Хотите создать несколько записей адресов на основе представленного диапазона.

У меня есть эта логика в моем address_controller:

def create @pool = Pool.find(params[:pool_id]) unless address_params[:ipv4_range_start].blank? || address_params[:ipv4_range_stop].blank? (address_params[:ipv4_range_start]..address_params[:ipv4_range_stop]).each do |octet| params[:address][:ipv4_octet3] = octet @address = @pool.addresses.build(address_params) if !@address.save render 'new' end end redirect_to pool_path(@pool), notice: "Address range created." else #something was missing @address = @pool.addresses.build(address_params) @address.errors.add_on_blank(:ipv4_range_start) @address.errors.add_on_blank(:ipv4_range_stop) render 'new' end end

Интересно, как я могу переместить это в модель Address? Похоже, слишком много для контроллера, но я не могу понять, как проходить через представленный диапазон, а также создавать и сохранять каждый адрес из самой модели Address.

Спасибо за любые предложения!

Джим

1 ответ

Я думаю, что ваша интуиция верна, что мы можем многое переместить в модель.

Отказ от ответственности: ни один из этих кодов не проверен; копирование и вставка его непосредственно в ваш код, вероятно, закончится слезами.

Итак, есть две части вашей логики, с которыми нам нужно иметь дело. Во-первых, убедитесь, что предусмотрены :ipv4_range_start и _stop. Для этого мы можем использовать проверку. Поскольку кажется, что вы не хотите, чтобы эти атрибуты требовались для всех Адресов, мы можем использовать опцию :on для предоставления контекста проверки. (Подробнее о контекстах здесь.):

# Address model
validates_presence_of :ipv4_range_start, :ipv4_range_stop, on: :require_range

Это on: :require_range part означает, что эта проверка обычно не запускается - она будет работать только тогда, когда мы скажем ActiveRecord использовать контекст :require_range.

Теперь мы можем сделать это в контроллере:

# AddressesController
def create @pool = Pool.find(params[:pool_id]) @address = @pool.addresses.build(address_params) if @address.invalid?(:require_range) render 'new' and return end # ...
end

Это выполняет то же, что и код в блоке else, но реальная логика находится в модели, а Rails заполняет объект errors для нас.

Теперь, когда у нас есть это, мы можем заниматься созданием объектов. Для этого мы можем написать метод класса в Address. Большая вещь о методах класса в моделях Rails является то, что они автоматически доступны коллекции ассоциации, так, например, если мы определим Address.foo метод класса, мы получаем @pool.addresses.foo бесплатно. Здесь метод класса, который будет создавать массив Адресов:

# Address model
def self.create_from_range!(attrs) start = attrs.fetch(:ipv4_range_start) stop = attrs.fetch(:ipv4_range_stop) self.transaction do (start..stop).map do |octet| self.create!(attrs.merge ipv4_octet3: octet) end end
end

Это почти так же, как ваш else блок, просто немного чище. Мы делаем self.create! в транзакции, так что если какой-либо из create! не все они будут отброшены назад. Мы делаем это внутри блока map вместо each так, чтобы, не допуская ошибки, метод вернет массив созданных объектов.

Теперь нам просто нужно использовать его в нашем контроллере:

def create # Our code from before @pool = Pool.find(params[:pool_id]) @address = @pool.addresses.build(address_params) if @address.invalid?(:require_range) render 'new' and return end # The new code begin @pool.addresses.create_from_range!(address_params) rescue ActiveRecord::ActiveRecordError flash[:error] = "Address range not created!" render 'new' and return end redirect_to @pool, notice: "Address range created."
end

Как вы можете видеть, мы использовали @pool.addresses.create_from_range! , поэтому ассоциация (Address#pool_id) будет заполнена для нас. Если бы мы хотели, мы могли бы присвоить возвращенный массив переменной экземпляра и показать созданные записи в представлении.

Это должно быть все, что вам нужно.

PS Стоит отметить, что Ruby обладает отличным встроенным классом IPAddr и потому, что IP-адрес - это просто номер (например, 3338456716 - десятичная форма 198.252.206.140), он может служить вам для хранения каждого IP-адреса как одного 4-байтовое целое вместо четырех отдельных столбцов. Большинство баз данных имеют полезные встроенные функции для работы с IP-адресами (PostgreSQL на самом деле имеет встроенный тип столбца inet для этого). Однако это зависит от вашего варианта использования, и на данный момент такая оптимизация может быть преждевременной. Ура!

licensed under cc by-sa 3.0 with attribution.