gmoney 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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