winton-sum 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/Capfile +3 -0
- data/MIT-LICENSE +18 -0
- data/README.markdown +55 -0
- data/Rakefile +45 -0
- data/config/database.example.yml +10 -0
- data/config/deploy.example.rb +36 -0
- data/config/externals.yml +18 -0
- data/config/mail.example.yml +32 -0
- data/config/schedule.rb +20 -0
- data/config.ru +3 -0
- data/db/migrate/001_users.rb +22 -0
- data/db/migrate/002_user_emails.rb +15 -0
- data/features/front_page.feature +14 -0
- data/features/read_email.feature +446 -0
- data/features/receive_email.feature +59 -0
- data/features/step_definitions/email.rb +52 -0
- data/features/step_definitions/given.rb +60 -0
- data/features/step_definitions/then.rb +32 -0
- data/features/step_definitions/when.rb +41 -0
- data/features/support/env.rb +27 -0
- data/features/support/helpers.rb +44 -0
- data/features/support/rspec.rb +11 -0
- data/features/support/webrat.rb +3 -0
- data/features/update_user.feature +76 -0
- data/gemspec.rb +45 -0
- data/lib/sum/boot.rb +29 -0
- data/lib/sum/controller/cron.rb +12 -0
- data/lib/sum/controller/front.rb +6 -0
- data/lib/sum/controller/new.rb +15 -0
- data/lib/sum/helper/application.rb +8 -0
- data/lib/sum/helper/cron.rb +12 -0
- data/lib/sum/helper/new.rb +26 -0
- data/lib/sum/model/incoming_mail.rb +89 -0
- data/lib/sum/model/user.rb +286 -0
- data/lib/sum/model/user_email.rb +31 -0
- data/lib/sum/view/email.haml +70 -0
- data/lib/sum/view/field.haml +10 -0
- data/lib/sum/view/form.haml +12 -0
- data/lib/sum/view/front.haml +25 -0
- data/lib/sum/view/layout.haml +26 -0
- data/lib/sum/view/new.haml +15 -0
- data/lib/sum.rb +30 -0
- data/public/image/1.png +0 -0
- data/public/image/2.png +0 -0
- data/public/image/3.png +0 -0
- data/public/image/bg.png +0 -0
- data/public/image/bot.png +0 -0
- data/public/image/facebox/b.png +0 -0
- data/public/image/facebox/bl.png +0 -0
- data/public/image/facebox/br.png +0 -0
- data/public/image/facebox/closelabel.gif +0 -0
- data/public/image/facebox/loading.gif +0 -0
- data/public/image/facebox/tl.png +0 -0
- data/public/image/facebox/tr.png +0 -0
- data/public/image/favicon.png +0 -0
- data/public/image/field_off.png +0 -0
- data/public/image/field_on.png +0 -0
- data/public/image/ribbon.png +0 -0
- data/public/image/screenshot_receive.png +0 -0
- data/public/image/screenshot_send.png +0 -0
- data/public/image/submit.png +0 -0
- data/public/image/top.png +0 -0
- data/public/javascript/facebox.js +319 -0
- data/public/javascript/jquery-1.3.2.js +19 -0
- data/public/javascript/sum.js +9 -0
- data/public/style/facebox.css +95 -0
- data/public/style/ie.css +35 -0
- data/public/style/print.css +30 -0
- data/public/style/screen.css +256 -0
- data/public/style/sum.css +22 -0
- data/public/style/sum.less +135 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/sum/model/incoming_mail_spec.rb +106 -0
- data/spec/sum/model/user_spec.rb +171 -0
- data/sum.gemspec +69 -0
- data/vendor/webrat/History.txt +358 -0
- data/vendor/webrat/MIT-LICENSE.txt +19 -0
- data/vendor/webrat/README.rdoc +85 -0
- data/vendor/webrat/Rakefile +200 -0
- data/vendor/webrat/install.rb +1 -0
- data/vendor/webrat/lib/webrat/core/configuration.rb +102 -0
- data/vendor/webrat/lib/webrat/core/elements/area.rb +31 -0
- data/vendor/webrat/lib/webrat/core/elements/element.rb +33 -0
- data/vendor/webrat/lib/webrat/core/elements/field.rb +409 -0
- data/vendor/webrat/lib/webrat/core/elements/form.rb +103 -0
- data/vendor/webrat/lib/webrat/core/elements/label.rb +31 -0
- data/vendor/webrat/lib/webrat/core/elements/link.rb +93 -0
- data/vendor/webrat/lib/webrat/core/elements/select_option.rb +35 -0
- data/vendor/webrat/lib/webrat/core/locators/area_locator.rb +38 -0
- data/vendor/webrat/lib/webrat/core/locators/button_locator.rb +54 -0
- data/vendor/webrat/lib/webrat/core/locators/field_by_id_locator.rb +37 -0
- data/vendor/webrat/lib/webrat/core/locators/field_labeled_locator.rb +56 -0
- data/vendor/webrat/lib/webrat/core/locators/field_locator.rb +25 -0
- data/vendor/webrat/lib/webrat/core/locators/field_named_locator.rb +41 -0
- data/vendor/webrat/lib/webrat/core/locators/form_locator.rb +19 -0
- data/vendor/webrat/lib/webrat/core/locators/label_locator.rb +34 -0
- data/vendor/webrat/lib/webrat/core/locators/link_locator.rb +74 -0
- data/vendor/webrat/lib/webrat/core/locators/locator.rb +20 -0
- data/vendor/webrat/lib/webrat/core/locators/select_option_locator.rb +59 -0
- data/vendor/webrat/lib/webrat/core/locators.rb +20 -0
- data/vendor/webrat/lib/webrat/core/logging.rb +24 -0
- data/vendor/webrat/lib/webrat/core/matchers/have_content.rb +73 -0
- data/vendor/webrat/lib/webrat/core/matchers/have_selector.rb +74 -0
- data/vendor/webrat/lib/webrat/core/matchers/have_tag.rb +21 -0
- data/vendor/webrat/lib/webrat/core/matchers/have_xpath.rb +147 -0
- data/vendor/webrat/lib/webrat/core/matchers.rb +4 -0
- data/vendor/webrat/lib/webrat/core/methods.rb +63 -0
- data/vendor/webrat/lib/webrat/core/mime.rb +29 -0
- data/vendor/webrat/lib/webrat/core/save_and_open_page.rb +48 -0
- data/vendor/webrat/lib/webrat/core/scope.rb +350 -0
- data/vendor/webrat/lib/webrat/core/session.rb +299 -0
- data/vendor/webrat/lib/webrat/core/xml/hpricot.rb +19 -0
- data/vendor/webrat/lib/webrat/core/xml/nokogiri.rb +76 -0
- data/vendor/webrat/lib/webrat/core/xml/rexml.rb +24 -0
- data/vendor/webrat/lib/webrat/core/xml.rb +115 -0
- data/vendor/webrat/lib/webrat/core.rb +14 -0
- data/vendor/webrat/lib/webrat/core_extensions/blank.rb +58 -0
- data/vendor/webrat/lib/webrat/core_extensions/deprecate.rb +8 -0
- data/vendor/webrat/lib/webrat/core_extensions/detect_mapped.rb +12 -0
- data/vendor/webrat/lib/webrat/core_extensions/meta_class.rb +6 -0
- data/vendor/webrat/lib/webrat/core_extensions/nil_to_param.rb +5 -0
- data/vendor/webrat/lib/webrat/core_extensions/tcp_socket.rb +27 -0
- data/vendor/webrat/lib/webrat/mechanize.rb +74 -0
- data/vendor/webrat/lib/webrat/merb.rb +9 -0
- data/vendor/webrat/lib/webrat/merb_multipart_support.rb +27 -0
- data/vendor/webrat/lib/webrat/merb_session.rb +82 -0
- data/vendor/webrat/lib/webrat/rack.rb +25 -0
- data/vendor/webrat/lib/webrat/rails.rb +106 -0
- data/vendor/webrat/lib/webrat/rspec-rails.rb +10 -0
- data/vendor/webrat/lib/webrat/selenium/application_server_factory.rb +40 -0
- data/vendor/webrat/lib/webrat/selenium/application_servers/base.rb +46 -0
- data/vendor/webrat/lib/webrat/selenium/application_servers/external.rb +26 -0
- data/vendor/webrat/lib/webrat/selenium/application_servers/merb.rb +50 -0
- data/vendor/webrat/lib/webrat/selenium/application_servers/rails.rb +44 -0
- data/vendor/webrat/lib/webrat/selenium/application_servers/sinatra.rb +37 -0
- data/vendor/webrat/lib/webrat/selenium/application_servers.rb +5 -0
- data/vendor/webrat/lib/webrat/selenium/location_strategy_javascript/button.js +19 -0
- data/vendor/webrat/lib/webrat/selenium/location_strategy_javascript/label.js +16 -0
- data/vendor/webrat/lib/webrat/selenium/location_strategy_javascript/webrat.js +5 -0
- data/vendor/webrat/lib/webrat/selenium/location_strategy_javascript/webratlink.js +12 -0
- data/vendor/webrat/lib/webrat/selenium/location_strategy_javascript/webratlinkwithin.js +15 -0
- data/vendor/webrat/lib/webrat/selenium/location_strategy_javascript/webratselectwithoption.js +5 -0
- data/vendor/webrat/lib/webrat/selenium/matchers/have_content.rb +66 -0
- data/vendor/webrat/lib/webrat/selenium/matchers/have_selector.rb +49 -0
- data/vendor/webrat/lib/webrat/selenium/matchers/have_tag.rb +72 -0
- data/vendor/webrat/lib/webrat/selenium/matchers/have_xpath.rb +45 -0
- data/vendor/webrat/lib/webrat/selenium/matchers.rb +4 -0
- data/vendor/webrat/lib/webrat/selenium/selenium_extensions.js +6 -0
- data/vendor/webrat/lib/webrat/selenium/selenium_rc_server.rb +84 -0
- data/vendor/webrat/lib/webrat/selenium/selenium_session.rb +248 -0
- data/vendor/webrat/lib/webrat/selenium/silence_stream.rb +18 -0
- data/vendor/webrat/lib/webrat/selenium.rb +81 -0
- data/vendor/webrat/lib/webrat/sinatra.rb +11 -0
- data/vendor/webrat/lib/webrat.rb +31 -0
- data/vendor/webrat/spec/fakes/test_session.rb +34 -0
- data/vendor/webrat/spec/integration/merb/Rakefile +35 -0
- data/vendor/webrat/spec/integration/merb/app/controllers/application.rb +2 -0
- data/vendor/webrat/spec/integration/merb/app/controllers/exceptions.rb +13 -0
- data/vendor/webrat/spec/integration/merb/app/controllers/testing.rb +27 -0
- data/vendor/webrat/spec/integration/merb/app/views/exceptions/not_acceptable.html.erb +63 -0
- data/vendor/webrat/spec/integration/merb/app/views/exceptions/not_found.html.erb +47 -0
- data/vendor/webrat/spec/integration/merb/app/views/layout/application.html.erb +12 -0
- data/vendor/webrat/spec/integration/merb/app/views/testing/show_form.html.erb +27 -0
- data/vendor/webrat/spec/integration/merb/app/views/testing/upload.html.erb +9 -0
- data/vendor/webrat/spec/integration/merb/config/environments/development.rb +15 -0
- data/vendor/webrat/spec/integration/merb/config/environments/rake.rb +11 -0
- data/vendor/webrat/spec/integration/merb/config/environments/test.rb +14 -0
- data/vendor/webrat/spec/integration/merb/config/init.rb +25 -0
- data/vendor/webrat/spec/integration/merb/config/rack.rb +11 -0
- data/vendor/webrat/spec/integration/merb/config/router.rb +34 -0
- data/vendor/webrat/spec/integration/merb/spec/spec.opts +1 -0
- data/vendor/webrat/spec/integration/merb/spec/spec_helper.rb +24 -0
- data/vendor/webrat/spec/integration/merb/spec/webrat_spec.rb +39 -0
- data/vendor/webrat/spec/integration/merb/tasks/merb.thor/app_script.rb +31 -0
- data/vendor/webrat/spec/integration/merb/tasks/merb.thor/common.rb +64 -0
- data/vendor/webrat/spec/integration/merb/tasks/merb.thor/gem_ext.rb +124 -0
- data/vendor/webrat/spec/integration/merb/tasks/merb.thor/main.thor +150 -0
- data/vendor/webrat/spec/integration/merb/tasks/merb.thor/ops.rb +93 -0
- data/vendor/webrat/spec/integration/merb/tasks/merb.thor/utils.rb +40 -0
- data/vendor/webrat/spec/integration/rack/Rakefile +5 -0
- data/vendor/webrat/spec/integration/rack/app.rb +73 -0
- data/vendor/webrat/spec/integration/rack/test/helper.rb +20 -0
- data/vendor/webrat/spec/integration/rack/test/webrat_rack_test.rb +62 -0
- data/vendor/webrat/spec/integration/rails/Rakefile +30 -0
- data/vendor/webrat/spec/integration/rails/app/controllers/application.rb +15 -0
- data/vendor/webrat/spec/integration/rails/app/controllers/buttons_controller.rb +7 -0
- data/vendor/webrat/spec/integration/rails/app/controllers/fields_controller.rb +4 -0
- data/vendor/webrat/spec/integration/rails/app/controllers/links_controller.rb +7 -0
- data/vendor/webrat/spec/integration/rails/app/controllers/webrat_controller.rb +43 -0
- data/vendor/webrat/spec/integration/rails/app/helpers/buttons_helper.rb +2 -0
- data/vendor/webrat/spec/integration/rails/app/helpers/fields_helper.rb +2 -0
- data/vendor/webrat/spec/integration/rails/app/helpers/links_helper.rb +2 -0
- data/vendor/webrat/spec/integration/rails/app/views/buttons/show.html.erb +11 -0
- data/vendor/webrat/spec/integration/rails/app/views/fields/show.html.erb +9 -0
- data/vendor/webrat/spec/integration/rails/app/views/links/show.html.erb +5 -0
- data/vendor/webrat/spec/integration/rails/app/views/webrat/before_redirect_form.html.erb +4 -0
- data/vendor/webrat/spec/integration/rails/app/views/webrat/buttons.html.erb +11 -0
- data/vendor/webrat/spec/integration/rails/app/views/webrat/form.html.erb +28 -0
- data/vendor/webrat/spec/integration/rails/config/boot.rb +109 -0
- data/vendor/webrat/spec/integration/rails/config/environment.rb +12 -0
- data/vendor/webrat/spec/integration/rails/config/environments/development.rb +17 -0
- data/vendor/webrat/spec/integration/rails/config/environments/selenium.rb +22 -0
- data/vendor/webrat/spec/integration/rails/config/environments/test.rb +22 -0
- data/vendor/webrat/spec/integration/rails/config/initializers/inflections.rb +10 -0
- data/vendor/webrat/spec/integration/rails/config/initializers/mime_types.rb +5 -0
- data/vendor/webrat/spec/integration/rails/config/initializers/new_rails_defaults.rb +17 -0
- data/vendor/webrat/spec/integration/rails/config/locales/en.yml +5 -0
- data/vendor/webrat/spec/integration/rails/config/routes.rb +18 -0
- data/vendor/webrat/spec/integration/rails/public/404.html +30 -0
- data/vendor/webrat/spec/integration/rails/public/422.html +30 -0
- data/vendor/webrat/spec/integration/rails/public/500.html +33 -0
- data/vendor/webrat/spec/integration/rails/script/about +4 -0
- data/vendor/webrat/spec/integration/rails/script/console +3 -0
- data/vendor/webrat/spec/integration/rails/script/dbconsole +3 -0
- data/vendor/webrat/spec/integration/rails/script/destroy +3 -0
- data/vendor/webrat/spec/integration/rails/script/generate +3 -0
- data/vendor/webrat/spec/integration/rails/script/performance/benchmarker +3 -0
- data/vendor/webrat/spec/integration/rails/script/performance/profiler +3 -0
- data/vendor/webrat/spec/integration/rails/script/performance/request +3 -0
- data/vendor/webrat/spec/integration/rails/script/plugin +3 -0
- data/vendor/webrat/spec/integration/rails/script/process/inspector +3 -0
- data/vendor/webrat/spec/integration/rails/script/process/reaper +3 -0
- data/vendor/webrat/spec/integration/rails/script/process/spawner +3 -0
- data/vendor/webrat/spec/integration/rails/script/runner +3 -0
- data/vendor/webrat/spec/integration/rails/script/server +3 -0
- data/vendor/webrat/spec/integration/rails/test/integration/button_click_test.rb +80 -0
- data/vendor/webrat/spec/integration/rails/test/integration/fill_in_test.rb +24 -0
- data/vendor/webrat/spec/integration/rails/test/integration/link_click_test.rb +27 -0
- data/vendor/webrat/spec/integration/rails/test/integration/webrat_test.rb +97 -0
- data/vendor/webrat/spec/integration/rails/test/test_helper.rb +25 -0
- data/vendor/webrat/spec/integration/sinatra/Rakefile +5 -0
- data/vendor/webrat/spec/integration/sinatra/classic_app.rb +64 -0
- data/vendor/webrat/spec/integration/sinatra/modular_app.rb +16 -0
- data/vendor/webrat/spec/integration/sinatra/test/classic_app_test.rb +37 -0
- data/vendor/webrat/spec/integration/sinatra/test/modular_app_test.rb +18 -0
- data/vendor/webrat/spec/integration/sinatra/test/test_helper.rb +16 -0
- data/vendor/webrat/spec/private/core/configuration_spec.rb +116 -0
- data/vendor/webrat/spec/private/core/field_spec.rb +85 -0
- data/vendor/webrat/spec/private/core/link_spec.rb +24 -0
- data/vendor/webrat/spec/private/core/logging_spec.rb +10 -0
- data/vendor/webrat/spec/private/core/session_spec.rb +195 -0
- data/vendor/webrat/spec/private/mechanize/mechanize_session_spec.rb +81 -0
- data/vendor/webrat/spec/private/merb/attaches_file_spec.rb +93 -0
- data/vendor/webrat/spec/private/merb/merb_session_spec.rb +61 -0
- data/vendor/webrat/spec/private/nokogiri_spec.rb +77 -0
- data/vendor/webrat/spec/private/rails/attaches_file_spec.rb +81 -0
- data/vendor/webrat/spec/private/rails/rails_session_spec.rb +112 -0
- data/vendor/webrat/spec/private/selenium/application_servers/rails_spec.rb +26 -0
- data/vendor/webrat/spec/public/basic_auth_spec.rb +24 -0
- data/vendor/webrat/spec/public/check_spec.rb +191 -0
- data/vendor/webrat/spec/public/choose_spec.rb +118 -0
- data/vendor/webrat/spec/public/click_area_spec.rb +106 -0
- data/vendor/webrat/spec/public/click_button_spec.rb +496 -0
- data/vendor/webrat/spec/public/click_link_spec.rb +511 -0
- data/vendor/webrat/spec/public/fill_in_spec.rb +209 -0
- data/vendor/webrat/spec/public/locators/field_by_xpath_spec.rb +19 -0
- data/vendor/webrat/spec/public/locators/field_labeled_spec.rb +172 -0
- data/vendor/webrat/spec/public/locators/field_with_id_spec.rb +16 -0
- data/vendor/webrat/spec/public/matchers/contain_spec.rb +114 -0
- data/vendor/webrat/spec/public/matchers/have_selector_spec.rb +142 -0
- data/vendor/webrat/spec/public/matchers/have_tag_spec.rb +39 -0
- data/vendor/webrat/spec/public/matchers/have_xpath_spec.rb +136 -0
- data/vendor/webrat/spec/public/reload_spec.rb +10 -0
- data/vendor/webrat/spec/public/save_and_open_spec.rb +70 -0
- data/vendor/webrat/spec/public/select_date_spec.rb +112 -0
- data/vendor/webrat/spec/public/select_datetime_spec.rb +137 -0
- data/vendor/webrat/spec/public/select_spec.rb +249 -0
- data/vendor/webrat/spec/public/select_time_spec.rb +100 -0
- data/vendor/webrat/spec/public/selenium/application_server_factory_spec.rb +49 -0
- data/vendor/webrat/spec/public/selenium/application_servers/external_spec.rb +12 -0
- data/vendor/webrat/spec/public/selenium/selenium_session_spec.rb +37 -0
- data/vendor/webrat/spec/public/set_hidden_field_spec.rb +5 -0
- data/vendor/webrat/spec/public/submit_form_spec.rb +5 -0
- data/vendor/webrat/spec/public/visit_spec.rb +58 -0
- data/vendor/webrat/spec/public/within_spec.rb +177 -0
- data/vendor/webrat/spec/rcov.opts +1 -0
- data/vendor/webrat/spec/spec.opts +2 -0
- data/vendor/webrat/spec/spec_helper.rb +50 -0
- metadata +554 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
$testing = true
|
|
2
|
+
$root = File.expand_path(File.dirname(__FILE__) + "/../../")
|
|
3
|
+
$:.unshift "#{$root}/lib"
|
|
4
|
+
|
|
5
|
+
require 'sum'
|
|
6
|
+
|
|
7
|
+
gem 'bmabey-email_spec', '=0.2.0'
|
|
8
|
+
gem 'rspec', '=1.2.7'
|
|
9
|
+
|
|
10
|
+
require 'email_spec'
|
|
11
|
+
require 'rack/test'
|
|
12
|
+
require 'spec/mocks'
|
|
13
|
+
require 'test/unit'
|
|
14
|
+
require "#{$root}/vendor/webrat/lib/webrat"
|
|
15
|
+
|
|
16
|
+
World do
|
|
17
|
+
def app
|
|
18
|
+
Application
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
include EmailSpec::Helpers
|
|
22
|
+
include EmailSpec::Matchers
|
|
23
|
+
include Rack::Test::Methods
|
|
24
|
+
include Test::Unit::Assertions
|
|
25
|
+
include Webrat::Matchers
|
|
26
|
+
include Webrat::Methods
|
|
27
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
def current_email_address
|
|
2
|
+
"cucumber@sumapp.com"
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
def fill_all_with_valid_data(hash={})
|
|
6
|
+
valid = {
|
|
7
|
+
'savings' => '0',
|
|
8
|
+
'income' => '0',
|
|
9
|
+
'bills' => '0',
|
|
10
|
+
'email' => current_email_address
|
|
11
|
+
}.merge(hash)
|
|
12
|
+
valid.each do |key, value|
|
|
13
|
+
fill_in "user[#{key}]", :with => value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_user
|
|
18
|
+
UserEmail.find_by_email(current_email_address).user
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def generate_email(options={})
|
|
22
|
+
<<EMAIL
|
|
23
|
+
Delivered-To: test@sumapp.com
|
|
24
|
+
Return-Path: <#{options[:from] || current_email_address}>
|
|
25
|
+
From: Winton Welsh <#{options[:from] || current_email_address}>
|
|
26
|
+
To: test@sumapp.com
|
|
27
|
+
Subject: #{options[:subject] || ''}
|
|
28
|
+
Content-Type: multipart/alternative; boundary=000325574d2a5057e1046eae4ba5
|
|
29
|
+
|
|
30
|
+
--000325574d2a5057e1046eae4ba5
|
|
31
|
+
Content-Type: text/plain; charset=ISO-8859-1
|
|
32
|
+
Content-Transfer-Encoding: 7bit
|
|
33
|
+
|
|
34
|
+
#{options[:body] || ''}
|
|
35
|
+
|
|
36
|
+
--000325574d2a5057e1046eae4ba5
|
|
37
|
+
Content-Type: text/html; charset=ISO-8859-1
|
|
38
|
+
Content-Transfer-Encoding: 7bit
|
|
39
|
+
|
|
40
|
+
#{options[:body] || ''}<br>
|
|
41
|
+
|
|
42
|
+
--000325574d2a5057e1046eae4ba5--
|
|
43
|
+
EMAIL
|
|
44
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Feature: Update user
|
|
2
|
+
|
|
3
|
+
As a user
|
|
4
|
+
I want to submit my information
|
|
5
|
+
In order to see a success page
|
|
6
|
+
|
|
7
|
+
Scenario: I submit a valid form with decimals
|
|
8
|
+
When I visit the front page
|
|
9
|
+
And I submit a valid form with decimals
|
|
10
|
+
Then I should see a success page
|
|
11
|
+
|
|
12
|
+
Scenario: I submit a valid form with numbers only
|
|
13
|
+
When I visit the front page
|
|
14
|
+
And I submit a valid form with numbers only
|
|
15
|
+
Then I should see a success page
|
|
16
|
+
|
|
17
|
+
Scenario: I submit a valid form with dollar signs
|
|
18
|
+
When I visit the front page
|
|
19
|
+
And I submit a valid form with dollar signs
|
|
20
|
+
Then I should see a success page
|
|
21
|
+
|
|
22
|
+
Scenario: I submit an invalid savings amount
|
|
23
|
+
When I visit the front page
|
|
24
|
+
And submit an invalid savings amount
|
|
25
|
+
Then I should see a form
|
|
26
|
+
And the savings field should have an error
|
|
27
|
+
And the error should be "is not a number"
|
|
28
|
+
|
|
29
|
+
Scenario: I submit an empty savings amount
|
|
30
|
+
When I visit the front page
|
|
31
|
+
And submit an empty savings amount
|
|
32
|
+
Then I should see a form
|
|
33
|
+
And the savings field should have an error
|
|
34
|
+
And the error should be "is not a number"
|
|
35
|
+
|
|
36
|
+
Scenario: I submit an invalid income amount
|
|
37
|
+
When I visit the front page
|
|
38
|
+
And submit an invalid income amount
|
|
39
|
+
Then I should see a form
|
|
40
|
+
And the income field should have an error
|
|
41
|
+
And the error should be "is not a number"
|
|
42
|
+
|
|
43
|
+
Scenario: I submit an empty income amount
|
|
44
|
+
When I visit the front page
|
|
45
|
+
And submit an empty income amount
|
|
46
|
+
Then I should see a form
|
|
47
|
+
And the income field should have an error
|
|
48
|
+
And the error should be "is not a number"
|
|
49
|
+
|
|
50
|
+
Scenario: I submit an invalid bills amount
|
|
51
|
+
When I visit the front page
|
|
52
|
+
And submit an invalid bills amount
|
|
53
|
+
Then I should see a form
|
|
54
|
+
And the bills field should have an error
|
|
55
|
+
And the error should be "is not a number"
|
|
56
|
+
|
|
57
|
+
Scenario: I submit an empty bills amount
|
|
58
|
+
When I visit the front page
|
|
59
|
+
And submit an empty bills amount
|
|
60
|
+
Then I should see a form
|
|
61
|
+
And the bills field should have an error
|
|
62
|
+
And the error should be "is not a number"
|
|
63
|
+
|
|
64
|
+
Scenario: I submit an invalid email
|
|
65
|
+
When I visit the front page
|
|
66
|
+
And submit an invalid email
|
|
67
|
+
Then I should see a form
|
|
68
|
+
And the email field should have an error
|
|
69
|
+
And the error should be "is invalid"
|
|
70
|
+
|
|
71
|
+
Scenario: I submit an empty email
|
|
72
|
+
When I visit the front page
|
|
73
|
+
And submit an empty email
|
|
74
|
+
Then I should see a form
|
|
75
|
+
And the email field should have an error
|
|
76
|
+
And the error should be "can't be blank"
|
data/gemspec.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
GEM_NAME = 'sum'
|
|
2
|
+
GEM_FILES = FileList['**/*'] - FileList[
|
|
3
|
+
'*.gem',
|
|
4
|
+
'*.log',
|
|
5
|
+
'config/database.yml',
|
|
6
|
+
'config/deploy.rb',
|
|
7
|
+
'config/mail.yml',
|
|
8
|
+
'coverage',
|
|
9
|
+
'coverage/**/*',
|
|
10
|
+
'log',
|
|
11
|
+
'log/**/*',
|
|
12
|
+
'pkg',
|
|
13
|
+
'pkg/**/*',
|
|
14
|
+
'tmp',
|
|
15
|
+
'tmp/**/*'
|
|
16
|
+
]
|
|
17
|
+
GEM_SPEC = Gem::Specification.new do |s|
|
|
18
|
+
# == CONFIGURE ==
|
|
19
|
+
s.author = "Winton Welsh"
|
|
20
|
+
s.email = "mail@wintoni.us"
|
|
21
|
+
s.homepage = "http://github.com/winton/#{GEM_NAME}"
|
|
22
|
+
s.summary = "A simple budgeting app built on Sinatra"
|
|
23
|
+
# == CONFIGURE ==
|
|
24
|
+
s.add_dependency('bmabey-email_spec', '=0.2.0')
|
|
25
|
+
s.add_dependency('cucumber', '>=0.3.11')
|
|
26
|
+
s.add_dependency('haml', '=2.0.9')
|
|
27
|
+
s.add_dependency('less', '>=0.8.11')
|
|
28
|
+
s.add_dependency('javan-whenever', '=0.3.6')
|
|
29
|
+
s.add_dependency('rack', '>=1.0.0')
|
|
30
|
+
s.add_dependency('rack-test', '>=0.4.0')
|
|
31
|
+
s.add_dependency('rspec', '>=1.2.7')
|
|
32
|
+
s.add_dependency('shotgun', '>=0.3')
|
|
33
|
+
s.add_dependency('sinatra', '=0.9.2')
|
|
34
|
+
s.add_dependency('winton-active_wrapper', '=0.1.5')
|
|
35
|
+
s.add_dependency('winton-externals', '=1.0.2')
|
|
36
|
+
s.add_dependency('winton-fetcher', '=0.1.2')
|
|
37
|
+
s.add_dependency('winton-secret_key', '=0.1.0')
|
|
38
|
+
s.extra_rdoc_files = [ "README.markdown" ]
|
|
39
|
+
s.files = GEM_FILES.to_a
|
|
40
|
+
s.has_rdoc = false
|
|
41
|
+
s.name = GEM_NAME
|
|
42
|
+
s.platform = Gem::Platform::RUBY
|
|
43
|
+
s.require_path = "lib"
|
|
44
|
+
s.version = "0.1.0"
|
|
45
|
+
end
|
data/lib/sum/boot.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "#{File.dirname(__FILE__)}/model/incoming_mail"
|
|
2
|
+
|
|
3
|
+
Application.class_eval do
|
|
4
|
+
|
|
5
|
+
# Sinatra
|
|
6
|
+
enable :raise_errors
|
|
7
|
+
set :environment, $testing ? :test : environment
|
|
8
|
+
set :root, File.expand_path("#{File.dirname(__FILE__)}/../../")
|
|
9
|
+
set :public, "#{root}/public"
|
|
10
|
+
set :logging, true
|
|
11
|
+
set :static, true
|
|
12
|
+
set :views, "#{root}/lib/sum/view"
|
|
13
|
+
|
|
14
|
+
# Database, logging, and email
|
|
15
|
+
$db, $log, $mail = ActiveWrapper.setup(
|
|
16
|
+
:base => root,
|
|
17
|
+
:env => environment,
|
|
18
|
+
:stdout => environment != :test
|
|
19
|
+
)
|
|
20
|
+
$db.establish_connection
|
|
21
|
+
if $mail.config
|
|
22
|
+
ActionMailer::Base.raise_delivery_errors = true
|
|
23
|
+
imap = { :receiver => IncomingMail, :type => :imap }
|
|
24
|
+
$mail.config[:imap].merge!(imap)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Secret key
|
|
28
|
+
$secret_key = SecretKey.new(root).read
|
|
29
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Application.class_eval do
|
|
2
|
+
|
|
3
|
+
get "/#{environment == :production ? $secret_key : 'cron'}" do
|
|
4
|
+
IncomingMail.process!
|
|
5
|
+
User.reset_users!
|
|
6
|
+
User.reset_spent_today!
|
|
7
|
+
User.send_emails! do |user|
|
|
8
|
+
haml(:email, :layout => false, :locals => { :u => user })
|
|
9
|
+
end
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Application.class_eval do
|
|
2
|
+
|
|
3
|
+
post '/new' do
|
|
4
|
+
if email = UserEmail.find_by_email(params[:user][:email])
|
|
5
|
+
if @user = email.user
|
|
6
|
+
@user.send_now = true
|
|
7
|
+
@user.update_attributes(params[:user])
|
|
8
|
+
end
|
|
9
|
+
else
|
|
10
|
+
@user = User.create(params[:user])
|
|
11
|
+
end
|
|
12
|
+
@title = valid? ? 'Success!' : 'Fix mistakes'
|
|
13
|
+
haml :new
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Application.class_eval do
|
|
2
|
+
helpers do
|
|
3
|
+
|
|
4
|
+
def errors_on(attribute)
|
|
5
|
+
@user && @user.errors.on(attribute)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def field(attribute, question)
|
|
9
|
+
partial(
|
|
10
|
+
:field,
|
|
11
|
+
:locals => {
|
|
12
|
+
:attribute => attribute,
|
|
13
|
+
:question => question
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def valid?(attribute=nil)
|
|
19
|
+
if attribute
|
|
20
|
+
@user && @user.errors.on(attribute).nil?
|
|
21
|
+
else
|
|
22
|
+
@user && @user.valid?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
class IncomingMail < ActionMailer::Base
|
|
2
|
+
|
|
3
|
+
class <<self
|
|
4
|
+
def process!
|
|
5
|
+
if $mail.config
|
|
6
|
+
Fetcher.create($mail.config[:imap]).fetch
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def receive(mail)
|
|
12
|
+
return unless body = get_body(mail)
|
|
13
|
+
emails = parse_emails(mail.subject, body.body)
|
|
14
|
+
numbers = parse_numbers(mail.subject, body.body)
|
|
15
|
+
start = parse_start(mail.subject, body.body)
|
|
16
|
+
stop = parse_stop(mail.subject, body.body)
|
|
17
|
+
|
|
18
|
+
if mail.from[0] && email = UserEmail.find_by_email(mail.from[0])
|
|
19
|
+
user = email.user
|
|
20
|
+
else
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if user
|
|
25
|
+
emails.each do |email|
|
|
26
|
+
user.add_email!(email, true)
|
|
27
|
+
end
|
|
28
|
+
numbers.each do |number|
|
|
29
|
+
user.spend!(number)
|
|
30
|
+
end
|
|
31
|
+
email.activate! if start
|
|
32
|
+
email.deactivate! if stop
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
[ emails, numbers, start, stop ]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def get_body(mail)
|
|
41
|
+
if mail.parts.empty?
|
|
42
|
+
mail
|
|
43
|
+
else
|
|
44
|
+
mail.parts.select { |part|
|
|
45
|
+
part['content-type'].content_type == 'text/plain'
|
|
46
|
+
}.first
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_emails(*args)
|
|
51
|
+
args.collect { |text|
|
|
52
|
+
text = text_upto(text, /\s/)
|
|
53
|
+
text.scan(/\S+@\S+\.\S+/)
|
|
54
|
+
}.flatten
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_numbers(*args)
|
|
58
|
+
args.collect { |text|
|
|
59
|
+
text = text_upto(text, /[^-+\d\.\s]/)
|
|
60
|
+
numbers = text.scan(/([-+]*)(\d+\.*\d*)/)
|
|
61
|
+
numbers.collect do |number|
|
|
62
|
+
operator, number = number
|
|
63
|
+
(operator == '+' ? -1 : 1) * number.to_f
|
|
64
|
+
end
|
|
65
|
+
}.flatten
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parse_start(*args)
|
|
69
|
+
args.collect { |text|
|
|
70
|
+
text.strip[0..4].downcase == 'start'
|
|
71
|
+
}.include?(true)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_stop(*args)
|
|
75
|
+
args.collect { |text|
|
|
76
|
+
text.strip[0..3].downcase == 'stop'
|
|
77
|
+
}.include?(true)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def text_upto(text, regex)
|
|
81
|
+
first_instance = text.index(regex)
|
|
82
|
+
return '' if first_instance == 0
|
|
83
|
+
if first_instance
|
|
84
|
+
text[0..first_instance-1]
|
|
85
|
+
else
|
|
86
|
+
text
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
class User < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
attr_accessible :email
|
|
4
|
+
attr_accessible :bills
|
|
5
|
+
attr_accessible :income
|
|
6
|
+
attr_accessible :savings
|
|
7
|
+
attr_accessible :timezone_offset
|
|
8
|
+
|
|
9
|
+
after_create :after_create_email
|
|
10
|
+
before_create :before_create_timestamps
|
|
11
|
+
|
|
12
|
+
before_save :before_save_recent_transactions
|
|
13
|
+
before_save :before_save_savings_goal
|
|
14
|
+
before_save :before_save_spending_goal
|
|
15
|
+
|
|
16
|
+
has_many :emails, :class_name => 'UserEmail', :dependent => :destroy
|
|
17
|
+
|
|
18
|
+
serialize :recent_transactions
|
|
19
|
+
|
|
20
|
+
validates_format_of(
|
|
21
|
+
:email,
|
|
22
|
+
:with => /\S+@\S+\.\S+/,
|
|
23
|
+
:unless => lambda { |r| r.email.blank? }
|
|
24
|
+
)
|
|
25
|
+
validates_numericality_of(
|
|
26
|
+
:bills,
|
|
27
|
+
:income,
|
|
28
|
+
:savings,
|
|
29
|
+
:unless => lambda { |r| r.bills.nil? && r.income.nil? && r.savings.nil? }
|
|
30
|
+
)
|
|
31
|
+
validates_presence_of :email, :unless => lambda { |r| r.email.nil? }
|
|
32
|
+
|
|
33
|
+
# Class methods
|
|
34
|
+
|
|
35
|
+
class <<self
|
|
36
|
+
|
|
37
|
+
def reset_users!
|
|
38
|
+
conditions = [ 'reset_at <= ?', Time.now.utc ]
|
|
39
|
+
users = self.find(:all, :conditions => conditions)
|
|
40
|
+
users.each do |user|
|
|
41
|
+
user.reset!
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset_spent_today!
|
|
46
|
+
conditions = [ 'send_at <= ?', Time.now.utc ]
|
|
47
|
+
users = self.find(:all, :conditions => conditions)
|
|
48
|
+
users.each do |user|
|
|
49
|
+
user.reset_spent_today!
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def send_emails!(&block)
|
|
54
|
+
conditions = [
|
|
55
|
+
'send_now = 1 OR send_at <= ?',
|
|
56
|
+
Time.now.utc
|
|
57
|
+
]
|
|
58
|
+
users = self.find(:all, :conditions => conditions)
|
|
59
|
+
users.each do |user|
|
|
60
|
+
user.emails.each do |email|
|
|
61
|
+
next unless email.active? && email.failures <= 5
|
|
62
|
+
begin
|
|
63
|
+
$mail.deliver(
|
|
64
|
+
:from => 'sum@sumapp.com',
|
|
65
|
+
:to => email.email,
|
|
66
|
+
:subject => "Today's budget",
|
|
67
|
+
:body => yield(user)
|
|
68
|
+
)
|
|
69
|
+
email.sent!
|
|
70
|
+
rescue Exception => e
|
|
71
|
+
email.increment!(:failures)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
user.sent!
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Instance methods
|
|
80
|
+
|
|
81
|
+
[ :email, :bills, :income, :savings ].each do |attribute|
|
|
82
|
+
define_method(attribute) do
|
|
83
|
+
read_attribute attribute
|
|
84
|
+
end
|
|
85
|
+
define_method("#{attribute}=") do |value|
|
|
86
|
+
write_attribute(
|
|
87
|
+
attribute,
|
|
88
|
+
attribute == :email ? value : to_number(value)
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def add_email!(address, send=false)
|
|
94
|
+
UserEmail.create(:email => address, :user_id => self.id)
|
|
95
|
+
if send
|
|
96
|
+
self.flash = "Successfully added #{address} to your account."
|
|
97
|
+
self.send_now = true
|
|
98
|
+
self.save
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Beginning of fiscal month
|
|
103
|
+
def beginning_of_month
|
|
104
|
+
self.reset_at - 1.month
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Days in this fiscal month
|
|
108
|
+
def days_in_month
|
|
109
|
+
self.reset_at.to_date - self.beginning_of_month.to_date
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Days left in this fiscal month
|
|
113
|
+
def days_left
|
|
114
|
+
to_local(self.reset_at).to_date - to_local(Time.now).to_date - 1
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Days left in this fiscal month, including today
|
|
118
|
+
def days_left_including_today
|
|
119
|
+
self.days_left + 1
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Days passed in this fiscal month
|
|
123
|
+
def days_passed
|
|
124
|
+
to_local(Time.now).to_date - to_local(self.beginning_of_month).to_date
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Days passed in this fiscal month, including today
|
|
128
|
+
def days_passed_including_today
|
|
129
|
+
self.days_passed + 1
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Reset spent_today
|
|
133
|
+
def reset_spent_today!
|
|
134
|
+
self.update_attribute :spent_today, 0
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def reset!
|
|
138
|
+
self.temporary_spending_cut = self.total_left * -1
|
|
139
|
+
if self.temporary_spending_cut < 0
|
|
140
|
+
self.temporary_spending_cut = 0
|
|
141
|
+
end
|
|
142
|
+
self.spent_this_month = 0
|
|
143
|
+
update_reset_at
|
|
144
|
+
self.save
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def sent!
|
|
148
|
+
self.flash = nil
|
|
149
|
+
self.send_now = false
|
|
150
|
+
update_send_at unless self.send_now_changed?
|
|
151
|
+
self.save
|
|
152
|
+
rescue
|
|
153
|
+
# This fixes a really confusing error when running cucumber features:
|
|
154
|
+
# No response yet. Request a page first. (Rack::Test::Error)
|
|
155
|
+
# Happens on User#save after using $mail.deliver.
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# How much the user should have spent in this period
|
|
159
|
+
def should_have_spent
|
|
160
|
+
self.spending_per_day * self.days_passed_including_today
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def spend!(amount)
|
|
164
|
+
amount = amount.to_f if amount.respond_to?(:upcase)
|
|
165
|
+
self.recent_transactions ||= []
|
|
166
|
+
self.recent_transactions.unshift(amount)
|
|
167
|
+
self.spent_this_month += amount
|
|
168
|
+
self.spent_today += amount
|
|
169
|
+
self.send_now = true
|
|
170
|
+
self.save
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Spending goal for the month
|
|
174
|
+
def spending_goal
|
|
175
|
+
read_attribute(:spending_goal) - self.temporary_spending_cut
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Today's spending goal
|
|
179
|
+
def spending_goal_today
|
|
180
|
+
(self.surplus(:exclude_today) / self.days_left_including_today) - self.spent_today
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Today's spending goal with savings included in money left to spend
|
|
184
|
+
def spending_goal_today_savings
|
|
185
|
+
(self.savings_goal + self.surplus(:exclude_today)) / self.days_left_including_today
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# How much the user is spending per day (ideally)
|
|
189
|
+
def spending_per_day
|
|
190
|
+
self.spending_goal / self.days_in_month
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# How much the user has spent this month
|
|
194
|
+
def spent_this_month(option=nil)
|
|
195
|
+
case option
|
|
196
|
+
when :exclude_today
|
|
197
|
+
read_attribute(:spent_this_month) - self.spent_today
|
|
198
|
+
else
|
|
199
|
+
read_attribute(:spent_this_month)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Variance from budget based on savings_goal
|
|
204
|
+
def surplus(option=nil)
|
|
205
|
+
two_decimals(self.spending_goal - spent_this_month(option))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Variance from budget based on should_have_spent
|
|
209
|
+
def surplus_for_period(option=nil)
|
|
210
|
+
two_decimals(self.should_have_spent - spent_this_month(option))
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Total remaining money for the month
|
|
214
|
+
def total_left(option=nil)
|
|
215
|
+
self.surplus(option) + self.savings_goal
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def after_create_email
|
|
221
|
+
self.add_email!(read_attribute(:email))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def before_create_timestamps
|
|
225
|
+
self.reset_at = update_send_at
|
|
226
|
+
update_reset_at
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def before_save_recent_transactions
|
|
230
|
+
if self.recent_transactions
|
|
231
|
+
self.recent_transactions = self.recent_transactions[0..4]
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def before_save_savings_goal
|
|
236
|
+
if self.savings
|
|
237
|
+
self.savings_goal = self.savings
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def before_save_spending_goal
|
|
242
|
+
if self.income && self.bills && self.savings
|
|
243
|
+
self.spending_goal = self.income - self.bills - self.savings
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def local_12am_to_server_time
|
|
248
|
+
time = to_local(Time.now)
|
|
249
|
+
time = DateTime.strptime(
|
|
250
|
+
time.strftime("%m/%d/%Y 12:00 AM %Z"),
|
|
251
|
+
"%m/%d/%Y %I:%M %p %Z"
|
|
252
|
+
)
|
|
253
|
+
to_system(time.to_time)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def to_local(time)
|
|
257
|
+
return time.utc unless self.timezone_offset
|
|
258
|
+
time.utc + self.timezone_offset
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def to_number(string)
|
|
262
|
+
string = string.gsub(/[^\d\.]/, '')
|
|
263
|
+
string.blank? ? string : string.to_f
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def to_system(time)
|
|
267
|
+
return time.utc unless self.timezone_offset
|
|
268
|
+
time.utc - self.timezone_offset
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def two_decimals(amount)
|
|
272
|
+
sprintf("%.2f", amount).to_f
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def update_reset_at
|
|
276
|
+
self.reset_at = self.reset_at + 1.month
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def update_send_at
|
|
280
|
+
if self.send_at
|
|
281
|
+
self.send_at = self.send_at + 1.day
|
|
282
|
+
else
|
|
283
|
+
self.send_at = local_12am_to_server_time
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|