الفرق بين المراجعتين لصفحة: «Rails/action cable overview»

من موسوعة حسوب
إنشاء الصفحة. هذه الصفحة من مساهمات "دعاء فرح"
 
طلا ملخص تعديل
 
(3 مراجعات متوسطة بواسطة نفس المستخدم غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:نظرة عامة على إجراء الربط في ريلز}}</noinclude>
<noinclude>{{DISPLAYTITLE:نظرة عامة على Action Cable في ريلز}}</noinclude>
ستتعرف في هذا الدليل على كيفية عمل Action Cable وكيفية استخدام WebSockets لدمج ميزات الوقت الفعلي في تطبيق Rails.
ستتعرف في هذا الدليل على كيفية عمل [[Rails/action cable|Action Cable]] وكيفية استخدام WebSockets لدمج ميزات الوقت الفعلي في تطبيق ريلز.


بعد قراءة هذا الدليل، ستعرف:
بعد قراءة هذا الدليل، ستتلعم:
* ما هو Action Cable و تكامل backend والواجهة الأمامية.
* ماهية [[Rails/action cable|Action Cable]] وكيفية دمج الواجهة الخلفية والأمامية الخاصة به.
* كيفية إعداد Action Cable.
* كيفية إعداد [[Rails/action cable|Action Cable]].
* كيفية إعداد القنوات.
* كيفية إعداد القنوات.
* النشر وإعداد الهندسة المعمارية لتشغيل Action Cable.
* النشر وإعداد المعمارية لتشغيل [[Rails/action cable|Action Cable]].


== المقدمة ==
== المقدمة ==
يقوم كابل العمل بدمج تطبيقات WebSockets مع باقي تطبيق Rails بسلاسة. يسمح لكتابة الميزات في الوقت الحقيقي في روبي بنفس الأسلوب والشكل كبقية تطبيق Rails، مع الاستمرار في الأداء وقابلية التطوير. إنه عرض كامل يوفر كلاً من إطار عمل JavaScript من جانب العميل وإطار عمل Ruby من جانب الخادم. لديك حق الوصول إلى نموذج المجال الكامل الخاص بك المكتوب مع Active Record أو ORM الخاص بك في الاختيار.
يدمج [[Rails/action cable|Action Cable]] مقابس الويب [[wikipedia:WebSocket|WebSockets]] مع باقي تطبيق ريلز بسلاسة. يسمح ذلك بكتابة الميزات في الوقت الحقيقي في روبي بنفس الأسلوب والشكل كبقية تطبيقات ريلز، مع الاستمرار في الأداء وقابلية التطوير. إنه عرض كامل يوفر كلًا من إطار عمل [[JavaScript]] من جانب العميل وإطار عمل روبي من جانب الخادم. لديك حق الوصول إلى نموذج المجال الكامل الخاص بك المكتوب مع [[Rails/active record|Active Record]] أو تقنية ORM الخاص بك في الاختيار.


== ماهو Pub/Sub ==
== ماهو Pub/Sub ==
يشير Pub / Sub أو Publish-Subscribe إلى نموذج قائمة انتظار الرسائل حيث يرسل مرسلو المعلومات (الناشرون) البيانات إلى فئة مجردة من المستلمين (المشتركين) دون تحديد المستلمين الفرديين. يستخدم كابل العمل هذا الأسلوب للتواصل بين الخادم والعديد من العملاء.
يشير Pub / Sub أو Publish-Subscribe إلى نموذج قائمة انتظار الرسائل حيث يرسل مرسلو المعلومات (الناشرون) البيانات إلى صنف مجرد من المستلمين (المشتركين) دون تحديد مستلمين فرديين. يستخدم [[Rails/action cable|Action Cable]] هذا الأسلوب للتواصل بين الخادم والعديد من العملاء.


== مكونات جانب - الخادم ==
== مكونات من جانب الخادم ==


=== الإتصالات ===
=== الاتصالات ===
تشكل الاتصالات أساس العلاقة بين العميل والخادم. لكل WebSocket التي قبلت من قبل الخادم، تُنشأ نسخة من كائن الاتصال. يصبح هذا الكائن أصل كل اشتراكات القناة التي أُنشأت من هناك. لا يتعامل الاتصال نفسه مع أي منطق تطبيق معين يتجاوز الاستيثاق والترخيص. يسمى عميل اتصال WebSocket مستهلك الاتصال. سينشئ مستخدم فردي زوجًا واحدًا للمستهلك للاتصال في كل علامة تبويب أو نافذة أو جهاز يفتحه المتصفح.
تشكل الاتصالات أساس العلاقة بين العميل والخادم. لكل مقبس ويب WebSocket مقبول من قبل الخادم، تُنشأ نسخة من كائن الاتصال. يصبح هذا الكائن الأب لكل اشتراكات القناة (channel subscriptions) التي أُنشأت من هناك. لا يتعامل الاتصال نفسه مع أي شيفرة تطبيق معين يتجاوز الاستيثاق والترخيص. يسمى عميل اتصال مقبس الويب WebSocket مستهلك الاتصال (connection consumer). سينشئ مستخدم فردي زوجًا واحدًا لمستهلك الاتصال في كل لسان أو نافذة أو جهاز يفتحه المتصفح.


الإتصالات هي نُسخ ApplicationCable :: Connection. في هذه الفئة، تسمح بالاتصال الوارد، وتشرع في تأسيسه إذا كان من الممكن التعرف على المستخدم.
الاتصالات هي نُسخ من <code>ApplicationCable::Connection</code>. في هذا الصنف، أنت تسمح بالاتصال الوارد، وتشرع في تأسيسه إذا كان من الممكن التعرف على المستخدم.


==== إعدادات الإتصال ====
==== إعدادات الاتصال ====
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/application_cable/connection.rb
# app/channels/application_cable/connection.rb
 
module ApplicationCable
module ApplicationCable
 
  class Connection < ActionCable::Connection::Base
 class Connection < ActionCable::Connection::Base
    identified_by :current_user
 
   identified_by :current_user
    def connect
 
      self.current_user = find_verified_user
   def connect
    end
 
     self.current_user = find_verified_user
    private
 
      def find_verified_user
   end
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
 
          verified_user
   private
        else
 
          reject_unauthorized_connection
     def find_verified_user
        end
 
      end
       if verified_user = User.find_by(id: cookies.encrypted[:user_id])
  end
 
end
         verified_user
 
       else
 
         reject_unauthorized_connection
 
       end
 
     end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


هنا ident_by هو معرف اتصال يمكن استخدامه للعثور على اتصال محدد في وقت لاحق. لاحظ أن أي علامة مُيزت كمعرف تُنشأ تلقائيًا مفوَّض بنفس الاسم على أي نُسخ قناة أُنشأت خارج الاتصال.
يكون <code>ident_by</code> هنا هو معرف اتصال يمكن استخدامه للعثور على اتصال محدد في وقت لاحق. لاحظ أن أي شيء مُيز كمعرف سيُنشِئ تلقائيًا مفوَّضًا (delegate) بنفس الاسم على أي نُسخة قناة أُنشأت خارج الاتصال.


يعتمد هذا المثال على حقيقة أنك ستتولى بالفعل استيثاق المستخدم في مكان آخر في التطبيق الخاص بك، وأن استيثاق ناجح يُعين ملف تعريف ارتباط موقّع عليه معرف المستخدم.
يعتمد هذا المثال على حقيقة أنك ستتولى بالفعل الاستيثاق من المستخدم في مكان آخر في التطبيق الخاص بك، ويُعيِّن ذلك الاستيثاق الناجح ملف تعريف ارتباط موقّع مع معرف المستخدم.


ثم يرسل ملف تعريف الارتباط تلقائيًا إلى نسخة الاتصال عند محاولة إجراء اتصال جديد، ويمكنك استخدام ذلك في تعيين current_user. من خلال تحديد الاتصال بواسطة هذا المستخدم الحالي نفسه، فإنك أيضًا تتأكد من أنه يمكنك في وقت لاحق استرداد جميع الاتصالات المفتوحة من قبل مستخدم معين (ومن المحتمل فصلها كلها إذا حذف المستخدم أو كان غير مصرح به).
بعد ذلك، يُرسَل ملف تعريف الارتباط تلقائيًا إلى نسخة الاتصال عند محاولة إجراء اتصال جديد، ويمكنك استخدام ذلك من تعيين <code>current_user</code>. من خلال تحديد الاتصال بواسطة هذا المستخدم الحالي نفسه، فإنك أيضًا تتأكد من أنه يمكنك في وقت لاحق استرداد جميع الاتصالات المفتوحة من قبل مستخدم معين (ومن المحتمل فصلها كلها إذا حذف المستخدم أو كان غير مصرح به).


=== القنوات ===
=== القنوات ===
تغلف القناة وحدة منطقية من العمل، مشابهة لما تفعله وحدة التحكم في إعداد MVC عادي. بشكل افتراضي، ينشئ Rails فئة الأصل ApplicationCable :: Channel لتغليف منطق مشترك بين قنواتك.
تغلف القناة وحدة منطقية من العمل، مشابهة لما تفعله وحدة التحكم في إعداد MVC عادي. بشكل افتراضي، ينشئ ريلز الصنف الأب <code>ApplicationCable::Channel</code> لتغليف شيفرة مشتركة بين قنواتك.


==== إعداد القناة الأصل ====
==== إعداد القناة الأب ====
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/application_cable/channel.rb
# app/channels/application_cable/channel.rb
 
module ApplicationCable
module ApplicationCable
 
  class Channel < ActionCable::Channel::Base
 class Channel < ActionCable::Channel::Base
  end
 
end
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


ثم يمكنك إنشاء فئات قناتك الخاصة. على سبيل المثال، قد يكون لديك ChatChannel و AppearanceChannel:
ثم يمكنك إنشاء أصناف قناتك (channel classes) الخاصة. على سبيل المثال، قد يكون لديك <code>ChatChannel</code> و <code>AppearanceChannel</code>:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/chat_channel.rb
# app/channels/chat_channel.rb
 
class ChatChannel < ApplicationCable::Channel
class ChatChannel < ApplicationCable::Channel
end
end
 
<nowiki>#</nowiki> app/channels/appearance_channel.rb
# app/channels/appearance_channel.rb
 
class AppearanceChannel < ApplicationCable::Channel
class AppearanceChannel < ApplicationCable::Channel
 
end
End
</syntaxhighlight>
</syntaxhighlight>


سطر 98: سطر 76:


==== الاشتراكات ====
==== الاشتراكات ====
يشترك المستهلكون في القنوات، كمشتركين. اتصالهم يسمى الاشتراك. ثم توجه الرسائل التي أُنتجت إلى اشتراكات القنوات هذه استنادًا إلى معرّف أُرسل بواسطة مستهلك الكبل.
يشترك المستهلكون في القنوات، كمشتركين (subscribers). اتصالهم يسمى «الاشتراك» (subscription). ثم توجه الرسائل التي أُنتجت إلى اشتراكات القنوات هذه استنادًا إلى معرّف أُرسل بواسطة مستهلك الوصل (cable consumer).
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/chat_channel.rb
# app/channels/chat_channel.rb
 
class ChatChannel < ApplicationCable::Channel
class ChatChannel < ApplicationCable::Channel
 
  # يُستدعى عندما يصبح المستهلك مشتركًا بهذه القناة
 # Called when the consumer has successfully
  def subscribed
 
  end
 # become a subscriber to this channel.
end
 
 def subscribed
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


== مكونات جانب - العميل ==
== مكونات من جانب العميل ==


=== الإتصالات ===
=== الاتصالات ===
يحتاج المستهلكون إلى نسخة من الاتصال على جانبهم. ينشأ هذا باستخدام JavaScript التالي، والذي أُنشأ افتراضيًا بواسطة Rails:
يحتاج المستهلكون إلى نسخة من الاتصال على جانبهم. ينشأ هذا باستخدام شيفرة [[JavaScript]] التالية، والتي تُولَّد افتراضيًا بواسطة ريلز:


==== مستهلك الاتصال ====
==== مستهلك الاتصال ====
<syntaxhighlight lang="rails">
<syntaxhighlight lang="javascript">
// app/assets/javascripts/cable.js
// app/assets/javascripts/cable.js
//= require action_cable
//= require action_cable
//= require_self
//= require_self
//= require_tree ./channels
//= require_tree ./channels
 
(function() {
(function() {
 
  this.App || (this.App = {});
 this.App || (this.App = {});
 
  App.cable = ActionCable.createConsumer();
 App.cable = ActionCable.createConsumer();
 
}).call(this);
}).call(this);
</syntaxhighlight>
</syntaxhighlight>


هذا سوف يعد المستهلك الذي سيتصل /cable على الخادم الخاص بك بشكل افتراضي. لن ينشأ الاتصال حتى تحدد أيضًا اشتراكًا واحدًا على الأقل ترغب في الحصول عليه.
هذا سوف يُجهِّز مستهلكًا سيتصل بـ ‎/cable على الخادم الخاص بك بشكل افتراضي. لن يُنشَأ الاتصال حتى تحدد أيضًا اشتراكًا واحدًا على الأقل ترغب في الحصول عليه.


==== المشترك ====
==== المشترك ====
يصبح المستهلك مشتركًا عن طريق إنشاء اشتراك لقناة معينة:
يصبح المستهلك مشتركًا عن طريق إنشاء اشتراك لقناة معينة:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/chat.coffee
# app/assets/javascripts/cable/subscriptions/chat.coffee
 
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
# app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create { channel: "AppearanceChannel" }


<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create { channel: "AppearanceChannel" }
</syntaxhighlight>
</syntaxhighlight>


بينما يقوم هذا بإنشاء الاشتراك، ستوصف الوظيفة المطلوبة للرد على البيانات المستلمة في وقت لاحق.
بينما يُنشِئ هذا الاشتراك، ستوصف الوظيفة المطلوبة للرد على البيانات المستلمة في وقت لاحق.


يمكن للمستهلك التصرف كمشترك في قناة معينة في أي عدد من المرات. على سبيل المثال، يمكن للمستهلك الاشتراك في غرف دردشة متعددة في نفس الوقت:
يمكن للمستهلك التصرف كمشترك في قناة معينة في أي عدد من المرات. على سبيل المثال، يمكن للمستهلك الاشتراك في غرف دردشة متعددة في نفس الوقت:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
</syntaxhighlight>
</syntaxhighlight>


== تفاعلات الخادم - العميل ==
== تفاعلات الخادم والعميل ==


=== التدفقات ===
=== المجاري ===
التدفقات توفر الآلية التي من خلالها مسار القنوات تنشر المحتوى (البث) لمشتركيهم.
توفر المجاري آلية توجه القنوات عبرها المحتوى المنشور (البث) لجميع المشتركين في ذلك المجرى.
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/chat_channel.rb
# app/channels/chat_channel.rb
 
class ChatChannel < ApplicationCable::Channel
class ChatChannel < ApplicationCable::Channel
 
  def subscribed
 def subscribed
    stream_from "chat_#{params[:room]}"
 
  end
   stream_from "chat_#{params[:room]}"
end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


إذا كان لديك تدفق مرتبط بنموذج، فيمكن إنشاء البث المستخدم من النموذج والقناة. سوف يشترك المثال التالي في بث مثل التعليقات: Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
إذا كان لديك مجرًى مرتبطًا بنموذج، فيمكن توليد البث المستخدم من النموذج والقناة. سوف يشترك المثال التالي في بث مثل comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
class CommentsChannel < ApplicationCable::Channel
class CommentsChannel < ApplicationCable::Channel
 
  def subscribed
 def subscribed
    post = Post.find(params[:id])
 
    stream_for post
   post = Post.find(params[:id])
  end
 
end
   stream_for post
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


سطر 200: سطر 154:
</syntaxhighlight>
</syntaxhighlight>


=== الإذاعة Broadcasting ===
=== البث (Broadcasting) ===
البث هو رابط pub / sub حيث يوجه أي شيء أُرسل من قبل ناشر مباشرة إلى المشتركين في القناة الذين يقومون ببث تلك الإذاعة المسماة. يمكن لكل قناة أن تبث بثًا أو أكثر من بث.
البث هو رابط pub / sub حيث يوجه أي شيء أُرسل من قبل ناشر مباشرة إلى المشتركين في القناة الذين يقومون ببث تلك الإذاعة المسماة. يمكن لكل قناة أن تبث بثًا أو أكثر من بث.


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


تسمى عمليات البث في مكان آخر في تطبيق Rails:
تُستدعَى عمليات البث في أي مكان آخر في تطبيق ريلز:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
WebNotificationsChannel.broadcast_to(
WebNotificationsChannel.broadcast_to(
 
  current_user,
 current_user,
  title: 'New things!',
 
  body: 'All the news fit to print'
 title: 'New things!',
 
 body: 'All the news fit to print'
 
)
)
</syntaxhighlight>
</syntaxhighlight>


يجري اتصال WebNotificationsChannel.broadcast_to بوضع رسالة في قائمة انتظار الاشتراك في محول الاشتراك الحالي (بشكل افتراضي redis للإنتاج و التزامن لبيئات التطوير والاختبار) تحت اسم بث منفصل لكل مستخدم. بالنسبة لمستخدم ذي معرف 1، سيكون اسم البث web_notifications: 1.
يضع الاستدعاء <code>WebNotificationsChannel.broadcast_to</code> رسالةً في قائمة انتظار الاشتراك في محول الاشتراك الحالي (بشكل افتراضي redis للإنتاج و التزامن لبيئات التطوير والاختبار) تحت اسم بث منفصل لكل مستخدم. بالنسبة لمستخدم ذي المعرف 1، سيكون اسم البث <code>web_notifications:1</code>.


توجه القناة لبث كل شيء يصل إلى web_notifications: 1 مباشرة إلى العميل عن طريق استدعاء رد الاتصال المتلقاة.
توجه القناة لبث كل شيء يصل إلى <code>web_notifications:1</code> مباشرة إلى العميل عن طريق استدعاء رد النداء <code>received</code>.


=== الاشتراكات ===
=== الاشتراكات ===
عندما يشترك المستهلك في قناة ، يكون بمثابة مشترك. هذا الاتصال يسمى الاشتراك. ثم توجه الرسائل الواردة إلى اشتراكات القنوات هذه استنادًا إلى مُعرّف أُرسل بواسطة مستهلك الكبل.
عندما يشترك المستهلك في قناة، يكون بمثابة مشترك (subscriber). هذا الاتصال يسمى الاشتراك (subscription). ثم توجه الرسائل الواردة إلى اشتراكات القنوات هذه استنادًا إلى مُعرّف أُرسل بواسطة مستهلك الربط (cable consumer).
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/chat.coffee
# app/assets/javascripts/cable/subscriptions/chat.coffee
 
# افترض أن طلبت مسبقًا الإذن لإرسال تنبيهات ويب
<nowiki>#</nowiki> Assumes you've already requested the right to send web notifications
 
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
 
  received: (data) ->
 received: (data) ->
    @appendLine(data)
 
   @appendLine(data)
  appendLine: (data) ->
 
    html = @createLine(data)
 appendLine: (data) ->
    $("[data-chat-room='Best Room']").append(html)
 
   html = @createLine(data)
  createLine: (data) ->
 
    """
   $("[data-chat-room='Best Room']").append(html)
    <article class="chat-line">
 
      <span class="speaker">#{data["sent_by"]}</span>
 createLine: (data) ->
      <span class="body">#{data["body"]}</span>
 
    </article>
   """
    """
 
   <article class="chat-line">
 
     <nowiki><span class="speaker">#{data["sent_by"]}</span></nowiki>
 
     <nowiki><span class="body">#{data["body"]}</span></nowiki>
 
   </article>
 
   """
</syntaxhighlight>
</syntaxhighlight>


=== تمرير المعاملات إلى القنوات ===
=== تمرير المعاملات إلى القنوات ===
يمكنك تمرير المعاملات من جانب العميل إلى جانب الخادم عند إنشاء اشتراك. فمثلا:
يمكنك تمرير المعاملات من جانب العميل إلى جانب الخادم عند إنشاء اشتراك. فمثلًا:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/chat_channel.rb
# app/channels/chat_channel.rb
 
class ChatChannel < ApplicationCable::Channel
class ChatChannel < ApplicationCable::Channel
 
  def subscribed
 def subscribed
    stream_from "chat_#{params[:room]}"
 
  end
   stream_from "chat_#{params[:room]}"
end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


يصبح الكائن الذي مُرر ليكون الوسيطة الأولى إلى subscriptions.create تجزئة params في قناة الكبل. الكلمة الرئيسية channel مطلوبة:
يصبح الكائن الذي مُرر ليكون الوسيط الأول إلى <code>subscriptions.create</code> المعاملات <code>params</code> (التي هي جدول Hash) في قناة الربط (cable channel). الكلمة المفتاحية <code>channel</code> مطلوبة:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/chat.coffee
# app/assets/javascripts/cable/subscriptions/chat.coffee
 
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
 
  received: (data) ->
 received: (data) ->
    @appendLine(data)
 
   @appendLine(data)
  appendLine: (data) ->
 
    html = @createLine(data)
 appendLine: (data) ->
    $("[data-chat-room='Best Room']").append(html)
 
   html = @createLine(data)
  createLine: (data) ->
 
    """
   $("[data-chat-room='Best Room']").append(html)
    <article class="chat-line">
 
      <span class="speaker">#{data["sent_by"]}</span>
 createLine: (data) ->
      <span class="body">#{data["body"]}</span>
 
    </article>
   """
    """
 
</syntaxhighlight><syntaxhighlight lang="rails">
   <article class="chat-line">
# يُستدعَى هذا في مكان ما في تطبيقك
 
# NewCommentJob ربما من
     <nowiki><span class="speaker">#{data["sent_by"]}</span></nowiki>
 
     <nowiki><span class="body">#{data["body"]}</span></nowiki>
 
   </article>
 
   """
 
<nowiki>#</nowiki> Somewhere in your app this is called, perhaps
 
<nowiki>#</nowiki> from a NewCommentJob.
 
ActionCable.server.broadcast(
ActionCable.server.broadcast(
 
  "chat_#{room}",
 "chat_#{room}",
  sent_by: 'Paul',
 
  body: 'This is a cool chat app.'
 sent_by: 'Paul',
 
 body: 'This is a cool chat app.'
 
)
)
</syntaxhighlight>
</syntaxhighlight>


=== إعادة إرسال رسالة ===
=== إعادة بث رسالة ===
حالة الاستخدام الشائعة هي إعادة إرسال رسالة أُرسلت بواسطة عميل واحد إلى أي عملاء آخرين متصلين.
حالة الاستخدام الشائعة هي إعادة رب (rebroadcast) رسالة أُرسلت بواسطة عميل واحد إلى أي عملاء آخرين متصلين.
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/chat_channel.rb
# app/channels/chat_channel.rb
 
class ChatChannel < ApplicationCable::Channel
class ChatChannel < ApplicationCable::Channel
 
  def subscribed
 def subscribed
    stream_from "chat_#{params[:room]}"
 
  end
   stream_from "chat_#{params[:room]}"
 
  def receive(data)
 end
    ActionCable.server.broadcast("chat_#{params[:room]}", data)
 
  end
 def receive(data)
end
 
</syntaxhighlight><syntaxhighlight lang="rails">
   ActionCable.server.broadcast("chat_#{params[:room]}", data)
# app/assets/javascripts/cable/subscriptions/chat.coffee
 
 end
 
End
 
<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/chat.coffee
 
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
 
  received: (data) ->
 received: (data) ->
    # data => { sent_by: "Paul", body: "This is a cool chat app." }
 
   # data => { sent_by: "Paul", body: "This is a cool chat app." }
 
App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
</syntaxhighlight>
</syntaxhighlight>سيُستلَم إعادة البث من قبل جميع العملاء المتصلين، بما في ذلك العميل الذي أرسل الرسالة. لاحظ أن <code>params</code> هي نفسها كما كانت عند الاشتراك في القناة.


سيتلقى إعادة البث من قبل جميع العملاء المتصلين، بما في ذلك العميل الذي أرسل الرسالة. لاحظ أن params هي نفسها كما كانت عند الاشتراك في القناة.
== أمثلة كاملة ==
 
خطوات الإعداد التالية متماثلة في كلا المثالين:
== أمثلة Full-Stack ==
# [[Rails/action cable overview#.D8.A5.D8.B9.D8.AF.D8.A7.D8.AF.D8.A7.D8.AA .D8.A7.D9.84.D8.A7.D8.AA.D8.B5.D8.A7.D9.84|إعداد الإتصال الخاص بك]].
خطوات الإعداد التالية شائعة في كلا المثالين:
# [[Rails/action cable overview#.D8.A5.D8.B9.D8.AF.D8.A7.D8.AF .D8.A7.D9.84.D9.82.D9.86.D8.A7.D8.A9 .D8.A7.D9.84.D8.A3.D8.A8|إعداد قناتك الأب]].
 
# [[Rails/action cable overview#.D9.85.D8.B3.D8.AA.D9.87.D9.84.D9.83 .D8.A7.D9.84.D8.A7.D8.AA.D8.B5.D8.A7.D9.84|الاتصال بالمستهلك]].
1 - إعداد الإتصال الخاص بك.
 
2 - إعداد قناتك الأصل.
 
3 - الإتصال بالمستهلك.


=== مثال 1: ظهور المستخدم ===
=== مثال 1: ظهور المستخدم ===
في ما يلي مثال بسيط لقناة تتعقب ما إذا كان المستخدم متصلاً أم لا وما هي الصفحة التي يستخدمونها. (يفيد ذلك في إنشاء ميزات التواجد مثل إظهار نقطة خضراء بجانب اسم المستخدم إذا كان متصلاً بالإنترنت).
في ما يلي مثال بسيط لقناة تتعقب ما إذا كان المستخدم متصلًا أم لا وما هي الصفحة التي يستخدمها. (يفيد ذلك في إنشاء ميزات التواجد مثل إظهار نقطة خضراء بجانب اسم المستخدم إذا كان متصلًا بالإنترنت).


إنشاء قناة تظهر جانب الخادم:
إنشاء قناة جانب الخادم لعرض آخر ظهور:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/appearance_channel.rb
# app/channels/appearance_channel.rb
 
class AppearanceChannel < ApplicationCable::Channel
class AppearanceChannel < ApplicationCable::Channel
 
  def subscribed
 def subscribed
    current_user.appear
 
  end
   current_user.appear
 
  def unsubscribed
 end
    current_user.disappear
 
  end
 def unsubscribed
 
  def appear(data)
   current_user.disappear
    current_user.appear(on: data['appearing_on'])
 
  end
 end
 
  def away
 def appear(data)
    current_user.away
 
  end
   current_user.appear(on: data['appearing_on'])
end
 
 end
 
 def away
 
   current_user.away
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


عند بدء الاشتراك، يلغى الاتصال المشترك، وننتهز هذه الفرصة لنقول "لقد ظهر المستخدم الحالي بالفعل". يمكن دعم واجهة برمجة التطبيقات التي تظهر / تختفي بواسطة Redis أو قاعدة بيانات أو أي شيء آخر.
عند بدء الاشتراك، يُطلَق استدعاء رد النداء <code>subscribed</code>، وننتهز هذه الفرصة لنقول "لقد ظهر المستخدم الحالي بالفعل". يمكن دعم واجهة برمجة التطبيقات التي تظهر / تختفي بواسطة Redis أو قاعدة بيانات أو أي شيء آخر.


إنشاء اشتراك قناة تظهر العميل:
إنشاء اشتراك قناة تظهر العميل:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/appearance.coffee
# app/assets/javascripts/cable/subscriptions/appearance.coffee
 
App.cable.subscriptions.create "AppearanceChannel",
App.cable.subscriptions.create "AppearanceChannel",
 
  # يُستدعَى عندما يصبح الاشتراك جاهزًا للاستعمال في الخادم
 # Called when the subscription is ready for use on the server.
  connected: ->
 
    @install()
 connected: ->
    @appear()
 
   @install()
  # WebSocket يُستدعَى عندما يُغلَق اتصال مقبس الويب
 
  disconnected: ->
   @appear()
    @uninstall()
 
 # Called when the WebSocket connection is closed.
  # يُستدعَى عندما يُرفَض الاشتراك من طرف الخادم
 
  rejected: ->
 disconnected: ->
    @uninstall()
 
   @uninstall()
  appear: ->
 
    # على الخادم `AppearanceChannel#appear(data)` يستدعي
 # Called when the subscription is rejected by the server.
    @perform("appear", appearing_on: $("main").data("appearing-on"))
 
 rejected: ->
  away: ->
 
    # على الخادم `AppearanceChannel#away` يستدعي
   @uninstall()
    @perform("away")
 
 appear: ->
 
  buttonSelector = "[data-behavior~=appear_away]"
   # Calls `AppearanceChannel#appear(data)` on the server.
 
  install: ->
   @perform("appear", appearing_on: $("main").data("appearing-on"))
    $(document).on "turbolinks:load.appearance", =>
 
      @appear()
 away: ->
 
    $(document).on "click.appearance", buttonSelector, =>
   # Calls `AppearanceChannel#away` on the server.
      @away()
 
      false
   @perform("away")
 
    $(buttonSelector).show()
 buttonSelector = "[data-behavior~=appear_away]"
 
  uninstall: ->
 install: ->
    $(document).off(".appearance")
 
    $(buttonSelector).hide()
   $(document).on "turbolinks:load.appearance", =>
 
     @appear()
 
   $(document).on "click.appearance", buttonSelector, =>
 
     @away()
 
     false
 
   $(buttonSelector).show()
 
 uninstall: ->
 
   $(document).off(".appearance")
 
   $(buttonSelector).hide()
</syntaxhighlight>
</syntaxhighlight>


==== تفاعل خادم - العميل ====
==== تفاعل الخادم - العميل ====
1 - يتصل العميل بالخادم عبر
1 - يتصل '''العميل''' '''بالخادم''' عبر:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
App.cable = ActionCable.createConsumer("ws://cable.example.com"). (cable.js).
App.cable =
ActionCable.createConsumer("ws://cable.example.com"). (cable.js)
</syntaxhighlight>
</syntaxhighlight>


يحدد الخادم هذا الاتصال بواسطة current_user.
يحدد الخادم هذا الاتصال بواسطة <code>current_user</code>.


2 - يشترك العميل في ظهور القناة عبر
2 - يشترك '''العميل''' في قناة آخر ظهور عبر:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
App.cable.subscriptions.create(channel: "AppearanceChannel"). (appearance.coffee)
App.cable.subscriptions.create(channel: "AppearanceChannel"). (appearance.coffee)
</syntaxhighlight>
</syntaxhighlight>


3 - يتعرف الخادم على بدء اشتراك جديد لظهور القناة ويشغل الاستدعاء المشترك الخاص به، مع استدعاء طريقة الظهور على current_user.
3 - يتعرف '''الخادم''' على الاشتراك الجديد المُنشَأ للتو بقناة آخر ظهور ويشغل رد النداء <code>subscribed</code> الخاص به، مع استدعاء التابع <code>appear</code> على <code>current_user</code>. (الملف appearance_channel.rb)


(appearance_channel.rb)
4 - يدرك '''العميل''' أنه أُنشأ الاشتراك ويستدعي appearance.coffe) <code>connected</code>) والذي يستدعي بدوره ‎<code>@install</code> و <code>appear@</code>. يستدعي <code>appear@</code> بعدئذٍ <code>(AppearanceChannel.appear(data</code> على '''الخادم'''، ويزود [[Ruby/Hash|جدول Hash]] من البيانات من <code>{ appearing_on: $("main").data("appearing-on")‎ }</code>. هذا ممكن لأن نسخة القناة من جانب الخادم تكشف تلقائيًا كل التوابع العامة المصرح عنها في الصنف (باستثناء ردود النداء)، بحيث يمكن الوصول إليها كإستدعاء إجراء بعيد عن طريق التابع <code>perform</code> الذي يخص الاشتراك.


4 - يدرك العميل أنه أُنشأ الإشتراك واستدعى appearance.coffe)  connected )  والتي بدورها تستدعي @install و appear@. و appear@ تستدعي  (AppearanceChannel#appear(data على الخادم. وتزود تجزئة البيانات من { appearing_on: $("main").data("appearing-on") }. هذا ممكن لأن طبقة القناة من جانب الخادم تكشف تلقائيًا كل التوابع العامة المعلنة في الفئة (ناقص عمليات الاسترجاعات)، بحيث يمكن الوصول إليها كإستدعاء إجراء بعيد عن طريق تابع perform الاشتراك.
5 - يتلقى '''الخادم''' طلب الإجراء <code>appear</code> في قناة آخر الظهور الخاصة بالاتصال المحدد بواسطة <code>current_user</code> (في الملف appearance_channel.rb). يسترد '''الخادم''' البيانات باستخدام المفتاح <code>:appearing_on</code> من [[Ruby/Hash|جدول Hash]] من البيانات ويعينها كقيمة <code>:on</code> على المفتاح الذي يمرر إلى <code>current_user.appear</code>.
 
5 - يتلقى الخادم طلب الإجراء appear في قناة الظهور الخاصة بالاتصال المحدد بواسطة (current_user (appearance_channel.rb. يسترد الخادم البيانات باستخدام المفتاح :appearing_on من تجزئة البيانات ويعينها كقيمة :on على المفتاح الذي يمرر إلى current_user.appear.


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


هذه قناة إخبارية على الويب تسمح لك بتشغيل إشعارات الويب من جانب العميل عند البث إلى مجموعات البث الصحيحة:
هذا المثال عبارة عن قناة إخبارية على الويب تسمح لك بتشغيل إشعارات الويب من جانب العميل عند البث إلى مجموعات البث الصحيحة:


إنشاء قناة إشعارات الويب من جانب الخادم:
إنشاء قناة إشعارات الويب من جانب الخادم:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/channels/web_notifications_channel.rb
# app/channels/web_notifications_channel.rb
 
class WebNotificationsChannel < ApplicationCable::Channel
class WebNotificationsChannel < ApplicationCable::Channel
 
  def subscribed
 def subscribed
    stream_for current_user
 
  end
   stream_for current_user
end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


إنشاء اشتراك قناة إشعارات الويب من جانب العميل:
إنشاء اشتراك قناة إشعارات الويب من جانب العميل:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/assets/javascripts/cable/subscriptions/web_notifications.coffee
# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
 
# يفترض جانب العميل أنك طلبت مسبقًا إذنًا لإرسال تنبيهات ويب
<nowiki>#</nowiki> Client-side which assumes you've already requested
 
<nowiki>#</nowiki> the right to send web notifications.
 
App.cable.subscriptions.create "WebNotificationsChannel",
App.cable.subscriptions.create "WebNotificationsChannel",
  received: (data) ->
    new Notification data["title"], body: data["body"]


 received: (data) ->
   new Notification data["title"], body: data["body"]
</syntaxhighlight>
</syntaxhighlight>


بث المحتوى إلى نسخة قناة إعلام عبر الإنترنت من مكان آخر في التطبيق الخاص بك:
بث المحتوى إلى نسخة قناة تنبيه عبر الويب (web notification channel) من مكان آخر في التطبيق الخاص بك:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> Somewhere in your app this is called, perhaps from a NewCommentJob
# NewCommentJob يُستدعَى هذا من مكان من من تطبيقك وربما من
 
WebNotificationsChannel.broadcast_to(
WebNotificationsChannel.broadcast_to(
 
  current_user,
 current_user,
  title: 'New things!',
 
  body: 'All the news fit to print'
 title: 'New things!',
 
 body: 'All the news fit to print'
 
)
)
</syntaxhighlight>
</syntaxhighlight>


يجري اتصالًا WebNotificationsChannel.broadcast_to بوضع رسالة في قائمة انتظار pubsub لمحول الاشتراك الحالي تحت اسم بث منفصل لكل مستخدم. بالنسبة لمستخدم ذي معرف 1، سيكون اسم البث web_notifications: 1.
يضع الاستدعاء <code>WebNotificationsChannel.broadcast_to</code> رسالةً في طابور انتظار النشر والاشتراك (pubsub) لمحول الاشتراك الحالي تحت اسم بث منفصل لكل مستخدم. بالنسبة لمستخدم ذي المعرف 1، سيكون اسم البث <code>web_notifications:1</code>.


توجه القناة لبث كل شيء يصل إلى web_notifications: 1 مباشرة إلى العميل عن طريق استدعاء received الاتصال المتلقاة. البيانات التي تُمرر كوسيطة هي البعثرة المرسلة كمعامل ثاني لمكالمة البث من جانب الخادم، JSON تشفرها للرحلة عبر السلك وتفككها لوسيطة البيانات عند الوصول كـ received.
توجه القناة لبث كل شيء يصل إلى <code>web_notifications:1</code> مباشرةً إلى العميل عن طريق استدعاء رد النداء <code>received</code>. البيانات التي تُمرر كوسيط هي جدول Hash المرسل كمعامل ثاني لاستدعاء البث من جانب الخادم، إذ يشفر بصيغة JSON أثناء رحلته عبر السلك ويفك تشفيره من أجل وسيط البيانات عند الوصول كـ <code>received</code>.


=== المزيد من الأمثلة الكاملة ===
=== المزيد من الأمثلة الكاملة ===
راجع مستودع rails / actioncable-examples لمثال كامل عن كيفية إعداد كابل الإجراء في تطبيق Rails وإضافة قنوات.
راجع المستودع [https://github.com/rails/actioncable-examples rails/actioncable-examples] لمثال كامل عن كيفية إعداد Action Cable في تطبيق ريلز وإضافة القنوات.


== إعدادات التكوين ==
== الضبط ==
يحتوي "كبل الإجراء" على تهيئتين مطلوبتين: مهايئ اشتراك وأصول طلب مسموح بها.
يتطلب "Action Cable" إعداد ضبطين هما: محول الاشتراك (subscription adapter) والأصول المسموح طلبها.


=== محول الاشتراك ===
=== محول الاشتراك ===
بشكل افتراضي، يبحث Action Cable عن ملف تكوين في config / cable.yml. يجب أن يحدد الملف محولًا لكل بيئة Rails. انظر قسم التبعيات للحصول على معلومات إضافية حول المحولات.
بشكل افتراضي، يبحث Action Cable عن ملف ضبط في config/cable.yml. يجب أن يحدد الملف محولًا لكل بيئة ريلز. انظر قسم [[Rails/action cable overview#.D8.A7.D9.84.D8.AA.D8.A8.D8.B9.D9.8A.D8.A7.D8.AA|التبعيات]] للحصول على معلومات إضافية حول المحولات.
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
development:
development:
 
  adapter: async
 adapter: async
 
test:
test:
 
  adapter: async
 adapter: async
 
production:
production:
 
  adapter: redis
 adapter: redis
  url: redis://10.10.3.153:6381
 
  channel_prefix: appname_production
 url: <nowiki>redis://10.10.3.153:6381</nowiki>
 
 channel_prefix: appname_production
</syntaxhighlight>
</syntaxhighlight>


==== إعدادات تكوين محول ====
==== ضبط المحول ====
فيما يلي قائمة بمحولات الاشتراك المتاحة للمستخدمين النهائيين.
فيما يلي قائمة بمحولات الاشتراك المتاحة للمستخدمين النهائيين.
* محول متزامن : المحول المتزامن هو من أجل التطوير / الاختبار ويجب عدم استخدامه في الإنتاج.
* المحول Async: المحول المتزامن (async adapter) هو من أجل التطوير / الاختبار ويجب عدم استخدامه في الإنتاج.
* محول Redis : يتطلب محول Redis من المستخدمين توفير عنوان URL يشير إلى خادم Redis. بالإضافة إلى ذلك، قد يوفر channel_prefix لتجنب تضارب اسم القناة عند استخدام خادم Redis نفسه لتطبيقات متعددة. راجع وثائق Redis PubSub لمزيد من التفاصيل.
* المحول Redis: يتطلب المحول Redis من المستخدمين توفير عنوان URL يشير إلى خادم Redis. بالإضافة إلى ذلك، قد يُوفَّر <code>channel_prefix</code> لتجنب تضارب اسم القناة عند استخدام خادم Redis نفسه لتطبيقات متعددة. راجع [https://redis.io/topics/pubsub#database-amp-scoping توثيق Redis للنشر/الاشتراك] لمزيد من التفاصيل.
* محول PostgreSQL : يستخدم محول PostgreSQL تجمع اتصال Active Record، وبالتالي تكوين قاعدة بيانات التكوين / database.yml للتطبيق، للاتصال به. هذا قد يتغير في المستقبل. # 27214
* المحول PostgreSQL: يستخدم المحول PostgreSQL مُجمَّع اتصال [[Rails/active record|Active Record]]، وبالتالي ضبط قاعدة البيانات config/database.yml للتطبيق، للاتصال به. هذا قد يتغير في المستقبل. [https://github.com/rails/rails/issues/27214 #27214].


=== طلب اصول مسموح بها ===
=== الأصول المسموح لها بالطلب ===
يقبل كابل الإجراء فقط الطلبات من الأصول المحددة، والتي مُررت إلى تهيئة الخادم كمصفوفة. يمكن أن تكون الأصول عبارة عن نسخ من السلاسل أو التعبيرات العادية، والتي يقابلها إجراء فحص متطابق.
يقبل [[Rails/action cable|Action Cable]] طلبات من أصول محددة فقط، والتي مُررت إلى ضبط الخادم كمصفوفة. يمكن أن تكون الأصول عبارة عن نسخ من السلاسل النصية أو التعبيرات النمطية، والتي تجري تطابقًا محددًا.
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
config.action_cable.allowed_request_origins = ['<nowiki>http://rubyonrails.com'</nowiki>, %r{[[about:blank|http://ruby.*]]}]
config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}]
</syntaxhighlight>
</syntaxhighlight>


لتعطيل والسماح بالطلبات من أي مصدر:
لتعطيل والسماح بالطلبات من أي أصل:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
config.action_cable.disable_request_forgery_protection = true
config.action_cable.disable_request_forgery_protection = true
</syntaxhighlight>
</syntaxhighlight>


بشكل افتراضي، يتيح لك Action Cable جميع الطلبات من localhost: 3000 عند التشغيل في بيئة التطوير.
بشكل افتراضي، يسمح [[Rails/action cable|Action Cable]] بجميع الطلبات من localhost:3000 عند التشغيل في بيئة التطوير.


=== إعدادات تكوين المستهلك ===
=== ضبط المستهلك ===
لتهيئة عنوان URL، أضف استدعاء إلى action_cable_meta_tag في تنسيق HTML HEAD. يستخدم هذا عنوان URL أو مسار يُعين عادةً عبر config.action_cable.url في ملفات تهيئة البيئة.
لتهيئة عنوان URL، أضف استدعاءً إلى <code>action_cable_meta_tag</code> في شيفرة HTML الخاصة بك في الوسم <code>[[HTML/head|<head>]]</code>. يستخدم هذا عنوان URL أو مسار يُعين عادةً عبر <code>config.action_cable.url</code> في ملفات ضبط البيئة.


=== إعدادات تكوينات اخرى ===
=== ضبط أمور أخرى ===
الخيار المشترك الآخر للتكوين هو علامات التسجيل المطبقة على مسجل الاتصال. في ما يلي مثال يستخدم معرف حساب المستخدم إذا كان متاحًا، أو "بدون حساب" آخر أثناء وضع العلامات:
الخيار المشترك الآخر المراد ضبطه هو وسوم التسجيل (log tags) المطبقة على مسجل الاتصال. في ما يلي مثال يستخدم معرف حساب المستخدم إذا كان متاحًا، أو "بدون حساب" آخر أثناء وضع الوسوم:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
config.action_cable.log_tags = [
config.action_cable.log_tags = [
 
  -> request { request.env['user_account_id'] || "no-account" },
 -> request { request.env['user_account_id'] || "no-account" },
  :action_cable,
 
  -> request { request.uuid }
 :action_cable,
 
 -> request { request.uuid }
 
]
]
</syntaxhighlight>
</syntaxhighlight>


للحصول على قائمة كاملة بجميع خيارات التكوين، راجع فئة ActionCable :: Server :: Configuration.
للحصول على قائمة كاملة بجميع خيارات الضبط، راجع الصنف <code>ActionCable::Server::Configuration</code>.


لاحظ أيضًا أن الخادم الخاص بك يجب أن يوفر على الأقل نفس عدد اتصالات قاعدة البيانات كما تعمل لديك. يُعين حجم تجمع العامل الافتراضي إلى 4، وهذا يعني أن عليك إجراء ذلك على الأقل المتوفرة. يمكنك تغيير ذلك في config / database.yml من خلال سمة pool.
لاحظ أيضًا أن الخادم الخاص بك يجب أن يوفر على الأقل نفس عدد اتصالات قاعدة البيانات كما تعمل لديك. يُعين حجم المجمع الافتراضي للعملية العاملة (default worker pool size) إلى 4، وهذا يعني أن عليك أن تجعل ذلك متوافرًا على الأقل. يمكنك تغيير ذلك في config/database.yml من خلال الخاصية <code>pool</code>.


== تشغيل خوادم الكابلات المستقلة ==
== تشغيل خوادم ربط مستقلة ==


=== في التطبيق ===
=== في التطبيق ===
يمكن تشغيل كبل الإجراء مع تطبيق Rails الخاص بك. على سبيل المثال، للاستماع لطلبات WebSocket على / websocket، حدد ذلك المسار إلى config.action_cable.mount_path:
يمكن تشغيل [[Rails/action cable|Action Cable]] بشكل منفصل ومستقل مع تطبيق ريلز الخاص بك. على سبيل المثال، للاستماع لطلبات مقبس ويب WebSocket على /websocket، حدد ذلك المسار في الضبط <code>config.action_cable.mount_path</code>:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> config/application.rb
# config/application.rb
 
class Application < Rails::Application
class Application < Rails::Application
 
  config.action_cable.mount_path = '/websocket'
 config.action_cable.mount_path = '/websocket'
end
 
End
</syntaxhighlight>
</syntaxhighlight>


يمكنك استخدام () App.cable = ActionCable.createConsumer للاتصال بملقم الكبل إذا استدعى action_cable_meta_tag في التخطيط.  
يمكنك استخدام <code>()App.cable = ActionCable.createConsumer</code> للاتصال بخادم الربط (cable server) إذا استُدعِي <code>action_cable_meta_tag</code> في التخطيط.  


يحدد مسار مخصص كوسيطة أولى createConsumer مثل
يُحدَّد مسار مخصص كوسيط أول إلى <code>createConsumer</code> مثل:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
(("App.cable =ActionCable.createConsumer ( "/ websocket.
App.cable =
ActionCable.createConsumer("/websocket")
</syntaxhighlight>
</syntaxhighlight>


لكل نسخة من خادمك تنشأه ولأي عامل ينتج عن خادمك، سيكون لديك أيضًا نسخة جديدة من كبل اإجراء، ولكن استخدام Redis يبقي الرسائل متزامنة عبر الاتصالات.
لكل نسخة تنشئها من خادمك ولكل عملية عاملة (worker) يولدها خادمك، سيكون لديك أيضًا نسخة جديدة من [[Rails/action cable|Action Cable]]، ولكن استخدام Redis يبقي الرسائل متزامنة عبر الاتصالات.


=== مستقل ===
=== الاستقلالية ===
يمكن فصل خادم الكبل عن خادم التطبيق العادي. لا يزال تطبيق Rack، ولكنه تطبيق Rack الخاص به. الإعداد الأساسي الموصى به هو كما يلي:
يمكن فصل خادم الربط (cable servers) عن خادم التطبيق العادي. لا يزال تطبيق Rack، ولكنه تطبيق Rack الخاص به. الإعداد الأساسي الموصى به هو كما يلي:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> cable/config.ru
# cable/config.ru
 
require_relative '../config/environment'
require_relative '../config/environment'
Rails.application.eager_load!
Rails.application.eager_load!
run ActionCable.server


run ActionCable.server
</syntaxhighlight>
</syntaxhighlight>


ثم يبدأ الخادم باستخدام binstub في bin / cable ala:
ثم يمكن بدء الخادم باستخدام binstub في <code>bin/cable</code>:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="bash">
<nowiki>#</nowiki>!/bin/bash
#!/bin/bash
 
bundle exec puma -p 28080 cable/config.ru
bundle exec puma -p 28080 cable/config.ru
</syntaxhighlight>
</syntaxhighlight>


ما ورد أعلاه سيبدأ خادم كابل على المنفذ 28080.
بتنفيذ ذلك، سيبدأ خادم الربط على المنفذ 28080.


=== ملاحظات ===
=== ملاحظات ===
لا يمتلك خادم WebSocket إمكانية الوصول إلى الجلسة، ولكن لديه حق الوصول إلى ملفات تعريف الارتباط. يمكن استخدام هذا عندما تحتاج إلى التعامل مع الاستيثاق. يمكنك رؤية طريقة واحدة للقيام بذلك باستخدام Devise في هذه المقالة.
لا يمتلك خادم WebSocket إمكانية الوصول إلى الجلسة، ولكن لديه حق الوصول إلى ملفات تعريف الارتباط. يمكن استخدام هذا عندما تحتاج إلى التعامل مع الاستيثاق. يمكنك رؤية طريقة واحدة للقيام بذلك باستخدام Devise في [http://www.rubytutorial.io/actioncable-devise-authentication هذه المقالة].


== التبعيات ==
== التبعيات ==
يوفر "كبل الإجراء" واجهة محول اشتراك لمعالجة عمليات pubsub الداخلية الخاصة به. بشكل افتراضي، تتضمن محولات غير متزامنة و مضمنة و PostgreSQL و Redis. المحول الافتراضي في تطبيقات Rails جديد هو محول غير متزامن (async).
يوفر "[[Rails/action cable|Action Cable]]" واجهة محول اشتراك لمعالجة عمليات النشر والاشتراك (pubsub) الداخلية الخاصة به. بشكل افتراضي، تُضمَّن المحولات غير متزامنة والسطرية و PostgreSQL و Redis. المحول الافتراضي في تطبيق ريلز جديد هو محول غير متزامن (async).


تم بناء جانب روبي من الأشياء على قمة websocket-driver، nio4r، and conjurrent-ruby.
تم بناء أشياء من طرف روبي فوق [https://github.com/faye/websocket-driver-ruby websocket-driver]، و [https://github.com/celluloid/nio4r nio4r]، و [https://github.com/ruby-concurrency/concurrent-ruby conjurrent-ruby].


== النشر ==
== النشر ==
يعمل كبل الاجراء من خلال مزيج من WebSockets و threads. يتعامل مع كل من أعمال أنابيب الإطار وعمل القناة المحددة من قبل المستخدم داخليًا من خلال الاستفادة من دعم روبي الأصلي. هذا يعني أنه يمكنك استخدام جميع نماذج Rails المنتظمة الخاصة بك دون أي مشكلة، طالما أنك لم ترتكب أي خطأ في سلامة الصفحات.
يعمل [[Rails/action cable|Action Cable]] من خلال مزيج من مقابس الويب WebSockets و<nowiki/>[[Ruby/Thread|الخيوط]]. تعالج كل من أعمال أنابيب الإطار وعمل القناة المحددة من قبل المستخدم داخليًا من خلال الاستفادة من دعم روبي الأصلي. هذا يعني أنه يمكنك استخدام جميع نماذج ريلز العادية الخاصة بك دون أي مشكلة، طالما أنك لم ترتكب أي خطأ في سلامة الصفحات.


يقوم خادم كبل الاجراء بتنفيذ اختطاف حامل المقبس لواجهة برمجة تطبيقات، مما يسمح باستخدام نمط مؤشرات ترابط متعددة لإدارة الاتصالات داخليًا، بغض النظر عن ما إذا كان خادم التطبيق متعدد الخيوط أم لا.
ينفذ خادم [[Rails/action cable|Action Cable]] الواجهة البرمجية لاختطاف المقبس Rack (أي Rack socket hijacking API)، مما يسمح باستخدام أنماط ذات خيوط متعددة (multithreaded pattern) لإدارة الاتصالات داخليًا، بغض النظر عن ما إذا كان خادم التطبيق متعدد الخيوط أم لا.


وفقًا لذلك، تعمل شركة Action Cable مع خوادم شائعة مثل Unicorn و Puma و Passenger.
وفقًا لذلك، يعمل [[Rails/action cable|Action Cable]] مع خوادم شائعة مثل Unicorn و Puma و Passenger.


== مصادر ==
== مصادر ==
* [https://guides.rubyonrails.org/action_cable_overview.html صفحة Action Cable Overview في توثيق Ruby On Rails الرسمي.]
* [https://guides.rubyonrails.org/action_cable_overview.html صفحة Action Cable Overview في توثيق Ruby On ريلز الرسمي.]
[[تصنيف:Rails]]
[[تصنيف:Rails]]
[[تصنيف:Rails Digging Deeper]]
[[تصنيف:Rails Digging Deeper]]

المراجعة الحالية بتاريخ 08:52، 25 مارس 2019

ستتعرف في هذا الدليل على كيفية عمل Action Cable وكيفية استخدام WebSockets لدمج ميزات الوقت الفعلي في تطبيق ريلز.

بعد قراءة هذا الدليل، ستتلعم:

  • ماهية Action Cable وكيفية دمج الواجهة الخلفية والأمامية الخاصة به.
  • كيفية إعداد Action Cable.
  • كيفية إعداد القنوات.
  • النشر وإعداد المعمارية لتشغيل Action Cable.

المقدمة

يدمج Action Cable مقابس الويب WebSockets مع باقي تطبيق ريلز بسلاسة. يسمح ذلك بكتابة الميزات في الوقت الحقيقي في روبي بنفس الأسلوب والشكل كبقية تطبيقات ريلز، مع الاستمرار في الأداء وقابلية التطوير. إنه عرض كامل يوفر كلًا من إطار عمل JavaScript من جانب العميل وإطار عمل روبي من جانب الخادم. لديك حق الوصول إلى نموذج المجال الكامل الخاص بك المكتوب مع Active Record أو تقنية ORM الخاص بك في الاختيار.

ماهو Pub/Sub

يشير Pub / Sub أو Publish-Subscribe إلى نموذج قائمة انتظار الرسائل حيث يرسل مرسلو المعلومات (الناشرون) البيانات إلى صنف مجرد من المستلمين (المشتركين) دون تحديد مستلمين فرديين. يستخدم Action Cable هذا الأسلوب للتواصل بين الخادم والعديد من العملاء.

مكونات من جانب الخادم

الاتصالات

تشكل الاتصالات أساس العلاقة بين العميل والخادم. لكل مقبس ويب WebSocket مقبول من قبل الخادم، تُنشأ نسخة من كائن الاتصال. يصبح هذا الكائن الأب لكل اشتراكات القناة (channel subscriptions) التي أُنشأت من هناك. لا يتعامل الاتصال نفسه مع أي شيفرة تطبيق معين يتجاوز الاستيثاق والترخيص. يسمى عميل اتصال مقبس الويب WebSocket مستهلك الاتصال (connection consumer). سينشئ مستخدم فردي زوجًا واحدًا لمستهلك الاتصال في كل لسان أو نافذة أو جهاز يفتحه المتصفح.

الاتصالات هي نُسخ من ApplicationCable::Connection. في هذا الصنف، أنت تسمح بالاتصال الوارد، وتشرع في تأسيسه إذا كان من الممكن التعرف على المستخدم.

إعدادات الاتصال

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user
 
    def connect
      self.current_user = find_verified_user
    end
 
    private
      def find_verified_user
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

يكون ident_by هنا هو معرف اتصال يمكن استخدامه للعثور على اتصال محدد في وقت لاحق. لاحظ أن أي شيء مُيز كمعرف سيُنشِئ تلقائيًا مفوَّضًا (delegate) بنفس الاسم على أي نُسخة قناة أُنشأت خارج الاتصال.

يعتمد هذا المثال على حقيقة أنك ستتولى بالفعل الاستيثاق من المستخدم في مكان آخر في التطبيق الخاص بك، ويُعيِّن ذلك الاستيثاق الناجح ملف تعريف ارتباط موقّع مع معرف المستخدم.

بعد ذلك، يُرسَل ملف تعريف الارتباط تلقائيًا إلى نسخة الاتصال عند محاولة إجراء اتصال جديد، ويمكنك استخدام ذلك من تعيين current_user. من خلال تحديد الاتصال بواسطة هذا المستخدم الحالي نفسه، فإنك أيضًا تتأكد من أنه يمكنك في وقت لاحق استرداد جميع الاتصالات المفتوحة من قبل مستخدم معين (ومن المحتمل فصلها كلها إذا حذف المستخدم أو كان غير مصرح به).

القنوات

تغلف القناة وحدة منطقية من العمل، مشابهة لما تفعله وحدة التحكم في إعداد MVC عادي. بشكل افتراضي، ينشئ ريلز الصنف الأب ApplicationCable::Channel لتغليف شيفرة مشتركة بين قنواتك.

إعداد القناة الأب

# app/channels/application_cable/channel.rb
module ApplicationCable
  class Channel < ActionCable::Channel::Base
  end
end

ثم يمكنك إنشاء أصناف قناتك (channel classes) الخاصة. على سبيل المثال، قد يكون لديك ChatChannel و AppearanceChannel:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
 
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end

يمكن بعد ذلك أن يشترك المستهلك في أي من هاتين القناتين أو كليهما.

الاشتراكات

يشترك المستهلكون في القنوات، كمشتركين (subscribers). اتصالهم يسمى «الاشتراك» (subscription). ثم توجه الرسائل التي أُنتجت إلى اشتراكات القنوات هذه استنادًا إلى معرّف أُرسل بواسطة مستهلك الوصل (cable consumer).

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  # يُستدعى عندما يصبح المستهلك مشتركًا بهذه القناة
  def subscribed
  end
end

مكونات من جانب العميل

الاتصالات

يحتاج المستهلكون إلى نسخة من الاتصال على جانبهم. ينشأ هذا باستخدام شيفرة JavaScript التالية، والتي تُولَّد افتراضيًا بواسطة ريلز:

مستهلك الاتصال

// app/assets/javascripts/cable.js
//= require action_cable
//= require_self
//= require_tree ./channels
 
(function() {
  this.App || (this.App = {});
 
  App.cable = ActionCable.createConsumer();
}).call(this);

هذا سوف يُجهِّز مستهلكًا سيتصل بـ ‎/cable على الخادم الخاص بك بشكل افتراضي. لن يُنشَأ الاتصال حتى تحدد أيضًا اشتراكًا واحدًا على الأقل ترغب في الحصول عليه.

المشترك

يصبح المستهلك مشتركًا عن طريق إنشاء اشتراك لقناة معينة:

# app/assets/javascripts/cable/subscriptions/chat.coffee
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
 
# app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create { channel: "AppearanceChannel" }

بينما يُنشِئ هذا الاشتراك، ستوصف الوظيفة المطلوبة للرد على البيانات المستلمة في وقت لاحق.

يمكن للمستهلك التصرف كمشترك في قناة معينة في أي عدد من المرات. على سبيل المثال، يمكن للمستهلك الاشتراك في غرف دردشة متعددة في نفس الوقت:

App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }

تفاعلات الخادم والعميل

المجاري

توفر المجاري آلية توجه القنوات عبرها المحتوى المنشور (البث) لجميع المشتركين في ذلك المجرى.

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end
end

إذا كان لديك مجرًى مرتبطًا بنموذج، فيمكن توليد البث المستخدم من النموذج والقناة. سوف يشترك المثال التالي في بث مثل comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE:

class CommentsChannel < ApplicationCable::Channel
  def subscribed
    post = Post.find(params[:id])
    stream_for post
  end
end

يمكنك بعد ذلك البث إلى هذه القناة كما يلي:

CommentsChannel.broadcast_to(@post, @comment)

البث (Broadcasting)

البث هو رابط pub / sub حيث يوجه أي شيء أُرسل من قبل ناشر مباشرة إلى المشتركين في القناة الذين يقومون ببث تلك الإذاعة المسماة. يمكن لكل قناة أن تبث بثًا أو أكثر من بث.

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

تُستدعَى عمليات البث في أي مكان آخر في تطبيق ريلز:

WebNotificationsChannel.broadcast_to(
  current_user,
  title: 'New things!',
  body: 'All the news fit to print'
)

يضع الاستدعاء WebNotificationsChannel.broadcast_to رسالةً في قائمة انتظار الاشتراك في محول الاشتراك الحالي (بشكل افتراضي redis للإنتاج و التزامن لبيئات التطوير والاختبار) تحت اسم بث منفصل لكل مستخدم. بالنسبة لمستخدم ذي المعرف 1، سيكون اسم البث web_notifications:1.

توجه القناة لبث كل شيء يصل إلى web_notifications:1 مباشرة إلى العميل عن طريق استدعاء رد النداء received.

الاشتراكات

عندما يشترك المستهلك في قناة، يكون بمثابة مشترك (subscriber). هذا الاتصال يسمى الاشتراك (subscription). ثم توجه الرسائل الواردة إلى اشتراكات القنوات هذه استنادًا إلى مُعرّف أُرسل بواسطة مستهلك الربط (cable consumer).

# app/assets/javascripts/cable/subscriptions/chat.coffee
# افترض أن طلبت مسبقًا الإذن لإرسال تنبيهات ويب
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
  received: (data) ->
    @appendLine(data)
 
  appendLine: (data) ->
    html = @createLine(data)
    $("[data-chat-room='Best Room']").append(html)
 
  createLine: (data) ->
    """
    <article class="chat-line">
      <span class="speaker">#{data["sent_by"]}</span>
      <span class="body">#{data["body"]}</span>
    </article>
    """

تمرير المعاملات إلى القنوات

يمكنك تمرير المعاملات من جانب العميل إلى جانب الخادم عند إنشاء اشتراك. فمثلًا:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end
end

يصبح الكائن الذي مُرر ليكون الوسيط الأول إلى subscriptions.create المعاملات params (التي هي جدول Hash) في قناة الربط (cable channel). الكلمة المفتاحية channel مطلوبة:

# app/assets/javascripts/cable/subscriptions/chat.coffee
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
  received: (data) ->
    @appendLine(data)
 
  appendLine: (data) ->
    html = @createLine(data)
    $("[data-chat-room='Best Room']").append(html)
 
  createLine: (data) ->
    """
    <article class="chat-line">
      <span class="speaker">#{data["sent_by"]}</span>
      <span class="body">#{data["body"]}</span>
    </article>
    """
# يُستدعَى هذا في مكان ما في تطبيقك
# NewCommentJob ربما من
ActionCable.server.broadcast(
  "chat_#{room}",
  sent_by: 'Paul',
  body: 'This is a cool chat app.'
)

إعادة بث رسالة

حالة الاستخدام الشائعة هي إعادة رب (rebroadcast) رسالة أُرسلت بواسطة عميل واحد إلى أي عملاء آخرين متصلين.

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end
 
  def receive(data)
    ActionCable.server.broadcast("chat_#{params[:room]}", data)
  end
end
# app/assets/javascripts/cable/subscriptions/chat.coffee
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
  received: (data) ->
    # data => { sent_by: "Paul", body: "This is a cool chat app." }
 
App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })

سيُستلَم إعادة البث من قبل جميع العملاء المتصلين، بما في ذلك العميل الذي أرسل الرسالة. لاحظ أن params هي نفسها كما كانت عند الاشتراك في القناة.

أمثلة كاملة

خطوات الإعداد التالية متماثلة في كلا المثالين:

  1. إعداد الإتصال الخاص بك.
  2. إعداد قناتك الأب.
  3. الاتصال بالمستهلك.

مثال 1: ظهور المستخدم

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

إنشاء قناة جانب الخادم لعرض آخر ظهور:

# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
  def subscribed
    current_user.appear
  end
 
  def unsubscribed
    current_user.disappear
  end
 
  def appear(data)
    current_user.appear(on: data['appearing_on'])
  end
 
  def away
    current_user.away
  end
end

عند بدء الاشتراك، يُطلَق استدعاء رد النداء subscribed، وننتهز هذه الفرصة لنقول "لقد ظهر المستخدم الحالي بالفعل". يمكن دعم واجهة برمجة التطبيقات التي تظهر / تختفي بواسطة Redis أو قاعدة بيانات أو أي شيء آخر.

إنشاء اشتراك قناة تظهر العميل:

# app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create "AppearanceChannel",
  # يُستدعَى عندما يصبح الاشتراك جاهزًا للاستعمال في الخادم
  connected: ->
    @install()
    @appear()
 
  # WebSocket يُستدعَى عندما يُغلَق اتصال مقبس الويب
  disconnected: ->
    @uninstall()
 
  # يُستدعَى عندما يُرفَض الاشتراك من طرف الخادم
  rejected: ->
    @uninstall()
 
  appear: ->
    # على الخادم `AppearanceChannel#appear(data)` يستدعي
    @perform("appear", appearing_on: $("main").data("appearing-on"))
 
  away: ->
    # على الخادم `AppearanceChannel#away` يستدعي
    @perform("away")
 
 
  buttonSelector = "[data-behavior~=appear_away]"
 
  install: ->
    $(document).on "turbolinks:load.appearance", =>
      @appear()
 
    $(document).on "click.appearance", buttonSelector, =>
      @away()
      false
 
    $(buttonSelector).show()
 
  uninstall: ->
    $(document).off(".appearance")
    $(buttonSelector).hide()

تفاعل الخادم - العميل

1 - يتصل العميل بالخادم عبر:

App.cable =
ActionCable.createConsumer("ws://cable.example.com"). (cable.js)

يحدد الخادم هذا الاتصال بواسطة current_user.

2 - يشترك العميل في قناة آخر ظهور عبر:

App.cable.subscriptions.create(channel: "AppearanceChannel"). (appearance.coffee)

3 - يتعرف الخادم على الاشتراك الجديد المُنشَأ للتو بقناة آخر ظهور ويشغل رد النداء subscribed الخاص به، مع استدعاء التابع appear على current_user. (الملف appearance_channel.rb)

4 - يدرك العميل أنه أُنشأ الاشتراك ويستدعي appearance.coffe) connected) والذي يستدعي بدوره ‎@install و appear@. يستدعي appear@ بعدئذٍ (AppearanceChannel.appear(data على الخادم، ويزود جدول Hash من البيانات من { appearing_on: $("main").data("appearing-on")‎ }. هذا ممكن لأن نسخة القناة من جانب الخادم تكشف تلقائيًا كل التوابع العامة المصرح عنها في الصنف (باستثناء ردود النداء)، بحيث يمكن الوصول إليها كإستدعاء إجراء بعيد عن طريق التابع perform الذي يخص الاشتراك.

5 - يتلقى الخادم طلب الإجراء appear في قناة آخر الظهور الخاصة بالاتصال المحدد بواسطة current_user (في الملف appearance_channel.rb). يسترد الخادم البيانات باستخدام المفتاح :appearing_on من جدول Hash من البيانات ويعينها كقيمة :on على المفتاح الذي يمرر إلى current_user.appear.

مثال 2: تلقي إشعارات ويب جديدة

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

هذا المثال عبارة عن قناة إخبارية على الويب تسمح لك بتشغيل إشعارات الويب من جانب العميل عند البث إلى مجموعات البث الصحيحة:

إنشاء قناة إشعارات الويب من جانب الخادم:

# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

إنشاء اشتراك قناة إشعارات الويب من جانب العميل:

# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
# يفترض جانب العميل أنك طلبت مسبقًا إذنًا لإرسال تنبيهات ويب
App.cable.subscriptions.create "WebNotificationsChannel",
  received: (data) ->
    new Notification data["title"], body: data["body"]

بث المحتوى إلى نسخة قناة تنبيه عبر الويب (web notification channel) من مكان آخر في التطبيق الخاص بك:

#  NewCommentJob يُستدعَى هذا من مكان من من تطبيقك وربما من
WebNotificationsChannel.broadcast_to(
  current_user,
  title: 'New things!',
  body: 'All the news fit to print'
)

يضع الاستدعاء WebNotificationsChannel.broadcast_to رسالةً في طابور انتظار النشر والاشتراك (pubsub) لمحول الاشتراك الحالي تحت اسم بث منفصل لكل مستخدم. بالنسبة لمستخدم ذي المعرف 1، سيكون اسم البث web_notifications:1.

توجه القناة لبث كل شيء يصل إلى web_notifications:1 مباشرةً إلى العميل عن طريق استدعاء رد النداء received. البيانات التي تُمرر كوسيط هي جدول Hash المرسل كمعامل ثاني لاستدعاء البث من جانب الخادم، إذ يشفر بصيغة JSON أثناء رحلته عبر السلك ويفك تشفيره من أجل وسيط البيانات عند الوصول كـ received.

المزيد من الأمثلة الكاملة

راجع المستودع rails/actioncable-examples لمثال كامل عن كيفية إعداد Action Cable في تطبيق ريلز وإضافة القنوات.

الضبط

يتطلب "Action Cable" إعداد ضبطين هما: محول الاشتراك (subscription adapter) والأصول المسموح طلبها.

محول الاشتراك

بشكل افتراضي، يبحث Action Cable عن ملف ضبط في config/cable.yml. يجب أن يحدد الملف محولًا لكل بيئة ريلز. انظر قسم التبعيات للحصول على معلومات إضافية حول المحولات.

development:
  adapter: async
 
test:
  adapter: async
 
production:
  adapter: redis
  url: redis://10.10.3.153:6381
  channel_prefix: appname_production

ضبط المحول

فيما يلي قائمة بمحولات الاشتراك المتاحة للمستخدمين النهائيين.

  • المحول Async: المحول المتزامن (async adapter) هو من أجل التطوير / الاختبار ويجب عدم استخدامه في الإنتاج.
  • المحول Redis: يتطلب المحول Redis من المستخدمين توفير عنوان URL يشير إلى خادم Redis. بالإضافة إلى ذلك، قد يُوفَّر channel_prefix لتجنب تضارب اسم القناة عند استخدام خادم Redis نفسه لتطبيقات متعددة. راجع توثيق Redis للنشر/الاشتراك لمزيد من التفاصيل.
  • المحول PostgreSQL: يستخدم المحول PostgreSQL مُجمَّع اتصال Active Record، وبالتالي ضبط قاعدة البيانات config/database.yml للتطبيق، للاتصال به. هذا قد يتغير في المستقبل. #27214.

الأصول المسموح لها بالطلب

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

config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}]

لتعطيل والسماح بالطلبات من أي أصل:

config.action_cable.disable_request_forgery_protection = true

بشكل افتراضي، يسمح Action Cable بجميع الطلبات من localhost:3000 عند التشغيل في بيئة التطوير.

ضبط المستهلك

لتهيئة عنوان URL، أضف استدعاءً إلى action_cable_meta_tag في شيفرة HTML الخاصة بك في الوسم <head>. يستخدم هذا عنوان URL أو مسار يُعين عادةً عبر config.action_cable.url في ملفات ضبط البيئة.

ضبط أمور أخرى

الخيار المشترك الآخر المراد ضبطه هو وسوم التسجيل (log tags) المطبقة على مسجل الاتصال. في ما يلي مثال يستخدم معرف حساب المستخدم إذا كان متاحًا، أو "بدون حساب" آخر أثناء وضع الوسوم:

config.action_cable.log_tags = [
  -> request { request.env['user_account_id'] || "no-account" },
  :action_cable,
  -> request { request.uuid }
]

للحصول على قائمة كاملة بجميع خيارات الضبط، راجع الصنف ActionCable::Server::Configuration.

لاحظ أيضًا أن الخادم الخاص بك يجب أن يوفر على الأقل نفس عدد اتصالات قاعدة البيانات كما تعمل لديك. يُعين حجم المجمع الافتراضي للعملية العاملة (default worker pool size) إلى 4، وهذا يعني أن عليك أن تجعل ذلك متوافرًا على الأقل. يمكنك تغيير ذلك في config/database.yml من خلال الخاصية pool.

تشغيل خوادم ربط مستقلة

في التطبيق

يمكن تشغيل Action Cable بشكل منفصل ومستقل مع تطبيق ريلز الخاص بك. على سبيل المثال، للاستماع لطلبات مقبس ويب WebSocket على ‎/websocket، حدد ذلك المسار في الضبط config.action_cable.mount_path:

# config/application.rb
class Application < Rails::Application
  config.action_cable.mount_path = '/websocket'
end

يمكنك استخدام ()App.cable = ActionCable.createConsumer للاتصال بخادم الربط (cable server) إذا استُدعِي action_cable_meta_tag في التخطيط.

يُحدَّد مسار مخصص كوسيط أول إلى createConsumer مثل:

App.cable =
ActionCable.createConsumer("/websocket")

لكل نسخة تنشئها من خادمك ولكل عملية عاملة (worker) يولدها خادمك، سيكون لديك أيضًا نسخة جديدة من Action Cable، ولكن استخدام Redis يبقي الرسائل متزامنة عبر الاتصالات.

الاستقلالية

يمكن فصل خادم الربط (cable servers) عن خادم التطبيق العادي. لا يزال تطبيق Rack، ولكنه تطبيق Rack الخاص به. الإعداد الأساسي الموصى به هو كما يلي:

# cable/config.ru
require_relative '../config/environment'
Rails.application.eager_load!
 
run ActionCable.server

ثم يمكن بدء الخادم باستخدام binstub في bin/cable:

#!/bin/bash
bundle exec puma -p 28080 cable/config.ru

بتنفيذ ذلك، سيبدأ خادم الربط على المنفذ 28080.

ملاحظات

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

التبعيات

يوفر "Action Cable" واجهة محول اشتراك لمعالجة عمليات النشر والاشتراك (pubsub) الداخلية الخاصة به. بشكل افتراضي، تُضمَّن المحولات غير متزامنة والسطرية و PostgreSQL و Redis. المحول الافتراضي في تطبيق ريلز جديد هو محول غير متزامن (async).

تم بناء أشياء من طرف روبي فوق websocket-driver، و nio4r، و conjurrent-ruby.

النشر

يعمل Action Cable من خلال مزيج من مقابس الويب WebSockets والخيوط. تعالج كل من أعمال أنابيب الإطار وعمل القناة المحددة من قبل المستخدم داخليًا من خلال الاستفادة من دعم روبي الأصلي. هذا يعني أنه يمكنك استخدام جميع نماذج ريلز العادية الخاصة بك دون أي مشكلة، طالما أنك لم ترتكب أي خطأ في سلامة الصفحات.

ينفذ خادم Action Cable الواجهة البرمجية لاختطاف المقبس Rack (أي Rack socket hijacking API)، مما يسمح باستخدام أنماط ذات خيوط متعددة (multithreaded pattern) لإدارة الاتصالات داخليًا، بغض النظر عن ما إذا كان خادم التطبيق متعدد الخيوط أم لا.

وفقًا لذلك، يعمل Action Cable مع خوادم شائعة مثل Unicorn و Puma و Passenger.

مصادر