Comments (11)
@paulcsmith This seems nice, thank you for working on it! 😄
Is there any easy way to replace ExMachina.EctoStrategy
by the EctoWithChangesetStrategy
here?
from ex_machina.
@tuvistavie If I understood correctly, you are asking how to use the EctoWithChangesetStrategy in your factory?
If so, here's how to do that. Instead of use ExMachina.Ecto
you would do something like
defmodule Factory do
use ExMachina
use MyApp.EctoWithChangesetStrategy, repo: MyRepo
end
Though the strategy in this issue won't work until I also pass the factory module and name. Right now it just passes the options given to use MyStrategy
, and they are a keyword list
from ex_machina.
@paulcsmith
Sorry if I missed something here but what is the behavior when a function is defined in two different strategies?
As far as I can tell, insert
is already defined by EctoStrategy
, will it be overriden if it is defined in a strategy used after?
from ex_machina.
@tuvistavie Good catch! I accidentally typoed. I corrected the snippet above from use Exmachina.Ecto
to use ExMachina
. That way the insert functions do not conflict :)
from ex_machina.
So there is a small problem with this that I came across as I was implementing it. The factory module can be passed without issue, but the factory name can't be passed when using the pipe operator. Here's an example
# Can pass the factory name to the strategy because we have it as the first
insert(:user)
# Can't pass factory name because it was given to build/2
build(:user) |> do_something_to_it |> insert
Here are some potential solutions, none of which I'm super excited about:
# Make it so that you can pass options to the pipeable version of the function.
# In this case, you would pass the factory name
build(:user) |> do_something |> insert(:user)
^ would work, but requires retyping the factory name :S
# build_pipeable puts a key __ex_machina_factory_name__
# If __ex_machina_factory_name__ is present it will be used as the factory name when piping to the custom function
build_pipeable(:user) |> do_something |> insert
^ would also work, but then we introduce a new concept and other potential ways that things could break by adding a key to maps/structs. It could be removed after piping to the custom function, but that seems strange to me.
Any thoughts on this?
from ex_machina.
After chatting at lunch a bit I think a better solution is none of the above and instead to rely on the struct name. We can add ExMachina.factory_name_from_struct/1
that would return a factory name for the struct
ExMachina.factory_name_from_struct(%MyApp.UserSubscription}) # Returns :user_subscription
That way you can use this in strategies for things like callbacks. The downside to this is that it's a bit less explicit, but it might actually be better in a lot of cases. Here's an example:
def user_factory do
%User{name: "me", admin: false}
end
def admin_factory do
build(:user, admin: true)
end
def before_insert_user(user) do
do_something_to(user)
end
Since the factory name is extracted from the struct, the before_insert_user
would be called for both the admin and user factory. So it is a bit less explicit, but I think the advantages of not adding new concepts or complicating the pipe functions makes it the best solution
from ex_machina.
@tuvistavie Do you mind giving master a try with a custom Ecto strategy like in the description? I believe it would look something like:
defmodule ExMachina.EctoWithChangesetStrategy do
use ExMachina.Strategy, function_name: :insert
def handle_insert(record, %{repo: repo, factory_module: module}) do
factory_name = ExMachina.Strategy.name_from_struct(record)
changeset_function_name = to_string(factory_name) <> "_changeset" |> String.to_atom
if Code.ensure_loaded?(module) && function_exported(module, changeset_function_name, 1) do
apply(module, changset_function_name, record) |> repo.insert!
else
repo.insert! record
end
end
end
If you don't have time, LMK and I can try this out in a few weeks
from ex_machina.
@paulcsmith Sure, I am going to give this a try, thank you very much for the great work!
from ex_machina.
I just tried it out.
I was using a forked version, so to reuse my old implementation, I went with the following code:
defmodule ExMachina.EctoWithChangesetStrategy do
use ExMachina.Strategy, function_name: :insert
def handle_insert(record, %{repo: repo, factory_module: module}) do
record
|> module.make_changeset
|> repo.insert!
end
end
In my factory, the make_changeset
looks like this:
def make_changeset(%AdminUser{} = user) do
{password, user} = Map.pop(user, :password)
AdminUser.changeset(user, %{password: password})
end
def make_changeset(record) do
Ecto.Changeset.change(record)
end
It is working perfectly for my use case, thank you very much 😃
from ex_machina.
Oh, I forgot, the only drawback is that the params_for
is part of ExMachina.Ecto
, which is coupled with ExMachina.EctoStrategy
, so I had to copy paste the function into my factory. It's simply a matter of calling ExMachina.Ecto
, so it's not that much of a big deal, but maybe it could be improved.
from ex_machina.
Very cool. I'm glad it worked. Yeah that is a drawback of using your own strategy for using ExMachina. I think for ExMachina 1.0 I will include a before_insert_
option for ExMachina.EctoStrategy. I've run in to a couple instances where it would be nice :) I believe that would solve those issues
from ex_machina.
Related Issues (20)
- def build/2 defines defaults multiple times HOT 2
- Use of `build( ... )` with Ecto 3.4+ leads to many association preload warnings HOT 7
- Compiler warning when using an a project using Elixir 1.11 HOT 3
- Insert with assocs fails constraint HOT 1
- Proposal: Introduce build_lazy/2 HOT 3
- Test-only implementation fails HOT 4
- Cast polymorphic embeds HOT 8
- Inserted data breaks manual test HOT 3
- Allow sequence in attributes passed to `insert_list` HOT 5
- Hundreds of depreciation compiler warnings when running tests HOT 4
- insert/2 doesn't work with compound primary keys HOT 3
- Allow sequences indices to be persisted between runs HOT 2
- sequence starting with 0
- Is this project maintained? HOT 17
- Sequence reset doesn't respect the `start_at` option
- sequence(:example_id, & &1) put chars instead of integer HOT 3
- Stringify `Ecto.Enum` values when using `string_params`/`string_params_with_assocs`
- Option to have `insert_list` leverage Ecto's `insert_all`
- an intermittent error on async false tests HOT 1
- nvim diagnostics error HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ex_machina.