0

I am creating an array of fields

def create_fields fields
  fields_list = []

  fields.each do |field|
    # puts "adding_field to array: #{field}"
    field_def = { field: field, data: { type: 'Text', description: '' }  }
    fields_list.push field_def
  end
  fields_list
end

The fields_list is being set to a jsonb field.

Lets say I pass in

create_fields ['Ford', 'BMW', 'Fiat']

Json result is an array:

{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}

How can I access the 'Ford' from the json array? Am i creating the array incorrectly? Is there a better way to create this array so I can access the field i want?

This assertion passes assert_equal(3, fields.count)

However i want to get 'Ford' and check it's properties, e.g. type = 'Text', type could equal 'Number' or whatever.

4
  • 1
    could you clarify what you really want to do? Honda is not included in any arrays here, so I'm wondering how you'd want to access it when it does not exist? Commented Mar 3, 2017 at 22:35
  • are you sure that your array include hash with 'field': 'Honda'? Commented Mar 3, 2017 at 22:35
  • @oreoluwa Sorry my bad, I meant Ford. Commented Mar 3, 2017 at 22:38
  • What you are calling a JSON result isn't valid JSON, nor could it be converted to JSON because it's not a valid Ruby array either. Commented Mar 3, 2017 at 22:46

4 Answers 4

5

The result of your create_fields method with the specified parameters is the following:

[
  {:field=>"Ford", :data=>{:type=>"Text", :description=>""}}, 
  {:field=>"BMW", :data=>{:type=>"Text", :description=>""}},
  {:field=>"Fiat", :data=>{:type=>"Text", :description=>""}}
] 

It means that if you want to access the line belonging to "Ford", you need to search for it like:

2.3.1 :019 > arr.select{|e| e[:field] == "Ford" }
 => [{:field=>"Ford", :data=>{:type=>"Text", :description=>""}}] 
2.3.1 :020 > arr.select{|e| e[:field] == "Ford" }[0][:data][:type]
 => "Text" 

This is not optimal, because you need to search an array O(n) instead of using the pros of a hash. If there are e.g.: 2 "Ford" lines, you'll get an array which contains 2 elements, harder to handle collisions in field value.

It would be better if you created the array like:

def create_fields fields
  fields_list = []

  fields.each do |field|
    # puts "adding_field to array: #{field}"
    field_def = [field, { type: 'Text', description: '' }  ]
    fields_list.push field_def
  end
  Hash[fields_list]
end

If you choose this version, you can access the members like:

2.3.1 :072 > arr = create_fields ['Ford', 'BMW', 'Fiat']
 => {"Ford"=>{:type=>"Text", :description=>""}, "BMW"=>{:type=>"Text", :description=>""}, "Fiat"=>{:type=>"Text", :description=>""}} 
2.3.1 :073 > arr["Ford"]
 => {:type=>"Text", :description=>""} 
2.3.1 :074 > arr["Ford"][:type]
 => "Text" 

Both of the above examples are Ruby dictionaries / Hashes. If you want to create a JSON from this, you will need to convert it:

2.3.1 :077 > require 'json'
 => true 
2.3.1 :078 > arr.to_json
 => "{\"Ford\":{\"type\":\"Text\",\"description\":\"\"},\"BMW\":{\"type\":\"Text\",\"description\":\"\"},\"Fiat\":{\"type\":\"Text\",\"description\":\"\"}}" 
Sign up to request clarification or add additional context in comments.

6 Comments

One thing that can help simplify the create_fields method is doing your mapping like: Hash[ fields.map { [ field, { ... } ] } ] though spread out on more than one for readibility. You've got the right idea there, but creating an intermediate variable and pushing to a temporary array is actually more work than necessary.
Yepp, you are right. There are shorter and more efficient ways to do that conversion. I can just use a {} from the beginning and just set its elements. This required minimal modification though.
You also can use detect method instead select :)
@AlexGolubenko Yes, but that doesn't clarify why this data structure is a wrong approach :P
@akg what would be the best data structure for this if you don't mind me asking?
|
3

This is a structure that makes more sense to me for accessing values based on known keys:

def create_fields fields
  fields_hash = {}

  fields.each do |field|
    fields_hash[field] = {type: 'Text', description: ''}
  end
  fields_hash
end

# The hash for fields_hash will look something like this:
{
  Ford: {
    type: "Text",
    description: ""
  },
  BMW: {...},
  Fiat: {...}
}

This will allow you to access the values like so: fields[:Ford][:type] in ruby and fields.Ford.type in JSON. Sounds like it would be easier to return an Object rather than an Array. You can access the values based on the keys more easily this way, and still have the option of looping through the object if you want.

Comments

1

Obviously, there are several ways of creating or accessing your data, but I'd always lean towards the developer picking a data structure best suited for your application. In your case currently, in order to access the Ford hash, you could use the Ruby Array#detect method as such:

ford = fields_list.detect{|field_hash| field_hash['field'] == 'Ford' }
ford['data'] # => {"type"=>"Text", "description"=>""}
ford['data']['type'] # => 'Text'

Comments

1

So, you have result of your method:

result = 
  [
   {"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}},
   {"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}},
   {"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
  ]

to get 'Ford' from it you can use simple method detect

result.detect { |obj| obj['field'] == 'Ford' }
#=> { "field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}

Also I recommend you to edit your method to make it more readable:

def create_fields(fields)
  fields.map do |field|
    {
      field: field,
      data: {
        type: 'Text',
        description: ''
      }
    }
  end
end

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.