Querying Expenses

Let’s turn our attention to the final piece of the puzzle: querying expenses back from the database. First, you’ll need a failing spec that records a few expenses into the ledger, with some of the expenses on the same date. Querying for that date should return only the matching expenses.

Add the following specs inside the RSpec.describe block in spec/integration/app/ ledger_spec.rb:

06-integration-specs/09/expense_tracker/spec/integration/app/ledger_spec.rb
 describe​ ​'#expenses_on'​ ​do
 it​ ​'returns all expenses for the provided date'​ ​do
  result_1 = ledger.record(expense.merge(​'date'​ => ​'2017-06-10'​))
  result_2 = ledger.record(expense.merge(​'date'​ => ​'2017-06-10'​))
  result_3 = ledger.record(expense.merge(​'date'​ => ​'2017-06-11'​))
 
 expect​(ledger.expenses_on(​'2017-06-10'​)).to contain_exactly(
  a_hash_including(​id: ​result_1.expense_id),
  a_hash_including(​id: ​result_2.expense_id)
  )
 end
 it​ ​'returns a blank array when there are no matching expenses'​ ​do
 expect​(ledger.expenses_on(​'2017-06-10'​)).to eq([])
 end
 end

The overall flow—adding three expenses and then searching for two of them by date—looks similar to what we did in the acceptance spec in Querying the Data. Here, you’re testing at the level of the individual Ledger object, rather than the entire app.

We’re using another composable matcher here to combine two matchers you’ve used before—contain_exactly and a_hash_including—into a new matcher that describes a nested data structure.

Run the spec file to make sure both specs fail:

 $ ​​bundle exec rspec spec/integration/app/ledger_spec.rb
 
 Randomized with seed 3824
 
 ExpenseTracker::Ledger
  #record
  with a valid expense
  successfully saves the expense in the DB
  when the expense lacks a payee
  rejects the expense as invalid
  #expenses_on
  returns a blank array when there are no matching expenses (FAILED - 1)
  returns all expenses for the provided date (FAILED - 2)
 
 Failures:
 
  1) ExpenseTracker::Ledger#expenses_on returns a blank array when there
  are no matching expenses
  Failure/Error: expect(ledger.expenses_on(2017-06-10’)).to eq([])
 
  expected: []
  got: nil
 
  (compared using ==)
  # ./spec/integration/app/ledger_spec.rb:59:in ‘block (3 levels) in
  <module:ExpenseTracker>’
  # ./spec/support/db.rb:9:in ‘block (3 levels) in <top (required)>’
  # ./spec/support/db.rb:9:in ‘block (2 levels) in <top (required)>’
 
  2) ExpenseTracker::Ledger#expenses_on returns all expenses for the
  provided date
  Failure/Error:
  expect(ledger.expenses_on(2017-06-10’)).to contain_exactly(
  a_hash_including(id: result_1.expense_id),
  a_hash_including(id: result_2.expense_id)
  )
 
  expected a collection that can be converted to an array with
  ‘#to_ary‘ or ‘#to_a‘, but got nil
  # ./spec/integration/app/ledger_spec.rb:52:in ‘block (3 levels) in
  <module:ExpenseTracker>’
  # ./spec/support/db.rb:9:in ‘block (3 levels) in <top (required)>’
  # ./spec/support/db.rb:9:in ‘block (2 levels) in <top (required)>’
 
 Finished in 0.02766 seconds (files took 0.16616 seconds to load)
 4 examples, 2 failures
 
 Failed examples:
 
 rspec ./spec/integration/app/ledger_spec.rb:58 #
 ExpenseTracker::Ledger#expenses_on returns a blank array when there are no
 matching expenses
 rspec ./spec/integration/app/ledger_spec.rb:47 #
 ExpenseTracker::Ledger#expenses_on returns all expenses for the provided date
 
 Randomized with seed 3824

Now, you’re ready to implement the logic to make the spec pass. The Ledger class will need to query the database’s expenses table for rows that have a matching date. You can use Sequel’s where method to filter by the date field.

In the exercises at the end of the previous chapter, you defined an empty expenses_on method for the Ledger class. Fill in its body with the following code:

06-integration-specs/09/expense_tracker/app/ledger.rb
 def​ expenses_on(date)
  DB[​:expenses​].where(​date: ​date).all
 end

Your integration specs should pass now:

 $ ​​bundle exec rspec spec/integration/app/ledger_spec.rb
 
 Randomized with seed 22267
 
 ExpenseTracker::Ledger
  #record
  with a valid expense
  successfully saves the expense in the DB
  when the expense lacks a payee
  rejects the expense as invalid
  #expenses_on
  returns all expenses for the provided date
  returns a blank array when there are no matching expenses
 
 Finished in 0.01832 seconds (files took 0.16797 seconds to load)
 4 examples, 0 failures
 
 Randomized with seed 22267

At this point, all the logic and specs are implemented at every layer of the app. It’s time to see if our outermost acceptance spec passes yet. Let’s run the entire suite:

 $ ​​bundle exec rspec
 
 Randomized with seed 21580
 F............
 
 Failures:
 
  1) Expense Tracker API records submitted expenses FIXED
  Expected pending ’Need to persist expenses’ to fail. No error was
  raised.
  # ./spec/acceptance/expense_tracker_api_spec.rb:22
 
 Finished in 0.04844 seconds (files took 0.22185 seconds to load)
 13 examples, 1 failure
 
 Failed examples:
 
 rspec ./spec/acceptance/expense_tracker_api_spec.rb:22 # Expense Tracker
 API records submitted expenses
 
 Randomized with seed 21580

RSpec is listing one failure, but says the spec has been fixed. Back in Saving Your Progress: Pending Specs, you marked the acceptance spec as pending because it wasn’t yet passing. That means it’ll pass as soon as you remove the pending line.

Search inside spec/acceptance/expense_tracker_api_spec.rb for the line containing the word pending, and delete the whole line. Once that’s done, your acceptance specs will pass, too:

 $ ​​bundle exec rspec
 
 Randomized with seed 14629
 .............
 
 Finished in 0.04986 seconds (files took 0.21623 seconds to load)
 13 examples, 0 failures
 
 Randomized with seed 14629

Nice! Over the span of just a few chapters, you’ve implemented a working JSON API to track expenses. At every step of the process, you’ve used RSpec to guide your design, catch regressions, and build your app with confidence.