Skip to content

Commit

Permalink
feat: add tests (#45)
Browse files Browse the repository at this point in the history
* add tests for actions

* rename migration

* add tests for commonchanges

* add tests for commonfilters

* add coveralls.json

* ignore test/support in coverage

* add tests for comparison filter

* remove IO.inspect

* add tests for common and schema query builders

* make credo happy

* remove private changeset function

* revert common filter change

* update function for consistency

* rename convert_to_field_comparison_filter, remove SchemasPG.Support.Repo from docs, and add warnings_as_errors for ci
  • Loading branch information
cylkdev authored Sep 14, 2024
1 parent e177535 commit e850292
Show file tree
Hide file tree
Showing 32 changed files with 3,380 additions and 199 deletions.
17 changes: 15 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@
# and its dependencies with the aid of the Mix.Config module.
import Config

config :ecto_shorts, repo: nil, error_module: EctoShorts.Actions.Error
config :ecto_shorts,
repo: nil,
error_module: EctoShorts.Actions.Error

if Mix.env() == :test do
config :ecto_shorts, repo: EctoShorts.Support.TestRepo
config :ecto_shorts, ecto_repos: [EctoShorts.Support.Repo]
config :ecto_shorts, repo: EctoShorts.Support.Repo
config :ecto_shorts, :sql_sandbox, true
config :ecto_shorts, EctoShorts.Support.Repo,
username: "postgres",
database: "ecto_shorts",
hostname: "localhost",
show_sensitive_data_on_connection_error: true,
log: :debug,
stacktrace: true,
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 10
end
17 changes: 17 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"terminal_options": {
"file_column_width": 75
},
"coverage_options": {
"html_filter_full_covered": true,
"treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 0
},
"custom_stop_words": [
"defdelegate"
],
"skip_files": [
"lib/ecto_shorts.ex",
"test/support/*"
]
}
24 changes: 14 additions & 10 deletions lib/actions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ defmodule EctoShorts.Actions do
@type schema_list :: list(Ecto.Schema.t)
@type schema_res :: {:ok, Ecto.Schema.t} | {:error, any}


alias EctoShorts.{CommonFilters, Actions.Error, Config}
alias EctoShorts.{Actions.Error, CommonFilters, Config}

@doc """
Gets a schema from the database
Expand Down Expand Up @@ -292,7 +291,6 @@ defmodule EctoShorts.Actions do
end
end


@doc """
Updates a schema with given updates. Can also accept a keyword options list.
Expand All @@ -312,7 +310,6 @@ defmodule EctoShorts.Actions do
iex> schema.first_name === user.first_name
true
"""

@spec update(
schema :: Ecto.Schema.t() | module(),
id :: pos_integer | String.t(),
Expand All @@ -338,7 +335,6 @@ defmodule EctoShorts.Actions do

def update(schema, schema_data, updates, opts \\ [])


def update(schema, schema_id, updates, opts) when is_integer(schema_id) or is_binary(schema_id) do
case get(schema, schema_id, opts) do
nil ->
Expand All @@ -355,13 +351,10 @@ defmodule EctoShorts.Actions do
end
end


def update(schema, schema_data, updates, opts) when is_list(updates) do
update(schema, schema_data, Map.new(updates), opts)
end



def update(schema, schema_data, updates, opts) do
repo!(opts).update(schema.changeset(schema_data, updates), opts)
end
Expand Down Expand Up @@ -400,7 +393,12 @@ defmodule EctoShorts.Actions do
"""
@spec delete(schema_data :: Ecto.Schema.t | schema_list() | module(), opts) :: {:ok, Ecto.Schema.t} | {:error, any()}
def delete(%schema{} = schema_data, opts) do
case repo!(opts).delete(schema_data, opts) do
# The schema data is wrapped in a changeset before delete
# so that ecto can apply the constraint error to the
# changeset instead of raising `Ecto.ConstraintError`.
changeset = schema.changeset(schema_data, %{})

case repo!(opts).delete(changeset, opts) do
{:error, changeset} ->
{:error, Error.call(
:internal_server_error,
Expand All @@ -419,6 +417,7 @@ defmodule EctoShorts.Actions do
def delete(schema, id) when is_atom(schema) and (is_binary(id) or is_integer(id)) do
delete(schema, id, default_opts())
end

@doc """
Deletes a schema. Can also accept a keyword options list.
Expand Down Expand Up @@ -509,6 +508,9 @@ defmodule EctoShorts.Actions do
end
end

def find_or_create_many(schema, param_list) do
find_or_create_many(schema, param_list, default_opts())
end

defp find_many(schema, param_list, opts) do
param_list
Expand Down Expand Up @@ -536,7 +538,9 @@ defmodule EctoShorts.Actions do
if Code.ensure_loaded?(schema) and function_exported?(schema, :create_changeset, 1) do
schema.create_changeset(params)
else
schema.changeset(struct(schema, %{}), params)
schema_data = struct(schema)

schema.changeset(schema_data, params)
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/actions/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ defmodule EctoShorts.Actions.Error do
@callback create_error(atom, String.t, map) :: t

def call(code, message, details) do
apply(error_module(), :create_error, [code, message, details])
module = error_module()

module.create_error(code, message, details)
end

def error_module, do: Application.get_env(:ecto_shorts, :error_module) || EctoShorts.Actions.Error
Expand Down
95 changes: 61 additions & 34 deletions lib/common_changes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ defmodule EctoShorts.CommonChanges do
cast_assoc: 3
]

alias EctoShorts.{Actions, SchemaHelpers, Config}
alias Ecto.Changeset
alias EctoShorts.{Actions, Config, SchemaHelpers}

@doc "Run's changeset function if when function returns true"
@spec put_when(
Expand All @@ -79,22 +79,47 @@ defmodule EctoShorts.CommonChanges do
end
end

@doc "Checks if field on changeset is empty list in data or changes"
@doc """
Returns true if the field on the changeset is an empty list in
the data or changes.
### Examples
iex> EctoShorts.CommonChanges.changeset_field_empty?(changeset, :comments)
"""
@spec changeset_field_empty?(Changeset.t, atom) :: boolean
def changeset_field_empty?(changeset, key) do
get_field(changeset, key) === []
end

@doc "Checks if field on changeset is nil in data or changes"
@doc """
Returns true if the field on the changeset is nil in the data
or changes.
### Examples
iex> EctoShorts.CommonChanges.changeset_field_nil?(changeset, :comments)
"""
@spec changeset_field_nil?(Changeset.t, atom) :: boolean
def changeset_field_nil?(changeset, key) do
is_nil(get_field(changeset, key))
changeset |> get_field(key) |> is_nil()
end

@doc """
This function is the primary use function
Preloads changeset assoc if change is made and then and put_or_cast's it
### Options
* `required_when_missing` - Sets `:required` to true if the
field is `nil` in both changes and data. See the
`:required` option documentation for details.
* `:required` - Indicates if the association is mandatory.
For one-to-one associations, a non-nil value satisfies
this validation. For many associations, a non-empty list
is sufficient. See [Ecto.Changeset.cast_assoc/3](https://hexdocs.pm/ecto/Ecto.Changeset.html#cast_assoc/3)
for more information.
## Example
Expand All @@ -103,31 +128,32 @@ defmodule EctoShorts.CommonChanges do
iex> CommonChanges.preload_change_assoc(changeset, :my_relation, required: true)
iex> CommonChanges.preload_change_assoc(changeset, :my_relation, required_when_missing: :my_relation_id)
"""
@spec preload_change_assoc(Changeset.t, atom, keyword()) :: Changeset.t
@spec preload_change_assoc(Changeset.t, atom) :: Changeset.t
@spec preload_change_assoc(Changeset.t(), atom(), keyword()) :: Changeset.t
def preload_change_assoc(changeset, key, opts) do
required? = if opts[:required_when_missing] do
changeset_field_nil?(changeset, opts[:required_when_missing])
else
opts[:required]
end
required? =
if opts[:required_when_missing] do
changeset_field_nil?(changeset, opts[:required_when_missing])
else
opts[:required] === true
end

opts = Keyword.put(opts, :required, required?)

if Map.has_key?(changeset.params, Atom.to_string(key)) do
changeset
|> preload_changeset_assoc(key, opts)
|> put_or_cast_assoc(key, opts)
|> preload_changeset_assoc(key, opts)
|> put_or_cast_assoc(key, opts)
else
cast_assoc(changeset, key, opts)
end
end

@spec preload_change_assoc(Changeset.t(), atom()) :: Changeset.t
def preload_change_assoc(changeset, key) do
if Map.has_key?(changeset.params, Atom.to_string(key)) do
changeset
|> preload_changeset_assoc(key)
|> put_or_cast_assoc(key)
|> preload_changeset_assoc(key)
|> put_or_cast_assoc(key)
else
cast_assoc(changeset, key)
end
Expand All @@ -154,14 +180,13 @@ defmodule EctoShorts.CommonChanges do

defp changeset_relationship_schema(changeset, key) do
if Map.has_key?(changeset.types, key) and relationship_exists?(changeset.types[key]) do
changeset.types
|> Map.get(key)
|> elem(1)
|> Map.get(:queryable)
{:assoc, assoc} = Map.get(changeset.types, key)

assoc.queryable
else
Logger.warning("Changeset relationship for CommonChanges.put_or_cast_assoc #{key} was not found")
%parent_schema{} = changeset.data

changeset
raise ArgumentError, "The key #{inspect(key)} is not an association for the queryable #{inspect(parent_schema)}."
end
end

Expand Down Expand Up @@ -191,13 +216,13 @@ defmodule EctoShorts.CommonChanges do

defp find_method_and_put_or_cast(changeset, key, params_data, opts) when is_list(params_data) do
cond do

SchemaHelpers.all_schemas?(params_data) -> put_assoc(
changeset,
key,
params_data,
opts
)
SchemaHelpers.all_schemas?(params_data) ->
put_assoc(
changeset,
key,
params_data,
opts
)

member_update?(params_data) ->
schema = changeset_relationship_schema(changeset, key)
Expand All @@ -207,13 +232,15 @@ defmodule EctoShorts.CommonChanges do

SchemaHelpers.any_created?(params_data) ->
changeset
|> preload_changeset_assoc(
key,
Keyword.put(opts, :ids, params_data |> data_ids |> Enum.reject(&is_nil/1))
)
|> cast_assoc(key, opts)
|> preload_changeset_assoc(
key,
Keyword.put(opts, :ids, params_data |> data_ids() |> Enum.reject(&is_nil/1))
)
|> cast_assoc(key, opts)

true ->
cast_assoc(changeset, key, opts)

true -> cast_assoc(changeset, key, opts)
end
end

Expand Down
13 changes: 8 additions & 5 deletions lib/common_filters.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,18 @@ defmodule EctoShorts.CommonFilters do
queryable :: Ecto.Queryable.t(),
params :: Keyword.t | map
) :: Ecto.Query.t
def convert_params_to_filter(query, params) when params === %{}, do: query
def convert_params_to_filter(query, params) when params === %{} do
query
end

def convert_params_to_filter(query, params) when is_map(params) do
convert_params_to_filter(query, Map.to_list(params))
end

def convert_params_to_filter(query, params) do
params
|> ensure_last_is_final_filter
|> Enum.reduce(query, &create_schema_filter/2)
|> ensure_last_is_final_filter
|> Enum.reduce(query, &create_schema_filter/2)
end

def create_schema_filter({filter, val}, query) when filter in @common_filters do
Expand All @@ -83,8 +86,8 @@ defmodule EctoShorts.CommonFilters do
defp ensure_last_is_final_filter(params) do
if Keyword.has_key?(params, :last) do
params
|> Keyword.delete(:last)
|> Kernel.++([last: params[:last]])
|> Keyword.delete(:last)
|> Kernel.++([last: params[:last]])
else
params
end
Expand Down
4 changes: 0 additions & 4 deletions lib/ecto_shorts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ defmodule EctoShorts do
Ecto Shorts is a library created to help shorten ecto interactions
and remove most of the related code.
There are 2 main modules `EctoShorts.Actions` and `EctoShorts.CommonChanges`
### Actions
Expand Down Expand Up @@ -36,8 +34,6 @@ defmodule EctoShorts do
* `:repo` - A module that uses the Ecto.Repo Module.
* `:replica` - If you don't want to perform any reads against your Primary, you can specify a replica to read from.
See `EctoShorts.CommonFilters` for more info on filters
### CommonChanges
Expand Down
Loading

0 comments on commit e850292

Please sign in to comment.