# Create Actions Create actions are used to create new records in the data layer. For example: ```elixir # on a ticket resource create :open do accept [:title] change set_attribute(status: :open) end ``` Here we have a create action called `:open` that allows setting the `title`, and sets the `status` to `:open`. It could be called like so: ```elixir Ticket |> Ash.Changeset.for_create(:open, %{title: "Need help!"}) |> Ash.create!() ``` See the [Code Interface guide](documentation/topics/code-interface.md) for creating an interface to call the action more elegantly, like so: ```elixir Support.open_ticket!("Need help!") ``` ## Bulk creates Bulk creates take a list or stream of inputs for a given action, and batches calls to the underlying data layer. Given our example above, you could call `Ash.bulk_create` like so: ```elixir Ash.bulk_create([%{title: "Foo"}, %{title: "Bar"}], Ticket, :open) ``` > ### Check the docs! {: .warning} > Make sure to thoroughly read and understand the documentation in `Ash.bulk_create/4` before using. Read each option and note the default values. By default, bulk creates don't return records or errors, and don't emit notifications. ## Performance Generally speaking, all regular Ash create actions are compatible (or can be made to be compatible) with bulk create actions. However, there are some important considerations. - `Ash.Resource.Change` modules can be optimized for bulk actions by implementing `batch_change/3`, `before_batch/3` and `after_batch/3`. If you implement `batch_change/3`, the `change` function will no longer be called, and you should swap any behavior implemented with `before_action` and `after_action` hooks to logic in the `before_batch` and `after_batch` callbacks. - Actions that reference arguments in changes, i.e `change set_attribute(:attr, ^arg(:arg))` will prevent us from using the `batch_change/3` behavior. This is usually not a problem, for instance that change is lightweight and would not benefit from being optimized with `batch_change/3` - If your action uses `after_action` hooks, or has `after_batch/3` logic defined for any of its changes, then we *must* ask the data layer to return the records it inserted. Again, this is not generally a problem because we throw away the results of each batch by default. If you are using `return_records?: true` then you are already requesting all of the results anyway. ## Returning a Stream Returning a stream allows you to work with a bulk action as an Elixir Stream. For example: ```elixir input_stream() |> Ash.bulk_create(Resource, :action, return_stream?: true, return_records?: true) |> Stream.map(fn {:ok, result} -> # process results {:error, error} -> # process errors end) |> Enum.reduce(%{}, fn {:ok, result}, acc -> # process results {:error, error} -> # process errors end) ``` > ### Be careful with streams {: .warning} > Because streams are lazily evaluated, if you were to do something like this: > ```elixir > [input1, input2, ...] # has 300 things in it > |> Ash.bulk_create( > Resource, > :action, > return_stream?: true, > return_records?: true, > batch_size: 100 # default is 100 > ) > |> Enum.take(150) # stream has 300, but we only take 150 > ``` > What would happen is that we would insert 200 records. The stream would end after we process the first two batches of 100. Be sure you aren't using things like `Stream.take` or `Enum.take` to limit the amount of things pulled from the stream, unless you actually want to limit the number of records created. ## What happens when you run a create Action When All actions are run in a transaction if the data layer supports it. You can opt out of this behavior by supplying `transaction?: false` when creating the action. When an action is being run in a transaction, all steps inside of it are serialized because transactions cannot be split across processes. - Authorization is performed on the changes - A before action hook is added to set up belongs_to relationships that are managed. This means potentially creating/modifying the destination of the relationship, and then changing the `destination_attribute` of the relationship. - `before_transaction` and `around_transaction` hooks are called (`Ash.Changeset.before_transaction/2`). Keep in mind, any validations that are marked as `before_action? true` (or all global validations if your action has `delay_global_validations? true`) will not have happened at this point. - A transaction is opened if the action is configured for it (by default they are) and the data layer supports transactions - `before_action` hooks are performed in order - The main action is sent to the data layer - `after_action` hooks are performed in order - Non-belongs-to relationships are managed, creating/updating/destroying related records. - The transaction is closed, if one was opened - `after_transaction` hooks are invoked with the result of the transaction (even if it was an error)