Git Product home page Git Product logo

Comments (11)

danhper avatar danhper commented on May 14, 2024

@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.

paulcsmith avatar paulcsmith commented on May 14, 2024

@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.

danhper avatar danhper commented on May 14, 2024

@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.

paulcsmith avatar paulcsmith commented on May 14, 2024

@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.

paulcsmith avatar paulcsmith commented on May 14, 2024

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?

cc @jsteiner @BlakeWilliams

from ex_machina.

paulcsmith avatar paulcsmith commented on May 14, 2024

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.

paulcsmith avatar paulcsmith commented on May 14, 2024

@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.

danhper avatar danhper commented on May 14, 2024

@paulcsmith Sure, I am going to give this a try, thank you very much for the great work!

from ex_machina.

danhper avatar danhper commented on May 14, 2024

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.

danhper avatar danhper commented on May 14, 2024

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.

paulcsmith avatar paulcsmith commented on May 14, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.