build status coverage report gem version documentation coverage

ATD

ATD

ATD is a new web development backend framework for Ruby. It is built on top of rack, and is meant to be small easy and light. That is why it provides a very minimalist way of writing your backend. It treats a request as a request, without splitting it based on the HTTP method.

Installation

Add this line to your application's Gemfile:

gem 'atd', :git => 'https://gitlab.com/izwick-schachter/atd.git'

Or, (not recommended) you can use only the rubygems version, which you can use with:

gem 'atd'

And then execute:

$ bundle

Or install it from the repository with:

$ git clone https://gitlab.com/izwick-schachter/atd.git
$ cd atd
$ bundle exec rake install

This will also allow you to use the current development version (USE WITH CAUTION), and to use that you can git checkout development and then bundle exec rake install.

Setup

Setup is as easy as require "atd" at the top of whatever file you need it in

Routing

Basic Routing

On the lowest level it can be used serve files or any strings when a path is requested (and optionally when a specific HTTP method is used). Here is a basic route that will serve Hello World when / is sent any HTTP request (GET, POST, PUT, PATCH, DELETE):

request "/", "Hello World"

The request method is also aliased to req and r for better code readability if you prefer. Here are 3 equivalent routes:

request "/", "Hello World"
req "/", "Hello World"
r "/", "Hello World"

For the purposes of this README, we will only use the request syntax, for improved readability, but anywhere in the code these are interchangeable, because they all return an ATD::Route object.

You could use the same basic routing syntax to return Hello World for a HTTP GET request to /:

request.get "/", "Hello World"

Or you could use a simpler (but equivalent) syntax:

get "/", "Hello World"

For the purposes of this README we will only use request.get and not get because we believe it adds clarity.

You can also have route respond to more than one HTTP verb with get.post.put.patch:

request.get.post.put.patch "/", "Hello World"

This will respond to GET, POST, PUT, and PATCH with Hello World, but will repond to DELETE with a 404 status code.

The get.post.put.patch is not recommended for readability. Instead use the following syntax:

request "/", "Hello World", respond_to: [:get, :post, :put, :patch]

Or, a simpler way would be with :ignore:

request "/", "Hello World", ignore: :delete

Serving Files

All files that you want to serve must be in the assets directory if they are, then it is simple to just create a route, and put the filename as the output. For example this will serve assets/index.html:

request "/", "index.html"

There are certain modifications which can be made to files between the route being requested and the files being served. That information can be found in the compilation section.

Advanced Routing

DefaultApp

DefaultApp is just another app which extends ATD::App, it is the app which doesn't have to be written to in class DefaultApp, any routes that are created in main will be added to DefaultApp, well... by default! To find out more about Apps, you can go to the Apps section.

Inheritance

Whenever you create a route, it is given to an App Class (which inherits from ATD::App). This isn't apparent when you create a route in main, but even when you do that the route is added to DefaultApp. If you are using Apps, than when you create a route in the App Class, that route is given to that Class. When you start the server, it then creates an instance of the App Class and starts it as a rack app. But that is not what the purpose of Apps are.

The intention of Apps are to allow you to use one App Class as a template class, from which you can create many different apps. An App Class is not a rack app all by itself. Every instance of an App Class is a rack app. But the rack app doesn't actually start until start is called. This means that you can create an instance of an App Class (an App), and then you can modify it before starting it. So for example, you can have an app which is impacted by an instance variable:

class MyApp < ATD::App
  attr_accessor :my_name
  request "/", "Hi! This is my_name's App!" do
    @http[:output] = @http[:output].gsub("my_name", @my_name)
  end
end

Which you can then create an instance of and modify the instance variable:

app = MyApp.new
app.my_name = "Fredrick"
app.start

Then when you try to access the website, it will respond to / with Hi! This is Fredrick's App!.

Blocks

You can also add blocks to the basic routing methods to execute code when they are reached, and customize the response to the incoming request. Here is an example that will return the HTTP method used to request /:

request "/" do
  @http[:output] = @http[:method]
end

This example uses the @http variable which is provided to the route when called. @http[:method] is the http verb used by the request, and @http[:output] is what the route will return. If you have already defined a return value, @http[:output] will already be set to it. For example:

request "/", "The HTTP Verb is: " do
  @http[:output] += @http[:method]
end

Will return The HTTP Verb is: get/post/put/patch/delete, depending on the http verb used in the request. It will also pre-parse a file. So if you have added an HTML file with request "/", "file.html", @http[:output] will be set to the contents of that file.

You could also run some complex method created outside the route. This route is called in the same scope as any block you were to declare. Here is an example:

request "/", "I'm computing a complex function" do
  complex_function
end
@http

The @http instance variable can be used to work with the http request the the route is parsing. Here are some ways you can use the @http variable:

@http[:status_code] = 200 # By default the status code is 200. You can manipulate this.
@http[:headers] = {} # By defualt there are no headers. You can add whatever you want.
@http[:method] = env["REQUEST_METHOD"] # Writing to this does nothing. Just a way for you to see the request method.
@http[:output] # This is set to the output you give in the args for request
@http[:request] # The associated Rack::Request for the request.

While you can use @http[:status_code] to change the status code, you can also set a status code with r "/", status_code: 200. That status code must be >= 100, as per the Rack Specification.

Controllers

ATD also had the capabilities to use controllers. A controller is an object which responds to controller methods, usually a module with extend self. Here is an example of an app using controllers:

module MyController
  def test_route
    return "You've reached test_route"
  end
end

request "/", "MyController#test_route" #=> "You've reached test_route"

Apps

ATD also allows you to use different "apps". Each app is independent of the others and lives is a class with the same name as the app. A file can have any number of apps, each of which can have it's own settings, files, and routes. By default, adding routes to main will make them a part of the app DefaultApp, which will work fine if you only need one app.

App Creation

To create an app you can use ATD.new("AppName"). It is important to note that ATD.new is not a constructor, although I will refer to it as one. It simply behaves like one because you can use it to "construct" a new app. The app creation process creates a new class which you can open anywhere in your app file, with the name you pass. The name must #respond_to?(:to_sym), and must be a valid class name. You must call the constructor before you begin adding routes to the class, or open the class at all.

You can also use the more intuitive way to create an app, which would be by declaring a class which extends ATD::App, like so:

class MyAppClass < ATD::App
  # All of my routes, code, etc.
end

Starting the App

There are two basic ways to start the app. You can start it by calling AppName.new.start or more simply AppName.start which will create an instance and call start on it, or you can use the more common syntax. For DefaultApp that would be:

request "/", "Hello World!"
start

And for MyApp that would be:

class MyApp
  request "/", "Hello World!"
  start
end

App Routing

To add routes to an app, you simply must call the standard routing method inside the app class. For example, to create and add routes to an app called Name I would use the following code:

class Name < ATD::App
  request "/", "Hello World"
end

Logging

Currently there is no specified logging interface. Just use puts or p to log to STDOUT.

Compilation

ATD will take your views and compress them for you so that your information can be transmitted more quickly. There are two different types of compilation, precompilation which occurs when the server is started, and compilation, which will live compile your code. While there are a few compilers and precompilers which will come with ATD most are user defined. To define compiler and precompiler methods simply add them like so:

module ATD::Compilation::Precomiler
  def extension(file, *opts) # This will work with any file that has .extension
    # File is the contents of the file being compiled
    # Whatever you return is what the file will be compiled to
    file
  end
end

module ATD::Compilation::Compiler
  # Same thing here
  def extension(file, *opts) # This will work with any file that has .extension
    file
  end
end

The compilation works by going through the file extensions from last to first and running the compilations for each extension in that order. For example file.html.erb will first be compiled by the ERB compiler, then the output of the ERB compiler will be compiled by the HTML compiler.

Design Paradigms

ATD is designed to fit into many different design paradigms, and to allow each person to adopt their own styles while leaving code readable to everyone. Do do this, the code was left fairly unstructured, but here are a few examples from well known frameworks.

Documentation

You can find the YARD docs at http://izwick-schachter.gitlab.io/atd/YARD/.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake test 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 GitLab at https://gitlab.com/izwick-schachter/atd/issues. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.