Evil clients use nested scopes to collect definitions and options, used by single operations.

Root Scope

Options and definitions for a root scope should be made in a body of client class:

class CatsClient < Evil::Client
  option :user,      proc(&:to_s)
  option :password,  proc(&:to_s)
  option :subdomain, proc(&:to_s)

  validate(:valid_subdomain) { %w[wild domestic].include? subdomain }

  path { "https://#{subdomain}.example.com/" }
  http_method "get"
  security { basic_auth(user, password) }
end

These options should be assigned to client instance (all undefined ones will be ignored by the initializer).

client = CatsClient.new user: "andy", password: "foo", subdomain: "wild"
client.options # => { user: "andy", password: "foo", subdomain: "wild" }

You're free to made definitions on the root level, or leave them to subscopes and concrete operations. The only recommendation is to define base path here for all subscopes to share it. It's well worth it to define security settings at the root level as well.

Subscopes

Then you can define named subscope with additional options and definitions. The root options, validators and definitions are inherited by subscopes:

class CatsClient < Evil::Client
  # ...

  scope :cats do
    # scope-specific options
    option :version, proc(&:to_i)

    validate(:supported_version) { version < 5 }

    # scope-specific redefinition of the root settings
    http_method { version.zero? ? :get : :post }
    path { "cats/v#{version}" }
  end
end
cats = client.cats(version: 3)
cats.options # => { user: "andy", password: "foo", subdomain: "wild", version: 3 }

You can define any number of subscopes at any level of nesting. Every next level will inherit a previous one. There is no difference in DSL for various labels (with a single exclusion for connection -- you can assign it to client as a whole, not to a nested scope).

class CatsClient < Evil::Client
  # ...
  scope :cats do
    # ...

    scope :video do
      # ...
    end

    scope :books do
      # ...
    end
  end
end

Inside a scope you can define the operation -- endpoints to remote API. Any operation belongs to the containing scope and inherits its options, validators and shared definitions (and can reload any).

class CatsClient < Evil::Client
  # ...
  scope :cats do
    # ...
    operation :fetch do
      # ...
    end
  end
end

Instantiation

There're several ways to instantiate the scope with options.

A verbose (explicit) style:

client.scopes[:cats].new(version: 3)

...and a bit shorter version (call is an alias for new):

client.scopes[:cats].(version: 3)

...and even shorter ([] is an alias for new as well):

client.scopes[:cats][version: 3]

Or just use the name of the scope as a method:

client.cats(version: 3)