Skip to content

feat(visitor): allow to create custom object (instead of Hash) according to context - #299

Open
kwatch wants to merge 8 commits into
ruby:masterfrom
kwatch:topic_custom-class-visitor
Open

feat(visitor): allow to create custom object (instead of Hash) according to context#299
kwatch wants to merge 8 commits into
ruby:masterfrom
kwatch:topic_custom-class-visitor

Conversation

@kwatch

Copy link
Copy Markdown

(Redirected from https://bugs.ruby-lang.org/issues/12960?next_issue_id=12959 .)

New visitor class to create custom object intead of Hash.
It creates custom object according to context (= last mapping key).

  • Enables data access like ydoc.foo.bar instead of ydoc['foo']['bar'].
  • Creates custom object instead of Hash, for example ydoc['teams'][0].is_a?(Team) and ydoc['teams'][0].members[0].is_a?(Member).

See document of CustomClassVisitor class for details.

You may feel that CustomClassVisitor is very experimental. I hope that at least changes of 'to_ruby.rb' will be imported to upstream, because implementation of CustomClassVisitor requires monkey-pathing to 'to_ruby.rb'.

This is necessary to create custom object as mapping instead of Hash.
For example, if you prefer `hashobj.key` instead of `hashobj['key']`:
## allows `h.foo` instead of `h['foo']`
class MagicHash < Hash
def method_missing(method, *args)
return super unless args.empty?
return self[method.to_s]
end
end
## override to generate MagicHash instead of Hash
class MagicVisitor < Psych::Visitors::ToRuby
def empty_mapping(o)
MagicHash.new
end
end
## example to access `ydoc.foo` instead of `ydoc['foo']`
input = <<'END'
tables:
- name: admin_users
columns:
- name: id
type: int
pkey: true
END
tree = Psych.parse(input)
visitor = MagicVisitor.create
ydoc = visitor.accept(tree)
p ydoc.tables[0].columns[0].name #=> "name"
p ydoc.tables[0].columns[0].type #=> "int"
This is necessary to generate custom object as mapping instead of Hash
according to context. For example:
TableObj = Struct.new('TableObj', 'name', 'columns')
ColumnObj = Struct.new('ColumnObj', 'name', 'type', 'pkey')
class CustomVisitor < Psych::Visitors::ToRuby
def initialize(*args)
super
@key_path = [] # ex: [] -> ['tables'] -> ['tables', 'columns']
end
def accept_key(k) # push keys
key = super k
@key_path << key
return key
end
def accept_value(v) # pop keys
value = super v
@key_path.pop()
return value
end
def empty_mapping(o) # generate custom object instead of Hash
case @key_path.last
when 'tables' ; return TableObj.new
when 'columns' ; return ColumnObj.new
else ; return super o
end
end
end
## example to generate custom object according to context
input = <<'END'
tables:
- name: admin_users
columns:
- name: id
type: int
pkey: true
END
tree = Psych.parse(input)
visitor = CustomVisitor.create
ydoc = visitor.accept(tree)
p ydoc['tables'][0].class #=> Struct::TableObj
p ydoc['tables'][0]['columns'][0].class #=> Struct::ColumnObj
This new class allows user to generate custom object instead of Hash.
See document of CustomClassVisitor class for details.
@tenderlove

Copy link
Copy Markdown
Member

I think I'm OK with the accept_key and accept_value change but not the addition of a new visitor class. I used the AST / Visitor pattern specifically so that people could implement (and maintain) their own visitors.

@kwatch

Copy link
Copy Markdown
Author

Thanks a lot.

I think I'm OK with the accept_key and accept_value change but not the addition of a new visitor class.

I see. I'll revert the following commits.

  • (#b5d3885) feat(psych): define 'Psych::Visitors::CustomClassVisitor' class
  • (#b81d44e) feat(psych): support key '*' for default custom class
  • (#34883b8) feat(psych): add test script

And I'll leave the rest commits.

  • (#4213ebe) feat(psych): allow to generate custom Hash object
  • (#c9061f6) feat(psych): allow custom Hash object for unknown tagged mapping
  • (#95eab41) feat(psych): add hook point for mapping key and value
  • (#080fd60) feat(psych): add hook points for mapping-like object
  • (#b14d98f) feat(psych): add hook points for merge ('<<')

Is it OK? Give me your advice.

@tenderlove

Copy link
Copy Markdown
Member

@kwatch yes, that makes sense. Please do it!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@kwatch@tenderlove