Resources

Active Record Session Store - https://github.com/rails/activerecord-session_store
User Agent - https://github.com/gshutler/useragent

You may want to consider using a more maintained version of the User Agent gem. https://github.com/art19/useragent

This episode is sponsored by Honeybadger
Download Source Code

Summary

# Terminal
bundle add activerecord-session_store
bundle add useragent
rails g model user_device user:references ip_address session_id:string:unique data:text properties:json

# config/initializers/session_store.rb
Rails.application.config.session_store :active_record_store, key: '_template_session'
ActiveRecord::SessionStore::Session.table_name = 'user_devices'

# db/migrate/20210620023327_create_user_devices.rb
class CreateUserDevices < ActiveRecord::Migration[6.1]
  def change
    create_table :user_devices do |t|
      t.references :user
      t.string :ip_address
      t.string :session_id
      t.text :data
      t.json :properties

      t.timestamps
    end
  end
end

# models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :user_devices, dependent: :destroy
end

# models/user_device.rb
class UserDevice < ApplicationRecord
  belongs_to :user
  store :properties, accessors: [:browser_name, :browser_version, :os_name], coder: JSON
end

# application_controller.rb
class ApplicationController < ActionController::Base
  before_action :update_user_device

  private

  def update_user_device
    return unless user_signed_in?

    user_device = UserDevice.find_by(session_id: rack_session_id)
    return unless user_device

    user_device.user = current_user if user_device.user.nil?
    user_device.ip_address = request.remote_ip

    user_agent = UserAgent.parse(request.user_agent)
    user_device.properties[:browser_name] = user_agent.browser
    user_device.properties[:browser_version] = user_agent.version
    user_device.properties[:os_name] = user_agent.os
    user_device.save
  end

  def rack_session_id
    request.env['rack.session.record'].session_id
  end
  helper_method :rack_session_id
end

# views/devise/registrations/edit.html.erb
<h1>Devices</h1>
<% resource.user_devices.each do |device| %>
  <p>
    <%= device.properties['browser_name'] %>
    (<%= device.properties['browser_version'] %>)<br>
    <%= device.properties['os_name'] %>
    <%= device.updated_at %>
    <% if device.session_id == rack_session_id %>
      <strong><em>active</em></strong>
    <% else %>
      <%= link_to 'delete', device, method: :delete %>
    <% end %>
  </p>
<% end %>

# config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :users
  resources :user_devices, only: :destroy
  root to: 'welcome#index'
end

# user_devices_controller.rb
class UserDevicesController < ApplicationController
  def destroy
    device = current_user.user_devices.find(params[:id])

    session_record = ActiveRecord::SessionStore::Session.find_by(session_id: device.session_id)
    session_record.destroy
    redirect_to edit_user_registration_path
  end
end

# views/layouts/application.html.erb
<%= link_to 'My Account', edit_user_registration_path %>