In part 2 we made some good progress on our new budget feature. Today we’ll finish off the core budget enhancement functionality.
Today’s objective
At this point we just need to add some interactive elements to the UI and I also want to add a site wide widget similar to the current income versus expenses
widget. This new widget will provide a visual indicator to the user as to whether they are currently running within their budget.
Getting started
As previously mentionned I’m not sure this is really a “follow along” type of series, but it is possible to follow along if you wish.
If you’ve been following along you can continue with the code from part 2, or you can grab the code from GitHub.
Clone the Repo:
Terminal
After grabbing the code we should set our local Ruby version and run bundle install
.
Terminal
Terminal
Next let’s create a new branch for today’s work.
Terminal
With that out of the way, let’s get to it!
Where we’re starting from
Let’s fire up the server and have a quick look at our current budget page.
Terminal
So our page is looking pretty good. All we need to do at this point is add in our various totals. We’ll also want to make the calculation of the totals dynamic so that when a user updates a budget field, the totals are recalculated without needing to refresh the page.
Let’s get at it!
Adding totals to the budget page
The totals are going to be calculated via JavaScript and jQuery. Our generator from part 1 created a budget.js.coffee
file, but we’re going to be sticking with plain JavaScript, so the first order of business is to rename the file.
Terminal
We also need to make a few changes to the _budget_item.html.erb
file. We’ll want to add a couple of classes so that we can easily access the spent
and + / -
fields with jQuery. I also want to display the spent
value without the -
sign, so we’ll add an .abs
transform to it.
/app/views/budget/_budget_item.html.erb
The only change above is the classes on the last two td
tags and the aforementionned transform of the spent
value to an absolute number.
The JavaScript
It’s now time to create our JavaScript. We’ll start off by creating functions for each total column. Let’s start off with the + / -
column.
Calculating the plus / minus column
For the + / -
column we just need to subtract the spent
column from the budgeted
column, and do this for all rows. Let’s see what this looks like.
/app/assets/javascripts/budget.js
All we’ve done above is call a function we’ve created, setPlusMinus
, on page load. The function itself is pretty simple. For each row we just subtract spent
from budgeted
and place the result in the + / -
column. We’re using the jQuery format currency plugin for converting the values on the UI to numbers (via asNumber()
) so we can perform the necessary arithmetic on them. We then use the formatCurrency()
function to display the result as a currency.
Notice we are also changing the class of the + / -
field depending on whether the value is positive or negative. Let’s add the styles we are referencing.
First we need to update application.css.scss
.
/app/assets/stylesheets/application.css.scss
The only change above is the addition of the budget
stylesheet.
Now let’s update the budget
stylesheet.
/app/assets/stylesheets/budget.css.scss
The above will display debit
values in black, credit
values in red. We’re also adding a bit of padding to a
links so that the Edit
links are spaced a little nicer.
With all the above changes in place, a page refresh (you might also need to clear your browser cache) yields:
Not bad!
Now we just need to do something similar with all the values for the Total
row. I won’t go thru an explanation of each function, they are all variations on the general theme of selecting all the appropriate values and adding them together.
/app/assets/javascripts/budget.js
A page refresh will show that our page is now looking pretty much complete:
Making the totals dynamic
One problem with the current implementation is that when the user updates a budgeted
value, the totals don’t update to reflect the change.
This can be fixed pretty easily, we just need to bind to the best_in_place
success
event.
Once again we’ll just update our JavaScript file, adding a new function bindBudgetValueUpdateSuccessEvent
, and calling into that function on page load.
/app/assets/javascripts/budget.js
With the above change the totals now update on a budget change.
So that takes care of the budget page functionality!
Adding a “budget” widget
One additional change I would like to make is to add a widget similar to the current income versus expenses
widget.
The first step is to add the new widget file:
Terminal
The contents of the file will be:
/app/views/shared/_budget_status.html.erb
Pretty simple! We’ve created a new monthly_budget_remaining
method (which we’ll need to create) and depending on whether we get a positive or negative number from this method we display either OVER BUDGET
or ON BUDGET
for our status.
We also need to make a few styling updates. We’ll be adding some specific styles for the budget-status
class, and then we also want to pull out the debit
and credit
styles from the .mtd-ytd
style definitions as we are going to be using these for the budget widget so want them to be scoped globally. The changes to layout.css.scss
are below:
/app/assets/stylesheets/layout.css.scss …line 44
We’ve added some styling for the budget-status
class and pulled the debit
and credit
classes out of the mtd-ytd
scope.
Since these classes are now global we can remove the debit
and credit
classes from budget.css
.
/app/assets/stylesheets/budget.css.scss
Now we need to create our new method in user.rb
.
/app/models/user.rb …line 63
Fairly simple, all we are doing is summing up our expenses for the month and subtracting that value from the sum of the budgeted
fields for our categories.
Note the budget status widget is an overall budget status indicator, i.e. it is possible to be over budget in a particular category but not have the status indicate the user is over-budget if overall they are not over budget.
One final change we need to make in order to see our widget in action is to add it to the following pages:
- app/views/budget/index.html.erb
- app/views/categories/index.html.erb
- app/views/reports/index.html.erb
- app/views/transactions/edit.html.erb
- app/views/transactions/index.html.erb
- app/views/transactions/new.html.erb
- app/views/users/edit.html.erb
Adding the widget is straight-forward, we’ll just place it after the current render 'shared/mtd_ytd'
call. For example:
/app/views/budget/index.html.erb
With these changes in place, we have a budget widget!
A few issues
We have a few minor issues with the current implementation of the budget functionality. One is that the budget widget will not update without a page refresh so won’t automatically reflect changes the user makes to their budget values. This is a pretty minor issue and pretty easy to fix but for now we’ll leave it as is.
The other issue is a bit more annoying. We currently show all categories on the budget page. This doesn’t always make sense, for instance when we have a category that is exclusively for income. For example:
So we see the ‘Pay’ category showing up on the budget page which doesn’t really make sense. There are a few ways we could deal with this:
- Leave it as is, and live with it.
- Change categories so that they are either
expense
orincome
categories. This would be a pretty big change to the way we currently handle things where individual transactions are tagged as being either income or expenses. It would also mean we couldn’t have categories that have both income and expense transactions in them. For instance, I use a ‘Miscellaneous’ category which contains both expenses and income. - Add a new field to categories that indicates whether or not it should be included in the budget.
I think the third solution is likely the best approach. Again, for now we’re going to leave things as is… but next time out we’ll implement this third approach.
We have once again been slacking on making sure we have tests in place for our new functionality. So let’s add some more tests before finishing up for the day.
Adding some tests
We’ll add some tests for the new user.rb
method we created and also for the new budget widget. Let’s start with the user
tests.
Adding tests for the user method
I won’t provide an explanation for the tests below as I think they are pretty self explanatory. We’re just creating some data and then ensuring we get the expected value back from the monthly_budget_remaining
method.
/spec/models/user_spec.rb …line 359 to 408
Let’s make sure the user
tests are passing:
Terminal
Looks good! Note that these are rather long running tests, we might want to look into improving the performance of these tests down the line. For now we’ll leave them as is however.
Adding tests for the widget
We already have some tests for the existing mtd / ytd
widget in the shared_examples_for_widget.rb
file. Since we now have two widgets, let’s start by renaming our existing file to be more specific to the mtd / ytd
widget.
Terminal
Now we can create a new file for the budget widget.
Terminal
The tests for the budget widget are very similar to the tests we created for the user
method. We’re just creating some data and then ensuring we get the expected value displaying in the budget widget.
/spec/support/shared_examples_for_budget_status_widget.rb
All pretty much self explanatory!
Now we just need to add the shared budget widget tests to the following files:
- spec/requests/budget_spec.rb
- spec/requests/categories_spec.rb
- spec/requests/reports_spec.rb
- spec/requests/transactions_spec.rb
- spec/requests/users_spec.rb
Doing so is pretty simple. For example:
/spec/requests/budget_spec.rb …line 22
With all that out of the way, let’s run our full test suite to ensure everything is passing.
Terminal
Sweet!
So that’s if for today, we can merge our code into our budget feature branch.
Terminal
Summary
So we’ve essentially finished our new budget feature. We just have one annoying little issue to fix up (specifying which categories show up on the budget page) which we’ll take care of next time.
Thanks for reading and I hope you enjoyed the post!