-
-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: ex_machina support #26
Conversation
@mathieuprog This produces the following error:
I'm not sure what the issue is. |
( |
I think the key was to change the |
@mathieuprog We're not really casting anything at all anymore since we have a custom cast fn. We could also just raise an error to cast via our fn instead. That will break ex_machina however. |
Why did you decide to change the |
Because i simplified some things that caused problems while refactoring. My first choice was to raise an error, but that breaks a test. Also I want an error if people try to cast via the normal cast. If you're ok with breaking the machina test, I'd rather just raise an error no matter what is provided to the fn. |
There will be complaints. What if we check if we are in the test environment and that |
@mathieuprog that would be a possibility. I'm not sure though why we would not allow this in general for other libraries. The complete opposite would work too:
|
You mean stop using |
@mathieuprog that makes sense We could also forbid cast alltogether and provide a custom ex machina strategy. it seems one should be able to override it. That way we would not give it access to special / private apis |
@mathieuprog I'll push a "strict" version with an error being raised in |
That's awesome 😐 of course this is the way to go... We need to update the readme as well in order to document the ex_machina strategy and how to use it. Just a code snippet is sufficient. |
The reason why we added The failing test before the introduction of |
7eac451
to
d1c9b35
Compare
@mathieuprog I've amended the PR. This shows that it is possible in general, but I've copied a lot of code. I think if we like this route in general, we should open a ticket at ex_machina to make this simpler without copying everything. |
You decided to return the |
@mathieuprog No real reason, I'm not sure which is better. |
Raising an error allows to send a message to the developer, e.g. use cast_polymorphic_embed for field X instead |
@mathieuprog Before we talk about details:
|
@mathieuprog The oposite could also be done: Add optional support to ex_machina instead of this way around. That would be easier to implement I think. |
The PRs are amazing:) I can also grant you more rights in this project if you'd like to contribute more in the future, as you have a good understanding of the codebase. Let me know in that case. For I think we both have a lack of interest to work with |
I prefer the least possible dependency to |
@mathieuprog I'm already maintainer of far too many libraries and I'm fine just creating PRs :) What are you saying with this?
In any case I can change the
We'll have some kind of chicken / egg problem here. I suppose ex_machina might say the same thing. The only difference is that this library would have to duplicate basically the whole |
I didn't look thoroughly yet but I would add the files, and redirect a future requester to these files and this discussion. But of course feel free to open an issue on ex_machina's side, but that's up to you:D I personally don't mind to have files in draft state in an ex_machina folder.
Good but it's cast/4, not cast/3 |
How do you see finishing up that PR? I think all what's left is the error message? Or do you have anything else in mind? |
d1c9b35
to
6c14b77
Compare
@mathieuprog Changed About the files from |
I will study your ex_machina code now:) by the way you mentioned there was an error (#26 (comment)); I checkout the code and the tests pass. So you fixed that error? |
@mathieuprog I fixed the test, there was an error in my code that the test correctly identified. |
Issue posted on ex_machina's repo: |
It might take several days, maybe weeks to get an answer, so can we remove the support of ex_machina at all for now? |
Maybe we can create a new PR based on this one, and prefix this one by "[ON HOLD]" ? |
@mathieuprog I'll create a new one without the ex_machina files. |
6c14b77
to
31547b4
Compare
@mathieuprog This PR now only contains |
Perfect! |
Hello 👋 is there a chance this could be merged soon ? |
@richthedev This is currently such an ugly hack that I don't think it should be merged. It is really ugly since a lot of code had to be copied since we couldn't hook into the existing ex_machina code. The options currently are:
|
@maennchen Thanks for the swift response. |
defp schema_polymorphic_embeds(schema) do | ||
Enum.filter( | ||
schema.__schema__(:fields), | ||
&match?({:parameterized, PolymorphicEmbed, _options}, schema.__schema__(:type, &1)) | ||
) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI this currently won't match arrays of polymorphic embeds. The fix is:
defp schema_polymorphic_embeds(schema) do
Enum.filter(
schema.__schema__(:fields),
fn field ->
case schema.__schema__(:type, field) do
{:parameterized, PolymorphicEmbed, _options} -> true
{:array, {:parameterized, PolymorphicEmbed, _options}} -> true
_ -> false
end
end
)
end
An ex_machina maintainer beam-community/ex_machina#407 asked for ideas but we didn't get back with any yet. 1. Use of config in the main application config :ex_machina, :cast_custom_types, MyApp.ExMachina.CastCustomTypes
defmodule MyApp.ExMachina.CastCustomTypes do
@behaviour ExMachina.CastCustomTypes
@impl true
def cast_custom_types(struct) do
struct
|> PolymorphicEmbed.ExMachina.CastCustomTypes.cast_custom_types()
|> OtherLibrary.ExMachina.CastCustomTypes.cast_custom_types()
end
@impl true
def schema_custom_types(struct) do
PolymorphicEmbed.ExMachina.CastCustomTypes.schema_custom_types(struct)
++ OtherLibrary.ExMachina.CastCustomTypes.schema_custom_types(struct)
end
end defmodule PolymorphicEmbed.ExMachina.CastCustomTypes do
@behaviour ExMachina.CastCustomTypes
@impl true
def cast_custom_types(struct) do
cast_polymorphic_embeds(struct)
end
@impl true
def schema_custom_types(struct) do
schema_polymorphic_embeds(struct)
end
end This would be the easiest to code on ex_machina's side because the configuration is globally accessible. 2. Use of metaprogrammaing The other way to inject code is through the use of Then we'd have in the tests: use ExMachina.Ecto, strategy: MyApp.ExMachina.EctoStrategy defmodule MyApp.ExMachina.EctoStrategy do
use ExMachina.EctoStrategy, cast_custom_types: [PolymorphicEmbed.ExMachina.CastCustomTypes]
end defmodule PolymorphicEmbed.ExMachina.CastCustomTypes do
@behaviour ExMachina.CastCustomTypes
@impl true
def cast_custom_types(struct) do
cast_polymorphic_embeds(struct)
end
@impl true
def schema_custom_types(struct) do
schema_polymorphic_embeds(struct)
end
end 3. Dependency Or ask ex_machina maintainers to detect the use of PolymorphicEmbed library and hardcode a module to call: if Code.ensure_loaded?(PolymorphicEmbed) do
PolymorphicEmbed.ExMachina.#function
end
And have ex_machina have an optional dependency on PolymorphicEmbed. Thoughts? |
Any updates? |
@nallwhy it does not seem it will be solved any time soon except if someone that needs this comes up with a solution. The ex_machina library needs to call our custom cast function, there's an issue open on their side. |
defp cast_polymorphic_embeds(%{__struct__: schema} = struct) do | ||
schema | ||
|> schema_polymorphic_embeds() | ||
|> Enum.reduce(struct, fn embed_key, struct -> | ||
casted_value = struct |> Map.get(embed_key) | ||
|
||
Map.put(struct, embed_key, casted_value) | ||
end) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't handle nested embeds, or auto-generated binary ids on the embed schema. Making this change does the trick:
defp cast_polymorphic_embeds(%{__struct__: schema} = struct) do | |
schema | |
|> schema_polymorphic_embeds() | |
|> Enum.reduce(struct, fn embed_key, struct -> | |
casted_value = struct |> Map.get(embed_key) | |
Map.put(struct, embed_key, casted_value) | |
end) | |
end | |
defp cast_polymorphic_embeds(%{__struct__: schema} = struct) do | |
schema | |
|> schema_polymorphic_embeds() | |
|> Enum.reduce(struct, &cast_polymorphic_embed/2) | |
end | |
defp cast_polymorphic_embed(embed_key, struct) do | |
casted_value = | |
case Map.get(struct, embed_key) do | |
embeds when is_list(embeds) -> Enum.map(embeds, &build_polymorphic_embed/1) | |
embed when is_map(embed) -> build_polymorphic_embed(embed) | |
embed -> embed | |
end | |
Map.put(struct, embed_key, casted_value) | |
end | |
defp build_polymorphic_embed(%{__struct__: schema} = struct) do | |
case schema.__schema__(:autogenerate_id) do | |
{key, _source, :binary_id} -> Map.put(struct, key, Ecto.UUID.generate()) | |
_ -> struct | |
end | |
|> cast() | |
end |
Closing. See: beam-community/ex_machina#407 (comment) |
No description provided.