Skip to content

Commit

Permalink
book store psql challenge complete
Browse files Browse the repository at this point in the history
  • Loading branch information
georgebarrett committed Apr 25, 2023
1 parent ea7958a commit 2a0fbac
Show file tree
Hide file tree
Showing 11 changed files with 430 additions and 0 deletions.
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
7 changes: 7 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

source "https://rubygems.org"

# gem "rails"

gem "rspec", "~> 3.12"
26 changes: 26 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.5.0)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.12.0)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-support (3.12.0)

PLATFORMS
arm64-darwin-21

DEPENDENCIES
rspec (~> 3.12)

BUNDLED WITH
2.4.12
16 changes: 16 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require_relative './lib/database_connection'
require_relative './lib/book_repository'

# We need to give the database name to the method `connect`.
DatabaseConnection.connect('book_store')

# Perform a SQL query on the database and get the result set.
# sql = 'SELECT id, title FROM books;'
# result = DatabaseConnection.exec_params(sql, [])

book_repository = BookRepository.new

# Print out each record from the result set .
book_repository.all.each do |record|
p record.id + " - " + record.title + " - " + record.author_name
end
177 changes: 177 additions & 0 deletions book_store_design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# {{BOOK STORE}} Model and Repository Classes Design Recipe

_Copy this recipe template to design and implement Model and Repository classes for a database table._

## 1. Design and create the Table

already done
```
## 2. Create Test SQL seeds
Your tests will depend on data stored in PostgreSQL to run.
If seed data is provided (or you already created it), you can skip this step.
```sql
-- EXAMPLE
-- (file: spec/seeds.sql)
-- Write your SQL seed here.
-- First, you'd need to truncate the table - this is so our table is emptied between each test run,
-- so we can start with a fresh state.
-- (RESTART IDENTITY resets the primary key)
TRUNCATE TABLE books RESTART IDENTITY; -- replace with your own table name.
-- Below this line there should only be `INSERT` statements.
-- Replace these statements with your own seed data.
INSERT INTO books (title, author_name) VALUES ('Piranesi', 'Suzanne Clarke');
INSERT INTO books (title, author_name) VALUES ('Born a Crime', 'Trevor Noah');
```

Run this SQL file on the database to truncate (empty) the table, and insert the seed data. Be mindful of the fact any existing records in the table will be deleted.

```bash
psql -h 127.0.0.1 book_store_test < seeds.sql
```

## 3. Define the class names

Usually, the Model class name will be the capitalised table name (single instead of plural). The same name is then suffixed by `Repository` for the Repository class name.

```ruby
# EXAMPLE
# Table name: books

# Model class
# (in lib/book.rb)
class Book
end

# Repository class
# (in lib/book_repository.rb)
class BookRepository
end
```

## 4. Implement the Model class

Define the attributes of your Model class. You can usually map the table columns to the attributes of the class, including primary and foreign keys.

```ruby
# EXAMPLE
# Table name: books

# Model class
# (in lib/book.rb)

class Book

# Replace the attributes by your own columns.
attr_accessor :id, :title, :author_name
end

# The keyword attr_accessor is a special Ruby feature
# which allows us to set and get attributes on an object,
# here's an example:
#
# student = Student.new
# student.name = 'Jo'
# student.name
```

*You may choose to test-drive this class, but unless it contains any more logic than the example above, it is probably not needed.*

## 5. Define the Repository Class interface

Your Repository class will need to implement methods for each "read" or "write" operation you'd like to run against the database.

Using comments, define the method signatures (arguments and return value) and what they do - write up the SQL queries that will be used by each method.

```ruby
# EXAMPLE
# Table name: books

# Repository class
# (in lib/book_repository.rb)

class BookRepository

# Selecting all records
# No arguments
def all
# Executes the SQL query:
# SELECT id, title, author_name FROM books;

# Returns an array of Book objects.
end

# Gets a single record by its ID
# One argument: the id (number)
end
```

## 6. Write Test Examples

Write Ruby code that defines the expected behaviour of the Repository class, following your design from the table written in step 5.

These examples will later be encoded as RSpec tests.

```ruby
# EXAMPLES

# 1
# Get all students

repo = BookRepository.new

books = repo.all

books.length # => 2

books[0].id # => 1
books[0].title # => 'Piranesi'
books[0].author_name # => 'Suzanne Clarke'

books[1].id # => 2
books[1].title # => 'Born a Crime'
books[1].author_name # => 'Trevor Noah'


```

Encode this example as a test.

## 7. Reload the SQL seeds before each test run

Running the SQL code present in the seed file will empty the table and re-insert the seed data.

This is so you get a fresh table contents every time you run the test suite.

```ruby
# EXAMPLE

# file: spec/book_repository_spec.rb

def reset_books_table
seed_sql = File.read('spec/seeds.sql')
connection = PG.connect({ host: '127.0.0.1', dbname: 'books_test' })
connection.exec(seed_sql)
end

describe BookRepository do
before(:each) do
reset_books_table
end

# (your tests will go here).
end
```

## 8. Test-drive and implement the Repository class behaviour

_After each test you write, follow the test-driving process of red, green, refactor to implement the behaviour._

5 changes: 5 additions & 0 deletions lib/book.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Book

# Replace the attributes by your own columns.
attr_accessor :id, :title, :author_name
end
24 changes: 24 additions & 0 deletions lib/book_repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require_relative './book'
require_relative './database_connection'

class BookRepository
def all
sql = 'SELECT id, title, author_name FROM books;'
result = DatabaseConnection.exec_params(sql, [])

books = []

result.each do |record|
book = Book.new

book.id = record['id']
book.title = record['title']
book.author_name = record['author_name']

books << book
end

return books

end
end
28 changes: 28 additions & 0 deletions lib/database_connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# file: lib/database_connection.rb

require 'pg'

# This class is a thin "wrapper" around the
# PG library. We'll use it in our project to interact
# with the database using SQL.

class DatabaseConnection
# This method connects to PostgreSQL using the
# PG gem. We connect to 127.0.0.1, and select
# the database name given in argument.
def self.connect(database_name)
@connection = PG.connect({ host: '127.0.0.1', dbname: database_name })
end

# This method executes an SQL query
# on the database, providing some optional parameters
# (you will learn a bit later about when to provide these parameters).
def self.exec_params(query, params)
if @connection.nil?
raise 'DatabaseConnection.exec_params: Cannot run a SQL query as the connection to'\
'the database was never opened. Did you make sure to call first the method '\
'`DatabaseConnection.connect` in your app.rb file (or in your tests spec_helper.rb)?'
end
@connection.exec_params(query, params)
end
end
31 changes: 31 additions & 0 deletions spec/book_repository_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require 'book_repository'
require 'database_connection'

def reset_books_table
seed_sql = File.read('spec/seeds.sql')
connection = PG.connect({ host: '127.0.0.1', dbname: 'book_store_test' })
connection.exec(seed_sql)
end

describe BookRepository do
before(:each) do
reset_books_table
end

it "returns two books" do
repo = BookRepository.new

books = repo.all

expect(books.length).to eq 2 # => 2

expect(books.first.id).to eq '1' # => 1
expect(books.first.title).to eq 'Piranesi' # => 'Piranesi'
expect(books.first.author_name).to eq 'Suzanne Clarke' # => 'Suzanne Clarke'

expect(books[1].id).to eq "2"
expect(books[1].title).to eq 'Born a Crime'
expect(books[1].author_name).to eq 'Trevor Noah'
end
end

8 changes: 8 additions & 0 deletions spec/seeds.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

TRUNCATE TABLE books RESTART IDENTITY; -- replace with your own table name.

-- Below this line there should only be `INSERT` statements.
-- Replace these statements with your own seed data.

INSERT INTO books (title, author_name) VALUES ('Piranesi', 'Suzanne Clarke');
INSERT INTO books (title, author_name) VALUES ('Born a Crime', 'Trevor Noah');
Loading

0 comments on commit 2a0fbac

Please sign in to comment.