Skip to content

Instantly share code, notes, and snippets.

@blimmer
Created September 27, 2017 15:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blimmer/20b4e376bce851df1c06af0f529a071a to your computer and use it in GitHub Desktop.
Save blimmer/20b4e376bce851df1c06af0f529a071a to your computer and use it in GitHub Desktop.
Rails Issue 30680 Workaround
# This class exists to make the caching of Foo objects more efficient.
# Marshaling full activerecord objects is expensive, especially in Rails 5
# because of this bug: https://github.com/rails/rails/issues/30680
# This simple ruby class selects a very small subset of parameters needed
# for common operations on these objects. It also acts like Foo, by
# passing attributes through to an in-memory Foo model.
class FooForCaching
attr_accessor :attributes
# This is a list of attributes we want to save in cache
ATTRIBUTES_CACHED = %i(
id
bar
baz
).freeze
# These are methods that we've ensured we have the cached attributes to properly
# calculate.
SAFE_PASSTHROUGH_METHODS = %i(
my_method_1
my_method_2
).concat(ATTRIBUTES_CACHED).freeze
# Constructor
# @param [Hash] attributes the attributes to cache. attributes not provided will be interpreted to be `nil`
def initialize(attributes)
@attributes = attributes.symbolize_keys.slice(*ATTRIBUTES_CACHED)
end
# Don't create the in-memory activerecord model to access the simple id
# attribute.
def id
attributes[:id]
end
# Pass undefined attribute methods to a memoized, in-memory Foo object
# to allow running methods on an ActiveRecord Foo object.
def method_missing(method, *args)
unless SAFE_PASSTHROUGH_METHODS.include?(method)
log_unsafelisted_method_passthrough(method)
end
if respond_to_missing?(method)
ar_model_with_attrs.public_send(method, *args)
else
super
end
end
# See https://rubocop.readthedocs.io/en/latest/cops_style/#stylemethodmissing
def respond_to_missing?(method, *args)
ar_model_with_attrs.public_methods.include?(method) || super(method, args)
end
# Do not accidentally cache the in-memory ActiveRecord object if it was created
# before the Marshal dump was called. Caching that object would re-introduce
# the problem that this class intends to solve.
def marshal_dump
@attributes
end
# Set the attributes instance variable when this object is unmarshalled from
# cache.
def marshal_load(attributes)
@attributes = attributes
end
private
def ar_model_with_attrs
@ar_model = Foo.new(@attributes).tap(&:readonly!)
end
# exposed for testing
def log_unsafelisted_method_passthrough(method)
Rails.logger.warn(
"[FooForCaching] Method #{method} called. This method will be delegated, but it's not explicitly marked as safe."
)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment