Satchel
Satchel

Reputation: 16724

Use an alias in YAML with Ruby

I have the following YAML:

- PRO_PLAN:
  - description: This is the Pro plan
    publicName: Pro Plan
    startDate: 12-20-2015

  - PRO_MONTHLY_DIRECT:
    - publicName: Pro Monthly 
      price: 20
      sub_target: zone

    - PRICING_COMPONENTS: &pro_entitlements
      analytics_range: 21600
      rules: 10
      cannon: true


  - PRO_ANNUAL_DIRECT:
    - publicName: Pro Annual  
      price: 240
      sub_target: zone

    - PRICING_COMPONENTS:
      <<: *pro_entitlements

The resulting array doesn't bring in the pro_entitlements for the second node:

[8] pry(BF)> app_hash[0]['PRO_PLAN'][1]
=> {"PRO_MONTHLY_DIRECT"=>[{"publicName"=>"Pro Monthly", "price"=>20, "sub_target"=>"zone"}, {"PRICING_COMPONENTS"=>nil, "analytics_range"=>21600, "page_rules"=>10, "polish"=>true}]}
[9] pry(BF)> app_hash[0]['PRO_PLAN'][2]
=> {"PRO_ANNUAL_DIRECT"=>[{"publicName"=>"Pro Annual", "price"=>240, "sub_target"=>"zone"}, {"PRICING_COMPONENTS"=>nil, "<<"=>nil}]}

Upvotes: 3

Views: 1779

Answers (4)

Anthon
Anthon

Reputation: 76598

I consider it a failure of the ruby YAML parser to detect an error. If you try to roundtrip this on my ruamel.yaml parser (Python based, which would preserve the alias/anchor names and the <<<) you get

expected a mapping or list of mappings for merging, but found scalar   
   in "<byte string>", line 11, column 27:  
        - PRICING_COMPONENTS: &pro_entitlements
                              ^

indicating that the rest of PRICING_COMPONENTS is a scalar (null) caused by non-indentation of the key/value pairs.

You don't even need to use python or my parser for that, you can check online that the parser you are using fails to throw an error.

The cause for this is that YAML files can have empty values for mappings. And your failure to indent analytics_range: 21600 make the whole sequence element

- PRICING_COMPONENTS: &pro_entitlements
  analytics_range: 21600
  rules: 10
  cannon: true

a single sequence element consisting of a single map, instead of a single element consisting of a map of PRICING_COMPONENTS to a mapping of the three values beneath it. In your case pro_elements is an anchor on the null value that is cause by the empty value for PRICING_COMPONENTS. So that is all "normal", although not what you wanted.

Where your parser goes wrong is on the use: << should throw an error on aliases that don't point to a anchor for a mapping, as the documentation specifies mappings to be merged

Upvotes: 0

the Tin Man
the Tin Man

Reputation: 160551

When I need to build a more complex YAML document, I generally start by using Ruby and Ruby's Hash and Array objects. The YAML serializer knows how to build aliases and anchors and will do so if we let it:

require 'yaml'

foo = {'foo' => 1}
bar = {'bar' => 2, 'foo' => foo}
baz = {'baz' => 3, 'foo' => foo}

puts [foo, bar, baz].to_yaml

# >> ---
# >> - &1
# >>   foo: 1
# >> - bar: 2
# >>   foo: *1
# >> - baz: 3
# >>   foo: *1

Here it's creating an alias for the foo array, then referencing it as it serializes the array of hashes.

Using the same idea for your YAML:

require 'yaml'

PRO_ENTITLEMENTS = {
  'analytics_range' => 21600,
  'rules'           => 10,
  'cannon'          => true
}

doc = [
  {
    'PRO_PLAN' => 
    [
      {
        'description' => 'This is the Pro plan',
        'publicName'  => 'Pro Plan',
        'startDate'   => '12-20-2015'
      },
      {
        'PRO_MONTHLY_DIRECT' => 
        [
          {
            'publicName' => 'Pro Monthly',
            'price'      => 20,
            'sub_target' => 'zone'
          },
          {
            'PRICING_COMPONENTS' => PRO_ENTITLEMENTS,
            'analytics_range'    => 21600,
            'rules'              => 10,
            'cannon'             => true
          }
        ]
      },
      {
        'PRO_ANNUAL_DIRECT' => 
        [
          {
            'publicName' => 'Pro Annual',
            'price'      => 240,
            'sub_target' => 'zone'
          },
          {
            'PRICING_COMPONENTS' => PRO_ENTITLEMENTS,
          }
        ]
      }
    ]
  }
]

puts doc.to_yaml

Running it returns:

---
- PRO_PLAN:
  - description: This is the Pro plan
    publicName: Pro Plan
    startDate: 12-20-2015
  - PRO_MONTHLY_DIRECT:
    - publicName: Pro Monthly
      price: 20
      sub_target: zone
    - PRICING_COMPONENTS: &1
        analytics_range: 21600
        rules: 10
        cannon: true
      analytics_range: 21600
      rules: 10
      cannon: true
  - PRO_ANNUAL_DIRECT:
    - publicName: Pro Annual
      price: 240
      sub_target: zone
    - PRICING_COMPONENTS: *1

This isn't guaranteed to be the right output for your use, only an example of how to build a structure in Ruby and have YAML output it so you can see what it's supposed to look like after serializing.

We can run a round-trip test:

YAML.load(doc.to_yaml)
# => [{"PRO_PLAN"=>
#       [{"description"=>"This is the Pro plan",
#         "publicName"=>"Pro Plan",
#         "startDate"=>"12-20-2015"},
#        {"PRO_MONTHLY_DIRECT"=>
#          [{"publicName"=>"Pro Monthly", "price"=>20, "sub_target"=>"zone"},
#           {"PRICING_COMPONENTS"=>
#             {"analytics_range"=>21600, "rules"=>10, "cannon"=>true},
#            "analytics_range"=>21600,
#            "rules"=>10,
#            "cannon"=>true}]},
#        {"PRO_ANNUAL_DIRECT"=>
#          [{"publicName"=>"Pro Annual", "price"=>240, "sub_target"=>"zone"},
#           {"PRICING_COMPONENTS"=>
#             {"analytics_range"=>21600, "rules"=>10, "cannon"=>true}}]}]}]

Upvotes: 6

Малъ Скрылевъ
Малъ Скрылевъ

Reputation: 16507

It seems correct syntax is to add an indent to sub Hash for PRICING_COMPONENTS:

---
- PRO_PLAN:
  - description: This is the Pro plan
    publicName: Pro Plan
    startDate: 12-20-2015

  - PRO_MONTHLY_DIRECT:
    - publicName: Pro Monthly 
      price: 20
      sub_target: zone

    - PRICING_COMPONENTS: &pro_entitlements
        analytics_range: 21600
        rules: 10
        cannon: true

  - PRO_ANNUAL_DIRECT:
    - publicName: Pro Annual  
      price: 240
      sub_target: zone

    - PRICING_COMPONENTS:
        <<: *pro_entitlements

Or as noted the previous man, add the Array dash instead of just Hash indent.

Upvotes: 1

Arsen
Arsen

Reputation: 10951

I'm not an YAML master but It looks like you use wrong syntax. Try this one:

-
  PRO_PLAN:
    -
      description: "This is the Pro plan"
      publicName: "Pro Plan"
      startDate: 12-20-2015
    -
      PRO_MONTHLY_DIRECT:
        -
          price: 20
          publicName: "Pro Monthly"
          sub_target: zone
        -
          PRICING_COMPONENTS: &pro_entitlements
            -
              analytics_range: 21600
              cannon: true
              rules: 10
              works: true
    -
      PRO_ANNUAL_DIRECT:
        -
          price: 240
          publicName: "Pro Annual"
          sub_target: zone
        -
          PRICING_COMPONENTS:
            -
              <<: *pro_entitlements

Upvotes: 0

Related Questions