2009-07-26 3 views
27

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

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

Спасибо.

+0

Вместо того, чтобы ждать позже, почему бы не построить это в логике формы? По мере того, как значения собираются, поставьте чек, который переключает 'nil' для любой записи, содержащей только пробельные символы или вообще ничего. Таким образом, пробелы никогда не попадают в сохраненные значения. – Telemachus

+0

Я думаю, проблема в том, что значения nil не будут перезаписывать значение db. – chrishomer

+0

Поскольку это теперь находится на первой странице Google для соответствующего поиска, обратите внимание на других поисковиков: теперь есть драгоценный камень, который сделает это автоматически для вас - https://github.com/grosser/clear_empty_attributes –

ответ

24

Рассмотрите, что вы делаете здесь, используя фильтры в контроллере, чтобы повлиять на поведение модели при ее сохранении или обновлении. Я думаю, что более чистым методом будет вызов 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 
>> 
+0

«before_save: blank_if_nil» должно быть «before_save: nil_if_blank». Мне нравится этот подход лучше всего до сих пор, с кодом, введенным в модель. – jdl

+0

@jdl спасибо, что указал на опечатку. Исправлена. –

+3

Немного новичок в Ruby/Rails, но не хотел бы, чтобы before_validate был лучше, чем before_save здесь? –

1

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

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.delete_if {|k,v| v.blank?}.

+23

Осторожно с этим. Если вы используете update_attributes, и у вас есть поля, которые могут быть пустыми, пользователь никогда не сможет очистить эти поля, если вы удалите их из хэша params. – jdl

+0

спасибо. так много ответов на основе техники, когда простое программирование сделает трюк. фильтры, обратные вызовы, наблюдатели, все супер удивительные вещи. но когда одна простая строка рубинового кода работает отлично, почему бы не использовать ее? btw @jdl, это можно затянуть, как я сделал, чтобы обрабатывать поля пароля в rails 3.1 ... params [: user] .delete_if {| k, v | /.*password.*/.match(k) && v.blank?} Может ли кто-нибудь сказать СУХОЙ КИСС? – pduey

+2

Чтобы обратиться к проблеме @ jdl, сделайте параметры, которые хотите уничтожить явным в условии: 'params.delete_if {| k, v | % w (killme1 killme2) .include? (k) и v.blank?} ' – evanrmurphy

1

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

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

Пробовал это с Rails 3.2.3 и Ruby 1.9.3p0. Любая идея, почему это даст «неопределенный метод», собирает! » для # '? Просто заметил, что этой теме 3 года. Теперь есть лучший способ? –

+0

Это [Хеш] (http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html), а не массивы - хэши не понимают 'collect' –

+0

Хорошо, тогда я думаю, что я не понять свой первоначальный ответ. Не 'params [: user]' хэш параметров, переданных в UserController? –

1

Крис,

Вот рекурсивный разбор Params, которые имеют Блана значения.

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 
+0

Chris, Также обратите внимание, что нехорошо делать что-то вроде: container.each {|| ... #modifying контейнер здесь } если вы планируете удалять/добавлять элементы в контейнере (не дело здесь, но просто имейте это в виду) –

1

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

Однако, я не думаю, что в этом случае это правильно. Аффект, замеченный, сводится к тому, что он не может кодировать разницу между пустой строкой и значением 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]) 
... 
0

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

3

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

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

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

0

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

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"] } 
2

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

0

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

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

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

Смежные вопросы