Existe uma maneira limpa de evitar chamar um método nil em um hash params nested?

Estou interessado em obter o parâmetro ‘name’ nested de um hash params. Chamando algo como

params[:subject][:name] 

gera um erro quando params [: subject] está vazio. Para evitar esse erro, geralmente escrevo algo assim:

 if params[:subject] && params[:subject][:name] 

Existe uma maneira mais limpa de implementar isso?

Verifique Ick talvez . Você não precisa refatorar significativamente seu código, apenas intercala talvez proxies quando necessário:

 params[:subject].maybe[:name] 

O mesmo autor ( raganwald ) também escreveu andand , com a mesma ideia.

  1. Você pode usar #try , mas não acho que seja muito melhor:

     params[:subject].try(:[], :name) 
  2. Ou use #fetch com o parâmetro padrão:

     params.fetch(:subject, {}).fetch(:name, nil) 
  3. Ou você pode definir #default= para novo hash vazio, mas não tente modificar os valores retornados a partir disso:

     params.default = {} params[:subject][:name] 

    Ele também quebra todos os testes simples para a existência, então você não pode escrever:

     if params[:subject] 

    porque ele irá retornar hash vazio, agora você tem que adicionar #present? ligue para todos os testes.

    Também isso sempre retorna hash quando não há valor para a chave, mesmo quando você espera string.

Mas pelo que vejo, você tenta extrair o parâmetro nested, em vez de atribuí-lo ao modelo e ali colocando sua lógica. Se você tem o modelo de Subject , basta atribuir:

 @subject = Subject.new(params[:subject]) 

shuld extrair todos os seus parâmetros usuário preenchido no formulário. Então você tenta salvá-los, para ver se o usuário passou valores válidos.

Se você estiver preocupado com o access a campos que o usuário não deve definir, adicione whitelist attr_accessible para campos que devem ser autorizados a definir com atribuição em massa (como no meu exemplo, com @subject.attributes = params[:subject] para atualização)

Ruby 2.3.0 torna isso muito fácil de fazer com #dig

 h = {foo: {bar: {baz: 1}}} h.dig(:foo, :bar, :baz) #=> 1 h.dig(:foo, :zot, :baz) #=> nil 

params[:subject].try(:[], :name) é a maneira mais limpa

Quando tenho o mesmo problema na codificação, às vezes uso `rescue ‘.

 name = params[:subject][:name] rescue "" # => "" 

Isso não é uma boa educação, mas acho que é uma maneira simples.

EDIT: Eu não uso mais dessa maneira com freqüência. Eu recomendo try ou fetch .

Na verdade não. Você pode tentar fetch ou try (do ActiveSupport), mas não é muito mais limpo do que o que você já tem.

Mais informações aqui:

  • Hash nested definido?

ATUALIZAÇÃO: Esqueceu sobre andand :

andand permite que você faça:

 params[:user].andand[:name] # nil guard is built-in 

Da mesma forma, você pode usar maybe da biblioteca Ick pela resposta acima .

Ou adicione [] a ele.

 class NilClass; def [](*); nil end end params[:subject][:name] 
 class Hash def fetch2(*keys) keys.inject(self) do |hash, key| hash.fetch(key, Hash.new) end end end 

por exemplo

 require 'minitest/autorun' describe Hash do it "#fetch2" do { yo: :lo }.fetch2(:yo).must_equal :lo { yo: { lo: :mo } }.fetch2(:yo, :lo).must_equal :mo end end 

Eu cross postou isso da minha resposta aqui:

Como verificar se params [: some] [: field] é nulo?

Eu tenho procurado por uma solução melhor também.

Então, imaginei que vamos usar uma maneira diferente de testar uma chave aninhada sendo definida:

 params[:some].try(:has_key?, :field) 

Não é ruim. Você recebe nil contra false se não estiver definido. Você também será true se o parâmetro estiver definido como nil .

Eu escrevi Dottie apenas para este caso de uso – alcançando profundamente um hash sem primeiro saber se existe toda a tree esperada. A syntax é mais sucinta do que usando try (Rails) ou maybe (Ick). Por exemplo:

 # in a Rails request, assuming `params` contains: { 'person' => { 'email' => 'jon@example.com' } } # there is no 'subject' # standard hash access (symbols will work here # because params is a HashWithIndifferentAccess) params[:person][:email] # => 'jon@example.com' params[:subject][:name] # undefined method `[]' for nil:NilClass # with Dottie Dottie(params)['person.email'] # => 'jon@example.com' Dottie(params)['subject.name'] # => nil # with Dottie's optional class extensions loaded, this is even easier dp = params.dottie dp['person.email'] # => 'jon@example.com' dp['subject.name'] # => nil dp['some.other.deeply.nested.key'] # => nil 

Confira os documentos se você quiser ver mais: https://github.com/nickpearson/dottie

Eu usei:

 params = {:subject => {:name => "Jack", :actions => {:peaceful => "use internet"}}} def extract_params(params, param_chain) param_chain.inject(params){|r,e| r=((r.class.ancestors.include?(Hash)) ? r[e] : nil)} end extract_params(params, [:subject,:name]) extract_params(params, [:subject,:actions,:peaceful]) extract_params(params, [:subject,:actions,:foo,:bar,:baz,:qux]) 

dá:

 => "Jack" => "use internet" => nil 

Você pode evitar o duplo access a hash com uma atribuição in-line:

 my_param = subj_params = params[:subject] && subj_params[:name]