Just a style question; many have helped me before. I have:
def fetch_or_insert_label(label) do
case Repo.get_by(Label, label: label) do
nil -> Repo.insert!(%Label{label: label})
label -> label
end
end
def fetch_or_insert_release_type(release_type) do
case Repo.get_by(ReleaseType, release_type: release_type) do
nil -> Repo.insert!(%ReleaseType{release_type: release_type})
release_type -> release_type
end
end
I have a bunch of these - how can I do something dynamic, ie:
artist = fetch_or_insert_by_model(Artist, artist_name)
def fetch_or_insert_by_model(model, artist) do
case Repo.get_by(model, artist: artist) do
nil -> Repo.insert!(%model{artist: artist})
artist -> artist
end
end
This yields in the following error:
error: expected struct name to be a compile time atom or alias, got: model
│
95 │ nil -> Repo.insert!(%model{artist: artist})
│
Just wondering if there’s an easier way - I have 6-7 of these to consolidate…
For that you can use struct/2 to construct the data you want from a given struct module.
From the doc:
Creates and updates a struct.
The struct argument may be an atom (which defines defstruct) or a struct
itself. The second argument is any Enumerable that emits two-element tuples
(key-value pairs) during enumeration.
Keys in the Enumerable that don’t exist in the struct are automatically
discarded. Note that keys must be atoms, as only atoms are allowed when
defining a struct. If keys in the Enumerable are duplicated, the last entry
will be taken (same behaviour as Map.new/1).
This function is useful for dynamically creating and updating structs, as well
as for converting maps to structs; in the latter case, just inserting the
appropriate :struct field into the map may not be enough and struct/2
should be used instead.
## Examples
defmodule User do
defstruct name: "john"
end
struct(User)
#=> %User{name: "john"}
opts = [name: "meg"]
user = struct(User, opts)
#=> %User{name: "meg"}
struct(user, unknown: "value")
#=> %User{name: "meg"}
struct(User, %{name: "meg"})
#=> %User{name: "meg"}
# String keys are ignored
struct(User, %{"name" => "meg"})
#=> %User{name: "john"}
artist = fetch_or_insert_model(Artist, %{artist: artist_name})
def fetch_or_insert_model(model, attrs) do
model_struct = struct(model, attrs)
{:ok, fetched_model} = Repo.insert(model_struct, on_conflict: :nothing, returning: true)
fetched_model
end
Seems to work great! The on_confict: :nothing doesn’t fetch the record on the second run, so will figure that out. This pattern is good though as we can reuse the method for all purposes.
artist = fetch_or_insert_model(Artist, %{artist: artist_name})
def fetch_or_insert_model(model, attrs) do
model_struct = struct(model, attrs)
case Repo.get_by(model, attrs) do
nil -> Repo.insert!(model_struct)
attrs -> attrs
end
end
If you want to insert new record or fetch existing one you can also do something like below.
Knowing that Repo.insert/1 will return {:ok, record} or {:error, changeset} you can do this.
artist = fetch_or_insert_model(Artist, %{artist: artist_name})
def fetch_or_insert_model(model, attrs) do
model_struct = struct(model, attrs)
case Repo.insert(model_struct) do
{:ok, entity} -> entity
{:error, changeset} -> Repo.get_by!(model, changeset.data)
end
end
I would love the upset to work, but no matter what I did with on_conflict, I would not get the already-present row from the database (on the second run).