Paulo Janeiro
Paulo Janeiro

Reputation: 3221

Best way to dynamically replace some values of a struct by other values of that struct in an Echo query

I have a function:

def listAll(dataSchema, fields) do                                 
    dataSchema |> order() |> Repo.all()
end

that returns a query result that looks like this:

[%Skeleton.Data.Evento{__meta__: #Ecto.Schema.Metadata<:loaded, "eventos">,
  date: "dewfwef", date_de: nil, date_es: nil, date_fr: nil, date_pt: nil,
  id: nil, inserted_at: nil, text: "hello", text_de: nil,
  text_es: nil, text_fr: "Bonjour", text_pt: "Olá", title: "Good morning", title_de: nil, title_es: nil, title_fr: nil, title_pt: "Bom dia"},
 %Skeleton.Data.Evento{__meta__: #Ecto.Schema.Metadata<:loaded, "eventos">,
  date: "ds", date_de: nil, date_es: nil, date_fr: nil, date_pt: nil, id: nil,
  inserted_at: nil, text: "Bye", text_de: nil, text_es: nil,
  text_fr: nil, text_pt: "Adeus", title: "Good evening", title_de: nil, title_es: nil, title_fr: nil, title_pt: "Boa noite"}]

fields is a list that can contain any number of keys:

fields = [:date_pt, :text_pt, :title_pt]

How to replace all the values of date, text, title (the same key name but without suffix) by the values of text_pt (or whatever key/keys passed in the fields argument) automatically? Note that we don't know upfront which keys we have as they are passed as arguments on a case by case situation and that I would like to do this in the Ecto query, not in the resulting list of structs.

Upvotes: 0

Views: 543

Answers (1)

Dogbert
Dogbert

Reputation: 222388

Here's how I'd do this, assuming fields contains a locale key with a string, e.g. "pt":

def listAll(dataSchema, %{locale: locale}) do                                 
    text_field = :"text_#{locale}"
    dataSchema
    |> select([m], %{m | text: field(m, ^text_field)})
    |> order()
    |> Repo.all()
end

This will replace the value of text with the value of the dynamically calculated field text_field.

Note: You should make sure the locale is validated at some point with a whitelist. If the user can send arbitrary locales, creating that atom can crash the VM. That : interpolation is equivalent to String.to_atom("text_#{locale}").


For the edited question: the function's API looks weird to me. There is probably a way better approach to do what you want to achieve. But if you really need to do it this way, here's the simplest way I can think of right now:

def listAll(dataSchema, fields) do                                 
  dataSchema
  |> select([m], %{m | text: field(m, ^text_field)})
  |> order()
  |> Repo.all()
  |> Enum.map(fn record ->
    Enum.reduce(fields, record, fn field, record ->
      # You might want to replace the Regex with something faster like `:binary.match` + `:binary.part` since you only need to find a '_' and remove everything after that.
      without_suffix = field |> Atom.to_string |> String.replace(~r/_.*/, "") |> String.to_existing_atom
      Map.put(record, without_suffix, Map.get(record, field))
    end)
  end)
end

Upvotes: 1

Related Questions