Skip to content

Commit 211bf94

Browse files
committed
Set infer: false by default in schema macro
1 parent d4f7bd2 commit 211bf94

File tree

8 files changed

+143
-82
lines changed

8 files changed

+143
-82
lines changed

lib/drops/relation/plugins/reading.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,34 +1407,39 @@ defmodule Drops.Relation.Plugins.Reading do
14071407
apply(repo, fun, [queryable] ++ args ++ [repo_opts])
14081408
end
14091409

1410+
@doc false
14101411
def operation(name, opts) when is_atom(name) do
14111412
{relation, _repo, queryable, rest_opts} = clean_opts(opts)
14121413
operation_opts = Keyword.get(rest_opts, name, rest_opts)
14131414

14141415
relation.add_operation(queryable, name, operation_opts)
14151416
end
14161417

1418+
@doc false
14171419
def operation(other, name, opts) when is_struct(other) and is_map_key(other, :queryable) do
14181420
{relation, _repo, _queryable, rest_opts} = clean_opts(opts)
14191421
operation_opts = Keyword.get(rest_opts, name, rest_opts)
14201422

14211423
relation.add_operation(other, name, operation_opts)
14221424
end
14231425

1426+
@doc false
14241427
def operation(other, name, opts) when is_struct(other) do
14251428
{relation, _repo, queryable, rest_opts} = clean_opts(opts, other)
14261429
operation_opts = Keyword.get(rest_opts, name, rest_opts)
14271430

14281431
relation.add_operation(relation.new(queryable), name, operation_opts)
14291432
end
14301433

1434+
@doc false
14311435
def operation(other, name, opts) when is_atom(other) do
14321436
{relation, _repo, queryable, rest_opts} = clean_opts(opts, other)
14331437
operation_opts = Keyword.get(rest_opts, name, rest_opts)
14341438

14351439
relation.add_operation(relation.new(queryable), name, operation_opts)
14361440
end
14371441

1442+
@doc false
14381443
def clean_opts(opts, queryable \\ nil) do
14391444
relation = opts[:relation]
14401445
repo = relation.repo()

lib/drops/relation/plugins/schema.ex

Lines changed: 127 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,74 +5,6 @@ defmodule Drops.Relation.Plugins.Schema do
55
This plugin adds the `schema/1` and `schema/2` macros for defining relation schemas.
66
It supports both automatic schema inference from database tables and manual schema
77
definition with Ecto.Schema syntax.
8-
9-
## Examples
10-
11-
### Automatic Schema Inference
12-
defmodule MyApp.Users do
13-
use Drops.Relation, otp_app: :my_app
14-
15-
schema("users", infer: true)
16-
end
17-
18-
iex> schema = MyApp.Users.schema()
19-
iex> schema.source
20-
:users
21-
iex> %{name: name, type: type, meta: %{default: default}} = MyApp.Users.schema(:email)
22-
iex> name
23-
:email
24-
iex> type
25-
:string
26-
iex> default
27-
nil
28-
29-
### Manual Schema Definition
30-
31-
You can also define schemas manually using familiar Ecto.Schema syntax:
32-
33-
schema("users") do
34-
field(:name, :string)
35-
field(:email, :string)
36-
field(:active, :boolean, default: true)
37-
38-
timestamps()
39-
end
40-
41-
This gives you full control over field definitions, types, and options.
42-
43-
## Hybrid Approach
44-
45-
Combine automatic inference with manual customizations:
46-
47-
defmodule MyApp.Users do
48-
use Drops.Relation, repo: MyApp.Repo
49-
50-
schema("users", infer: true) do
51-
field(:full_name, :string, virtual: true)
52-
53-
has_many(:posts, MyApp.Posts)
54-
end
55-
end
56-
57-
### Schema Access and Struct Generation
58-
59-
iex> schema_module = MyApp.Users.__schema_module__()
60-
iex> is_atom(schema_module)
61-
true
62-
63-
iex> user = MyApp.Users.struct(%{name: "John", email: "[email protected]"})
64-
iex> user.__struct__
65-
MyApp.Users.User
66-
iex> user.name
67-
"John"
68-
iex> user.email
69-
70-
71-
## Options
72-
73-
- `infer: true` - Automatically infer schema from database (default)
74-
- `struct: "CustomName"` - Use custom struct module name
75-
- Standard Ecto.Schema options are supported in manual definitions
768
"""
779

7810
alias Drops.Relation.Schema
@@ -87,7 +19,7 @@ defmodule Drops.Relation.Plugins.Schema do
8719

8820
use Drops.Relation.Plugin.MacroStruct,
8921
key: :schema,
90-
struct: [:name, block: nil, fields: nil, opts: [], infer: true]
22+
struct: [:name, block: nil, fields: nil, opts: [], infer: false]
9123

9224
def new(name) when is_binary(name) do
9325
%Macros.Schema{name: name}
@@ -99,7 +31,7 @@ defmodule Drops.Relation.Plugins.Schema do
9931

10032
def new(name, opts) when is_binary(name) and is_list(opts) do
10133
opts = Keyword.delete(opts, :do)
102-
infer = Keyword.get(opts, :infer, true)
34+
infer = Keyword.get(opts, :infer, false)
10335

10436
%{new(name) | opts: opts, infer: infer}
10537
end
@@ -109,7 +41,80 @@ defmodule Drops.Relation.Plugins.Schema do
10941
end
11042
end
11143

112-
defmacro schema(fields, opts \\ [])
44+
@doc """
45+
Defines a schema for the relation.
46+
47+
By default, this creates an empty schema that you must populate with manual field
48+
definitions. Use `infer: true` option to automatically introspect the database table.
49+
50+
## Parameters
51+
52+
- `table_name` - String name of the database table
53+
- `opts` - Keyword list of options (optional)
54+
55+
## Options
56+
57+
- `infer: false` - Use only manual field definitions (default: false)
58+
- `infer: true` - Automatically infer schema from database table
59+
- `struct: "CustomName"` - Use custom struct module name instead of default
60+
61+
## Returns
62+
63+
Sets up the relation to generate:
64+
- A `schema/0` function that returns the complete schema metadata
65+
- A `schema/1` function that returns a specific field by name
66+
67+
## Examples
68+
69+
Manual schema definition:
70+
71+
iex> defmodule Relations.Users do
72+
...> use Drops.Relation, repo: MyApp.Repo
73+
...>
74+
...> schema("users") do
75+
...> field(:name, :string)
76+
...> field(:email, :string)
77+
...> end
78+
...> end
79+
...>
80+
iex> user = Relations.Users.struct(%{name: "Alice Johnson", email: "[email protected]"})
81+
iex> user.__struct__
82+
Relations.Users.User
83+
iex> user.name
84+
"Alice Johnson"
85+
iex> user.email
86+
87+
88+
With automatic inference:
89+
90+
iex> defmodule Relations.Users do
91+
...> use Drops.Relation, repo: MyApp.Repo
92+
...>
93+
...> schema("users", infer: true)
94+
...> end
95+
iex> schema = Relations.Users.schema()
96+
iex> schema.source
97+
:users
98+
iex> length(schema.fields) > 0
99+
true
100+
101+
With custom struct name:
102+
103+
iex> defmodule Relations.People do
104+
...> use Drops.Relation, repo: MyApp.Repo
105+
...>
106+
...> schema("users", struct: "Person", infer: true)
107+
...> end
108+
...>
109+
iex> user = Relations.People.struct(%{name: "Alice Johnson", email: "[email protected]"})
110+
iex> user.__struct__
111+
Relations.People.Person
112+
iex> user.name
113+
"Alice Johnson"
114+
iex> user.email
115+
116+
"""
117+
defmacro schema(name, opts \\ [])
113118

114119
defmacro schema(name, opts) when is_binary(name) do
115120
block = opts[:do]
@@ -129,6 +134,47 @@ defmodule Drops.Relation.Plugins.Schema do
129134
end
130135
end
131136

137+
@doc """
138+
Defines a schema with manual field definitions or combines inference with custom fields.
139+
140+
This form allows you to either define a completely manual schema using Ecto.Schema
141+
syntax, or combine automatic inference with additional custom fields and associations.
142+
143+
## Parameters
144+
145+
- `table_name` - String name of the database table
146+
- `opts` - Keyword list of options
147+
- `block` - Schema definition block using Ecto.Schema syntax
148+
149+
## Options
150+
151+
- `infer: false` - Use only the manual field definitions in the block (default: false)
152+
- `infer: true` - Automatically infer schema from database and merge with block
153+
- `struct: "CustomName"` - Use custom struct module name
154+
155+
## Returns
156+
157+
Sets up the relation with either a purely manual schema or a merged schema
158+
combining inference with custom definitions.
159+
160+
## Examples
161+
162+
iex> defmodule Relations.Users do
163+
...> use Drops.Relation, repo: MyApp.Repo
164+
...>
165+
...> schema("users", infer: true) do
166+
...> field(:role, :string, default: "member")
167+
...> field(:full_name, :string, virtual: true)
168+
...> end
169+
...> end
170+
...>
171+
iex> schema = Relations.Users.schema()
172+
iex> %{name: name, meta: %{default: default}} = schema[:role]
173+
iex> name
174+
:role
175+
iex> default
176+
"member"
177+
"""
132178
defmacro schema(name, opts, block) when is_binary(name) do
133179
block = block[:do]
134180

@@ -153,6 +199,7 @@ defmodule Drops.Relation.Plugins.Schema do
153199
end
154200
end
155201

202+
@doc false
156203
def put_schema(relation, opts) do
157204
schema =
158205
case context(relation, :schema) do
@@ -174,6 +221,15 @@ defmodule Drops.Relation.Plugins.Schema do
174221
else
175222
source_schema
176223
end
224+
225+
%{name: name, infer: false, block: block} when not is_nil(block) ->
226+
Generator.schema_from_block(name, block)
227+
228+
%{name: name, infer: false, block: nil} ->
229+
Schema.new(%{source: String.to_atom(name)})
230+
231+
%{name: name, block: nil} ->
232+
Schema.new(%{source: String.to_atom(name)})
177233
end
178234

179235
Module.put_attribute(relation, :schema, schema)

test/drops/relation/associations_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ defmodule Drops.Relation.AssociationsTest do
33

44
describe "defining associations" do
55
relation(:user_groups) do
6-
schema("user_groups") do
6+
schema("user_groups", infer: true) do
77
belongs_to(:user, Test.Relations.Users)
88
belongs_to(:group, Test.Relations.Groups)
99
end
1010
end
1111

1212
relation(:users) do
13-
schema("users") do
13+
schema("users", infer: true) do
1414
has_many(:user_groups, Test.Relations.UserGroups)
1515
has_many(:groups, through: [:user_groups, :group])
1616
end
1717
end
1818

1919
relation(:groups) do
20-
schema("groups") do
20+
schema("groups", infer: true) do
2121
has_many(:user_groups, Test.Relations.UserGroups)
2222
has_many(:users, through: [:user_groups, :user])
2323
end

test/drops/relation/metadata_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defmodule Drops.Relation.MetadataIntegrationTest do
2121

2222
describe "parameterized types with metadata" do
2323
relation(:metadata_test) do
24-
schema("metadata_test") do
24+
schema("metadata_test", infer: true) do
2525
field(:status, Ecto.Enum, values: [:active, :inactive, :pending])
2626
field(:priority, :integer, default: 10)
2727
end

test/drops/relation/plugins/reading_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,13 @@ defmodule Drops.Relations.Plugins.ReadingTest do
431431
end
432432

433433
relation(:association_items) do
434-
schema("association_items") do
434+
schema("association_items", infer: true) do
435435
belongs_to(:association, Test.Relations.Associations)
436436
end
437437
end
438438

439439
relation(:associations) do
440-
schema("associations") do
440+
schema("associations", infer: true) do
441441
has_many(:items, Test.Relations.AssociationItems)
442442
belongs_to(:parent, Test.Relations.AssociationParents)
443443
end

test/drops/relation/plugins/schema_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ defmodule Drops.Relation.Plugins.SchemaTest do
191191
end
192192

193193
relation(:users_with_embeds) do
194-
schema("users") do
194+
schema("users", infer: true) do
195195
field(:name, :string)
196196
embeds_one(:metadata, TestEmbedded)
197197
embeds_many(:tags, TestEmbedded)
@@ -250,7 +250,7 @@ defmodule Drops.Relation.Plugins.SchemaTest do
250250

251251
describe "embeds with inline schemas" do
252252
relation(:users_with_inline_embeds) do
253-
schema("users") do
253+
schema("users", infer: true) do
254254
field(:name, :string)
255255

256256
embeds_one :profile, Profile do

test/drops/relation/schema_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Drops.Relation.SchemaTest do
55
@describetag adapter: :sqlite
66

77
relation(:metadata_test) do
8-
schema("metadata_test") do
8+
schema("metadata_test", infer: true) do
99
field(:status, Ecto.Enum, values: [:active, :inactive, :pending])
1010
field(:priority, :integer, default: 10)
1111
end

test/drops/relation_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ defmodule Drops.RelationTest do
199199

200200
describe "customizing fields" do
201201
relation(:users) do
202-
schema("users") do
202+
schema("users", infer: true) do
203203
field(:tags, Ecto.Enum, values: [:red, :green, :blue])
204204
field(:status, :string, default: "active")
205205
end
@@ -236,7 +236,7 @@ defmodule Drops.RelationTest do
236236

237237
describe "overriding inferred fields" do
238238
relation(:users) do
239-
schema("users") do
239+
schema("users", infer: true) do
240240
field(:name, :binary)
241241
end
242242
end

0 commit comments

Comments
 (0)