feature request: ability to inject definition into existing module
ananthakumaran opened this issue · comments
exprotobuf currently supports this feature via inject & only option. I am currently experimenting with some database library where ability to inject defstruct/encode/decode into existing module would make the api cleaner.
Protox provides the namespace
option:
defmodule Bar do
use Protox, schema: """
syntax = "proto3";
enum Enum {
FOO = 0;
BAR = 1;
}
""",
namespace: Namespace
end
Isn't it what you're looking for?
no, the namespace will create a nested module
defmodule Bar
defmodule Namespace do
defstruct [..]
end
end
inject directly injects the defstruct
into the calling module.
something like
defmodule Enum do
use Protox, schema: """
syntax = "proto3";
enum Enum {
FOO = 0;
BAR = 1;
}
""",
inject: true
end
it will inject the defstruct and other methods directly into the calling module without creating nested module using defmodule
Actually, the following code
defmodule Dummy do
use Protox, schema: """
syntax = "proto3";
message Foo {
int32 a = 1;
}
""",
namespace: Namespace
end
produces
defmodule Namespace do
defmodule Foo do
defstruct [...]
end
end
Thus, Dummy
is completely ignored (I should make it clear in the documentation) and Foo
is "injected" into Namespace
.
If you don't use the namespace
option, then the code is directly "injected" in the global namespace and you would end up with:
defmodule Foo do
defstruct [...]
end
Does it answer your question?
Let me try with an example. For the schema
message Foo {
int32 a = 1;
}
What I want is the ability to add extra code inside the generated Foo module.
defmodule Foo do
# added by protox
defstruct [...]
def encode(.)
def decode(.)
# added by user
def bar() do
end
end
with the proposed inject future, I would be able to do
defmodule Foo do
use Protox, schema: "..", inject: true
# added by user
def bar() do
end
end
I would be able to call Foo.bar()
. From user perspective, the defstruct
is injected by the protox. If protox always generates defmodule Foo
, there is no way for a user to add extra functions to the generated module.
OK, I understand now. To be honest, I discarded this possibility right from the beginning as I felt it was a lot of work just to avoid writing a new module:
defmodule Dummy do
use Protox, schema: """
syntax = "proto3";
message Foo {
int32 a = 1;
}
"""
end
defmodule WorkWithFoo do
def bar() do
end
end
Also, I don't like what it implies in terms of renaming messages names:
defmodule Bar do
use Protox, schema: """
syntax = "proto3";
message Foo {
int32 a = 1;
}
""",
inject: true
end
Suddenly, Foo
becomes Bar
just because it has been "injected" into Bar
. Someone looking at the protobuf definition files won't find the messages he's looking for.
I realise it's more or less the debate of "inheritance vs composition". I usually strongly favour the latest.
That being said, I'll welcome any PR that brings this functionality :-)
Those are good points, I am also not sure how well it would work with Enums, Nested msgs etc.
But for some use cases, API wise, it would be ideal to add extra methods to the generated module instead of using a different module, otherwise one would have to come up with a new name for the same thing.
defmodule Friends.Person do
use Ecto.Schema
schema "people" do
field :first_name, :string
field :last_name, :string
field :age, :integer
end
def changeset(person, params \\ %{}) do
person
|> Ecto.Changeset.cast(params, ~w(first_name last_name age))
|> Ecto.Changeset.validate_required([:first_name, :last_name])
end
end
Here for example, casting/validation logic are kept together