Permalink
Browse files

Support for pre-caching belongs_to associations

  • Loading branch information...
1 parent 8f589ef commit b9b280d323df8cea5ae540e9d4ae3dcba4bee24e @evanmiller evanmiller committed Feb 8, 2013
Showing with 45 additions and 26 deletions.
  1. +14 −18 src/boss_db.erl
  2. +20 −2 src/boss_db_controller.erl
  3. +11 −6 src/boss_record_compiler.erl
View
@@ -12,7 +12,6 @@
find_first/3,
find_last/2,
find_last/3,
- get/1,
count/1,
count/2,
counter/1,
@@ -65,25 +64,20 @@ db_call(Msg) ->
Reply
end.
-%% @spec find(Id::string()) -> BossRecord | {error, Reason}
-%% @doc Find a BossRecord with the specified `Id'.
+%% @spec find(Id::string()) -> Value | {error, Reason}
+%% @doc Find a BossRecord with the specified `Id' (e.g. "employee-42") or a value described
+%% by a dot-separated path (e.g. "employee-42.manager.name").
find("") -> undefined;
find(Key) when is_list(Key) ->
- db_call({find, Key});
+ [IdToken|Rest] = string:tokens(Key, "."),
+ case db_call({find, IdToken}) of
+ undefined -> undefined;
+ {error, Reason} -> {error, Reason};
+ BossRecord -> BossRecord:get(string:join(Rest, "."))
+ end;
find(_) ->
{error, invalid_id}.
-%% @spec get(Path::string()) -> Value | {error, Reason} | undefined
-%% @doc Find a BossRecord or value described by the dot-separated `Path' (e.g., "employee-42.manager.name").
-get(Path) when is_list(Path) ->
- [IdToken|Rest] = string:tokens(Path, "."),
- case find(IdToken) of
- {error, Reason} ->
- {error, Reason};
- BossRecord ->
- BossRecord:get(string:join(Rest, "."))
- end.
-
%% @spec find(Type::atom(), Conditions) -> [ BossRecord ]
%% @doc Query for BossRecords. Returns all BossRecords of type
%% `Type' matching all of the given `Conditions'
@@ -94,8 +88,9 @@ find(Type, Conditions) ->
%% @doc Query for BossRecords. Returns BossRecords of type
%% `Type' matching all of the given `Conditions'. Options may include
%% `limit' (maximum number of records to return), `offset' (number of records
-%% to skip), `order_by' (attribute to sort on), and `descending' (whether to
-%% sort the values from highest to lowest)
+%% to skip), `order_by' (attribute to sort on), `descending' (whether to
+%% sort the values from highest to lowest), and `include' (list of belongs_to
+%% associations to pre-cache)
find(Type, Conditions, Options) ->
Max = proplists:get_value(limit, Options, all),
Skip = proplists:get_value(offset, Options, 0),
@@ -104,7 +99,8 @@ find(Type, Conditions, Options) ->
true -> descending;
_ -> ascending
end,
- db_call({find, Type, normalize_conditions(Conditions), Max, Skip, Sort, SortOrder}).
+ Include = proplists:get_value(include, Options, []),
+ db_call({find, Type, normalize_conditions(Conditions), Max, Skip, Sort, SortOrder, Include}).
%% @spec find_first( Type::atom(), Conditions ) -> Record | undefined
%% @doc Query for the first BossRecord of type `Type' matching all of the given `Conditions'
View
@@ -71,14 +71,32 @@ handle_call({find, Key}, _From, #state{ cache_enable = false } = State) ->
{Adapter, Conn} = db_for_key(Key, State),
{reply, Adapter:find(Conn, Key), State};
-handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder} = Cmd, From,
+handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder, Include} = Cmd, From,
#state{ cache_enable = true, cache_prefix = Prefix } = State) ->
Key = {Type, Conditions, Max, Skip, Sort, SortOrder},
case boss_cache:get(Prefix, Key) of
undefined ->
{reply, Res, _} = handle_call(Cmd, From, State#state{ cache_enable = false }),
case is_list(Res) of
true ->
+ DummyRecord = boss_record_lib:dummy_record(Type),
+ BelongsToNames = DummyRecord:belongs_to_names(),
+ IncludedRecords = lists:foldl(fun
+ ({RelationshipName, InnerInclude}, Acc) ->
+ RecordList = case lists:member(RelationshipName, BelongsToNames) of
+ true ->
+ IdList = lists:map(fun(Record) -> Record:id() end, Res),
+ handle_call({find, RelationshipName,
+ [{'id', 'in', IdList}], all, 0, id, ascending,
+ InnerInclude}, From, State);
+ _ ->
+ []
+ end,
+ RecordList ++ Acc
+ end, [], lists:map(fun({R, I}) -> {R, I}; (R) -> {R, []} end, Include)),
+ lists:map(fun(Rec) ->
+ boss_cache:set(Prefix, Rec:id(), Rec, State#state.cache_ttl)
+ end, IncludedRecords),
boss_cache:set(Prefix, Key, Res, State#state.cache_ttl),
WatchString = lists:concat([inflector:pluralize(atom_to_list(Type)), ", ", Type, "-*.*"]),
boss_news:set_watch(Key, WatchString, fun boss_db_cache:handle_collection_news/3,
@@ -90,7 +108,7 @@ handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder} = Cmd, From,
boss_news:extend_watch(Key),
{reply, CachedValue, State}
end;
-handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder}, _From, #state{ cache_enable = false } = State) ->
+handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder, _}, _From, #state{ cache_enable = false } = State) ->
{Adapter, Conn} = db_for_type(Type, State),
{reply, Adapter:find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder), State};
@@ -379,14 +379,15 @@ attribute_names_forms(ModuleName, Parameters) ->
has_one_forms(HasOne, ModuleName, Opts) ->
Type = proplists:get_value(module, Opts, HasOne),
ForeignKey = proplists:get_value(foreign_key, Opts, atom_to_list(ModuleName) ++ "_id"),
+ Include = proplists:get_value(include, Opts, []),
[erl_syntax:add_precomments([erl_syntax:comment(
[lists:concat(["% @spec ", HasOne, "() -> ", Type, " | undefined"]),
lists:concat(["% @doc Retrieves the `", Type, "' with `", ForeignKey, "' ",
"set to the `Id' of this `", ModuleName, "'"])])],
erl_syntax:function(erl_syntax:atom(HasOne),
[erl_syntax:clause([], none, [
first_or_undefined_forms(
- has_many_application_forms(Type, ForeignKey, 1, id, false)
+ has_many_application_forms(Type, ForeignKey, 1, id, false, Include)
)
])]))
].
@@ -398,6 +399,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
IsDescending = proplists:get_value(descending, Opts, false),
Singular = inflector:singularize(atom_to_list(HasMany)),
Type = proplists:get_value(module, Opts, Singular),
+ Include = proplists:get_value(include, Opts, []),
ForeignKey = proplists:get_value(foreign_key, Opts, atom_to_list(ModuleName) ++ "_id"),
[erl_syntax:add_precomments([erl_syntax:comment(
[
@@ -406,7 +408,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
"set to the `Id' of this `", ModuleName, "'"])])],
erl_syntax:function(erl_syntax:atom(HasMany),
[erl_syntax:clause([], none, [
- has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending)
+ has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending, Include)
])])),
erl_syntax:add_precomments([erl_syntax:comment(
[
@@ -416,7 +418,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
erl_syntax:function(erl_syntax:atom("first_"++Singular),
[erl_syntax:clause([], none, [
first_or_undefined_forms(
- has_many_application_forms(Type, ForeignKey, 1, Sort, IsDescending)
+ has_many_application_forms(Type, ForeignKey, 1, Sort, IsDescending, Include)
)
])])),
erl_syntax:add_precomments([erl_syntax:comment(
@@ -427,7 +429,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
erl_syntax:function(erl_syntax:atom("last_"++Singular),
[erl_syntax:clause([], none, [
first_or_undefined_forms(
- has_many_application_forms(Type, ForeignKey, 1, Sort, not IsDescending)
+ has_many_application_forms(Type, ForeignKey, 1, Sort, not IsDescending, Include)
)
])]))
].
@@ -438,7 +440,7 @@ first_or_undefined_forms(Forms) ->
[erl_syntax:variable(?PREFIX++"Record")]),
erl_syntax:clause([erl_syntax:underscore()], none, [erl_syntax:atom(undefined)])]).
-has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending) ->
+has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending, Include) ->
erl_syntax:application(
erl_syntax:atom(?DATABASE_MODULE),
erl_syntax:atom(find),
@@ -457,7 +459,10 @@ has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending) ->
erl_syntax:atom(Sort)]),
erl_syntax:tuple([
erl_syntax:atom(descending),
- erl_syntax:atom(IsDescending)])
+ erl_syntax:atom(IsDescending)]),
+ erl_syntax:tuple([
+ erl_syntax:atom(include),
+ erl_syntax:list(lists:map(fun erl_syntax:atom/1, Include))])
])
]).

0 comments on commit b9b280d

Please sign in to comment.