gmoney 0.2.1 → 0.4.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 +5 -0
- data/README.rdoc +54 -24
- data/gmoney.gemspec +5 -5
- data/lib/extensions/nil_class.rb +5 -0
- data/lib/extensions/string.rb +4 -0
- data/lib/gmoney.rb +2 -1
- data/lib/gmoney/feed_parser.rb +0 -4
- data/lib/gmoney/portfolio.rb +36 -3
- data/lib/gmoney/transaction.rb +54 -3
- data/spec/fixtures/new_portfolio_feed.xml +11 -0
- data/spec/fixtures/new_transaction_feed.xml +17 -0
- data/spec/fixtures/updated_portfolio_feed.xml +13 -0
- data/spec/nil_class_spec.rb +7 -0
- data/spec/portfolio_spec.rb +74 -3
- data/spec/string_spec.rb +8 -0
- data/spec/transaction_spec.rb +160 -0
- metadata +9 -3
data/Manifest
CHANGED
@@ -3,6 +3,7 @@ README.rdoc
|
|
3
3
|
Rakefile
|
4
4
|
gmoney.gemspec
|
5
5
|
lib/extensions/fixnum.rb
|
6
|
+
lib/extensions/nil_class.rb
|
6
7
|
lib/extensions/string.rb
|
7
8
|
lib/gmoney.rb
|
8
9
|
lib/gmoney/authentication_request.rb
|
@@ -21,6 +22,8 @@ spec/authentication_request_spec.rb
|
|
21
22
|
spec/fixtures/cacert.pem
|
22
23
|
spec/fixtures/default_portfolios_feed.xml
|
23
24
|
spec/fixtures/empty_portfolio_feed.xml
|
25
|
+
spec/fixtures/new_portfolio_feed.xml
|
26
|
+
spec/fixtures/new_transaction_feed.xml
|
24
27
|
spec/fixtures/portfolio_9_feed.xml
|
25
28
|
spec/fixtures/portfolio_feed_with_returns.xml
|
26
29
|
spec/fixtures/position_feed_for_9_GOOG.xml
|
@@ -29,7 +32,9 @@ spec/fixtures/positions_feed_for_portfolio_9.xml
|
|
29
32
|
spec/fixtures/positions_feed_for_portfolio_9r.xml
|
30
33
|
spec/fixtures/transaction_feed_for_GOOG_1.xml
|
31
34
|
spec/fixtures/transactions_feed_for_GOOG.xml
|
35
|
+
spec/fixtures/updated_portfolio_feed.xml
|
32
36
|
spec/gmoney_spec.rb
|
37
|
+
spec/nil_class_spec.rb
|
33
38
|
spec/portfolio_feed_parser_spec.rb
|
34
39
|
spec/portfolio_spec.rb
|
35
40
|
spec/position_feed_parser_spec.rb
|
data/README.rdoc
CHANGED
@@ -9,61 +9,88 @@ A gem for interacting with the Google Finance API
|
|
9
9
|
== Usage
|
10
10
|
|
11
11
|
Login
|
12
|
-
|
12
|
+
-----
|
13
13
|
|
14
14
|
GMoney::GFSession.login('google username', 'password')
|
15
15
|
|
16
16
|
Portfolios
|
17
|
-
|
17
|
+
--------
|
18
18
|
|
19
19
|
Reading
|
20
|
-
portfolios = GMoney::Portfolio.all #returns all of a users portfolios
|
21
|
-
portfolio = GMoney::Portfolio.find(9) #returns a specific portfolio
|
20
|
+
> portfolios = GMoney::Portfolio.all #returns all of a users portfolios
|
21
|
+
> portfolio = GMoney::Portfolio.find(9) #returns a specific portfolio
|
22
|
+
|
23
|
+
> positions = portfolio.positions #returns an Array of the Positions held within a given portfolio
|
22
24
|
|
23
|
-
|
25
|
+
Creating
|
26
|
+
> portfolio = GMoney::Portfolio.new
|
27
|
+
> portfolio.title = "My New Portfolio"
|
28
|
+
> portfolio.save #returns portfolio object
|
29
|
+
|
30
|
+
Updating
|
31
|
+
> portfolio = GMoney::Portfolio.find 9
|
32
|
+
> portfolio.title = "My Updated Portfolio Title"
|
33
|
+
> portfolio.save #returns portfolio object
|
24
34
|
|
25
35
|
Deleting
|
26
|
-
GMoney::Portfolio.delete 2
|
36
|
+
> GMoney::Portfolio.delete 2
|
27
37
|
|
28
38
|
or
|
29
39
|
|
30
|
-
portfolio = GMoney::Portfolio.find 2
|
31
|
-
portfolio.delete #call delete on an instance of a portfolio
|
40
|
+
> portfolio = GMoney::Portfolio.find 2
|
41
|
+
> portfolio.delete #call delete on an instance of a portfolio
|
32
42
|
|
33
43
|
|
34
44
|
Positions
|
35
|
-
|
45
|
+
--------
|
36
46
|
|
37
47
|
Reading
|
38
|
-
positions = GMoney::Position.find(9) #returns all of a users positions within a given portfolio, i.e. Portfolio "9"
|
39
|
-
position = GMoney::Position.find("9/NASDAQ:GOOG") #returns a specific position within a given portfolio
|
48
|
+
> positions = GMoney::Position.find(9) #returns all of a users positions within a given portfolio, i.e. Portfolio "9"
|
49
|
+
> position = GMoney::Position.find("9/NASDAQ:GOOG") #returns a specific position within a given portfolio
|
50
|
+
|
51
|
+
> transactions = position.transactions #returns an Array of the Transactions within a given position
|
40
52
|
|
41
|
-
|
53
|
+
Creating/Updating
|
54
|
+
Positions are created/updated via transactions
|
42
55
|
|
43
56
|
Deleting
|
44
|
-
GMoney::Position.delete '2/NASDAQ:GOOG'
|
57
|
+
> GMoney::Position.delete '2/NASDAQ:GOOG'
|
45
58
|
|
46
59
|
or
|
47
60
|
|
48
|
-
position = GMoney::Position.find '2/NASDAQ:GOOG'
|
49
|
-
position.delete #call delete on an instance of a position
|
61
|
+
> position = GMoney::Position.find '2/NASDAQ:GOOG'
|
62
|
+
> position.delete #call delete on an instance of a position
|
50
63
|
|
51
|
-
Transactions
|
52
64
|
|
65
|
+
Transactions
|
66
|
+
--------
|
53
67
|
|
54
68
|
Reading
|
55
|
-
transactions = GMoney::Transaction.find("9/NASDAQ:GOOG") #returns all of a users transactions within a given position
|
56
|
-
transaction = GMoney::Transaction.find("9/NASDAQ:GOOG/2") #returns a specific transaction within a given position
|
69
|
+
> transactions = GMoney::Transaction.find("9/NASDAQ:GOOG") #returns all of a users transactions within a given position
|
70
|
+
> transaction = GMoney::Transaction.find("9/NASDAQ:GOOG/2") #returns a specific transaction within a given position
|
71
|
+
|
72
|
+
Creating
|
73
|
+
> transaction = GMoney::Transaction.new
|
74
|
+
> transaction.portfolio = 9 #Must be a valid portfolio id
|
75
|
+
> transaction.ticker = 'nyse:c' #Must be a valid ticker symbol
|
76
|
+
> transaction.type = 'Buy' #Must be one of the following: Buy, Sell, Sell Short, Buy to Cover
|
77
|
+
> transaction.save #returns transaction object
|
78
|
+
|
79
|
+
Updating
|
80
|
+
> transaction = GMoney::Transaction.find('9/NYSE:C/1')
|
81
|
+
> transaction.shares = 50
|
82
|
+
> transaction.price = 3.50
|
83
|
+
> transaction.save #returns transaction object
|
57
84
|
|
58
85
|
Deleting
|
59
|
-
GMoney::Transaction.delete '2/NASDAQ:GOOG/1'
|
86
|
+
> GMoney::Transaction.delete '2/NASDAQ:GOOG/1'
|
60
87
|
|
61
88
|
or
|
62
89
|
|
63
|
-
transaction = GMoney::Transaction.find '2/NASDAQ:GOOG/1'
|
64
|
-
transaction.delete #call delete on an instance of a transaction
|
90
|
+
> transaction = GMoney::Transaction.find '2/NASDAQ:GOOG/1'
|
91
|
+
> transaction.delete #call delete on an instance of a transaction
|
65
92
|
|
66
|
-
|
93
|
+
----------
|
67
94
|
|
68
95
|
Options parameter
|
69
96
|
|
@@ -81,8 +108,11 @@ Options parameter
|
|
81
108
|
all take a hash as an optional last parameter. Valid values for this hash are:
|
82
109
|
|
83
110
|
* :returns - By default Google does not return a comprehensive list of returns data. Set this parameter to true to access this information.
|
84
|
-
* :refresh - GMoney caches the data returned from Google by default. Set :refresh
|
85
|
-
* :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
|
111
|
+
* :refresh - GMoney caches the data returned from Google by default. Set :refresh => true if you would like to get the latest data.
|
112
|
+
* :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
|
113
|
+
|
114
|
+
----------
|
115
|
+
There are still some rough edges. Feedback is appreciated.
|
86
116
|
|
87
117
|
== License
|
88
118
|
|
data/gmoney.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{gmoney}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.4.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-11-
|
9
|
+
s.date = %q{2009-11-17}
|
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/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"]
|
12
|
+
s.extra_rdoc_files = ["README.rdoc", "lib/extensions/fixnum.rb", "lib/extensions/nil_class.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/nil_class.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/new_portfolio_feed.xml", "spec/fixtures/new_transaction_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/fixtures/updated_portfolio_feed.xml", "spec/gmoney_spec.rb", "spec/nil_class_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
|
-
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Gmoney", "--main", "README.rdoc
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Gmoney", "--main", "README.rdoc"]
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
s.rubyforge_project = %q{gmoney}
|
18
18
|
s.rubygems_version = %q{1.3.4}
|
data/lib/extensions/string.rb
CHANGED
data/lib/gmoney.rb
CHANGED
@@ -11,6 +11,7 @@ require 'net/https'
|
|
11
11
|
require 'rexml/document'
|
12
12
|
|
13
13
|
require 'extensions/fixnum'
|
14
|
+
require 'extensions/nil_class'
|
14
15
|
require 'extensions/string'
|
15
16
|
|
16
17
|
require 'gmoney/authentication_request'
|
@@ -27,7 +28,7 @@ require 'gmoney/transaction'
|
|
27
28
|
require 'gmoney/transaction_feed_parser'
|
28
29
|
|
29
30
|
module GMoney
|
30
|
-
VERSION = '0.
|
31
|
+
VERSION = '0.4.0'
|
31
32
|
GF_URL = "https://finance.google.com/finance"
|
32
33
|
GF_FEED_URL = "#{GF_URL}/feeds/default"
|
33
34
|
GF_PORTFOLIO_FEED_URL = "#{GF_FEED_URL}/portfolios"
|
data/lib/gmoney/feed_parser.rb
CHANGED
@@ -14,9 +14,6 @@ module GMoney
|
|
14
14
|
finance_object = feed_class.new
|
15
15
|
finance_data = parsed_entry.elements["gf:#{feed_class_string}Data"]
|
16
16
|
|
17
|
-
#TODO - have someone peer review this. Is it bad practice to use instance_variable_set because
|
18
|
-
#it breaks encapsulation? (Even though it actually enhances the encapsulation in the "domain" classes
|
19
|
-
#by not allowing users to set attributes that should be read only (i.e. id, updated, return1w))
|
20
17
|
finance_object.instance_variable_set("@id", parsed_entry.elements['id'].text)
|
21
18
|
finance_object.instance_variable_set("@title", parsed_entry.elements['title'].text)
|
22
19
|
finance_object.instance_variable_set("@updated", DateTime.parse(parsed_entry.elements['updated'].text))
|
@@ -25,7 +22,6 @@ module GMoney
|
|
25
22
|
finance_data.attributes.each { |attr_name, attr_value| set_ivar.call(finance_object, attr_name, attr_value) }
|
26
23
|
parsed_entry.elements['gf:symbol'].each { |attr_name, attr_value| set_ivar.call(finance_object, attr_name, attr_value)} if options[:symbol]
|
27
24
|
|
28
|
-
#TODO - This is only going to work for USD for now. Might need to updated to make a "Money" object to store amount and currency code.
|
29
25
|
finance_data.elements.each do |cg|
|
30
26
|
finance_object.instance_variable_set("@#{cg.name.camel_to_us}", cg.elements['gd:money'].attributes['amount'].to_f)
|
31
27
|
end
|
data/lib/gmoney/portfolio.rb
CHANGED
@@ -2,6 +2,7 @@ module GMoney
|
|
2
2
|
class Portfolio
|
3
3
|
class PortfolioRequestError < StandardError;end
|
4
4
|
class PortfolioDeleteError < StandardError;end
|
5
|
+
class PortfolioSaveError < StandardError;end
|
5
6
|
|
6
7
|
attr_accessor :title, :currency_code
|
7
8
|
|
@@ -27,6 +28,10 @@ module GMoney
|
|
27
28
|
@positions.is_a?(Array) ? @positions : [@positions]
|
28
29
|
end
|
29
30
|
|
31
|
+
def save
|
32
|
+
save_portfolio
|
33
|
+
end
|
34
|
+
|
30
35
|
def self.delete(id)
|
31
36
|
delete_portfolio(id)
|
32
37
|
end
|
@@ -56,16 +61,44 @@ module GMoney
|
|
56
61
|
|
57
62
|
portfolios
|
58
63
|
end
|
64
|
+
|
65
|
+
def save_portfolio
|
66
|
+
raise PortfolioSaveError, 'Portfolios must have a title' if @title.blank?
|
67
|
+
|
68
|
+
@currency_code ||= 'USD'
|
69
|
+
|
70
|
+
#Rcov hates multi-line strings and I hate red on my test coverage report.
|
71
|
+
atom_string = "<?xml version='1.0'?><entry xmlns='http://www.w3.org/2005/Atom' xmlns:gf='http://schemas.google.com/finance/2007' xmlns:gd='http://schemas.google.com/g/2005'><title type='text'>#{title}</title> <gf:portfolioData currencyCode='#{currency_code}'/></entry>"
|
72
|
+
|
73
|
+
url = @id ? @id : GF_PORTFOLIO_FEED_URL
|
74
|
+
|
75
|
+
#Some firewalls block HTTP PUT messages. To get around this, you can include a
|
76
|
+
#X-HTTP-Method-Override: PUT header in a POST request
|
77
|
+
headers = {"Authorization" => "GoogleLogin auth=#{GFSession.auth_token}", "Content-Type" => "application/atom+xml"}
|
78
|
+
headers["X-HTTP-Method-Override"] = "PUT" if @id #if there is already an @id defined then we are updating a portfolio
|
79
|
+
|
80
|
+
request = GFRequest.new(url, :method => :post, :body => atom_string, :headers => headers)
|
81
|
+
|
82
|
+
response = GFService.send_request(request)
|
83
|
+
|
84
|
+
if response.status_code == HTTPCreated || response.status_code == HTTPOK
|
85
|
+
portfolio = PortfolioFeedParser.parse_portfolio_feed(response.body)[0]
|
86
|
+
self.instance_variable_set("@id", portfolio.id) if response.status_code == HTTPCreated
|
87
|
+
else
|
88
|
+
raise PortfolioSaveError, response.body
|
89
|
+
end
|
90
|
+
portfolio
|
91
|
+
end
|
59
92
|
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#the X-HTTP-Method-Override set to "DELETE"
|
93
|
+
#Some firewalls block HTTP DELETE messages. To get around this, you can include a
|
94
|
+
#X-HTTP-Method-Override: DELETE header in a POST request
|
63
95
|
def self.delete_portfolio(id)
|
64
96
|
url = "#{GF_PORTFOLIO_FEED_URL}/#{id}"
|
65
97
|
response = GFService.send_request(GFRequest.new(url, :method => :post, :headers => {"Authorization" => "GoogleLogin auth=#{GFSession.auth_token}", "X-HTTP-Method-Override" => "DELETE"}))
|
66
98
|
raise PortfolioDeleteError, response.body if response.status_code != HTTPOK
|
67
99
|
end
|
68
100
|
|
101
|
+
private :save_portfolio
|
69
102
|
private_class_method :retreive_portfolios, :delete_portfolio
|
70
103
|
end
|
71
104
|
end
|
data/lib/gmoney/transaction.rb
CHANGED
@@ -2,15 +2,19 @@ module GMoney
|
|
2
2
|
class Transaction
|
3
3
|
class TransactionRequestError < StandardError; end
|
4
4
|
class TransactionDeleteError < StandardError;end
|
5
|
+
class TransactionSaveError < StandardError;end
|
5
6
|
|
6
7
|
attr_reader :id, :updated, :title
|
7
|
-
|
8
|
-
attr_accessor :type, :date, :shares, :notes, :commission, :price
|
8
|
+
attr_accessor :type, :date, :shares, :notes, :commission, :price, :portfolio, :ticker, :currency_code
|
9
9
|
|
10
10
|
def self.find(id, options={})
|
11
11
|
find_by_url("#{GF_PORTFOLIO_FEED_URL}/#{id.portfolio_id}/positions/#{id.position_id}/transactions/#{id.transaction_id}", options)
|
12
12
|
end
|
13
13
|
|
14
|
+
def save
|
15
|
+
save_transaction
|
16
|
+
end
|
17
|
+
|
14
18
|
def self.delete(id)
|
15
19
|
delete_transaction(id)
|
16
20
|
end
|
@@ -34,7 +38,7 @@ module GMoney
|
|
34
38
|
return transactions[0] if transactions.size == 1
|
35
39
|
|
36
40
|
transactions
|
37
|
-
end
|
41
|
+
end
|
38
42
|
|
39
43
|
#If you are working behind some firewalls DELETE HTTP request won't work.
|
40
44
|
#To overcome this problem the google doc say to use a post request with
|
@@ -45,6 +49,53 @@ module GMoney
|
|
45
49
|
raise TransactionDeleteError, response.body if response.status_code != HTTPOK
|
46
50
|
end
|
47
51
|
|
52
|
+
def save_transaction
|
53
|
+
raise TransactionSaveError, "You must include a portfolio id, ticker symbol, and transaction type ['Buy', 'Sell', 'Buy to Cover', 'Sell Short'] in order to create a transaction." if !is_valid_transaction?
|
54
|
+
|
55
|
+
@currency_code ||= 'USD'
|
56
|
+
@date ||= Time.now.strftime("%Y-%m-%dT%H:%M:%S.000")
|
57
|
+
@shares ||= 0
|
58
|
+
|
59
|
+
atom_string = "<?xml version='1.0'?>
|
60
|
+
<entry xmlns='http://www.w3.org/2005/Atom'
|
61
|
+
xmlns:gf='http://schemas.google.com/finance/2007'
|
62
|
+
xmlns:gd='http://schemas.google.com/g/2005'>
|
63
|
+
<gf:transactionData date='#{@date}' shares='#{@shares}' type='#{@type}'>"
|
64
|
+
|
65
|
+
atom_string += "<gf:commission><gd:money amount='#{@commission}' currencyCode='#{@currency_code}'/></gf:commission>" if @commission
|
66
|
+
atom_string += "<gf:price><gd:money amount='#{@price}' currencyCode='#{@currency_code}'/></gf:price>" if @price
|
67
|
+
atom_string += "</gf:transactionData></entry>"
|
68
|
+
|
69
|
+
url = @id ? @id : "#{GF_PORTFOLIO_FEED_URL}/#{@portfolio}/positions/#{@ticker}/transactions"
|
70
|
+
|
71
|
+
#Some firewalls block HTTP PUT messages. To get around this, you can include a
|
72
|
+
#X-HTTP-Method-Override: PUT header in a POST request
|
73
|
+
headers = {"Authorization" => "GoogleLogin auth=#{GFSession.auth_token}", "Content-Type" => "application/atom+xml"}
|
74
|
+
headers["X-HTTP-Method-Override"] = "PUT" if @id #if there is already an @id defined then we are updating a transaction
|
75
|
+
|
76
|
+
request = GFRequest.new(url, :method => :post, :body => atom_string, :headers => headers)
|
77
|
+
|
78
|
+
response = GFService.send_request(request)
|
79
|
+
|
80
|
+
if response.status_code == HTTPCreated || response.status_code == HTTPOK
|
81
|
+
transaction = TransactionFeedParser.parse_transaction_feed(response.body)[0]
|
82
|
+
self.instance_variable_set("@id", transaction.id) if response.status_code == HTTPCreated
|
83
|
+
transaction.portfolio = @portfolio if response.status_code == HTTPCreated
|
84
|
+
else
|
85
|
+
raise TransactionSaveError, response.body
|
86
|
+
end
|
87
|
+
transaction
|
88
|
+
end
|
89
|
+
|
90
|
+
def is_valid_transaction?
|
91
|
+
!(@portfolio.to_s.blank? || @ticker.blank? || !is_valid_transaction_type?)
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_valid_transaction_type?
|
95
|
+
['Buy', 'Sell', 'Buy to Cover', 'Sell Short'].include?(@type)
|
96
|
+
end
|
97
|
+
|
98
|
+
private :save_transaction, :is_valid_transaction?, :is_valid_transaction_type?
|
48
99
|
private_class_method :find_by_url, :delete_transaction
|
49
100
|
end
|
50
101
|
end
|
@@ -0,0 +1,11 @@
|
|
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/38</id>
|
4
|
+
<updated>2009-11-14T21:52:46.000Z</updated>
|
5
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/finance/2007#portfolio' />
|
6
|
+
<title type='text'>New Portfolio</title>
|
7
|
+
<link rel='self' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/38' />
|
8
|
+
<link rel='edit' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/38' />
|
9
|
+
<gd:feedLink href='http://finance.google.com/finance/feeds/user@example.com/portfolios/38/positions' />
|
10
|
+
<gf:portfolioData currencyCode='USD' gainPercentage='0.0' return1w='0.0' return1y='0.0' return3m='0.0' return3y='0.0' return4w='0.0' return5y='0.0' returnOverall='0.0' returnYTD='0.0' />
|
11
|
+
</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/12</id>
|
4
|
+
<updated>2009-11-18T01:44:50.000Z</updated>
|
5
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/finance/2007#transaction' />
|
6
|
+
<title type='text'>12</title>
|
7
|
+
<link rel='self' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/9/positions/NASDAQ%3AGOOG/transactions/12' />
|
8
|
+
<link rel='edit' type='application/atom+xml' href='http://finance.google.com/finance/feeds/default/portfolios/9/positions/NASDAQ%3AGOOG/transactions/12' />
|
9
|
+
<gf:transactionData date='2009-11-17T00:00:00.000' shares='50.0' type='Buy'>
|
10
|
+
<gf:commission>
|
11
|
+
<gd:money amount='20.0' currencyCode='USD' />
|
12
|
+
</gf:commission>
|
13
|
+
<gf:price>
|
14
|
+
<gd:money amount='450.0' currencyCode='USD' />
|
15
|
+
</gf:price>
|
16
|
+
</gf:transactionData>
|
17
|
+
</entry>
|
@@ -0,0 +1,13 @@
|
|
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/38</id>
|
4
|
+
<updated>2009-11-14T22:12:15.000Z</updated>
|
5
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/finance/2007#portfolio' />
|
6
|
+
<title type='text'>Updated Title</title>
|
7
|
+
<link rel='self' type='application/atom+xml' href='http://finance.google.com/finance/feeds/user@example.com/portfolios/38' />
|
8
|
+
<link rel='edit' type='application/atom+xml' href='http://finance.google.com/finance/feeds/user@example.com/portfolios/38' />
|
9
|
+
<gd:feedLink href='http://finance.google.com/finance/feeds/user@example.com/portfolios/38/positions' />
|
10
|
+
<gf:portfolioData currencyCode='USD' gainPercentage='0.0' return1w='0.0' return1y='0.0' return3m='0.0' return3y='0.0' return4w='0.0' return5y='0.0' returnOverall='0.0' returnYTD='0.0' />
|
11
|
+
</entry>
|
12
|
+
|
13
|
+
|
data/spec/portfolio_spec.rb
CHANGED
@@ -5,7 +5,9 @@ describe GMoney::Portfolio do
|
|
5
5
|
@default_feed = File.read('spec/fixtures/default_portfolios_feed.xml')
|
6
6
|
@feed_with_returns = File.read('spec/fixtures/portfolio_feed_with_returns.xml')
|
7
7
|
@empty_feed = File.read('spec/fixtures/empty_portfolio_feed.xml')
|
8
|
-
@portfolio_9_feed = File.read('spec/fixtures/portfolio_9_feed.xml')
|
8
|
+
@portfolio_9_feed = File.read('spec/fixtures/portfolio_9_feed.xml')
|
9
|
+
@new_portfolio_feed = File.read('spec/fixtures/new_portfolio_feed.xml')
|
10
|
+
@updated_portfolio_feed = File.read('spec/fixtures/updated_portfolio_feed.xml')
|
9
11
|
end
|
10
12
|
|
11
13
|
before(:each) do
|
@@ -133,7 +135,58 @@ describe GMoney::Portfolio do
|
|
133
135
|
portfolio_delete_helper("#{@url}/asdf")
|
134
136
|
|
135
137
|
lambda { GMoney::Portfolio.delete("asdf") }.should raise_error(GMoney::Portfolio::PortfolioDeleteError, @gf_response.body)
|
136
|
-
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should save a portfolio" do
|
141
|
+
portfolio = GMoney::Portfolio.new
|
142
|
+
portfolio.title = "New Portfolio"
|
143
|
+
|
144
|
+
@gf_response.status_code = 200
|
145
|
+
@gf_response.body = @new_portfolio_feed
|
146
|
+
|
147
|
+
portfolio_save_helper(portfolio)
|
148
|
+
|
149
|
+
portfolio_return = portfolio.save
|
150
|
+
|
151
|
+
portfolio_return.id.should be_eql('http://finance.google.com/finance/feeds/user@example.com/portfolios/38')
|
152
|
+
portfolio_return.title.should be_eql("New Portfolio")
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should update a portfolio when an @id is already set" do
|
156
|
+
portfolio = GMoney::Portfolio.new
|
157
|
+
portfolio.title = "New Portfolio"
|
158
|
+
portfolio.instance_variable_set("@id", "http://finance.google.com/finance/feeds/user@example.com/portfolios/38")
|
159
|
+
|
160
|
+
@gf_response.status_code = 201
|
161
|
+
@gf_response.body = @new_portfolio_feed
|
162
|
+
|
163
|
+
portfolio_save_helper(portfolio)
|
164
|
+
|
165
|
+
portfolio.save
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should raise a PortfolioSaveError if the portfolio title is empty or nil" do
|
169
|
+
portfolio = GMoney::Portfolio.new
|
170
|
+
lambda { portfolio.save }.should raise_error(GMoney::Portfolio::PortfolioSaveError, 'Portfolios must have a title')
|
171
|
+
|
172
|
+
portfolio.title = ""
|
173
|
+
lambda { portfolio.save }.should raise_error(GMoney::Portfolio::PortfolioSaveError, 'Portfolios must have a title')
|
174
|
+
|
175
|
+
portfolio.title = " "
|
176
|
+
lambda { portfolio.save }.should raise_error(GMoney::Portfolio::PortfolioSaveError, 'Portfolios must have a title')
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should raise a PortfolioSaveError if a portfolio with the same title already exists" do
|
180
|
+
portfolio = GMoney::Portfolio.new
|
181
|
+
portfolio.title = "New Portfolio" #already exists
|
182
|
+
|
183
|
+
@gf_response.status_code = 400
|
184
|
+
@gf_response.body = 'A Portfolio with this name already exists.'
|
185
|
+
|
186
|
+
portfolio_save_helper(portfolio)
|
187
|
+
|
188
|
+
lambda { portfolio.save }.should raise_error(GMoney::Portfolio::PortfolioSaveError, 'A Portfolio with this name already exists.')
|
189
|
+
end
|
137
190
|
|
138
191
|
def portfolio_helper(url, id = nil, options = {})
|
139
192
|
GMoney::GFSession.should_receive(:auth_token).and_return('toke')
|
@@ -158,5 +211,23 @@ describe GMoney::Portfolio do
|
|
158
211
|
GMoney::GFRequest.should_receive(:new).with(url, :method => :post, :headers => {"Authorization" => "GoogleLogin auth=toke", "X-HTTP-Method-Override" => "DELETE"}).and_return(@gf_request)
|
159
212
|
|
160
213
|
GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
|
161
|
-
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def portfolio_save_helper(portfolio)
|
217
|
+
title = portfolio.title
|
218
|
+
currency_code = portfolio.currency_code ? portfolio.currency_code : 'USD'
|
219
|
+
|
220
|
+
atom_string = "<?xml version='1.0'?><entry xmlns='http://www.w3.org/2005/Atom' xmlns:gf='http://schemas.google.com/finance/2007' xmlns:gd='http://schemas.google.com/g/2005'><title type='text'>#{title}</title> <gf:portfolioData currencyCode='#{currency_code}'/></entry>"
|
221
|
+
|
222
|
+
url = portfolio.id ? portfolio.id : GMoney::GF_PORTFOLIO_FEED_URL
|
223
|
+
|
224
|
+
GMoney::GFSession.should_receive(:auth_token).and_return('toke')
|
225
|
+
|
226
|
+
headers = {"Authorization" => "GoogleLogin auth=toke", "Content-Type" => "application/atom+xml"}
|
227
|
+
headers["X-HTTP-Method-Override"] = "PUT" if portfolio.id
|
228
|
+
|
229
|
+
GMoney::GFRequest.should_receive(:new).with(url, :method => :post, :body => atom_string, :headers => headers).and_return(@gf_request)
|
230
|
+
|
231
|
+
GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
|
232
|
+
end
|
162
233
|
end
|
data/spec/string_spec.rb
CHANGED
@@ -81,4 +81,12 @@ describe String do
|
|
81
81
|
lambda {"123/NASDAQ:GOOG/2134/23".transaction_id}.should raise_error(String::TransactionParseError)
|
82
82
|
"123/NASDAQ:GOOG/23".transaction_id.should be_eql("23")
|
83
83
|
end
|
84
|
+
|
85
|
+
it "should be able to detect blank strings" do
|
86
|
+
nil.blank?.should be_true
|
87
|
+
"".blank?.should be_true
|
88
|
+
" ".blank?.should be_true
|
89
|
+
" a ".blank?.should be_false
|
90
|
+
"234".blank?.should be_false
|
91
|
+
end
|
84
92
|
end
|
data/spec/transaction_spec.rb
CHANGED
@@ -4,6 +4,7 @@ describe GMoney::Transaction do
|
|
4
4
|
before(:all) do
|
5
5
|
@goog_feed = File.read('spec/fixtures/transactions_feed_for_GOOG.xml')
|
6
6
|
@goog_feed_1 = File.read('spec/fixtures/transaction_feed_for_GOOG_1.xml')
|
7
|
+
@new_transaction_feed = File.read('spec/fixtures/new_transaction_feed.xml')
|
7
8
|
end
|
8
9
|
|
9
10
|
before(:each) do
|
@@ -85,6 +86,140 @@ describe GMoney::Transaction do
|
|
85
86
|
lambda { GMoney::Transaction.delete("9/NASDAQ:GOOG/24") }.should raise_error(GMoney::Transaction::TransactionDeleteError, @gf_response.body)
|
86
87
|
end
|
87
88
|
|
89
|
+
it "should create only valid transactions" do
|
90
|
+
trans = GMoney::Transaction.new
|
91
|
+
|
92
|
+
#Make is_valid_transaction? method public for testing purposes
|
93
|
+
def trans.public_is_valid_transaction?(*args)
|
94
|
+
is_valid_transaction?(*args)
|
95
|
+
end
|
96
|
+
|
97
|
+
trans.portfolio = 1
|
98
|
+
trans.ticker = "NYSE:GLD"
|
99
|
+
trans.type = 'Buy'
|
100
|
+
trans.public_is_valid_transaction?.should be_true
|
101
|
+
|
102
|
+
trans.type = 'Sell'
|
103
|
+
trans.public_is_valid_transaction?.should be_true
|
104
|
+
|
105
|
+
trans.type = 'Buy to Cover'
|
106
|
+
trans.public_is_valid_transaction?.should be_true
|
107
|
+
|
108
|
+
trans.type = 'Sell Short'
|
109
|
+
trans.public_is_valid_transaction?.should be_true
|
110
|
+
|
111
|
+
trans.portfolio = 1
|
112
|
+
trans.ticker = "NYSE:GLD"
|
113
|
+
trans.type = nil
|
114
|
+
trans.public_is_valid_transaction?.should be_false
|
115
|
+
|
116
|
+
trans.portfolio = 1
|
117
|
+
trans.ticker = "NYSE:GLD"
|
118
|
+
trans.type = 'buy'
|
119
|
+
trans.public_is_valid_transaction?.should be_false
|
120
|
+
|
121
|
+
trans.portfolio = 1
|
122
|
+
trans.ticker = " "
|
123
|
+
trans.type = 'Buy'
|
124
|
+
trans.public_is_valid_transaction?.should be_false
|
125
|
+
|
126
|
+
trans.portfolio = " "
|
127
|
+
trans.ticker = "NYSE:GLD"
|
128
|
+
trans.type = 'Buy'
|
129
|
+
trans.public_is_valid_transaction?.should be_false
|
130
|
+
|
131
|
+
trans.portfolio = nil
|
132
|
+
trans.ticker = nil
|
133
|
+
trans.type = nil
|
134
|
+
trans.public_is_valid_transaction?.should be_false
|
135
|
+
|
136
|
+
trans.portfolio = nil
|
137
|
+
trans.ticker = "NYSE:GLD"
|
138
|
+
trans.type = 'Buy'
|
139
|
+
trans.public_is_valid_transaction?.should be_false
|
140
|
+
|
141
|
+
trans.portfolio = "1"
|
142
|
+
trans.ticker = nil
|
143
|
+
trans.type = 'Buy'
|
144
|
+
trans.public_is_valid_transaction?.should be_false
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should save a transaction" do
|
148
|
+
transaction = GMoney::Transaction.new
|
149
|
+
transaction.portfolio = 9
|
150
|
+
transaction.ticker = 'NASDAQ:GOOG'
|
151
|
+
transaction.type = 'Buy'
|
152
|
+
transaction.shares = 50
|
153
|
+
transaction.price = 450.0
|
154
|
+
transaction.commission = 20.0
|
155
|
+
transaction.date = '2009-11-17T00:00:00.000'
|
156
|
+
|
157
|
+
@gf_response.status_code = 200
|
158
|
+
@gf_response.body = @new_transaction_feed
|
159
|
+
|
160
|
+
transaction_save_helper(transaction)
|
161
|
+
|
162
|
+
transaction_return = transaction.save
|
163
|
+
|
164
|
+
transaction_return.id.should be_eql('http://finance.google.com/finance/feeds/user@example.com/portfolios/9/positions/NASDAQ:GOOG/transactions/12')
|
165
|
+
transaction_return.commission.should be_eql(20.0)
|
166
|
+
transaction_return.price.should be_eql(450.0)
|
167
|
+
transaction_return.type.should be_eql('Buy')
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should update a transaction when an @id is already set" do
|
171
|
+
transaction = GMoney::Transaction.new
|
172
|
+
transaction.portfolio = 9
|
173
|
+
transaction.ticker = 'NASDAQ:GOOG'
|
174
|
+
transaction.type = 'Buy'
|
175
|
+
transaction.shares = 50
|
176
|
+
transaction.price = 450.0
|
177
|
+
transaction.commission = 20.0
|
178
|
+
transaction.date = '2009-11-17T00:00:00.000'
|
179
|
+
transaction.instance_variable_set("@id", "http://finance.google.com/finance/feeds/user@example.com/portfolios/9/positions/NASDAQ:GOOG/transactions/12")
|
180
|
+
|
181
|
+
@gf_response.status_code = 201
|
182
|
+
@gf_response.body = @new_transaction_feed
|
183
|
+
|
184
|
+
transaction_save_helper(transaction)
|
185
|
+
|
186
|
+
transaction.save
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should raise a TransactionSaveError if the transaction type, ticker, or portfolio are not set" do
|
190
|
+
transaction = GMoney::Transaction.new
|
191
|
+
|
192
|
+
lambda { transaction.save }.should raise_error(GMoney::Transaction::TransactionSaveError, "You must include a portfolio id, ticker symbol, and transaction type ['Buy', 'Sell', 'Buy to Cover', 'Sell Short'] in order to create a transaction.")
|
193
|
+
|
194
|
+
transaction.portfolio = 9
|
195
|
+
transaction.type = 'Buy'
|
196
|
+
lambda { transaction.save }.should raise_error(GMoney::Transaction::TransactionSaveError, "You must include a portfolio id, ticker symbol, and transaction type ['Buy', 'Sell', 'Buy to Cover', 'Sell Short'] in order to create a transaction.")
|
197
|
+
|
198
|
+
transaction.portfolio = nil
|
199
|
+
transaction.ticker = 'NASDAQ:GOOG'
|
200
|
+
transaction.type = 'Buy'
|
201
|
+
lambda { transaction.save }.should raise_error(GMoney::Transaction::TransactionSaveError, "You must include a portfolio id, ticker symbol, and transaction type ['Buy', 'Sell', 'Buy to Cover', 'Sell Short'] in order to create a transaction.")
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should give you a warning from Google if your transaction attributes are bad" do
|
205
|
+
transaction = GMoney::Transaction.new
|
206
|
+
transaction.portfolio = 9
|
207
|
+
transaction.ticker = 'asdfasd:asdfs' #invalid ticker
|
208
|
+
transaction.type = 'Buy'
|
209
|
+
transaction.shares = 50
|
210
|
+
transaction.price = 450.0
|
211
|
+
transaction.commission = 20.0
|
212
|
+
transaction.date = '2009-11-17T00:00:00.000'
|
213
|
+
|
214
|
+
@gf_response.status_code = 400
|
215
|
+
@gf_response.body = 'Some of the values submitted are not valid and have been ignored.'
|
216
|
+
|
217
|
+
transaction_save_helper(transaction)
|
218
|
+
|
219
|
+
lambda { transaction.save }.should raise_error(GMoney::Transaction::TransactionSaveError, "Some of the values submitted are not valid and have been ignored.")
|
220
|
+
|
221
|
+
end
|
222
|
+
|
88
223
|
def transaction_helper(id, options={})
|
89
224
|
GMoney::GFSession.should_receive(:auth_token).and_return('toke')
|
90
225
|
|
@@ -104,4 +239,29 @@ describe GMoney::Transaction do
|
|
104
239
|
|
105
240
|
GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
|
106
241
|
end
|
242
|
+
|
243
|
+
def transaction_save_helper(transaction)
|
244
|
+
currency_code = transaction.currency_code ? transaction.currency_code : 'USD'
|
245
|
+
|
246
|
+
atom_string = "<?xml version='1.0'?>
|
247
|
+
<entry xmlns='http://www.w3.org/2005/Atom'
|
248
|
+
xmlns:gf='http://schemas.google.com/finance/2007'
|
249
|
+
xmlns:gd='http://schemas.google.com/g/2005'>
|
250
|
+
<gf:transactionData date='#{transaction.date}' shares='#{transaction.shares}' type='#{transaction.type}'>"
|
251
|
+
|
252
|
+
atom_string += "<gf:commission><gd:money amount='#{transaction.commission}' currencyCode='#{currency_code}'/></gf:commission>" if transaction.commission
|
253
|
+
atom_string += "<gf:price><gd:money amount='#{transaction.price}' currencyCode='#{currency_code}'/></gf:price>" if transaction.price
|
254
|
+
atom_string += "</gf:transactionData></entry>"
|
255
|
+
|
256
|
+
url = transaction.id ? transaction.id : "#{GMoney::GF_PORTFOLIO_FEED_URL}/#{transaction.portfolio}/positions/#{transaction.ticker}/transactions"
|
257
|
+
|
258
|
+
GMoney::GFSession.should_receive(:auth_token).and_return('toke')
|
259
|
+
|
260
|
+
headers = {"Authorization" => "GoogleLogin auth=toke", "Content-Type" => "application/atom+xml"}
|
261
|
+
headers["X-HTTP-Method-Override"] = "PUT" if transaction.id
|
262
|
+
|
263
|
+
GMoney::GFRequest.should_receive(:new).with(url, :method => :post, :body => atom_string, :headers => headers).and_return(@gf_request)
|
264
|
+
|
265
|
+
GMoney::GFService.should_receive(:send_request).with(@gf_request).and_return(@gf_response)
|
266
|
+
end
|
107
267
|
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.
|
4
|
+
version: 0.4.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-11-
|
12
|
+
date: 2009-11-17 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -22,6 +22,7 @@ extensions: []
|
|
22
22
|
extra_rdoc_files:
|
23
23
|
- README.rdoc
|
24
24
|
- lib/extensions/fixnum.rb
|
25
|
+
- lib/extensions/nil_class.rb
|
25
26
|
- lib/extensions/string.rb
|
26
27
|
- lib/gmoney.rb
|
27
28
|
- lib/gmoney/authentication_request.rb
|
@@ -42,6 +43,7 @@ files:
|
|
42
43
|
- Rakefile
|
43
44
|
- gmoney.gemspec
|
44
45
|
- lib/extensions/fixnum.rb
|
46
|
+
- lib/extensions/nil_class.rb
|
45
47
|
- lib/extensions/string.rb
|
46
48
|
- lib/gmoney.rb
|
47
49
|
- lib/gmoney/authentication_request.rb
|
@@ -60,6 +62,8 @@ files:
|
|
60
62
|
- spec/fixtures/cacert.pem
|
61
63
|
- spec/fixtures/default_portfolios_feed.xml
|
62
64
|
- spec/fixtures/empty_portfolio_feed.xml
|
65
|
+
- spec/fixtures/new_portfolio_feed.xml
|
66
|
+
- spec/fixtures/new_transaction_feed.xml
|
63
67
|
- spec/fixtures/portfolio_9_feed.xml
|
64
68
|
- spec/fixtures/portfolio_feed_with_returns.xml
|
65
69
|
- spec/fixtures/position_feed_for_9_GOOG.xml
|
@@ -68,7 +72,9 @@ files:
|
|
68
72
|
- spec/fixtures/positions_feed_for_portfolio_9r.xml
|
69
73
|
- spec/fixtures/transaction_feed_for_GOOG_1.xml
|
70
74
|
- spec/fixtures/transactions_feed_for_GOOG.xml
|
75
|
+
- spec/fixtures/updated_portfolio_feed.xml
|
71
76
|
- spec/gmoney_spec.rb
|
77
|
+
- spec/nil_class_spec.rb
|
72
78
|
- spec/portfolio_feed_parser_spec.rb
|
73
79
|
- spec/portfolio_spec.rb
|
74
80
|
- spec/position_feed_parser_spec.rb
|
@@ -93,7 +99,7 @@ rdoc_options:
|
|
93
99
|
- --title
|
94
100
|
- Gmoney
|
95
101
|
- --main
|
96
|
-
- README.rdoc
|
102
|
+
- README.rdoc
|
97
103
|
require_paths:
|
98
104
|
- lib
|
99
105
|
required_ruby_version: !ruby/object:Gem::Requirement
|