gmoney 0.0.2 → 0.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.
data/Manifest CHANGED
@@ -2,6 +2,7 @@ Manifest
2
2
  README.rdoc
3
3
  Rakefile
4
4
  gmoney.gemspec
5
+ lib/extensions/fixnum.rb
5
6
  lib/extensions/string.rb
6
7
  lib/gmoney.rb
7
8
  lib/gmoney/authentication_request.rb
@@ -22,9 +23,11 @@ spec/fixtures/default_portfolios_feed.xml
22
23
  spec/fixtures/empty_portfolio_feed.xml
23
24
  spec/fixtures/portfolio_9_feed.xml
24
25
  spec/fixtures/portfolio_feed_with_returns.xml
26
+ spec/fixtures/position_feed_for_9_GOOG.xml
25
27
  spec/fixtures/positions_feed_for_portfolio_14.xml
26
28
  spec/fixtures/positions_feed_for_portfolio_9.xml
27
29
  spec/fixtures/positions_feed_for_portfolio_9r.xml
30
+ spec/fixtures/transaction_feed_for_GOOG_1.xml
28
31
  spec/fixtures/transactions_feed_for_GOOG.xml
29
32
  spec/gmoney_spec.rb
30
33
  spec/portfolio_feed_parser_spec.rb
data/README.rdoc CHANGED
@@ -4,17 +4,61 @@ A gem for interacting with the Google Finance API
4
4
 
5
5
  == Install
6
6
 
7
+ gem install gmoney
8
+
9
+ or
10
+
7
11
  gem install jspradlin-gmoney --source http://gems.github.com
8
12
 
9
13
  == Usage
10
14
 
11
- GMoney::GFSession.login('google username', 'password')
15
+ Login
16
+ -----
17
+
18
+ > GMoney::GFSession.login('google username', 'password')
19
+
20
+ Portfolios
21
+ --------
22
+
23
+ > portfolios = GMoney::Portfolio.all #returns all of a users portfolios
24
+ > portfolio = GMoney::Portfolio.find(9) #returns a specific portfolio
25
+
26
+ > positions = portfolio.positions #returns an Array of the Positions held within a given portfolio
27
+
28
+ Positions
29
+ --------
12
30
 
13
- portfolios = GMoney::Portfolio.all #or GMoney::Portfolio.all(:with_returns => true)
31
+ > positions = GMoney::Position.find(9) #returns all of a users positions within a given portfolio, i.e. Portfolio "9"
32
+ > position = GMoney::Position.find("9/NASDAQ:GOOG") #returns a specific position within a given portfolio
33
+
34
+ > transactions = position.transactions #returns an Array of the Transactions within a given position
35
+
36
+ Transactions
37
+ --------
38
+
39
+ > transactions = GMoney::Transaction.find("9/NASDAQ:GOOG") #returns all of a users transactions within a given position
40
+ > transaction = GMoney::Transaction.find("9/NASDAQ:GOOG/2") #returns a specific transaction within a given position
41
+
42
+ ----------
43
+
44
+ Options parameter
45
+
46
+
47
+ The following methods:
48
+ Portfolio.all
49
+ Portfolio.find
50
+ portfolio.positions #where portfolio is an instance of the Portfolio class
14
51
 
15
- #wait for a minute or so
52
+ Position.find
53
+ position.transactions #where position is an instance of the Position class
54
+
55
+ Transaction.find
56
+
57
+ all take a hash as an optional last parameter. Valid values for this hash are:
16
58
 
17
- portfolios will now contain a list of all of your google finance portfolios, positions, and transactions
59
+ * :returns - By default Google does not return a comprehensive list of returns data. Set this parameter to true to access this information.
60
+ * :refresh - GMoney caches the data returned from Google by default. Set :refresh => true if you would like to get the latest data.
61
+ * :eager - GMoney lazily loads children data (i.e. positions for a given Portfolio). If you would like to load all of this data at once set :eager => true
18
62
 
19
63
  == License
20
64
 
data/Rakefile CHANGED
@@ -1,12 +1,13 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
- require 'spec/rake/spectask'
4
+ require 'spec/rake/spectask'
5
+ require 'lib/gmoney'
5
6
 
6
- task :default => :specs
7
+ task :default => :spec
7
8
 
8
9
  desc "Run the gmoney specs"
9
- task :specs do
10
+ task :spec do
10
11
  Spec::Rake::SpecTask.new do |t|
11
12
  t.spec_files = FileList['spec/**/*_spec.rb']
12
13
  t.rcov = true
@@ -15,11 +16,14 @@ task :specs do
15
16
  end
16
17
  end
17
18
 
18
- Echoe.new('gmoney', '0.0.2') do |p|
19
- p.description = "A gem for interacting with the Google Finance API"
20
- p.url = "http://github.com/jspradlin/gmoney"
21
- p.author = "Justin Spradlin"
22
- p.email = "jspradlin@gmail.com"
23
- p.ignore_pattern = ["coverage/*", "*~"]
24
- p.development_dependencies = []
19
+ desc "Gemify GMoney"
20
+ namespace :gemify do
21
+ Echoe.new('gmoney', GMoney.version) do |p|
22
+ p.description = "A gem for interacting with the Google Finance API"
23
+ p.url = "http://github.com/jspradlin/gmoney"
24
+ p.author = "Justin Spradlin"
25
+ p.email = "jspradlin@gmail.com"
26
+ p.ignore_pattern = ["coverage/*", "*~"]
27
+ p.development_dependencies = []
28
+ end
25
29
  end
data/gmoney.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{gmoney}
5
- s.version = "0.0.2"
5
+ s.version = "0.1.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Justin Spradlin"]
9
- s.date = %q{2009-09-16}
9
+ s.date = %q{2009-10-01}
10
10
  s.description = %q{A gem for interacting with the Google Finance API}
11
11
  s.email = %q{jspradlin@gmail.com}
12
- s.extra_rdoc_files = ["README.rdoc", "lib/extensions/string.rb", "lib/gmoney.rb", "lib/gmoney/authentication_request.rb", "lib/gmoney/feed_parser.rb", "lib/gmoney/gf_request.rb", "lib/gmoney/gf_response.rb", "lib/gmoney/gf_service.rb", "lib/gmoney/gf_session.rb", "lib/gmoney/portfolio.rb", "lib/gmoney/portfolio_feed_parser.rb", "lib/gmoney/position.rb", "lib/gmoney/position_feed_parser.rb", "lib/gmoney/transaction.rb", "lib/gmoney/transaction_feed_parser.rb"]
13
- s.files = ["Manifest", "README.rdoc", "Rakefile", "gmoney.gemspec", "lib/extensions/string.rb", "lib/gmoney.rb", "lib/gmoney/authentication_request.rb", "lib/gmoney/feed_parser.rb", "lib/gmoney/gf_request.rb", "lib/gmoney/gf_response.rb", "lib/gmoney/gf_service.rb", "lib/gmoney/gf_session.rb", "lib/gmoney/portfolio.rb", "lib/gmoney/portfolio_feed_parser.rb", "lib/gmoney/position.rb", "lib/gmoney/position_feed_parser.rb", "lib/gmoney/transaction.rb", "lib/gmoney/transaction_feed_parser.rb", "spec/authentication_request_spec.rb", "spec/fixtures/cacert.pem", "spec/fixtures/default_portfolios_feed.xml", "spec/fixtures/empty_portfolio_feed.xml", "spec/fixtures/portfolio_9_feed.xml", "spec/fixtures/portfolio_feed_with_returns.xml", "spec/fixtures/positions_feed_for_portfolio_14.xml", "spec/fixtures/positions_feed_for_portfolio_9.xml", "spec/fixtures/positions_feed_for_portfolio_9r.xml", "spec/fixtures/transactions_feed_for_GOOG.xml", "spec/gmoney_spec.rb", "spec/portfolio_feed_parser_spec.rb", "spec/portfolio_spec.rb", "spec/position_feed_parser_spec.rb", "spec/position_spec.rb", "spec/request_spec.rb", "spec/response_spec.rb", "spec/service_spec.rb", "spec/session_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/string_spec.rb", "spec/transaction_feed_parser_spec.rb", "spec/transaction_spec.rb"]
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/extensions/fixnum.rb", "lib/extensions/string.rb", "lib/gmoney.rb", "lib/gmoney/authentication_request.rb", "lib/gmoney/feed_parser.rb", "lib/gmoney/gf_request.rb", "lib/gmoney/gf_response.rb", "lib/gmoney/gf_service.rb", "lib/gmoney/gf_session.rb", "lib/gmoney/portfolio.rb", "lib/gmoney/portfolio_feed_parser.rb", "lib/gmoney/position.rb", "lib/gmoney/position_feed_parser.rb", "lib/gmoney/transaction.rb", "lib/gmoney/transaction_feed_parser.rb"]
13
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "gmoney.gemspec", "lib/extensions/fixnum.rb", "lib/extensions/string.rb", "lib/gmoney.rb", "lib/gmoney/authentication_request.rb", "lib/gmoney/feed_parser.rb", "lib/gmoney/gf_request.rb", "lib/gmoney/gf_response.rb", "lib/gmoney/gf_service.rb", "lib/gmoney/gf_session.rb", "lib/gmoney/portfolio.rb", "lib/gmoney/portfolio_feed_parser.rb", "lib/gmoney/position.rb", "lib/gmoney/position_feed_parser.rb", "lib/gmoney/transaction.rb", "lib/gmoney/transaction_feed_parser.rb", "spec/authentication_request_spec.rb", "spec/fixtures/cacert.pem", "spec/fixtures/default_portfolios_feed.xml", "spec/fixtures/empty_portfolio_feed.xml", "spec/fixtures/portfolio_9_feed.xml", "spec/fixtures/portfolio_feed_with_returns.xml", "spec/fixtures/position_feed_for_9_GOOG.xml", "spec/fixtures/positions_feed_for_portfolio_14.xml", "spec/fixtures/positions_feed_for_portfolio_9.xml", "spec/fixtures/positions_feed_for_portfolio_9r.xml", "spec/fixtures/transaction_feed_for_GOOG_1.xml", "spec/fixtures/transactions_feed_for_GOOG.xml", "spec/gmoney_spec.rb", "spec/portfolio_feed_parser_spec.rb", "spec/portfolio_spec.rb", "spec/position_feed_parser_spec.rb", "spec/position_spec.rb", "spec/request_spec.rb", "spec/response_spec.rb", "spec/service_spec.rb", "spec/session_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/string_spec.rb", "spec/transaction_feed_parser_spec.rb", "spec/transaction_spec.rb"]
14
14
  s.homepage = %q{http://github.com/jspradlin/gmoney}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Gmoney", "--main", "README.rdoc"]
16
16
  s.require_paths = ["lib"]
@@ -0,0 +1,13 @@
1
+ class Fixnum
2
+ def portfolio_id
3
+ self.to_s.portfolio_id
4
+ end
5
+
6
+ def position_id
7
+ self.to_s.position_id
8
+ end
9
+
10
+ def transaction_id
11
+ self.to_s.transaction_id
12
+ end
13
+ end
@@ -1,4 +1,16 @@
1
1
  class String
2
+ class PortfolioParseError < StandardError; end
3
+ class PositionParseError < StandardError; end
4
+ class TransactionParseError < StandardError; end
5
+
6
+ @@portfolio_re = /\d+/
7
+ @@portfolio_re_in = /^\d+$/
8
+ @@position_re = /\d+\/[a-zA-Z]+:[a-zA-Z]+/
9
+ @@position_re_in = /^\d+\/[a-zA-Z]+:[a-zA-Z]+$/
10
+ @@transaction_re = /\d+\/[a-zA-Z]+:[a-zA-Z]+\/\d+/
11
+ @@transaction_re_in = /^\d+\/[a-zA-Z]+:[a-zA-Z]+\/\d+$/
12
+
13
+
2
14
  def camel_to_us
3
15
  add_us = gsub(/(.)([A-Z][a-z]+)/, '\1_\2')
4
16
  add_us.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase
@@ -7,4 +19,44 @@ class String
7
19
  def is_numeric?
8
20
  Float self rescue false
9
21
  end
22
+
23
+ def portfolio_feed_id
24
+ self[self.rindex('/')+1..-1]
25
+ end
26
+
27
+ def position_feed_id
28
+ portfolio = self[self.rindex('portfolios/')+11..index('/positions')-1]
29
+ position = self[rindex('/')+1..-1]
30
+ "#{portfolio}/#{position}"
31
+ end
32
+
33
+ def portfolio_id
34
+ if self[@@transaction_re_in] || self[@@position_re_in] || self[@@portfolio_re_in]
35
+ self[@@portfolio_re]
36
+ else
37
+ raise PortfolioParseError
38
+ end
39
+ end
40
+
41
+ def position_id
42
+ if self[@@portfolio_re_in]
43
+ ""
44
+ elsif self[@@position_re_in]
45
+ self[self.index('/')+1..-1]
46
+ elsif self[@@transaction_re_in]
47
+ self[self.index('/')+1..self.rindex('/')-1]
48
+ else
49
+ raise PositionParseError
50
+ end
51
+ end
52
+
53
+ def transaction_id
54
+ if self[@@position_re_in]
55
+ ""
56
+ elsif self[@@transaction_re_in]
57
+ self[self.rindex('/')+1..-1]
58
+ else
59
+ raise TransactionParseError
60
+ end
61
+ end
10
62
  end
data/lib/gmoney.rb CHANGED
@@ -10,6 +10,7 @@ require 'net/http'
10
10
  require 'net/https'
11
11
  require 'rexml/document'
12
12
 
13
+ require 'extensions/fixnum'
13
14
  require 'extensions/string'
14
15
 
15
16
  require 'gmoney/authentication_request'
@@ -26,9 +27,10 @@ require 'gmoney/transaction'
26
27
  require 'gmoney/transaction_feed_parser'
27
28
 
28
29
  module GMoney
29
- VERSION = '0.0.2'
30
+ VERSION = '0.1.0'
30
31
  GF_URL = "https://finance.google.com/finance"
31
32
  GF_FEED_URL = "#{GF_URL}/feeds/default"
33
+ GF_PORTFOLIO_FEED_URL = "#{GF_FEED_URL}/portfolios"
32
34
 
33
35
  HTTPOK = 200
34
36
  HTTPCreated = 201
@@ -10,7 +10,7 @@ module GMoney
10
10
  target.instance_variable_set("@#{name.camel_to_us}", value)
11
11
  end
12
12
 
13
- doc.elements.each('feed/entry') do |parsed_entry|
13
+ doc.elements.each('//entry') do |parsed_entry|
14
14
  finance_object = feed_class.new
15
15
  finance_data = parsed_entry.elements["gf:#{feed_class_string}Data"]
16
16
 
@@ -2,19 +2,34 @@ module GMoney
2
2
  class Portfolio
3
3
  class PortfolioRequestError < StandardError;end
4
4
 
5
- attr_accessor :title, :currency_code, :positions
5
+ attr_accessor :title, :currency_code
6
6
 
7
7
  attr_reader :id, :feed_link, :updated, :gain_percentage, :return1w, :return4w, :return3m,
8
8
  :return_ytd, :return1y, :return3y, :return5y, :return_overall,
9
9
  :cost_basis, :days_gain, :gain, :market_value
10
+
11
+ def self.all(options = {})
12
+ retreive_portfolios(:all, options)
13
+ end
10
14
 
11
- def initialize()
12
- @positions = []
15
+ def self.find(id, options = {})
16
+ retreive_portfolios(id, options)
13
17
  end
14
18
 
15
- def self.all(options = {})
16
- url = "#{GF_FEED_URL}/portfolios"
17
- url += "?returns=true" if options[:with_returns]
19
+ def positions(options = {})
20
+ if options[:refresh]
21
+ @positions = Position.find(@id.portfolio_feed_id, options)
22
+ else
23
+ @positions ||= Position.find(@id.portfolio_feed_id, options)
24
+ end
25
+
26
+ @positions.is_a?(Array) ? @positions : [@positions]
27
+ end
28
+
29
+ def self.retreive_portfolios(id, options = {})
30
+ url = GF_PORTFOLIO_FEED_URL
31
+ url += "/#{id}" if id != :all
32
+ url += "?returns=true" if options[:returns]
18
33
  portfolios = []
19
34
 
20
35
  response = GFService.send_request(GFRequest.new(url, :headers => {"Authorization" => "GoogleLogin auth=#{GFSession.auth_token}"}))
@@ -22,13 +37,16 @@ module GMoney
22
37
  if response.status_code == HTTPOK
23
38
  portfolios = PortfolioFeedParser.parse_portfolio_feed(response.body)
24
39
  else
25
- raise PortfolioRequestError
40
+ raise PortfolioRequestError, response.body
26
41
  end
27
42
 
28
- portfolios.each do |portfolio|
29
- portfolio.positions = Position.find_by_url(portfolio.feed_link, {:with_returns => options[:with_returns]})
30
- end
31
- portfolios
32
- end
43
+ portfolios.each { |p| p.instance_variable_set("@positions", p.positions(options))} if options[:eager]
44
+
45
+ return portfolios[0] if portfolios.size == 1
46
+
47
+ portfolios
48
+ end
49
+
50
+ private_class_method :retreive_portfolios
33
51
  end
34
52
  end
@@ -1,33 +1,44 @@
1
1
  module GMoney
2
2
  class Position
3
3
  class PositionRequestError < StandardError; end
4
- attr_accessor :transactions
5
-
6
4
  attr_reader :id, :updated, :title, :feed_link, :exchange, :symbol, :shares,
7
5
  :full_name, :gain_percentage, :return1w, :return4w, :return3m,
8
6
  :return_ytd, :return1y, :return3y, :return5y, :return_overall,
9
7
  :cost_basis, :days_gain, :gain, :market_value
10
8
 
11
- def initialize
12
- @transactions = []
9
+ def self.find(id, options={})
10
+ find_by_url("#{GF_PORTFOLIO_FEED_URL}/#{id.portfolio_id}/positions/#{id.position_id}", options)
11
+ end
12
+
13
+ def transactions(options={})
14
+ if options[:refresh]
15
+ @transactions = Transaction.find(@id.position_feed_id, options)
16
+ else
17
+ @transactions ||= Transaction.find(@id.position_feed_id, options)
18
+ end
19
+
20
+ @transactions.is_a?(Array) ? @transactions : [@transactions]
13
21
  end
14
22
 
15
23
  def self.find_by_url(url, options = {})
16
24
  positions = []
17
- url += "?returns=true" if options[:with_returns]
25
+ url += "?returns=true" if options[:returns]
18
26
 
19
27
  response = GFService.send_request(GFRequest.new(url, :headers => {"Authorization" => "GoogleLogin auth=#{GFSession.auth_token}"}))
20
28
 
21
29
  if response.status_code == HTTPOK
22
30
  positions = PositionFeedParser.parse_position_feed(response.body) if response.status_code == 200
23
31
  else
24
- raise PositionRequestError
32
+ raise PositionRequestError, response.body
25
33
  end
26
34
 
27
- positions.each do |position|
28
- position.transactions = Transaction.find_by_url(position.feed_link)
29
- end
30
- positions
35
+ positions.each { |p| p.instance_variable_set("@transactions", p.transactions(options))} if options[:eager]
36
+
37
+ return positions[0] if positions.size == 1
38
+
39
+ positions
31
40
  end
41
+
42
+ private_class_method :find_by_url
32
43
  end
33
44
  end
@@ -6,16 +6,26 @@ module GMoney
6
6
 
7
7
  attr_accessor :type, :date, :shares, :notes, :commission, :price
8
8
 
9
- def self.find_by_url(url)
9
+ def self.find(id, options={})
10
+ find_by_url("#{GF_PORTFOLIO_FEED_URL}/#{id.portfolio_id}/positions/#{id.position_id}/transactions/#{id.transaction_id}", options)
11
+ end
12
+
13
+ def self.find_by_url(url, options={})
10
14
  transactions = []
11
15
 
12
16
  response = GFService.send_request(GFRequest.new(url, :headers => {"Authorization" => "GoogleLogin auth=#{GFSession.auth_token}"}))
13
17
 
14
18
  if response.status_code == HTTPOK
15
- TransactionFeedParser.parse_transaction_feed(response.body)
19
+ transactions = TransactionFeedParser.parse_transaction_feed(response.body)
16
20
  else
17
- raise TransactionRequestError
21
+ raise TransactionRequestError, response.body
18
22
  end
19
- end
23
+
24
+ return transactions[0] if transactions.size == 1
25
+
26
+ transactions
27
+ end
28
+
29
+ private_class_method :find_by_url
20
30
  end
21
31
  end
@@ -0,0 +1,25 @@
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gf='http://schemas.google.com/finance/2007' xmlns:gd='http://schemas.google.com/g/2005'>
3
+ <id>http://finance.google.com/finance/feeds/user@example.com/portfolios/9/positions/NASDAQ:GOOG</id>
4
+ <updated>2009-09-27T22:35:35.000Z</updated>
5
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/finance/2007#position' />
6
+ <title type='text'>Google Inc.</title>
7
+ <link rel='self' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/9/positions/NASDAQ%3AGOOG' />
8
+ <gd:feedLink href='http://finance.google.com/finance/feeds/user@example.com/portfolios/9/positions/NASDAQ:GOOG/transactions' />
9
+ <gf:positionData gainPercentage='0.1742898491' return1w='0.002075448663' return1y='0.1665490533' return3m='0.1665490533' return3y='0.1665490533' return4w='0.1665490533' return5y='0.1665490533' returnOverall='0.1665490533' returnYTD='0.1665490533'
10
+ shares='325.0'>
11
+ <gf:costBasis>
12
+ <gd:money amount='136300.25' currencyCode='USD' />
13
+ </gf:costBasis>
14
+ <gf:daysGain>
15
+ <gd:money amount='-1394.24285' currencyCode='USD' />
16
+ </gf:daysGain>
17
+ <gf:gain>
18
+ <gd:money amount='23755.75' currencyCode='USD' />
19
+ </gf:gain>
20
+ <gf:marketValue>
21
+ <gd:money amount='160056.0' currencyCode='USD' />
22
+ </gf:marketValue>
23
+ </gf:positionData>
24
+ <gf:symbol exchange='NASDAQ' fullName='Google Inc.' symbol='GOOG' />
25
+ </entry>
@@ -0,0 +1,17 @@
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gf='http://schemas.google.com/finance/2007' xmlns:gd='http://schemas.google.com/g/2005'>
3
+ <id>http://finance.google.com/finance/feeds/user@example.com/portfolios/9/positions/NASDAQ:GOOG/transactions/2</id>
4
+ <updated>2009-09-12T18:22:44.000Z</updated>
5
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/finance/2007#transaction' />
6
+ <title type='text'>2</title>
7
+ <link rel='self' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/9/positions/NASDAQ%3AGOOG/transactions/2' />
8
+ <link rel='edit' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/9/positions/NASDAQ%3AGOOG/transactions/2' />
9
+ <gf:transactionData date='2009-09-01T00:00:00.000' shares='100.0' type='Buy'>
10
+ <gf:commission>
11
+ <gd:money amount='50.0' currencyCode='USD' />
12
+ </gf:commission>
13
+ <gf:price>
14
+ <gd:money amount='400.0' currencyCode='USD' />
15
+ </gf:price>
16
+ </gf:transactionData>
17
+ </entry>
@@ -17,7 +17,7 @@ describe GMoney::Portfolio do
17
17
  @gf_response = GMoney::GFResponse.new
18
18
  @gf_response.status_code = 200
19
19
  @gf_response.body = @default_feed
20
- @positions = []
20
+ @positions = nil
21
21
  end
22
22
 
23
23
  it "should return all Portfolios when status_code is 200" do
@@ -27,70 +27,87 @@ describe GMoney::Portfolio do
27
27
  portfolios.size.should be_eql(3)
28
28
  end
29
29
 
30
- it "should return a portfolio with returns data is :with_returns == true" do
30
+ it "should return a portfolio with returns data is :returns == true" do
31
31
  @url = 'https://finance.google.com/finance/feeds/default/portfolios?returns=true'
32
32
  @gf_response.body = @feed_with_returns
33
33
 
34
- portfolios = portfolio_helper(@url, {:with_returns => true})
34
+ portfolios = portfolio_helper(@url, :all, {:returns => true})
35
35
 
36
36
  portfolios.size.should be_eql(3)
37
37
  portfolios[0].cost_basis.should be_eql(2500.00)
38
38
  portfolios[0].gain_percentage.should be_eql(28.3636)
39
39
  portfolios[0].return4w.should be_eql(-0.1670616114)
40
-
41
40
  portfolios[2].cost_basis.should be_nil
42
41
  end
43
42
 
44
43
  it "should raise an error if the portfolio request does not return an OK status code" do
45
- @gf_response.status_code = 404
46
-
44
+ @gf_response.status_code = 401
47
45
  lambda { portfolio_helper(@url) }.should raise_error(GMoney::Portfolio::PortfolioRequestError)
48
46
  end
49
47
 
50
- it "should return an Array with only the default 'My Portfolio' portfolio when a user does not define his own portfolios" do
48
+ it "should raise an error with an error message when the user requests an invalid portfolio" do
49
+ @url += '/235234'
50
+ @gf_response.status_code = 404
51
+ @gf_response.body = "No portfolio exists with pid 235234"
52
+
53
+ lambda { portfolio_helper(@url, 235234) }.should raise_error(GMoney::Portfolio::PortfolioRequestError, @gf_response.body)
54
+ end
55
+
56
+ it "should return the default 'My Portfolio' portfolio when a user has not defined his own portfolios" do
51
57
  @gf_response.body = @empty_feed
52
- portfolios = portfolio_helper(@url)
53
-
54
- portfolios.size.should be_eql(1)
55
- portfolios[0].title.should be_eql('My Portfolio')
58
+ portfolio = portfolio_helper(@url)
59
+ portfolio.title.should be_eql('My Portfolio')
56
60
  end
57
61
 
58
- =begin TODO - create a method that retreives individual portfolios
59
- it "should return a specific portfolio is the request a specific portfolio" do
62
+ it "should return a specific portfolio if the user requests a specific portfolio" do
60
63
  @url += '/9'
61
64
  @gf_request.url = @url
62
65
 
63
66
  @gf_response.body = @portfolio_9_feed
64
- portfolios = portfolio_helper(@url)
67
+ portfolio = portfolio_helper(@url, 9)
68
+ portfolio.title.should be_eql('GMoney Test')
69
+ end
70
+
71
+ it "should grab the latest positions when :refresh is used." do
72
+ @url += '/9'
73
+ @gf_request.url = @url
74
+ @positions = [GMoney::Position.new, GMoney::Position.new]
75
+
76
+ @gf_response.body = @portfolio_9_feed
77
+ portfolio = portfolio_helper(@url, 9)
78
+
79
+ GMoney::Position.should_receive(:find).with("9", {:refresh => true}).and_return(@positions)
65
80
 
66
- portfolios.size.should be_eql(1)
67
- portfolios[0].title.should be_eql('GMoney Test')
81
+ portfolio.positions(:refresh => true).size.should be_eql(2)
68
82
  end
69
- =end
83
+
84
+ it "should use the cached portfolios when :refresh is not used (and the portfolios have already been set)" do
85
+ @url += '/9'
86
+ @gf_request.url = @url
87
+ @positions = [GMoney::Position.new, GMoney::Position.new]
88
+
89
+ @gf_response.body = @portfolio_9_feed
90
+ portfolio = portfolio_helper(@url, 9, :eager => true)
91
+
92
+ GMoney::Position.should_not_receive(:find).with(9, :eager => true)
93
+ portfolio.positions.size.should be_eql(2)
94
+ end
95
+
70
96
 
71
- def portfolio_helper(url, options = {})
97
+ def portfolio_helper(url, id = nil, options = {})
72
98
  GMoney::GFSession.should_receive(:auth_token).and_return('toke')
73
99
 
74
100
  GMoney::GFRequest.should_receive(:new).with(url, :headers => {"Authorization" => "GoogleLogin auth=toke"}).and_return(@gf_request)
75
101
 
76
102
  GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
77
103
 
78
- position_urls = get_position_urls(@gf_response.body)
79
-
80
- position_urls.each do |position_url|
81
- GMoney::Position.should_receive(:find_by_url).with(position_url, {:with_returns => options[:with_returns]}).any_number_of_times.and_return(@positions)
104
+ if options[:eager]
105
+ feed_ids = get_feed_ids(@gf_response.body)
106
+ feed_ids.each do |feed_id|
107
+ GMoney::Position.should_receive(:find).with(feed_id.portfolio_feed_id, options).any_number_of_times.and_return(@positions)
108
+ end
82
109
  end
83
110
 
84
- GMoney::Portfolio.all(options)
85
- end
86
-
87
- def get_position_urls(feed)
88
- doc = REXML::Document.new(feed)
89
- feed_links = []
90
-
91
- doc.elements.each('feed/entry') do |parsed_entry|
92
- feed_links << parsed_entry.elements['gd:feedLink'].attributes['href']
93
- end
94
- feed_links
111
+ portfolios = id ? GMoney::Portfolio.find(id, options) : GMoney::Portfolio.all(options)
95
112
  end
96
113
  end
@@ -5,12 +5,13 @@ describe GMoney::Position do
5
5
  @default_feed = File.read('spec/fixtures/positions_feed_for_portfolio_9.xml')
6
6
  @feed_with_returns = File.read('spec/fixtures/positions_feed_for_portfolio_9r.xml')
7
7
  @empty_feed = File.read('spec/fixtures/positions_feed_for_portfolio_14.xml')
8
+ @feed_for_port9_goog = File.read('spec/fixtures/position_feed_for_9_GOOG.xml')
8
9
  end
9
10
 
10
11
  before(:each) do
11
- @url = 'https://finance.google.com/finance/feeds/default/portfolios/9/positions'
12
+ @portfolio_id = '9'
12
13
 
13
- @gf_request = GMoney::GFRequest.new(@url)
14
+ @gf_request = GMoney::GFRequest.new(@portfolio_id)
14
15
  @gf_request.method = :get
15
16
 
16
17
  @gf_response = GMoney::GFResponse.new
@@ -21,15 +22,15 @@ describe GMoney::Position do
21
22
 
22
23
  it "should return all Positions when status_code is 200" do
23
24
  @gf_response.body = @default_feed
24
- positions = position_helper(@url)
25
+ positions = position_helper(@portfolio_id)
25
26
 
26
27
  positions.size.should be_eql(5)
27
28
  end
28
29
 
29
- it "should return a position with returns data is :with_returns == true" do
30
+ it "should return a position with returns data is :returns == true" do
30
31
  @gf_response.body = @feed_with_returns
31
32
 
32
- positions = position_helper(@url, {:with_returns => true})
33
+ positions = position_helper(@portfolio_id, {:returns => true})
33
34
 
34
35
  positions.size.should be_eql(5)
35
36
  positions[0].cost_basis.should be_eql(615.00)
@@ -40,41 +41,69 @@ describe GMoney::Position do
40
41
  end
41
42
 
42
43
  it "should raise an error if the position request does not return an OK status code" do
43
- @gf_response.status_code = 404
44
-
45
- lambda { position_helper(@url) }.should raise_error(GMoney::Position::PositionRequestError)
44
+ @gf_response.status_code = 404
45
+ lambda { position_helper(@portfolio_id) }.should raise_error(GMoney::Position::PositionRequestError)
46
46
  end
47
47
 
48
- =begin TODO - create a method that retreives individual positions
49
- it "should return a specific position is the request a specific position" do
48
+ it "should return a specific position if the user requests a specific position" do
49
+ @portfolio_id = '9/NASDAG:GOOG'
50
+ @gf_response.body = @feed_for_port9_goog
51
+
52
+ position = position_helper(@portfolio_id)
53
+
54
+ position.cost_basis.should be_eql(136300.25)
55
+ position.return1w.should be_eql(0.002075448663)
50
56
  end
51
- =end
57
+
58
+
59
+ it "should raise an error message when a user request an invalid Position" do
60
+ @portfolio_id = '9/NASDAG:ASDF'
61
+ @gf_response.status_code = 404
62
+ @gf_response.body = "No position exists with ticker NASDAQ:ASDF"
52
63
 
53
- def position_helper(url, options = {})
64
+ lambda { position_helper(@portfolio_id)}.should raise_error(GMoney::Position::PositionRequestError, @gf_response.body)
65
+ end
66
+
67
+ it "should grab the latest transactions when :refresh is used" do
68
+ @position_id = '9/NASDAG:GOOG'
69
+ @transactions = [GMoney::Transaction.new, GMoney::Transaction.new, GMoney::Transaction.new]
70
+
71
+ @gf_response.body = @feed_for_port9_goog
72
+ position = position_helper(@position_id, :refresh => true)
73
+
74
+ GMoney::Transaction.should_receive(:find).with(position.id.position_feed_id, {:refresh => true}).and_return(@transactions)
75
+
76
+ position.transactions(:refresh => true).size.should be_eql(3)
77
+ end
78
+
79
+ it "should grab cached transactions when :refresh is not used" do
80
+ @position_id = '9/NASDAG:GOOG'
81
+ @transactions = [GMoney::Transaction.new, GMoney::Transaction.new, GMoney::Transaction.new]
82
+
83
+ @gf_response.body = @feed_for_port9_goog
84
+ position = position_helper(@position_id, :eager => true)
85
+
86
+ GMoney::Transaction.should_not_receive(:find).with(position.id.position_feed_id, {:eager => true})
87
+ position.transactions(:eager => true).size.should be_eql(3)
88
+ end
89
+
90
+ def position_helper(id, options = {})
54
91
  GMoney::GFSession.should_receive(:auth_token).and_return('toke')
55
92
 
56
- send_url = options[:with_returns] ? (url + '?returns=true') : url
93
+ url = "#{GMoney::GF_PORTFOLIO_FEED_URL}/#{id.portfolio_id}/positions/#{id.position_id}"
94
+ send_url = options[:returns] ? (url + '?returns=true') : url
57
95
 
58
96
  GMoney::GFRequest.should_receive(:new).with(send_url, :headers => {"Authorization" => "GoogleLogin auth=toke"}).and_return(@gf_request)
59
97
 
60
98
  GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
61
99
 
62
- transaction_urls = get_transaction_urls(@gf_response.body)
63
-
64
- transaction_urls.each do |transaction_url|
65
- GMoney::Transaction.should_receive(:find_by_url).with(transaction_url).any_number_of_times.and_return(@transactions)
66
- end
100
+ if options[:eager]
101
+ feed_ids = get_feed_ids(@gf_response.body)
102
+ feed_ids.each do |feed_id|
103
+ GMoney::Transaction.should_receive(:find).with(feed_id.position_feed_id, options).any_number_of_times.and_return(@transactions)
104
+ end
105
+ end
67
106
 
68
- GMoney::Position.find_by_url(url, options)
69
- end
70
-
71
- def get_transaction_urls(feed)
72
- doc = REXML::Document.new(feed)
73
- feed_links = []
74
-
75
- doc.elements.each('feed/entry') do |parsed_entry|
76
- feed_links << parsed_entry.elements['gd:feedLink'].attributes['href']
77
- end
78
- feed_links
107
+ GMoney::Position.find(id, options)
79
108
  end
80
109
  end
data/spec/spec_helper.rb CHANGED
@@ -1 +1,11 @@
1
1
  require File.dirname(__FILE__) + '/../lib/gmoney'
2
+
3
+ def get_feed_ids(feed)
4
+ doc = REXML::Document.new(feed)
5
+ feed_ids = []
6
+
7
+ doc.elements.each('//entry') do |parsed_entry|
8
+ feed_ids << parsed_entry.elements['id'].text
9
+ end
10
+ feed_ids
11
+ end
data/spec/string_spec.rb CHANGED
@@ -37,4 +37,43 @@ describe String do
37
37
  '$123.23'.is_numeric?.should be_false
38
38
  '.'.is_numeric?.should be_false
39
39
  end
40
+
41
+ it "should be able to parse a portfolio id from an portfolio feed url" do
42
+ "http://finance.google.com/finance/feeds/user@example.com/portfolios/9".portfolio_feed_id.should be_eql("9")
43
+ end
44
+
45
+ it "should be able to parse a position id from an position feed feed url" do
46
+ "http://finance.google.com/finance/feeds/user@example.com/portfolios/9/positions/NASDAG:GOOG".position_feed_id.should be_eql("9/NASDAG:GOOG")
47
+ end
48
+
49
+ it "should be able to parse a portfolio id out of a string" do
50
+ 1.portfolio_id.should be_eql("1")
51
+ "1".portfolio_id.should be_eql("1")
52
+ lambda { "asdf".portfolio_id }.should raise_error(String::PortfolioParseError)
53
+ lambda { "123/NASDAQ:GOOG/2134/23".portfolio_id }.should raise_error(String::PortfolioParseError)
54
+ "124".portfolio_id.should be_eql("124")
55
+ "1/NASDAQ:GOOG".portfolio_id.should be_eql("1")
56
+ "123/NASDAQ:GOOG".portfolio_id.should be_eql("123")
57
+ "123/NASDAQ:GOOG/23".portfolio_id.should be_eql("123")
58
+ end
59
+
60
+ it "should be able to parse a position id out of a string" do
61
+ 1.position_id.should be_eql("")
62
+ "1".position_id.should be_eql("")
63
+ lambda { "asdf".position_id }.should raise_error(String::PositionParseError)
64
+ lambda {"123/NASDAQ:GOOG/2134/23".position_id}.should raise_error(String::PositionParseError)
65
+ "1/NASDAQ:GOOG".position_id.should be_eql("NASDAQ:GOOG")
66
+ "123/NASDAQ:GOOG".position_id.should be_eql("NASDAQ:GOOG")
67
+ "123/NASDAQ:GOOG/23".position_id.should be_eql("NASDAQ:GOOG")
68
+ end
69
+
70
+ it "should be able to parse a transaction id out of a string" do
71
+ lambda { 1.transaction_id }.should raise_error(String::TransactionParseError)
72
+ lambda { "1".transaction_id }.should raise_error(String::TransactionParseError)
73
+ lambda { "asdf".transaction_id }.should raise_error(String::TransactionParseError)
74
+ "1/NASDAQ:GOOG".transaction_id.should be_eql("")
75
+ lambda {"123/NASDAQ:GOOG/asdf".transaction_id}.should raise_error(String::TransactionParseError)
76
+ lambda {"123/NASDAQ:GOOG/2134/23".transaction_id}.should raise_error(String::TransactionParseError)
77
+ "123/NASDAQ:GOOG/23".transaction_id.should be_eql("23")
78
+ end
40
79
  end
@@ -3,10 +3,12 @@ require File.join(File.dirname(__FILE__), '/spec_helper')
3
3
  describe GMoney::Transaction do
4
4
  before(:all) do
5
5
  @goog_feed = File.read('spec/fixtures/transactions_feed_for_GOOG.xml')
6
+ @goog_feed_1 = File.read('spec/fixtures/transaction_feed_for_GOOG_1.xml')
6
7
  end
7
8
 
8
9
  before(:each) do
9
10
  @url = 'https://finance.google.com/finance/feeds/default/portfolios/9/positions/NASDAQ:GOOG/transactions'
11
+ @transaction_id = '9/NASDAQ:GOOG'
10
12
 
11
13
  @gf_request = GMoney::GFRequest.new(@url)
12
14
  @gf_request.method = :get
@@ -17,7 +19,7 @@ describe GMoney::Transaction do
17
19
  end
18
20
 
19
21
  it "should return all Tranasactions when the status code is 200" do
20
- transactions = transaction_helper(@url)
22
+ transactions = transaction_helper(@transaction_id)
21
23
 
22
24
  transactions.size.should be_eql(4)
23
25
  transactions[1].commission.should be_eql(12.75)
@@ -25,22 +27,31 @@ describe GMoney::Transaction do
25
27
  end
26
28
 
27
29
  it "should raise an error when the status code is not 200" do
30
+ @transaction_id += '/1'
28
31
  @gf_response.status_code = 404
32
+ @gf_response.body = "No transaction exists with tid 1"
29
33
 
30
- lambda { transaction_helper(@url) }.should raise_error(GMoney::Transaction::TransactionRequestError)
34
+ lambda { transaction_helper(@transaction_id) }.should raise_error(GMoney::Transaction::TransactionRequestError, @gf_response.body)
31
35
  end
32
36
 
33
- =begin TODO - create a method that retreives individual transactions
34
- it "should return a specific transactions is the request a specific transactions" do
35
- =end
37
+ it "should return a specific transactions if the user request a specific transaction" do
38
+ @transaction_id += '/2'
39
+ @gf_response.body = @goog_feed_1
40
+ transaction = transaction_helper(@transaction_id)
36
41
 
37
- def transaction_helper(url)
42
+ transaction.commission.should be_eql(50.0)
43
+ transaction.price.should be_eql(400.0)
44
+ end
45
+
46
+ def transaction_helper(id, options={})
38
47
  GMoney::GFSession.should_receive(:auth_token).and_return('toke')
39
48
 
49
+ url = "#{GMoney::GF_PORTFOLIO_FEED_URL}/#{id.portfolio_id}/positions/#{id.position_id}/transactions/#{id.transaction_id}"
50
+
40
51
  GMoney::GFRequest.should_receive(:new).with(url, :headers => {"Authorization" => "GoogleLogin auth=toke"}).and_return(@gf_request)
41
52
 
42
53
  GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
43
54
 
44
- GMoney::Transaction.find_by_url(url)
55
+ GMoney::Transaction.find(id, options)
45
56
  end
46
57
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gmoney
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Spradlin
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-16 00:00:00 -04:00
12
+ date: 2009-10-01 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -21,6 +21,7 @@ extensions: []
21
21
 
22
22
  extra_rdoc_files:
23
23
  - README.rdoc
24
+ - lib/extensions/fixnum.rb
24
25
  - lib/extensions/string.rb
25
26
  - lib/gmoney.rb
26
27
  - lib/gmoney/authentication_request.rb
@@ -40,6 +41,7 @@ files:
40
41
  - README.rdoc
41
42
  - Rakefile
42
43
  - gmoney.gemspec
44
+ - lib/extensions/fixnum.rb
43
45
  - lib/extensions/string.rb
44
46
  - lib/gmoney.rb
45
47
  - lib/gmoney/authentication_request.rb
@@ -60,9 +62,11 @@ files:
60
62
  - spec/fixtures/empty_portfolio_feed.xml
61
63
  - spec/fixtures/portfolio_9_feed.xml
62
64
  - spec/fixtures/portfolio_feed_with_returns.xml
65
+ - spec/fixtures/position_feed_for_9_GOOG.xml
63
66
  - spec/fixtures/positions_feed_for_portfolio_14.xml
64
67
  - spec/fixtures/positions_feed_for_portfolio_9.xml
65
68
  - spec/fixtures/positions_feed_for_portfolio_9r.xml
69
+ - spec/fixtures/transaction_feed_for_GOOG_1.xml
66
70
  - spec/fixtures/transactions_feed_for_GOOG.xml
67
71
  - spec/gmoney_spec.rb
68
72
  - spec/portfolio_feed_parser_spec.rb