Tuesday, 30 March 2010

Rails - Day 13 -ActiveRecord Relationships Part II

Rails -  Day 10 - Testing Rails Controllers
Rails -  Day 12 -ActiveRecord Relationships Part I

In the last post, we started to define the relationships for a mock expenses tracking application that you can clone from github here.

We are creating a pseudo application to record the expenses that might incur for a small business.

We have created a mock UI below which we are using to drive the structure of the Expense model.

In the last post, we defined how an Expense model object is associated with an ExpenseType model object.

In the screenshot above, you can see that there are 3 inputs for the NetVat and Gross values of every expense that is entered into the system.

An ExpensePayment model object will be created to capture this logic and will be associated with the parent Expense model object. The next step is to generate the files that will aid us in our endeavour of creating an ExpensePayment model with the following command:

ruby script/generate model ExpensePayment  

This generates the following files:

We want to define the usage of this new object before we create the class itself.

The generator has kindly hinted that this a good idea and has created a test/unit/expense_payment_test.rb file which we will update with the following test case to ensure that we can at least store and display the correct payment attributes in the format we expect:

One thing of note is the crazy include ActionView::Helpers statement which appears after the class declaration.  The include method will mixin a module's methods at the instance level that will become methods of the instance.  You can see above that we have 2 calls to number_to_currency at the end of the test case.  The number_to_currency method is mixed in to the ExpensePaymentTest class instance from the ActionView::Helpers module as is defined in the include statement.  Mixins are ridiculously easy in ruby compared to the hoops you have to jump through in C# to achieve a similar experience.  As the methods suggest, they will format the two totals from the ExpensePayment object.

This test will not pass as the ExpensePayment object has not yet been created but as we are using TDD, the behaviour of the object can be defined up front before running the migration.  If we look at the UI at the top of the page, you should see 3 input fields for netvat and gross.  It is safe to say that only net attribute needs persisted to the database as we can derive both the gross and the VAT incurred from the net total.  You can see below how we set this value in the above code with the lines:

payment = ExpensePayment.new

payment.net = 3.23
payment.vat_at_payment = 17.5

We are also storing a vat_at_payment attribute because if you live in a crazy mixed up place like the UK, the VAT rate might vary from month to month as the government tries to recoup all the money it has spent dishing out to banks and waging wars against third world countries.  

We then create the following migration to create the expense_payments table.  The expense_payments table will only contain the net and vat_at_payment fields:

That still leaves the vat and gross attributes which are formatting to assert their values in the above test.  The model is updated to add these attributes:

If we run the above test, it now passes.  We have created a usable ExpensePayment object using the power of TDD.  We should of course create test cases for nil objects but I will leave that as an exercise for you the reader.

belongs_to and has_one newbie Confusion

We now want to define the relationship between Expense and ExpensePayment.

In the last post, we illustrated the ActiveRecord belongs_to relationship.  There is a probably lesser known has_one relationship that to be honest, I had a bit of a problem getting my head around.  Searching the web for answers seemed to suggest this is a bit of a grey area for newbies like myself. The belongs_to relationship seems to be a better fit when linking to pre-existing objects.  For example, in the previous post, it was stated that an Expense belongs_to ExpenseType.  The ExpenseType objects were created before linking the Expense to the ExpenseType.

So in this example, we are going to say that an Expense has_one ExpensePayment.  has_one makes more sense in this scenario because when we create an Expense, we are also creating an ExpensePayment object at the same time.  

As always, we write a test to flesh out our desired behaviour:

Next a migration is created to add the expense_id foreign key to the expense_payments table with the command:

ruby script/generate migration add_expense_id_to_expense_payment

This newly created migration is updated to the following:

The development and test environments are migrated with the following consecutive commands: 

rake db:migrate
rake environment RAILS_ENV=test db:migrate

Then the Expense model is updated to the following:

And lastly the ExpensePayment model is updated to the following:

This slightly confusing and contradictory relationship is now defined.  The tests now pass and we can progress to the UI.

That is it for ActiveRecord relationships which can be slightly ambiguous in places.  I have not mentioned many to many relationships but there are exactly 1,043,293 examples on the web for you to discover. 

Next up, I want to mention forms in Rails.

No comments:

Post a Comment