docile
docile copied to clipboard
How to execute a method from a context in a different context?
Hi,
We're using Docile on Avo to manage some parts of our DSL. We're experiencing an issue where a method call within the block is not executing in the desired context. Here's a simplified example of the problem:
require "docile"
module HasItems
attr_reader :items
def initialize
@items = []
end
def field(id)
@items << id
end
end
class Panel
include HasItems
end
class Resource
include HasItems
def panel(&block)
panel = Docile.dsl_eval(Panel.new, &block)
puts "Panel items: #{panel.items}"
end
def extracted_fields
puts "Context inside extracted_fields method: #{self}"
field :extracted
end
def fields
field :id
panel do
puts "Context inside panel do block: #{self}"
extracted_fields
end
end
end
resource = Resource.new
resource.fields
puts "Resource items: #{resource.items}"
Result:
Context inside panel do block: #<Panel:0x00007fd830d3d488>
Context inside extracted_fields method: #<Resource:0x00007fd830d3d758>
Panel items: []
Resource items: [:id, :extracted]
Expected result (ignoring the context prints):
Panel items: [:extracted]
Resource items: [:id]
We'd like to make it as easy as possible to extract methods in the most natuarl way, as a method that they can run inside our custom blocks (panel
, tab
, sidebar
, and more).
When the panel
block it's executed the extracted_fields
method is not found, so it falls back to the @__fallback__
context which is the resource's context. The problem here is that the code from extracted_fields
was called from the panel
block with the intention to add a field to that panel, but it was added to the resource
.
We've found 2 workarounds
1 - Pass the context to the method
We don't like this approach as it makes the user learn yeat another pattern that they might implement badly and doesn't feel that natural.
def extracted_fields(context)
puts "Context inside extracted_fields method: #{self}"
context.field :extracted
end
def fields
field :id
panel do
puts "Context inside panel do block: #{self}"
extracted_fields(self)
end
end
2 - Make extracted_fields
return a Proc
and instance_exec
it
It seems unnatural again to have a method run a block. But mor importantly, they have to call instance_exec
inside the panel
block which we don't want
def extracted_fields
puts "Context inside extracted_fields method: #{self}"
-> { field :extracted }
end
def fields
field :id
panel do
puts "Context inside panel do block: #{self}"
instance_exec &extracted_fields
end
end
Both workarounds require public DSL interaction, and one of our goals is to keep the public DSL as simple as possible.
Ideal scenario(s)
The ideal scenario would be that they declare the methods on the resource file and just rub them inside the custom blocks (panel
, tab
, sidebar
, and more).
def extracted_fields
field :extracted
end
def fields
field :id
panel do
extracted_fields
end
end
Next ideal scenario
If that's impossible, we'd like to expose a simple method call or something similar that would be simple to implement and least destructive.
def extracted_fields
field :extracted
end
def fields
field :id
panel do
unpack_fields extracted_fields
# or maybe
unpack_fields -> { extracted_fields }
end
end
Is there a more elegant solution or best practice within Docile to ensure that method calls within a block execute in the expected context while avoiding the need for additional public DSL interactions? We'd greatly appreciate your help and guidance on this matter.
Thank you!