Version 4.0 of Lita, the Ruby chat bot framework, has been released. It contains several new features and improvements for both users and plugin developers. If you're brand new to Lita, you can install it by running gem install lita and learn to use it on the getting started page. Otherwise, read on for all the changes in the latest version.

Feature overview

  • New configuration system with support for data type validation, custom validations, and nested attributes.
  • Rack middleware can now be added to the built-in web server.
  • Handler plugins can be created without inheriting from Lita::Handler by using mixins for only the desired functionality.
  • Handlers can manually set their Redis namespace, allowing complicated plugins to span across multiple classes while still sharing the same data store.
  • Callbacks for chat routes, HTTP routes, and events can be supplied as a block or callable object rather than the name of an instance method to invoke.
  • Both adapters and handlers can be created by passing a block to Lita.register_adapter and Lita.register_handler, respectively.
  • Messages that don't match any handler routes will now trigger an :unhandled_message event.
  • New RSpec matchers for all types of routes with authorization group support, improved syntax and additional functionality.
  • Easily test HTTP routes with the new http helper method for RSpec.
  • New authorization methods to make management and testing of authorization groups easier.
  • Major refactoring to reduce reliance on global state, offering safer, more predictable programming and testing.
  • Improved failure detection and messaging for Redis and the built-in web server.

The guides on the documentation site have also been greatly improved.

Upgrading for Lita users

Lita 4 is completely backwards compatible for users. All existing functionality and plugins will continue to work. However, Lita 4 introduces a new configuration system, and a few things will need to change in your lita_config.rb file as the plugins you use begin to take advantage of features from Lita 4. Most configuration will remain unchanged, but a couple of attributes will emit deprecation warnings until they are updated to be compatible with the new style. These attributes are:

  • config.adapter

    This attribute has been deprecated in favor of config.adapters, which has a separate attribute for each adapter by name, just as config.handlers does. Existing versions of adapters will continue to read from the original config.adapter attribute, but once a version of the adapter you're using is released that uses the new system, you'll need to update your configuration accordingly.

    Example: Using the HipChat adapter, config.adapter.jid = "12345_123456@chat.hipchat.com" would become config.adapters.hipchat.jid = "12345_123456@chat.hipchat.com".

  • config.redis

    The redis configuration attribute is now a hash. Lita 4 will continue to support struct-style access, but only hash-style access will be supported in Lita 5, so you should update now.

    Example: config.redis.host = "redis.example.com" will become config.redis[:host] = "redis.example.com"

In addition to these configuration changes, Lita's built-in daemonization feature has been deprecated without replacement. Instead, daemonization of your Lita process should be done using dedicated process management tools for your operating system. Popular choices are systemd, Upstart, and runit. Lita's built-in daemonization will be removed completely in Lita 5. For more detail on the reasons behind this change, Mike Perham's Don't Daemonize Your Daemons! is good reading.

Upgrading for plugin developers

In accordance with Lita's versioning policy, Lita 4's only breaking change is the removal of one API that was already deprecated in Lita 3:

  • Lita::User.find

    This method was an alias for Lita::User.create, but had a misleading name, so it was deprecated in Lita 3. It has been removed completely in Lita 4. If you have any code that is still using find, change it to using create if you are creating a user, and one of the find_by_* methods if you're looking up an existing user.

Although this is the only breaking change in Lita 4, the new version introduces several new deprecations. All of these APIs will continue to work for Lita 4, but will be removed in Lita 5, so you should upgrade your plugins for future compatibility. The easiest way to upgrade your plugins is to change the code to use the Lita 4 versions of the deprecated APIs, then release a new gem with a dependency on Lita 4 or greater. If you want to make your plugin compatible with both the old and new APIs so that Lita 4 is not required to use your plugin, your plugin code should use multiple code paths, one for each deprecated API. It is recommended to take the easier approach, to reduce the complexity of your plugin code and help move users forward. After all, they can continue to use the existing version of your plugin if they aren't ready to upgrade to Lita 4.

The following APIs are now deprecated:

  • Lita::Adapter.require_config

    Lita 4 introduces a new, more powerful configuration system that is used by both adapters and handlers. While require_config only caused Lita to validate that a configuration attribute was set, the new system has more capabilities.

    Example: require_config(:foo) will simply change to config(:foo). Each attribute must be specified with a separate call to config.

  • Lita::Adapter.required_configs

    This attribute returns an array of required configuration attributes as set with require_config. If for some reason you were accessing this, you'll want to use configuration_builder instead, although that returns a Lita::ConfigurationBuilder object rather than an array of simple values.

  • Lita.config.adapter

    Under the new configuration system, each adapter has its own configuration object under Lita.config.adapters, just like handlers always have. Configuration attributes defined using Lita::Adapter.config will be found at Lita.config.adapters.your_adapter_namespace.your_configuration_attribute.

  • Lita::Handler.default_config

    In Lita 3, handlers defined configuration attributes by defining the class method default_config, which was passed a Lita::Config object that behaved like an OpenStruct. This code should be converted to use the configuration system, which is achieved by callilng the class method config for each desired configuration attribute. The new system is more strict, so all configuration attributes must be declared, even if their default value is nil.

    Example:

    Before:

    def self.default_config(config)
      config.use_special_mode = false
      config.custom_value = nil
    end
    

    After:

    config :use_special_mode, default: false
    config :custom_value
    

    In either case, the handler's configuration can be accessed at runtime with Lita::Handler#config. If both types of configuration declarations are present, the old style will take precedence, for backwards compatibility.

  • Lita::Config

    This entire class is deprecated with the introduction of the new configuration system. If you were using this class directly before, you might want to consider using a hash or OpenStruct from the standard library. If you have a plugin that needs nested configuration attributes, the new configuration system's DSL allows for this without having to manually instantiate a configuration object.

  • Lita::Authorization class methods

    The authorization module is now a class, and all the methods are instance methods that use configuration derived from the currently running robot. Code should be upgraded to access authorization via the robot.

    Example: Lita::Authorization.user_in_group?(user, :group) would now be robot.auth.user_in_group?(user, :group).

  • Lita::RSpec routing matchers

    Lita 4's RSpec routing matchers use a new syntax that is more akin to RSpec style. Rather than enumerate each possible usage here, the following code block will demonstrate a few examples:

    ### Lita 3
    
    # passes but doesn't actually test anything
    it { routes("foo") }
    
    it { routes("foo").to(:bar) }
    
    # bypasses the authorization protected_method requires
    it { routes("foo").to(:protected_method) }
    
    it { doesnt_route("foo").to(:bar) }
    
    ### Lita 4
    
    # actually ensures that the message matches at least one route
    it { is_expected.to route("foo") }
    
    it { is_expected.to route("foo").to(:bar) }
    
    # requires the authorization group to be specified
    it { is_expected.to route("foo").to(:protected_method).with_authorization_for(:baz_admins) }
    
    # negating the expectation doesn't change the name of the matcher
    it { is_expected.not_to route("foo").to(:bar) }
    

    As you can see from the example above, the new matchers don't give a false positive when the name of the route is not supplied. Lita 3's routing matchers also ignored the authorization system, while Lita 4's do not. In order to test a route that requires authorization, use the with_authorization_for fluent interface. The routing syntax for testing HTTP routes and event handlers follows a similar style to the chat routes. When you run your test suite, the deprecation warnings will tell you exactly what the new syntax is whenever the old syntax is encountered. The old syntax will be removed in Lita 5.

Lastly, Lita 4 includes some internal API changes that don't cause runtime functionality to break, but may cause tests written against Lita 3 to fail if they were directly using or stubbing some of these old APIs. To make the transition smooth, Lita 4 has a special mode called "version 3 compatibility mode" that activates the old code paths while the test suite is running. Compatibility mode is turned on by default. When you're ready to upgrade your test suite, turn it off by adding the following code to spec/spec_helper.rb:

require "lita/rspec"

Lita.version_3_compatibility_mode = false

This may cause some tests to begin failing that pass with compatibility mode on. The cause of each failure will be one of the following:

  • Your code is using Lita::Adapter.require_config. Use Lita::Adapter.config instead.
  • Your code is stubbing one of the class methods on Lita::Authorization. If you're doing that so that a test behaves as though a user is in a particular authorization group, you can use robot.auth.add_user_to_group!(user, group) instead. This new bang method does not require a "requesting user," making the programmatic management of authorization groups much simpler.
  • Your code directly accesses or stubs class methods on the top-level Lita module. Most of the global state previously stored only on this object is now stored using a Lita::Registry object. Instead of resetting the state on the Lita module for each test, a fresh registry is used for each test. You can access the test's registry with the registry method from any test. Compatibility mode works by using the real Lita object instead of a fresh registry.

    Example: Lita.register_handler(MyTestHandler) should change to registry.register_handler(MyTestHandler).

Lita 3 compatibility mode will be removed in Lita 5, and only the new code paths will work.