anycable-rails 1.0.0.preview2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -122
- data/README.md +14 -34
- data/lib/anycable/rails.rb +36 -2
- data/lib/anycable/rails/actioncable/channel.rb +4 -0
- data/lib/anycable/rails/actioncable/connection.rb +39 -35
- data/lib/anycable/rails/actioncable/connection/persistent_session.rb +7 -3
- data/lib/anycable/rails/actioncable/connection/serializable_identification.rb +42 -0
- data/lib/anycable/rails/actioncable/remote_connections.rb +11 -0
- data/lib/anycable/rails/actioncable/testing.rb +35 -0
- data/lib/anycable/rails/channel_state.rb +46 -0
- data/lib/anycable/rails/compatibility.rb +4 -7
- data/lib/anycable/rails/compatibility/rubocop.rb +0 -1
- data/lib/anycable/rails/compatibility/rubocop/config/default.yml +3 -0
- data/lib/anycable/rails/compatibility/rubocop/cops/anycable/instance_vars.rb +1 -1
- data/lib/anycable/rails/compatibility/rubocop/cops/anycable/stream_from.rb +4 -4
- data/lib/anycable/rails/railtie.rb +26 -18
- data/lib/anycable/rails/refinements/subscriptions.rb +5 -0
- data/lib/anycable/rails/session_proxy.rb +19 -2
- data/lib/anycable/rails/version.rb +1 -1
- data/lib/generators/anycable/download/USAGE +14 -0
- data/lib/generators/anycable/download/download_generator.rb +83 -0
- data/lib/generators/anycable/setup/USAGE +2 -0
- data/lib/{rails/generators → generators}/anycable/setup/setup_generator.rb +100 -88
- data/lib/generators/anycable/setup/templates/Procfile.dev.tt +6 -0
- data/lib/{rails/generators → generators}/anycable/setup/templates/config/anycable.yml.tt +22 -4
- data/lib/generators/anycable/setup/templates/config/cable.yml.tt +11 -0
- data/lib/{rails/generators/anycable/setup/templates/config/initializers/anycable.rb → generators/anycable/setup/templates/config/initializers/anycable.rb.tt} +1 -1
- data/lib/generators/anycable/with_os_helpers.rb +55 -0
- metadata +29 -52
- data/lib/anycable/rails/compatibility/rubocop/cops/anycable/remote_disconnect.rb +0 -31
- data/lib/rails/generators/anycable/setup/templates/Procfile +0 -2
- data/lib/rails/generators/anycable/setup/templates/Procfile.dev +0 -3
- data/lib/rails/generators/anycable/setup/templates/bin/heroku-web +0 -7
- data/lib/rails/generators/anycable/setup/templates/config/cable.yml.tt +0 -11
| @@ -15,16 +15,20 @@ module ActionCable | |
| 15 15 | 
             
                    return super unless socket.session
         | 
| 16 16 |  | 
| 17 17 | 
             
                    super.tap do |req|
         | 
| 18 | 
            -
                      req.env[Rack::RACK_SESSION] =
         | 
| 19 | 
            -
                        AnyCable::Rails::SessionProxy.new(req.env[Rack::RACK_SESSION], socket.session)
         | 
| 18 | 
            +
                      req.env[::Rack::RACK_SESSION] =
         | 
| 19 | 
            +
                        AnyCable::Rails::SessionProxy.new(req.env[::Rack::RACK_SESSION], socket.session)
         | 
| 20 20 | 
             
                    end
         | 
| 21 21 | 
             
                  end
         | 
| 22 22 |  | 
| 23 23 | 
             
                  def commit_session!
         | 
| 24 | 
            -
                    return unless request_loaded? && request.session.loaded?
         | 
| 24 | 
            +
                    return unless request_loaded? && request.session.respond_to?(:loaded?) && request.session.loaded?
         | 
| 25 25 |  | 
| 26 26 | 
             
                    socket.session = request.session.to_json
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 | 
             
                end
         | 
| 29 29 | 
             
              end
         | 
| 30 30 | 
             
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            ::ActionCable::Connection::Base.prepend(
         | 
| 33 | 
            +
              ::ActionCable::Connection::PersistentSession
         | 
| 34 | 
            +
            )
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ActionCable
         | 
| 4 | 
            +
              module Connection
         | 
| 5 | 
            +
                module SerializableIdentification
         | 
| 6 | 
            +
                  extend ActiveSupport::Concern
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  class_methods do
         | 
| 9 | 
            +
                    def identified_by(*identifiers)
         | 
| 10 | 
            +
                      super
         | 
| 11 | 
            +
                      Array(identifiers).each do |identifier|
         | 
| 12 | 
            +
                        define_method(identifier) do
         | 
| 13 | 
            +
                          instance_variable_get(:"@#{identifier}") || fetch_identifier(identifier)
         | 
| 14 | 
            +
                        end
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  # Generate identifiers info.
         | 
| 20 | 
            +
                  # Converts GlobalID compatible vars to corresponding global IDs params.
         | 
| 21 | 
            +
                  def identifiers_hash
         | 
| 22 | 
            +
                    identifiers.each_with_object({}) do |id, acc|
         | 
| 23 | 
            +
                      obj = instance_variable_get("@#{id}")
         | 
| 24 | 
            +
                      next unless obj
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      acc[id] = AnyCable::Rails.serialize(obj)
         | 
| 27 | 
            +
                    end.compact
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def identifiers_json
         | 
| 31 | 
            +
                    identifiers_hash.to_json
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  # Fetch identifier and deserialize if neccessary
         | 
| 35 | 
            +
                  def fetch_identifier(name)
         | 
| 36 | 
            +
                    @cached_ids[name] ||= @cached_ids.fetch(name) do
         | 
| 37 | 
            +
                      AnyCable::Rails.deserialize(ids[name.to_s])
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "action_cable/remote_connections"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ActionCable::RemoteConnections::RemoteConnection.include(ActionCable::Connection::SerializableIdentification)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ActionCable::RemoteConnections::RemoteConnection.prepend(Module.new do
         | 
| 8 | 
            +
              def disconnect(reconnect: true)
         | 
| 9 | 
            +
                ::AnyCable.broadcast_adapter.broadcast_command("disconnect", identifier: identifiers_json, reconnect: reconnect)
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end)
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # This file contains patches to Action Cable testing modules
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # Trigger autoload (if constant is defined)
         | 
| 6 | 
            +
            begin
         | 
| 7 | 
            +
              ActionCable::Channel::TestCase # rubocop:disable Lint/Void
         | 
| 8 | 
            +
              ActionCable::Connection::TestCase # rubocop:disable Lint/Void
         | 
| 9 | 
            +
            rescue NameError
         | 
| 10 | 
            +
              return
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ActionCable::Channel::ChannelStub.prepend(Module.new do
         | 
| 14 | 
            +
              def subscribe_to_channel
         | 
| 15 | 
            +
                # allocate @streams
         | 
| 16 | 
            +
                streams
         | 
| 17 | 
            +
                handle_subscribe
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ActionCable::Channel::ConnectionStub.prepend(Module.new do
         | 
| 22 | 
            +
              def socket
         | 
| 23 | 
            +
                @socket ||= AnyCable::Socket.new(env: {})
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              alias_method :anycable_socket, :socket
         | 
| 27 | 
            +
            end)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            ActionCable::Connection::TestConnection.prepend(Module.new do
         | 
| 30 | 
            +
              def initialize(request)
         | 
| 31 | 
            +
                @request = request
         | 
| 32 | 
            +
                @cached_ids = {}
         | 
| 33 | 
            +
                super
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end)
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module AnyCable
         | 
| 4 | 
            +
              module Rails
         | 
| 5 | 
            +
                module ChannelState
         | 
| 6 | 
            +
                  module ClassMethods
         | 
| 7 | 
            +
                    def state_attr_accessor(*names)
         | 
| 8 | 
            +
                      names.each do |name|
         | 
| 9 | 
            +
                        channel_state_attributes << name
         | 
| 10 | 
            +
                        class_eval <<~RUBY, __FILE__, __LINE__ + 1
         | 
| 11 | 
            +
                          def #{name}
         | 
| 12 | 
            +
                            return @#{name} if instance_variable_defined?(:@#{name})
         | 
| 13 | 
            +
                            @#{name} = AnyCable::Rails.deserialize(connection.socket.istate["#{name}"], json: true) if connection.anycable_socket
         | 
| 14 | 
            +
                          end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                          def #{name}=(val)
         | 
| 17 | 
            +
                            connection.socket.istate["#{name}"] = AnyCable::Rails.serialize(val, json: true) if connection.anycable_socket
         | 
| 18 | 
            +
                            instance_variable_set(:@#{name}, val)
         | 
| 19 | 
            +
                          end
         | 
| 20 | 
            +
                        RUBY
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def channel_state_attributes
         | 
| 25 | 
            +
                      return @channel_state_attributes if instance_variable_defined?(:@channel_state_attributes)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      @channel_state_attributes =
         | 
| 28 | 
            +
                        if superclass.respond_to?(:channel_state_attributes)
         | 
| 29 | 
            +
                          superclass.channel_state_attributes.dup
         | 
| 30 | 
            +
                        else
         | 
| 31 | 
            +
                          []
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def self.included(base)
         | 
| 37 | 
            +
                    base.extend ClassMethods
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            ActiveSupport.on_load(:action_cable) do
         | 
| 44 | 
            +
              # `state_attr_accessor` must be available in Action Cable
         | 
| 45 | 
            +
              ::ActionCable::Channel::Base.include(AnyCable::Rails::ChannelState)
         | 
| 46 | 
            +
            end
         | 
| @@ -33,6 +33,10 @@ module AnyCable | |
| 33 33 | 
             
                    res = yield
         | 
| 34 34 | 
             
                    diff = instance_variables - was_ivars
         | 
| 35 35 |  | 
| 36 | 
            +
                    if self.class.respond_to?(:channel_state_attributes)
         | 
| 37 | 
            +
                      diff.delete_if { |ivar| self.class.channel_state_attributes.include?(:"#{ivar.to_s.sub(/^@/, "")}") }
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 36 40 | 
             
                    unless diff.empty?
         | 
| 37 41 | 
             
                      raise AnyCable::CompatibilityError,
         | 
| 38 42 | 
             
                        "Channel instance variables are not supported by AnyCable, " \
         | 
| @@ -48,12 +52,5 @@ module AnyCable | |
| 48 52 | 
             
                    raise AnyCable::CompatibilityError, "Periodical timers are not supported by AnyCable"
         | 
| 49 53 | 
             
                  end
         | 
| 50 54 | 
             
                end)
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                ActionCable::RemoteConnections::RemoteConnection.prepend(Module.new do
         | 
| 53 | 
            -
                  def disconnect
         | 
| 54 | 
            -
                    raise AnyCable::CompatibilityError,
         | 
| 55 | 
            -
                      "Disconnecting remote clients is not supported by AnyCable yet"
         | 
| 56 | 
            -
                  end
         | 
| 57 | 
            -
                end)
         | 
| 58 55 | 
             
              end
         | 
| 59 56 | 
             
            end
         | 
| @@ -4,7 +4,6 @@ require "rubocop" | |
| 4 4 | 
             
            require "pathname"
         | 
| 5 5 |  | 
| 6 6 | 
             
            require_relative "rubocop/cops/anycable/stream_from"
         | 
| 7 | 
            -
            require_relative "rubocop/cops/anycable/remote_disconnect"
         | 
| 8 7 | 
             
            require_relative "rubocop/cops/anycable/periodical_timers"
         | 
| 9 8 | 
             
            require_relative "rubocop/cops/anycable/instance_vars"
         | 
| 10 9 |  | 
| @@ -25,7 +25,7 @@ module RuboCop | |
| 25 25 | 
             
                  #   end
         | 
| 26 26 | 
             
                  #
         | 
| 27 27 | 
             
                  class InstanceVars < RuboCop::Cop::Cop
         | 
| 28 | 
            -
                    MSG = "Channel instance variables are not supported in AnyCable"
         | 
| 28 | 
            +
                    MSG = "Channel instance variables are not supported in AnyCable. Use `state_attr_accessor` instead"
         | 
| 29 29 |  | 
| 30 30 | 
             
                    def on_class(node)
         | 
| 31 31 | 
             
                      find_nested_ivars(node) do |nested_ivar|
         | 
| @@ -11,7 +11,7 @@ module RuboCop | |
| 11 11 | 
             
                  #   # bad
         | 
| 12 12 | 
             
                  #   class MyChannel < ApplicationCable::Channel
         | 
| 13 13 | 
             
                  #     def follow
         | 
| 14 | 
            -
                  #        | 
| 14 | 
            +
                  #       stream_for(room) {}
         | 
| 15 15 | 
             
                  #     end
         | 
| 16 16 | 
             
                  #   end
         | 
| 17 17 | 
             
                  #
         | 
| @@ -36,15 +36,15 @@ module RuboCop | |
| 36 36 | 
             
                  #
         | 
| 37 37 | 
             
                  class StreamFrom < RuboCop::Cop::Cop
         | 
| 38 38 | 
             
                    def_node_matcher :stream_from_with_block?, <<-PATTERN
         | 
| 39 | 
            -
                      (block (send _ :stream_from ...) ...)
         | 
| 39 | 
            +
                      (block {(send _ :stream_from ...) (send _ :stream_for ...)} ...)
         | 
| 40 40 | 
             
                    PATTERN
         | 
| 41 41 |  | 
| 42 42 | 
             
                    def_node_matcher :stream_from_with_callback?, <<-PATTERN
         | 
| 43 | 
            -
                      (send _ :stream_from str_type? (block (send nil? :lambda) ...))
         | 
| 43 | 
            +
                      {(send _ :stream_from str_type? (block (send nil? :lambda) ...)) (send _ :stream_for ... (block (send nil? :lambda) ...))}
         | 
| 44 44 | 
             
                    PATTERN
         | 
| 45 45 |  | 
| 46 46 | 
             
                    def_node_matcher :args_of_stream_from, <<-PATTERN
         | 
| 47 | 
            -
                      (send _ :stream_from str_type? $...)
         | 
| 47 | 
            +
                      {(send _ :stream_from str_type? $...)  (send _ :stream_for $...)}
         | 
| 48 48 | 
             
                    PATTERN
         | 
| 49 49 |  | 
| 50 50 | 
             
                    def_node_matcher :coder_symbol?, "(pair (sym :coder) ...)"
         | 
| @@ -1,23 +1,24 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require "anycable/rails/channel_state"
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module AnyCable
         | 
| 4 6 | 
             
              module Rails
         | 
| 5 7 | 
             
                class Railtie < ::Rails::Railtie # :nodoc:
         | 
| 6 8 | 
             
                  initializer "anycable.disable_action_cable_mount", after: "action_cable.set_configs" do |app|
         | 
| 7 | 
            -
                     | 
| 8 | 
            -
                    adapter = ::ActionCable.server.config.cable&.fetch("adapter", nil)
         | 
| 9 | 
            -
                    next unless AnyCable::Rails.compatible_adapter?(adapter)
         | 
| 9 | 
            +
                    next unless AnyCable::Rails.enabled?
         | 
| 10 10 |  | 
| 11 11 | 
             
                    app.config.action_cable.mount_path = nil
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| 14 14 | 
             
                  initializer "anycable.logger", after: "action_cable.logger" do |_app|
         | 
| 15 | 
            -
                    AnyCable.logger =  | 
| 15 | 
            +
                    AnyCable.logger = ::ActionCable.server.config.logger
         | 
| 16 16 |  | 
| 17 | 
            -
                     | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                       | 
| 17 | 
            +
                    AnyCable.configure_server do
         | 
| 18 | 
            +
                      AnyCable.logger = ActiveSupport::TaggedLogging.new(::ActionCable.server.config.logger)
         | 
| 19 | 
            +
                      # Broadcast server logs to STDOUT in development
         | 
| 20 | 
            +
                      if ::Rails.env.development? &&
         | 
| 21 | 
            +
                          !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, STDOUT)
         | 
| 21 22 | 
             
                        console = ActiveSupport::Logger.new(STDOUT)
         | 
| 22 23 | 
             
                        console.formatter = ::Rails.logger.formatter
         | 
| 23 24 | 
             
                        console.level = ::Rails.logger.level
         | 
| @@ -42,23 +43,30 @@ module AnyCable | |
| 42 43 |  | 
| 43 44 | 
             
                  initializer "anycable.connection_factory", after: "action_cable.set_configs" do |app|
         | 
| 44 45 | 
             
                    ActiveSupport.on_load(:action_cable) do
         | 
| 45 | 
            -
                       | 
| 46 | 
            -
                      if  | 
| 47 | 
            -
             | 
| 46 | 
            +
                      # Add AnyCable patch method stub (we use it in ChannelState to distinguish between Action Cable and AnyCable)
         | 
| 47 | 
            +
                      # NOTE: Method could be already defined if patch was loaded manually
         | 
| 48 | 
            +
                      ActionCable::Connection::Base.attr_reader(:anycable_socket) unless ActionCable::Connection::Base.method_defined?(:anycable_socket)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      app.config.to_prepare do
         | 
| 51 | 
            +
                        AnyCable.connection_factory = ActionCable.server.config.connection_class.call
         | 
| 52 | 
            +
                      end
         | 
| 48 53 |  | 
| 54 | 
            +
                      if AnyCable::Rails.enabled?
         | 
| 55 | 
            +
                        require "anycable/rails/actioncable/connection"
         | 
| 49 56 | 
             
                        if AnyCable.config.persistent_session_enabled
         | 
| 50 57 | 
             
                          require "anycable/rails/actioncable/connection/persistent_session"
         | 
| 51 | 
            -
                          ::ActionCable::Connection::Base.prepend(
         | 
| 52 | 
            -
                            ::ActionCable::Connection::PersistentSession
         | 
| 53 | 
            -
                          )
         | 
| 54 | 
            -
                        end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                        app.config.to_prepare do
         | 
| 57 | 
            -
                          AnyCable.connection_factory = ActionCable.server.config.connection_class.call
         | 
| 58 58 | 
             
                        end
         | 
| 59 59 | 
             
                      end
         | 
| 60 60 | 
             
                    end
         | 
| 61 61 | 
             
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  initializer "anycable.testing" do |app|
         | 
| 64 | 
            +
                    next unless ::Rails.env.test?
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    ActiveSupport.on_load(:action_cable) do
         | 
| 67 | 
            +
                      require "anycable/rails/actioncable/testing"
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 62 70 | 
             
                end
         | 
| 63 71 | 
             
              end
         | 
| 64 72 | 
             
            end
         | 
| @@ -7,6 +7,11 @@ module AnyCable | |
| 7 7 | 
             
                    # Find or add a subscription to the list
         | 
| 8 8 | 
             
                    def fetch(identifier)
         | 
| 9 9 | 
             
                      add("identifier" => identifier) unless subscriptions[identifier]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      unless subscriptions[identifier]
         | 
| 12 | 
            +
                        raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch("channel")}"
         | 
| 13 | 
            +
                      end
         | 
| 14 | 
            +
             | 
| 10 15 | 
             
                      subscriptions[identifier]
         | 
| 11 16 | 
             
                    end
         | 
| 12 17 | 
             
                  end
         | 
| @@ -5,8 +5,6 @@ module AnyCable | |
| 5 5 | 
             
                # Wrap `request.session` to lazily load values provided
         | 
| 6 6 | 
             
                # in the RPC call (set by the previous calls)
         | 
| 7 7 | 
             
                class SessionProxy
         | 
| 8 | 
            -
                  delegate_missing_to :@rack_session
         | 
| 9 | 
            -
             | 
| 10 8 | 
             
                  attr_reader :rack_session, :socket_session
         | 
| 11 9 |  | 
| 12 10 | 
             
                  def initialize(rack_session, socket_session)
         | 
| @@ -41,6 +39,25 @@ module AnyCable | |
| 41 39 | 
             
                    rack_session.keys + socket_session.keys
         | 
| 42 40 | 
             
                  end
         | 
| 43 41 |  | 
| 42 | 
            +
                  # Delegate both publuc and private methods to rack_session
         | 
| 43 | 
            +
                  def respond_to_missing?(name, include_private = false)
         | 
| 44 | 
            +
                    return false if name == :marshal_dump || name == :_dump
         | 
| 45 | 
            +
                    rack_session.respond_to?(name, include_private) || super
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def method_missing(method, *args, &block)
         | 
| 49 | 
            +
                    if rack_session.respond_to?(method, true)
         | 
| 50 | 
            +
                      rack_session.send(method, *args, &block)
         | 
| 51 | 
            +
                    else
         | 
| 52 | 
            +
                      super
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # This method is used by StimulusReflex to obtain `@by`
         | 
| 57 | 
            +
                  def instance_variable_get(name)
         | 
| 58 | 
            +
                    super || rack_session.instance_variable_get(name)
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 44 61 | 
             
                  private
         | 
| 45 62 |  | 
| 46 63 | 
             
                  def restore!
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            Description:
         | 
| 2 | 
            +
                Install AnyCable-Go web server locally (the latest version by default).
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Example:
         | 
| 5 | 
            +
                rails generate anycable:download
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                This will ask:
         | 
| 8 | 
            +
                    Where to store a binary file.
         | 
| 9 | 
            +
                This will create:
         | 
| 10 | 
            +
                    `<bin_path>/anycable-go`.
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                rails generate anycable:download --bin-path=/usr/local/bin
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                rails generate anycable:download --version=1.0.0
         | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "generators/anycable/with_os_helpers"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module AnyCableRailsGenerators
         | 
| 6 | 
            +
              # Downloads anycable-go binary
         | 
| 7 | 
            +
              class DownloadGenerator < ::Rails::Generators::Base
         | 
| 8 | 
            +
                namespace "anycable:download"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                include WithOSHelpers
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                VERSION = "latest"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                class_option :bin_path,
         | 
| 15 | 
            +
                  type: :string,
         | 
| 16 | 
            +
                  desc: "Where to download AnyCable-Go server binary (default: #{DEFAULT_BIN_PATH})"
         | 
| 17 | 
            +
                class_option :version,
         | 
| 18 | 
            +
                  type: :string,
         | 
| 19 | 
            +
                  desc: "Specify the AnyCable-Go version (defaults to latest release)"
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def download_bin
         | 
| 22 | 
            +
                  out = options[:bin_path] || DEFAULT_BIN_PATH
         | 
| 23 | 
            +
                  version = options[:version] || VERSION
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  download_exe(
         | 
| 26 | 
            +
                    release_url(version),
         | 
| 27 | 
            +
                    to: out,
         | 
| 28 | 
            +
                    file_name: "anycable-go"
         | 
| 29 | 
            +
                  )
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  true
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def release_url(version)
         | 
| 37 | 
            +
                  return latest_release_url(version) if version == "latest"
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  if Gem::Version.new(version).segments.first >= 1
         | 
| 40 | 
            +
                    new_release_url("v#{version}")
         | 
| 41 | 
            +
                  else
         | 
| 42 | 
            +
                    legacy_release_url("v#{version}")
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def legacy_release_url(version)
         | 
| 47 | 
            +
                  "https://github.com/anycable/anycable-go/releases/download/#{version}/" \
         | 
| 48 | 
            +
                    "anycable-go-v#{version}-#{os_name}-#{cpu_name}"
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def new_release_url(version)
         | 
| 52 | 
            +
                  "https://github.com/anycable/anycable-go/releases/download/#{version}/" \
         | 
| 53 | 
            +
                    "anycable-go-#{os_name}-#{cpu_name}"
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def latest_release_url(version)
         | 
| 57 | 
            +
                  "https://github.com/anycable/anycable-go/releases/latest/download/" \
         | 
| 58 | 
            +
                    "anycable-go-#{os_name}-#{cpu_name}"
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def download_exe(url, to:, file_name:)
         | 
| 62 | 
            +
                  file_path = File.join(to, file_name)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  run "#{sudo(to)}curl -L #{url} -o #{file_path}", abort_on_failure: true
         | 
| 65 | 
            +
                  run "#{sudo(to)}chmod +x #{file_path}", abort_on_failure: true
         | 
| 66 | 
            +
                  run "#{file_path} -v", abort_on_failure: true
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def sudo(path)
         | 
| 70 | 
            +
                  sudo = ""
         | 
| 71 | 
            +
                  unless File.writable?(path)
         | 
| 72 | 
            +
                    if yes? "Path is not writable 😕. Do you have sudo privileges?"
         | 
| 73 | 
            +
                      sudo = "sudo "
         | 
| 74 | 
            +
                    else
         | 
| 75 | 
            +
                      say_status :error, "❌ Failed to install AnyCable-Go WebSocket server", :red
         | 
| 76 | 
            +
                      raise StandardError, "Path #{path} is not writable!"
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  sudo
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         |