Error Accessing Custom Object Attributes via Liquid Template

I created a custom generator in Jekyll that randomly select templates and applies posts to them. To organize, I created a Section class that organizes each section and adds it to an array in site data called homepage_sections. This works as expected, but when I try and reference anything in the Section object stored in *site.data[“homepage_sections”], I get an error. The Section object holds a Hash of placements, each having a template and set of Jekyll posts.

template (index.html) – one example of what I have tried

{% for section in site.data["homepage_sections"] %}
<section class="section">
    <div class="container-fuild">
      <div class="masonry-box clearfix">
        {% for placement in section.placements %}
          <div class="{{ placement[0] }}-side">
            {% include posts/{{placement[1].template}}.html %}  
          </div>
        {% endfor %}
      </div>
    </div>
</section>
{% endfor %}

error:

Liquid Exception: undefined method `to_liquid' for #<SevenElephants::SectionGenerators::Section:0x0000004008b97a88 @placements={"left"=>{"template"=>"double", "posts"=>[#<Jekyll::Document _posts/team/2023-02-27-trust-starts-here.md collection=posts>, #<Jekyll::Document _posts/personal/2023-03-27-feeling-shiny-new.md collection=posts>]}, "center"=>{"template"=>"double", "posts"=>[#<Jekyll::Document _posts/team/2023-02-06-direct-conversations.md collection=posts>, #<Jekyll::Document _posts/team/2023-02-14-agile-is-rigorous.md collection=posts>]}, "right"=>{"template"=>"double", "posts"=>[#<Jekyll::Document _posts/team/2023-01-28-need-to-drop.md collection=posts>, #<Jekyll::Document _posts/opinions/2023-02-01-product-versus-project.md collection=posts>]}}> variable = variable.to_liquid ^^^^^^^^^^ in index.html

Thanks in advance for any/all help!

From what I understand, you’re trying to access an instance variable (“@” variable) from your liquid template, when the syntax you used (instance.property) causes liquid to try calling the to_liquid method on your Section object.

You can try using the Convertible mixin in the same way it’s done for pages in the Jekyll codebase. This will provide the missing to_liquid method (you may need to set the ATTRIBUTES_FOR_LIQUID array)

Another way to fix this might be to replace the Section objects with Hashes

PS : Can you re-run jekyll build (or serve) with the --trace option and post the full output?

Thank you @pcouy. I will look into that. I will say that the instance variable I am trying to access is a Hash, so I would have hoped it would have just worked :slight_smile:

The instance varieble being a hash or something else does not seem related to your error. It’s that doing instance.property in a template seems to call the instance.to_liquid method. From what I understand, to_liquid is expected to return a Hash of the properties you want to be able to access from templates. Using the Convertible mixin seems to be the Jekyll way to do that

@pcouy Going down the Convertible mixin route is proving to be a rabbit hole of needing other Jekyll-based objects (e.g. Converter) when the variable I have in the loop

{% for placement in section.placements %}

is a Hash. I am not sure why Jekyll/Liquid thinks it isn’t

How about making a to_liquidmethod that returns a hash of the properties you want to access from liquid ?

def to_liquid
  {
    "placements" => @placements
  }
end

as a method of your Section class

So much closer. Thank you. I had this way to start, but missed a couple of things.

The to_liquid method

def to_liquid
   @data
end

The index.html template

{% for section in site.data["homepage_sections"] %}
<section class="section">
    <div class="container-fuild">
      <div class="masonry-box clearfix">
        {% for placement in section %}
          <div class="{{ placement[0] }}-side">
            {% include %}
          </div>
        {% endfor %}
      </div>
    </div>
</section>
{% endfor %}

A Section in the markup looks like

{"left"=>{"template"=>"single","posts"=>[Jekyll::Document]s}}

What I can’t figure out now is how to pass the posts into the dynamic include file?

{% assign include_file = placement[1].template | prepend: "posts/" | append: ".html" %}
{% include {{ include_file }} posts=??? %}

If I output placement[1].posts, the posts render to the screen (the markup that is), but can’t seem to provide them as data to render the include

Thanks again for your help on this

So I figured it out.

Here is the full template

---
layout: default
title: Home
---

{% for section in site.data["homepage_sections"] %}
<section class="section">
    <div class="container-fuild">
      <div class="masonry-box clearfix">
        {% for placement in section %}
          {% assign include_file = placement[1].template | prepend: "posts/" | append: ".html" %}
          {% assign include_posts = placement[1].posts %}
          <div class="{{ placement[0] }}-side">
            {% include {{ include_file }} posts=include_posts %}
          </div>
        {% endfor %}
      </div>
    </div>
</section>
{% endfor %}

Here is the full Section class

class Section
            
            attr_reader :data
            
            def initialize(site)
                @data = {}
            end

            def add_posts(place, template, posts)
                @data[place] = {"template" => template, "posts" => posts}
            end

            def get_placement(place) 
                @data[place] ? @data[place] : {}
            end

            def to_liquid
                @data
            end
        end

Thanks!!!

1 Like