Intro

Rails ActionCable allows you to open a socket connection from inside rails to the client and then push info from the server to the client. HTTP was designed for the client to ask for info from the server in the form of a request and then the server would send info to the client, whereas web sockets enables the server or the client to be the initiator and send info without either knowing when it’s coming. This tutorial will cover a basic stock ticker example utilizing the Rails 5 feature. The example will display stocks with their price and the price of the stocks will update on the clients browser whenever it changes.

Rails ActionCable Tutorial

Setting up Rails 5

To get started you need to be using rails5-beta3
This process is largely up to you but here is one way of doing this with RVM:

$ rvm gemset create rails5-beta3
ruby-2.2.2 - #gemset created /home/agent-006/.rvm/gems/ruby-2.2.2@rails5-beta3
ruby-2.2.2 - #generating rails5-beta3 wrappers........
$ rvm gemset use rails5-beta3
Using ruby-2.2.2 with gemset rails5-beta3
$ gem install rails -v 5.0.0.beta3 --no-rdoc --no-ri
Fetching: rack-2.0.0.alpha.gem (100%)
Successfully installed rack-2.0.0.alpha
Fetching: concurrent-ruby-1.0.1.gem (100%)
Successfully installed concurrent-ruby-1.0.1
Fetching: sprockets-3.5.2.gem (100%)
Successfully installed sprockets-3.5.2
Fetching: thread_safe-0.3.5.gem (100%)
Successfully installed thread_safe-0.3.5
Fetching: tzinfo-1.2.2.gem (100%)
Successfully installed tzinfo-1.2.2
Fetching: i18n-0.7.0.gem (100%)
Successfully installed i18n-0.7.0
Fetching: activesupport-5.0.0.beta3.gem (100%)
Successfully installed activesupport-5.0.0.beta3
Fetching: mini_portile2-2.0.0.gem (100%)
Successfully installed mini_portile2-2.0.0
Fetching: nokogiri-1.6.7.2.gem (100%)
Building native extensions.  This could take a while...
Successfully installed nokogiri-1.6.7.2
Fetching: loofah-2.0.3.gem (100%)
Successfully installed loofah-2.0.3
Fetching: rails-html-sanitizer-1.0.3.gem (100%)
Successfully installed rails-html-sanitizer-1.0.3
Fetching: rails-deprecated_sanitizer-1.0.3.gem (100%)
Successfully installed rails-deprecated_sanitizer-1.0.3
Fetching: rails-dom-testing-1.0.7.gem (100%)
Successfully installed rails-dom-testing-1.0.7
Fetching: rack-test-0.6.3.gem (100%)
Successfully installed rack-test-0.6.3
Fetching: erubis-2.7.0.gem (100%)
Successfully installed erubis-2.7.0
Fetching: builder-3.2.2.gem (100%)
Successfully installed builder-3.2.2
Fetching: actionview-5.0.0.beta3.gem (100%)
Successfully installed actionview-5.0.0.beta3
Fetching: actionpack-5.0.0.beta3.gem (100%)
Successfully installed actionpack-5.0.0.beta3
Fetching: sprockets-rails-3.0.3.gem (100%)
Successfully installed sprockets-rails-3.0.3
Fetching: thor-0.19.1.gem (100%)
Successfully installed thor-0.19.1
Fetching: method_source-0.8.2.gem (100%)
Successfully installed method_source-0.8.2
Fetching: railties-5.0.0.beta3.gem (100%)
Successfully installed railties-5.0.0.beta3
Fetching: bundler-1.11.2.gem (100%)
Successfully installed bundler-1.11.2
Fetching: arel-7.0.0.gem (100%)
Successfully installed arel-7.0.0
Fetching: activemodel-5.0.0.beta3.gem (100%)
Successfully installed activemodel-5.0.0.beta3
Fetching: activerecord-5.0.0.beta3.gem (100%)
Successfully installed activerecord-5.0.0.beta3
Fetching: globalid-0.3.6.gem (100%)
Successfully installed globalid-0.3.6
Fetching: activejob-5.0.0.beta3.gem (100%)
Successfully installed activejob-5.0.0.beta3
Fetching: mime-types-2.99.1.gem (100%)
Successfully installed mime-types-2.99.1
Fetching: mail-2.6.3.gem (100%)
Successfully installed mail-2.6.3
Fetching: actionmailer-5.0.0.beta3.gem (100%)
Successfully installed actionmailer-5.0.0.beta3
Fetching: websocket-extensions-0.1.2.gem (100%)
Successfully installed websocket-extensions-0.1.2
Fetching: websocket-driver-0.6.3.gem (100%)
Building native extensions.  This could take a while...
Successfully installed websocket-driver-0.6.3
Fetching: nio4r-1.2.1.gem (100%)
Building native extensions.  This could take a while...
Successfully installed nio4r-1.2.1
Fetching: actioncable-5.0.0.beta3.gem (100%)
Successfully installed actioncable-5.0.0.beta3
Fetching: rails-5.0.0.beta3.gem (100%)
Successfully installed rails-5.0.0.beta3
36 gems installed

Note, when the terminal session is closed RVM will revert to the default gemset which will have whatever Rails version you had before. Use $ rvm gemset use rails5-beta3 to use Rails 5 again.

To make sure Rails 5 is properly configured run the command: $ rails -v It should output: Rails 5.0.0.beta3

The First Step

Now the environment is setup and ready to create a Rails 5 application.

$ rails new stock-ticker
      create  
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
      create  Gemfile
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/javascripts/application.js
      create  app/assets/javascripts/cable.coffee
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/jobs/application_job.rb
      create  app/mailers/application_mailer.rb
      create  app/models/application_record.rb
      create  app/views/layouts/application.html.erb
      create  app/views/layouts/mailer.html.erb
      create  app/views/layouts/mailer.text.erb
      create  app/assets/images/.keep
      create  app/assets/javascripts/channels
      create  app/assets/javascripts/channels/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  bin/update
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/secrets.yml
      create  config/cable.yml
      create  config/puma.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/active_record_belongs_to_required_by_default.rb
      create  config/initializers/application_controller_renderer.rb
      create  config/initializers/assets.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/callback_terminator.rb
      create  config/initializers/cookies_serializer.rb
      create  config/initializers/cors.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/per_form_csrf_tokens.rb
      create  config/initializers/request_forgery_protection.rb
      create  config/initializers/session_store.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/apple-touch-icon-precomposed.png
      create  public/apple-touch-icon.png
      create  public/favicon.ico
      create  public/robots.txt
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/test_helper.rb
      create  tmp
      create  tmp/.keep
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
      remove  config/initializers/cors.rb
         run  bundle install
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Resolving dependencies.......
Installing rake 10.5.0
Using concurrent-ruby 1.0.1
Using i18n 0.7.0
Installing minitest 5.8.4
Using thread_safe 0.3.5
Using builder 3.2.2
Using erubis 2.7.0
Using mini_portile2 2.0.0
Installing json 1.8.3 with native extensions
Using nio4r 1.2.1
Using websocket-extensions 0.1.2
Using mime-types 2.99.1
Using arel 7.0.0
Using bundler 1.11.2
Installing byebug 8.2.2 with native extensions
Installing coffee-script-source 1.10.0
Installing execjs 2.6.0
Using method_source 0.8.2
Using thor 0.19.1
Installing debug_inspector 0.0.2 with native extensions
Installing ffi 1.9.10 with native extensions
Installing multi_json 1.11.2
Installing rb-fsevent 0.9.7
Installing puma 3.0.2 with native extensions
Installing sass 3.4.21
Installing tilt 2.0.2
Installing spring 1.6.4
Installing sqlite3 1.3.11 with native extensions
Installing turbolinks-source 5.0.0.beta2
Using tzinfo 1.2.2
Using nokogiri 1.6.7.2
Using rack 2.0.0.alpha
Using websocket-driver 0.6.3
Using mail 2.6.3
Installing coffee-script 2.4.1
Installing uglifier 2.7.2
Installing rb-inotify 0.9.7
Installing turbolinks 5.0.0.beta2
Using activesupport 5.0.0.beta3
Using loofah 2.0.3
Using rack-test 0.6.3
Using sprockets 3.5.2
Installing listen 3.0.6
Using rails-deprecated_sanitizer 1.0.3
Using globalid 0.3.6
Using activemodel 5.0.0.beta3
Installing jbuilder 2.4.1
Using rails-html-sanitizer 1.0.3
Installing spring-watcher-listen 2.0.0
Using rails-dom-testing 1.0.7
Using activejob 5.0.0.beta3
Using activerecord 5.0.0.beta3
Using actionview 5.0.0.beta3
Using actionpack 5.0.0.beta3
Using actioncable 5.0.0.beta3
Using actionmailer 5.0.0.beta3
Using railties 5.0.0.beta3
Using sprockets-rails 3.0.3
Installing coffee-rails 4.1.1
Installing jquery-rails 4.1.0
Installing web-console 3.1.1
Using rails 5.0.0.beta3
Installing sass-rails 5.0.4
Bundle complete! 15 Gemfile dependencies, 63 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
         run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted

And navigate to the folder

$ cd stock-ticker

Preparing the models, controllers and views

The stocks need to have a basic setup. To do this a scaffold for the stocks is made. The stocks will have a symbol and a price.

$ rails generate scaffold stock symbol:string price:decimal
Running via Spring preloader in process 25276
      invoke  active_record
      create    db/migrate/(timestamp)_create_stocks.rb
      create    app/models/stock.rb
      invoke    test_unit
      create      test/models/stock_test.rb
      create      test/fixtures/stocks.yml
      invoke  resource_route
       route    resources :stocks
      invoke  scaffold_controller
      create    app/controllers/stocks_controller.rb
      invoke    erb
      create      app/views/stocks
      create      app/views/stocks/index.html.erb
      create      app/views/stocks/edit.html.erb
      create      app/views/stocks/show.html.erb
      create      app/views/stocks/new.html.erb
      create      app/views/stocks/_form.html.erb
      invoke    test_unit
      create      test/controllers/stocks_controller_test.rb
      invoke    helper
      create      app/helpers/stocks_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/stocks/index.json.jbuilder
      create      app/views/stocks/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/stocks.coffee
      invoke    scss
      create      app/assets/stylesheets/stocks.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

And as a matter of completeness the scale and precision can be set for the price. This step isn’t necessary and can be skipped.

# db/migrate/(timestamp)_create_stocks.rb
class CreateStocks < ActiveRecord::Migration[5.0]
  def change
    create_table :stocks do |t|
      t.string :symbol
      t.decimal :price, precision: 8, scale: 2

      t.timestamps
    end
  end
end

And then migrate.

$ rake db:migrate
== (timestamp) CreateStocks: migrating =====================================
-- create_table(:stocks)
   -> 0.0034s
== (timestamp) CreateStocks: migrated (0.0036s) ============================

Enabling ActionCable

In order to start using ActionCable there are two things that need to be done. First, 2 lines of CoffeeScript have to be uncommented. These lines make the ‘App’ and ‘App.cable’ object available in other CoffeeScript files.

# app/assets/javascripts/cable.coffee
# Action Cable provides the framework to deal with WebSockets in Rails.
# You can generate new channels where WebSocket features live using the rails generate channel command.
#
# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
#
#= require action_cable
#= require_self
#= require_tree ./channels
#
@App ||= {}                               # <-- these lines
App.cable = ActionCable.createConsumer()  # <-- these lines

Then the cable route has to be uncommented.

# config/routes.rb
Rails.application.routes.draw do
  resources :stocks
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  # Serve websocket cable requests in-process
  mount ActionCable.server => '/cable' # <-- this line
end

The ActionCable Portion

Now to generate a channel for the stocks.

$ rails generate channel stocks follow unfollow_all
Running via Spring preloader in process 25935
      create  app/channels/stocks_channel.rb
      create  app/assets/javascripts/channels/stocks.coffee

Channel generation accepts the channel’s name and then a list of methods. In this case the channel name is stocks and it has a follow method and a unfollow_all method.

Now that there is a channel for the stocks there has to be a stream to distribute data as well. For now all consumers will be listening to the stream “stock_stream:all_stocks” and all data will be transmitted over this stream. Streams are an ephemeral thing that can appear and disappear when a user starts listening or all users stop listening to it. This means that there is no need to predefine streams. Another thing is the appearance of the word “consumer”, this word means “WebSocket client” in the ActionCable lexicon. The code below sets each consumer to listen to “stock_stream:all_stocks” as soon as they connect.

# app/channels/stocks_channel.rb
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class StocksChannel < ApplicationCable::Channel
  def subscribed
    stream_from "stock_stream:all_stocks"  # Added default stream
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def follow
  end

  def unfollow_all
  end
end

The broadcast method, shown below, broadcasts data to every consumer listening to a stream.

ActionCable.server.broadcast(stream, payload)

To make the client update the stock price when it changes there needs to be a broadcast. This can be achieved by creating a hook for database commits in the stock model to broadcast whenever the stock is updated.

# app/models/stock.rb
class Stock < ApplicationRecord
  validates_format_of :symbol, with: /\A[A-Za-z0-9]+\Z/, message: "can only contain letters and numbers"
  validates :symbol, uniqueness: true

  after_commit :broadcast, on: :update

  def broadcast
    ActionCable.server.broadcast( "stock_stream:all_stocks", { symbol: symbol, price: price.to_f } )
  end
end

A few validations are also added here.
With all clients listening to the “stock_stream:all_stocks” stream every consumer will receive the broadcast.

When channels are generated a parallel javascript file is made.

# app/assets/javascripts/channels/stocks.coffee
App.stocks = App.cable.subscriptions.create "StocksChannel",
  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
    console.log(data) # Log all incoming data
  
  follow: ->
    @perform 'follow'
  
  unfollow_all: ->
    @perform 'unfollow_all'

This file defines a class (App.stocks) with five functions. The comments describe the first three nicely and the last two were specified in the generation.

For now all that needs to be done is console logging all received data to see that the stream is working.

Now try it! And if you’ve been running the server be sure to restart it.

Create a stock in one tab and have the web console open in that same tab, then in another tab edit the stock. Firefox logged this in the the first tab.

Object { symbol: "LOP", price: 3.43 }

In this case a stock with a symbol of “LOP” was created and then it’s price was edited to 3.43

Using the ActionCable Portion

Now that the information part has been worked out the data has to be used to update the stocks. The first step here would be making the entry’s price findable by javascript.

<!-- app/views/stocks/index.html.erb -->
<p id="notice"><%= notice %></p>

<h1>Stocks</h1>

<table>
  <thead>
    <tr>
      <th>Symbol</th>
      <th>Price</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @stocks.each do |stock| %>
      <tr id="<%= stock.symbol %>">  <!-- Added an id so javascript can find the row of a specific stock -->
        <td><%= stock.symbol %></td>
        <td id="price"><%= stock.price %></td>  <!-- Added an id so javascript can find the price column of a stock -->
        <td><%= link_to 'Show', stock %></td>
        <td><%= link_to 'Edit', edit_stock_path(stock) %></td>
        <td><%= link_to 'Destroy', stock, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Stock', new_stock_path %>

In the received function of the channel the price is updated.

# app/assets/javascripts/channels/stocks.coffee
  received: (data) ->
    # Called when there's incoming data on the websocket for this channel
    console.log(data) # This will be left alone for debug purposes
    
    $("#" + data.symbol + " #price").text(data.price)

This is usable and you can demo it now. Again, remember to restart the server.

Refining the ActionCable Portion

This is all well and good so far but there is an issue here.

No matter where the user is on the site they are listening to all the stocks. Whether they are looking at a single stock or even none at all. To fix this the consumer needs to be made to listen to a specific stream.

To tackle this problem the capabilities of streams has to be used. A ‘stream-id’ data attribute will be made and placed in the body of the page.

<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title>StockTicker</title>
    <%= csrf_meta_tags %>
    <%= action_cable_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  </head>

  <body<% if content_for? :stock_id %> data-stream-id="<%= yield( :stock_id ) %>"<% end %>> <!-- Add data-stream-id attribute if :stock_id is present -->
    <%= yield %>
  </body>
</html>

And the views will supply the stream-id

<!-- app/views/stocks/index.html.erb -->
<% provide :stock_id, "all_stocks" %>
...
<!-- app/views/stocks/show.html.erb -->
<% provide :stock_id, @stock.symbol %>
<p id="notice"><%= notice %></p>

<div id="<%= @stock.symbol %>">  <!-- Wrap in container with id set to the stock symbol -->
  <p>
    <strong>Symbol:</strong>
    <%= @stock.symbol %>
  </p>

  <p>
    <strong>Price:</strong>
    <span id="price"><%= @stock.price %></span> <!-- Set element that contains the price's id to 'price'-->
  </p>
<div>

<%= link_to 'Edit', edit_stock_path(@stock) %> |
<%= link_to 'Back', stocks_path %>

In both of these edits a call to ‘provide’ sets the ‘stream-id’. In the second edit, in the same way that the entry’s price was made findable by javascript in the index view, the show view was formatted to behave similarly.

Then javascript can access the stream-id attribute. Finish the follow function that was generated and have it be called after the user connects and for every page change.

# app/assets/javascripts/channels/stocks.coffee
App.stocks = App.cable.subscriptions.create "StocksChannel",
  connected: ->
    # Called when the subscription is ready for use on the server
    unless @callbackSet  # set a callback to listen to the new stream after every page change.
      $(document).on 'turbolinks:load', -> App.stocks.follow()
      @callbackSet = true
    @follow() # Call follow the initial time.

  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
    console.log(data) # this will be left alone for debug purposes
    
    $("#" + data.symbol + " #price").text(data.price)

  follow: ->
    @unfollow_all()  # Stop listening to all streams
    if $("body").attr("data-stream-id")  # Listen to next stream if there is one.
      @perform 'follow', stock: $("body").data("stream-id")
  
  unfollow_all: ->
    @perform 'unfollow_all'

When the javascript function ‘follow’ is called it calls the serverside method ‘unfollow_all’. ‘unfollow_all’ calls ‘stop_all_streams’ which stops all streams to the user. It then calls ‘follow’ serverside and passes it a variable called ‘stock’. Methods on the rails side of things only get called with one variable and in this case it is named ‘data’. ‘data’ contains a hash of all the variables passed in from javascript. The variable passed through javascript is ‘stock’ and it can be accessed in the method through data[‘stock’]

# app/channels/stocks_channel.rb
class StocksChannel < ApplicationCable::Channel
  def subscribed # Default stream removed
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def follow( data ) # Added data variable
    stream_from "stock_stream:#{data['stock']}" # Added specific stock stream
  end

  def unfollow_all
    stop_all_streams # Added call to 'stop_all_streams'
  end
end

Adding another broadcast that broadcasts to the single stock stream finishes off that problem.

# app/models/stock.rb
class Stock < ApplicationRecord
  validates_format_of :symbol, with: /\A[A-Za-z0-9]+\Z/, message: "can only contain letters and numbers"
  validates :symbol, uniqueness: true

  after_commit :broadcast, on: :update

  def broadcast
    ActionCable.server.broadcast( "stock_stream:#{symbol}",  { symbol: symbol, price: price.to_f } ) # Added specific stock broadcast
    ActionCable.server.broadcast( "stock_stream:all_stocks", { symbol: symbol, price: price.to_f } )
  end
end

And there it is, finished. Remember to restart the server…

To see the last change create multiple stocks and view one.

When you edit that one in another tab the consumer will receive the change but when you change the other stock one no data will be broadcast to the consumer.

Diagrams

Here are a few diagrams to explain how this ActionCable example works.

This diagram above shows how a consumer can send data to a channel through perform. In this case a function called perform is being called on the App.stocks object. This sends a request to the channel to execute the 'follow' method and pass the stock variable to it. The channel then subscribes the consumer to the stock.
When the consumer is listening to the stream it can then receive updates broadcasted on that stream without the consumer having to know the data is coming. In this case there are three consumers, one that is listening to a specific stock "LOP" and two subscribed to "all_stocks". With the broadcast shown in the above diagram the one that is only subscribed to "LOP" receives nothing and both subscribed to "all_stocks" receive the data.
This broadcast in the above diagram is the opposite with the one subscribed to "LOP" receiving the data and the other two receiving nothing.

All text and images are copyrighted © 2016 Daniel Machen

All source code is available under the MIT License.

The MIT License

Copyright (c) 2016 Daniel Machen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.