f



extract into a method - struggling to abstract

Hi

I have working code that I have currently loops through an example returning matches via nokogiri module.

This works

require 'nokogiri'
require 'awesome_print'

@doc = Nokogiri::XML(File.open('/home/sayth/data/20160702RHIL0.xml'))

@doc.search('race').map do |race|
  nominations = race.search('nomination')
                    .map do |nomination|
    {
      number: nomination['number'].to_i,
      id: nomination['id'].to_i,
      horse: nomination['horse'].to_s,
      age: nomination['age'].to_i,
      sex: nomination['sex'].to_s,
      colour: /\W(.+?)\d?\s/.match(nomination['description'].to_s)[1]
    }
  end

  a = { race['id'].to_i => nominations }
  ap a
end

_______________________________________________________________________________

So at this point I know that I need to replicate this for 6 sections in total, logically to me then I want to abstract especially the middle.

    {
      number: nomination['number'].to_i,
      id: nomination['id'].to_i,
      horse: nomination['horse'].to_s,
      age: nomination['age'].to_i,
      sex: nomination['sex'].to_s,
      colour: /\W(.+?)\d?\s/.match(nomination['description'].to_s)[1]
    }

and substitute a variable into race.search('nomination') this to replace the word nomination and to then replace all 'nomination' in the above section.

So this is what I have done.

data = %w(number id horse age sex colour)

def extract_value(path_id, data)
  @doc.search('race').map do |race|
    puts path_id
    nominations = race.search(path_id)
                      .map do | |
      data
    end
    values = { race['id'].to_i => nominations }
    ap values
    return values
  end
end

extract_value('nomination', data)

_______________________________________________________________________________

The main part I cannot wrap my head around is how to get these sections in without replicating it in the definition.

number: nomination['number'].to_i,

How can I better abstract this out?

Cheers

Sayth

0
Sayth
7/3/2016 2:01:31 PM
comp.lang.ruby 48886 articles. 0 followers. Post Follow

4 Replies
449 Views

Similar Articles

[PageSpeed] 32

On 03.07.2016 16:01, Sayth Renshaw wrote:
> Hi
>
> I have working code that I have currently loops through an example returning matches via nokogiri module.
>
> This works
>
> require 'nokogiri'
> require 'awesome_print'
>
> @doc = Nokogiri::XML(File.open('/home/sayth/data/20160702RHIL0.xml'))

You are not closing the file here.  Better:

@doc = File.open('/home/sayth/data/20160702RHIL0.xml', 'rb') do |io|
   Nokogiri::XML(io)
end

> @doc.search('race').map do |race|
>    nominations = race.search('nomination')
>                      .map do |nomination|
>      {
>        number: nomination['number'].to_i,
>        id: nomination['id'].to_i,
>        horse: nomination['horse'].to_s,
>        age: nomination['age'].to_i,
>        sex: nomination['sex'].to_s,
>        colour: /\W(.+?)\d?\s/.match(nomination['description'].to_s)[1]
>      }
>    end
>
>    a = { race['id'].to_i => nominations }
>    ap a
> end
>
> _______________________________________________________________________________
>
> So at this point I know that I need to replicate this for 6 sections in total, logically to me then I want to abstract especially the middle.
>
>      {
>        number: nomination['number'].to_i,
>        id: nomination['id'].to_i,
>        horse: nomination['horse'].to_s,
>        age: nomination['age'].to_i,
>        sex: nomination['sex'].to_s,
>        colour: /\W(.+?)\d?\s/.match(nomination['description'].to_s)[1]
>      }
>
> and substitute a variable into race.search('nomination') this to replace the word nomination and to then replace all 'nomination' in the above section.

I would replace the variable name "nomination" by something more 
abstract, e.g. "node".irb

> So this is what I have done.
>
> data = %w(number id horse age sex colour)
>
> def extract_value(path_id, data)
>    @doc.search('race').map do |race|
>      puts path_id
>      nominations = race.search(path_id)
>                        .map do | |
>        data
>      end
>      values = { race['id'].to_i => nominations }
>      ap values
>      return values
>    end
> end
>
> extract_value('nomination', data)

I think you are almost there:

data = %w(number id horse age sex colour)

def extract_value(path_id, data)
   @doc.search('race').map do |race|
     puts path_id

     extracted_data =
       race.search(path_id).map do |node|
         {}.tap do |hash|
           data.each {|d| hash[d.to_sym] = Integer(node[d])}
         end
       end

     values = { race['id'].to_i => extracted_data }
     ap values

     # next line will prematurely return from the method,
     # do you want that?
     return values
   end
end

extract_value('nomination', data)

> _______________________________________________________________________________
>
> The main part I cannot wrap my head around is how to get these sections in without replicating it in the definition.
>
> number: nomination['number'].to_i,
>
> How can I better abstract this out?

See above.  Is this what you intended?

Kind regards

	robert


-- 
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
0
Robert
7/3/2016 4:52:29 PM
This is close.


data = %w(number id horse age sex colour)

def extract_value(path_id, data)
   @doc.search('race').map do |race|
     puts path_id

     extracted_data =
       race.search(path_id).map do |node|
         {}.tap do |hash|
           data.each {|d| hash[d.to_sym] = Integer(node[d])}
         end
       end

     values = { race['id'].to_i => extracted_data }
     ap values

     # next line will prematurely return from the method,
     # do you want that?
     return values
   end
end

extract_value('nomination', data
_______________________________________________________________________________

However not all values are integers, some are strings.

Can you make templates that you can drop into the function?

I started down the path of trying to build a factory to generate structures based on input type and while it works I think creating a template like I would in web would make sense.

Creating template
____________________________________________________________________
def data_form(_id, path = 'nomination')
  terms = %w(one two three)
  noms = terms.collect { |x| path.to_s + "[#{x}].to_i" }
  puts noms
end
____________________________________________________________________

If I just defined the templates as:

structure = {
      number: nomination['number'].to_i,
      id: nomination['id'].to_i,
      horse: nomination['horse'].to_s,
      age: nomination['age'].to_i,
      sex: nomination['sex'].to_s,
      colour: /\W(.+?)\d?\s/.match(nomination['description'].to_s)[1]
    }

Then the method could be really simplified to just take in the node and the template to use.

Just Like

def extract_value(_node, data)
  term = 'node' + 's'
  @doc.search('race').map do |race|
    term = race.search('node')
               .map do |_node|
      data
    end
  end
  a = { race['id'].to_i => term }
  ap a
end

extract_value('nomination', structure)

Sayth

PS I learnt tap from your previous post I had thought it was a typo of map but no http://ruby-doc.org/core-2.3.1/Object.html#method-i-tap :-)
0
Sayth
7/4/2016 11:27:33 AM
for example template using json

noms.json

{
    "number": "nomination['number'].to_i",
    "id": "nomination['id'].to_i",
    "horse": "nomination['horse'].to_s",
    "age": "nomination['age'].to_i",
    "sex": "nomination['sex'].to_s",
    "colour": "/\W(.+?)\d?\s/.match(nomination['description'].to_s)[1]"
}

json.rb

require 'json'

file = File.read('noms.json')
data_hash = JSON.parse(file)
ary = 0..data_hash.length
ary.each do |x|
  out = "#{data_hash.keys[x]}: #{data_hash.values[x]}"
  puts out
end

_____________________________________________________________
out

number: nomination['number'].to_i
id: nomination['id'].to_i
horse: nomination['horse'].to_s
age: nomination['age'].to_i
sex: nomination['sex'].to_s
colour: /W(.+?)d?s/.match(nomination['description'].to_s)[1]

Is this a good idea to go down?
0
Sayth
7/4/2016 12:58:46 PM
On 04.07.2016 13:27, Sayth Renshaw wrote:
> This is close.

> However not all values are integers, some are strings.
>
> Can you make templates that you can drop into the function?

Just a rough idea


data = {
   'number' => :to_i,
   'id' => :to_i,
   'horse' => nil,
   'age' => :to_i,
   'sex' => :to_s,
   'colour' => lambda {|x| "Color #{x}"},
}

def extract_value(path_id, data)
    @doc.search('race').map do |race|
      puts path_id

      extracted_data =
        race.search(path_id).map do |node|
          {}.tap do |hash|
            data.each {|d, m|
              val = node[d]
              hash[d.to_sym] = m ? m.to_proc[val] : val)
            }
          end
        end

      values = { race['id'].to_i => extracted_data }
      ap values

      # next line will prematurely return from the method,
      # do you want that?
      return values
    end
end

There is room for optimization, i.e. avoiding repeated to_proc calls.

> PS I learnt tap from your previous post I had thought it was a typo of map but no http://ruby-doc.org/core-2.3.1/Object.html#method-i-tap :-)

:-)

Kind regards

	robert


-- 
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
0
Robert
7/5/2016 9:21:55 PM
Reply: