How to implement a dsl module?
I'm quite a newbie in ruby who started to learn metaprogramming techniques in this language. Now I'm trying to write a DSL module to be able to annotate classes like in RoR. Unfortunately, I still don't get some things on this topic which causes some troubles in realisation.
Here's a code example of a tag annotation:
module Annotations
def self.included(host_class)
host_class.extend(ClassMethods)
end
def tags(*values)
if values.empty?
if defined? @tags
@tags
else
self.class.tags(values)
end
else
@tags = []
values.map { |value| validate_tag(value) }.each { |tag| @tags << tag}
end
end
module ClassMethods
def tags(*values)
unless defined? @tags
superclass_tags = self.superclass.tags if self.superclass.respond_to?(:tags)
@tags = superclass_tags&.any? ? superclass_tags : []
end
if values.empty?
@tags
else
if self.superclass.respond_to?(:tags)
values.map { |value| validate_tag(value) }.each { |tag| @tags << tag}
else
@tags = values.map { |value| validate_tag(value) }
end
@tags
end
end
alias_method :tag, :tags
def validate_tag(tag)
raise 'Tag should be less than 15 chars.' if tag.to_s.length > 15
end
end
end
class Foo
include Annotations
tags :x1, :x2
end
When I try to execute it, then it will be always the same wrong output.
Foo.tags
=> [nil, nil]
Foo.tag 'c'
Foo.tags
=> [nil]
foo2 = Foo.new
foo2.tags :c5, :c9
foo2.tags
=> [nil, nil]
# etc...
Could you help me to improve code/make me understand where I've made a mistake?
Solution 1:
The only problem is in your validate_tag
method. The guard is checking if the length of the tag as a string is greater than 15 and raising an exception if that happens, but there's no value explicitly added for when that doesn't happen. And in that case Ruby returns nil.
You could try updating it to return the tag if tag.to_s.length > 15
returns false:
def validate_tag(tag)
raise 'Tag should be less than 15 chars.' if tag.to_s.length > 15
tag
end
Solution 2:
Your validate_tag
method does not return tag
, but you are mapping over your values
argument in tags
and calling validate_tag(value)
, converting an array like [:foo, :bar]
into [nil, nil]
.