Solid (Liquid) templating basic for loop in Phoenix

Using the Solid hex, which is a Liquid templating language implementation in Phoenix 1.6

How to I get a for loop working with a Phoenix generated list of structs, I am suspecting there is some format particular to Solid

iex(2)> alias App.Channels
App.Channels
iex(3)> channels = Channels.list_channels()
[debug] QUERY OK source="channels" db=10.2ms decode=1.3ms queue=12.0ms idle=788.2ms
SELECT c0."id", c0."about", c0."address", c0."banner", c0."contact", c0."description", c0."disclaimer", c0."footer", c0."icon", c0."meta_description", c0."name", c0."primary_color", c0."privacy", c0."secondary_color", c0."slug", c0."status", c0."tagline", c0."terms", c0."is_free?", c0."is_published?", c0."is_listed?", c0."is_featured?", c0."is_private?", c0."is_invitable?", c0."is_previewable?", c0."is_subscribable?", c0."is_suspended?", c0."snowflake", c0."creator_id", c0."owner_id", c0."contact_id", c0."website_id", c0."inserted_at", c0."updated_at" FROM "channels" AS c0 []
[
  %App.Channels.Channel{
    website_id: 1,
    is_listed?: nil,
    updated_at: ~N[2021-11-07 14:49:20],
    description: nil,
    banner: nil,
    status: nil,
    is_free?: false,
    owner_id: 1,
    secondary_color: nil,
    is_previewable?: nil,
    disclaimer: nil,
    meta_description: nil,
    is_private?: nil,
    snowflake: nil,
    icon: nil,
    hosts: #Ecto.Association.NotLoaded<association :hosts is not loaded>,
    tagline: nil,
    name: "niccolox-channel",
    channel_hosts: #Ecto.Association.NotLoaded<association :channel_hosts is not loaded>,
    address: nil,
    privacy: nil,
    terms: nil,
    primary_color: nil,
    is_suspended?: nil,
    is_invitable?: nil,
    __meta__: #Ecto.Schema.Metadata<:loaded, "channels">,
    inserted_at: ~N[2021-11-07 14:49:20],
    posts: #Ecto.Association.NotLoaded<association :posts is not loaded>,
    is_published?: nil,
    contact: nil,
    slug: nil,
    is_subscribable?: nil,
    is_featured?: nil,
    creator_id: nil,
    about: nil,
    website: #Ecto.Association.NotLoaded<association :website is not loaded>,
    id: 1,
    footer: nil,
    contact_id: nil
  },
  %App.Channels.Channel{
    website_id: 1,
    is_listed?: nil,
    updated_at: ~N[2021-11-07 14:49:20],
    description: nil,
    banner: nil,
    status: nil,
    is_free?: false,
    owner_id: 1,
    secondary_color: nil,
    is_previewable?: nil,
    disclaimer: nil,
    meta_description: nil,
    is_private?: nil,
    snowflake: nil,
    icon: nil,
    hosts: #Ecto.Association.NotLoaded<association :hosts is not loaded>,
    tagline: nil,
    name: "free-channel",
    channel_hosts: #Ecto.Association.NotLoaded<association :channel_hosts is not loaded>,
    address: nil,
    privacy: nil,
    terms: nil,
    primary_color: nil,
    is_suspended?: nil,
    is_invitable?: nil,
    __meta__: #Ecto.Schema.Metadata<:loaded, "channels">,
    inserted_at: ~N[2021-11-07 14:49:20],
    posts: #Ecto.Association.NotLoaded<association :posts is not loaded>,
    is_published?: nil,
    contact: nil,
    slug: nil,
    is_subscribable?: nil,
    is_featured?: nil,
    creator_id: nil,
    about: nil,
    website: #Ecto.Association.NotLoaded<association :website is not loaded>,
    id: 2,
    footer: nil,
    contact_id: nil
  },
  %App.Channels.Channel{
    website_id: 2,
    is_listed?: nil,
    updated_at: ~N[2021-11-07 14:49:20],
    description: nil,
    banner: nil,
    status: nil,
    is_free?: false,
    owner_id: 1,
    secondary_color: nil,
    is_previewable?: nil,
    disclaimer: nil,
    meta_description: nil,
    is_private?: nil,
    snowflake: nil,
    icon: nil,
    hosts: #Ecto.Association.NotLoaded<association :hosts is not loaded>,
    tagline: nil,
    name: "website-channel",
    channel_hosts: #Ecto.Association.NotLoaded<association :channel_hosts is not loaded>,
    address: nil,
    privacy: nil,
    terms: nil,
    primary_color: nil,
    is_suspended?: nil,
    is_invitable?: nil,
    __meta__: #Ecto.Schema.Metadata<:loaded, "channels">,
    inserted_at: ~N[2021-11-07 14:49:20],
    posts: #Ecto.Association.NotLoaded<association :posts is not loaded>,
    is_published?: nil,
    contact: nil,
    slug: nil,
    is_subscribable?: nil,
    is_featured?: nil,
    creator_id: nil,
    about: nil,
    website: #Ecto.Association.NotLoaded<association :website is not loaded>,
    id: 3,
    footer: nil,
    contact_id: nil
  },
  %App.Channels.Channel{
    website_id: 2,
    is_listed?: nil,
    updated_at: ~N[2021-11-07 14:49:20],
    description: nil,
    banner: nil,
    status: nil,
    is_free?: false,
    owner_id: 1,
    secondary_color: nil,
    is_previewable?: nil,
    disclaimer: nil,
    meta_description: nil,
    is_private?: nil,
    snowflake: nil,
    icon: nil,
    hosts: #Ecto.Association.NotLoaded<association :hosts is not loaded>,
    tagline: nil,
    name: "local-channel",
    channel_hosts: #Ecto.Association.NotLoaded<association :channel_hosts is not loaded>,
    address: nil,
    privacy: nil,
    terms: nil,
    primary_color: nil,
    is_suspended?: nil,
    is_invitable?: nil,
    __meta__: #Ecto.Schema.Metadata<:loaded, "channels">,
    inserted_at: ~N[2021-11-07 14:49:20],
    posts: #Ecto.Association.NotLoaded<association :posts is not loaded>,
    is_published?: nil,
    contact: nil,
    slug: nil,
    is_subscribable?: nil,
    is_featured?: nil,
    creator_id: nil,
    about: nil,
    website: #Ecto.Association.NotLoaded<association :website is not loaded>,
    id: 4,
    footer: nil,
    contact_id: nil
  }
]
iex(4)> template = "{% for channel in channels %} channel {% else %} The collection is empty. {% endfor %}"
"{% for channel in channels %} channel {% else %} The collection is empty. {% endfor %}"
iex(5)> {:ok, template} = Solid.parse(template)
{:ok,
 %Solid.Template{
   parsed_template: [
     tag: [
       {Solid.Tag.For,
        [
          field: ["channel"],
          enumerable: [field: ["channels"]],
          parameters: %{},
          result: [text: [" channel "]],
          else_exp: [result: [text: [" The collection is empty. "]]]
        ]}
     ]
   ]
 }}
iex(6)> Solid.render(template, %{ "channels" => channels }) |> to_string
" channel  channel  channel  channel "

but when I try to access channel.name I get an error

iex(7)> template = "{% for channel in channels %} {{ channel.name }} {% else %} The collection is empty. {% endfor %}"
"{% for channel in channels %} {{ channel.name }} {% else %} The collection is empty. {% endfor %}"
iex(8)> {:ok, template} = Solid.parse(template)
{:ok,
 %Solid.Template{
   parsed_template: [
     tag: [
       {Solid.Tag.For,
        [
          field: ["channel"],
          enumerable: [field: ["channels"]],
          parameters: %{},
          result: [
            text: [" "],
            object: [argument: [field: ["channel", "name"]], filters: []],
            text: [" "]
          ],
          else_exp: [result: [text: [" The collection is empty. "]]]
        ]}
     ]
   ]
 }}
iex(9)> Solid.render(template, %{ "channels" => channels }) |> to_string
** (UndefinedFunctionError) function App.Channels.Channel.fetch/2 is undefined (App.Channels.Channel does not implement the Access behaviour)
    (app 0.1.0) Appbot.Channels.Channel.fetch(%App.Channels.Channel{website_id: 1, is_listed?: nil, updated_at: ~N[2021-11-07 14:49:20], description: nil, banner: nil, status: nil, is_free?: false, owner_id: 1, secondary_color: nil, is_previewable?: nil, disclaimer: nil, meta_description: nil, is_private?: nil, snowflake: nil, icon: nil, hosts: #Ecto.Association.NotLoaded<association :hosts is not loaded>, tagline: nil, name: "niccolox-channel", channel_hosts: #Ecto.Association.NotLoaded<association :channel_hosts is not loaded>, address: nil, privacy: nil, terms: nil, primary_color: nil, is_suspended?: nil, is_invitable?: nil, __meta__: #Ecto.Schema.Metadata<:loaded, "channels">, inserted_at: ~N[2021-11-07 14:49:20], posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, is_published?: nil, contact: nil, slug: nil, is_subscribable?: nil, is_featured?: nil, creator_id: nil, about: nil, website: #Ecto.Association.NotLoaded<association :website is not loaded>, id: 1, footer: nil, contact_id: nil}, "name")
    (elixir 1.12.2) lib/access.ex:285: Access.get/3
    (solid 0.10.0) lib/solid/context.ex:90: Solid.Context.do_get_in/2
    (elixir 1.12.2) lib/enum.ex:1582: Enum."-map/2-lists^map/1-0-"/2
    (solid 0.10.0) lib/solid/context.ex:21: Solid.Context.get_in/3
    (solid 0.10.0) lib/solid/argument.ex:35: Solid.Argument.get/3
    (solid 0.10.0) lib/solid/object.ex:11: Solid.Object.render/3
    (solid 0.10.0) lib/solid.ex:102: Solid.do_render/3
    (solid 0.10.0) lib/solid.ex:85: anonymous fn/3 in Solid.render/3
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (solid 0.10.0) lib/solid.ex:83: Solid.render/3
    (solid 0.10.0) lib/solid/tag/for.ex:96: anonymous fn/6 in Solid.Tag.For.do_for/5
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (solid 0.10.0) lib/solid/tag/for.ex:89: Solid.Tag.For.do_for/5
    (solid 0.10.0) lib/solid/tag.ex:89: Solid.Tag.eval/3
    (solid 0.10.0) lib/solid.ex:111: Solid.render_tag/3
    (solid 0.10.0) lib/solid.ex:85: anonymous fn/3 in Solid.render/3
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (solid 0.10.0) lib/solid.ex:83: Solid.render/3
    (solid 0.10.0) lib/solid.ex:71: Solid.render/3
iex(9)>

You can see from that stacktrace it is suggesting rather strongly that it is calling get_in, which is not going to work on your struct unless you implement the Access behaviour.

You could read the docs/source for Solid.render or naively try turning your channels into maps to see if that works.

thanks @cmotl you are correct I think

currently not possible, maybe new feature for Solid

Open Question:
What patterns do work with Enumerables in Solid?

Can I make a non-Phoenix Context generator derived Ecto call and use that data structure to get working for loop?

Sorry, I have no knowledge of this Solid thing.

Did you try it using Map.from_struct/1? You can use whatever call you want/need to get the data how you need it.

I’ve never used the phoenix context generators apart from the initial “let’s see how they work”. Remeber “contexts aren’t a thing” :wink: