Skip to content

Commit 9bc419f

Browse files
committed
Extracted
1 parent 2abe8c5 commit 9bc419f

File tree

5 files changed

+170
-140
lines changed

5 files changed

+170
-140
lines changed

README.md

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,20 @@
22

33
A library that generates random parameters for Ash resource actions. It provides a convenient way to create random test data for your Ash resources by automatically generating random values for accepts and arguments.
44

5-
## Installation
6-
7-
Add `ash_random_params` to your list of dependencies in `mix.exs`:
8-
9-
```elixir
10-
def deps do
11-
[
12-
{:ash_random_params, "~> 0.2.0"}
13-
]
14-
end
15-
```
16-
175
## Usage
186

197
Add the `random_params` DSL to your Ash resource:
208

219
```elixir
22-
defmodule MyApp.Post do
10+
defmodule Post do
2311
use Ash.Resource, extensions: [AshRandomParams]
2412

2513
attributes do
2614
uuid_primary_key :id
15+
attribute :author, :string, allow_nil?: false
2716
attribute :title, :string, allow_nil?: false
2817
attribute :content, :string, allow_nil?: true
18+
attribute :tag, :string, allow_nil?: false, default: "JS"
2919
end
3020

3121
random_params do
@@ -34,6 +24,46 @@ defmodule MyApp.Post do
3424
end
3525
```
3626

27+
### Using Random Params
28+
29+
```elixir
30+
# Basic usage
31+
Post.random_params!(:create)
32+
=> %{author: "author-81491", title: "title-388112", content: nil, tag: "JS"}
33+
34+
# With initial params
35+
Post.random_params!(:create, %{author: "James"})
36+
=> %{author: "James", title: "title-388112", content: nil, tag: "JS"}
37+
38+
# With options
39+
Post.random_params!(:create, %{author: "James"}, %{
40+
populate: [:content],
41+
omit: [:title],
42+
include_defaults?: false
43+
})
44+
=> %{author: "James", content: "content-38128"}
45+
```
46+
47+
### Default Behavior
48+
49+
By default, it generates random values for attributes and arguments that have `allow_nil?: false` and no default value (`default == nil`). In the example above, `author` and `title` fall into this category.
50+
51+
### Belongs To Relationships
52+
53+
For attributes and arguments that match the `name` or `source_attribute` of a `belongs_to` relationship:
54+
- In `create` actions, they are generated with `nil` values
55+
- In other actions, they are not generated at all
56+
57+
This behavior exists because:
58+
- In `create` actions, omitting a value is equivalent to setting it to `nil`
59+
- In `update` actions, omitting a value preserves the existing relationship, while explicitly setting it to `nil` removes the relationship
60+
61+
### Options
62+
63+
- `populate`: Forces generation of random values for specified attributes/arguments, overriding the default behavior
64+
- `omit`: Prevents generation of random values for specified attributes/arguments, overriding the default behavior
65+
- `include_defaults?`: When set to `true`, includes default values for attributes/arguments that have either `allow_nil?: true` or a non-nil default value. Defaults to `true`. In the example above, this would add `%{content: nil, tag: "JS"}` to the generated params.
66+
3767
### Custom Random Generator
3868

3969
You can implement a custom random generator by using the `AshRandomParams.Random` behaviour:
@@ -44,7 +74,7 @@ defmodule MyRandom do
4474

4575
@impl AshRandomParams.Random
4676
def random(%{type: Ash.Type.Integer}, _opts, _context) do
47-
777 # Custom random integer
77+
777
4878
end
4979

5080
@impl AshRandomParams.Random
@@ -55,28 +85,24 @@ defmodule MyRandom do
5585
end
5686
```
5787

58-
### Using Random Params
59-
60-
```elixir
61-
# Basic usage
62-
Post.random_params!(:create)
63-
64-
# With initial params
65-
Post.random_params!(:create, %{key: "value"})
66-
67-
# With options
68-
Post.random_params!(:create, %{key: "value"}, %{
69-
fill: [:optional_field], # Fill optional fields
70-
unfill: [:required_field] # Leave required fields as nil
71-
})
72-
```
73-
7488
## Features
7589

7690
- Automatically generates random values for action accepts and arguments
7791
- Supports custom random value generators
7892
- Useful for testing and development
7993

94+
## Installation
95+
96+
Add `ash_random_params` to your list of dependencies in `mix.exs`:
97+
98+
```elixir
99+
def deps do
100+
[
101+
{:ash_random_params, "~> 0.1.0"}
102+
]
103+
end
104+
```
105+
80106
## License
81107

82108
MIT

lib/random/default_random.ex

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,6 @@ defmodule AshRandomParams.DefaultRandom do
1515
end)
1616
end
1717

18-
# Union
19-
def random(
20-
%{type: _union_type, constraints: [{:types, type_infos} | _]},
21-
_opts,
22-
%{action_context: action_context} = context
23-
) do
24-
ash_opts = action_context |> Ash.Context.to_opts()
25-
26-
{_subtype_tag, type_info} =
27-
case context do
28-
%{subtype_tag: subtype_tag} ->
29-
type_infos
30-
|> Enum.find(fn
31-
{^subtype_tag, _type_info} -> true
32-
{_, _} -> false
33-
end)
34-
35-
%{} ->
36-
type_infos |> Enum.random()
37-
end
38-
39-
subtype = type_info |> Keyword.fetch!(:type)
40-
tag = type_info |> Keyword.fetch!(:tag)
41-
tag_value = type_info |> Keyword.fetch!(:tag_value)
42-
attr_names = subtype |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name)
43-
44-
# primary_create = subtype |> Ash.Resource.Info.primary_action(:create)
45-
46-
subtype
47-
|> Ash.Changeset.for_create(:fac_rec, %{}, ash_opts)
48-
|> Ash.create!(ash_opts)
49-
|> Map.from_struct()
50-
|> Map.take(attr_names)
51-
|> Map.put(tag, tag_value)
52-
end
53-
5418
# One of atoms
5519
def random(%{type: Ash.Type.Atom, constraints: constraints}, _opts, _context) do
5620
constraints |> Keyword.fetch!(:one_of) |> Enum.random()

lib/transformer.ex

Lines changed: 45 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ defmodule AshRandomParams.Transformer do
44
alias Ash.Resource.Relationships.BelongsTo
55
alias Ash.Resource.Builder
66

7-
def before?(Ash.Resource.Transformers.BelongsToAttribute), do: false
8-
def before?(_), do: true
9-
10-
def after?(Ash.Resource.Transformers.BelongsToAttribute), do: true
11-
def after?(_), do: false
12-
137
def transform(dsl_state) do
148
dsl_state
159
|> add_random_params()
@@ -19,14 +13,14 @@ defmodule AshRandomParams.Transformer do
1913
def add_random_params(dsl_state) do
2014
action = Builder.build_action_argument(:action, :atom, default: :create)
2115
init_params = Builder.build_action_argument(:init_params, :map, default: %{})
22-
fill = Builder.build_action_argument(:fill, {:array, :atom}, default: [])
23-
unfill = Builder.build_action_argument(:unfill, {:array, :atom}, default: [])
24-
tagged_unions = Builder.build_action_argument(:tagged_unions, :map, default: %{})
16+
populate = Builder.build_action_argument(:populate, {:array, :atom}, default: [])
17+
omit = Builder.build_action_argument(:omit, {:array, :atom}, default: [])
18+
include_defaults? = Builder.build_action_argument(:include_defaults?, :boolean, default: true)
2519

2620
dsl_state
2721
|> Builder.add_action(:action, :random_params,
2822
returns: :map,
29-
arguments: [action, init_params, unfill, fill, tagged_unions],
23+
arguments: [action, init_params, omit, populate, include_defaults?],
3024
run: &__MODULE__.do_random_params/2
3125
)
3226
|> Builder.add_interface(:random_params, args: [:action, {:optional, :init_params}])
@@ -50,20 +44,16 @@ defmodule AshRandomParams.Transformer do
5044
arguments: %{
5145
action: action,
5246
init_params: init_params,
53-
unfill: unfill,
54-
fill: fill,
55-
tagged_unions: tagged_unions
47+
omit: omit,
48+
populate: populate,
49+
include_defaults?: include_defaults?
5650
}
5751
} =
5852
input,
5953
ctx
6054
) do
61-
{random_mod, random_opts} =
62-
random = AshRandomParams.Info.random_params_random!(input.resource)
63-
6455
action = Ash.Resource.Info.action(resource, action)
6556
init_keys = init_params |> Map.keys()
66-
union_keys = tagged_unions |> Map.values()
6757

6858
belongs_to_attrs =
6959
resource
@@ -84,32 +74,51 @@ defmodule AshRandomParams.Transformer do
8474

8575
candidates = accepted_attrs ++ action.arguments
8676

87-
placeholder =
88-
candidates
89-
|> then(fn attr_or_args ->
90-
if action.type == :create do
91-
attr_or_args
92-
else
93-
attr_or_args
94-
|> Enum.reject(&(&1.name in rel_names))
95-
|> Enum.reject(&(&1.name in belongs_to_attrs))
96-
end
97-
end)
98-
|> Map.new(&{&1.name, nil})
77+
defaults =
78+
if include_defaults? do
79+
candidates
80+
|> then(fn attr_or_args ->
81+
if action.type == :create do
82+
attr_or_args
83+
else
84+
attr_or_args
85+
|> Enum.reject(&(&1.name in rel_names))
86+
|> Enum.reject(&(&1.name in belongs_to_attrs))
87+
end
88+
end)
89+
|> Enum.reject(&(&1.name in omit))
90+
|> Enum.reject(&(&1.name in init_keys))
91+
|> Map.new(fn %{name: name, default: default} ->
92+
value =
93+
case default do
94+
default when is_function(default, 0) ->
95+
default.()
96+
97+
default ->
98+
default
99+
end
100+
101+
{name, value}
102+
end)
103+
else
104+
%{}
105+
end
99106

100107
generated_params =
101108
(
102-
fields_by_unfill =
109+
{random_mod, random_opts} =
110+
random = AshRandomParams.Info.random_params_random!(input.resource)
111+
112+
fields_by_omit =
103113
candidates
104114
|> Enum.reject(&(&1.name in rel_names))
105115
|> Enum.reject(&(&1.name in belongs_to_attrs))
106-
|> Enum.reject(&(&1.name in union_keys))
107-
|> Enum.reject(&(&1.name in unfill))
108-
|> Enum.reject(& &1.allow_nil?)
116+
|> Enum.reject(&(&1.name in omit))
117+
|> Enum.reject(&(&1.allow_nil? || &1.default != nil))
109118

110-
fields_by_fill = candidates |> Enum.filter(&(&1.name in fill))
119+
fields_by_fill = candidates |> Enum.filter(&(&1.name in populate))
111120

112-
(fields_by_unfill ++ fields_by_fill)
121+
(fields_by_omit ++ fields_by_fill)
113122
|> Enum.reject(&(&1.name in init_keys))
114123
|> Map.new(fn %{name: name, default: default} = attr_or_arg ->
115124
value =
@@ -128,28 +137,7 @@ defmodule AshRandomParams.Transformer do
128137
end)
129138
)
130139

131-
result_params = placeholder |> Map.merge(init_params) |> Map.merge(generated_params)
132-
133-
union_params =
134-
tagged_unions
135-
|> Enum.reject(fn {_tag_field, union_field} -> union_field in init_keys end)
136-
|> Map.new(fn {tag_field, union_field} ->
137-
union_attr = resource |> Ash.Resource.Info.attribute(union_field)
138-
subtype_tag = result_params |> Map.fetch!(tag_field)
139-
140-
{random_mod, random_opts} = random = AshRandomParams.Info.random_params_random!(resource)
141-
142-
union_value =
143-
random_mod.random(union_attr, random_opts, %{
144-
random: random,
145-
action_context: ctx,
146-
subtype_tag: subtype_tag
147-
})
148-
149-
{union_field, union_value}
150-
end)
151-
152-
result_params = Map.merge(result_params, union_params)
140+
result_params = defaults |> Map.merge(init_params) |> Map.merge(generated_params)
153141

154142
{:ok, result_params}
155143
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule AshRandomParams.MixProject do
44
def project do
55
[
66
app: :ash_random_params,
7-
version: "0.2.0",
7+
version: "0.1.0",
88
elixir: "~> 1.17",
99
consolidate_protocols: Mix.env() not in [:dev, :test],
1010
start_permanent: Mix.env() == :prod,

0 commit comments

Comments
 (0)