Skip to content
This repository was archived by the owner on Mar 10, 2021. It is now read-only.

Cache store #132

Closed
wants to merge 9 commits into from
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<li>Page meta title and description editing</li>
<li>Easily bring your own authentication system in one tiny function</li>
<li>Create new dynamic pages, delete dynamic pages</li>
<li>Content caching to avoid too many trips to the database</li>
</ul>
</td>
</tr>
Expand Down Expand Up @@ -108,6 +109,29 @@ as generate migrations and an authorization module in your `lib/thesis_auth.ex`.
```
$ mix ecto.migrate
```

##### Page content caching

To enable content caching, add this in your config/config.exs:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe flesh out the paragraph here, give a little more background regarding why this is desirable.


```elixir
config :thesis,
enable_cache: true
cache_name: :thesis_cache # This is the default
```

Your application will also need to start ConCache, typically from a supervisor like so:

```elixir
import Supervisor.Spec

Supervisor.start_link([
...
supervisor(ConCache, [[], [name: :thesis_cache]]),
...
])
```

<br/>

---
Expand Down
3 changes: 2 additions & 1 deletion examples/example-phx-1_2/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ config :logger, :console,
config :thesis,
store: Thesis.EctoStore,
authorization: Example.ThesisAuth,
uploader: Thesis.RepoUploader
uploader: Thesis.RepoUploader,
enable_cache: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to allow them to specify their own cache store if they want, rather than just a boolean true/false.

To do that, let's change this to cache: Thesis.CacheStore. We can still allow them to specify a cache_name if they want.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I like your solution a lot better :) The one thing I'm not crazy about though is the cache_name option - that's really a specific sub-setting. What about if we could do something like:

config :thesis, cache: {Thesis.CacheStore, [other: :options, can: "go here"]}

And then our store behavior functions could (optionally with a [] default) accept those options as a second argument like so:

def page(slug, [cache_name: name]) do
  ConCache.get_or_store(name, slug, fn() ->
    store().page(slug)
  end)
end

def page_contents(slug, opts) when is_binary(slug) do
  page_contents(page(slug, opts))
end

Users will be able to pass in configuration values from the thesis config to their own custom stores or caches without having to either hardcode it in their own modules or set up their own additional config.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've got me thinking, @anthonylebrun ... I think to be consistent, we'd use this strategy instead (note the separate Thesis.EctoStore config just below the main config):

# Configure thesis content editor
config :thesis,
  store: Thesis.EctoStore,
  cache: Thesis.CacheStore,
  authorization: Example.ThesisAuth,
  uploader: Thesis.RepoUploader

config :thesis, Thesis.EctoStore, repo: Example.Repo

config :thesis, Thesis.CacheStore,
  cache_name: "whut_whut",
  other_option: "heyo"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! This seems more idiomatic than what I was suggesting :) I'm still learning those Elixir conventions.


# config :thesis, Thesis.OspryUploader,
# ospry_public_key: System.get_env("OSPRY_PUBLIC_KEY")
Expand Down
1 change: 1 addition & 0 deletions examples/example-phx-1_2/lib/example.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ defmodule Example do
supervisor(Example.Endpoint, []),
# Start your own worker by calling: Example.Worker.start_link(arg1, arg2, arg3)
# worker(Example.Worker, [arg1, arg2, arg3]),
supervisor(ConCache, [[], [name: :thesis_cache]])
]

# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
Expand Down
2 changes: 2 additions & 0 deletions examples/example-phx-1_2/mix.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
%{"certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"},
"con_cache": {:hex, :con_cache, "0.12.1", "7553dcd51ee86fd52bd9ea9aa4b33e71bebf0b5fc5ab60e63d2e0bcaa260f937", [], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.1", "ccc6fd304f9bb785f2c3cfd0ee8da6bad6544ab12ca5f7162b20a743d938417c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"exactor": {:hex, :exactor, "2.2.3", "a6972f43bb6160afeb73e1d8ab45ba604cd0ac8b5244c557093f6e92ce582786", [], [], "hexpm"},
"file_system": {:hex, :file_system, "0.2.1", "c4bec8f187d2aabace4beb890f0d4e468f65ca051593db768e533a274d0df587", [], [], "hexpm"},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
Expand Down
3 changes: 2 additions & 1 deletion examples/example-phx-1_3/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ import_config "#{Mix.env}.exs"
config :thesis,
store: Thesis.EctoStore,
authorization: ExamplePhx.ThesisAuth,
uploader: Thesis.RepoUploader
uploader: Thesis.RepoUploader,
# uploader: <MyApp>.<CustomUploaderModule>
# uploader: Thesis.OspryUploader
enable_cache: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change to:

cache: Thesis.CacheStore

config :thesis, Thesis.EctoStore, repo: ExamplePhx.Repo
# config :thesis, Thesis.OspryUploader,
# ospry_public_key: "pk-prod-asdfasdfasdfasdf"
Expand Down
1 change: 1 addition & 0 deletions examples/example-phx-1_3/lib/example_phx/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule ExamplePhx.Application do
supervisor(ExamplePhxWeb.Endpoint, []),
# Start your own worker by calling: ExamplePhx.Worker.start_link(arg1, arg2, arg3)
# worker(ExamplePhx.Worker, [arg1, arg2, arg3]),
supervisor(ConCache, [[], [name: :thesis_cache]])
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
2 changes: 2 additions & 0 deletions examples/example-phx-1_3/mix.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
%{"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
"con_cache": {:hex, :con_cache, "0.12.1", "7553dcd51ee86fd52bd9ea9aa4b33e71bebf0b5fc5ab60e63d2e0bcaa260f937", [], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.4", "defde3c8eca385bd86466d2e1491d19e77f9b79ad996dc8e89e4e107f3942f40", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"exactor": {:hex, :exactor, "2.2.3", "a6972f43bb6160afeb73e1d8ab45ba604cd0ac8b5244c557093f6e92ce582786", [], [], "hexpm"},
"file_system": {:hex, :file_system, "0.2.1", "c4bec8f187d2aabace4beb890f0d4e468f65ca051593db768e533a274d0df587", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
Expand Down
8 changes: 4 additions & 4 deletions lib/thesis/api_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ defmodule Thesis.ApiController do
def assets(conn, _params), do: conn

def update(conn, %{"contents" => contents, "page" => page}) do
{:ok, _page} = store().update(page, contents)
{:ok, _page} = cache().update(page, contents)
json conn, %{}
end

def delete(conn, %{"path" => path}) do
{:ok, _page} = store().delete(%{"slug" => path})
{:ok, _page} = cache().delete(%{"slug" => path})
json conn, %{}
end

def backups_for_page(conn, %{"page_slug" => page_slug}) do
backups =
page_slug
|> store().backups()
|> cache().backups()
|> Enum.map(&Backup.with_pretty_datetime/1)
|> Enum.map(fn b ->
%{
Expand All @@ -36,7 +36,7 @@ defmodule Thesis.ApiController do
end

def restore(conn, %{"backup_id" => backup_id}) do
backup = store().restore(String.to_integer(backup_id))
backup = cache().restore(String.to_integer(backup_id))
json conn, %{revision: backup.page_json}
end

Expand Down
12 changes: 12 additions & 0 deletions lib/thesis/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ defmodule Thesis.Config do
Application.get_env(:thesis, :store)
end

def enable_cache do
Application.get_env(:thesis, :enable_cache, false)
end

def cache_name do
Application.get_env(:thesis, :cache_name, :thesis_cache)
end

def cache do
if enable_cache(), do: Thesis.CacheStore, else: store()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change to:

def cache do
  Application.get_env(:thesis, :cache, store())
end

end

def dynamic_pages do
Application.get_env(:thesis, :dynamic_pages)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/thesis/controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ defmodule Thesis.Controller.Plug do

def call(conn, _opts) do
path = Thesis.Utilities.normalize_path(conn.request_path)
current_page = store().page(path)
page_contents = store().page_contents(current_page)
current_page = cache().page(path)
page_contents = cache().page_contents(current_page)

conn
|> assign(:thesis_dynamic_page, false) # Overridden in render_dynamic/2
Expand Down
63 changes: 63 additions & 0 deletions lib/thesis/stores/cache_store.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Thesis.CacheStore do
@moduledoc """
Thesis.CacheStore is a proxy store that acts in conjunction with a proper
store like Thesis.EctoStore to provide caching and faster page load times.
It can be enabled by setting `Thesis.Config.enable_cache` is set to `true`.
"""

@behaviour Thesis.Store

import Thesis.Config
alias Thesis.{Page, PageContent}

def page(slug) do
ConCache.get_or_store(cache_name(), slug, fn() ->
store().page(slug)
end)
end

def page_contents(slug) when is_binary(slug) do
page_contents(page(slug))
end

def page_contents(nil) do
cache_get(:global, fn() ->
store().page_contents(nil)
end)
end

def page_contents(page = %Page{id: page_id}) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's decouple this from Thesis.EctoStore and use a map instead of %Page here.

def page_contents(page = %{id: page_id}) do

cache_get(page_id, fn() ->
store().page_contents(page)
end)
end

def update(page_params = %{"slug" => slug}, contents_params) do
cache_delete(slug)
store().page_contents(slug) |> Enum.each(fn(%PageContent{page_id: page_id}) ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's decouple this from Thesis.EctoStore and use a map instead of %PageContent here.

store().page_contents(slug) |> Enum.each(fn(%{page_id: page_id}) ->

We should probably review the rest of the code base and make sure we're doing that elsewhere too. :)

cache_delete(page_id)
end)
store().update(page_params, contents_params)
end

def delete(params = %{"slug" => slug}) do
ConCache.delete(cache_name(), slug)
store().delete(params)
end

def restore(id) do
store().restore(id)
end

def backups(page_slug_or_id) do
store().backups(page_slug_or_id)
end

defp cache_get(key, fun) do
ConCache.get_or_store(cache_name(), key, fun)
end

defp cache_delete(key) do
ConCache.delete(cache_name(), key)
end
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Thesis.Mixfile do
end

def application do
[extra_applications: [:logger]]
[extra_applications: [:logger, :con_cache]]
end

defp deps do
Expand All @@ -28,6 +28,7 @@ defmodule Thesis.Mixfile do
{:plug, ">= 1.0.0"},
{:poison, ">= 1.0.0"},
{:httpoison, ">= 0.11.0"},
{:con_cache, "~> 0.12.1"},
{:html_sanitize_ex, ">= 1.3.0"},
{:lz_string, "~> 0.0.7"},
{:ex_doc, ">= 0.12.0", only: [:dev]},
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
%{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
"con_cache": {:hex, :con_cache, "0.12.1", "7553dcd51ee86fd52bd9ea9aa4b33e71bebf0b5fc5ab60e63d2e0bcaa260f937", [], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "0.8.6", "335f723772d35da499b5ebfdaf6b426bfb73590b6fcbc8908d476b75f8cbca3f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.3", "b1896b129db30d54073bedd5f3ba8a99dd6d64ebae3cf59057ae287060b46905", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.16.4", "4bf6b82d4f0a643b500366ed7134896e8cccdbab4d1a7a35524951b25b1ec9f0", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"exactor": {:hex, :exactor, "2.2.3", "a6972f43bb6160afeb73e1d8ab45ba604cd0ac8b5244c557093f6e92ce582786", [], [], "hexpm"},
"hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
Expand Down