Есть ли чистый способ избежать вызова метода на nil во вложенном параметре hash?

Мне интересно получить вложенный параметр 'name' хэша params. Вызов чего-то типа

params[:subject][:name]

выдает ошибку, когда параметры [: subject] пусты. Чтобы избежать этой ошибки, я обычно пишу что-то вроде этого:

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

Есть ли более чистый способ реализовать это?

Ответ 1

Проверьте Ick. Вам не нужно значительно реорганизовывать свой код, просто перефразируйте, возможно, прокси, когда это необходимо:

params[:subject].maybe[:name]

Тот же автор (raganwald) также написал andand, с той же идеей.

Ответ 2

  • Вы можете использовать #try, но я не думаю, что это намного лучше:

    params[:subject].try(:[], :name)
    
  • Или используйте #fetch с параметром по умолчанию:

    params.fetch(:subject, {}).fetch(:name, nil)
    
  • Или вы можете установить #default= на новый пустой хэш, но не пытайтесь изменить значения, возвращаемые из этого:

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

    Он также разбивает все простые тесты на существование, поэтому вы не можете писать:

    if params[:subject]
    

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

    Также это всегда возвращает хеш, когда нет значения для ключа, даже если вы ожидаете строку.

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

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

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

Если вы беспокоитесь о доступе к полям, которые пользователь не должен устанавливать, добавьте attr_accessible белый список для полей whoich, которым должно быть разрешено устанавливать с массовым назначением (как в моем примере, с @subject.attributes = params[:subject] для обновления)

Ответ 3

Ruby 2.3.0 делает это очень легко сделать с # dig

h = {foo: {bar: {baz: 1}}}

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

Ответ 4

params[:subject].try(:[], :name) - самый чистый способ

Ответ 5

Когда у меня такая же проблема при кодировании, я иногда использую `rescue '.

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

Это не хорошие манеры, но я думаю, что это простой способ.

EDIT: Я больше не использую этот способ. Я рекомендую try или fetch.

Ответ 6

Не совсем. Вы можете попробовать fetch или try (из ActiveSupport), но он не намного чище, чем у вас уже есть.

Подробнее здесь:

UPDATE: Забыл andand:

andand позволяет:

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

Аналогично, вы можете использовать maybe из Ick library за ответ выше.

Ответ 7

Или добавьте к нему [].

class NilClass; def [](*); nil end end
params[:subject][:name]

Ответ 8

class Hash
  def fetch2(*keys)
    keys.inject(self) do |hash, key|
      hash.fetch(key, Hash.new)
    end
  end
end

например.

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

Ответ 9

Я перекрестился с этим ответом здесь:

Как проверить, нет ли параметров [: some] [: field] nil?

Я искал лучшее решение.

Итак, я решил позволить использовать try другой способ проверить наличие вложенного ключа:

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

Это не плохо. Вы получите nil vs. false, если он не установлен. Вы также получаете true, если параметр имеет значение nil.

Ответ 10

Я написал Dottie только для этого случая использования - доходит до хэша, не зная, существует ли все ожидаемое дерево. Синтаксис более краткий, чем использование try (Rails) или maybe (Ick). Например:

# in a Rails request, assuming `params` contains:
{ 'person' => { 'email' => '[email protected]' } } # there is no 'subject'

# standard hash access (symbols will work here
# because params is a HashWithIndifferentAccess)
params[:person][:email] # => '[email protected]'
params[:subject][:name] # undefined method `[]' for nil:NilClass

# with Dottie
Dottie(params)['person.email'] # => '[email protected]'
Dottie(params)['subject.name'] # => nil

# with Dottie optional class extensions loaded, this is even easier
dp = params.dottie
dp['person.email'] # => '[email protected]'
dp['subject.name'] # => nil
dp['some.other.deeply.nested.key'] # => nil

Проверьте документы, если вы хотите увидеть больше: https://github.com/nickpearson/dottie

Ответ 11

Я использовал:

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])

дает:

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

Ответ 12

Вы можете избежать двойного хеш-доступа с помощью встроенного назначения:

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