Search code examples
ruby-on-railsjsonrubyevaljsonb

In Ruby on Rails is there a better way than using eval() to accses this jsonb dynamically?


I've got some jsonb in a database and I want to write to an attribute dynamically, I know the path to the attribute but it could be any depth and any attribute name.

I know the path to the attribute, however the only way I've found of writing this dynamiaclly is with eval().

eval("self.some_json_column['an_array'][10]['a_different_array'][5]['color'] = 'blue'")
self.save

So I know the depth and the array index, but it could be any depth or index. I can build the string and pass it into eval()

However I know that eval() is a last case scenario and was wonderign if it's possible to build the path and write (in this case that color json attribute) dynamically without using eval()

Thanks.


Solution

  • You can reduce your way through the Hash that is returned by self.some_json_column

    *path, target = ['an_array',10,'a_different_array',5,'color']
    node = path.reduce(self.some_json_colum, &:[])
    node[target] = 'blue' if node  
    

    This has no error handling if any part of the path is incorrect

    You could also look into dig depending on ruby version like so

    node = self.some_json_column.dig(*path)
    node[target] = 'blue' if node
    

    This will return nil if any part of the path does not match

    Example:

    h = {
      'an_array' => [0,1,2,3,4,5,6,7,8,9,
        {'a_different_array' => [1,2,3,4,5, {'color' => 'orange'}]}
      ]
    }
    
    *path,target  = ['an_array',10,'a_different_array',5,'color']
    
    h.dig(*path)[target] = 'blue'
    
    h
    #=> {"an_array"=>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
    #     {"a_different_array"=>[0, 1, 2, 3, 4, 
    #       {"color"=>"blue"}
    #     ]}
    #   ]}