Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

kwatch
Copy link

@kwatch kwatch commented Nov 19, 2016

(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
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
Author

kwatch commented Jan 4, 2017

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
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants