View Source EctoClone (EctoClone v0.2.0)

Documentation for EctoClone.

Summary

Types

Link to this type

related_schema_with_options()

View Source
@type related_schema_with_options() :: module() | list()
@type target() :: struct()

Functions

Link to this function

clone(target, repo, new_attrs, related_schemas_with_options, global_opts \\ [])

View Source

Deep clones a target record.

target is the record to be cloned.

repo is the ecto repo to operate on, the current implementation supports only a single repo.

new_attrs is a map that will override values on the clone of the target.

related_schemas_with_options is the list of schemas to clone in relation to the target. The order of schemas does not matter, EctoClone determines clone order by associations. See examples for setting options per schema. Suppored options include:

map - a function that takes the source row fields as a map and returns the cloned row fields

where - a function that appends a where clause to the sql query and thus filters which rows should be cloned

Examples

To clone a post with it's comments:

post = Repo.insert!(%Post{title: "hello"})
Repo.insert!(%Comment{body: "p", post_id: post.id})
{:ok, clone_id} = EctoClone.clone(post, Repo, %{title: "New Title"}, [Comment])

To clone a post with it's tags, comments, comment edits:

EctoClone.clone(post, Repo, %{}, [
  PostTag,
  Comment,
  CommentEdit
])

Options on schemas allow chaning values of cloned records:

EctoClone.clone(post, Repo, %{}, [
  PostTag,
  [Comment, map: fn comment -> Map.put(comment, :likes, 0) end],
  CommentEdit
])

The where option allows selective cloning at the schema level:

EctoClone.clone(post, Repo, %{}, [
  PostTag,
  [Comment, where: fn query -> where(query, [comment], comment.likes >= 0) end],
  CommentEdit
])

If you use the ecto timestamps and want to set inserted_at on all of your cloned records, you can use the map option globally:

now = your_get_timestamp_function()
EctoClone.clone(
  post,
  Repo,
  %{},
  [PostTag, Comment, CommentEdit],
  map: fn r -> Map.put(r, :inserted_at, now) end
)

Association Requirements

Each related schema being cloned must have either a belongs_to or has_one through association to the target.

A Comment can be cloned along with a Post because it has belongs_to :post, Post

A CommentEdit can be cloned with a Post because it has belongs_to :comment, Comment and has_one :post, through: [:comment, :post]

It is possible to have multiple possible relations to the target. A schema can have multiple has_one through:

schema "comment_pair" do
  belongs_to :comment_a, Comment
  belongs_to :comment_b, Comment

  has_one :post_a, through: [:comment_a, :post]
  has_one :post_b, through: [:comment_b, :post]
end

Or a schema can have both a belongs_to AND has_one through:

schema "moderation_flag" do
  belongs_to :post, Post
  belongs_to :comment, Comment

  has_one :comment_post, through: [:comment, :post]
end

These examples, while contrived, show that a record can have multple potential paths to being related to the target. Every possible path is exhausted when determing if a record is related to the clone target.