qticker 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 494c931926f1f8c3f6192e66db4a075a35a56198
4
- data.tar.gz: b2f124d4e050d554f5681108b28e0f62ce82424f
3
+ metadata.gz: 0745f12aa2f16c3d7ea823422960cfe8ba8890dd
4
+ data.tar.gz: 8c708da85dab21449ba719530a3b1588ff9cf60a
5
5
  SHA512:
6
- metadata.gz: 67182ffe4f96c7427ac6822a603e2884786859dcd8c27074d35d2a2b0acbe13a78647cce4d052a48f1ad8458a795aae55dc06ab864ce336601b01831c783ba93
7
- data.tar.gz: 2e0286a1c602d99806552cad09ead288e5d4387fc9f0c1722b4d94de2f2f677e514fea9532d4a3cf84425344f0679b553603071f9b0edffafc9cbfd2e9d930aa
6
+ metadata.gz: 7ac5910a40bd4729b4348a5b1fcb01f73265eaddf64720a61fe9c5377d1d251eb0183fc72493f2f58e7c82f2744a5904c2d6f3d02694804aba8a6454ecc4b56d
7
+ data.tar.gz: a4693099fd176c612cb0a67185ea8806cc251fd0472595db88d967d10c44fa6f1f873f66bf0daa3a80ace63b9cdd8ad393302dfcbe472cc804c63ccde04933af
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../config/environment.rb'
3
+ require_relative '../lib/qticker.rb'
4
4
 
5
- cli = MainCli.new
6
-
7
- cli.welcome("Quick Ticker", -> {cli.ticker_symbol_prompt})
5
+ cli = QuickTicker::MainCli.new
6
+ cli.welcome("Quick Ticker", -> {cli.ticker_symbol_prompt})
data/lib/cli.rb CHANGED
@@ -1,51 +1,108 @@
1
- class Cli
1
+ module QuickTicker
2
2
 
3
- attr_accessor :stock, :scraper
3
+ class Cli
4
4
 
5
- def initialize(cli = nil)
6
- self.scraper = Scraper.new(self)
7
- end
5
+ attr_accessor :stock, :scraper, :last_option_lambda, :exit_message
8
6
 
9
- def welcome(mode_name, mode_lambda)
10
- print "\nWelcome to " + mode_name + "!\n"
11
- mode_lambda.()
12
- end
7
+ def initialize(cli = nil)
8
+ self.scraper = QuickTicker::Scraper.new(self)
9
+ end
13
10
 
14
- def display_quote
15
- self.stock.display
16
- self.stock.quote.display
17
- self.stock_option_menu("Display a company description", -> { self.display_desc })
18
- end
11
+ def welcome(mode_name, mode_lambda)
12
+ print "\nWelcome to " + mode_name + "!\n"
13
+ mode_lambda.()
14
+ end
19
15
 
20
- def display_desc
21
- self.stock.display
22
- self.stock.desc.display
23
- self.stock_option_menu("Redisplay your quote", -> { self.display_quote })
24
- end
16
+ def display_stock_header
17
+ print "\n#{self.stock.name} (#{self.stock.exchange}:#{self.stock.symbol})\n\n"
18
+ end
25
19
 
26
- def stock_option_menu(opt_1_string, opt_1_lambda = nil)
27
- gets
28
- puts "1. #{opt_1_string} for #{self.stock.symbol}."
29
- puts "2. Enter another ticker symbol."
30
- puts "Enter any other key to exit."
31
- gets.strip.gsub('.', '')
32
- end
20
+ def display_stock_quote
21
+ puts "Current: #{self.stock.quote.price} #{stock.quote.change}(#{stock.quote.change_pct}%)"
22
+ puts "Open: #{self.stock.quote.open}"
23
+ puts "Volume: #{self.stock.quote.volume}"
24
+ puts "Avg Vol: #{self.stock.quote.volume_avg}"
25
+ puts "Mkt Cap: #{self.stock.quote.mkt_cap}"
26
+ puts "P/E(ttm): #{self.stock.quote.pe_ttm}"
27
+ puts "Yield: #{self.stock.quote.div_yld}%"
28
+ end
33
29
 
34
- def symbol_validation(symbol, valid, fixture_url = nil)
35
- # stock_array[0] is a stock if one was succesfully created and nil otherwise.
36
- # stock_array[1] indicates whether the symbol cooresponds to a mutual fund.
37
- stock_array = self.scraper.load_gfs(symbol, fixture_url)
38
- self.stock = stock_array[0]
39
- valid = self.stock.nil? ? false : true
40
- puts "Invalid ticker symbol." if !valid
41
- puts "Mutual funds are not currently not supported." if stock_array[1]
42
- self.display_quote if valid
43
- # returns an array.
44
- # array[0] - whether the entered symbol was valid
45
- # array[1] = stock_array[1] - whether the entered
46
- # symbol was a mutal fund.
47
- [valid, stock_array[1]]
48
- end
30
+ def display_stock_description
31
+ print "#{self.stock.description.sector} : #{self.stock.description.industry}\n\n"
32
+ puts "#{self.stock.description.summary}".fit
33
+ end
34
+
35
+ def display_stock_related_companies
36
+ output = "Symbol Price Mkt Cap\n"
37
+ self.stock.related_companies.each do |stock_related_company|
38
+ output += (stock_related_company.symbol + ' ' * 8)[0,8]
39
+ output += (stock_related_company.price + ' ' * 8)[0,8]
40
+ output += ("#{stock_related_company.change}(#{stock_related_company.change_pct}%)" + ' ' * 16)[0,16]
41
+ output += stock_related_company.mkt_cap + "\n"
42
+ end
43
+ puts output
44
+ end
45
+
46
+ def fetch_stock_quote
47
+ self.display_stock_header
48
+ self.display_stock_quote
49
+ self.call_stock_option_menu({ option_1_string: "Display a company description", option_2_string: "Display related companies", option_1_lambda: -> { self.fetch_stock_description }, option_2_lambda: -> { self.fetch_stock_related_companies }, last_option_lambda: -> {self.last_option_lambda.()} })
50
+ end
49
51
 
52
+ def fetch_stock_description
53
+ self.display_stock_header
54
+ self.display_stock_description
55
+ self.call_stock_option_menu({ option_1_string: "Display a quote", option_2_string: "Display related companies", option_1_lambda: -> { self.fetch_stock_quote }, option_2_lambda: -> { self.fetch_stock_related_companies }, last_option_lambda: -> {self.last_option_lambda.()} })
56
+ end
57
+
58
+ def fetch_stock_related_companies
59
+ self.display_stock_header
60
+ self.display_stock_related_companies
61
+ self.call_stock_option_menu({ option_1_string: "Display a quote", option_2_string: "Display a company description", option_1_lambda: -> { self.fetch_stock_quote }, option_2_lambda: -> { self.fetch_stock_description }, last_option_lambda: -> {self.last_option_lambda.()} })
62
+ end
63
+
64
+ def call_stock_option_menu(option_hash)
65
+ process_stock_option_menu_input(display_stock_option_menu(option_hash[:option_1_string], option_hash[:option_2_string]), option_hash[:option_1_lambda], option_hash[:option_2_lambda], option_hash[:last_option_lambda])
66
+ end
67
+
68
+ def display_stock_option_menu(option_1_string, option_2_string)
69
+ gets
70
+ puts "1. #{option_1_string} for #{self.stock.symbol}."
71
+ puts "2. #{option_2_string} for #{self.stock.symbol}."
72
+ puts "3. Enter another ticker symbol."
73
+ puts "Enter any other key to exit."
74
+ gets.strip.gsub('.', '')
75
+ end
76
+
77
+ def process_stock_option_menu_input(input, option_1_lambda, option_2_lambda, option_3_lambda)
78
+ if input == "1"
79
+ option_1_lambda.()
80
+ elsif input == "2"
81
+ option_2_lambda.()
82
+ elsif input == "3"
83
+ option_3_lambda.()
84
+ else
85
+ puts self.exit_message
86
+ return nil
87
+ end
88
+ end
89
+
90
+ def symbol_validation(symbol, fixture_url = nil)
91
+ # stock_array[0] is a stock if one was succesfully created and nil otherwise.
92
+ # stock_array[1] indicates whether the symbol cooresponds to a mutual fund.
93
+ stock_array = self.scraper.load_gfs(symbol, fixture_url)
94
+ self.stock = stock_array[0]
95
+ valid = self.stock.nil? ? false : true
96
+ puts "Invalid ticker symbol." if !valid
97
+ puts "Mutual funds are not currently not supported." if stock_array[1]
98
+ self.fetch_stock_quote if valid
99
+ # returns an array.
100
+ # array[0] - whether the entered symbol was valid
101
+ # array[1] = stock_array[1] - whether the entered
102
+ # symbol was a mutal fund.
103
+ [valid, stock_array[1]]
104
+ end
105
+
106
+ end
50
107
 
51
108
  end
@@ -1,58 +1,54 @@
1
- class DevCli < Cli
1
+ module QuickTicker
2
2
 
3
- attr_accessor :main
3
+ class DevCli < QuickTicker::Cli
4
4
 
5
- def initialize(main_cli)
6
- super(main_cli)
7
- self.main = main_cli
8
- end
5
+ attr_accessor :main
9
6
 
10
- def option_menu
11
- puts "\nPlease select a fixture to load:"
12
- puts "1. Load MSFT.html"
13
- puts "2. Load IBM.html"
14
- puts "3. Load QQQ.html"
15
- puts "4. Load FBIOX.html"
16
- puts "Or enter any other key to return to"
17
- puts "your regularly scheduled program."
18
- input = gets.strip.gsub('.', '')
19
- path = File.expand_path(File.dirname(__FILE__)).chomp('bin') + '/fixtures/'
20
- if input == "1"
21
- valid = self.symbol_validation("MSFT", false, path + "MSFT.html")
22
- elsif input == "2"
23
- valid = self.symbol_validation("IBM", false, path + "IBM.html")
24
- elsif input == "3"
25
- valid = self.symbol_validation("QQQ", false, path + "QQQ.html")
26
- elsif input == "4"
27
- valid = self.symbol_validation("FBIOX", false, path + "FBIOX.html")
28
- else
29
- puts "Leaving Developer Mode and resuming program."
7
+ def initialize(main_cli)
8
+ super(main_cli)
9
+ self.main = main_cli
10
+ self.last_option_lambda = -> { self.call_dev_option_menu }
11
+ self.exit_message = "Leaving Developer Mode and resuming program."
30
12
  end
31
- return nil
32
- end
33
13
 
34
- def stock_option_menu(opt_1_string, opt_1_lambda)
35
- input = super(opt_1_string, opt_1_lambda)
36
- if input == "1"
37
- opt_1_lambda.()
38
- elsif input == "2"
39
- self.option_menu
40
- else
41
- puts "Leaving Developer Mode and resuming program."
42
- return nil
14
+ def call_dev_option_menu
15
+ process_dev_option_menu_input(display_dev_option_menu)
43
16
  end
44
- end
45
17
 
46
- def symbol_validation(symbol, valid, fixture_url = nil)
47
- valid_array = super(symbol, valid, fixture_url)
48
- if valid_array[1] # whether entered symbol was a mutual fund
49
- puts ""
50
- option_menu
18
+ def display_dev_option_menu
19
+ puts "\nPlease select a fixture to load:"
20
+ puts "1. Load MSFT.html"
21
+ puts "2. Load IBM.html"
22
+ puts "3. Load QQQ.html"
23
+ puts "4. Load FBIOX.html"
24
+ puts "Or enter any other key to return to"
25
+ puts "your regularly scheduled program."
26
+ gets.strip.gsub('.', '')
51
27
  end
52
- valid_array[0] # returns whether entered symbol was valid
53
- end
54
28
 
29
+ def process_dev_option_menu_input(input)
30
+ if input == "1"
31
+ valid = self.symbol_validation("MSFT", "http://lair001.github.io/fixtures/qticker/MSFT.html")
32
+ elsif input == "2"
33
+ valid = self.symbol_validation("IBM", "http://lair001.github.io/fixtures/qticker/IBM.html")
34
+ elsif input == "3"
35
+ valid = self.symbol_validation("QQQ", "http://lair001.github.io/fixtures/qticker/QQQ.html")
36
+ elsif input == "4"
37
+ valid = self.symbol_validation("FBIOX", "http://lair001.github.io/fixtures/qticker/FBIOX.html")
38
+ else
39
+ puts self.exit_message
40
+ end
41
+ return nil
42
+ end
55
43
 
44
+ def symbol_validation(symbol, fixture_url = nil)
45
+ valid_array = super(symbol, fixture_url)
46
+ if valid_array[1] # whether entered symbol was a mutual fund
47
+ call_dev_option_menu
48
+ end
49
+ valid_array[0] # returns whether entered symbol was valid
50
+ end
56
51
 
52
+ end
57
53
 
58
54
  end
@@ -1,39 +1,34 @@
1
- class MainCli < Cli
1
+ module QuickTicker
2
2
 
3
- attr_accessor :scraper, :dev
3
+ class MainCli < QuickTicker::Cli
4
4
 
5
- def initialize(cli = nil)
6
- super(cli)
7
- self.dev = DevCli.new(self)
8
- end
5
+ attr_accessor :dev
6
+
7
+ def initialize(cli = nil)
8
+ super(cli)
9
+ self.dev = QuickTicker::DevCli.new(self)
10
+ self.last_option_lambda = -> { self.ticker_symbol_prompt }
11
+ self.exit_message = "Thank you for using Quick Ticker!"
12
+ end
9
13
 
10
14
 
11
- def ticker_symbol_prompt
12
- valid = false
13
- while !valid do
14
- print "\nPlease enter a ticker symbol: "
15
- symbol = gets.strip.upcase
16
- if symbol == "DEV"
17
- self.welcome("Developer Mode", -> {self.dev.option_menu}) if symbol == "DEV"
18
- else
19
- valid = self.symbol_validation(symbol, valid)
15
+ def ticker_symbol_prompt
16
+ valid = false
17
+ while !valid do
18
+ print "\nPlease enter a ticker symbol: "
19
+ symbol = gets.strip.upcase
20
+ if symbol == "DEV"
21
+ self.dev.welcome("Developer Mode", -> {self.dev.call_dev_option_menu}) if symbol == "DEV"
22
+ else
23
+ valid = self.symbol_validation(symbol)
24
+ end
20
25
  end
21
26
  end
22
- end
23
27
 
24
- def stock_option_menu(opt_1_string, opt_1_lambda)
25
- input = super(opt_1_string, opt_1_lambda)
26
- if input == "1"
27
- opt_1_lambda.()
28
- elsif input == "2"
29
- self.ticker_symbol_prompt
30
- else
31
- return nil
28
+ def symbol_validation(symbol, fixture_url = nil)
29
+ super(symbol)[0] # returns whether entered symbol was valid
32
30
  end
33
- end
34
31
 
35
- def symbol_validation(symbol, valid, fixture_url = nil)
36
- super(symbol, valid)[0] # returns whether entered symbol was valid
37
32
  end
38
33
 
39
- end
34
+ end
@@ -1 +1,16 @@
1
- require_relative '../config/environment.rb'
1
+ require 'nokogiri'
2
+ require 'word_wrap'
3
+ require 'word_wrap/core_ext'
4
+ require 'watir'
5
+ require 'phantomjs'
6
+
7
+ require_relative '../lib/cli.rb'
8
+ require_relative '../lib/table.rb'
9
+ require_relative '../lib/stock_attribute.rb'
10
+ require_relative '../lib/stock_description.rb'
11
+ require_relative '../lib/dev_cli.rb'
12
+ require_relative '../lib/main_cli.rb'
13
+ require_relative '../lib/stock_quote.rb'
14
+ require_relative '../lib/stock_related_company.rb'
15
+ require_relative '../lib/scraper.rb'
16
+ require_relative '../lib/stock.rb'
@@ -1,82 +1,114 @@
1
- class Scraper
1
+ module QuickTicker
2
2
 
3
- attr_accessor :cli, :gfs_noko_html, :stock
3
+ class Scraper
4
4
 
5
- def initialize(cli)
6
- self.cli = cli
7
- end
5
+ attr_accessor :browser, :cli, :gfs_noko_html, :stock
8
6
 
9
- def gfs_url(symbol)
10
- "https://www.google.com/finance?q=" + symbol
11
- end
7
+ def initialize(cli)
8
+ self.cli = cli
9
+ self.browser = Watir::Browser.new(:phantomjs)
10
+ end
12
11
 
13
- def load_gfs_noko_html(url)
14
- self.gfs_noko_html = Nokogiri::HTML(open(url))
15
- end
12
+ def gfs_url(symbol)
13
+ "https://www.google.com/finance?q=" + symbol
14
+ end
16
15
 
17
- # Returns an array. Array[0] is a stock if one was succesfully created and nil otherwise.
18
- # Array[1] indicates whether the symbol cooresponds to a mutual fund.
19
- def load_gfs(symbol, fixture_url = nil)
20
- fixture_url.nil? ? load_gfs_noko_html(self.gfs_url(symbol)) : load_gfs_noko_html(fixture_url)
21
- return [nil, true] unless self.gfs_noko_html.text.match('\(MUTF:').nil?
22
- return [nil, false] if self.gfs_noko_html.css("span.pr").text.strip == "" # checks whether the page lists a price
23
- # return [nil, false] if self.gfs_noko_html.css("div.fjfe-content").text.include?("- produced no matches.")
24
- [self.create_stock(symbol), false]
25
- end
16
+ def load_gfs_noko_html(url)
17
+ browser.goto(url)
18
+ self.gfs_noko_html = Nokogiri::HTML(browser.html)
19
+ end
26
20
 
27
- def create_stock(symbol)
28
- data = scrape_stock(symbol)
29
- Stock.new(data)
30
- end
21
+ # Returns an array. Array[0] is a stock if one was succesfully created and nil otherwise.
22
+ # Array[1] indicates whether the symbol cooresponds to a mutual fund.
23
+ def load_gfs(symbol, fixture_url = nil)
24
+ fixture_url.nil? ? load_gfs_noko_html(self.gfs_url(symbol)) : load_gfs_noko_html(fixture_url)
25
+ return [nil, true] unless self.gfs_noko_html.text.match('\(MUTF:').nil?
26
+ return [nil, false] if self.gfs_noko_html.css("span.pr").text.strip == "" # checks whether the page lists a price
27
+ # return [nil, false] if self.gfs_noko_html.css("div.fjfe-content").text.include?("- produced no matches.")
28
+ [self.create_stock(symbol), false]
29
+ end
31
30
 
32
- def scrape_stock(symbol)
33
- data = { stock: {} }
34
- data[:stock][:symbol] = symbol
35
- begin
36
- data[:stock][:name] = self.gfs_noko_html.css("div.g-first a").text.match('(?<=All news for )[\w,.)() ]*(?= »)')[0]
37
- rescue NoMethodError
38
- data[:stock][:name] = ""
31
+ def create_stock(symbol)
32
+ data = package_stock(symbol)
33
+ QuickTicker::Stock.new(data)
39
34
  end
40
- data[:stock][:exchange] = self.gfs_noko_html.css("span.dis-large").text.split("\n")[0]
41
- data[:stock] = self.nil_to_empty_str(data[:stock])
42
- data[:quote] = self.scrape_quote
43
- data[:desc] = self.scrape_desc
44
- data
45
- end
46
35
 
47
- def scrape_quote
48
- data = {}
49
- data[:price] = self.gfs_noko_html.css("span.pr").text.strip
50
- data[:change] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[0]
51
- begin
52
- data[:change_pct] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[1].gsub(/[)(]/, '')
53
- rescue NoMethodError
54
- data[:change_pct] = ""
36
+ def package_stock(symbol)
37
+ data = {}
38
+ data[:stock] = self.scrape_stock(symbol)
39
+ data[:quote] = self.scrape_stock_quote
40
+ data[:description] = self.scrape_stock_description
41
+ data[:related_companies] = self.scrape_stock_related_companies
42
+ data
55
43
  end
56
- data[:range] = self.gfs_noko_html.css("td[data-snapfield='range']+td").text.strip
57
- data[:range_yr] = self.gfs_noko_html.css("td[data-snapfield='range_52week']+td").text.strip
58
- data[:open] = self.gfs_noko_html.css("td[data-snapfield='open']+td").text.strip
59
- data[:volume] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[0]
60
- data[:volume_avg] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[1]
61
- data[:mkt_cap] = self.gfs_noko_html.css("td[data-snapfield='market_cap']+td").text.strip
62
- data[:pe_ttm] = self.gfs_noko_html.css("td[data-snapfield='pe_ratio']+td").text.strip
63
- data[:div_yld] = self.gfs_noko_html.css("td[data-snapfield='latest_dividend-dividend_yield']+td").text.strip.split("/")[1]
64
- nil_to_empty_str(data)
65
- end
66
44
 
67
- def scrape_desc
68
- data = {}
69
- data[:sector] = self.gfs_noko_html.css("a#sector").text
70
- data[:industry] = self.gfs_noko_html.css("a#sector+a").text
71
- data[:summary] = self.gfs_noko_html.css("div.companySummary").text.gsub("More from Reuters »", "").strip
72
- nil_to_empty_str(data)
73
- end
45
+ def scrape_stock(symbol)
46
+ data = {}
47
+ data[:symbol] = symbol
48
+ begin
49
+ data[:name] = self.gfs_noko_html.css("div.g-first a").text.match('(?<=All news for )[\w,.)() ]*(?= »)')[0]
50
+ rescue NoMethodError
51
+ data[:name] = ""
52
+ end
53
+ data[:exchange] = self.gfs_noko_html.css("span.dis-large").text.split("\n")[0]
54
+ nil_to_empty_str(data)
55
+ end
56
+
57
+ def scrape_stock_quote
58
+ data = {}
59
+ data[:price] = self.gfs_noko_html.css("span.pr").text.strip
60
+ data[:change] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[0]
61
+ begin
62
+ data[:change_pct] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[1].gsub(/[)(]/, '').chomp("%")
63
+ rescue NoMethodError
64
+ data[:change_pct] = ""
65
+ end
66
+ data[:range] = self.gfs_noko_html.css("td[data-snapfield='range']+td").text.strip
67
+ data[:range_yr] = self.gfs_noko_html.css("td[data-snapfield='range_52week']+td").text.strip
68
+ data[:open] = self.gfs_noko_html.css("td[data-snapfield='open']+td").text.strip
69
+ data[:volume] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[0]
70
+ data[:volume_avg] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[1]
71
+ data[:mkt_cap] = self.gfs_noko_html.css("td[data-snapfield='market_cap']+td").text.strip
72
+ data[:pe_ttm] = self.gfs_noko_html.css("td[data-snapfield='pe_ratio']+td").text.strip
73
+ data[:div_yld] = self.gfs_noko_html.css("td[data-snapfield='latest_dividend-dividend_yield']+td").text.strip.split("/")[1]
74
+ nil_to_empty_str(data)
75
+ end
74
76
 
75
- # convert any nil values to empty strings to avoid exceptions
76
- def nil_to_empty_str(data_hash)
77
- data_hash.each do |key, value|
78
- data_hash[key] = "" if data_hash[key].nil?
77
+ def scrape_stock_description
78
+ data = {}
79
+ data[:sector] = self.gfs_noko_html.css("a#sector").text
80
+ data[:industry] = self.gfs_noko_html.css("a#sector+a").text
81
+ data[:summary] = self.gfs_noko_html.css("div.companySummary").text.gsub("More from Reuters »", "").strip
82
+ nil_to_empty_str(data)
79
83
  end
84
+
85
+ def scrape_stock_related_companies
86
+ data = []
87
+ for i in (0..10) do
88
+ begin
89
+ data << {
90
+ symbol: self.gfs_noko_html.css("table#cc-table td.ctsymbol")[i].text,
91
+ price: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td")[i].text,
92
+ change: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td+td")[i].text,
93
+ change_pct: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td+td+td")[i].text.chomp("%"),
94
+ mkt_cap: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td+td+td+td+td")[i].text
95
+ }
96
+ rescue NoMethodError
97
+ i = 11
98
+ end
99
+ end
100
+ data.collect do |related_company_hash|
101
+ nil_to_empty_str(related_company_hash)
102
+ end
103
+ end
104
+
105
+ # convert any nil values to empty strings to avoid exceptions
106
+ def nil_to_empty_str(data_hash)
107
+ data_hash.each do |key, value|
108
+ data_hash[key] = "" if data_hash[key].nil?
109
+ end
110
+ end
111
+
80
112
  end
81
113
 
82
114
  end