Adds a basic settings model
This commit is contained in:
parent
39caf4a94f
commit
97f899e11d
5 changed files with 197 additions and 1 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Pinchflat.Profiles.MediaProfile do
|
defmodule Pinchflat.Profiles.MediaProfile do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
A media profile is a set of settings that can be applied to many media sources
|
A media profile is a set of configuration options that can be applied to many media sources
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
|
||||||
76
lib/pinchflat/settings.ex
Normal file
76
lib/pinchflat/settings.ex
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
defmodule Pinchflat.Settings do
|
||||||
|
@moduledoc """
|
||||||
|
The Settings context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Query, warn: false
|
||||||
|
alias Pinchflat.Repo
|
||||||
|
|
||||||
|
alias Pinchflat.Settings.Setting
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of settings.
|
||||||
|
|
||||||
|
Returns [%Setting{}, ...]
|
||||||
|
"""
|
||||||
|
def list_settings do
|
||||||
|
Repo.all(Setting)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates or updates a setting, returning the parsed value.
|
||||||
|
Raises if an unsupported datatype is used.
|
||||||
|
|
||||||
|
Returns value in type of `Ecto.Enum.mappings(Setting, :datatype)`
|
||||||
|
"""
|
||||||
|
def set!(name, value) do
|
||||||
|
set!(name, value, infer_datatype(value))
|
||||||
|
end
|
||||||
|
|
||||||
|
def set!(name, value, datatype) do
|
||||||
|
# Only create if doesn't exist
|
||||||
|
case Repo.get_by(Setting, name: to_string(name)) do
|
||||||
|
nil -> create_setting!(name, value, datatype)
|
||||||
|
setting -> update_setting!(setting, value, datatype)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets the parsed value of a setting. Raises if the setting does not exist.
|
||||||
|
|
||||||
|
Returns value in type of `Ecto.Enum.mappings(Setting, :datatype)`
|
||||||
|
"""
|
||||||
|
def get!(name) do
|
||||||
|
Setting
|
||||||
|
|> Repo.get_by!(name: to_string(name))
|
||||||
|
|> read_setting()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp change_setting(setting, attrs) do
|
||||||
|
Setting.changeset(setting, attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_setting!(name, value, datatype) do
|
||||||
|
%Setting{}
|
||||||
|
|> change_setting(%{name: to_string(name), value: to_string(value), datatype: datatype})
|
||||||
|
|> Repo.insert!()
|
||||||
|
|> read_setting()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_setting!(setting, value, datatype) do
|
||||||
|
setting
|
||||||
|
|> change_setting(%{value: to_string(value), datatype: datatype})
|
||||||
|
|> Repo.update!()
|
||||||
|
|> read_setting()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_setting(%{value: value, datatype: :string}), do: value
|
||||||
|
defp read_setting(%{value: value, datatype: :boolean}), do: value in ["true", "t", "1"]
|
||||||
|
defp read_setting(%{value: value, datatype: :integer}), do: String.to_integer(value)
|
||||||
|
defp read_setting(%{value: value, datatype: :float}), do: String.to_float(value)
|
||||||
|
|
||||||
|
defp infer_datatype(value) when is_boolean(value), do: :boolean
|
||||||
|
defp infer_datatype(value) when is_integer(value), do: :integer
|
||||||
|
defp infer_datatype(value) when is_float(value), do: :float
|
||||||
|
defp infer_datatype(value) when is_binary(value), do: :string
|
||||||
|
end
|
||||||
24
lib/pinchflat/settings/setting.ex
Normal file
24
lib/pinchflat/settings/setting.ex
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule Pinchflat.Settings.Setting do
|
||||||
|
@moduledoc """
|
||||||
|
A Setting is a key-value pair with a datatype used to track user-level settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "settings" do
|
||||||
|
field :name, :string
|
||||||
|
field :value, :string
|
||||||
|
field :datatype, Ecto.Enum, values: ~w(boolean string integer float)a
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(setting, attrs) do
|
||||||
|
setting
|
||||||
|
|> cast(attrs, [:name, :value, :datatype])
|
||||||
|
|> validate_required([:name, :value, :datatype])
|
||||||
|
|> unique_constraint([:name])
|
||||||
|
end
|
||||||
|
end
|
||||||
15
priv/repo/migrations/20240306173305_create_settings.exs
Normal file
15
priv/repo/migrations/20240306173305_create_settings.exs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pinchflat.Repo.Migrations.CreateSettings do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:settings) do
|
||||||
|
add :name, :string, null: false
|
||||||
|
add :value, :string, null: false
|
||||||
|
add :datatype, :string, null: false
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
create unique_index(:settings, [:name])
|
||||||
|
end
|
||||||
|
end
|
||||||
81
test/pinchflat/settings_test.exs
Normal file
81
test/pinchflat/settings_test.exs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
defmodule Pinchflat.SettingsTest do
|
||||||
|
use Pinchflat.DataCase
|
||||||
|
|
||||||
|
alias Pinchflat.Settings
|
||||||
|
alias Pinchflat.Settings.Setting
|
||||||
|
|
||||||
|
describe "list_settings/0" do
|
||||||
|
test "returns all settings" do
|
||||||
|
Settings.set!("foo", "bar")
|
||||||
|
assert [_] = Settings.list_settings()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "set/2" do
|
||||||
|
test "creates a new setting if one does not exist" do
|
||||||
|
assert Repo.aggregate(Setting, :count, :id) == 0
|
||||||
|
Settings.set!("foo", "bar")
|
||||||
|
assert Repo.aggregate(Setting, :count, :id) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates an existing setting if one exists" do
|
||||||
|
Settings.set!("foo", "bar")
|
||||||
|
assert Repo.aggregate(Setting, :count, :id) == 1
|
||||||
|
Settings.set!("foo", "baz")
|
||||||
|
assert Repo.aggregate(Setting, :count, :id) == 1
|
||||||
|
assert Settings.get!("foo") == "baz"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns the parsed value" do
|
||||||
|
assert Settings.set!("foo", true) == true
|
||||||
|
assert Settings.set!("foo", false) == false
|
||||||
|
assert Settings.set!("foo", 123) == 123
|
||||||
|
assert Settings.set!("foo", 12.34) == 12.34
|
||||||
|
assert Settings.set!("foo", "bar") == "bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows for atom keys" do
|
||||||
|
assert Settings.set!(:foo, "bar") == "bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "blows up when an unsupported datatype is used" do
|
||||||
|
assert_raise FunctionClauseError, fn ->
|
||||||
|
Settings.set!("foo", nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "set/3" do
|
||||||
|
test "allows manual specification of datatype" do
|
||||||
|
assert Settings.set!("foo", "true", :boolean) == true
|
||||||
|
assert Settings.set!("foo", "false", :boolean) == false
|
||||||
|
assert Settings.set!("foo", "123", :integer) == 123
|
||||||
|
assert Settings.set!("foo", "12.34", :float) == 12.34
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get/1" do
|
||||||
|
test "returns the value of the setting" do
|
||||||
|
Settings.set!("str", "bar")
|
||||||
|
Settings.set!("bool", true)
|
||||||
|
Settings.set!("int", 123)
|
||||||
|
Settings.set!("float", 12.34)
|
||||||
|
|
||||||
|
assert Settings.get!("str") == "bar"
|
||||||
|
assert Settings.get!("bool") == true
|
||||||
|
assert Settings.get!("int") == 123
|
||||||
|
assert Settings.get!("float") == 12.34
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows for atom keys" do
|
||||||
|
Settings.set!("str", "bar")
|
||||||
|
assert Settings.get!(:str) == "bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "blows up when the setting does not exist" do
|
||||||
|
assert_raise Ecto.NoResultsError, fn ->
|
||||||
|
Settings.get!("foo")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue