简体   繁体   中英

Elixir macro with Agent for state

So I am trying to get my head around how migrations work in Ecto. By this I dont mean how to use them but what is actually happening in the code. What I am seeing is that when various macros are run then state is been pumped into an Agent.

When the mix task is run to migrate the data then the Agents data is retrieved and the appropriate SQL commands are ran.

This blows my mind, I dont understand how an Agent process is alive during compilation. Ok, so I kind of understand that during macro expansion then any code outside of quoted blocks can be ran, including spawning processes but why is it still alive after compilation/expansion and during actual execution?

Chris

I dont understand how an Agent process is alive during compilation.

It isn't. The macros turn the DSL into code that calls functions in Ecto.Migration and Ecto.Migration.Runner . Here's an example. The following migration:

defmodule M.Repo.Migrations.CreatePosts do
  use Ecto.Migration

  def change do
    create(:posts) do
      add :title, :string
      add :content, :text
    end
  end
end

compiles into this Erlang:

-module('Elixir.M.Repo.Migrations.CreatePosts').

...

change() ->
    _@1 = #{'__struct__' := 'Elixir.Ecto.Migration.Table'} =
          posts,
    'Elixir.Ecto.Migration.Runner':start_command({create,
                          'Elixir.Ecto.Migration':'__prefix__'(_@1)}),
    case case _@1 of
       #{primary_key := _@2} -> _@2;
       _@2 when erlang:is_map(_@2) ->
           erlang:error({badkey, primary_key, _@2});
       _@2 -> _@2:primary_key()
     end
    of
      _@3 when (_@3 =:= nil) or (_@3 =:= false) ->
      _@4 = nil, nil;
      _ ->
      _@4 =
          'Elixir.Ecto.Migration.Runner':repo_config(migration_primary_key,
                             []),
      'Elixir.Ecto.Migration':add(case
                    'Elixir.Access':get(_@4, name, nil)
                      of
                    _@5
                        when (_@5 =:= nil) or
                           (_@5 =:= false) ->
                        id;
                    _@6 -> _@6
                      end,
                      case 'Elixir.Access':get(_@4, type, nil)
                      of
                    _@7
                        when (_@7 =:= nil) or
                           (_@7 =:= false) ->
                        bigserial;
                    _@8 -> _@8
                      end,
                      [{primary_key, true}])
    end,
    'Elixir.Ecto.Migration':add(title, string),
    'Elixir.Ecto.Migration':add(content, text),
    'Elixir.Ecto.Migration.Runner':end_command(),
    _@1.

Even if you don't read Erlang, you can figure out that it's calling Ecto.Migration.Runner.start_command , and then calls functions that add data to the agent, and then finally calls .end_command . All this happens at runtime when the migration is executed, not compile time. At compile time, only the DSL is expanded to this code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM