class UComponent2 module DataAccessor def data=(value) @data = value end def data; @data; end end def self.fn(&block) new(block) end def self.call(data = {}) new.call(data) end def self.to_proc -> (data = {}) { call(data) } end def self.template(&block) define_method(:__template) { block } end def self.defaults(data = {}) define_method(:__defaults) { data } end def initialize(fn = nil) @fn = fn || __template @defaults = respond_to?(:__defaults) ? __defaults : {} end def call(data) builder = Nokogiri::HTML::Builder.new builder.context = @builder builder.extend(DataAccessor) builder.data = apply_defaults_to(data) builder.instance_eval(&@fn) builder.to_html.sub!(/]/, '') end def to_proc -> (data) { call(data) } end private def apply_defaults_to(data) return @defaults.merge(data) if data.is_a?(Hash) data || @defaults end end class HelloWorldV2 < UComponent2 template do span.bar.foo! { text("Hello World") } end end HelloWorldV2.call class GreetV2 < UComponent2 defaults name: 'John Doe' template do span.bar.foo! { text("Hello #{data[:name]}") } end end GreetV2.call GreetV2.call name: 'Rodrigo' [{name: 'Rodrigo'}, {name: 'Talita'}, {}].map(&GreetV2) class NumberV2 < UComponent2 template do strong { text(data) } end end NumberV2.call 1 [1,2,3,4].map(&NumberV2) number_v2 = UComponent2.fn do strong { text(data) } end number_v2.call 1 [1,2,3,4].map(&number_v2)