Skip to content

Instantly share code, notes, and snippets.

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 henrik/ebc0e3a154ceddf150c1867b00c862d6 to your computer and use it in GitHub Desktop.
Save henrik/ebc0e3a154ceddf150c1867b00c862d6 to your computer and use it in GitHub Desktop.
Proof of concept for a version of https://github.com/barsoom/attr_extras that uses block arguments to get lazy-loaded defaults.
module AttrExtras
def pattr_initialize(&block)
arg_names = block.parameters.map(&:last)
arg_names.each do
attr_reader _1
private _1
end
define_method(:__attr_extras_init_block, &block)
define_method(:initialize) do |*args, **kwargs|
# Procs won't enforce the presence of positional arguments. So we need a lambda: https://stackoverflow.com/a/2946734/6962
lambda = method(:__attr_extras_init_block).to_proc
block_binding = instance_exec(*args, **kwargs, &lambda)
raise "Initializer block must return `binding`!" unless block_binding.is_a?(Binding)
arg_names.each do
value = block_binding.local_variable_get(_1)
instance_variable_set("@#{_1}", value)
end
end
end
end
class MyClass
extend AttrExtras
# You sadly need to return a `binding` from here. Haven't found a way around it.
#
# The `@value_set_from_block = …` is here to show that we can put any additional initializer logic in the block, and even use the args.
pattr_initialize { |a, b = "default b", c:, time: Time.now| @value_set_from_block = b.reverse; binding }
attr_reader :value_set_from_block
def show_values
pp({ a: a, b: b, c: c, time: time, value_set_from_block: value_set_from_block })
end
end
MyClass.new("my a", c: "my c").show_values
# This sleep shows that the default Time.now is evaluated on each initialization and not once when the class is defined.
sleep 1
MyClass.new("my a", "my b", c: "my c").show_values
# Complains about missing required arguments, both positional and keyword.
MyClass.new().show_values
@henrik
Copy link
Author

henrik commented May 4, 2022

An inadvisable way around having to return the binding: https://stackoverflow.com/q/72062159/6962

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment