Skip to content

ridiculous/usable

Repository files navigation

Usable Gem Version Build Status Code Climate

Usable provides an elegant way to mount and configure your modules. Class level settings can be configured on a per module basis, available to both the module and including class. Allows you to include only the methods you want.

Installation

Add this line to your application's Gemfile:

gem 'usable'

Usage

Configure a module to have "usable" defaults:

module VersionMixin
  extend Usable

  config do
    max_versions 25
    table_name 'versions'
    observer { Class.new }
  end
  
  def save_version
    "Saving #{usables.max_versions} #{usables.table_name}"
  end

  def destroy_version
    "Deleting versions from #{usables.table_name}"
  end
end

Include the module into a class using usable, which will copy over the configs:

class Model
  extend Usable

  usable VersionMixin, only: :save_version do
    max_versions 10
  end

  def save
    save_version
  end
end

model = Model.new
model.save_version         # => "Saving 10 versions"
model.destroy_version      # => NoMethodError: undefined method `destroy_version' for #<Model:...
model.usables.max_versions # => 10
model.usables.table_name   # => "version"

Model now has a #save_versions method but no #destroy_version method. Usable has effectively mixed in the given module using include. Ruby 2+ offers the prepend method, which can be used instead by specifying it as the :method option:

Model.usable VersionMixin, method: :prepend

A usable module can also be extended onto a class with method: :extend

Usable reserves the :only and :method keys. All other keys in the given hash are defined as config settings. If you really want to define a config on the target class with one of these names, you can simply define them in the block:

Model.usable VersionMixin, only: [:save_version] do
  only "Will be set on `Model.usables.only` and namespaced under `Model.usables.version_mixin.only`"
end

Configuring Modules

Configuration settings defined on a "usable" module will be copied to the including class. Usable defines a config method on extended modules (alias for usables) to use for setting default configuration options:

module Mixin
  extend Usable
  config.language = :en
  config do
    country 'US'
    state 'Hawaii'
    census({
      population: 1_400_00,
      daily_visitors: 218_150
    })
  end
end

Model.usable Mixin
Model.usables[:state]                 # => 'Hawaii'
Model.usables.census[:daily_visitors] # => 218150

Confidently calling methods

We should all be writing confident code, which is why you might want to call configurable methods through the usable_method class and instance method. Methods passed in with the :only option will always return nil when called. Thus, the confidence.

Here's the same example as above, rewritten to call methods through the Usable interface:

Model.usable_method(model, :save_version).call    # => "Saving up to 10 versions to custom_versions"
model.usable_method(:save_version).call           # => "Saving up to 10 versions to custom_versions"
Model.usable_method(model, :destroy_version).call # => nil

Module Naming Conventions

Modules with the following names found within the target module's namespace will be automatically used.

  • ClassMethods
  • InstanceMethods

Notes

If the given module is modified by the :only option, then Usable will duplicate the module so that it doesn't mutate it globally. Duplicating a module returns an anonymous module. But anonymous mods in the ancestor list can be confusing. So Usable gives the modified module a name, which is the same name as the original module but with "Used" appended.

Mixin => MixinUsed

Tips and Tricks

since 3.6

Eager-load and freeze usables in production Rails environments with the frozen setting. Note that config.cache_classes (Rails 3+) or config.eager_load (Rails 4+) must also be true, since it hooks into Rails' eager load process.

config.usable_config.frozen = true

since 3.4

Import just a module's constants:

usable ExampleMod, only: :constants

Currently works with usable ExampleMod, only: [] since version 2.0

since 3.3 - (not required)

The Usable::Struct function is available for creating value objects with defaults. If you require "usable/struct" the class function is available to create classes:

class Route < Usable::Struct(paths: %w[api v2 v3])
end

Route.usables.to_h          # => {:paths=>["api", "v2", "v3"]}
Route.new.paths             # => ["api", "v2", "v3"] 
Route.new(paths: nil).paths # => nil

since version 2.0

When usable modules define the same config setting, the last one mounted takes precedence. Fortunately, Usable also "stacks" config settings by namespacing them:

module Robot
  extend Usable
  config do
    speak 'beep bop'
  end
end

module Human
  extend Usable
  config do
    speak 'Hello'
  end
end

class User
  extend Usable
  usable Human, Robot
end

User.usables.speak       # => "beep bop"
User.usables.human.speak # => "Hello"
User.usables.robot.speak # => "beep bop"

Persisted Defaults

Stores usables to a yaml file, so config can be reloaded in subsequent sessions. Useful for setting some defaults to work with locally.

begin
  require "usable"
  require "usable/persistence"
  # Stores commonly used variables across console sessions
  # e.g.
  # > oo.guide = Guide.last
  # > oo.guide
  def oo
    @oo ||= Class.new { extend Usable::Persistence }.new
  end

  # Define previously stored keys on the top level binding
  oo._config.keys.each do |key|
    define_singleton_method(key) { oo.send(key) }
  end
rescue LoadError
  puts "[INFO] Couldn't load `usable` gem. The helper method `oo` is unavailable for this session."
end

Production

When running in production you may want to eager-load any lazily defined attributes and freeze them, ensuring thread safety. Usable provides a railtie that can be configured to freeze Usable and and constants extended with Usable.

Enable the frozen setting:

# config/production.rb
Acme::Application.configure do
  # Freeze all +usables+ after initialize, eager-loading any lazily defined attributes
  config.usable_config.frozen = true
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ridiculous/usable.

About

Make your Ruby modules more usable

Resources

License

Stars

Watchers

Forks

Packages

No packages published