Skip to content

LeakyBucket/charlotte

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Charlotte Gitter chat

These are pretty sparse for now but should get better soon.

What?

Charlotte is a Web Framework written in Elixir. It takes after both Rails and Sinatra in some ways. In other ways Charlotte is a different animal entirely.

Why?

That's a good question. When I first started playing with Elixir there weren't a ton of frameworks out there. I decided that as part of learning the language I'd build one.

Short How

The quickest way to get started is to add Charlotte as a dependency to your project.

  { :charlotte, "~> 0.3.0" }

Once you have added and installed the dependency you are good to go.

There are a few different environment variables that Charlotte expects. These can be set outside the application or defaults can be given to Charlotte.start/2

  default_config = HashDict.new

  Charlotte.start [], [default_config: build_config(default_config)]

  def build_config(default_config) do
    default_config |>
      HashDict.put("CHARLOTTE_CONTROLLER_PATH", __DIR__ <> "/support/controllers") |>
      HashDict.put("CHARLOTTE_VIEW_PATH", __DIR__ <> "/support/views") |>
      HashDict.put("CHARLOTTE_ASSET_PATH", __DIR__ <> "/support/assets") |>
      HashDict.put("CHARLOTTE_HOST", "localhost") |>
      HashDict.put("CHARLOTTE_PROTOCOL", "tcp") |>
      HashDict.put("CHARLOTTE_ACCEPTORS", "100") |>
      HashDict.put("CHARLOTTE_COMPRESS", "false") |>
      HashDict.put("CHARLOTTE_PORT", "8000")
  end

Long How

Routes

Routing in Charlotte is a bit different than most other frameworks. Routes are actually a concern of the controller.

  defmodule MyController do
    use Charlotte.Handlers.HTTP

    def routes do
      [
        {"/", :root}
      ]
    end

    def root(verb, params, conn) do
      ...
    end
  end

Each controller defines the routes that it is responsible for. Charlotte expects this to be in the form of a list of {path, action} tuples. The path should be a binary/string with the actual path that should be matched.

You can provide Cowboy-style bindings in this path declaration.

  def routes do
    [
      {"/", :root},
      {"/path/with/:variable", :variable_path}
    ]
  end

These bindings will be present in the parameters passed to the action. In the example above a request to /path/with/value would be handled by the variable_path action and in that action params[:variable] would return "value".

The action should always be an atom with the same name as the function to be called when that path is requested. That function is assumed to exist in the module that defines the route.

Controllers

Organization

A Charlotte controller is simply a module that exists in the CHARLOTTE_CONTROLLER_PATH directory.

  # Assuming CHARLOTTE_CONTROLLER_PATH environment var is set to "/var/www/app/controllers"

  $ ls /var/www/app/controllers
    users.ex
    fees.ex
    locations.ex

In the above example Charlotte would assume users.ex, fees.ex and locations.ex define controller modules.

Content

In order for a controller module to be useful for Charlotte it needs to do the following:

  • Implement a routes function as described above
  • Implement the appropriate Cowboy callbacks for the protocol it supports
  • Handle view rendering and formatting and sending the response to the client

It is possible to implement the last two requirements yourself or you can simply use the appropriate Charlotte Handler.

  defmodule MyController do
    use Charlotte.Handlers.HTTP

    ...
  end

The HTTP Handler will define the required callbacks for HTTP 1.1 in Cowboy as well as a few helpers for generating a response: render/3 redirect/2 forbidden/1

Responding

The render function takes 3 arguments:

  • status
  • bindings
  • conn

The status is the integer status code that should accompany the response. The default value is 200.

The bindings should be a key: value list for the view. The keys should map to variable names in your view.

The conn is the conn record that was passed to your Controller action. This is used in building and sending the response.

The redirect function takes 2 arguments:

  • status
  • conn

The status is the integer status code that should accompany the response. The default is 302.

The conn is the conn record that was passed to your Controller action. This is used in building and sending the response.

The forbidden function takes one argument:

  • conn

The conn is the conn record that was passed to your Controller action. This is used in building and sending the response.

Views

Organization

Views in Charlotte are organized similarly to Views in Rails. The view files for a controller are expected to be in a subdirectory of the CHARLOTTE_VIEW_PATH with the same name as the controller module that uses the view. The actual view file is expected to have the same name as the controller action that renders it.

  # Assuming a CHARLOTTE_VIEW_PATH of "/var/www/app/views"

  $ ls -R /var/www/app/views
    ./users:
      show.ex
      new.ex
    ./fees:
      list.ex
    ./locations:
      new.ex

The above structure would result in the following code being generated:

  • Charlotte.Views.Users
    • show/1
    • new/1
  • Charlotte.Views.Fees
    • list/1
  • Charlotte.Views.Locations
    • new/1

These are public functions and can be called from any where at any time. However the render function which is injected by the HTTP Handler will look them up and call the appropriate function.

Content

Views are rendered by EEx. Any assigned variables should be preceeded by an @

  Your name is <%= @name %>

When you are running your application in development views will be read from the file each time they are called. This means you don't need to restart the application to get changes to existing views. However Charlotte doesn't pick up new view files unless it is restarted. This is due to the way the views are structured when compiled.

There are plans to build a default URL that when requested will cause a recompile of all Views.

Charlotte currently doesn't have any helpers for link generation. This is planned for the future.

Configuration

A brief word on configuration. Charlotte is built to use a 12 Factor approach to configuration.

With this in mind the framework it's self expects a few configuration variables to be present:

  • CHARLOTTE_CONTROLLER_PATH
  • CHARLOTTE_VIEW_PATH
  • CHARLOTTE_ASSET_PATH
  • CHARLOTTE_HOST
  • CHARLOTTE_PROTOCOL
  • CHARLOTTE_ACCEPTORS
  • CHARLOTTE_COMPRESS
  • CHARLOTTE_PORT

Charlotte uses EnvConf to manage configuration settings, this also allows you to use the same in your application without any additional setup.

It is possible to send defaults when starting Charlotte from your application module:

  default_config = HashDict.new

  Charlotte.start [], [default_config: build_config(default_config)]

  def build_config(default_config) do
    default_config |>
      HashDict.put("CHARLOTTE_CONTROLLER_PATH", __DIR__ <> "/support/controllers") |>
      HashDict.put("CHARLOTTE_VIEW_PATH", __DIR__ <> "/support/views") |>
      HashDict.put("CHARLOTTE_ASSET_PATH", __DIR__ <> "/support/assets") |>
      HashDict.put("CHARLOTTE_HOST", "localhost") |>
      HashDict.put("CHARLOTTE_PROTOCOL", "tcp") |>
      HashDict.put("CHARLOTTE_ACCEPTORS", "100") |>
      HashDict.put("CHARLOTTE_COMPRESS", "false") |>
      HashDict.put("CHARLOTTE_PORT", "8000")
  end

You can store/build these defaults however you wish. All Charlotte cares about is that they are given as a HashDict.

EnvConf will not overwrite any values that are already present in the environment so it is safe to leave dev defaults in your code. They won't overwrite configuration set in the environment directly on a production system.