bridgetownrb / bridgetown

A next-generation progressive site generator & fullstack framework, powered by Ruby
https://www.bridgetownrb.com
MIT License
1.13k stars 114 forks source link

feat: Dot access for data hashes (front matter, YAML/JSON files, etc.) #144

Closed jaredcwhite closed 3 years ago

jaredcwhite commented 4 years ago

I've been experimenting with ways to provide dot access (aka page.data.title, site.metadata.twitter.handle, etc.) within Ruby templates/plugins. By far I find it the most annoying aspect of transitioning from Liquid to ERB/etc.

Since Bridgetown already switched from plain hashes to using ActiveSupport::HashWithIndifferentAccess, the next logical step would be for that to provide dot access as well. Oddly, I wasn't able to find any widely-referenced example for how to add that, so this is my best attempt at a monkey-patch:

ActiveSupport::HashWithIndifferentAccess.class_eval do
  def respond_to_missing?(key, *)
    return true if "#{key}".end_with?("=")

    key?[key]
  end

  def method_missing(key, *args)
    if "#{key}".end_with?("=")
       self["#{key}".chop] = args.first
    elsif self.key?(key)
      self[key]
    else
      default
    end
  end
end

My feeling is that this should be added as a feature out-of-the-box, but if there's some reason it doesn't sit right with anyone (language purity? performance?), the monkey-patch could be disabled with an ENV var or something.

There's also the issue of keys colliding with valid Ruby methods (couldn't have a front matter key called each for obvious reasons), but that's no different than with ActiveRecord and database column names, so I think most Ruby programmers will understand the caveats involved, and there's always the typical [] access method to fall back on.

jaredcwhite commented 4 years ago

Another nice thing about this is doing data&.something&.something_else&.value…yes, I know the dig method exists for hashes but it's not my favorite pattern TBH.

KonnorRogers commented 4 years ago

I have a blog post on how I did this in Snowpacker if you wanna take a look, yes there's some optimizations I could've been made as pointed out when I posted this on reddit, but you get the idea.

https://www.reddit.com/r/ruby/comments/i7k2i9/creating_dynamic_getters_and_setters_on_a_ruby

https://blog.konnor.site/ruby/dynamic_getters_and_setters/

jaredcwhite commented 3 years ago

I gave this some more thought, and while dot access could be reasonably tested not to cause any issues within the Bridgetown codebase, the same couldn't be said for other gems—or future integration points with Rails for that matter. As an alternative, I had experimented with a subclass of HashWithIndifferentAccess called HashWithDotAccess, and if we switch to that specifically, we don't have to worry about any monkey patch. So I think that's a preferable way to go.