1

My app passes to different methods a json_element for which the keys are different, and sometimes empty.

To handle it, I have been hard-coding the extraction with the following sample code:

def act_on_ruby_tag(json_element)

begin

  # logger.progname        = __method__
  logger.debug             json_element

  code                   = json_element['CODE']['$'] unless json_element['CODE'].nil?
  predicate              = json_element['PREDICATE']['$'] unless json_element['PREDICATE'].nil?
  replace                = json_element['REPLACE-KEY']['$'] unless json_element['REPLACE-KEY'].nil?
  hash                   = json_element['HASH']['$'] unless json_element['HASH'].nil?

I would like to eliminate hardcoding the values, and not quite sure how.

I started to think through it as follows:

keys = json_element.keys
keys.each do |k|
        set_key = k.downcase
        instance_variable_set("@" + set_key, json_element[k]['$']) unless json_element[k].nil?
      end 

And then use @code for example in the rest of the method.

I was going to try to turn into a method and then replace all this hardcoded code.

But I wasn't entirely sure if this is a good path.

0

2 Answers 2

3

It's almost always better to return a hash structure from a method where you have things like { code: ... } rather than setting arbitrary instance variables. If you return them in a consistent container, it's easier for callers to deal with delivering that to the right location, storing it for later, or picking out what they want and discarding the rest.

It's also a good idea to try and break up one big, clunky step with a series of smaller, lighter operations. This makes the code a lot easier to follow:

def extract(json)
  json.reject do |k, v|
    v.nil?
  end.map do |k, v|
    [ k.downcase, v['$'] ]
  end.to_h
end

Then you get this:

extract(
  'TEST' => { '$' => 'value' },
  'CODE' => { '$' => 'code' },
  'NULL' => nil
)
# => {"test"=>"value", "code"=>"code"}

If you want to persist this whole thing as an instance variable, that's a fairly typical pattern, but it will have a predictable name that's not at the mercy of whatever arbitrary JSON document you're consuming.

An alternative is to hard-code the keys in a constant like:

KEYS = %w[ CODE PREDICATE ... ]

Then use that instead, or one step further, define that in a YAML or JSON file you can read-in for configuration purposes. It really depends on how often these will change, and what sort of expectations you have about the irregularity of the input.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks...I realized that the "$" is actually an artfact of using CobravsMongoose gem.BUT...the real problem is what you are discussing....to pull out the keys and to check if there is a value or not without erroring out. Sometimes one of the keys has no value -- it is empty. That's why I have the nil? check.
Empty and nil? are two different things, just keep that in mind. If you're just looking for things you can navigate into, a test vs. v is sufficient. Minimal code is usually easier to debug.
0

This is a slightly more terse way to do what your original code does.

code, predicate, replace, hash = json_element.values_at *%w{
  CODE PREDICATE REPLACE-KEY HASH
}.map { |x| x.fetch("$", nil) if x }

3 Comments

in these cases, I would need to know aprori that the keys are `code, predicate, replace...etc"? Those change each time.....thinking whether it helps to know ahead of time...it might....
How can I parse this out -- I may be able to create this without the $ artifact.
This is a way to extract 4 particular keys into top level variables, without using metaprogramming. As often in ruby there are many ways to do the same thing

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.