Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git://github.com/evanmiller/ChicagoBoss into…

… multiple_listeners

Conflicts:
	src/boss/boss_web_controller.erl
  • Loading branch information...
commit 4e620add6d02174709122459923ad98fc99793dc 2 parents ea9044f + 35647ba
Grzegorz Stanislawski authored
Showing with 402 additions and 262 deletions.
  1. +36 −36 doc-src/api-controller.html
  2. +13 −12 doc-src/api-db.html
  3. +1 −1  doc-src/api-mq.html
  4. +1 −1  doc-src/api-news.html
  5. +7 −5 doc-src/api-record.html
  6. +2 −2 doc-src/api-session.html
  7. +2 −2 doc-src/api-test.html
  8. +3 −1 doc-src/api.html
  9. +1 −1  doc-src/boss.css
  10. +1 −1  doc-src/guide-db-driver.html
  11. +1 −1  skel.template
  12. +1 −1  skel/Makefile
  13. +7 −4 skel/priv/init/news.erl
  14. +1 −1  skel/start.sh
  15. +6 −1 src/bmq/bmq_channel_controller.erl
  16. +1 −1  src/boss.app.src
  17. +37 −0 src/boss/boss_controller_lib.erl
  18. +0 −5 src/boss/boss_db.erl
  19. +25 −24 src/boss/boss_db_mock_controller.erl
  20. +1 −1  src/boss/boss_files.erl
  21. +1 −2  src/boss/boss_record_compiler.erl
  22. +3 −37 src/boss/boss_router_controller.erl
  23. +7 −4 src/boss/boss_web.erl
  24. +1 −1  src/boss/boss_web.hrl
  25. +73 −32 src/boss/boss_web_controller.erl
  26. +23 −14 src/boss/boss_web_test.erl
  27. +6 −6 src/boss/db_adapters/boss_db_adapter_mnesia.erl
  28. +6 −5 src/boss/db_adapters/boss_db_adapter_mongodb.erl
  29. +11 −4 src/boss/db_adapters/boss_db_adapter_mysql.erl
  30. +11 −5 src/boss/db_adapters/boss_db_adapter_pgsql.erl
  31. +17 −4 src/boss/db_adapters/boss_db_adapter_tyrant.erl
  32. +47 −24 src/simple_bridge/misultin_bridge_modules/misultin_request_bridge.erl
  33. +49 −23 src/simple_bridge/mochiweb_bridge_modules/mochiweb_request_bridge.erl
72 doc-src/api-controller.html
View
@@ -8,15 +8,15 @@
&nbsp; <a href="#simplebridge">SimpleBridge request object</a></p>
<p>Chicago Boss associates each URL with a function of a controller.
The URL <nobr>/foo/bar</nobr> will call the function <code>foo_controller:bar</code>.
-Each controller module should go into your project's src/controller/ directory and the file name should end with "_controller.erl".
+Each controller module should go into your project's src/controller/ directory and the file name should start with the application name and end with "_controller.erl", e.g. "appname_my_controller.erl".
Helper functions should go into your project's src/lib/ directory.
Controllers can take one parameter or two parameters: the <a href="#simplebridge">SimpleBridge request object</a>, and an optional session ID (if <a href="api-session.html">sessions</a> are enabled). Declare it like:</p>
<div class="code">
- <span class="attr">-module</span>(my_controller, [Req]).
+ <span class="attr">-module</span>(appname_my_controller, [Req]).
</div>
-Or:
+<p>Or:</p>
<div class="code">
- <span class="attr">-module</span>(my_controller, [Req, SessionID]).
+ <span class="attr">-module</span>(appname_my_controller, [Req, SessionID]).
</div>
<p>Each exported controller function takes two or three arguments:</p>
<ul>
@@ -60,7 +60,7 @@
<a name="routes"></a>
<h3>Routes</h3>
-<p>Most routing takes place in the controller pattern-matching code. You can define additional routes in <code>priv/my_application.routes</code>. The file contains a list of erlang terms, one per line finished with a dot. Each term is a tuple with a URL or an HTTP status code as the first term, and a <code>{Controller, Action}</code> or <code>{Controller, Action, Parameters}</code> tuple as the second term.</p>
+<p>Most routing takes place in the controller pattern-matching code. You can define additional routes in <code>priv/my_application.routes</code>. The file contains a list of erlang terms, one per line finished with a dot. Each term is a tuple with a URL or an HTTP status code as the first term, and a <code>{Controller::string(), Action::string()}</code> or <code>{Controller::string(), Action::string(), Parameters::proplist()}</code> tuple as the second term.</p>
<p>A few examples:</p>
@@ -81,21 +81,22 @@
<p>If an action takes three arguments, then the function <code>before_/1</code> in your controller will be passed the action name as a string and should return one of:</P>
-<div class="code">
+<div class="code spec">
{ok, ExtraInfo}
</div>
<p><code>ExtraInfo</code> will be passed as the third argument to the action, and as a variable called "before_" to the templates.</p>
-<div class="code">
+<div class="code spec">
{redirect, Location}
</div>
+<p><code>Location = string() | [{Key<span class="typevar">::atom()</span>, Value<span class="typevar">::atom()</span>}]</code></p>
<p>Do not execute the action. Instead, perform a 302 redirect to <code>Location</code>, which can be a string or a proplist that will be converted to a URL using the routes system.</p>
<p>Probably most common before_ looks like:</p>
-<div class="code">
+<div class="code spec">
before_(_) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="function">my_user_lib</span>:<span class="function">require_login</span>(Req).
</div>
@@ -108,78 +109,77 @@
<p>Whether or not it takes a third argument, a controller action should return with one of the following:</p>
-<div class="code">
+<div class="code spec">
ok
</div>
<p>The template will be rendered without any variables.</p>
-<div class="code">
+<div class="code spec">
{ok, Variables<span class="typevar">::proplist()</span>}
</div>
<p><code>Variables</code> will be passed into the associated <a href="api-view.html#nav">Django template</a>.</p>
-<div class="code">
+<div class="code spec">
{ok, Variables<span class="typevar">::proplist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p><code>Variables</code> will be passed into the associated Django template, and <code>Headers</code> are HTTP headers you want to set (e.g., <code>Content-Type</code>).</p>
-<div class="code">
+<div class="code spec">
{redirect, Location}
</div>
+<p><code>Location = string() | [{action, Value<span class="typevar">::string()</span>}, ...]</code></p>
<p>Perform a 302 HTTP redirect to <code>Location</code>, which may be a URL string or a proplist of parameters that will be converted to a URL using the routes system.</p>
-<div class="code">
+<div class="code spec">
{redirect, Location, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Perform a 302 HTTP redirect to <code>Location</code> and set additional HTTP <code>Headers</code>.</p>
-<div class="code">
+<div class="code spec">
{action_other, OtherLocation}
</div>
-<p><code>OtherLocation = {Controller<span class="typevar">::atom()</span>, Action<span class="typevar">::atom()</span>}</code></p>
+<p><code>OtherLocation = [{action, Value<span class="typevar">::string()</span>}, ...]</code></p>
-<p>Execute the specified controller action, but without performing an HTTP redirect.</p>
+<p>Execute the controller action specified by <code>OtherLocation</code>, but without performing an HTTP redirect.</p>
-<div class="code">
+<div class="code spec">
{render_other, OtherLocation}
</div>
-<p><code>OtherLocation = {Controller<span class="typevar">::atom()</span>, Action<span class="typevar">::atom()</span>}</code></p>
+<p><code>OtherLocation = [{action, Value<span class="typevar">::string()</span>}, ...]</code></p>
<p>Render the view from <code>OtherLocation</code>, but don't actually execute the associated controller action.</p>
-<div class="code">
+<div class="code spec">
{render_other, OtherLocation, Variables}
</div>
-<p><code>OtherLocation = {Controller<span class="typevar">::atom()</span>, Action<span class="typevar">::atom()</span>}</code></p>
-
<p>Render the view from <code>OtherLocation</code> using <code>Variables</code>, but don't actually execute the associated controller action.</p>
-<div class="code">
+<div class="code spec">
{output, Output<span class="typevar">::iolist()</span>}
</div>
<p>Skip views altogether and return <code>Output</code> to the client.</p>
-<div class="code">
+<div class="code spec">
{output, Output<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Skip views altogether and return <code>Output</code> to the client while setting additional HTTP <code>Headers</code>.</p>
-<div class="code">
+<div class="code spec">
{json, Data<span class="typevar">::proplist()</span>}
</div>
<p>Return <code>Data</code> as a JSON object to the client. Performs appropriate serialization if the values in Data contain a BossRecord or a list of BossRecords.</p>
-<div class="code">
+<div class="code spec">
{json, Data<span class="typevar">::proplist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Return <code>Data</code> to the client as a JSON object while setting additional HTTP <code>Headers</code>.</p>
-<div class="code">
+<div class="code spec">
not_found
</div>
@@ -195,12 +195,12 @@
<li>The result of the before_ function, provided one exists</li>
</ol>
<p>The <code>after_</code> function should return a (possibly) modified HTTP result tuple. Result tuples may be one of:</p>
-<div class="code">
+<div class="code spec">
{redirect, Location<span class="typevar">::string()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Performs a 302 HTTP redirect to <code>Location</code> and sets additional HTTP <code>Headers</code>.</p>
-<div class="code">
+<div class="code spec">
{ok, Payload<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Returns a 200 OK response to the client with <code>Payload</code> as the HTTP body, and sets additional HTTP <code>Headers</code>.</p>
@@ -210,36 +210,36 @@
<p>Controller functions are passed a SimpleBridge request object (slightly modified for Boss's purposes). Useful functions in the request object include:</p>
-<div class="code">
+<div class="code spec">
request_method() -&gt; atom()
</div>
<p>Get the request method, e.g. GET, POST, etc.</p>
-<div class="code">
+<div class="code spec">
query_param( Key<span class="typevar">::string()</span> ) -&gt; string() | undefined
</div>
<p>Get the value of a given query string parameter (e.g. "?id=1234")</p>
-<div class="code">
+<div class="code spec">
post_param( Key<span class="typevar">::string()</span> ) -&gt; string() | undefined
</div>
<p>Get the value of a given POST parameter</p>
-<div class="code">
+<div class="code spec">
deep_post_param( [ Path<span class="typevar">::string()</span> ] ) -&gt; DeepParam | undefined
</div>
<p>Get the value of a given "deep" POST parameter.
This function parses parameters that have numerical or labeled indices, such as "widget[4][name]", and returns either a value or a set of nested lists (for numerical indices) and proplists (for string indices).</p>
-<div class="code">
- header( Key<span class="typevar">::atom()</span> ) -&gt; string() | undefined
+<div class="code spec">
+ header( Header<span class="typevar">::string()</span> | atom() ) -&gt; string() | undefined
</div>
-<p>Get the value of a given HTTP request header. Valid values are:</p>
+<p>Get the value of a given HTTP request header. Valid values are strings or one of these atoms:</p>
<ul>
<li><code>accept</code></li>
<li><code>accept_language</code></li>
@@ -264,7 +264,7 @@
<li><code>x_forwarded_for</code></li>
</ul>
-<div class="code">
+<div class="code spec">
cookie( Key<span class="typevar">::string()</span> ) -&gt; string() | undefined
</div>
25 doc-src/api-db.html
View
@@ -2,6 +2,19 @@
{% block api_content %}
<p>BossDB is a database abstraction layer used for querying and updating the database. Currently Tokyo Tyrant, Mnesia, MySQL, and PostgreSQL are supported.</p>
+
+<h2>Functions</h2>
+
+<p>Functions in the <code>boss_db</code> module include:</p>
+{% for function in functions %}
+{% if function.description_long %}
+<div class="code spec">
+ {{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
+</div>
+<p>{{ function.description_long }}</p>
+{% endif %}
+{% endfor %}
+
<h2>Conditions and Comparison Operators</h2>
<p>The "find" and "count" functions each take a set of <code>Conditions</code>, which specify search criteria. Similar to Microsoft's <a href="http://msdn.microsoft.com/en-us/library/bb308959.aspx">LINQ</a>, the <code>Conditions</code> can use a special non-Erlang syntax for conciseness. This special syntax can't be compiled with Erlang's default compiler, so you'll have to let Boss compile your controllers which use it.</p>
@@ -169,16 +182,4 @@
</div>
<p>The "key" attribute is less than or equal to Value.</p>
-
-<h2>Functions</h2>
-
-<p>Functions in the <code>boss_db</code> module include:</p>
-{% for function in functions %}
-{% if function.description_long %}
-<div class="code">
- {{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
-</div>
-<p>{{ function.description_long }}</p>
-{% endif %}
-{% endfor %}
{% endblock %}
2  doc-src/api-mq.html
View
@@ -22,7 +22,7 @@
{% for function in functions %}
{% if function.description_long %}
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>{{ function.description_long }}</p>
2  doc-src/api-news.html
View
@@ -31,7 +31,7 @@
<p>Functions for managing watches are:</p>
{% for function in functions %}
{% if function.description_long %}
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>{{ function.description_long }}</p>
12 doc-src/api-record.html
View
@@ -38,7 +38,7 @@
{% for function in functions %}
{% ifnotequal function.function "trivial_counter" %}
<a name="{{ function.function }}"></a>
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>
@@ -57,13 +57,13 @@
<p>Special associations are generated from the following module attributes:</p>
-<div class="code">
+<div class="code spec">
<span class="attr">-belongs_to</span>(foo).
</div>
<p>Requires a matching <code>FooId</code> in the parameter list. Adds a function <code>foo()</code> which returns the BossRecord (of any type, usually <code>foo</code>) with ID equal to the current BossRecord's <code>FooId</code>.</p>
-<div class="code">
+<div class="code spec">
<span class="attr">-has</span>({bar, 1}).<br />
<span class="attr">-has</span>({bars, Count}). % Count &gt; 1<br />
<span class="attr">-has</span>({bars, many}). % alias for 1 million<br />
@@ -72,9 +72,11 @@
<p>Generates a function <code>bar()</code> or <code>bars()</code> which returns up to <code>Count</code> "bar" BossRecords with <code>FooId</code> equal to this BossRecord's ID. If Count is greater than 1, also creates <code>first_bar()</code> and <code>last_bar()</code> which return the first and last items in the association.</p>
<p>When <code>Count</code> is not equal to 1, <code>has</code> can also take a proplist of options as the third element in the tuple:</p>
-<div class="code">
+
+<div class="code spec">
<span class="attr">-has</span>({bars, many, [{sort_by, first_name}]}).</span>
</div>
+
<p>Valid options are:</p>
<ul>
<li><code>sort_by</code> - attribute to sort on. Defaults to 'id'</li>
@@ -93,7 +95,7 @@
<p>The two above attributes work similar to <code>belongs_to</code> and <code>has_many/has_one</code> in Rails.</p>
-<div class="code">
+<div class="code spec">
<span class="attr">-counter</span>(foo_counter).
</div>
4 doc-src/api-session.html
View
@@ -17,7 +17,7 @@
{% for function in session_functions %}
{% if function.description_long %}
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>{{ function.description_long }}</p>
@@ -44,7 +44,7 @@
{% for function in flash_functions %}
{% if function.description_long %}
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>{{ function.description_long }}</p>
4 doc-src/api-test.html
View
@@ -33,7 +33,7 @@
<p>Functions available in the <code>boss_web_test</code> module include:</p>
{% for function in test_functions %}
{% if function.description_long %}
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>{{ function.description_long }}</p>
@@ -45,7 +45,7 @@
<p>The <code>Assertions</code> list in a <code>boss_web_test</code> invocation will usually refer to functions in the <code>boss_assert</code> module. Available functions include:</p>
{% for function in assert_functions %}
{% if function.description_long %}
-<div class="code">
+<div class="code spec">
{{ function.function }}{% if function.typespec %}{{ function.typespec }}{% endif %}
</div>
<p>{{ function.description_long }}</p>
4 doc-src/api.html
View
@@ -1,9 +1,11 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Chicago Boss: The Official API Reference</title>
</head>
<body>
- <link rel="stylesheet" type="text/css" href="/stylesheets/boss.css" />
+ <link rel="stylesheet" type="text/css" href="boss.css" />
+ <p><em><a href="/">Chicago Boss home</a></em></p>
<div style="text-align: center; font-style: italic;">
<p>The Chicago Boss API is mostly stable, but still might change before 1.0.</p>
2  doc-src/boss.css
View
@@ -27,7 +27,7 @@ div.quote {
font-style: italic;
}
div.code {
-padding: 10px; font-family: Courier New; background-color: black; color: white;
+padding: 10px; font-family: Courier New; background-color: #CCC; color: black;
}
div.code span.attr {
color: #00FF1A;
2  doc-src/guide-db-driver.html
View
@@ -15,7 +15,7 @@
</div>
<p>Returns a BossRecord matching the <code>Id</code>. The <code>Id</code> must be unique across data types.</p>
<div class="code">
- find(Conn, Type<span class="typevar">::atom()</span>, Conditions, Max<span class="typevar">::integer()</span>, Skip<span class="typevar">::integer()</span>, Sort<span class="typevar">::atom()</span>, SortOrder) -&gt; [BossRecord]
+ find(Conn, Type<span class="typevar">::atom()</span>, Conditions, Max<span class="typevar">::integer()</span> | all, Skip<span class="typevar">::integer()</span>, Sort<span class="typevar">::atom()</span>, SortOrder) -&gt; [BossRecord]
</div>
<p>Queries for BossRecords. The <code>Conditions</code> are guaranteed to be in {Key, Operator, Value} format. See <a href="api-db.html">the BossDB API</a> for a list of query operators you need to support.</p>
<div class="code">
2  skel.template
View
@@ -22,7 +22,7 @@
{template, "skel/boss.config", "{{dest}}/boss.config"}.
{template, "skel/src/mail/outgoing_mail_controller.erl", "{{dest}}/src/mail/{{appid}}_outgoing_mail_controller.erl"}.
{template, "skel/src/mail/incoming_mail_controller.erl", "{{dest}}/src/mail/{{appid}}_incoming_mail_controller.erl"}.
-{template, "skel/priv/init/news.erl", "{{dest}}/priv/init/{{appid}}_news.erl"}.
+{template, "skel/priv/init/news.erl", "{{dest}}/priv/init/{{appid}}_01_news.erl"}.
{file, "skel/priv/static/chicago-boss.png", "{{dest}}/priv/static/chicago-boss.png"}.
{file, "skel/priv/static/favicon.ico", "{{dest}}/priv/static/favicon.ico"}.
{file, "skel/priv/boss.routes", "{{dest}}/priv/{{appid}}.routes"}.
2  skel/Makefile
View
@@ -13,4 +13,4 @@ update_po:
.PHONY: test
test:
- $(ERL) -pa {{src}}/ebin -run boss_web_test -noshell
+ $(ERL) -pa $(PWD)/ebin -pa {{src}}/ebin -run boss_web_test start $(APP) -noshell
11 skel/priv/init/news.erl
View
@@ -1,13 +1,16 @@
--module({{appid}}_news).
+-module({{appid}}_01_news).
--export([init/0]).
+-export([init/0, stop/1]).
% This script is first executed at server startup and should
-% return a list of WatchIDs that should be cancelled if the
-% script is ever reloaded (for example, via the admin application).
+% return a list of WatchIDs that should be cancelled in the stop
+% function below (stop is executed if the script is ever reloaded).
init() ->
{ok, []}.
+stop(ListOfWatchIDs) ->
+ lists:map(fun boss_news:cancel_watch/1, ListOfWatchIDs).
+
%%%%%%%%%%% Ideas
% boss_news:watch("user-42.*",
% fun
2  skel/start.sh
View
@@ -7,6 +7,6 @@ cd `dirname $0`
# If your server is running in an untrusted environment, you should probably
# change the cookie too. (All nodes in a cluster must have the same cookie.)
-exec erl -pa $PWD/ebin -pa {{src}}/ebin -pa {{src}}/deps/*/ebin \
+exec erl +K true -pa $PWD/ebin -pa {{src}}/ebin -pa {{src}}/deps/*/ebin \
-boot start_sasl -config boss -s boss -setcookie abc123 -detached \
-sname john
7 src/bmq/bmq_channel_controller.erl
View
@@ -32,6 +32,10 @@ handle_cast({From, pull, 'last', Subscriber}, State) ->
{NewSubscribers, LastPull} = pull_messages(State#state.last_pull, Subscriber, State),
gen_server:reply(From, {ok, LastPull}),
{noreply, State#state{subscribers = NewSubscribers, last_pull = LastPull}};
+handle_cast({From, pull, undefined, Subscriber}, State) ->
+ {NewSubscribers, LastPull} = pull_messages(undefined, Subscriber, State),
+ gen_server:reply(From, {ok, LastPull}),
+ {noreply, State#state{subscribers = NewSubscribers, last_pull = LastPull}};
handle_cast({From, pull, Timestamp, Subscriber}, State) when is_integer(Timestamp) ->
{NewSubscribers, LastPull} = pull_messages(Timestamp, Subscriber, State),
gen_server:reply(From, {ok, LastPull}),
@@ -88,10 +92,11 @@ seconds_to_micro_seconds(Seconds) ->
now_to_micro_seconds({MegaSecs, Secs, MicroSecs}) ->
MegaSecs * 1000 * 1000 * 1000 * 1000 + Secs * 1000 * 1000 + MicroSecs.
+messages_newer_than_timestamp(undefined, Messages) ->
+ Messages;
messages_newer_than_timestamp(Timestamp, Messages) ->
messages_newer_than_timestamp(Timestamp, Messages, []).
-
messages_newer_than_timestamp(_, [], Acc) ->
lists:reverse(Acc);
messages_newer_than_timestamp(Ts1, [{Msg, Ts2}|Rest], Acc) when Ts2 > Ts1 ->
2  src/boss.app.src
View
@@ -1,7 +1,7 @@
{application, boss,
[
{description, "Chicago Boss web framework"},
- {vsn, "0.6.7"},
+ {vsn, "0.6.8"},
{registered, [
boss_cache, boss_cache_sup,
boss_db, boss_db_sup,
37 src/boss/boss_controller_lib.erl
View
@@ -0,0 +1,37 @@
+-module(boss_controller_lib).
+-export([convert_params_to_tokens/3]).
+
+convert_params_to_tokens(Variables, ControllerModule, Action) ->
+ DummyController = apply(ControllerModule, new, lists:seq(1, proplists:get_value(new, ControllerModule:module_info(exports)))),
+ Routes = DummyController:'_routes'(),
+ lists:foldr(fun
+ ({RouteName, RouteTokens}, {Acc, Vars}) when RouteName =:= Action ->
+ Result = lists:foldr(fun
+ (_, false) ->
+ false;
+ (Token, {Acc1, Vars1}) when is_atom(Token) ->
+ CamelCase = atom_to_list(Token),
+ Underscore = list_to_atom(string:to_lower(inflector:underscore(CamelCase))),
+ case proplists:get_value(Underscore, Vars1) of
+ undefined ->
+ false;
+ Value ->
+ {[Value|Acc1], proplists:delete(Underscore, Vars1)}
+ end;
+ (Token, {Acc1, Vars1}) ->
+ {[Token|Acc1], Vars1}
+ end, {[], Variables}, RouteTokens),
+ case Result of
+ false ->
+ {Acc, Vars};
+ {Acc1, Vars1} ->
+ case length(Vars1) =< length(Vars) of
+ true ->
+ {Acc1, Vars1};
+ false ->
+ {Acc, Vars}
+ end
+ end;
+ (_, {Acc, Vars}) ->
+ {Acc, Vars}
+ end, {[], Variables}, Routes).
5 src/boss/boss_db.erl
View
@@ -28,7 +28,6 @@
type/1,
data_type/2]).
--define(DEFAULT_MAX, (1000 * 1000 * 1000)).
-define(DEFAULT_TIMEOUT, (30 * 1000)).
start() ->
@@ -96,10 +95,6 @@ find(Type, Conditions, Max, Skip, Sort) ->
%% Note that Time attributes are stored internally as numbers, so you should
%% sort them numerically.
-find(Type, Conditions, many, Skip, Sort, SortOrder) ->
- find(Type, Conditions, ?DEFAULT_MAX, Skip, Sort, SortOrder);
-find(Type, Conditions, all, Skip, Sort, SortOrder) ->
- find(Type, Conditions, ?DEFAULT_MAX, Skip, Sort, SortOrder);
find(Type, Conditions, Max, Skip, Sort, SortOrder) ->
gen_server:call(boss_db, {find, Type, normalize_conditions(Conditions), Max, Skip, Sort, SortOrder},
?DEFAULT_TIMEOUT).
49 src/boss/boss_db_mock_controller.erl
View
@@ -24,7 +24,7 @@ handle_call({find, Type, Conditions, Max, Skip, SortBy, SortOrder}, _From, [{Dic
Records = do_find(Dict, Type, Conditions, Max, Skip, SortBy, SortOrder),
{reply, Records, State};
handle_call({count, Type, Conditions}, _From, [{Dict, _IdCounter}|_] = State) ->
- Records = do_find(Dict, Type, Conditions, 1000 * 1000 * 1000 * 1000, 0, id, str_ascending),
+ Records = do_find(Dict, Type, Conditions, all, 0, id, str_ascending),
{reply, length(Records), State};
handle_call({delete, Id}, _From, [{Dict, IdCounter}|OldState]) ->
{reply, ok, [{dict:erase(Id, Dict), IdCounter}|OldState]};
@@ -82,29 +82,30 @@ handle_info(_Info, State) ->
{noreply, State}.
do_find(Dict, Type, Conditions, Max, Skip, SortBy, SortOrder) ->
- lists:sublist(lists:nthtail(Skip,
- lists:sort(fun(RecordA, RecordB) ->
- AttributeA = sortable_attribute(RecordA, SortBy),
- AttributeB = sortable_attribute(RecordB, SortBy),
- case SortOrder of
- str_ascending ->
- AttributeA < AttributeB;
- str_descending ->
- AttributeA > AttributeB;
- num_ascending ->
- AttributeA < AttributeB;
- num_descending ->
- AttributeA > AttributeB
- end
- end,
- lists:map(fun({_, V}) -> V end,
- dict:to_list(dict:filter(
- fun(_Id, Record) when is_tuple(Record) ->
- element(1, Record) =:= Type andalso
- match_cond(Record, Conditions);
- (_Id, _) ->
- false
- end, Dict))))), Max).
+ Tail = lists:nthtail(Skip,
+ lists:sort(fun(RecordA, RecordB) ->
+ AttributeA = sortable_attribute(RecordA, SortBy),
+ AttributeB = sortable_attribute(RecordB, SortBy),
+ case SortOrder of
+ str_ascending ->
+ AttributeA < AttributeB;
+ str_descending ->
+ AttributeA > AttributeB;
+ num_ascending ->
+ AttributeA < AttributeB;
+ num_descending ->
+ AttributeA > AttributeB
+ end
+ end,
+ lists:map(fun({_, V}) -> V end,
+ dict:to_list(dict:filter(
+ fun(_Id, Record) when is_tuple(Record) ->
+ element(1, Record) =:= Type andalso
+ match_cond(Record, Conditions);
+ (_Id, _) ->
+ false
+ end, Dict))))),
+ case Max of all -> Tail; _ -> lists:sublist(Tail, Max) end.
match_cond(_Record, []) ->
true;
2  src/boss/boss_files.erl
View
@@ -76,7 +76,7 @@ view_file_list() ->
ViewFiles ++ filelib:wildcard(MailPattern).
init_file_list(App) ->
- filelib:wildcard(filename:join([root_priv_dir(App), "init", "*.erl"])).
+ lists:sort(filelib:wildcard(filename:join([root_priv_dir(App), "init", "*.erl"]))).
routes_file(App) ->
filename:join([root_priv_dir(App), lists:concat([App, ".routes"])]).
3  src/boss/boss_record_compiler.erl
View
@@ -2,7 +2,6 @@
-author('emmiller@gmail.com').
-define(DATABASE_MODULE, boss_db).
-define(PREFIX, "BOSSRECORDINTERNAL").
--define(HAS_MANY_LIMIT, 1000 * 1000 * 1000).
-export([compile/1, compile/2, edoc_module/1, edoc_module/2, trick_out_forms/1]).
@@ -273,7 +272,7 @@ has_one_forms(HasOne, ModuleName, Opts) ->
].
has_many_forms(HasMany, ModuleName, many, Opts) ->
- has_many_forms(HasMany, ModuleName, ?HAS_MANY_LIMIT, Opts);
+ has_many_forms(HasMany, ModuleName, all, Opts);
has_many_forms(HasMany, ModuleName, Limit, Opts) ->
Sort = proplists:get_value(sort_by, Opts, 'id'),
SortOrder = proplists:get_value(sort_order, Opts, str_ascending),
40 src/boss/boss_router_controller.erl
View
@@ -45,7 +45,7 @@ handle_call({handle, StatusCode}, _From, State) ->
not_found;
[#boss_handler{ controller = C, action = A, params = P }] ->
ControllerModule = list_to_atom(boss_files:web_controller(State#state.application, C)),
- {Tokens, []} = convert_params_to_tokens(P, ControllerModule, list_to_atom(A)),
+ {Tokens, []} = boss_controller_lib:convert_params_to_tokens(P, ControllerModule, list_to_atom(A)),
{ok, {C, A, Tokens}}
end,
{reply, Result, State};
@@ -70,7 +70,7 @@ handle_call({route, Url}, _From, State) ->
end;
[#boss_route{ controller = C, action = A, params = P }] ->
ControllerModule = list_to_atom(boss_files:web_controller(State#state.application, C)),
- {Tokens, []} = convert_params_to_tokens(P, ControllerModule, list_to_atom(A)),
+ {Tokens, []} = boss_controller_lib:convert_params_to_tokens(P, ControllerModule, list_to_atom(A)),
{ok, {C, A, Tokens}}
end,
{reply, Route, State};
@@ -91,7 +91,7 @@ handle_call({unroute, Controller, Action, Params}, _From, State) ->
Result = case RoutedURL of
undefined ->
ControllerModule = list_to_atom(boss_files:web_controller(State#state.application, Controller)),
- {Tokens, Variables1} = convert_params_to_tokens(Params, ControllerModule, list_to_atom(Action)),
+ {Tokens, Variables1} = boss_controller_lib:convert_params_to_tokens(Params, ControllerModule, list_to_atom(Action)),
URL = case Tokens of
[] ->
@@ -174,37 +174,3 @@ default_action(State, Controller) ->
"index"
end.
-convert_params_to_tokens(Variables, ControllerModule, Action) ->
- DummyController = apply(ControllerModule, new, lists:seq(1, proplists:get_value(new, ControllerModule:module_info(exports)))),
- Routes = DummyController:'_routes'(),
- lists:foldr(fun
- ({RouteName, RouteTokens}, {Acc, Vars}) when RouteName =:= Action ->
- Result = lists:foldr(fun
- (_, false) ->
- false;
- (Token, {Acc1, Vars1}) when is_atom(Token) ->
- CamelCase = atom_to_list(Token),
- Underscore = list_to_atom(string:to_lower(inflector:underscore(CamelCase))),
- case proplists:get_value(Underscore, Vars1) of
- undefined ->
- false;
- Value ->
- {[Value|Acc1], proplists:delete(Underscore, Vars1)}
- end;
- (Token, {Acc1, Vars1}) ->
- {[Token|Acc1], Vars1}
- end, {[], Variables}, RouteTokens),
- case Result of
- false ->
- {Acc, Vars};
- {Acc1, Vars1} ->
- case length(Vars1) =< length(Vars) of
- true ->
- {Acc1, Vars1};
- false ->
- {Acc, Vars}
- end
- end;
- (_, {Acc, Vars}) ->
- {Acc, Vars}
- end, {[], Variables}, Routes).
11 src/boss/boss_web.erl
View
@@ -5,11 +5,14 @@
reload_routes() ->
gen_server:call(boss_web, reload_routes).
-reload_translations() ->
- gen_server:call(boss_web, reload_translations).
+reload_translation(Locale) ->
+ gen_server:call(boss_web, {reload_translation, Locale}).
-reload_news() ->
- gen_server:call(boss_web, reload_news).
+reload_all_translations() ->
+ gen_server:call(boss_web, reload_all_translations).
+
+reload_init_scripts() ->
+ gen_server:call(boss_web, reload_init_scripts).
get_all_routes() ->
gen_server:call(boss_web, get_all_routes).
2  src/boss/boss_web.hrl
View
@@ -2,7 +2,7 @@
-record(boss_app_info, {
application,
base_url,
- watches,
+ init_data,
router_sup_pid,
router_pid,
translator_sup_pid,
105 src/boss/boss_web_controller.erl
View
@@ -30,7 +30,10 @@ terminate(Reason, #state{ is_master_node = true } = State) ->
ok
end,
terminate(Reason, State#state{ is_master_node = false });
-terminate(_Reason, _State) ->
+terminate(_Reason, State) ->
+ lists:map(fun(AppInfo) ->
+ stop_init_scripts(AppInfo#boss_app_info.application, AppInfo#boss_app_info.init_data)
+ end, State#state.applications),
error_logger:logfile(close),
boss_translator:stop(),
boss_router:stop(),
@@ -135,9 +138,9 @@ handle_info(timeout, State) ->
true -> boss_load:load_all_modules(AppName, TranslatorSupPid);
false -> ok
end,
- InitWatches = init_watches(AppName),
+ InitData = run_init_scripts(AppName),
#boss_app_info{ application = AppName,
- watches = InitWatches,
+ init_data = InitData,
router_sup_pid = RouterSupPid,
translator_sup_pid = TranslatorSupPid,
base_url = (if BaseURL =:= "/" -> ""; true -> BaseURL end),
@@ -148,7 +151,13 @@ handle_info(timeout, State) ->
{noreply, State#state{ applications = AppInfoList }}.
-handle_call(reload_translations, _From, State) ->
+handle_call({reload_translation, Locale}, _From, State) ->
+ lists:map(fun(AppInfo) ->
+ [{_, TranslatorPid, _, _}] = supervisor:which_children(AppInfo#boss_app_info.translator_sup_pid),
+ boss_translator:reload(TranslatorPid, Locale)
+ end, State#state.applications),
+ {reply, ok, State};
+handle_call(reload_all_translations, _From, State) ->
lists:map(fun(AppInfo) ->
[{_, TranslatorPid, _, _}] = supervisor:which_children(AppInfo#boss_app_info.translator_sup_pid),
boss_translator:reload_all(TranslatorPid)
@@ -160,10 +169,11 @@ handle_call(reload_routes, _From, State) ->
boss_router:reload(RouterPid)
end, State#state.applications),
{reply, ok, State};
-handle_call(reload_news, _From, State) ->
+handle_call(reload_init_scripts, _From, State) ->
NewApplications = lists:map(fun(AppInfo) ->
- lists:map(fun boss_news:cancel_watch/1, AppInfo#boss_app_info.watches),
- AppInfo#boss_app_info{ watches = init_watches(AppInfo#boss_app_info.application) }
+ stop_init_scripts(AppInfo#boss_app_info.application, AppInfo#boss_app_info.init_data),
+ NewInitData = run_init_scripts(AppInfo#boss_app_info.application),
+ AppInfo#boss_app_info{ init_data = NewInitData }
end, State#state.applications),
{reply, ok, State#state{ applications = NewApplications }};
handle_call(get_all_routes, _From, State) ->
@@ -233,15 +243,31 @@ find_application_for_path(Path, Default, [App|Rest], LongestMatch) ->
find_application_for_path(Path, Default, Rest, LongestMatch)
end.
-init_watches(AppName) ->
- lists:foldr(fun(File, Acc) ->
+stop_init_scripts(Application, InitData) ->
+ lists:foldr(fun(File, _) ->
case boss_compiler:compile(File, []) of
{ok, Module} ->
- case lists:suffix("_news.erl", File) of
- true ->
- {ok, NewWatches} = Module:init(),
- NewWatches ++ Acc;
- false ->
+ case proplists:get_value(Module, InitData, init_failed) of
+ init_failed ->
+ ok;
+ ScriptInitData ->
+ catch Module:stop(ScriptInitData)
+ end;
+ Error -> error_logger:error_msg("Compilation of ~p failed: ~p~n", [File, Error])
+ end
+ end, ok, boss_files:init_file_list(Application)).
+
+run_init_scripts(AppName) ->
+ lists:foldl(fun(File, Acc) ->
+ case boss_compiler:compile(File, []) of
+ {ok, Module} ->
+ case catch Module:init() of
+ {ok, Info} ->
+ [{Module, Info}|Acc];
+ ok ->
+ [{Module, true}|Acc];
+ Error ->
+ error_logger:error_msg("Execution of ~p failed: ~p~n", [File, Error]),
Acc
end;
Error ->
@@ -327,14 +353,14 @@ process_request(AppInfo, Req, development, Instance, "/doc/"++ModelName, Session
end,
process_result(AppInfo, Result);
process_request(AppInfo, Req, Mode, Instance, Url, SessionID) ->
+ RouterPid = AppInfo#boss_app_info.router_pid,
if
Mode =:= development ->
ControllerList = boss_files:web_controller_list(AppInfo#boss_app_info.application),
- boss_router:set_controllers(AppInfo#boss_app_info.router_pid, ControllerList);
+ boss_router:set_controllers(RouterPid, ControllerList);
true ->
ok
end,
- RouterPid = AppInfo#boss_app_info.router_pid,
Location = case boss_router:route(RouterPid, Url) of
{ok, {Controller, Action, Tokens}} ->
{Controller, Action, Tokens};
@@ -370,16 +396,22 @@ process_result(AppInfo, {redirect, "http://"++Where, Headers}) ->
process_result(AppInfo, {redirect, "/"++string:join(tl(string:tokens(Where, "/")), "/"), Headers});
process_result(AppInfo, {redirect, "https://"++Where, Headers}) ->
process_result(AppInfo, {redirect, "/"++string:join(tl(string:tokens(Where, "/")), "/"), Headers});
-process_result(AppInfo, {redirect, {Controller, Action, Params}, Headers}) ->
- URL = boss_router:unroute(AppInfo#boss_app_info.router_pid, Controller, Action, Params),
- process_result(AppInfo, {redirect, URL, Headers});
+process_result(_AppInfo, {redirect, {Application, Controller, Action, Params}, Headers}) ->
+ RouterPid = boss_web:router_pid(list_to_atom(lists:concat([Application]))),
+ URL = boss_router:unroute(RouterPid, Controller, Action, Params),
+ BaseURL = boss_web:base_url(list_to_atom(lists:concat([Application]))),
+ {302, [{"Location", BaseURL ++ URL}, {"Cache-Control", "no-cache"}|Headers], ""};
process_result(AppInfo, {redirect, Where, Headers}) ->
{302, [{"Location", AppInfo#boss_app_info.base_url ++ Where}, {"Cache-Control", "no-cache"}|Headers], ""};
process_result(_, {ok, Payload, Headers}) ->
{200, [{"Content-Type", proplists:get_value("Content-Type", Headers, "text/html")}
|proplists:delete("Content-Type", Headers)], Payload}.
+<<<<<<< HEAD
load_and_execute(production, {Controller, _, _} = Location, AppInfo, Instance, Req, SessionID) ->
+=======
+load_and_execute(Mode, {Controller, _, _} = Location, AppInfo, Req, SessionID) when Mode =:= production; Mode =:= testing->
+>>>>>>> 35647bade37ccab697e23b2256d0259c940e4b51
case lists:member(boss_files:web_controller(AppInfo#boss_app_info.application, Controller),
AppInfo#boss_app_info.controller_modules) of
true -> execute_action(Location, AppInfo, Instance, Req, SessionID);
@@ -509,16 +541,26 @@ execute_action({Controller, Action, Tokens} = Location, AppInfo, Instance, Req,
Result
end;
{redirect, Where} ->
- {redirect, process_redirect(Where, Module)}
+ {redirect, process_redirect(Controller, Where, AppInfo)}
end
end.
-process_redirect([{_, _}|_] = Where, Controller) ->
+process_location(Controller, [{_, _}|_] = Where, AppInfo) ->
+ {_, TheController, TheAction, CleanParams} = process_redirect(Controller, Where, AppInfo),
+ ControllerModule = list_to_atom(boss_files:web_controller(AppInfo#boss_app_info.application, Controller)),
+ ActionAtom = list_to_atom(TheAction),
+ {Tokens, []} = boss_controller_lib:convert_params_to_tokens(CleanParams, ControllerModule, ActionAtom),
+ {TheController, TheAction, Tokens}.
+
+process_redirect(Controller, [{_, _}|_] = Where, AppInfo) ->
+ TheApplication = proplists:get_value(application, Where, AppInfo#boss_app_info.application),
TheController = proplists:get_value(controller, Where, Controller),
TheAction = proplists:get_value(action, Where),
- CleanParams = proplists:delete(controller, proplists:delete(action, Where)),
- {TheController, TheAction, CleanParams};
-process_redirect(Where, _) ->
+ CleanParams = lists:foldl(fun(Key, Vars) ->
+ proplists:delete(Key, Vars)
+ end, Where, [application, controller, action]),
+ {TheApplication, TheController, TheAction, CleanParams};
+process_redirect(_, Where, _) ->
Where.
process_action_result(Info, ok, AppInfo, AuthInfo) ->
@@ -532,13 +574,12 @@ process_action_result(Info, {render_other, OtherLocation}, AppInfo, AuthInfo) ->
process_action_result(Info, {render_other, OtherLocation, []}, AppInfo, AuthInfo);
process_action_result(Info, {render_other, OtherLocation, Data}, AppInfo, AuthInfo) ->
process_action_result(Info, {render_other, OtherLocation, Data, []}, AppInfo, AuthInfo);
-process_action_result(Info, {render_other, {Controller, Action}, Data, Headers}, AppInfo, AuthInfo) ->
- process_action_result(Info, {render_other, {Controller, Action, []}, Data, Headers}, AppInfo, AuthInfo);
-process_action_result({_, Req, SessionID, _}, {render_other, {_, _, _} = OtherLocation, Data, Headers}, AppInfo, AuthInfo) ->
- render_view(OtherLocation, AppInfo, Req, SessionID, [{"_before", AuthInfo}|Data], Headers);
+process_action_result({{Controller, _, _}, Req, SessionID, _}, {render_other, OtherLocation, Data, Headers}, AppInfo, AuthInfo) ->
+ render_view(process_location(Controller, OtherLocation, AppInfo),
+ AppInfo, Req, SessionID, [{"_before", AuthInfo}|Data], Headers);
-process_action_result({_, Req, SessionID, LocationTrail}, {action_other, OtherLocation}, AppInfo, _) ->
- execute_action(OtherLocation, AppInfo, Req, SessionID, LocationTrail);
+process_action_result({{Controller, _, _}, Req, SessionID, LocationTrail}, {action_other, OtherLocation}, AppInfo, _) ->
+ execute_action(process_location(Controller, OtherLocation, AppInfo), AppInfo, Req, SessionID, LocationTrail);
process_action_result({_, Req, SessionID, LocationTrail}, not_found, AppInfo, _) ->
NotFoundLocation = boss_router:handle(AppInfo#boss_app_info.router_pid, 404),
@@ -546,8 +587,8 @@ process_action_result({_, Req, SessionID, LocationTrail}, not_found, AppInfo, _)
process_action_result(Info, {redirect, Where}, AppInfo, AuthInfo) ->
process_action_result(Info, {redirect, Where, []}, AppInfo, AuthInfo);
-process_action_result({{Controller, _, _}, _, _, _}, {redirect, Where, Headers}, _, _) ->
- {redirect, process_redirect(Where, Controller), Headers};
+process_action_result({{Controller, _, _}, _, _, _}, {redirect, Where, Headers}, AppInfo, _) ->
+ {redirect, process_redirect(Controller, Where, AppInfo), Headers};
process_action_result(Info, {json, Data}, AppInfo, AuthInfo) ->
process_action_result(Info, {json, Data, []}, AppInfo, AuthInfo);
37 src/boss/boss_web_test.erl
View
@@ -1,35 +1,35 @@
% web-centric functional tests
-module(boss_web_test).
--export([start/0, start/1, run_tests/1, get_request/4, post_request/5, read_email/4]).
+-export([start/1, run_tests/1, get_request/4, post_request/5, read_email/4]).
-export([follow_link/4, follow_redirect/3, submit_form/5]).
-export([find_link_with_text/2]).
-include("boss_web.hrl").
-start() ->
- start(["mock"]).
-
-start([Adapter]) ->
- run_tests([Adapter|boss_files:test_list()]).
+start([Application]) ->
+ start([Application, "mock"]);
+start([Application, Adapter]) ->
+ run_tests([Application, Adapter|boss_files:test_list()]).
bootstrap_test_env(Application, Adapter) ->
AdapterMod = list_to_atom("boss_db_adapter_"++Adapter),
- RouterSupPid = boss_router:start([{application, Application},
+ ok = application:start(Application),
+ {ok, RouterSupPid} = boss_router:start([{application, Application},
{controllers, boss_files:web_controller_list(Application)}]),
boss_db:start([{adapter, AdapterMod}]),
boss_session:start(),
boss_mq:start(),
lists:map(fun(File) ->
- ok = boss_compiler:compile(File, [])
+ {ok, _} = boss_compiler:compile(File, [])
end, boss_files:init_file_list(Application)),
boss_news:start(),
boss_mail:start([{driver, boss_mail_driver_mock}]),
- TranslatorSupPid = boss_translator:start([{application, Application}]),
+ {ok, TranslatorSupPid} = boss_translator:start([{application, Application}]),
boss_load:load_all_modules(Application, TranslatorSupPid),
#boss_app_info{
application = Application,
base_url = "",
- watches = [],
+ init_data = [],
router_sup_pid = RouterSupPid,
translator_sup_pid = TranslatorSupPid,
model_modules = boss_files:model_list(Application),
@@ -62,6 +62,7 @@ get_request(Url, Headers, Assertions, Continuations) ->
receive
{_From, AppInfo} ->
RequesterPid = spawn(fun() -> get_request_loop(AppInfo) end),
+ link(RequesterPid),
RequesterPid ! {self(), Url, Headers},
receive_response(RequesterPid, Assertions, Continuations)
end.
@@ -75,6 +76,7 @@ post_request(Url, Headers, Contents, Assertions, Continuations) ->
receive
{_From, AppInfo} ->
RequesterPid = spawn(fun() -> post_request_loop(AppInfo) end),
+ link(RequesterPid),
RequesterPid ! {self(), Url, Headers, Contents},
receive_response(RequesterPid, Assertions, Continuations)
end.
@@ -303,7 +305,11 @@ get_request_loop(AppInfo) ->
receive
{From, Uri, Headers} ->
Req = make_request('GET', Uri, Headers),
- Result = boss_web_controller:process_request(AppInfo, Req, testing, Uri, undefined),
+ [{_, RouterPid, _, _}] = supervisor:which_children(AppInfo#boss_app_info.router_sup_pid),
+ [{_, TranslatorPid, _, _}] = supervisor:which_children(AppInfo#boss_app_info.translator_sup_pid),
+ Result = boss_web_controller:process_request(AppInfo#boss_app_info {
+ router_pid = RouterPid, translator_pid = TranslatorPid },
+ Req, testing, Uri, undefined),
From ! {self(), Uri, Result};
Other ->
error_logger:error_msg("Unexpected message in get_request_loop: ~p~n", [Other])
@@ -316,9 +322,13 @@ post_request_loop(AppInfo) ->
erlang:put(mochiweb_request_body, Body),
erlang:put(mochiweb_request_body_length, length(Body)),
erlang:put(mochiweb_request_post, mochiweb_util:parse_qs(Body)),
+ [{_, RouterPid, _, _}] = supervisor:which_children(AppInfo#boss_app_info.router_sup_pid),
+ [{_, TranslatorPid, _, _}] = supervisor:which_children(AppInfo#boss_app_info.translator_sup_pid),
Req = make_request('POST', Uri,
[{"Content-Encoding", "application/x-www-form-urlencoded"} | Headers]),
- Result = boss_web_controller:process_request(AppInfo, Req, testing, Uri, undefined),
+ Result = boss_web_controller:process_request(AppInfo#boss_app_info{
+ router_pid = RouterPid, translator_pid = TranslatorPid },
+ Req, testing, Uri, undefined),
From ! {self(), Uri, Result};
Other ->
error_logger:error_msg("Unexpected message in post_request_loop: ~p~n", [Other])
@@ -328,8 +338,7 @@ make_request(Method, Uri, Headers) ->
Req = mochiweb_request:new(
false, %Socket
Method, Uri, {1, 0}, mochiweb_headers:make(Headers)),
- DocRoot = "./static",
- simple_bridge:make_request(mochiweb_request_bridge, {Req, DocRoot}).
+ simple_bridge:make_request(mochiweb_request_bridge, Req).
receive_response(RequesterPid, Assertions, Continuations) ->
receive
12 src/boss/db_adapters/boss_db_adapter_mnesia.erl
View
@@ -41,7 +41,8 @@ find(_, Id) when is_list(Id) ->
end.
% -----
-find(_, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions), is_integer(Max),
+find(_, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions),
+ is_integer(Max) orelse Max =:= all,
is_integer(Skip), is_atom(Sort), is_atom(SortOrder) ->
% Mnesia allows a pattern to be provided against which it will check records.
% This allows 'eq' conditions to be handled by Mnesia itself. The list of remaining
@@ -107,11 +108,10 @@ apply_skip(List, Skip) when Skip >= length(List) ->
apply_skip(List, Skip) ->
lists:nthtail(Skip, List).
-apply_max(List, Max) when Max>0, length(List) > Max ->
- {MaxList,_} = lists:split(Max, List),
- MaxList;
-apply_max(List, _Max) ->
- List.
+apply_max(List, all) ->
+ List;
+apply_max(List, Max) when is_integer(Max) ->
+ lists:sublist(List, Max).
test_rec(Rec,{Key, 'not_equals', Value}) ->
apply(Rec,Key,[]) /= Value;
11 src/boss/db_adapters/boss_db_adapter_mongodb.erl
View
@@ -50,7 +50,7 @@ find(Conn, Id) when is_list(Id) ->
end.
find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions),
- is_integer(Max), is_integer(Skip),
+ is_integer(Max) orelse Max =:= all, is_integer(Skip),
is_atom(Sort), is_atom(SortOrder) ->
% ?LOG("find Type", Type),
% ?LOG("find Conditions", Conditions),
@@ -58,10 +58,11 @@ find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_
true ->
Collection = type_to_collection(Type),
Res = execute(Conn, fun() ->
- Selector = build_conditions(Conditions,
- {Sort, pack_sort_order(SortOrder)}),
- mongo:find(Collection, Selector, [],
- Skip, Max)
+ Selector = build_conditions(Conditions, {Sort, pack_sort_order(SortOrder)}),
+ case Max of
+ all -> mongo:find(Collection, Selector, [], Skip);
+ _ -> mongo:find(Collection, Selector, [], Skip, Max)
+ end
end),
case Res of
{ok, Curs} ->
15 src/boss/db_adapters/boss_db_adapter_mysql.erl
View
@@ -45,7 +45,7 @@ find(Pid, Id) when is_list(Id) ->
end.
find(Pid, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions),
- is_integer(Max), is_integer(Skip),
+ is_integer(Max) orelse Max =:= all, is_integer(Skip),
is_atom(Sort), is_atom(SortOrder) ->
case boss_record_lib:ensure_loaded(Type) of
true ->
@@ -54,9 +54,16 @@ find(Pid, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_l
case Res of
{data, MysqlRes} ->
Columns = mysql:get_result_field_info(MysqlRes),
+ ResultRows = mysql:get_result_rows(MysqlRes),
+ FilteredRows = case {Max, Skip} of
+ {all, Skip} when Skip > 0 ->
+ lists:nthtail(Skip, ResultRows);
+ _ ->
+ ResultRows
+ end,
lists:map(fun(Row) ->
activate_record(Row, Columns, Type)
- end, mysql:get_result_rows(MysqlRes));
+ end, FilteredRows);
{error, MysqlRes} ->
{error, mysql:get_result_reason(MysqlRes)}
end;
@@ -266,8 +273,8 @@ build_select_query(Type, Conditions, Max, Skip, Sort, SortOrder) ->
["SELECT * FROM ", TableName,
" WHERE ", build_conditions(Conditions),
" ORDER BY ", atom_to_list(Sort), " ", sort_order_sql(SortOrder),
- " LIMIT ", integer_to_list(Max),
- " OFFSET ", integer_to_list(Skip)
+ case Max of all -> ""; _ -> [" LIMIT ", integer_to_list(Max),
+ " OFFSET ", integer_to_list(Skip)] end
].
join([], _) -> [];
16 src/boss/db_adapters/boss_db_adapter_pgsql.erl
View
@@ -35,17 +35,23 @@ find(Conn, Id) when is_list(Id) ->
end.
find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions),
- is_integer(Max), is_integer(Skip),
+ is_integer(Max) orelse Max =:= all, is_integer(Skip),
is_atom(Sort), is_atom(SortOrder) ->
case boss_record_lib:ensure_loaded(Type) of
true ->
Query = build_select_query(Type, Conditions, Max, Skip, Sort, SortOrder),
Res = pgsql:equery(Conn, Query, []),
case Res of
- {ok, Columns, Rows} ->
+ {ok, Columns, ResultRows} ->
+ FilteredRows = case {Max, Skip} of
+ {all, Skip} when Skip > 0 ->
+ lists:nthtail(Skip, ResultRows);
+ _ ->
+ ResultRows
+ end,
lists:map(fun(Row) ->
activate_record(Row, Columns, Type)
- end, Rows);
+ end, FilteredRows);
{error, Reason} ->
{error, Reason}
end;
@@ -232,8 +238,8 @@ build_select_query(Type, Conditions, Max, Skip, Sort, SortOrder) ->
["SELECT * FROM ", TableName,
" WHERE ", build_conditions(Conditions),
" ORDER BY ", atom_to_list(Sort), " ", sort_order_sql(SortOrder),
- " LIMIT ", integer_to_list(Max),
- " OFFSET ", integer_to_list(Skip)
+ case Max of all -> ""; _ -> [" LIMIT ", integer_to_list(Max),
+ " OFFSET ", integer_to_list(Skip)] end
].
join([], _) -> [];
21 src/boss/db_adapters/boss_db_adapter_tyrant.erl
View
@@ -31,13 +31,20 @@ find(_, Id) when is_list(Id) ->
{error, Reason}
end.
-find(_, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions), is_integer(Max),
+find(_, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_list(Conditions),
+ is_integer(Max) orelse Max =:= all,
is_integer(Skip), is_atom(Sort), is_atom(SortOrder) ->
case boss_record_lib:ensure_loaded(Type) of
true ->
Query = build_query(Type, Conditions, Max, Skip, Sort, SortOrder),
- lists:map(fun({_Id, Record}) -> activate_record(Record, Type) end,
- medici:mget(medici:search(Query)));
+ ResultRows = medici:mget(medici:search(Query)),
+ FilteredRows = case {Max, Skip} of
+ {all, Skip} when Skip > 0 ->
+ lists:nthtail(Skip, ResultRows);
+ _ ->
+ ResultRows
+ end,
+ lists:map(fun({_Id, Record}) -> activate_record(Record, Type) end, FilteredRows);
false ->
[]
end.
@@ -144,7 +151,13 @@ attribute_to_colname(Attribute) ->
build_query(Type, Conditions, Max, Skip, Sort, SortOrder) ->
Query = build_conditions(Type, Conditions),
- medici:query_order(medici:query_limit(Query, Max, Skip), atom_to_list(Sort), SortOrder).
+ Query1 = apply_limit(Query, Max, Skip),
+ medici:query_order(Query1, atom_to_list(Sort), SortOrder).
+
+apply_limit(Query, all, _) ->
+ Query;
+apply_limit(Query, Max, Skip) ->
+ medici:query_limit(Query, Max, Skip).
build_conditions(Type, Conditions) ->
build_conditions1([{'_type', 'equals', atom_to_list(Type)}|Conditions], []).
71 src/simple_bridge/misultin_bridge_modules/misultin_request_bridge.erl
View
@@ -33,35 +33,58 @@ peer_ip(Req) ->
peer_port(Req) ->
Req:get(peer_port).
+header(connection, Req) ->
+ misultin_utility:get_key_value('Connection', Req:get(headers));
+header(accept, Req) ->
+ misultin_utility:get_key_value('Accept', Req:get(headers));
+header(host, Req) ->
+ misultin_utility:get_key_value('Host', Req:get(headers));
+header(if_modified_since, Req) ->
+ misultin_utility:get_key_value('If-Modified-Since', Req:get(headers));
+header(if_match, Req) ->
+ misultin_utility:get_key_value('If-Match', Req:get(headers));
+header(if_none_match, Req) ->
+ misultin_utility:get_key_value('If-None-Match', Req:get(headers));
+header(if_range, Req) ->
+ misultin_utility:get_key_value('If-Range', Req:get(headers));
+header(if_unmodified_since, Req) ->
+ misultin_utility:get_key_value('If-Unmodified-Since', Req:get(headers));
+header(range, Req) ->
+ misultin_utility:get_key_value('Range', Req:get(headers));
+header(referer, Req) ->
+ misultin_utility:get_key_value('Referer', Req:get(headers));
+header(user_agent, Req) ->
+ misultin_utility:get_key_value('User-Agent', Req:get(headers));
+header(accept_ranges, Req) ->
+ misultin_utility:get_key_value('Accept-Ranges', Req:get(headers));
+header(cookie, Req) ->
+ misultin_utility:get_key_value('Cookie', Req:get(headers));
+header(keep_alive, Req) ->
+ misultin_utility:get_key_value('Keep-Alive', Req:get(headers));
+header(location, Req) ->
+ misultin_utility:get_key_value('Location', Req:get(headers));
+header(content_length, Req) ->
+ misultin_utility:get_key_value('Content-Length', Req:get(headers));
+header(content_type, Req) ->
+ misultin_utility:get_key_value('Content-Type', Req:get(headers));
+header(content_encoding, Req) ->
+ misultin_utility:get_key_value('Content-Encoding', Req:get(headers));
+header(authorization, Req) ->
+ misultin_utility:get_key_value('Authorization', Req:get(headers));
+header(transfer_encoding, Req) ->
+ misultin_utility:get_key_value('Transfer-Encoding', Req:get(headers));
header(Header, Req) ->
-% proplists:get_value(Header, Req:get(headers))
misultin_utility:get_key_value(Header, Req:get(headers)).
headers(Req) ->
- Headers = Req:get(headers),
- F = fun(Header) -> proplists:get_value(Header, Headers) end,
- Headers1 = [
- {connection, F('Connection')},
- {accept, F('Accept')},
- {host, F('Host')},
- {if_modified_since, F('If-Modified-Since')},
- {if_match, F('If-Match')},
- {if_none_match, F('If-Range')},
- {if_unmodified_since, F('If-Unmodified-Since')},
- {range, F('Range')},
- {referer, F('Referer')},
- {user_agent, F('User-Agent')},
- {accept_ranges, F('Accept-Ranges')},
- {cookie, F('Cookie')},
- {keep_alive, F('Keep-Alive')},
- {location, F('Location')},
- {content_length, F('Content-Length')},
- {content_type, F('Content-Type')},
- {content_encoding, F('Content-Encoding')},
- {authorization, F('Authorization')},
- {transfer_encoding, F('Transfer-Encoding')}
+ Headers1 = [ connection, accept, host, if_modified_since,
+ if_match, if_none_match, if_range, if_unmodified_since,
+ range, referer, user_agent, accept_ranges, cookie,
+ keep_alive, location, content_length, content_type,
+ content_encoding, authorization, transfer_encoding
],
- [{K, V} || {K, V} <- Headers1, V /= undefined].
+ Headers2 = lists:map(fun(H) -> {H, header(H, Req)} end, Headers1),
+ [{K, V} || {K, V} <- Headers2, V /= undefined].
cookies(Req) ->
Headers = headers(Req),
72 src/simple_bridge/mochiweb_bridge_modules/mochiweb_request_bridge.erl
View
@@ -41,34 +41,60 @@ peer_port(Req) ->
{ok, {_IP, Port}} = mochiweb_socket:peername(Socket),
Port.
+header(connection, Req) ->
+ Req:get_header_value("connection");
+header(accept, Req) ->
+ Req:get_header_value("accept");
+header(host, Req) ->
+ Req:get_header_value("host");
+header(if_modified_since, Req) ->
+ Req:get_header_value("if-modified-since");
+header(if_match, Req) ->
+ Req:get_header_value("if-match");
+header(if_none_match, Req) ->
+ Req:get_header_value("if-none-match");
+header(if_unmodified_since, Req) ->
+ Req:get_header_value("if-unmodified-since");
+header(if_range, Req) ->
+ Req:get_header_value("if-range");
+header(range, Req) ->
+ Req:get_header_value("range");
+header(user_agent, Req) ->
+ Req:get_header_value("user-agent");
+header(accept_language, Req) ->
+ Req:get_header_value("accept-language");
+header(accept_ranges, Req) ->
+ Req:get_header_value("accept-ranges");
+header(cookie, Req) ->
+ Req:get_header_value("cookie");
+header(keep_alive, Req) ->
+ Req:get_header_value("keep-alive");
+header(location, Req) ->
+ Req:get_header_value("location");
+header(content_length, Req) ->
+ Req:get_header_value("content-length");
+header(content_type, Req) ->
+ Req:get_header_value("content-type");
+header(content_encoding, Req) ->
+ Req:get_header_value("content-encoding");
+header(authorization, Req) ->
+ Req:get_header_value("authorization");
+header(x_forwarded_for, Req) ->
+ Req:get_header_value("x-forwarded-for");
+header(transfer_encoding, Req) ->
+ Req:get_header_value("transfer-encoding");
header(Header, Req) ->
Req:get_header_value(Header).
headers(Req) ->
- F = fun(Header) -> Req:get_header_value(Header) end,
- Headers1 = [
- {connection, F("connection")},
- {accept, F("accept")},
- {host, F("host")},
- {if_modified_since, F("if-modified-since")},
- {if_match, F("if-match")},
- {if_none_match, F("if-range")},
- {if_unmodified_since, F("if-unmodified-since")},
- {range, F("range")},
- {referer, F("referer")},
- {user_agent, F("user-agent")},
- {accept_language, F("accept-language")},
- {accept_ranges, F("accept-ranges")},
- {cookie, F("cookie")},
- {keep_alive, F("keep-alive")},
- {location, F("location")},
- {content_length, F("content-length")},
- {content_type, F("content-type")},
- {content_encoding, F("content-encoding")},
- {authorization, F("authorization")},
- {x_forwarded_for, F("x-forwarded-for")},
- {transfer_encoding, F("transfer-encoding")}
+ Headers = [connection, accept, host, if_modified_since,
+ connection, accept, host, if_modified_since, if_match,
+ if_none_match, if_unmodified_since, if_range, range,
+ referer, user_agent, accept_language, accept_ranges,
+ cookie, keep_alive, location, content_length, content_type,
+ content_encoding, authorization, x_forwarded_for, transfer_encoding
],
+ Headers1 = lists:map(fun(H) -> {H, header(H, Req)} end, Headers),
[{K, V} || {K, V} <- Headers1, V /= undefined].
cookies(Req) ->
Please sign in to comment.
Something went wrong with that request. Please try again.