Skip to content

Commit

Permalink
Support for pre-caching belongs_to associations
Browse files Browse the repository at this point in the history
  • Loading branch information
evanmiller committed Feb 8, 2013
1 parent 8f589ef commit b9b280d
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 26 deletions.
32 changes: 14 additions & 18 deletions src/boss_db.erl
Expand Up @@ -12,7 +12,6 @@
find_first/3,
find_last/2,
find_last/3,
get/1,
count/1,
count/2,
counter/1,
Expand Down Expand Up @@ -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'
Expand All @@ -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),
Expand All @@ -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'
Expand Down
22 changes: 20 additions & 2 deletions src/boss_db_controller.erl
Expand Up @@ -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,
Expand All @@ -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};

Expand Down
17 changes: 11 additions & 6 deletions src/boss_record_compiler.erl
Expand Up @@ -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)
)
])]))
].
Expand All @@ -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(
[
Expand All @@ -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(
[
Expand All @@ -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(
Expand All @@ -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)
)
])]))
].
Expand All @@ -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),
Expand All @@ -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))])
])
]).

Expand Down

0 comments on commit b9b280d

Please sign in to comment.