it-swarm.asia

الاستفادة من طريقة الصنبور في روبي

كنت فقط أقرأ مقالًا في مدونة ولاحظت أن المؤلف استخدم 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"
102
Kyle Decot

عندما يواجه القراء:

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

سيتعين عليهم اتباع جميع الأسطر الثلاثة ومن ثم إدراك أنه مجرد إنشاء مثيل باسم user.

لو كان:

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

ثم سيكون ذلك واضحا على الفور. لن يضطر القارئ إلى قراءة ما هو داخل الكتلة ليعلم أنه تم إنشاء مثيل user.

92
sawa

حالة أخرى لاستخدام الصنبور هي إجراء معالجة على الكائن قبل إعادته.

لذا بدلاً من هذا:

def some_method
  ...
  some_object.serialize
  some_object
end

يمكننا حفظ خط إضافي:

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

في بعض الحالات ، يمكن أن توفر هذه التقنية أكثر من سطر واحد وتجعل الشفرة أكثر إحكاما.

32
megas

إن استخدام النقر ، كما فعل المدون ، هو مجرد وسيلة ملائمة. ربما تكون مبالغة في مثالك ، ولكن في الحالات التي ترغب في القيام فيها بمجموعة من الأشياء مع المستخدم ، يمكن القول أن توفر واجهة تبحث أنظف. لذلك ، ربما يكون أفضل في مثال على النحو التالي:

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

مرة أخرى ، هذا أمر قابل للنقاش - لكن يمكن إثبات أن الإصدار الثاني يبدو أكثر فوضى ، ويستغرق تحليلًا بشريًا أكثر قليلاً لمعرفة أنه يتم استدعاء جميع الأساليب على نفس الكائن.

23
Rebitzele

قد يكون ذلك مفيدًا مع تصحيح سلسلة من النطاقات المتسلسلة 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
15
VKatz

تصور المثال الخاص بك داخل وظيفة

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
11
SystematicFrank

إذا كنت ترغب في إرجاع المستخدم بعد تعيين اسم المستخدم ، فستحتاج إلى القيام به

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

مع tap ، يمكنك حفظ هذا العائد المحرج

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

ينتج عنه رمز أقل تشوشًا حيث يقتصر نطاق المتغير فقط على الجزء الذي تكون هناك حاجة إليه حقًا. أيضا ، المسافة البادئة داخل كتلة يجعل رمز أكثر قابلية للقراءة عن طريق الحفاظ على رمز ذات الصلة معا.

وصف tap تقول :

ينتج نفسه عن الكتلة ، ثم يعيد نفسه. الغرض الأساسي من هذه الطريقة هو "الاستفادة من" سلسلة أسلوب ، من أجل إجراء عمليات على نتائج وسيطة داخل السلسلة.

إذا بحثنا عن مصدر شفرة Rails لـ tap use ، فيمكننا إيجاد بعض الاستخدامات المثيرة للاهتمام. فيما يلي بعض العناصر (ليست قائمة شاملة) التي ستمنحنا بعض الأفكار حول كيفية استخدامها:

  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
    
10
Wand Maker

أود أن أقول أنه لا توجد ميزة لاستخدام tap. الفائدة المحتملة الوحيدة ، كما يشير @ sawa هي ، وأقتبس: "لن يضطر القارئ إلى قراءة ما بداخل المجموعة لمعرفة أنه تم إنشاء مستخدم مثيل." ومع ذلك ، في هذه المرحلة ، يمكن تقديم حجة مفادها أنه إذا كنت تفعل منطقًا غير مبسط لإنشاء السجلات ، فسيتم توصيل نيتك بشكل أفضل عن طريق استخراج هذا المنطق إلى طريقته الخاصة.

أوافق على الرأي القائل بأن tap يمثل عبئًا غير ضروري على قابلية قراءة التعليمات البرمجية ، ويمكن القيام به بدون أو استبداله بتقنية أفضل ، مثل أسلوب استخراج .

في حين أن tap هي وسيلة ملائمة ، إلا أنها تفضيل شخصي أيضًا. جرب tap. ثم اكتب بعض التعليمات البرمجية دون استخدام الحنفية ، معرفة ما إذا كنت تحب طريقة واحدة على أخرى.

8
gylaz

تباين في إجابة 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
8
Giuseppe

إنه مساعد لتسلسل المكالمات. يمرر الكائن الخاص به إلى الكتلة المعطاة ، وبعد انتهاء الكتلة ، يُرجع الكائن:

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

الفائدة هي أن النقر يؤدي دائمًا إلى إرجاع الكائن الذي يسمى عليه ، حتى إذا كانت الكتلة ترجع بعض النتائج الأخرى. وبالتالي يمكنك إدراج كتلة صنبور في منتصف خط أنابيب الطريقة الحالية دون كسر التدفق.

8
Pushp Raj Saurabh

أنت على صواب: استخدام tap في مثالك هو نوع من العبث وربما أقل نظافة من بدائلك.

كما يلاحظ Rebitzele ، tap هي مجرد وسيلة ملائمة ، غالبًا ما تستخدم لإنشاء مرجع أقصر للكائن الحالي.

إحدى حالات الاستخدام الجيد لـ tap هي لتصحيح الأخطاء: يمكنك تعديل الكائن ، وطباعة الحالة الحالية ، ثم متابعة تعديل الكائن في نفس الكتلة. انظر هنا على سبيل المثال: http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions .

أنا أحيانًا أرغب في استخدام tap_الطرق الداخلية للعودة المشروطة مبكرًا أثناء إرجاع الكائن الحالي بخلاف ذلك.

3
Jacob Brown

يمكن أن يكون هناك عدد من الاستخدامات والأماكن التي قد نتمكن من استخدام tap. حتى الآن لم أجد إلا بعد استخدامين لـ 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) هل وجدت نفسك تستدعي طريقة على بعض الأشياء ، ولا تكون القيمة المرجعة هي ما تريده؟ ربما كنت ترغب في إضافة قيمة عشوائية إلى مجموعة من المعلمات المخزنة في التجزئة. تقوم بتحديثه مع تجزئة. [] ، لكنك تعود شريط بدلاً من تجزئة المعلمات ، لذلك يجب عليك إعادته بشكل صريح. أي

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) خمس طرق روبي ، يجب عليك أن تستخدم

3
Aamir

في Rails ، يمكننا استخدام tap لقائمة المعلمات بشكل صريح:

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

سأقدم مثالًا آخر استخدمته. لديّ طريقة user_params تُرجع المعلمات اللازمة للحفظ للمستخدم (هذا هو مشروع Rails)

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

يمكنك أن ترى أنني لا أعود بأي شيء إلا أن روبي يعيد إخراج السطر الأخير.

بعد ذلك ، بعد وقت ما ، كنت بحاجة إلى إضافة سمة جديدة بشروط. لذلك ، لقد غيرتها إلى شيء مثل هذا:

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

هنا يمكننا استخدام الحنفية لإزالة المتغير المحلي وإزالة العائد:

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
1
rubyprince

ماهو الفرق؟

الفرق من حيث قراءة الكود هو الأسلوبية البحتة.

رمز المشي من خلال:

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

0
BKSpurgeon

هناك أداة تسمى 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!
0
Evmorov

يمكنك جعل أكوادك أكثر حرفية باستخدام الحنفية ، ويمكنك تحقيق إدارة أفضل للمتغيرات المحلية. على سبيل المثال ، في التعليمة البرمجية التالية ، لا تحتاج إلى تعيين متغير محلي إلى الكائن الذي تم إنشاؤه حديثًا ، في نطاق الطريقة. لاحظ أن متغير الكتلة ، u ، يتم تحديد نطاقه داخل الكتلة. انها في الواقع واحدة من محاسن روبي كود.

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