Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

several cache-related questions #401

Open
dreyks opened this issue May 17, 2016 · 13 comments
Open

several cache-related questions #401

dreyks opened this issue May 17, 2016 · 13 comments

Comments

@dreyks
Copy link

dreyks commented May 17, 2016

  1. It seems that cache does not take into consideration neither template not cell code change. Rails does template digesting to combat this
  2. Say I want only a part of cell template cached: common use-case is "cached html with uncached js". What is the best way to achieve this?

Currently I've come up with something like this

class MyCell < Cell::ViewModel
  cache :html { 'my_cache_key' }

  def show
    call(:html) + render(:js)
  end

  def html
    render :show
  end
end

And then I have show.erb which has all the html (and this state is cached), and (uncached) js.erb that has all the access to cell instance variables

What d'you think?

Btw, for some weird reason I have to write call(:html) + render(:js).html_safe, otherwise the js template gets escaped

@apotonick
Copy link
Member

I can only reply to 2.) and I think it looks totally fine to me. The point of Cells is to get away from ugly view fragment caching by modelling your view as an object (with multiple states, if you want that).

Only call with rails-cells is html_safeed, render really only returns the string.

@dreyks
Copy link
Author

dreyks commented May 17, 2016

Ok, got it, thanks

But what about code change? Right now I have to Rails.cache.clear after each deploy that changes code in cell class, which is sub-optimal

@apotonick
Copy link
Member

Bring it on, sounds like a good feature!

@dreyks
Copy link
Author

dreyks commented May 17, 2016

This is not as straightforward to implement as I thought. Rails' cache is a view helper, and therefore it knows from which template it was called. Cells' cache on the other hand is called without any knowledge about the template that would render.

Will look into this closer when I have free time

@apotonick
Copy link
Member

Yeah, but we have a better internal API where you can find the respective state template and hash it, on the class level. No problem!

I'm here if you need help - maybe join Gitter and we chat there at some point.

@dreyks
Copy link
Author

dreyks commented May 17, 2016

Yeah, that's what I thought too, but then again, someone can do

def show
  render :surprise
end

and how will we know that...

How do I find the chat-room in gitter?

@apotonick
Copy link
Member

@dreyks
Copy link
Author

dreyks commented May 17, 2016

#402

@schorsch
Copy link

schorsch commented Sep 6, 2016

I would not dive into this template cache digest thing as it over-complicates things.

We rather should go for a simpler cache getter/cleaning methods in the core.

f. ex. this is some code i am using to delete the cache for specific cells:

class SomeCell

  cache :show do
    "#{model.id}/#{model.updated_at.strftime('%Y%m%d%H%M%S')}"
  end

  cache :public_show do
    "#{model.id}/#{model.updated_at.strftime('%Y%m%d%H%M%S')}"
  end

  # Delete the cache for a single or multiple states (views)
  # If states is empty deletes all defined caches
  # Needed whenever a view is updated, for manual deletion of the cache:
  #
  #   Model.select(:id, :updated_at).each{|i| SomeCell.delete_cache( i, :show)}
  #
  # @param [aModel] obj
  # @param [Array<Symbol>, Symbol] states name of cache partial see defs on top
  def self.delete_cache(obj, states)
    state_keys = states.is_a?(Array) ? states : [states]
    state_keys ||= version_procs.keys
    state_keys.each do |i|
      key = cache_key(i, obj)
      #btw. this does not seem to work maybe bcs my local prefix 'cache' in rails.cache_config is not prepended 
      ::Rails.cache.delete(key)   
    end
  end

  # Get a cache key for a given object and state
  # @param [aModel] obj
  # @param [Array<Symbol>, Symbol] states name of cache partial see defs on top
  # @return [String]
  def self.cache_key(state, obj)
    cell = self.(obj)
    # ??
    state_cache_key(state, version_procs[state].evaluate(cell))
  end
end

the specs continue to have a rather ugly lookup:

  describe 'cache keys' do
    it 'has keys' do
      obj = FactoryGirl.create(:a_model)
      cell = cell(:some, obj)
      # WTF ??
      key = cell.class.state_cache_key(:show, cell.class.version_procs[:show].evaluate(cell))
      expect(key).to eq ("cells/some/show/#{obj.id}/#{obj.updated_at.strftime('%Y%m%d%H%M%S')}")
    end
  end

This could be continued with methods to destroy all caches for a cell, all caches for an object across cells, etc. I am using redis + gem readthis for caching and think about to just use http://redis.io/commands/keys command, which by the help of *-lookups would render all of the above useless.

@dreyks
Copy link
Author

dreyks commented Sep 6, 2016

Yeah, after spending some time fiddling with template digesting I gave up and now use a Capistrano task that clears cell caches for cells whose file were changed between deploys

@damien-roche
Copy link

damien-roche commented Jun 27, 2018

Has anything changed? Recently run into this issue myself. I simply added a 'version' to the cell as part of my cache key, and I change that if I update the code. Not ideal, but it works.

Btw, great gem! I've redesigned a monstrous Rails app recently and persevered with cells for the frontend and it has proven to be much cleaner and simpler to maintain. It is especially useful to bundle assets along with the cells.

@PikachuEXE
Copy link
Contributor

Here is my example for caching:
Make cell depends on template + translations + assets

module PokemonTypeCardDefaultStyle

  class Cell < ::SomeBase::Cell

    # === Caching === #
    cache(
      :show,
      :cache_key,
      expires_in: :cache_valid_time_period_length,
      if: :can_be_cached?,
    )

    def can_be_cached?
      # Use following code when child cell(s) is/are used
      # [
      #   child_cell_1,
      # ].all?(&:can_be_cached?)

      true
    end

    def cache_valid_time_period_length
      # Use following code when child cell(s) is/are used
      # [
      #   child_cell_1,
      # ].map(&:cache_valid_time_period_length).min

      # Long time
      100.years
    end

    def self.cache_key
      super.merge(
        # Update this if cell logic updated (code update != logic update)
        logic: :v2017_10_26_1054,
        template: TEMPLATE_FILES_CONTENT_CACHE_KEY,
        translations: TRANSLATION_FILES_CONTENT_CACHE_KEY,
        assets: ASSET_FILES_CONTENT_CACHE_KEY,
      )
    end

    def cache_key
      super.merge(
        current_locale: ::I18n.config.locale,

        pokemon_type: pokemon_type.cache_key,
      )
    end
    # === Caching === #

    def show
      render
    end

    private

    Contract ::Pokemon::Type
    attr_reader :pokemon_type

    TEMPLATE_FILES_CONTENT_CACHE_KEY = begin
      view_folder_path = File.expand_path("views", __dir__)
      file_paths = Dir.glob(File.join(view_folder_path, "**", "*"))

      file_digests = file_paths.map do |file_path|
        next nil unless File.file?(file_path)

        ::Digest::MD5.hexdigest(File.read(file_path))
      end.compact

      ::Digest::MD5.hexdigest(file_digests.join(""))
    end
    private_constant :TEMPLATE_FILES_CONTENT_CACHE_KEY

    TRANSLATION_FILES_CONTENT_CACHE_KEY = begin
      folder_path = ::Rails.root.join(
        "config/locales/path/to/cell/translations",
      )
      file_paths = Dir.glob(File.join(folder_path, "**", "*"))

      file_digests = file_paths.map do |file_path|
        ::Digest::MD5.hexdigest(File.read(file_path))
      end

      ::Digest::MD5.hexdigest(file_digests.join(""))
    end
    private_constant :TRANSLATION_FILES_CONTENT_CACHE_KEY

    
    ASSET_FILES_CONTENT_CACHE_KEY = begin
      folder_path = ::Rails.root.join(
        *[
          "app/assets/images/path/to/cell/assets",
        ].join("/").split("/"),
      )
      file_paths = Dir.glob(File.join(folder_path, "**", "*"))

      file_digests = file_paths.map do |file_path|
        next nil unless File.file?(file_path)

        ::Digest::MD5.hexdigest(File.read(file_path))
      end

      ::Digest::MD5.hexdigest(file_digests.join(""))
    end
    private_constant :ASSET_FILES_CONTENT_CACHE_KEY

  end
end

If you need to a cell to depend on super cell's cache key:

module PokemonTypeCardCompactStyle

  class Cell < ::PokemonTypeCardDefaultStyle::Cell

    # === Caching === #

    def self.cache_key
      super.merge(
        # Update this if cell logic updated (code update != logic update)
        logic:    [
          super.fetch(:logic, nil),
          :v2018_09_17_1426,
        ].compact.join("+"),
        template: TEMPLATE_FILES_CONTENT_CACHE_KEY,
      )
    end

    def cache_key
      super.merge(
        current_locale: ::I18n.config.locale,

        pokemon_type: pokemon_type.cache_key,
      )
    end
    # === Caching === #

    def show
      render
    end

    TEMPLATE_FILES_CONTENT_CACHE_KEY = begin
      view_folder_path = File.expand_path("views", __dir__)
      file_paths = Dir.glob(File.join(view_folder_path, "**", "*"))

      file_digests = file_paths.map do |file_path|
        next nil unless File.file?(file_path)

        ::Digest::MD5.hexdigest(File.read(file_path))
      end.compact

      ::Digest::MD5.hexdigest(file_digests.join(""))
    end
    private_constant :TEMPLATE_FILES_CONTENT_CACHE_KEY
end

@dreyks
Copy link
Author

dreyks commented Sep 20, 2018

yeah, i ended up doing something similar two years ago. i'm not using cells right now though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants