Rails'de ActionCable nedir ve nasıl kullanılır?
Websocket nedir?
websocket TCP üzerine kurulu HTTP gibi bir protokoldür ve HTML5 ile birlikte gelerek HTTP protokolünün gerçek zamanlı uygulamalardaki eksiklerini kapatmıştır. HTTP request/response mantığı üzerinde kurulu protokoldür yani client tarafından bir istek yapılır ve server tarafından bu isteğe cevap beklenir, durum böyle olunca istek yapmayan clientlar bu requesti alamamaktaydı bu durumda chat uygulamaları gibi gerçek zamanlı uygulamalarda büyük bir problem oluşturmaktaydı. Websocketten önce bu problem belirli aralıklarda ajax ile client tarafına sunucu tarafından istek yaparak değişiklikleri göndererek kısmen çözülebiliyordu fakat bu işlem gereksiz veri transferlerine ve yavaşlamaya neden olmaktaydı, websocket ile birlikte bu problemin önüne geçildi yani websocket HTTP’nin aksine kanallar aracılığı ile server tarafında herhangi bir değişiklik olduğunda sadece istek yapan client’a değil kanala bağlı tüm clientlara ilgili değişikliği göndermektedir böylece aradaki bağlantı hiç kopartılmadan gerçek zamanlı olarak sadece yapılan değişiklikler gönderilmiş olur.
ActionCable nedir?
Action Cable Rails-5 ile gelen ve websocket kullanabilmemiz için geliştirilmiş olan bir kütüphanedir, RailsConf 2015'te duyurulmuştur. Action Cable bize server tarafta ruby ile client tarafta ise javascript ile full-stack bir yapı sunar. Active Record veya ORM modeli ile yazdığımız modellerimize erişebiliriz. Action Cable bir mesajlaşma pattern’ı olan Publisher/Subscriber yaklaşımı kullanarak server ve clientlar arasında bağlantı sağlar.
Örnek Uygulama:
Teknik detayları aşağıda bulunan DHH’nin örneği üzerinden açıklayacağım.
Öncelikle sırasıyla aşağıdaki işlemleri yapıp uygulamamızı hazırlıyoruz.
Uygulamayı oluşturma:
ruby
rails new chat --skip-spring
Room adlı controllerımızı oluşturuyoruz:
ruby
rails g controller rooms show
Database’imizi ve Message modelimizi oluşturuyoruz:
rails db:create
rails g model message content:text
rails db:migrate
Message tablomuza yeni bir kayıt oluşturuyoruz:
ruby
Message.create(content: 'hello world')
Son olarak da root dosyamızı düzenliyoruz:
ruby
root to: 'rooms#show'
Controllerımızı ve view sayfalarımızı aşağıdaki gibi güncelliyoruz: “`ruby
app/controllers/rooms_controller.rb
class RoomsController < ApplicationController def show @messages = Message.all end end ”`
# app/views/rooms/show.html.erb
<h1>Chat room</h1>
<div id="messages">
<%= render @messages %>
</div>
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="room_speaker">
</form>
# app/view/messages/_message.html.erb
<div class=“message”>
<p><%= message.content %></p>
</div>
ve ‘rails s’ diyip uygulamanın http://localhost:3000/’dan kullanıcı tarafındaki görüntüsünü görebiliriz.
ActionCable tarafı için şu işlemleri yapıyoruz:
config/routes.rb dosyamıza aşağıdaki satırı koyarak ActionCable’ı monte ediyoruz.
ruby
mount ActionCable.server => '/cable'
Rails projesinin oluşturulması ile birlikte oluşan javascript/cable.js dosyamız şu şekildedir:
//= require action_cable
//= require_self
//= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);
Route dosyamıza yukarıdaki satırı ekledikten sonra browser console'umuzdan “App.cable” ı yazdığımızda aşağıdaki sonucu alabilmeniz gerekiyor.
Consumer {url: "ws://localhost:3000/cable", subscriptions: Subscriptions, connection: Connection}
Artık örneğimiz için kanal oluşturma zamanı geldi.
rails g channel room speak
ile birlikte iki adet yeni dosyamız oluşacak: javascript tarafı için app/assets/javascripts/channels/room.coffee dosyası ve ruby tarafı için app/channels/room_channel.rb dosyası oluşacak.
Dosyalarımız başlangıçta şu şekildedir:
#app/channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
def subscribed
# stream_from "some_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak
end
end
‘subscriber’ ve ‘unsubscriber’ ActionCable’ın default callback’leridir.
#app/assets/javascripts/channels/room.coffee
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
speak: ->
@perform 'speak'
connected methodu kanal ile ilk bağlantı oluşturulduğunda çalışan methotdur, received methodu ise veri geldiğinde çalışan fonksiyondur. speak methodu ise clienttan gelen veriyi room_channel.rb’nin içindeki speak fonksiyonuna gönderdiğimiz methoddur.
Daha sonra veri iletimini gerçekleştirmek için dosyalarımızı şu şekilde göncelliyoruz:
#app/assets/javascripts/channels/room.coffee
received: (data) ->
$('#messages').append data['message']
speak: (message) ->
@perform 'speak', message: message
$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
if event.keyCode is 13 # return/enter = send
App.room.speak event.target.value
event.target.value = ''
event.preventDefault()
#app/channels/room_channel.rb
def speak(data)
Message.create! content: data['message']
end
modelimizin içini bu şekilde güncelliyoruz.
#app/models/message.rb
class Message < ApplicationRecord
after_create_commit { MessageBroadcastJob.perform_later self }
end
daha sonra Job’ımızı oluşturuyoruz:
rails g job MessageBroadcast
ve içini bu şekilde güncelliyoruz:
class MessageBroadcastJob < ApplicationJob
queue_as :default
def perform(message)
ActionCable.server.broadcast 'room_channel', message: render_message(message)
end
private
def render_message(message)
ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
end
end
İşlemlerimizi tamamladıktan sonra burada işleyen işlemi anlatmanın vakti geldi. Bu uygulama ActionCable’ın mantığını anlatmak adına tek odalı bir chat uygulaması için yapılmış bir uygulamadır istenilirse kod geliştirilerek çok odalı bir chat uygulaması yapılabilir. Uygulamada kullanıcı ‘Say Something:’ kısmından girdiği yazıyı entera basarak gönderir ve server tarafına gelen mesaj #app/assets/javascripts/channels/room.coffee dosyasının en altında bulunan javascript fonksiyonu ile yakalanır ve yine aynı klasörde bulunan speak methoduna gönderilir, bu dosyadaki speak methodu #app/channels/room_channel.rb dosyasındaki speak methoduna gelen data message olarak gönderilir. #app/channels/room_channel.rb dosyasında bulunan speak fonksiyonu ile yakalanan data veri tabanındaki Message tablosuna
ruby
Message.create! content: data['message']
ile kayıt edilir ve Message modelinde
ruby
after_create_commit { MessageBroadcastJob.perform_later self }
ile job tetiklenir. Job çalıştığı anda render_message methodunu ‘room_channel’ kanalına
ActionCable.server.broadcast 'room_channel', message: render_message(message)
ile yayınlar. Yayınlanan data işlem yapılmak üzere #app/assets/javascripts/channels/room.coffee dosyasının içindeki received methoduna gider. Bu fonksiyon ile gelen data kullanıcı tarafına gönderilir. Görüldüğü gibi Rails ile websocket işlemlerini yapmak ActionCable ile oldukça kolay. Son olarak bir dip not:
Eğer uygulamanızda Redis kullanıyorsanız cable.yml dosyamızdaki development ayarımızı şu şekilde güncellememiz gerekiyor ve productionda ise Redis To Go URL'imizi vermeniz gerekmektedir:
development:
adapter: redis
url: redis://localhost:6379/1