I help maintain an open source Ruby library.
Every now and then, we get feature requests that make me scratch my head.
Isn't this a syntax error?
class Foo < Dash
include Persistable.new(persist_method: :commit)
end
Yes, yes it is.
module Persistable
def initialize(*args)
end
end
class Foo < Dash
include Persistable.new(persist_method: :commit)
end
#=> NoMethodError: undefined method `new'
Yay, no syntax errors!
module Persistable
def self.new(*args)
self
end
end
class Foo < Dash
include Persistable.new(persist_method: :commit)
end
What can we do with this?
Michael Herold
Full-stack Engineer, AcceptOn
module Persistable
def self.new(options = {})
adapter = options.delete(:adapter) || :json
persist_method = options.delete(:persist_method) || :persist
Builder.new(adapter, persist_method).mod
end
end
module Persistable
def self.included(base)
base.send(:include, new)
end
end
class PersistableClass
include Persistable
end
class SecondPersistableClass
include Persistable.new
end
class Builder
attr_reader :mod
def initialize(adapter_type, persist_method, mod = Module.new)
@adapter_type = adapter_type
@persist_method = persist_method.to_sym
@mod = mod
add_adapter
add_persist_method
override_persist_method
end
end
#add_adapter
class Builder
def add_adapter
mod.send(:include, Persistable.adapters[@adapter_type])
end
end
module Persistable
def self.adapters
@adapters ||= {}
end
def self.register_adapter(identifier, adapter)
adapters[identifier] = adapter
self
end
end
#add_persist_method
class Builder
def add_persist_method
mod.send(:include, Persistable::Persistence)
end
end
module Persistable
module Persistence
def persist(store = nil)
# ...
end
end
end
#override_persist_method
class Builder
def override_persist_method
return if @persist_method == :persist
mod.send(:alias_method, @persist_method, :persist)
mod.send(:undef_method, :persist)
end
end
So, what is generically useful outside of this context?
class Builder
attr_reader :mod
def initialize
# Stateless hook
add_persist_method
end
def add_persist_method
mod.send(:include, Persistable::Persistence)
end
end
class Builder
def inclusions
[]
end
private
def add_included_hook
inclusions.each { |inclusion| mod.send(:include, inclusion) }
end
end
class PersistableBuilder < Builder
def inclusions
[Persistable::Persistence]
end
end
class Builder
def initialize
# Stateful hooks
add_adapter
override_persist_method
end
end
class Builder
def hooks
[]
end
private
def add_defined_hooks
hooks.each { |hook| send(:hook) }
end
end
class PersistableBuilder < Builder
def hooks
[:add_adapter, :override_persist_method]
end
private
def add_adapter
mod.send(:include, Persistable.adapters[@adapter_type])
end
def override_persist_method
# ...
end
end
module MyBuildableModule
include Buildable
builder MyBuilder
end
class MyClass
include MyBuildableModule.new(pet: :peeve)
end
class UnconfiguredPersistableHash < Hash
include Hashie::Extensions::Persistable
end
hash = UnconfiguredPersistableHash.new
hash.class.ancestors.map(&:to_s)
# => [
"UnconfiguredPersistableHash",
"#<Module:0x005591dc2d51e0>",
"Hashie::Extensions::Persistable::Persistence",
"Hashie::Extensions::Persistable::Json",
"Hashie::Extensions::Persistable",
"Hash",
# ...]
I'm still working out the details of what I want to include.
Is there anything you can extract into a gem?
Presentation available at:
https://michaelherold.github.io/module_presentation
Source code will (likely) be available soon at:
https://github.com/michaelherold/module_builder
Want to chat?