ruby-on-railspostgresqlfixturesminitest

Cannot use Array of Hashes in RoR fixtures


I use Ruby On Rails together with a PostgreSQL database. For performance reasons I use a jsonb-field with an array of hashes and not extracting this into a specific table. In the real world this blob looks like

[ { "row": 1, "price": 0, "column": 1, "status": 1, "index_row": 1, "contingency": 0, "index_column": 1 }, { "row": 1, "price": 0, "column": 2, "status": 1, "index_row": 1, "contingency": 0, "index_column": 2 }... ]

and works fine.

Now I tried to increase the test coverage and noticed, that I cannot create an array of hashes using the standard fixture approach. Suppose the following:

CREATE TABLE IF NOT EXISTS public.circles
(
    id bigint NOT NULL DEFAULT nextval('circles_id_seq'::regclass),
    blob jsonb
)

and a fixture file circles.yml with the following content:

templatecircle_1:
  blob: <%= [1, 2] %>

This works as expected when running bin/rails test.

templatecircle_1:
  blob: {"abc": "def"}

This works as expected, too.

However,

templatecircle_1:
  blob: <%= [1, {"abc": "def"}] %>

yields into an ActiveRecord::Fixture::FormatError:

Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Error: (): did not find expected node content while parsing a flow node at line 2 column 11

I even tried to use a workaround as following:

templatecircle_1:
  blob: <%= JSON.parse("[1, {\"abc\": \"def\"}]") %>

which gives the same error.

Does anyone has an idea, how I can create an array of hashes in RoR fixtures?


Solution

  • To define "array of hashes" field in the YAML fixtures file, use - notation for every array element and : for every hash key/value pair. Don't need ERB syntax or some JSON parsing. Just such structure:

    # test/fixtures/circles.yml
    
    templatecircle_1:
      blob:
        - row: 1
          price: 0
          column: 1
          status: 1
          index_row: 1
          contingency: 0
          index_column: 1
        - row: 1
          price: 0
          column: 2
          status: 1
          index_row: 1
          contingency: 0
          index_column: 2
    

    After that you can call inside your test

    Circle.first.blob
    # => [{
    #      "row"=>1,
    #      "price"=>0,
    #      "column"=>1,
    #      "status"=>1,
    #      "index_row"=>1,
    #      "contingency"=>0,
    #      "index_column"=>1
    #    },
    #      "row"=>1,
    #      "price"=>0,
    #      "column"=>2,
    #      "status"=>1,
    #      "index_row"=>1,
    #      "contingency"=>0,
    #      "index_column"=>2
    #    }]
    

    As you see it will be parsed automatically


    So for [1, {"abc": "def"}] the structure will be:

    # test/fixtures/circles.yml
    
    templatecircle_1:
      blob:
        - 1
        - abc: def
    

    And in Ruby:

    Circle.first.blob
    # => [1, {"abc"=>"def"}]
    

    It's also possible to use such syntax (just remove ERB from your original attempt):

    # test/fixtures/circles.yml
    
    templatecircle_1:
      blob: [1, {"abc": "def"}]
    

    And in Ruby:

    Circle.first.blob
    # => [1, {"abc"=>"def"}]
    

    If you still need ERB by some reasons, you can use to_json method

    # test/fixtures/circles.yml
    
    templatecircle_1:
      blob: <%= [1, { "abc": "def" }].to_json %>
    

    And in Ruby:

    Circle.first.blob
    # => [1, {"abc"=>"def"}]