Live Blogging a Book Review App

Part I - rails new

For the past few years I have been posting short reviews on all the books I read on Facebook. My current process is problematic for a few reasons. First, I probably have friends on Facebook who do not care to read my book reviews in their newsfeeds. Second, my current system for posting reviews is a lost opportunity because I am not systematically collecting data that could be used to inform future reading habits.

Screen Shot 2017-10-23 at 8.24.43 PM.png

I know generally that I read a mostly nonfiction, mostly historical and political, written mostly by white male authors. I would like a way to save my book reviews so that I have the ability to calculate various metrics over time, because I am cool like that. I would also like my friends, who are interested, to be able to comment on my reviews. I also intend to continue to post my reviews on social media, but I think sharing a url to a review in this app rather than the full text of a review will be less intrusive in my friends' newsfeeds.

I believe there is a straightforward software solution to my needs and as a result I have decided to invite you all on a journey. As with any personal project, I expect to learn quite a bit in the process of creating an app to solve the aforementioned problem. This will be the first of hopefully many (no promises) posts that I write as I develop the bookshelf app.

SCHEMA BrainstoRM

Screen Shot 2017-10-23 at 8.14.14 PM.png

When I start an app I like to draft a database schema before I start coding. In this case I'm imagining a few tables shown above. First, I'll have a book table, which will be the main model in this App. My book model is going to store some basic information along with Amazon and Audible affiliate links so I can become rich. 

I will also have a review model, which will store the text review I write about a given book. I am imagining a one to one relationship between books and reviews.

To help track interesting metrics I also will have a table for storing genres and authors. Both the author and genre will have books. I could have a book_genres or author_books table, if I anticipate needing the functionality of having a book in multiple genres or with multiple authors, but I am okay with limiting myself to one genre per book and I cannot remember the last co-written book I've read which makes me think that it will not be a common enough use case to merit this type of database design.

Next, I'll want to have comments because I want my friends to be able to comment on my reviews. Reviews will have many comments. I do not like anonymous internet comments, so I will also need to have a way to store people's identities. To that end I'll also have a users table.

I will also say that users have many reviews, although I will be the only user writing reviews I can imagine a subsequent iteration in which we try to compete with GoodReads and implement some sort of multi-tenancy platform where all users can post reviews of the books they read. It's also a good excuse to walk through an omniauth implementation with the Devise gem.

For the remainder of this post, we'll focus on the nitty gritty setup I run when I start a new Rails application.

Rails NeW

To start our new book review application we will run the following command:

rails new bookshelf -d postgresql --skip-turbolinks --skip-spring -T

This will create a new rails application called "bookshelf" using a Postgresql database (easy to deploy to Heroku later), skipping turbolinks and spring (we can add them later if we need the, and I don't think we'll need them) and skipping the test directory. Do not worry, we'll be tdd-ing this app, I just prefer RSpec to minitest.

 Behold the glory that is rails new

Behold the glory that is rails new

I have mentioned Ryan Flach's common rails setup gist a few times on this blog. I still swear by it for all my rails new needs. Let's change directories to our new bookshelf folder.

$ cd bookshelf

Version Control

I'll be using git for version control for this project, which I may or may not reference in subsequent posts. I will try to save various iterations of this app on GitHub so readers can see the progression or pick up from a certain point of particular point along the way.

I've already created a repository on GitHub, so now to initialize a new git repository in my bookshelf directory I run:

$ git init

Next I add a remote to my repository, which points my local repository to my repository on GitHub.

$ git remote add origin https://github.com/<your_github_username>/<repository_name>.git

After that I add all the changes I've made by running rails new.

$ git add .

Next, I add a commit message.

$ git commit -m "Initial commit"

Finally, I push my commit to GitHub. I use the force flag to override the readme I accidentally created when I created my repository on GitHub.

$ git push --force

It is a bad idea to get in the habit of using the --force flag because you may unintentionally overwrite code on GitHub.

Gems

Gems are code libraries that add additional functionality to a Ruby project. I will open my project's Gemfile add the following gems: figaro, rspec-rails, capybara, launchy, shoulda-matchers, database_cleaner, factory_gril_rails, which again you read about here.

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.2'
# Use postgresql as the database for Active Record
gem 'pg', '~> 0.18'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Store environment variables securely across my app
gem 'figaro'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'pry' # use pry debugger tool
  gem 'rspec-rails' # use rspec in place of minitest
  gem 'shoulda-matchers' # easy model association and validation testing
  gem 'capybara' # integration testing
  gem 'launchy' # open the app in a browser for additional debugging
  gem 'database_cleaner' # clean the db between tests
  gem 'factory_girl_rails' # generate db objects wihout repetitive code in tests
  gem 'simplecov', require: false # generate test coverage reports
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Next I'll run the following commands in order:

Run bundle to install the gems we just added

$ bundle

Setup RSpec

$ rails g rspec:install

Setup Figaro

$ bundle exec figaro install

Make spec support folder

$ mkdir spec/support

Make a factory_girl config file

$ touch spec/support/factory_gril.rb

Make a factories folder to hold all our forthcoming factories

$ mkdir spec/factories

Make a database_cleaner config file

$ touch spec/support/database_cleaner.rb

I will add the following configurations to the following files:

# spec/rails_helper.rb

require 'capybara/rails'

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

# Uncomment Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

And this:

# spec/support/factory_girl.rb

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

And this:

# spec/support/database_cleaner.rb

RSpec.configure do |config|

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

Wrapping up

Finally, we'll commit the work we've done! Nailed it. You can find everything we've discussed in this branch of my bookshelf repo.

In my next post we'll start with some of the actual implementation of our bookshelf application. Thanks again to Ryan Flach for an awesome Rails setup gist.

If you like the idea of me writing a series of detailed technical posts about this side project, let me know. Thanks for reading!