├── priv ├── empty ├── test_session_config │ ├── mock.config │ ├── cache.config │ └── mnesia.config ├── clean_for_release.sh └── elixir │ └── boss │ ├── flash.ex │ ├── repo.ex │ ├── session.ex │ └── web_controller.ex ├── test ├── good.dtl ├── boss_elixir_compiler_test.erl ├── bad.dtl ├── std.hrl ├── boss_assert_test.erl ├── boss_files_test.erl ├── boss_mq_test.erl ├── boss_web_controller_handle_request_test.erl ├── boss_controller_compiler_test.erl ├── boss_web_controller_test.erl └── boss_load_test.erl ├── skel ├── priv │ ├── migrations │ │ └── .gitignore │ ├── static │ │ ├── favicon.ico │ │ ├── chicago-boss.png │ │ └── edoc │ │ │ ├── erlang.png │ │ │ └── chicagoboss.png │ ├── boss.routes │ └── init │ │ └── news.erl ├── rebar ├── src │ ├── mail │ │ ├── view │ │ │ ├── test_message.txt │ │ │ └── test_message.html │ │ ├── incoming_mail_controller.erl │ │ └── outgoing_mail_controller.erl │ ├── project.app.src │ └── view │ │ └── lib │ │ ├── filter_modules │ │ └── custom_filters.erl │ │ ├── tag_modules │ │ └── custom_tags.erl │ │ └── README ├── rebar.cmd ├── init-dev.sh ├── start-server.bat ├── rebar.config └── init.sh ├── rebar ├── doc-src ├── api-request.html ├── chicago-boss.png ├── trivial_boss_record.erl ├── api-mq.html ├── api-session.html ├── api.html ├── api-view.html ├── boss.css ├── api-test.html ├── api-websocket.html └── api-news.html ├── rebar.cmd ├── .gitignore ├── src ├── overview.edoc ├── boss │ ├── chicago_boss_types.hrl │ ├── boss_session_test.erl │ ├── boss_model_manager_adapter.erl │ ├── compiler_adapters │ │ ├── boss_compiler_adapter_erlang.erl │ │ ├── boss_compiler_adapter_elixir.erl │ │ └── boss_compiler_adapter_lfe.erl │ ├── template_adapters │ │ ├── boss_template_adapter_jade.erl │ │ ├── boss_template_adapter_eex.erl │ │ └── boss_template_adapter_erlydtl.erl │ ├── boss_app.erl │ ├── types.erl │ ├── boss_session_adapter.erl │ ├── boss_router_sup.erl │ ├── boss_mq_sup.erl │ ├── boss_service_handler.erl │ ├── boss_mail_sup.erl │ ├── boss_session_mock_sup.erl │ ├── boss_translator_sup.erl │ ├── mq_adapters │ │ └── boss_mq_adapter_tinymq.erl │ ├── boss_html_errors_template.dtl │ ├── filters │ │ ├── boss_lang_filter.erl │ │ ├── boss_cache_vars_filter.erl │ │ └── boss_cache_page_filter.erl │ ├── boss_html_error_template.dtl │ ├── boss_session_sup.erl │ ├── boss.erl │ ├── model_manager_adapters │ │ ├── boss_model_manager_ecto.erl │ │ └── boss_model_manager_boss_db.erl │ ├── boss_mail_controller.erl │ ├── boss_web.hrl │ ├── boss_model_manager.erl │ ├── boss_translator.erl │ ├── boss_migrate.erl │ ├── boss_flash.erl │ ├── boss_web.erl │ ├── boss_html_doc_template.dtl │ ├── boss_mail_driver_smtp.erl │ ├── boss_elixir_compiler.erl │ ├── boss_env.erl │ ├── session_adapters │ │ ├── boss_session_adapter_mock.erl │ │ ├── boss_session_adapter_cache.erl │ │ └── boss_session_adapter_mnesia.erl │ ├── boss_mq_controller.erl │ ├── boss_sup.erl │ ├── boss_controller_lib.erl │ ├── boss_web_controller_cowboy.erl │ ├── boss_translator_controller.erl │ ├── boss_mochicow_handler.erl │ ├── boss_json.erl │ ├── boss_erlydtl_tags.erl │ ├── boss_router.erl │ ├── boss_session.erl │ ├── boss_service_sup.erl │ ├── boss_session_controller.erl │ ├── boss_mail_driver_mock.erl │ ├── boss_web_controller_util.erl │ ├── boss_mq.erl │ ├── boss_session_mock_controller.erl │ └── boss_session_test_app.erl ├── tests │ └── ct │ │ └── make_app_SUITE.erl ├── boss.app.src └── boss_test │ └── boss_test.erl ├── ebin ├── bson.app ├── boss_db_test.app ├── mongodb.app ├── boss_session_test.app ├── boss_translator.app ├── erlydtl.app └── medici.app ├── .travis.yml ├── dialyzer.sh ├── include ├── simple_bridge.hrl ├── httpd_r12b5.hrl └── yaws_api.hrl ├── mix.exs ├── make-plt.sh ├── LICENSE ├── README_LFE.md ├── travis-dialyzer.sh ├── README_TESTS.md ├── ct └── boss_mq_SUITE.erl ├── windows-make.bat ├── CODING_STANDARDS.md ├── bin └── boss ├── skel.template ├── Makefile ├── rebar.config ├── README_ELIXIR.md └── README_DATABASE.md /priv/empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/good.dtl: -------------------------------------------------------------------------------- 1 | {{test}} 2 | -------------------------------------------------------------------------------- /skel/priv/migrations/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/rebar -------------------------------------------------------------------------------- /skel/rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/skel/rebar -------------------------------------------------------------------------------- /skel/src/mail/view/test_message.txt: -------------------------------------------------------------------------------- 1 | This is a plain-text email sent to {{ address }}. 2 | -------------------------------------------------------------------------------- /doc-src/api-request.html: -------------------------------------------------------------------------------- 1 | {% extends "api.html" %} 2 | {% block api_content %} 3 | {% endblock %} 4 | -------------------------------------------------------------------------------- /rebar.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set rebarscript=%~f0 4 | escript.exe "%rebarscript:.cmd=%" %* 5 | -------------------------------------------------------------------------------- /skel/rebar.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set rebarscript=%~f0 4 | escript.exe "%rebarscript:.cmd=%" %* 5 | -------------------------------------------------------------------------------- /doc-src/chicago-boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/doc-src/chicago-boss.png -------------------------------------------------------------------------------- /skel/priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/skel/priv/static/favicon.ico -------------------------------------------------------------------------------- /doc-src/trivial_boss_record.erl: -------------------------------------------------------------------------------- 1 | -module(boss_record, [Id]). 2 | -compile(export_all). 3 | -counter(trivial_counter). 4 | -------------------------------------------------------------------------------- /skel/priv/static/chicago-boss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/skel/priv/static/chicago-boss.png -------------------------------------------------------------------------------- /skel/priv/static/edoc/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/skel/priv/static/edoc/erlang.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/*.beam 2 | ebin/boss.app 3 | *.beam 4 | deps 5 | erl_crash.dump 6 | *~ 7 | #*# 8 | .*# 9 | *# 10 | .eunit 11 | -------------------------------------------------------------------------------- /skel/priv/static/edoc/chicagoboss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requirement/ChicagoBoss/master/skel/priv/static/edoc/chicagoboss.png -------------------------------------------------------------------------------- /src/overview.edoc: -------------------------------------------------------------------------------- 1 | @author Evan Miller 2 | @doc Chicago Boss is an MVC framework for Erlang. 3 | @see boss_record_compiler 4 | @see boss_db 5 | -------------------------------------------------------------------------------- /src/boss/chicago_boss_types.hrl: -------------------------------------------------------------------------------- 1 | %% NO Executable code, type declarations only 2 | 3 | -type execution_mode() :: 'development' | 'production'. 4 | -------------------------------------------------------------------------------- /test/boss_elixir_compiler_test.erl: -------------------------------------------------------------------------------- 1 | -module(boss_elixir_compiler_test). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -include("../src/boss/boss_web.hrl"). 4 | -------------------------------------------------------------------------------- /test/bad.dtl: -------------------------------------------------------------------------------- 1 |
This is an HTML email to {{ address }}.
2 | 3 |Click here to visit the home page.
4 | -------------------------------------------------------------------------------- /priv/test_session_config/mock.config: -------------------------------------------------------------------------------- 1 | [{boss_session_test, 2 | [ 3 | {session_adapter, mock}, 4 | {session_key, "_boss_session"}, 5 | {session_exp_time, 525600} 6 | ] 7 | }]. 8 | -------------------------------------------------------------------------------- /priv/test_session_config/cache.config: -------------------------------------------------------------------------------- 1 | [{boss_session_test, 2 | [ 3 | {session_adapter, cache}, 4 | {session_key, "_boss_session"}, 5 | {session_exp_time, 525600} 6 | ] 7 | }]. 8 | -------------------------------------------------------------------------------- /priv/test_session_config/mnesia.config: -------------------------------------------------------------------------------- 1 | [{boss_session_test, 2 | [ 3 | {session_adapter, mnesia}, 4 | {session_key, "_boss_session"}, 5 | {session_exp_time, 525600} 6 | ] 7 | }]. 8 | -------------------------------------------------------------------------------- /test/std.hrl: -------------------------------------------------------------------------------- 1 | -include_lib("proper/include/proper.hrl"). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -include("../src/boss/boss_web.hrl"). 4 | -import(prop_runner,[gen/2]). 5 | 6 | -compile(export_all). 7 | -------------------------------------------------------------------------------- /ebin/bson.app: -------------------------------------------------------------------------------- 1 | {application, bson, 2 | [{description, "BSON are JSON-like objects with a standard binary serialization. See bsonspec.org."}, 3 | {vsn, "0"}, 4 | {modules, [bson, bson_binary, bson_tests]} 5 | ]}. 6 | -------------------------------------------------------------------------------- /skel/start-server.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | CALL rebar.cmd get-deps 3 | CALL rebar.cmd compile 4 | FOR /F "tokens=*" %%i in ('"rebar.cmd boss c=start_dev_cmd ^| findstr werl"') do set myvar=%%i 5 | START "Erlang Window" %myvar% 6 | -------------------------------------------------------------------------------- /skel/src/mail/incoming_mail_controller.erl: -------------------------------------------------------------------------------- 1 | -module({{appid}}_incoming_mail_controller). 2 | -compile(export_all). 3 | 4 | authorize_(User, DomainName, IPAddress) -> 5 | true. 6 | 7 | % post(FromAddress, Message) -> 8 | % ok. 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - R16B02 4 | - R16B01 5 | 6 | script: 7 | - rebar get-deps 8 | - rebar compile 9 | - rebar eunit -v skip_deps=true 10 | - mkdir plt 11 | - ./travis-dialyzer.sh 12 | notifications: 13 | email: false -------------------------------------------------------------------------------- /skel/src/project.app.src: -------------------------------------------------------------------------------- 1 | {application, {{appid}}, [ 2 | {description, "My Awesome Web Framework"}, 3 | {vsn, "0.0.1"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib, crypto]}, 7 | {env, []} 8 | ]}. 9 | -------------------------------------------------------------------------------- /src/boss/boss_session_test.erl: -------------------------------------------------------------------------------- 1 | -module(boss_session_test). 2 | -export([start/0, start/1, stop/0]). 3 | 4 | start() -> 5 | start([]). 6 | 7 | start([]) -> 8 | application:start(boss_session_test). 9 | 10 | stop() -> 11 | application:stop(boss_session_test). 12 | -------------------------------------------------------------------------------- /skel/rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {boss, ".*", {git, "git://github.com/ChicagoBoss/ChicagoBoss.git", {tag, "v0.8.10"}}} 3 | ]}. 4 | {plugin_dir, ["priv/rebar"]}. 5 | {plugins, [boss_plugin]}. 6 | {eunit_compile_opts, [{src_dirs, ["src/test"]}]}. 7 | {lib_dirs, ["{{src}}/deps/elixir/lib"]}. 8 | -------------------------------------------------------------------------------- /skel/src/view/lib/filter_modules/custom_filters.erl: -------------------------------------------------------------------------------- 1 | -module({{appid}}_custom_filters). 2 | -compile(export_all). 3 | 4 | % put custom filters in here, e.g. 5 | % 6 | % my_reverse(Value) -> 7 | % lists:reverse(binary_to_list(Value)). 8 | % 9 | % "foo"|my_reverse => "oof" 10 | -------------------------------------------------------------------------------- /dialyzer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PATH=$PATH:/usr/local/bin:/usr/bin 4 | PLT=plt/cb.plt 5 | 6 | echo "" 7 | dialyzer ebin/ \ 8 | -Werror_handling \ 9 | -Wno_undefined_callbacks \ 10 | -Wrace_conditions \ 11 | --fullpath \ 12 | --plt $PLT # -Wunmatched_returns -n 13 | # 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/tests/ct/make_app_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(make_app_SUITE). 2 | -include_lib("common_test/include/ct.hrl"). 3 | -export([all/0]). 4 | -export([make_app/1]). 5 | 6 | all() -> [make_app]. 7 | 8 | %% This would be a make_app/1 not a make_app/0 9 | make_app(_Config) -> 10 | os:cmd("cd ../../ && make app PROJECT=testproj"). 11 | 12 | -------------------------------------------------------------------------------- /ebin/boss_db_test.app: -------------------------------------------------------------------------------- 1 | {application, boss_db_test, 2 | [{description, "Chicago Boss DB Testing Application"}, 3 | {vsn, "0.1"}, 4 | {modules, [ 5 | boss_db_test, 6 | boss_db_test_app 7 | ]}, 8 | {registered, []}, 9 | {mod, {boss_db_test_app, []}}, 10 | {env, []}, 11 | {applications, [kernel, stdlib]}]}. 12 | -------------------------------------------------------------------------------- /ebin/mongodb.app: -------------------------------------------------------------------------------- 1 | {application, mongodb, 2 | [{description, "Client interface to MongoDB, also known as the driver. See www.mongodb.org"}, 3 | {vsn, "0.0"}, 4 | {modules, [mongodb_app, mongo, mongo_protocol, mongo_connect, mongo_query, mongo_cursor, mvar, mongodb_tests]}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib]}, 7 | {mod, {mongodb_app, []}} 8 | ]}. -------------------------------------------------------------------------------- /skel/src/view/lib/tag_modules/custom_tags.erl: -------------------------------------------------------------------------------- 1 | -module({{appid}}_custom_tags). 2 | -compile(export_all). 3 | 4 | % put custom tags in here, e.g. 5 | % 6 | % reverse(Variables, Options) -> 7 | % lists:reverse(binary_to_list(proplists:get_value(string, Variables))). 8 | % 9 | % {% reverse string="hello" %} => "olleh" 10 | % 11 | % Variables are the passed-in vars in your template 12 | -------------------------------------------------------------------------------- /ebin/boss_session_test.app: -------------------------------------------------------------------------------- 1 | {application, boss_session_test, 2 | [{description, "Chicago Boss Session Testing Application"}, 3 | {vsn, "0.1"}, 4 | {modules, [ 5 | boss_session_test, 6 | boss_session_test_app 7 | ]}, 8 | {registered, []}, 9 | {mod, {boss_session_test_app, []}}, 10 | {env, []}, 11 | {applications, [kernel, stdlib]}]}. 12 | -------------------------------------------------------------------------------- /include/simple_bridge.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(debug_print). 2 | -define(debug_print, true). 3 | -define(PRINT(Var), error_logger:info_msg("DEBUG: ~p:~p~n~p~n ~p~n", [?MODULE, ?LINE, ??Var, Var])). 4 | -endif. 5 | 6 | -record(cookie, { name, value, path="/", minutes_to_live=20 }). 7 | -record(header, { name, value }). 8 | -record(response, { statuscode=200, headers=[], cookies=[], data=[] }). 9 | -record(uploaded_file, { original_name, temp_file, size }). -------------------------------------------------------------------------------- /ebin/boss_translator.app: -------------------------------------------------------------------------------- 1 | {application, boss_translator, 2 | [{description, "BossTranslator I18n engine"}, 3 | {vsn, "0.01"}, 4 | {modules, [ 5 | boss_translator, 6 | boss_translator_app, 7 | boss_translator_controller, 8 | boss_translator_sup 9 | ]}, 10 | {registered, []}, 11 | {mod, {boss_translator_app, []}}, 12 | {env, []}, 13 | {applications, [kernel, stdlib]}]}. 14 | -------------------------------------------------------------------------------- /src/boss/boss_model_manager_adapter.erl: -------------------------------------------------------------------------------- 1 | -module (boss_model_manager_adapter). 2 | -export ([behaviour_info/1]). 3 | 4 | %% @spec behaviour_info( atom() ) -> [ {Function::atom(), Arity::integer()} ] | undefined 5 | behaviour_info (callbacks) -> 6 | [ {start, 0}, {stop, 0}, {compile, 2}, {edoc_module, 2}, 7 | {is_model_instance, 2}, {dummy_instance, 1}, 8 | {to_json, 1} %, {from_json, 1} 9 | ]; 10 | behaviour_info (_Other) -> undefined. 11 | -------------------------------------------------------------------------------- /skel/src/mail/outgoing_mail_controller.erl: -------------------------------------------------------------------------------- 1 | -module({{appid}}_outgoing_mail_controller). 2 | -compile(export_all). 3 | 4 | %% See http://www.chicagoboss.org/api-mail-controller.html for what should go in here 5 | 6 | test_message(FromAddress, ToAddress, Subject) -> 7 | Headers = [ 8 | {"Subject", Subject}, 9 | {"To", ToAddress}, 10 | {"From", FromAddress} 11 | ], 12 | {ok, FromAddress, ToAddress, Headers, [{address, ToAddress}]}. 13 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Boss.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ app: :boss, 6 | version: "0.8.7", 7 | deps: deps(Mix.env), 8 | name: "Chicago Boss", 9 | source_url: "https://github.com/evanmiller/ChicagoBoss", 10 | elixir: "~> 0.10.3" ] 11 | end 12 | 13 | def application do 14 | [] 15 | end 16 | 17 | defp deps(_) do 18 | [ { :ecto, github: "elixir-lang/ecto" }, 19 | { :postgrex, github: "ericmj/postgrex" } ] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/boss.app.src: -------------------------------------------------------------------------------- 1 | {application, boss, 2 | [ 3 | {description, "Chicago Boss web framework, now serving three flavors of Comet"}, 4 | {vsn, "0.8.7"}, 5 | {registered, [ 6 | boss_mq, boss_mq_sup, 7 | boss_session, boss_session_sup, 8 | boss_session_mock, boss_session_mock_sup 9 | ]}, 10 | {modules, []}, 11 | {applications, [ 12 | kernel, 13 | stdlib, 14 | crypto 15 | ]}, 16 | {mod, {boss_app, []}}, 17 | {env, []} 18 | ]}. 19 | -------------------------------------------------------------------------------- /src/boss/compiler_adapters/boss_compiler_adapter_erlang.erl: -------------------------------------------------------------------------------- 1 | -module(boss_compiler_adapter_erlang). 2 | -compile(export_all). 3 | 4 | file_extensions() -> ["erl"]. 5 | 6 | controller_module(AppName, Controller) -> lists:concat([AppName, "_", Controller, "_controller"]). 7 | 8 | module_name_for_file(_AppName, File) -> filename:basename(File, ".erl"). 9 | 10 | compile_controller(File, Options) -> 11 | boss_controller_compiler:compile(File, Options). 12 | 13 | compile(File, Options) -> 14 | boss_compiler:compile(File, Options). 15 | -------------------------------------------------------------------------------- /priv/clean_for_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Trim the fat 4 | 5 | rm -rf deps/*/.git 6 | rm -rf deps/*/rebar 7 | rm -rf deps/erlydtl/tests 8 | rm -rf deps/mochicow/examples 9 | rm -rf deps/epgsql/test_data 10 | rm -rf deps/misultin/examples 11 | rm -rf deps/boss_db/priv/test_* 12 | rm -rf deps/cowboy/test 13 | rm -rf deps/cowboy/examples 14 | rm -rf deps/gen_smtp/testdata 15 | rm -rf deps/mochiweb/support 16 | rm -rf deps/mochiweb/examples 17 | rm -rf deps/pmod_transform/tests 18 | rm -rf deps/jaderl/tests 19 | sed -i "s/{vsn, git}/{vsn, \"0\"}/" deps/*/src/*.app.src 20 | -------------------------------------------------------------------------------- /src/boss/template_adapters/boss_template_adapter_jade.erl: -------------------------------------------------------------------------------- 1 | -module(boss_template_adapter_jade). 2 | -compile(export_all). 3 | 4 | file_extensions() -> ["jade"]. 5 | 6 | translatable_strings(_Module) -> []. 7 | 8 | source(_Module) -> "". 9 | 10 | dependencies(_Module) -> []. 11 | 12 | render(Module, Variables, Options) -> 13 | Module:render(Variables, Options). 14 | 15 | compile_file(ViewPath, Module, Options) -> 16 | OutDir = proplists:get_value(out_dir, Options), 17 | ok = jaderl:compile(ViewPath, Module, [{out_dir, OutDir}]), 18 | {ok, Module}. 19 | 20 | -------------------------------------------------------------------------------- /src/boss/boss_app.erl: -------------------------------------------------------------------------------- 1 | %% @author Evan MillerIn file {{ filename }}:
11 |{% now "D, j F Y g:i a" %}
20 | 21 | 22 | -------------------------------------------------------------------------------- /ebin/erlydtl.app: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | {application, erlydtl, 3 | [{description, "ErlyDTL implements most but not all of the Django Template Language"}, 4 | {vsn, "0.6.1"}, 5 | {modules, [ 6 | erlydtl, 7 | erlydtl_compiler, 8 | erlydtl_dateformat, 9 | erlydtl_dateformat_tests, 10 | erlydtl_deps, 11 | erlydtl_example_variable_storage, 12 | erlydtl_filters, 13 | erlydtl_functional_tests, 14 | erlydtl_parser, 15 | erlydtl_runtime, 16 | erlydtl_scanner, 17 | erlydtl_unittests 18 | ]}, 19 | {applications, [kernel, stdlib]}, 20 | {registered, []} 21 | ]}. 22 | -------------------------------------------------------------------------------- /src/boss/filters/boss_lang_filter.erl: -------------------------------------------------------------------------------- 1 | -module(boss_lang_filter). 2 | -export([config_default_value/0, config_key/0, before_filter/2, after_filter/3]). 3 | 4 | config_default_value() -> auto. 5 | config_key() -> lang. 6 | 7 | before_filter(auto, RequestContext) -> 8 | {ok, proplists:delete(language, RequestContext)}; 9 | before_filter(Language, RequestContext) -> 10 | {ok, proplists:delete(language, RequestContext) ++ [{language, Language}]}. 11 | 12 | after_filter({ok, Payload, Headers}, auto, _) -> 13 | {ok, Payload, Headers}; 14 | after_filter({ok, Payload, Headers}, Language, _) -> 15 | {ok, Payload, boss_web_controller:merge_headers(Headers, [{"Content-Language", Language}])}; 16 | after_filter(Other, _, _) -> Other. 17 | -------------------------------------------------------------------------------- /src/boss/boss_html_error_template.dtl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |{{ error }}
13 | {{ request }}
15 | {{ appinfo }}
17 | {% now "D, j F Y g:i a" %}
18 |BossMQ is an abstraction layer for channel-based messaging that can be used to implement real-time notifications (i.e. Comet). With BossMQ, any controller action can function as a long-polling endpoint simply by calling boss_mq:pull/2:
6 | receive_chat('GET', []) ->
7 | {ok, Timestamp, Messages} = boss_mq:pull("my-channel", now)
8 | {output, Messages}.
9 |
10 |
11 | The call to pull/2 blocks until a message arrives on "my-channel". Because of Erlang's lightweight process model, you usually don't need to worry if pull/2 takes a long time to complete.
To send a message to a channel, you call boss_mq:push/2:
Currently, only an in-memory message queue is supported, so all messaging must occur in the same CB cluster. Additional adapters will be added in the future to support more complex installations.
20 | 21 |{{ function.description_long }}
30 |This is EDoc autogenerated documentation for your models and web controllers. 11 | If you are looking for ChicagoBoss API, it could be found here. 12 | Wiki is here. 13 |
14 |{% now "D, j F Y g:i a" %}
37 |BossSession is a multiadapter session management. The following adapters are supported (specified in boss.config):
4 |cache - Store sessions in whatever cache system is configured. Requires cache_enable to be set to true
6 | mnesia - Mnesia. Requires the node to have a name.mock - In-memory sessions which uses ETS.BossSession is automatically started, so, by default an ETS based session support is provided.
11 | 12 |The current session ID is available to controllers as the second module parameter. To disable sessions, set "session_enable" to "false" in your boss.config file.
13 | 14 |BossFlash is a utility on top of boss_session, you can store messages between requests, automatically populated to the view as {{ "{{ boss_flash }}" }} and deleted from session after used.
{{ function.description_long }}
25 |Add flash messages in the controller like:
33 | 34 |Use the boss_flash var in the view, should render "notice - Flash Title - Flash Message":
39 |{{ function.description_long }}
54 |The Chicago Boss API is mostly stable, but still might change before 1.0.
14 |Chicago Boss liked Django's templating language so much, he decided to steal it. Template files go in your project's src/view/ folder (in subdirectories that correspond to the controller name), and will have access to the variables you pass from your Controller. The template file associated with the function foo_controller:bar will be src/view/foo/bar.html. (Template filenames can also use .txt, .dtl and .js file extensions.)
Note: if you use the the extends tag, the file path should be relative to your project's src/view/ directory.
Chicago Boss has support for the most common features of the Django Template Language, so many existing Django templates should work out of the box. Your templates will be compiled down to Erlang BEAM code using ErlyDTL to give you the fastest Erlang templates this side of Stockholm.
8 | 9 |100% of Django filters are supported. The only unsupported tag is csrf_token.
10 | 11 |If you use repeated logic or template snippets, you can put helper templates into your project's src/view/lib/tag_html directory. The variables appearing in helper templates must be supplied by the caller. For example, if you have a file called "src/view/lib/tag_html/my_custom_tag.html" which uses a variable called "foo", you can invoke it from other templates like:
13 |String literals or variables may be passed as arguments to custom tags.
17 |Another option for factoring out repeated logic is to use ErlyDTL's "include" tag. The difference between using an "include" tag and custom tags is that "include" will have access to all variables currently in the caller's scope, whereas custom tags only have access to the variables explicitly passed in. (In addition, custom tags are compiled only once for the entire project, but included files are compiled for each template that includes it.)
18 |For more complicated processing, you can create helper modules. Tag helper modules reside in src/view/lib/tag_modules, and export functions which take in a proplist of variables and return rendered HTML. Filter helper modules reside in src/view/lib/filter_modules, and export functions which take in a binary string and return a filter iolist. See those directories in your project for examples.
20 |The following variables are automatically passed to your views without you doing any work:
22 |_base_url - The value of the config setting "base_url" or blank if not set_before - The result of the authorization filter, if present_lang - The content language of the rendered view_session - Key/value pairs from the current session, if it existsTwo other template languages are supported on an experimental basis: Jade (provided by jaderl) and embedded Elixir.
31 |To use Jade, simply create templates with extension ".jade".
33 |To use embedded Elixir, create templates with extension ".eex", and prefix your variables with @.
Example Elixir:
36 |For more information on embedded Elixir, see the EEx docs.
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /src/boss/boss_mq.erl: -------------------------------------------------------------------------------- 1 | %% @doc Chicago Boss messaging abstraction 2 | 3 | -module(boss_mq). 4 | 5 | -export([start/0, start/1, stop/0]). 6 | 7 | -ifdef(TEST). 8 | -compile(export_all). 9 | -endif. 10 | 11 | -export([ 12 | pull/1, 13 | pull/2, 14 | pull/3, 15 | poll/1, 16 | poll/2, 17 | push/2, 18 | now/1]). 19 | -type channel() ::string(). 20 | -type mq_return() :: {ok, integer(), [_]}|{error,string()}. 21 | 22 | -spec start() -> any(). 23 | start() -> 24 | MQOptions = make_queue_options(), 25 | MQAdapter = get_mq_adapater(), 26 | MQOptions1 = [{adapter, list_to_atom("boss_mq_adapter_"++atom_to_list(MQAdapter))}|MQOptions], 27 | start(MQOptions1). 28 | 29 | -spec start(_) -> any(). 30 | start(Options) -> 31 | boss_mq_sup:start_link(Options). 32 | 33 | -spec stop() -> 'ok'. 34 | stop() -> 35 | ok. 36 | 37 | -spec get_mq_adapater() -> atom(). 38 | get_mq_adapater() -> 39 | case application:get_env(mq_adapter) of 40 | {ok, Val} -> Val; 41 | _ -> tinymq 42 | end. 43 | 44 | make_queue_options() -> 45 | lists:foldl(fun(OptName, Acc) -> 46 | case application:get_env(OptName) of 47 | {ok, Val} -> [{OptName, Val}|Acc]; 48 | _ -> Acc 49 | end 50 | end, 51 | [], 52 | [mq_port, mq_host, mq_max_age]). 53 | 54 | 55 | 56 | -spec pull(channel()) -> mq_return(). 57 | %% @doc Pull messages from the specified `Channel'. If none are in the queue, blocks 58 | %% until a message is pushed to the queue. 59 | pull(Channel) -> 60 | pull(Channel, undefined). 61 | 62 | -spec pull(channel(),undefined|non_neg_integer()) -> mq_return(). 63 | %% @doc Pull messages from the specified `Channel' after `Since' (a timestamp returned from a previous `pull'). 64 | %% If no such messages are in the queue, blocks until a message is pushed to the queue. 65 | pull(Channel, Timestamp) -> 66 | pull(Channel, Timestamp, infinity). 67 | 68 | -spec pull(channel(),undefined|integer()|{integer(), integer(), integer()},infinity|non_neg_integer()) -> mq_return(). 69 | %% @doc Pull messages from the specified `Channel' after `Since' (a timestamp returned from a previous `pull'). If no such messages 70 | %% are in the queue, blocks until a message is pushed to the queue, or until `Timeout' seconds have elapsed. 71 | pull(Channel, {MegaSecs, Secs, MicroSecs}, Timeout) -> 72 | pull(Channel, 1000 * 1000 * (1000 * 1000 * MegaSecs + Secs) + MicroSecs, Timeout); 73 | pull(Channel, Timestamp, Timeout) when is_list(Channel) -> 74 | TimeoutMs = convert_to_ms(Timeout), 75 | CallStatus = gen_server:call({global, ?MODULE}, {pull, Channel, Timestamp, self()}), 76 | pull_recieve(TimeoutMs, CallStatus). 77 | 78 | 79 | -spec pull_recieve('infinity' | non_neg_integer(),{ok, _}|any()) -> mq_return(). 80 | pull_recieve(TimeoutMs, {ok, PullTime}) -> 81 | receive 82 | {_From, NewTimestamp, Messages} -> 83 | {ok, NewTimestamp, Messages} 84 | after 85 | TimeoutMs -> 86 | {ok, PullTime, []} 87 | end; 88 | pull_recieve(_, Error) -> Error. 89 | 90 | 91 | -spec convert_to_ms('infinity' | non_neg_integer()) -> 'infinity' | non_neg_integer(). 92 | convert_to_ms(infinity) -> infinity; 93 | convert_to_ms(Timeout) -> 94 | Timeout * 1000. 95 | 96 | 97 | -spec poll(channel()) -> mq_return(). 98 | %% @doc Like `pull/1', but returns immediately if no messages are in the queue. 99 | poll(Channel) -> 100 | poll(Channel, last). 101 | 102 | -spec poll(channel(),last|integer()) -> mq_return(). 103 | %% @doc Like `pull/2', but returns immediately if no matching messages are in the queue. 104 | poll(Channel, Timestamp) when is_list(Channel) -> 105 | gen_server:call({global, ?MODULE}, {poll, Channel, Timestamp}). 106 | 107 | 108 | -spec push(channel(),_) -> {ok, non_neg_integer()}. 109 | %% @doc Pushes a message to the specified `Channel'. 110 | push(Channel, Message) when is_list(Channel) -> 111 | gen_server:call({global, ?MODULE}, {push, Channel, Message}). 112 | 113 | -spec now(channel()) -> non_neg_integer(). 114 | %% @doc Retrieves the current time for the server managing `Channel'. 115 | now(Channel) when is_list(Channel) -> 116 | gen_server:call({global, ?MODULE}, {now, Channel}). 117 | 118 | -------------------------------------------------------------------------------- /include/yaws_api.hrl: -------------------------------------------------------------------------------- 1 | %%%---------------------------------------------------------------------- 2 | %%% File : yaws_api.hrl 3 | %%% Author : Claes WikstromJump to: boss_web_test
4 | boss_assert
Chicago Boss ships with a unique functional test framework, where tests are structured as trees of continuations. All assertions are performed in callbacks, and additional tests are also performed in callbacks. For more information on the design of Boss's functional tests, see “Functional Tests As A Tree Of Continuations”.
6 | 7 |To create a test suite, create a module in the src/test/functional/ directory. Your test module should export a start/0 function, which should invoke a function from the boss_web_test module. This function will in turn invoke functions in the boss_assert module (to run assertions) and the boss_web_test module (to run further tests). A simple example of a test module with included start/0 function would be:
10 | -module(my_app_test). 11 | 12 | -export([start/0]). 13 | 14 | start() -> 15 | boss_web_test:get_request("/", [], 16 | [fun boss_assert:http_ok/1], []). 17 |18 | 19 |
The above test issues a GET request to the root URL, asserts that the response is 200 OK, and quits.
20 | 21 |When you are ready to run your test suite, type "./rebar boss c=test_functional" in your project directory.
22 | 23 | 24 |Functions in the boss_web_test issue HTTP requests to your web application (or check for emails sent by it). All functions in the boss_web_test module take the same two final arguments.
Second to last, Assertions is a list of funs that must return a tuple {Passed, ErrorMessage}, where:
28 |
Passed - a boolean indicating whether the test passedErrorMessage - an error message to be displayed if the test failedEach fun in Assertions takes a single argument, which is the response object of the current test. The response object will usually by an HTTP response, but in boss_web_test:read_email/4, it's an email.
Most assertions will take the form of calls to boss_assert, which is documented below.
The last argument to any boss_web_test function is called Continuations. Continuations is a list of additional tests — funs that take the HTTP response value as their only argument, and use it to invoke additional tests from the boss_web_test module. Funs in Continuations should be preceded by a string label, e.g.:
39 | boss_web_test:get_request("/", [], [],
40 | [ "Click the register link", fun(Res) -> ... end,
41 | "Click the copyright link", fun(Res) -> ... end,
42 | ... ]).
43 |
44 | The key thing to understand about Continuations is that they are performed in parallel. Any database manipulations that occur in one continuation cannot affect sibling continuations.
Functions available in the boss_web_test module include:
{{ function.description_long }}
55 |The Assertions list in a boss_web_test invocation will usually refer to functions in the boss_assert module. Available functions include:
{{ function.description_long }}
71 |WebSockets are an HTML5 technology used for two-way messaging from inside a web page. As of version 0.8.0, Chicago Boss provides infrastructure for defining one or more WebSocket controllers. Note that WebSockets are only supported with Cowboy, so put {server, cowboy} in your boss.config before attempting anything on this page.
The server WebSocket API is based around Erlang message-passing; to send a message to a particular client, simply send it a message like:
6 |To handle incoming messages from clients, you'll need to create WebSocket controllers in your project's src/websocket directory. Each controller module should have a name of the form <app name>_<service name>_websocket.erl and be a parameterized module with parameters for Req and SessionId, e.g.:
The module should implement the boss_service_handler behavior. The boss_service_handler behavior consists of the following six functions:
Initialize the service.
20 | 21 |Handle a client joining a service.
28 | 29 |Handle a client leaving a service.
47 | 48 |Handle an incoming message from a client.
55 | 56 |Handle an outgoing broadcast message from some Erlang process to 63 | the connected web sockets. It is your responsibility to keep track of 64 | the web sockets (via handle_join and handle_close) and send the 65 | outgoing messages as you see fit.
66 | 67 |Handle an informational message sent to the underlying gen_server process.
75 | 76 |Perform any cleanup before shutting down the service.
80 | 81 |WebSocket URLs are automatically generated from the WebSocket controllers in your project's src/websocket directory. For example, if you have myapp_foobar_websocket.erl, then to create a new WebSocket on the client:
The WebSocket URLs respect the base_url config parameter. For example, if the base_url is set to "/chat", then you would use this code instead:
For a reference on the WebSocket client programming, see Mozilla's WebSockets page.
97 | 98 | {% endblock %} 99 | -------------------------------------------------------------------------------- /doc-src/api-news.html: -------------------------------------------------------------------------------- 1 | {% extends "api.html" %} 2 | {% block api_content %} 3 |The BossNews API lets you cleanly separate notification logic from your models and controllers. You can specify model attributes and collections to watch, and have a callback function invoked when that part of the model changes. BossNews might be used to send email updates about changes occuring on a website, or it might be used in conjunction with BossMQ to provide users with real-time events (for example, to fire an alert when someone replies to a user's forum post).
5 |boss_news moduleThe boss_news module provides functions for creating and managing event listeners, called watches. Watches take a TopicString, which is a comma-separated list of topics to watch, and then execute the provided function when a topic changes. The following table summarizes valid topics and the events that the callback function will receive:
| Topic | Event | EventInfo |
|---|---|---|
RecordId.Attribute | updated | {Record, Attribute::atom(), OldValue, NewValue} |
RecordId.* | updated | {Record, Attribute::atom(), OldValue, NewValue} |
RecordId | deleted | DeletedRecord |
Type-*.Attribute | updated | {Record, Attribute::atom(), OldValue, NewValue} |
Type-*.* | updated | {Record, Attribute::atom(), OldValue, NewValue} |
Collection | created | NewRecord |
Collection | deleted | DeletedRecord |
Examples of valid topics are:
20 |"user-42.status""user-*.status""user-42.*"user-*.*"users"The return value of the callback function can be used to cancel (or extend) the watch that invoked the function. This feature can be used to ensure that a callback is only performed once. Meaningful return values are:
28 |{ok, cancel_watch} - Cancel the invoking watch.{ok, extend_watch} - Extend the invoking watch.(Other return values are ignored.)
33 |Functions for managing watches are:
34 | 35 | {% for function in functions %} 36 | {% if function.description_long %} 37 |{{ function.description_long }}
42 |*_news.erl startup scriptListeners should be set up in the priv/init/<appname>_news.erl file in your project directory. The module should have one exported function, init/0, which is executed at server startup. This function is a convenient place to set up listeners before any web requests are received, but listeners can be set up anywhere in the application.
The script's init/0 function must return {ok, ListOfWatchIDs}. If the script is reloaded during the lifetime of the application (for example, via the admin application), then this list of watches will be cancelled before the script is executed again.
If the admin application is installed, other applications can post data update notifications to a Chicago Boss server via a simple HTTP API:
54 | 55 |56 | POST /admin/news_api/created/<record-id> 57 | POST /admin/news_api/updated/<record-id> 58 | POST /admin/news_api/deleted/<record-id> 59 |60 | 61 |
The body of the POST request should include model attributes in the form new[<attr>]=<val> and old[<attr>]=<val>. The created API takes only "new" attributes. The deleted API takes only "old" attributes. The updated API takes both "new" attributes and their corresponding "old" attributes.
When invoked via the HTTP API, all values are passed to the listeners as strings.
63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /src/boss/boss_session_mock_controller.erl: -------------------------------------------------------------------------------- 1 | -module(boss_session_mock_controller). 2 | -behaviour(gen_server). 3 | 4 | -export([start_link/0, start_link/1]). 5 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 6 | 7 | -record(boss_session, {sid, data}). 8 | 9 | -record(state, { 10 | table, 11 | session_dict = dict:new(), 12 | ttl_tree = gb_trees:empty(), 13 | exp_time 14 | }). 15 | 16 | start_link() -> 17 | start_link([]). 18 | 19 | start_link(Args) -> 20 | gen_server:start_link({global, boss_session_mock}, ?MODULE, Args, []). 21 | 22 | init(Options) -> 23 | TableId = ets:new(?MODULE,[set,named_table,{keypos, 2}]), 24 | {ok, #state{ table = TableId, exp_time = proplists:get_value(session_exp_time, Options, 1440) }}. 25 | 26 | handle_call({session_exists, SessionID}, _From, State) -> 27 | Exists = dict:is_key(SessionID, State#state.session_dict), 28 | NewState = case Exists of 29 | true -> 30 | NowSeconds = now_seconds(), 31 | Val = dict:fetch(SessionID, State#state.session_dict), 32 | State#state{ 33 | ttl_tree = tiny_pq:move_value(Val, NowSeconds + State#state.exp_time, SessionID, State#state.ttl_tree), 34 | session_dict = dict:store(SessionID, 35 | NowSeconds + State#state.exp_time, 36 | State#state.session_dict)}; 37 | false -> 38 | State 39 | end, 40 | {reply, Exists, prune_expired_sessions(NewState, now_seconds())}; 41 | handle_call({create_session, SessionID, Data}, _From, State) -> 42 | NowSeconds = now_seconds(), 43 | ets:insert(State#state.table, #boss_session{sid=SessionID, data=Data}), 44 | NewState = State#state{ 45 | ttl_tree = tiny_pq:insert_value(NowSeconds + State#state.exp_time, SessionID, State#state.ttl_tree), 46 | session_dict = dict:store(SessionID, NowSeconds + State#state.exp_time, State#state.session_dict) 47 | }, 48 | {reply, ok, NewState}; 49 | handle_call({lookup_session, SessionID}, _From, State) -> 50 | Data = case ets:lookup(State#state.table, SessionID) of 51 | [S] -> S#boss_session.data; 52 | [] -> [] 53 | end, 54 | {reply, Data, State}; 55 | handle_call({lookup_session_value, SessionID, Key}, _From, State) -> 56 | Data = case ets:lookup(State#state.table, SessionID) of 57 | [S] -> S#boss_session.data; 58 | [] -> [] 59 | end, 60 | {reply, proplists:get_value(Key, Data), State}; 61 | handle_call({set_session_value, SessionID, Key, Value}, _From, State) -> 62 | Data = case ets:lookup(State#state.table, SessionID) of 63 | [S] -> S#boss_session.data; 64 | [] -> [] 65 | end, 66 | Data1 = case proplists:is_defined(Key,Data) of 67 | true -> 68 | Rest = proplists:delete(Key,Data), 69 | [{Key,Value}|Rest]; 70 | false -> 71 | [{Key,Value}|Data] 72 | end, 73 | ets:insert(State#state.table, #boss_session{sid=SessionID,data=Data1}), 74 | {reply, ok, State}; 75 | handle_call({delete_session, SessionID}, _From, State) -> 76 | ets:delete(State#state.table, SessionID), 77 | NewState = case dict:find(SessionID, State#state.session_dict) of 78 | {ok, Val} -> 79 | State#state{ 80 | ttl_tree = tiny_pq:delete_value(Val, SessionID, State#state.ttl_tree), 81 | session_dict = dict:erase(SessionID, State#state.session_dict) 82 | }; 83 | _ -> 84 | State 85 | end, 86 | {reply, ok, NewState}; 87 | handle_call({delete_session_value, SessionID, Key}, _From, State) -> 88 | case ets:lookup(?MODULE,SessionID) of 89 | [S] -> 90 | Data = S#boss_session.data, 91 | case proplists:is_defined(Key,Data) of 92 | true -> 93 | Data1 = proplists:delete(Key,Data), 94 | ets:insert(State#state.table, #boss_session{ sid=SessionID, data=Data1 }); 95 | false -> 96 | ok 97 | end; 98 | [] -> 99 | ok 100 | end, 101 | {reply, ok, State}. 102 | 103 | handle_cast(_, State) -> 104 | {noreply, State}. 105 | 106 | terminate(_Reason, _State) -> 107 | ok. 108 | 109 | code_change(_OldVsn, State, _Extra) -> 110 | {ok, State}. 111 | 112 | handle_info(_Info, State) -> 113 | {noreply, State}. 114 | 115 | prune_expired_sessions(#state{ ttl_tree = Tree, session_dict = Dict, table = TableId } = State, NowSeconds) -> 116 | {NewDict, NewTree} = tiny_pq:prune_collect_old(fun(SessionID, DictAcc) -> 117 | ets:delete(TableId, SessionID), 118 | dict:erase(SessionID, DictAcc) 119 | end, Dict, Tree, NowSeconds), 120 | State#state{ ttl_tree = NewTree, session_dict = NewDict }. 121 | 122 | now_seconds() -> 123 | {A, B, _} = erlang:now(), 124 | A * 1000 * 1000 + B. 125 | 126 | -------------------------------------------------------------------------------- /README_ELIXIR.md: -------------------------------------------------------------------------------- 1 | Using Elixir with Chicago Boss 2 | == 3 | 4 | Elixir is a nifty new language for the Erlang VM which you can use in parts of 5 | your project instead of regular Erlang if you prefer. 6 | 7 | Supported features: 8 | 9 | * Elixir web controllers 10 | * Ecto models 11 | * Embedded Elixir views 12 | * Elixir library files in src/lib/ (but no inter-file macros!) 13 | 14 | Requirements: 15 | 16 | * Erlang R16B or later 17 | * A love of danger 18 | 19 | Learn more about Elixir at http://www.elixir-lang.org/ 20 | 21 | Setup 22 | -- 23 | 24 | To enable Elixir, you need to do the following: 25 | 26 | 1. Uncomment {elixir, ...} in the "deps" section of rebar.config in the 27 | ChicagoBoss directory 28 | 29 | 2. Copy priv/elixir to the src/ directory in ChicagoBoss 30 | 31 | 3. Download Elixir and friends: 32 | 33 | ./rebar get-deps 34 | 35 | 4. First pass compile: 36 | 37 | ./rebar compile 38 | 39 | 5. Download Elixir dependencies: 40 | 41 | PATH=./deps/elixir/bin:.:$PATH mix deps.get 42 | 43 | 6. Workaround for poolboy error: 44 | 45 | cp rebar deps/poolboy 46 | 47 | 7. Retry Elixir dependencies: 48 | 49 | PATH=./deps/elixir/bin:.:$PATH mix deps.get 50 | 51 | 8. Make a new project: 52 | 53 | make app PROJECT=my_application 54 | 55 | 56 | Controller API 57 | -- 58 | 59 | Put your Elixir controllers into src/controller/ as before. The source files 60 | should NOT be prefixed with the application name. Example: 61 | 62 | src/controller/puppy_controller.ex 63 | 64 | Open up the controller and write a basic module, like: 65 | 66 | defmodule MyApplication.PuppyController do 67 | use Boss.WebController 68 | 69 | def index(:GET, []) do 70 | {:output, "Hello, world!"} 71 | end 72 | 73 | def about(:GET, []) do 74 | {:output, "Hello, world!"} 75 | end 76 | 77 | end 78 | 79 | To test it out, start the server and visit: 80 | 81 | http://localhost:8001/puppy/index 82 | 83 | The ChicagoBoss API is essentially the same as the Erlang one. 84 | 85 | There are two variables implicitly available to your handlers: "req" (the 86 | SimpleBridge request object) and "session_id" (the session identifier, if 87 | present). 88 | 89 | Other macros available include "before_", "cache_", "after_", and "lang_", 90 | which take the same arguments as their Erlang counterparts. 91 | 92 | Tokens are passed in as Elixir strings (binaries), not Erlang strings. 93 | 94 | Feel free to mix and match Elixir and Erlang controllers in the same project, 95 | but watch out for name collisions. 96 | 97 | You can access the regular Chicago Boss Erlang API using atoms: 98 | 99 | get :index, [] do 100 | puppies = :boss_db.find(:puppy, [{:name, :equals, "Fido"}]) 101 | {:ok, [{:puppies, puppies}]} 102 | end 103 | 104 | 105 | View API 106 | -- 107 | 108 | Chicago Boss also support EEx files (Embedded Elixir). These should go in 109 | src/view/