Created
September 27, 2017 15:04
-
-
Save blimmer/20b4e376bce851df1c06af0f529a071a to your computer and use it in GitHub Desktop.
Rails Issue 30680 Workaround
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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