Permalink
Browse files

Basic Buxfer updating code in place, with a rake task that can be run…

… nightly
  • Loading branch information...
1 parent aac9f14 commit 7c7875eb5fdaf9ddf6769bc9d7b5e23b2a0e5715 @konklone konklone committed Feb 28, 2010
Showing with 144 additions and 7 deletions.
  1. +1 −0 .gitignore
  2. +18 −7 Rakefile
  3. +8 −0 models.rb
  4. +44 −0 updater/buxfer.rb
  5. +16 −0 updater/buxfer.yml.example
  6. +57 −0 updater/updater.rb
View
@@ -1,2 +1,3 @@
/config/database.yml
/*.sqlite3
+/updater/buxfer.yml
View
@@ -1,12 +1,13 @@
-desc 'Migrate the database'
-task :migrate => :environment do
- ActiveRecord::Base.logger = Logger.new STDOUT
- ActiveRecord::Migrator.migrate 'migrations', (ENV['version'] ? ENV['version'].to_i : nil)
+desc "Update all accounts"
+task :update => :environment do
+ require 'updater/updater'
+
+ User.all.each do |user|
+ Updater.new(user).update_accounts!
+ end
end
-def all_models
- [User, Account, Day]
-end
+
namespace :fixtures do
@@ -24,6 +25,16 @@ namespace :fixtures do
end
+desc 'Migrate the database'
+task :migrate => :environment do
+ ActiveRecord::Base.logger = Logger.new STDOUT
+ ActiveRecord::Migrator.migrate 'migrations', (ENV['version'] ? ENV['version'].to_i : nil)
+end
+
+def all_models
+ [User, Account, Day]
+end
+
desc 'Loads environment'
task :environment do
require 'ohnomymoney'
View
@@ -19,6 +19,14 @@ class Account < ActiveRecord::Base
named_scope :assets, :conditions => {:account_type => 'assets'}
named_scope :debts, :conditions => {:account_type => 'debts'}
+ def assets?
+ account_type == 'credit'
+ end
+
+ def debts?
+ account_type == 'debts'
+ end
+
def needs_name
account_type != 'worth'
end
View
@@ -0,0 +1,44 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'json'
+require 'net/http'
+require 'net/https'
+require 'uri'
+
+class Buxfer
+ attr_accessor :token
+
+ def initialize(email, password)
+ self.token = token_for email, password
+ end
+
+ def get(command, query_string)
+ http = Net::HTTP.new 'www.buxfer.com', 443
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+
+ resp, data = http.get "/api/#{command}.json?#{query_string}", nil
+
+ result = JSON.parse data
+ response = result['response']
+
+ if response['status'] != 'OK'
+ puts "Error: #{response['status'].gsub(/ERROR: /, '')}"
+ exit
+ end
+
+ response
+ end
+
+ def token_for(email, password)
+ response = get 'login', "userid=#{email}&password=#{password}"
+ response ? response['token'] : nil
+ end
+
+ def accounts
+ response = get 'accounts', "token=#{token}"
+ response ? response['accounts'] : nil
+ end
+
+end
View
@@ -0,0 +1,16 @@
+:credentials:
+ :email:
+ :password:
+
+
+# local_id is the local database ID of an account
+# remote_id is Buxfer's ID for that account
+:accounts:
+ - :local_id:
+ :remote_id:
+ - :local_id:
+ :remote_id:
+ - :local_id:
+ :remote_id:
+ - :local_id:
+ :remote_id:
View
@@ -0,0 +1,57 @@
+# needs the ohnomymoney environment loaded beforehand
+require File.join(File.dirname(__FILE__), 'buxfer')
+
+class Updater
+ attr_accessor :user
+ attr_accessor :buxfer
+ attr_accessor :maps
+ attr_accessor :manual
+
+ def initialize(user)
+ buxfer = YAML.load_file File.join(File.dirname(__FILE__), 'buxfer.yml')
+
+ self.user = user
+ self.buxfer = Buxfer.new buxfer[:credentials][:email], buxfer[:credentials][:password]
+ self.maps = buxfer[:accounts]
+ self.manual = buxfer[:manual]
+ end
+
+ def update_accounts!
+ worth = 0
+
+ # the accounts in Buxfer
+ buxfer.accounts.map do |remote|
+ map = maps.find {|m| m[:remote_id] == remote['id']}
+ if map
+ account = user.accounts.find map[:local_id]
+ balance = (remote['balance'] * 100).to_i # convert to pennies
+ balance *= -1 if account.debts?
+
+ worth += balance
+ update_account! account, balance
+ end
+ end
+
+ # the manual accounts that Buxfer doesn't support
+ manual.each do |map|
+ account = user.accounts.find map[:local_id]
+ balance = map[:balance]
+
+ worth += balance
+ update_account! account, map[:balance]
+ end
+
+ # the net worth of it all
+ account = user.accounts.worth.first
+ update_account! account, worth
+ end
+
+ # idempotent - will overwrite balance if one exists for today
+ def update_account!(account, balance)
+ day = account.days.find_or_initialize_by_date_of Time.now.to_date
+ day.user_id = user.id
+ day.balance = balance
+ day.save!
+ puts "Updated #{account.name} with balance of #{balance}, for #{day.date_of}."
+ end
+end

0 comments on commit 7c7875e

Please sign in to comment.