it-swarm.com.ru

Эквивалент .try () для хэша, чтобы избежать ошибок «неопределенного метода» для nil?

В Rails мы можем сделать следующее, если значение не существует, чтобы избежать ошибки:

@myvar = @comment.try(:body)

Что эквивалентно, когда я копаюсь в хэш и не хочу получить ошибку?

@myvar = session[:comments][@comment.id]["temp_value"] 
# [:comments] may or may not exist here

В приведенном выше случае session[:comments]try[@comment.id] не работает. Что бы?

158
sscirrus

Вы забыли поставить . перед try:

@myvar = session[:comments].try(:[], @comment.id)

поскольку [] - это имя метода, когда вы делаете [@comment.id].

253
Andrew Grimm

Объявление Ruby 2.3.0-preview1 включает введение оператора безопасной навигации.

Оператор безопасной навигации, который уже существует в C #, Groovy и Swift, введен для упрощения обработки нуля как obj&.foo. Array#Dig и Hash#Dig также добавлены.

Это означает, что с 2.3 ниже кода

account.try(:owner).try(:address)

можно переписать на

account&.owner&.address

Однако следует быть осторожным, чтобы & не был заменой #try. Взгляните на этот пример:

> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil

Он также включает в себя аналогичный способ: Array#Dig и Hash#Dig. Так что теперь это

city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)

можно переписать на

city = params.Dig(:country, :state, :city)

Опять же, #Dig не повторяет поведение #try. Так что будьте осторожны с возвращением значений. Если params[:country] возвращает, например, целое число, будет возбуждено TypeError: Integer does not have #Dig method.

60
baxang

Самое красивое решение - это старое ответ Младена Яблановича , поскольку оно позволяет вам копать хэш глубже, чем вы могли бы с помощью прямых вызовов .try(), если вы хотите, чтобы код по-прежнему выглядел хорошо:

class Hash
  def get_deep(*fields)
    fields.inject(self) {|acc,e| acc[e] if acc}
  end
end

Вы должны быть осторожны с различными объектами (особенно params), поскольку строки и массивы также отвечают на: [], но возвращаемое значение может быть не тем, что вы хотите, и массив создает исключение для строк или символов, используемых в качестве индексов.

Вот почему в предложенной форме этого метода (ниже) (обычно некрасиво) test для .is_a?(Hash) используется вместо (обычно лучше) .respond_to?(:[]):

class Hash
  def get_deep(*fields)
    fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
  end
end

a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}

puts a_hash.get_deep(:one, :two               ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three       ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr            ).inspect    # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect    # => nil

Последний пример вызовет исключение : "Символ как индекс массива (TypeError)", если он не защищен этим уродливым "is_a? (Hash)" ,.

25
Arsen7

Правильное использование try с хешем: @sesion.try(:[], :comments).

@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
14
Pablo Castellazzi

Обновление: Начиная с Ruby 2.3 использования # Dig

Большинство объектов, которые отвечают на [], ожидают аргумент Integer, а Hash является исключением, которое принимает любой объект (например, строки или символы).

Ниже приведена чуть более надежная версия ответ Arsen7 , которая поддерживает вложенный массив Array, Hash, а также любые другие объекты, которые ожидают, что целое число передано [].

Это не дурак, так как кто-то, возможно, создал объект, который реализует [] и не принимает аргумент Integer. Однако это решение прекрасно работает в общем случае, например извлечение вложенных значений из JSON (который имеет как Hash, так и Array):

class Hash
  def get_deep(*fields)
    fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
  end
end

Он может использоваться так же, как решение Arsen7, но также поддерживает массивы, например.

json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }

json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
14
Benjamin Dobell
@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]

Из Ruby 2.0 вы можете сделать:

@myvar = session[:comments].to_h[@comment.id].to_h["temp_value"]

Из Ruby 2.3 вы можете сделать:

@myvar = session.Dig(:comments, @comment.id, "temp_value")
12
sawa

Начиная с Ruby 2.3 это становится немного проще. Вместо того, чтобы вкладывать операторы try или определять свой собственный метод, теперь вы можете использовать Hash#Dig ( документация ).

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

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

Или в приведенном выше примере:

session.Dig(:comments, @comment.id, "temp_value")

Это дает дополнительное преимущество, так как больше напоминает try, чем некоторые из приведенных выше примеров. Если какой-либо из аргументов приведет к тому, что хеш вернет ноль, то он ответит ноль.

11
Steve Smith

скажем, вы хотите найти params[:user][:email], но не уверены, присутствует ли user в params или нет. Затем-

ты можешь попробовать:

params[:user].try(:[], :email)

Он вернет либо nil (если user отсутствует или email отсутствует в user), либо в противном случае значение email в user.

10
Rajesh Paul

Другой подход:

@myvar = session[:comments][@comment.id]["temp_value"] rescue nil

Это также может быть немного опасно, потому что может скрывать слишком много, лично мне это нравится.

Если вы хотите больше контроля, вы можете рассмотреть что-то вроде:

def handle # just an example name, use what speaks to you
    raise $! unless $!.kind_of? NoMethodError # Do whatever checks or 
                                              # reporting you want
end
# then you may use
@myvar = session[:comments][@comment.id]["temp_value"] rescue handle
6
Nicolas Goy

Когда вы делаете это:

myhash[:one][:two][:three]

Вы просто соединяете несколько вызовов метода "[]", и возникает ошибка, если myhash [: one] возвращает nil, потому что nil не имеет метода []. Итак, один простой и довольно хакерский способ - добавить метод [] в Niclass, который возвращает nil: я бы настроил это в приложении Rails следующим образом:

Добавьте метод:

#in lib/Ruby_extensions.rb
class NilClass
  def [](*args)
    nil
  end
end

Требовать файл:

#in config/initializers/app_environment.rb
require 'Ruby_extensions'

Теперь вы можете без опаски вызывать вложенные хэши: я демонстрирую в консоли здесь:

>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
2
Max Williams

Эндрю ответ не работал для меня, когда я попробовал это снова недавно. Может быть, что-то изменилось?

@myvar = session[:comments].try('[]', @comment.id)

'[]' в кавычках вместо символа :[]

1
claptimes