Currently Shaf support the following commands (new
, server
, test
, console
, generate
, upgrade
and version
). It's also possible to extend Shaf with custom commands and generators. Whenever the shaf
command is executed the file config/customize.rb
is loaded and checked for additional commands. To add a custom command, create a class that inherits from Shaf::Command::Base
. Either put it directly in config/customize.rb
or put it in a separate file and require that file inside config/customize.rb
. Your customized class must call the inherited class method identifier
with a String
/Symbol
/Regexp
(or an array of String
/Symbol
/Regexp
values) that identifies the command. The identifier is used to match arguments passed to shaf
. The command/generator must respond to call
without any arguments. The arguments after the identifer will be availble from the instance method args
. Writing a couple of simple commands that echos back the arguments would be written as:
class EchoCommand < Shaf::Command::Base
identifier :echo
def call
puts args
end
end
class EchoUpCommand < Shaf::Command::Base
identifier :echo, :up
def call
puts args.map(&:upcase)
end
end
class EchoDownCommand < Shaf::Command::Base
identifier :echo, :down
def call
puts args.map(&:downcase)
end
end
Then running shaf echo Hello World
would print:
Hello
World
Running shaf echo up Hello World
would print:
HELLO
WORLD
Running shaf echo down Hello World
would print:
hello
world
These commands are of course useless, but hopefully they give you an idea of what is happening.
Generators work pretty much the same as commands, but they MUST inherit from Shaf::Generator::Base
. Generators also inherits the following instance methods that may help generating files processed through erb:
template_dir
read_template(file, directory = nil)
render(template, locals = {})
write_output(file, content)
Example:
class FooServiceGenerator < Shaf::Generator::Base
identifier :foo
def template_dir
"generator_templates"
end
def call
content = render("foo_service", {some_variables: "used_in_template"})
write_output("api/services/foo_service.rb", content)
end
end
This would require the file generator_templates/foo_service.erb
to exist in the project root. Executing shaf generate foo
would then read that template, process it through erb (utilizing any local variables given to render
) and then create the output file api/services/foo_service.rb
.
Shaf parses request payloads using a Parser. It ships with two parsers - one for form data and one for json payloads. The former handles the mediatypes "application/x-www-form-urlencoded" and "multipart/form-data". The latter handles json formatted payloads, e.g. "application/json", and "application/foobar+json".
All Parsers should inherit from Shaf::Parser::Base
and must either call ::mime_type(key, mime_type)
(where key
is Symbol
and mime_type
is a String
) or implement ::can_handle?(request)
(where request
is a Sinatra::Request
instance). Parsers have attr_reader
s for request
and body
(the input String). All Parsers must also respond to #call
and the value returned from #call
will be available in controllers through the payload
helper. An example Parser that handles xml could look like this:
require 'active_support/core_ext/hash'
class XmlParser < Shaf::Parser::Base
mime_type :xml, 'application/xml'
def call
Hash.from_xml(body)
end
end
When #respond_with
is used, the serialization is delegated to the best matching Responder
. Each Responder
manages a specific media type. Thus the "best" Responder
is the one that best matches the request's Accept
header.
Shaf ships with three responders. They support the mediatypes application/hal+json
, application/problem+json
and text/html
. (If the Accept
header does not match any of those, the default response format will be application/hal+json
).
Each responder is a subclass of Shaf::Responder::Base
. To add more responders, simply define new subclasses of Shaf::Responder::Base
.
All responders must implement #body
and they must call ::mime_type(key, mime_type)
(where key
is Symbol
and mime_type
is a String
).
#body
must return the serialized response.
Responders are instantiated with new(controller, resource, **options)
and they have attr_reader
s for each of those arguments.
Responders may override ::can_handle?(resource)
, which is used to decide whether or not a responder is able to process a given object.
Say that you would like to be able to return Siren payloads. And you happen to have a MyCustomSirenSerializer
class that can turn any object into a proper siren payload. Then adding a Siren responder would look like this.
class SirenResponder < Shaf::Responder::Base
mime_type :siren, 'application/vnd.siren+json'
def body
MyCustomSirenSerializer.call(resource)
end
end
Then, when your controller actions are using respond_with some_resource_object
. Your client's will be able to choose if they would like the response to be formatted as application/hal+json
or application/vnd.siren+json
(by setting the Accept
header correspondingly).
Authentication with HTTP works by using the Authorization header. It's value is made up of two parts - a scheme and credentials.
All authenticators must be subclasses of Shaf::Authenticator::Base
TODO