Подтвердить что ты не робот

Сделать пустые параметры [] nil

Когда пользователь отправляет форму и оставляет определенные поля пустыми, они сохраняются как пустые в БД. Я хотел бы выполнить итерацию с помощью коллекции params [: user] (например), и если поле пустое, установите перед ним значение nil перед обновлением атрибутов. Я не могу понять, как это сделать, но поскольку единственный способ, которым я знаю для итерации, создает новые объекты:

coll = params[:user].each do |c|
    if c == ""
       c = nil
    end
end

Спасибо.

4b9b3361

Ответ 1

Рассмотрите, что вы делаете здесь, используя фильтры в контроллере, чтобы повлиять на поведение модели при ее сохранении или обновлении. Я думаю, что более чистым методом будет обратный вызов before_save в модели или наблюдателе. Таким образом, вы получаете то же поведение независимо от того, откуда происходит изменение, будь то через контроллер, консоль или даже при запуске пакетных процессов.

Пример:

class Customer < ActiveRecord::Base
  NULL_ATTRS = %w( middle_name )
  before_save :nil_if_blank

  protected

  def nil_if_blank
    NULL_ATTRS.each { |attr| self[attr] = nil if self[attr].blank? }
  end
end

Это дает ожидаемое поведение:

>> c = Customer.new
=> #<Customer id: nil, first_name: nil, middle_name: nil, last_name: nil>
>> c.first_name = "Matt"
=> "Matt"
>> c.middle_name = "" # blank string here
=> ""
>> c.last_name = "Haley"
=> "Haley"
>> c.save
=> true
>> c.middle_name.nil?
=> true
>>

Ответ 2

Если вы просто хотите убить пробелы, вы можете просто сделать params.delete_if {|k,v| v.blank?}.

Ответ 3

Хороший камень для обработки этого в модели: https://github.com/rmm5t/strip_attributes

Он определяет крюк before_validation, который обрезает пробелы и устанавливает пустые строки в nil.

Ответ 4

before_save кажется неправильным местом для меня, что если вы хотите использовать значение перед сохранением. Поэтому вместо этого я перенаправил сеттеры:

# include through module or define under active_record
def self.nil_if_blank(*args)
  args.each do |att|
    define_method att.to_s + '=' do |val|
      val = nil if val.respond_to?(:empty?) && val.empty?
      super(val)
    end
  end
end

#inside model
nil_if_blank :attr1, :attr2

Чтобы быть полным, я добавляю следующее в lib/my_model_extensions.rb

module MyModelExtensions
  def self.included(base)
    base.class_eval do
      def self.nil_if_blank(*args)
        args.each do |att|
          define_method att.to_s + '=' do |val|
            val = nil if val.respond_to?(:empty?) && val.empty?
            super(val)
          end
        end
      end
    end
  end
end

и используйте его следующим образом:

class MyModel
  include MyModelExtensions
  nil_if_blank :attr1, :attr2
end

Ответ 5

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

Однако, я не думаю, что в этом случае это правильно. Аффект, замеченный, сводится к тому, что он не может кодировать разницу между пустой строкой и значением nil в HTTP-запросе. По этой причине он должен быть исправлен на уровне контроллера. Это также означает, что в других местах по-прежнему можно сохранить пустую строку в модели (что может быть связано с законной причиной, а если нет, то это просто для покрытия стандартными проверками).

Код, который я использую для решения этой проблемы:

# application_controller.rb
...

def clean_params
  @clean_params ||= HashWithIndifferentAccess.new.merge blank_to_nil( params )
end

def blank_to_nil(hash)
  hash.inject({}){|h,(k,v)|
    h.merge(
      k => case v
      when Hash  : blank_to_nil v
      when Array : v.map{|e| e.is_a?( Hash ) ? blank_to_nil(e) : e}
      else v == "" ? nil : v
      end
    )
  }
end

...

Я пытался сохранить код как можно более кратким, хотя читаемость несколько пострадала, поэтому вот пример для демонстрации его функциональности:

require "test/unit"
class BlankToNilTest < Test::Unit::TestCase

  def blank_to_nil(hash)
    hash.inject({}){|h,(k,v)|
      h.merge(
        k => case v
        when Hash  : blank_to_nil v
        when Array : v.map{|e| e.is_a?( Hash ) ? blank_to_nil(e) : e}
        else v == "" ? nil : v
        end
      )
    }
  end

  def test_should_convert_blanks_to_nil
    hash =        {:a => nil, :b => "b", :c => ""}
    assert_equal( {:a => nil, :b => "b", :c => nil}, blank_to_nil(hash) )
  end

  def test_should_leave_empty_hashes_intact
    hash =        {:a => nil, :b => "b", :c => {}}
    assert_equal( {:a => nil, :b => "b", :c => {}}, blank_to_nil(hash) )
  end

  def test_should_leave_empty_arrays_intact
    hash =        {:a => nil, :b => "b", :c => []}
    assert_equal( {:a => nil, :b => "b", :c => []}, blank_to_nil(hash) )
  end

  def test_should_convert_nested_hashes
    hash =        {:a => nil, :b => "b", :c => {:d => 2, :e => {:f => "",  :g => "",  :h => 5}, :i => "bar"}}
    assert_equal( {:a => nil, :b => "b", :c => {:d => 2, :e => {:f => nil, :g => nil, :h => 5}, :i => "bar"}}, blank_to_nil(hash) )
  end

  def test_should_convert_nested_hashes_in_arrays
    hash =        {:book_attributes => [{:name => "b", :isbn => "" },{:name => "c", :isbn => "" }], :shelf_id => 2}
    assert_equal( {:book_attributes => [{:name => "b", :isbn => nil},{:name => "c", :isbn => nil}], :shelf_id => 2}, blank_to_nil(hash))
  end

  def test_should_leave_arrays_not_containing_hashes_intact
    hash =        {:as => ["", nil, "foobar"]}
    assert_equal( {:as => ["", nil, "foobar"]}, blank_to_nil(hash))
  end

  def test_should_work_with_mad_combination_of_arrays_and_hashes
    hash =        {:as => ["", nil, "foobar", {:b => "b", :c => "",  :d => nil, :e => [1,2,3,{:a => "" }]}]}
    assert_equal( {:as => ["", nil, "foobar", {:b => "b", :c => nil, :d => nil, :e => [1,2,3,{:a => nil}]}]}, blank_to_nil(hash))
  end

end

Это можно затем использовать в контроллере следующим образом:

...
@book.update_attributes(clean_params[:book])
...

Ответ 6

Вы можете использовать attribute_normalizer gem и использовать пустой нормализатор, который преобразует пустые строки в значениях nil.

Ответ 7

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

params = params.inject({}){|new_params, kv| 
  new_params[kv[0]] = kv[1].blank? ? nil : kv[1]
  new_params
}

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

params.merge(params){|k, v| v.blank? ? nil : v}

Ответ 8

Используйте метод сбора "на месте" (также известный как карта!)

params[:user].collect! {|c| c == "" ? nil : c}

Ответ 9

Крис

Вот рекурсивный синтаксический анализ параметров, имеющих значения blanc.

before_filter :process_params

......



private
def process_params
....
  set_blanc_values_to_nil(params)
end

# Maybe move method to ApplicationController
# recursively sets all blanc values to nil
def set_blanc_values_to_nil!(my_hash)
    my_hash.keys.each do |key|
        val = my_hash[key]
        next if val.nil?
        my_hash[key] = nil if val.is_a?(String) && val.empty?
        set_blanc_values_to_nil!(val) if val.is_a? Hash
    end
end

Ответ 10

Я обобщил ответ и сделал крючок/расширение, которое можно использовать в качестве инициализатора. Это позволяет использовать его для нескольких моделей. Я добавил его как часть моего ретрансляции ActiveRecordHelpers в GitHub

Ответ 11

Вот как я это сделал.

def remove_empty_params(param, key)
  param[key] = param[key].reject { |c| c.empty? }
end

и назовите его

remove_empty_params(params[:shipments], :included_clients)

Не нужно быть супер сложным в модели. И таким образом вы можете контролировать, какие параметры очищаются.

params = {
      "shipments"=>{
        "included_clients" => ["", "4"]
      }
    }

превратится в

>> params["shipments"]
=> {"included_clients" => ["4"] }

Ответ 12

Если вы знаете, какие атрибуты вы хотите кодировать заготовками как nils, вы можете использовать следующий атрибут setterter:

def colour=(colour)
  super(colour.blank? ? nil : colour)
end

Немного громоздкий, если у вас есть много атрибутов для покрытия.

Ответ 13

В контроллере приложений:

class ApplicationController < ActionController::Base

  def nilify(p)
    p.transform_values!{|v| v.present? ? v : nil }
  end

end

В вашем контроллере измените метод фильтра сильных параметров, чтобы вызвать nilify:

class UserController < ApplicationController

  def user_params
    nilify params.require(:user).permit(:email, :name)
  end

end