2013-02-01 18 views
55

Mam w mojej aplikacji model Backbone, który nie jest typowym płaskim obiektem, jest to duży obiekt zagnieżdżony i przechowujemy zagnieżdżone części w kolumnach TEXT w bazie danych MySQL.Railsy konwertują puste tablice na nils w params żądania

Chciałem obsłużyć kodowanie/dekodowanie JSON w Rails API tak, że z zewnątrz wygląda na to, że możesz POST/GET ten jeden zagnieżdżony obiekt JSON, nawet jeśli jego części są przechowywane jako napisy JSON.

Jednak napotkałem problem, w którym Railsy w magiczny sposób konwertują puste tablice na wartości nil. Na przykład, jeśli POST to:

{ 
    name: "foo", 
    surname: "bar", 
    nested_json: { 
    complicated: [] 
    } 
} 

Mój kontroler Rails widzi to:

{ 
    :name => "foo", 
    :surname => "bar", 
    :nested_json => { 
    :complicated => nil 
    } 
} 

A więc moje dane JSON został zmieniony ..

ktoś napotkasz ten problem wcześniej? Dlaczego Railsy modyfikowały moje dane POST?

UPDATE

Tutaj jest gdzie zrobić:

https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288

A oto ~ dlaczego oni to robią:

https://github.com/rails/rails/pull/8862

Więc teraz jest pytanie , jak najlepiej sobie z tym poradzić w mojej sytuacji zagnieżdżonego interfejsu JSON API?

+0

Znalazłem, gdzie to robi deep_munge https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288. Nadal nie wiem, dlaczego to robi. – Karolis

+0

Linki do master/actionpack nie wskazują już właściwej linii. Link do tagu lub commit. –

Odpowiedz

38

Po wielu poszukiwaniach odkryłem, że zaczynasz w Railsach 4.1 można pominąć deep_munge „funkcji” całkowicie przy użyciu

config.action_dispatch.perform_deep_munge = false 

nie mogłem znaleźć żadnej dokumentacji, ale można zobaczyć wprowadzenie tej opcji tutaj: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

Jest możliwe zagrożenie bezpieczeństwa w czynieniu więc, udokumentowane tutaj: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

+2

Czy wiesz, jak mogę go wyłączyć, jeśli używam starszej wersji szyny? –

9

Wygląda na to, że jest to znana, niedawno wprowadzony problem: https://github.com/rails/rails/issues/8832

Jeśli wiesz, gdzie pusta tablica będzie zawsze można params[:...][:...] ||= [] w sposób przed filtrem.

Ewentualnie można modyfikować do JSON metodę kręgosłupie modelu wyraźnie stringifying wartość nested_json korzystając JSON.stringify() zanim zostanie wysłane i analizowania go ręcznie przy użyciu JSON.parse w before_filter.

Brzydko, ale zadziała.

+0

Tak to pierwotnie działało, analizowałem/szynąłem w Backbone. Jednak pomyślałem, że fajnie jest nie mieć częściowo napisanego JSON-a podczas publikowania - fajnie jest po prostu POST-aować jeden duży JSON, tak jakbym mówił do API bazującego na dokumencie. – Karolis

+0

Op. .. enter przesyła to pole tekstowe.Na razie po prostu poszedłem z łataniem ActionDispatch :: Request.deep_munge, jak omówiono na https://github.com/rails/rails/pull/8862, dopóki nie pojawi się w stabilnym Railsach, co mogło spowodować, że połączyły PR 2 dni temu. – Karolis

7

Możesz ponownie przeanalizować parametry na własną rękę, jak to:

class ApiController 
    before_filter :fix_json_params 

    [...] 

    protected 

    def fix_json_params 
    if request.content_type == "application/json" 
     @reparsed_params = JSON.parse(request.body.string).with_indifferent_access 
    end 
    end 

    private 

    def params 
    @reparsed_params || super 
    end 
end 

Działa to patrząc na prośby o zawartości typu JSON, re-parsowania treści żądania, a następnie przechwycenie params metoda zwracania ponownie przeanalizowanych parametrów, jeśli istnieją.

+3

Znalazłem [to sedno] (https://gist.github.com/imanel/e01089009479a1596056) pracując z Rails 3.2.13. – ciastek

+0

@ciastek great gist! – mateusmaso

+3

@Grandpa nie byłoby lepiej połączyć ponumerowane params z oryginalnymi parametrami? (np. parametry żądania adresu URL zostaną utracone) –

3

Wpadłem na podobny problem.

Naprawiono, wysyłając pusty ciąg jako część tablicy.

Więc idealnie twoi params chciałbym

{ 
    name: "foo", 
    surname: "bar", 
    nested_json: { 
    complicated: [""] 
    } 
} 

Więc zamiast wysyłać pustą tablicę zawsze przebieg („”) w moją prośbę do ominięcia głęboki proces munging.

-5

Oto sposób obejścia tego problemu.

def fix_nils obj 

    # fixes an issue where rails turns [] into nil in json data passed to params 

    case obj 
    when nil 
     return [] 
    when Array 
     return obj.collect { |x| nils_to_empty_arrays x } 
    when Hash 
     newobj = {} 
     obj.each do |k,v| 
     newobj[k] = nils_to_empty_arrays v 
     end 
     return newobj 
    else 
     return obj 
    end 

end 

A potem po prostu zrób

fixed_params = fix_nils params 

który działa tak długo, jak nie masz w swoich Nils params celowo.

+0

To jest dość straszne, ponieważ zmieni _all_ nils w puste tablice. Problem musi zostać rozwiązany w czasie parsetu, nie można go naprawić po parsowaniu, ponieważ nie można odróżnić rzeczywistych wartości pustych od pustych tablic. Jest całkowicie poprawne, aby ktoś przekazał 'null' jako wartość, a to przekształci ją w pustą tablicę. Po prostu utrwala problem, a nawet go pogarsza. – Percy

1

Wpadłem na podobny problem i okazało się, że przekazanie tablicy z pustym łańcuchem byłoby poprawnie przetwarzane przez Railsy, ​​jak wspomniano powyżej. W przypadku wystąpienia tego podczas składania formularza, może chcesz dołączyć pusty ukryte pole odpowiadający param tablicy:

<input type="hidden" name="model[attribute_ids][]"/> 

Gdy rzeczywisty param jest pusty regulator będzie zawsze widzieć tablicę z pustym ciągiem, dzięki temu przesyłanie danych stanie się bezpaństwowcem.

+0

Pomogło mi to w wyborze elementu, który korzystał z ukrytych pól do przesłania wybranych wartości, ale żadnego, jeśli nie było wyboru. Technika ta przypomina mi o hacku checkbox, który jest wbudowany w pomocnika formularza check_box_tag. – febeling

2

Oto (uważam) rozsądne rozwiązanie, które nie wymaga ponownego analizowania surowej treści żądania. To może nie zadziałać, jeśli twój klient zapisuje dane formularzy, ale w moim przypadku JESTEM POSTING.

w application_controller.rb:

# replace nil child params with empty list so updates occur correctly 
    def fix_empty_child_params resource, attrs 
    attrs.each do |attr| 
     params[resource][attr] = [] if params[resource].include? attr and params[resource][attr].nil? 
    end 
    end 

Następnie w kontrolerze ....

before_action :fix_empty_child_params, only: [:update] 

def fix_empty_child_params 
    super :user, [:child_ids, :foobar_ids] 
end 

wpadłem na to, w mojej sytuacji, gdy pisał zasobów zawiera zarówno child_ids: [] lub child_ids: nil Chcę, aktualizacja oznacza "usuń wszystkie dzieci". Jeśli klient nie zamierza aktualizować listy child_ids, nie powinien być wysyłany w treści POST, w takim przypadku params[:resource].include? attr będzie false, a parametry żądania pozostaną niezmienione.

+1

Uwaga, że ​​nie byłoby to dobre rozwiązanie dla PATCH – aceofspades

+1

@aceofspades Myślę, że masz rację! Chociaż gdyby klient wysłał 'PATCH', aby usunąć wszystkie relacje z innym zasobem, np.' {User: {child_ids: []}}, to działałoby bez tego rozwiązania? –

+1

W przeciwnym razie powyższe może zostać zaktualizowane, aby natychmiast powrócić, jeśli metoda żądania to "PATCH". Szkoda, że ​​nie jest łatwo ustalić, czy param został wysłany unieważniony, czy też nie został wysłany przez klienta w przypadku "PATCH". –

Powiązane problemy