Showing posts with label Rails. Show all posts
Showing posts with label Rails. Show all posts

How to show a Tree (TreeView) in a Rails view?

Well there seems to be no one-way for displaying a tree in Rails. But it has to be some javascript/AJAX doing its thing with CSS on HTML lists. So I took a look at YUI Tree (the first thing that google threw up) but it felt bloated and the learning curve was a bit too steep. Some time later.. I found a tree plugin for JQuery and also found that there is a RailsCast to use JQuery with Rails (so I knew its possible to interop).
I've been wanting to look at what JQuery is - with the massive good-will it is building up. Did a couple of tutorials 1 and 2 on their site to get started. Downloaded the jquery.js file (1 file for all the goodness !! awesome).
Next it's time to download the zip for the excellent JQuery tree plugin by Jorn Zaefferer - incidentally its the same person who wrote the second tutorial. I felt better about my chances for success.

I'm assuming you have a Rails app and the code for your hieararchy all done to this point. It's going to be a simple tree showing the hierarchy (no fancy AJAX callbacks or delayed loading of children... although I think its supported)

Unzip the treeview plugin folder v1.4 (it has a version of jquery bundled too inside the lib subfolder). I see
  • jquery.treeview.XXX.js (the javascript files) : Moved them to my public\javascripts folder within my Rails project folder. Also copied the lib subfolder here.
  • jquery.treeview.css (the CSS stylesheet) : Moved them to my public\stylesheets folder
  • images subfolder : Moved the images in this folder to a new subfolder public\images\jquery.treeview.images
Now the CSS file has references to the images. So do a find-replace to make sure all the images are reachable in their new locations; "url(images/" becomes "url(../images/jquery.treeview.images/"

I have a show action on my controller that creates @root_node which is a typical Node object with a Children attribute - an array of child nodes. So now we need something to render it as an hierarchical HTML list - a Rails helper method for your controller



  def display_segment( node )

html = "<li>"
node_class = node.children.length == 0 ? "file" : "folder"
html << "<span class=\"#{node_class}\">#{h(node.to_s)} </span>"
html << "<ul id=\"children_of_#{h(node.sid)}\">"
node.children.each{|child_node|

html << display_segment( child_node )
}
html << "</ul></li>"
end



Let's also take a look at the view show.html.erb



<html>
<head>
<h3> You are viewing <%= @data_file_path %> </h3>

<title><%= controller.action_name %></title>
<%= stylesheet_link_tag 'appstylesheet'%>
</head>

<body>
<ul id="my_tree" class="filetree">
<%= display_segment(@root_node) %>
</ul>

</body>
</html>


Things to note
  • First we need an enclosing ul block with css class set to filetree. This would be picked up our custom javascript snippet to JQuery-magically turn into a tree. So we need to make a note to give it a good id - here my_tree
  • Next the content is rendered via our recursive helper function. Children of a node are in a nested ul block. Each leaf-node is a li item and has css class as "file". parents have css class as "folder"
So now it should look something like this.



Now the stage is set for JQuery to do its thing. First lets introduce these 2 lines to the head tag of our view.


<%= javascript_include_tag 'lib/jquery', 'jquery.treeview', 'custom' %>
<%= stylesheet_link_tag 'jquery.treeview.css' %>


So we say we want to use the jquery javascript file, followed by the treeview plugin and finally our own custom javascript file that we'll write next.
We also want to use the treeview CSS stylesheet. You can do a "View Source" in firefox to see what the translated HTML looks like - std. stuff.

We're nearing the end here - Our \public\javascripts\custom.js file to sprinkle some JQuery dust on this thing


$(document).ready( function() {
$("#my_tree").treeview();
} );


Here we're giving a function to be executed when the page/document is ready. This function finds our top-level list element with id = "my_tree" and calls the treeview() method on it.



Victory Jig time!

Graphs in Ruby On Rails


Well I was building my personal expense tracking web app and soon enough I need to graph the top expense categories... A little searching led to gruff and it worked right outta the box.


First up, Gruff needs RMagick which needs ImageMagick. Tricky.. so go here and I hit Q#7
Soon you should land up
here download the rmagick-win32 gem zip. This archive has both RMagick gem and the install for the right version of ImageMagick. Installing the latest version of ImageMagick may not work.. (I didnt try.. the docs warned me off).
So install ImageMagick first. Next install the gem with 'gem install rmagick --local' in the folder where you unzipped the archive.
Now switch to your Rails Dev Environment. Open up config/environment.rb - Add this line at the end 'require gruff'
Add a new action to the controller of your choice.



  def showTopN
g = Gruff::Bar.new
g.title = "Hello Gruff"
g.theme_37signals

Expense.get_top_n_categories(:limit=>10).each{ |category|
g.data(category.name, category.total_amount.to_f)
}

send_data g.to_blob, :disposition=>'inline', :type=>'image/png', :filename=>'top_n.pdf'
end




Now close all command windows. Restart Webrick (your Rails Web Server) to take into account changes made to your PATH env var.
Fire up a browser.. in my case to http://localhost:3000/outflow/showTopN

Update:Normal people would be overjoyed at this point.. but not me. I wanted to create this graph at runtime .. So when I click on a link that I imagine will be there, I callback on my controller (yes using AJAX), create my graph and then slot it into a div tag on the same page. This will help me chart different sub-sets of data without leaving the page.
But how? so I post a question on stackoverflow
Solution:
report_controller.rb
require 'gruff'
class ReportController < ApplicationController
def showTopNCategories

end

def drawBarChart
g = Gruff::Bar.new
g.title = "Hello Gruff"
g.theme_37signals

Expense.get_top_n_categories(:limit=>10).each{ |category|
g.data(category.name, category.total_amount.to_f)
}
g.write('public/images/top_n.png')
render(:text=>'top_n.png')
end
end



showTopNCategories.rhtml


<%content_for("page_scripts") do -%>
function updateImg(id, imageFile)
{
$(id).innerHTML = '<img src="/images/' + imageFile + '"/>';
}
<% end -%>

<!-- some controls -->
<%= link_to_remote("Refresh Chart",
:complete => "updateImg('div_barchart', request.responseText)",
:url=>{:action=>'drawBarChart'})%>

<div id="div_barchart">

</div>



application.rhtml


<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<!-- the usual -->

<script type="text/javascript"> <%= @content_for_page_scripts %> </script>
</head>
<!-- rest of the usual -->




The quirk here was that
  • gruff considers the Rails Project folder as the base folder.. so you need public/images/FileName.png as the parameter to g.write. Rails views however have public as the base folder. So image paths are relative.. /images/FileName.png
  • the javascript function didn't wire up correctly. It dies silently if you make a typo for instance. Use alert("Hit!"); liberally to know if the function is being hit.

ShowMeTheMoney-16 Let's play tag the expense with a category

Well I know its been a long time... I took a break.. I got married in it. Long story short... its been a while. But now let's end what I started
Basically we want to tag each expense with some text. Not much time would be spent on the updating and deleting categories. If we can just create categories and select existing ones, we should be good. We’ll need to get some confirmation from the user on this.. So we’ll defer the delete for sometime.
So basically a dropdown with existing categories – that is editable. So while entering the expense entry, the user can either choose an existing category or add a new one.





I wrote up an acceptance test. Although the former is more readable (being a DoFixture.. however I find that FitLibrary isn't ported yet for Ruby) So we have to make do with the corresponding ActionFixture.. more verbose but will do.

First lets add a migration to add the categories table and add a foreign key in the expenses table to refer to the former.
> ruby script/generate migration add_categories
This will create the corresponding file in db\migrate folder.

class AddCategories < ActiveRecord::Migration
def self.up
create_table :categories do |table|
table.column :name, :string, :limit => 80
end
execute "INSERT INTO categories VALUES (1, 'General');"
add_column :expenses, :category_id, :integer, {:null=>false, :default =>1}
execute "alter table expenses add constraint fk_expenses_to_categories foreign key(category_id) references categories(id)"

end

def self.down

execute "ALTER TABLE expenses DROP FOREIGN KEY fk_expenses_to_categories"
remove_column :expenses, :category_id
drop_table :categories

end
end


Now to run this migration against all three databases dev, prod and test
> rake db:migrate RAILS_ENV="test"


Add the category model with the unit tests for that one. We focus only on creation and retrieval .. don’t see any edits or deletes happening for categories

class CategoryTest < Test::Unit::TestCase
def test_create
def test_saveWithoutCategoryName_fails.
end


Now onto expense_test.rb
We need to
  • create the expense category if it does not exist
  • use the existing category if possible
  • ensure that the associated category is retrieved from the plot
the first two should be the responsibility of the outflow_controller
create categories.yml first. update expense model tests one at a time to account for the category.
Update OutflowController to create or use-existing category on creation of expense entry.


class OutflowController < ApplicationController
...

auto_complete_for :category, :name

def create
raise ArgumentError, "Category missing!", caller[1..-1] if (params[:category].nil? || params[:category][:name].nil?)

sCategoryName = (params[:category][:name]).strip
category = Category.find(:first, :conditions=>["name = ?", sCategoryName])
category = Category.create( {:name => sCategoryName} ) if (category.nil?)
params[:expense][:category_id] = category.id

@expense = Expense.new(params[:expense])
if (@expense.save)
flash[:notice] = 'Expense entry saved'
redirect_to :action=>'list'
else
render :action=>'new'
end
end
end



Update views to have an auto-completing field for the user to enter the category for an expense. This is a one-line addition in the controller as shown below coupled with the following in the view.
/app/views/outflow/_form.rhtml


<tr>
<td> <%= getAppString(:label_category) %> </td>
<td>
<%= text_field_with_auto_complete :category, :name%>
</td>
</tr>




Right on! Let’s complete the implementation required for the second part of our acceptance test.

class ExpenseGetTopNCategoriesTest < Test::Unit::TestCase
fixtures :expenses, :categories

def test_getTopN_categories
obCategories = Expense.get_top_n_categories

assert_equal 3, obCategories.length
assert_equal categories(:rent_category).name, obCategories[0].name
assert_in_delta expenses(:rent_expense_entry).amount, obCategories[0].total_amount, 0.01

assert_equal categories(:apparel_category).name, obCategories[1].name
assert_in_delta expenses(:apparel_expense_entry).amount, obCategories[1].total_amount, 0.01

assert_equal categories(:entertainment_category).name, obCategories[2].name
assert_in_delta expenses(:movies_expense_entry).amount + expenses(:another_movie_expense_entry).amount,
obCategories[2].total_amount, 0.01
end

...


run ruby script/server –s
a little playing around helps me settle on this query
Expense.find_by_sql("SELECT categories.name, sum(amount) as total_amount
from expenses
join categories on category_id = categories.id
group by category_id
order by total_amount desc")

Stackoverflow users help me to convert this into Rails Model speak

Expense.find(:all, :select =>

"categories.name name, sum(amount)
total_amount
",
:joins => "inner join categories on category_id =
categories.id
",
:group => "category_id",
:order => "total_amount desc"


That turns out fine. I add more tests and a couple of more tables to the acceptance test to retrieve top n categories, top categories in a date range, top n categories in a date range. Soon enough...



Check in everything. Repeat the same for tagging inflows with categories. Should be easy. Until next time...

ShowMeTheMoney-17 - I will paginate.. with some help

Iteration#2 Day 4/10

You know when I run rake on the command line to run my tests, i get a lot of deprecation warnings ... which stink! I have been secretly plotting the death of those horrid lil pagination Deprecation warnings. When I run rake from the command prompt, they get in my way of seeing the test results

DEPRECATION WARNING: paginate is deprecated and will be removed from Rails 2.0 (Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination) See http://www.rubyonrails.org/deprecation for details. (called from list at L:/Gishu/Ruby/Rails/ShowMeTheMoney/app/controllers/inflow_controller.rb:12)

After some initial investigation to determine the effort it would take, I find it’s really simple – Its going to be tough going back to the non-rails world out there.
Hey! We're moving.. to will_paginate

The nice folks at errtheblog.com have come up with a better way for pagination (the mechanism to display those next previous page links at the bottom). The current way has some performance issues and needs more code to be written. It has been moved out of the rails 2.0 into a plugin called classic_pagination and hence the warnings.
Now we’ll see how to move to the new way in less than half the amount of typing I did for this post till now.
  • First install the plugin like this..
CmdPrompt> ruby script\plugin install svn://errtheblog.com/svn/plugins/will_paginate
  • Next off to the OutflowController’s list method. There we see..
@expense_pages, @expenses = paginate :expenses, :order=>'created_at', :per_page => 10

Off with the old 2 paginator and array objects and.. in with the new WillPaginate::Collection object
@expenses = Expense.paginate :page=>params[:page], :per_page=>10, :order=>’created_at’


  • Finally we go to the list action’s view. Near the end of the page, we have

<%= link_to 'Previous page', { :page => @expense_pages.current.previous } if @expense_pages.current.previous %>
<%= link_to 'Next page', { :page => @expense_pages.current.next } if @expense_pages.current.next %>

Now check this out.. I love it when you shrink code while keeping the functionality intact. You will agree that this is much neater.

<%= will_paginate @expenses %>


We try it out in our browser and it works as advertised. Awesome. Let’s update the InflowController similarly.. All clear. Hit F5 in firefox… looks good. Rake from the command line is much better now without all those pesky warnings. Of course now we have integrated test running with green bars and the works within Aptana Studio. Groovy! (In case you hit some icebergs.. here’s the Railscasts tutorial video and words straight from the creators at ErrTheBlog )
Update: Will_paginate has moved... http://github.com/mislav/will_paginate/wikis/installation




ShowMeTheMoney-16 - Tallying up the expenses

Iteration#2 Day 3/10

Some TODOs I have jotted down.
  • place all view strings (displayed to the user) under the control of ApplicationHelper::ResourceManager
  • inflow/list with the table style similar to outflow/list
  • credit and expense rows are listed in order of ID. Ordering by entry date is what is required ( Found this out while I was actually using the app.. Real users! nothing comes close to them for feedback)
For the last item – lets take expense first (Credit should be identical), we add another record to the fixtures file, but out of order – make the year 2007 as shown below. We need to update some existing tests to update @fixtureRecordCount to 3. Done!

file: /test/fixtures/expenses.yml
rent_expense_entry:
id: 1
created_at: 2008-01-19 00:00:00
description: rent
amount: 10000
apparel_expense_entry:
id: 2
created_at: 2008-02-20 00:00:00
description: A pair of Jeans
amount: 1000.50
movies_expense_entry:
id: 3
created_at: 2007-12-20 00:00:00
description: Taare Zameen Par
amount: 300

Next, we need to update the controller test to check if the records are displayed in order. For that we inspect the controller’s @expenses variable and assert that they are retrieved order by date i.e. 3 – 1 - 2

def test_list
get :list
assert_response :success
assert_template 'list'
assert_not_nil assigns(:expenses)
assert_equal(@fixtureRecordCount, assigns(:expenses).size )
assert_equal(3, assigns(:expenses)[0].id,not ordered by date”)
assert_equal(1, assigns(:expenses)[1].id)
assert_equal(2, assigns(:expenses)[2].id)
end


Run the tests to see if it fails. It does. That validates our test. Next some research on how to implement this, how do we tell paginate to order the records by “created_at” ? Search search.. online... can't find it. Once again the book comes to the rescue, Pg 352, Section 17.7 Hurray!
def list
@expense_pages, @expenses = paginate :expenses, :order=>'created_at', :per_page => 10
end


The :order key-value pair is all we need. It’s really that simple!!! I even tried out via the browser – It just works!! I LOOVVEEE Rails!!!
Do it again for the credits page. TODOs are done! Quick run of all tests. Green!

Alrighthy! now we got to build ourselves a balance tracker.

file: /test/unit/balance_tracker_test.rb

require File.dirname(__FILE__) + '/../test_helper'

class BalanceTrackerTest < Test::Unit::TestCase
fixtures :credits, :expenses
def test_balance
assert_in_delta(19700, BalanceTracker.balance, 0.01 )

Credit.create(:description=>"D", :amount=>300.50)
assert_in_delta(20000.5, BalanceTracker.balance, 0.01 )

Expense.create(:description=>"E", :amount=>10000)
assert_in_delta(10000.5, BalanceTracker.balance, 0.01 )
end
end



file: /app/models/balance_tracker.rb
class BalanceTracker
def BalanceTracker.balance
models = Credit.find_by_sql("select sum(amount) as totalAmount from Credits");
fTotalCredit = models[0].attributes["totalAmount"].to_f

models = Expense.find_by_sql("select sum(amount) as totalAmount from Expenses");
return fTotalCredit - models[0].attributes["totalAmount"].to_f;
end
end


Done. Now we have everything to go and work on our acceptance test fixtures to make the actionfixture table and second rowfixture table pass.

file: /AcceptanceTests/track_balance.rb

require File.dirname(__FILE__) + '/../test/test_helper'
require 'inflow_controller'
require 'fit/fixture'

class CreditsHelper < Test::Unit::TestCase
def setup
Credit.delete_all
@controller = InflowController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_dummy
end
end
class ExpensesHelper < Test::Unit::TestCase
def setup
Expense.delete_all
@controller = OutflowController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_dummy
end
end
module AcceptanceTests
class TrackBalance < Fit::Fixture

#enter attributes
attr_accessor :description, :amount, :select_entry

def initialize
@creditHelper = CreditsHelper.new "test_dummy"
@creditHelper.setup

@expenseHelper = ExpensesHelper.new "test_dummy"
@expenseHelper.setup
super
end

#press methods
def add_credit_entry
@creditHelper.get :new
@creditHelper.post :create, :credit => {:description => @description, :amount => @amount}
end
def delete_credit_entry
@creditHelper.post :destroy, :id => getID_ForSelectedEntry(Credit)
end
def add_expense_entry
@expenseHelper.get :new
@expenseHelper.post :create, :expense => {:description => @description, :amount => @amount}
end
def delete_expense_entry
@expenseHelper.post :destroy, :id => getID_ForSelectedEntry(Expense)
end
#check methods
def current_balance
BalanceTracker.balance
end

private
def getID_ForSelectedEntry model
obSelectedEntry = model.find(:all, :order => "id")[@select_entry.to_i - 1]
obSelectedEntry.id
end
end
end


file: /AcceptanceTests/get_inflow_records.rb

puts File.dirname(__FILE__) + '\get_inflow_records'
require File.dirname(__FILE__) + '\get_inflow_records'
require 'fit/row_fixture'
module AcceptanceTests
class GetOutflowRecords < Fit::RowFixture
def query
recordset = []
Expense.find(:all, :order=>'created_at').each{|x|
recordset << InflowRecord.new(x.created_at, x.description, x.amount)
}
end
def get_target_class
InflowRecord
end
end
end


Our Reward !!!

ShowMeTheMoney-15 - Aptana + Radrails.. Hell Yeah!!!

Honey, I got a new IDE!!
Author-Update-Feb2010: Netbeans with the Ruby bundle is my IDE of choice now.. Haven't seen if Aptana has also progressed since then. Netbeans has auto-complete and feels snappier than Radrails.

I was getting kind of opening the same set of files everytime in SciTe and the tacky way of switching between tabs and so on... Of course it had "sessions".. but I didnt remember to save them at the exact points of time.. All in all not happy.
Then I came across Aptana as a free IDE for ROR. With the now inherent apprehension of switching to something new.. I try it on another machine and it turned out pretty well.. Then I went Live on my dev machine. So here's how I went about it
  1. First stop http://www.aptana.com/rails. Read all about it
  2. Download the community edition from here. Around 90 Megs
  3. Next we install it. Now we need the plugin for Rails for all the goodness.
    • Main Menu > Software Updates > Find and Install... > Search for new features to install.
    • Select 'Aptana: RadRails Development Environment' in the list and click Finish
  4. However the auto-get/update service doesn't want to play with me. It showed that it was unable to reach a certain http location. I copy-pasted that url into the browser and it was reachable. I also found the Aptana page offers on manual RadRails installation
  5. So I did that. I think Aptana restarted once. Next we need to open our existing project in this.. After a few minutes of fiddling..
    • Main Menu > File > Import...
    • Select 'Existing Folder as new project' in the 'Others' category and click Next
    • Now browse and select our ShowMeTheMoney project folder and click Finish
  6. Yodel! Can you feeel the loooovvvveee tooonnaaaaiiigghhhttt ?

I can run my tests with a click of the toolbar button shown above.. and Green bars in the GUI!

We got the project hierarchy on the left, integrated refactoring and automated test support and... I'm speechless with joy (for a few minutes.. before I found something that didnt work.. but hey that's ok. As a developer, I can tolerate that does its basic job well... I found another way to do it in the IDE.) And it does ROCK!!

Aptana is apparently a stripped down version of the Eclipse framework. So Eclipse users should feel at home.. as a Visual Studio user I found myself hunting for keystrokes to switch open editor panes and stuff.. needs some more time at the helm.
But good work.. I pat myself on the back. SciTe has been a trusty steed... but now it has earned its rest at my stable of old tools.

ShowMeTheMoney-14 - The Source Code so far..

This is a listing of all the source files produced till now in this iteration (Note: Some of it comes from the next couple of posts i.e. the pagination plugin. It is more of a snapshot after post ShowMeTheMoney-6


file: /test/unit/expense_test.rb

require File.dirname(__FILE__) + '/../test_helper'

class ExpenseTest < Test::Unit::TestCase
fixtures :expenses
def setup
@firstExpense = getFirstEntry
@fixtureRecordCount = 3
end
def test_create
sDescription = 'Rent'
fAmount = 10000

obExpenseEntry = Expense.new
obExpenseEntry.description = sDescription
obExpenseEntry.amount = fAmount
assert obExpenseEntry.save, obExpenseEntry.errors.full_messages.join("; ")

assert_equal @fixtureRecordCount + 1, Expense.count
obExpenseEntry.reload

assert_not_nil obExpenseEntry.id
assert_not_nil obExpenseEntry.created_at
assert_equal sDescription, obExpenseEntry.description
assert_equal fAmount, obExpenseEntry.amount
end

def test_retrieve
assert_kind_of Expense, @firstExpense
assert_equal expenses(:rent_expense_entry).id, @firstExpense.id
assert_equal expenses(:rent_expense_entry).description, @firstExpense.description
assert_equal expenses(:rent_expense_entry).amount, @firstExpense.amount
assert_equal expenses(:rent_expense_entry).created_at, @firstExpense.created_at
end

def test_saveWithNoDescriptionFails
[nil, " "].each{|value|
@firstExpense.description = " "

assert !@firstExpense.save, "Record saved without description!"
assert_equal 1, @firstExpense.errors.count
assert_equal "can't be blank", @firstExpense.errors.on(:description)
}
end

def test_saveWithNoAmountFails
[nil, " "].each{|value|
@firstExpense.amount = value

assert !@firstExpense.save, "Record saved with non-numeric amount!"
assert_equal 1, @firstExpense.errors.count
assert_equal "is not a number", @firstExpense.errors.on(:amount)
}
end

def test_saveWithZeroOrNegativeAmountFails
[0, -24.00].each{|value|
@firstExpense.amount = value

assert !@firstExpense.save, "Credit of amount 0 saved"
assert_equal 1, @firstExpense.errors.count
assert_equal "should be greater than 0", @firstExpense.errors.on(:amount)
}
end

def test_destroy
assert_nothing_raised { getFirstEntry }

getFirstEntry.destroy

assert_raise (ActiveRecord::RecordNotFound) { getFirstEntry }
end


private
def getFirstEntry
Expense.find(1)
end

end


file: /app/models/expense.rb
class Expense < ActiveRecord::Base
validates_presence_of :description
validates_numericality_of :amount

protected
def validate
if (errors.on(:amount).nil?) && (amount <= 0)
errors.add(:amount, "should be greater than 0")
end
end
end



file: /test/functional/outflow_controller_test.rb
require File.dirname(__FILE__) + '/../test_helper'
require 'outflow_controller'

# Re-raise errors caught by the controller.
class OutflowController; def rescue_action(e) raise e end; end

class OutflowControllerTest < Test::Unit::TestCase
fixtures :expenses
def setup
@controller = OutflowController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@fixtureRecordCount = 3
end

def test_new
get :new

assert_response :success
assert_template 'new'
assert_not_nil assigns(:expense)
end

def test_list
get :list
assert_response :success
assert_template 'list'
assert_not_nil assigns(:expenses)
assert_equal(@fixtureRecordCount, assigns(:expenses).size )

assert_equal(3, assigns(:expenses)[0].id, "not ordered by date")
assert_equal(1, assigns(:expenses)[1].id)
assert_equal(2, assigns(:expenses)[2].id)
end

def test_index
get :index
assert_response :success
assert_template 'list'
end

def test_create
test_new
sDescription = "movies"; fAmount = 140;
post :create, :expense => {:description=>sDescription, :amount=>fAmount}

assert_response :redirect
assert_redirected_to :action => 'list'
assert_equal @fixtureRecordCount+1, Expense.count

obExpenseEntry = Expense.find(:first, :order=>'id DESC')
assert_equal(sDescription, obExpenseEntry.description)
assert_equal(fAmount, obExpenseEntry.amount)
end

def test_destroy
rentExpenseID = expenses(:rent_expense_entry).id
assert_nothing_raised { Expense.find(rentExpenseID) }

post :destroy, :id => rentExpenseID
assert_equal @fixtureRecordCount - 1, Expense.count
assert_raise(ActiveRecord::RecordNotFound) { Expense.find(rentExpenseID) }

assert_redirected_to :action=>'list'
end
end




file: /app/controllers/outflow_controller.rb
class OutflowController < ApplicationController

def list
@expenses = Expense.paginate :per_page=>10, :page=>params[:page], :order=>'created_at'
end

def index
list
render :action => 'list'
end

def new
@expense = Expense.new
end
def create
@expense = Expense.new(params[:expense])
if (@expense.save)
flash[:notice] = 'Expense entry saved'
redirect_to :action=>'list'
else
render :action=>'new'
end
end

def destroy
Expense.find(params[:id]).destroy
redirect_to :action=>'list'
end
end




file: /app/views/outflow/_form.rhtml

<%= error_messages_for(:expense) %>

<!-- Form for expense -->
<table>
<tr>
<td>
<label for='expense_created_at'>
<%= getAppString(:label_created_at) %>
</label>
</td>
<td>
<%= datetime_select 'expense', 'created_at' %>
</td>
</tr>
<tr>
<td>
<label for='expense_description'>
<%= getAppString(:label_description) %>
</label>
</td>
<td>
<%= text_field 'expense', 'description' %>
</td>
</tr>
<tr>
<td>
<label for='expense_amount'>
<%= getAppString(:label_amount) %>
</label>
</td>
<td>
<%= text_field 'expense', 'amount' %>
</td>
</tr>
</table>


file: /app/views/outflow/new.rhtml


<h1> <%= getAppString(:title_new_expense_page) %></h1>

<% form_tag :action=>'create' do %>
<%= render :partial=>'form' %>
<%= submit_tag getAppString(:label_button_add) %>
<% end %>

<%= link_to getAppString(:label_link_back), :action=>'list' %>


file: /app/views/outflow/list.rhtml

<h1><%= getAppString(:title_list_expense_page)%></h1>

<hr/>
<table class='detailsTable'>
<tr>
<th><%= getAppString(:label_created_at) %></th>
<th><%= getAppString(:label_description) %></th>
<th><%= getAppString(:label_amount) %></th>
</tr>
<% for expense in @expenses %>
<tr>
<td class='transactionDate'> <%= h( expense.created_at.strftime("%d %b %Y" ) ) %> </td>
<td class='transactionDescription'> <%= h( expense.description ) %> </td>
<td class='transactionNumeric'> <%= h( sprintf("%0.2f", expense.amount) ) %> </td>
<td class='delete'><%= button_to getAppString(:label_button_delete), \
{ :action => 'destroy', :id => expense.id },
:confirm => getAppString(:confirm_delete),
:method => :post %></td>
</tr>
<% end %>
</table>
<hr/>

<%= will_paginate @expenses %>

<%= button_to getAppString(:label_button_new), :action => 'new' %>


file: /app/helpers/application_helper.rb

# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
class ResourceManager
@strings = {
:label_created_at => "Date",
:label_description => "Description",
:label_amount => "Amount",
:label_button_new => "New Entry",
:label_button_add => "Create",
:label_button_delete => "Delete",
:label_link_back => "Back",
:title_new_credit_page => "Yay! Enter credit details here..",
:title_new_expense_page => "Sigh! Enter expense details here..",
:title_list_credit_page => "Inflows",
:title_list_expense_page => "Outflows",

:confirm_delete => "Are you sure you wish to delete this ?",

}
def ResourceManager.getString( sKey )
sKey = @strings[sKey] || "Not found!";
end
end

def getAppString( stringID )
ApplicationHelper::ResourceManager.getString(stringID)
end
end