Преимущество метода крана в рубине

Я просто читал статью в блоге и заметил, что автор использовал tap в фрагменте что-то вроде:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

Мой вопрос в том, что именно преимущество или преимущество использования tap? Могу я просто сделать:

user = User.new
user.username = "foobar"
user.save!

или еще лучше:

user = User.create! username: "foobar"

Ответ 1

Когда читатели сталкиваются:

user = User.new
user.username = "foobar"
user.save!

им нужно будет следовать всем трем строкам, а затем признать, что он просто создает экземпляр с именем user.

Если бы это было:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

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

Ответ 2

Другой случай использования крана - это сделать манипуляцию с объектом перед его возвратом.

Итак, вместо этого:

def some_method
  ...
  some_object.serialize
  some_object
end

мы можем сохранить дополнительную строку:

def some_method
  ...
  some_object.tap{ |o| o.serialize }
end

В некоторой ситуации этот метод может сэкономить более одной строки и сделать код более компактным.

Ответ 3

Использование tap, как сделал блоггер, - просто метод удобства. В вашем примере это может быть излишним, но в тех случаях, когда вы хотите сделать что-то с пользователем, коснитесь, возможно, обеспечит более удобный интерфейс. Поэтому, возможно, это может быть лучше в следующем примере:

user = User.new.tap do |u|
  u.build_profile
  u.process_credit_card
  u.ship_out_item
  u.send_email_confirmation
  u.blahblahyougetmypoint
end

Использование вышеизложенного позволяет легко увидеть, что все эти методы сгруппированы вместе, поскольку все они относятся к одному и тому же объекту (пользователь в этом примере). Альтернативой будет:

user = User.new
user.build_profile
user.process_credit_card
user.ship_out_item
user.send_email_confirmation
user.blahblahyougetmypoint

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

Ответ 4

Это может быть полезно при отладке серии ActiveRecord привязанных областей.

User
  .active                      .tap { |users| puts "Users so far: #{users.size}" } 
  .non_admin                   .tap { |users| puts "Users so far: #{users.size}" }
  .at_least_years_old(25)      .tap { |users| puts "Users so far: #{users.size}" }
  .residing_in('USA')

Это упрощает отладку в любой точке цепочки без необходимости хранить что-либо в локальной переменной и не требует значительного изменения исходного кода.

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

def rockwell_retro_encabulate
  provide_inverse_reactive_current
  synchronize_cardinal_graham_meters
  @result.tap(&method(:puts))
  # Will debug `@result` just before returning it.
end

Ответ 5

Визуализируйте свой пример внутри функции

def make_user(name)
  user = User.new
  user.username = name
  user.save!
end

При таком подходе существует большой риск обслуживания, в основном неявное возвращаемое значение.

В этом коде вы зависите от save!, возвращающего сохраненного пользователя. Но если вы используете другую утку (или ваш текущий эволюционирует), вы можете получить другие материалы, такие как отчет о статусе завершения. Поэтому изменения в утке могут нарушить код, что бы не произошло, если вы обеспечили возвращаемое значение с помощью простого user или используйте отвод.

Я видел такие случаи довольно часто, особенно с функциями, в которых возвращаемое значение обычно не используется, за исключением одного темного багги-угла.

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

def make_user(name)
  user = User.new
  user.username = name
  return user.save!       # notice something different now?
end

Ответ 6

Если вы хотите вернуть пользователя после установки имени пользователя, которое вам нужно выполнить

user = User.new
user.username = 'foobar'
user

С помощью tap вы можете сохранить это неудобное возвращение

User.new.tap do |user|
  user.username = 'foobar'
end

Ответ 7

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

Описание tap говорит:

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

Если мы ищем исходный код rails для использования tap, мы можем найти несколько интересных примеров использования. Ниже приведены несколько пунктов (не исчерпывающий список), которые дадут нам несколько идей о том, как их использовать:

  1. Добавить элемент в массив на основе определенных условий

    %w(
    annotations
    ...
    routes
    tmp
    ).tap { |arr|
      arr << 'statistics' if Rake.application.current_scope.empty?
    }.each do |task|
      ...
    end
    
  2. Инициализация массива и его возврат

    [].tap do |msg|
      msg << "EXPLAIN for: #{sql}"
      ...
      msg << connection.explain(sql, bind)
    end.join("\n")
    
  3. Как синтаксический сахар для того, чтобы сделать код более читабельным. В приведенном ниже примере можно сказать, что использование переменных hash и server делает цель кода более понятной.

    def select(*args, &block)
        dup.tap { |hash| hash.select!(*args, &block) }
    end
    
  4. Инициализируйте/вызовите методы для вновь созданных объектов.

    Rails::Server.new.tap do |server|
       require APP_PATH
       Dir.chdir(Rails.application.root)
       server.start
    end
    

    Ниже приведен пример из тестового файла

    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}]
      pirate.save!
    end
    
  5. Чтобы воздействовать на результат вызова yield без необходимости использования временной переменной.

    yield.tap do |rendered_partial|
      collection_cache.write(key, rendered_partial, cache_options)
    end
    

Ответ 8

Вариант ответа @sawa:

Как уже отмечалось, использование tap помогает понять намерение вашего кода (хотя и не обязательно делает его более компактным).

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

def tapping1
  # setting up a hash
  h = {}
  # working on it
  h[:one] = 1
  h[:two] = 2
  # returning the hash
  h
end

Здесь, с другой стороны, вы с самого начала знаете, что инициализированный хеш будет выходным блоком (и в этом случае возвращаемым значением функции).

def tapping2
  # a hash will be returned at the end of this block;
  # all work will occur inside
  Hash.new.tap do |h|
    h[:one] = 1
    h[:two] = 2
  end
end

Ответ 9

Я бы сказал, что нет пользы для использования tap. Единственное потенциальное преимущество, о чем указывает @sawa, и я цитирую: "Читателю не нужно было бы читать то, что находится внутри блока, чтобы знать, что пользователь экземпляра создано". Тем не менее, в этот момент можно сделать вывод, что если вы делаете логику создания не-упрощенных записей, ваше намерение будет лучше передано, извлекая эту логику в свой собственный метод.

Я придерживаюсь мнения, что tap является ненужной нагрузкой на читабельность кода и может быть выполнен без или заменен лучшей техникой, например Метод извлечения.

В то время как tap - метод удобства, это также личное предпочтение. Дайте tap попытку. Затем напишите некоторый код без использования крана, посмотрите, нравится ли вам один путь через другой.

Ответ 10

Это помощник для цепочки вызовов. Он передает свой объект в данный блок и после завершения блока возвращает объект:

an_object.tap do |o|
  # do stuff with an_object, which is in o #
end  ===> an_object

Преимущество в том, что tap всегда возвращает вызываемый объект, даже если блок возвращает другой результат. Таким образом, вы можете вставить блок ответвлений в середину существующего конвейера метода, не нарушая поток.

Ответ 11

Вы правы: использование tap в вашем примере является бессмысленным и, вероятно, менее чистым, чем ваши альтернативы.

Как отмечает Rebitzele, tap - это просто удобный метод, часто используемый для создания более короткой ссылки на текущий объект.

Один хороший вариант использования для tap предназначен для отладки: вы можете изменить объект, распечатать текущее состояние, а затем продолжить модификацию объекта в том же блоке. См. Здесь, например: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions.

Мне иногда нравится использовать tap внутри методов для условного возврата раньше, возвращая текущий объект в противном случае.

Ответ 12

Здесь может быть множество применений и мест, где мы можем использовать tap. До сих пор я нашел только 2 использования tap.

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

(1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
    tap    { |x| puts "array: #{x.inspect}" }.
    select { |x| x%2 == 0 }.
    tap    { |x| puts "evens: #{x.inspect}" }.
    map    { |x| x*x }.
    tap    { |x| puts "squares: #{x.inspect}" }

2) Вы когда-нибудь находили, что вызываете метод на каком-либо объекте, а возвращаемое значение не является тем, что вы хотели? Возможно, вы хотели добавить произвольное значение к набору параметров, хранящихся в хэше. Вы обновляете его с помощью Hash. [], но вы возвращаетесь bar вместо хэша params, поэтому вам нужно явно его вернуть. то есть

def update_params(params)
  params[:foo] = 'bar'
  params
end

Чтобы преодолеть эту ситуацию здесь, вступает в игру метод tap. Просто назовите его на объекте, а затем пройдите к блоку с кодом, который вы хотите запустить. Объект будет передан блоку, а затем будет возвращен. то есть

def update_params(params)
  params.tap {|p| p[:foo] = 'bar' }
end

Существуют десятки других случаев использования, попробуйте найти их сами:)

Источник:
1) Ссылка API Dock Object
2) five-ruby-methods-you-should-be-using

Ответ 13

В рельсах мы можем использовать tap для параметров белого списка:

def client_params
    params.require(:client).permit(:name).tap do |whitelist|
        whitelist[:name] = params[:client][:name]
    end
end

Ответ 14

Я приведу другой пример, который я использовал. У меня есть метод user_params, который возвращает параметры, необходимые для сохранения для пользователя (это проект Rails)

def user_params
  params.require(:user).permit(
    :first_name,
    :last_name,
    :email,
    :address_attributes
  )
end

Вы можете видеть, что я ничего не возвращаю, но ruby возвращает выходные данные последней строки.

Затем через некоторое время мне нужно было условно добавить новый атрибут. Итак, я изменил что-то вроде этого:

def user_params 
  u_params = params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  )
  u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  u_params
end

Здесь мы можем использовать tap, чтобы удалить локальную переменную и удалить возврат:

def user_params 
  params.require(:user).permit(
    :first_name, 
    :last_name, 
    :email,
    :address_attributes
  ).tap do |u_params|
    u_params[:time_zone] = address_timezone if u_params[:address_attributes]
  end
end

Ответ 15

Вы можете сделать свои коды более модульными с помощью крана и добиться лучшего управления локальными переменными. Например, в следующем коде вам не нужно назначать локальную переменную только что созданному объекту в рамках метода. Обратите внимание, что блок-блок, u, находится внутри блока. Это на самом деле одна из красавиц рубинового кода.

def a_method
  ...
  name = "foobar"
  ...
  return User.new.tap do |u|
    u.username = name
    u.save!
  end
end

Ответ 16

В чем разница?

Разница в удобочитаемости кода носит чисто стилистический характер.

Код пройти через:

user = User.new.tap do |u|
  u.username = "foobar"
  u.save!
end

Ключевые моменты:

  • Обратите внимание, как переменная u теперь используется в качестве параметра блока?
  • После завершения блока переменная user должна указывать на пользователя (с именем пользователя: ‘foobar и тем, кто также сохранен).
  • Это просто приятно и легче читать.

Документация по API

Вот легкая для чтения версия исходного кода:

class Object
  def tap
    yield self
    self
  end
end

Для получения дополнительной информации см. эти ссылки:

https://apidock.com/ruby/Object/tap

http://ruby-doc.org/core-2.2.3/Object.html#method-i-tap

Ответ 17

Существует инструмент под названием flog, который измеряет, насколько сложно читать метод. "Чем выше оценка, тем больше боли в коде".

def with_tap
  user = User.new.tap do |u|
    u.username = "foobar"
    u.save!
  end
end

def without_tap
  user = User.new
  user.username = "foobar"
  user.save!
end

def using_create
  user = User.create! username: "foobar"
end

и в соответствии с результатом порки метод с tap труднее всего читать (и я согласен с этим)

 4.5: main#with_tap                    temp.rb:1-4
 2.4:   assignment
 1.3:   save!
 1.3:   new
 1.1:   branch
 1.1:   tap

 3.1: main#without_tap                 temp.rb:8-11
 2.2:   assignment
 1.1:   new
 1.1:   save!

 1.6: main#using_create                temp.rb:14-16
 1.1:   assignment
 1.1:   create!