Lakers < ActiveRecord::Base
Tags: ruby, activerecord, sql, codenewbie
Published: 2020-10-16 18:17:45

Hello again, World!

It has been a while since I wrote my last blog on [API > CLI > Space Exploration](https://dev.to/codinghall/api-cli-space-exploration-108f). I was sooo exhilarated in passing my first capstone project, and now slowly diving into the world of SQL (Structured Query Language), ORM (Object Relational Mapping), Dynamic ORMs, Rake and Active Record. 

Understanding SQL is imperative to managing databases. Now I know how to Create, Read, Update and Delete (CRUD) database inputs. Special thanks to [DB Browser for SQLite](https://sqlitebrowser.org/). After one week of exposure, I feel comfortable with basic SQL queries, aggregate functions, complex joins, grouping and sorting data. ORM is where SQL (the database systems) and Ruby Object-Oriented Programming (OOP) communicate with one another. Ruby does not store or manage this data directly. However, with the amazing Ruby gem `sqlite3` and a database connection being fully established, it allows us to CRUD our database inputs with Ruby OOP methods. Persisting more data will result in a more complex application, and ORM design pattern is essential to the implementation effort. I find meta-programming for abstract ORM is highly challenging (and fun!). As a novice programmer, this meta-programming exercise is my first exposure, and a great foundation to fully grasp how Active Record works in the background.

####**Active Record**

Active Record creates a mapping between our database and Ruby models. In the MVC (Model-View-Controller), Active Record is the model responsible for providing the ORM framework. I will get to the Active Record mechanics by building a case study from scratch.

![winners](https://www.nba.com/lakers/sites/lakers/files/championsheaderimage-4-1540x700.jpg)

We all know the Lakers recently won the 2020 NBA champion (YAYYY!), making its historic return to the top of the league since 2009 and 2010 when Kobe led the Lakers back to back titles. Today's Active Record is derived from the Lakers stats. We are going to focus on AR Associations mainly on `belongs_to`, `has_many`, and `has_many :through`. I provide basic schema and structure of the intended object relationships and database.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i9tzzd5s4dod6amt48lw.png)

Lakers `has_many` Players. Lakers `has_many` Fans `:through` Player.
Player `belongs_to` Lakers team. Player `has_many` Fans. 
Fan `belongs_to` Player.

####**Gemfile, Environment and Rakefile**

I set up our required gems on `Gemfile`, then `bundle install`.
```ruby
source 'https://rubygems.org'

gem 'sqlite3'
gem 'pry'
gem 'rake'
gem 'activerecord', '<6.0.0'
gem 'sinatra-activerecord', :require => 'active_record'
```

Moving on to our  `config/environment.rb`, we need to establish connection in between our sqlite database and Ruby OOP models.
```ruby
require 'rake'
require 'active_record'
require 'date'
require 'sinatra/activerecord'
 
require 'bundler/setup'
Bundler.require

Dir[File.join(File.dirname(__FILE__), "../app/models", "*.rb")].each {|f| require f}

ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'db/lakers.sqlite')
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
```
`ActiveRecord::Base.logger`, or SQL logger, provides information at each execution, which becomes useful when users perform database migration often.

Rake is one of the Ruby task management tools. `rake db:migrate` and `rake console` are our most common Rake tasks.
```ruby 
require_relative './config/environment.rb'
require 'sinatra/activerecord/rake'

desc 'drop into the Pry console'
task :console do 
    Pry.start 
end
```
The sinatra/activerecord/rake provides a set of common administration tasks. When you type rake -T into the Terminal, you will see the following:
```ruby
rake console                # drop into the Pry console
rake db:create              # Creates the database from DATABASE_URL or config/database.yml ...
rake db:create_migration    # Create a migration (parameters: NAME, VERSION)
rake db:drop                # Drops the database from DATABASE_URL or config/database.yml fo...
rake db:environment:set     # Set the environment value for the database
rake db:fixtures:load       # Loads fixtures into the current environment's database
rake db:migrate             # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE...
rake db:migrate:status      # Display status of migrations
rake db:rollback            # Rolls the schema back to the previous version (specify steps w...
rake db:schema:cache:clear  # Clears a db/schema_cache.yml file
rake db:schema:cache:dump   # Creates a db/schema_cache.yml file
rake db:schema:dump         # Creates a db/schema.rb file that is portable against any DB su...
rake db:schema:load         # Loads a schema.rb file into the database
rake db:seed                # Loads the seed data from db/seeds.rb
rake db:setup               # Creates the database, loads the schema, and initializes with t...
rake db:structure:dump      # Dumps the database structure to db/structure.sql
rake db:structure:load      # Recreates the databases from the structure.sql file
rake db:version             # Retrieves the current schema version number
```
When I type `rake db:create_migration create_lakers`, a new file will be created under `db/migrate` folder with filename `20201013230646_create_lakers.rb`. Timestamp hierarchy is critical to the naming convention. It is displayed as year-month-date-hour-minute-second. I can now populate the columns and datatypes of our `lakers` table. 

I will also create our second and third tables for `players` and `fans` with `rake db:create_migration create_players` and `rake db:create_migration create_fans`.

Building `lakers` table prior to `players` and `fans` is a better approach. The `lakers` table does not have any foreign keys or dependencies. Both `players` and `fans` have foreign keys and dependencies.
```ruby
class CreateLakers < ActiveRecord::Migration[5.2]
  def change
    create_table :lakers do |t|
        t.string :season
        t.integer :wins
        t.integer :losses
        t.string :coach 
    end 
  end
end

class CreatePlayers < ActiveRecord::Migration[5.2]
  def change
    create_table :players do |t|
      t.string :name
      t.integer :yrs_exp
      t.integer :jersey_number
      t.integer :laker_id
    end 
  end
end

class CreateFans < ActiveRecord::Migration[5.2]
  def change
    create_table :fans do |t|
      t.string :name
      t.integer :age
      t.integer :player_id
    end 
  end
end
```
Once files have been saved, we can execute the `rake db:migrate` command in the terminal. Let's double check by using rake command `rake db:migrate:status`.
```ruby
database: db/lakers.sqlite

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20201013230646  Create lakers
   up     20201013230742  Create players
   up     20201014025021  Create fans
```
We have now successfully created our three tables (lakers, players, fans). Another new file `schema.rb` is also created under the `db` folder. The file is auto-generated as we update our database, and it should not be altered or modified. 
```ruby
ActiveRecord::Schema.define(version: 2020_10_14_025021) do

  create_table "fans", force: :cascade do |t|
    t.string "name"
    t.integer "age"
    t.integer "player_id"
  end

  create_table "lakers", force: :cascade do |t|
    t.string "season"
    t.integer "wins"
    t.integer "losses"
    t.string "coach"
  end

  create_table "players", force: :cascade do |t|
    t.string "name"
    t.integer "yrs_exp"
    t.integer "jersey_number"
    t.integer "laker_id"
  end

end
```

####**ActiveRecord Associations**

I created a new folder directory `app/models/` for our Ruby classes `laker.rb`,  `player.rb`, and `fan.rb`.
```ruby
class Laker < ActiveRecord::Base 
    has_many :players
    has_many :fans, through: :player
end

class Player < ActiveRecord::Base
    belongs_to :laker 
    has_many :fans
end

class Fan < ActiveRecord::Base 
    belongs_to :player
end
```
The class `Laker`, `Player` and `Fan` along with its associations are inheriting AR macros via `ActiveRecord::Base`. Laker `has_many` players (not player). The pluralization naming system is prudent in establishing object relationships. The same goes to the player `has_many` fans, and laker `has_many` fans `through:` player. Note the singular use of player. The use of singularization and pluralization has to semantically align with the intended object relationships.  

This allows the use of Ruby meta-programmed methods as the classes inheriting from `ActiveRecord::Base`.

####**ActiveRecord Macros (or methods)**

We can now execute `rake console`, and practice some of these AR macros.
```ruby
[1] pry(main)> Laker.methods.size
=> 637

[2] pry(main)> Player.methods.size
=> 625

[3] pry(main)> Fan.methods.size
=> 613

[4] pry(main)> Laker.methods - Player.methods
=> [:before_add_for_players,
 :before_add_for_players?,
 :before_add_for_players=,
 :after_add_for_players,
 :after_add_for_players?,
 :after_add_for_players=,
 :before_remove_for_players,
 :before_remove_for_players?,
 :before_remove_for_players=,
 :after_remove_for_players,
 :after_remove_for_players?,
 :after_remove_for_players=]
```
Insane how Ruby gem `Active Record` meta-programmed, and made these methods readily available for us, programmers, to use. Active Record allows us to eliminate the low level programming, and dive right into the implementation methods.

`Laker` class has built-in 637 methods, `Player` class has 625 and `Fan` class has 613! If you are curious with the additional 12 methods `Laker` class has, refer to pry console [4].
```ruby
[5] pry(main)> latest_champ = Laker.new(season: "2019-20", wins: 52, losses: 19, coach: "Frank Vogel")
=> #<Laker:0x00007fc297985a88 id: nil, season: "2019-20", wins: 52, losses: 19, coach: "Frank Vogel">

[6] pry(main)> latest_champ.save
=> true

[7] pry(main)> latest_champ
=> #<Laker:0x00007fc297985a88 id: 1, season: "2019-20", wins: 52, losses: 19, coach: "Frank Vogel">

[8] pry(main)> previous_champ = Laker.create(season: "2009-10", wins: 57, losses: 25, coach: "Phil Jackson")
=> #<Laker:0x00007fc298378d60 id: 2, season: "2009-10", wins: 57, losses: 25, coach: "Phil Jackson">

[9] pry(main)> Laker.all
=> [#<Laker:0x00007fc2983a3cb8 id: 1, season: "2019-20", wins: 52, losses: 19, coach: "Frank Vogel">, #<Laker:0x00007fc2983a1e90 id: 2, season: "2009-10", wins: 57, losses: 25, coach: "Phil Jackson">]
```
On pry console [5] and [7], `Active Record` gives us the pre-programmed reader and writer methods capabilities (getter and setter, respectively). If you recall, we used to build associations manually with Ruby methods `attr_reader`, `attr_writer` and `attr_accessor`.

The `.new` method instantiates an object instance, and will only be stored in the database with `.save` method. Once saved, an `id: 1` value will be provided. Alternatively, by using `.create` method, the id value will be created upon instantiation. Refer to pry console [8].

As programmers, we do not directly manipulate the `id` value to our object instances, and let the Active Record meta-programming methods execute the id values. Similar concept to our previous SQL exercise by having id as a default integer primary key. Our database will be less error prone by self-organizing the data inputs upon execution.
```ruby
[10] pry(main)> lebron = Player.new(name: "LeBron James", yrs_exp: 17, jersey_number: 23)
=> #<Player:0x00007f89c3b99200 id: nil, name: "LeBron James", yrs_exp: 17, jersey_number: 23, laker_id: nil>

[11] pry(main)> latest_champ.players << lebron
=> [#<Player:0x00007f89c3b99200 id: 1, name: "LeBron James", yrs_exp: 17, jersey_number: 23, laker_id: 1>]
```
Shovel `<<` (or push) method is similar to our `.save` method. The one downside of `<<` method, it will return all player instances every single time we execute. Imagine how many rosters currently we have in our team, and our terminal possibly clogging up. With Active Record association methods, Lebron's foreign key `laker_id: 1` is automatically assigned. 
```ruby
[12] pry(main)> ad = Player.all.build(name: "Anthony Davis", yrs_exp: 8, jersey_number: 3)
=> #<Player:0x00007f89c0e539d0 id: nil, name: "Anthony Davis", yrs_exp: 8, jersey_number: 3, laker_id: nil>

[13] pry(main)> ad.save
=> true

[14] pry(main)> ad
=> #<Player:0x00007f89c0e539d0 id: 2, name: "Anthony Davis", yrs_exp: 8, jersey_number: 3, laker_id: nil>
```
In the `Player` class, we can utilize `Player.all` method along with `.build` to instantiate a new player, Anthony Davis. `.build` only instantiates new objects. We need to explicitly `.save` the object in order to store in our database. Once saved, the id primary key will be assigned to `id: 2`.
```ruby
[15] pry(main)> Player.find_or_create_by(name: "Kyle Kuzma", yrs_exp: 3, jersey_number: 0)
=> #<Player:0x00007f89c3d6b538 id: 3, name: "Kyle Kuzma", yrs_exp: 3, jersey_number: 0, laker_id: nil>

[16] pry(main)> Player.find_by(name: "Alex Caruso")
=> nil

[17] pry(main)> alex = latest_champ.players.build(name: "Alex Caruso", yrs_exp: 3, jersey_number: 4)
=> #<Player:0x00007f89c098e288 id: nil, name: "Alex Caruso", yrs_exp: 3, jersey_number: 4, laker_id: 1>

[18] pry(main)> alex.save
=> true

[19] pry(main)> Player.find_by(name: "Alex Caruso")
=> #<Player:0x00007f89c0c555e8 id: 4, name: "Alex Caruso", yrs_exp: 3, jersey_number: 4, laker_id: 1>
```
The `find_or_create_by` method helps you to find an object instance. If not found, the instance will be created and stored in our database. Refer to pry console [15]. Another similar method is `find_by`, we do not yet have Alex Caruso, and hence, the return is nil.

We can now get more creative by chaining the `Laker` class object instance `latest_champ` in order to build its `players`' instance. Active Record assigns Alex Caruso's `laker_id: 1` from `latest_champ`'s id. Quick reminder that the `.build` method does not save the object instance, and thus, the player `id: nil` on pry console [17]. Once saved, and we can double check its existence with `find_by` method.
```ruby
[20] pry(main)> david = alex.fans.create(name: "David", age: 37)
=> #<Fan:0x00007f89bf8c3c28 id: 1, name: "David", age: 37, player_id: 4>

[21] pry(main)> Laker.first.players.find(4).fans
=> [#<Fan:0x00007f89c3b590d8 id: 1, name: "David", age: 37, player_id: 4>]
```
As we went through our `Laker` class and `Player` class, you are most likely getting the overall schema implementation and how to apply CRUD functionality with Ruby OOP logic via `Active Record`. Moving forward to our third `Fan` class, we can create fan's object instance via `Player` class, as well as finding out a specific fan of a player from `Laker`'s recent 2020 championship. 

Let's review our overall folder structure hierarchy. When it comes to consolidating Ruby OOP and database, model structure and organization are critical at its inception.
```ruby
> app/models
    laker.rb
    player.rb
> config
    environment.rb
> db
  > migrate
      20201013230646_create_lakers.rb
      20201013230742_create_players.rb
      20201014025021_create_fans.rb
    lakers.sqlite
    schema.rb
> Gemfile
> Gemfile.lock
> Rakefile 
```
As you saw earlier, we have hundreds of meta-programmed methods and I have only applied a few. Good enough for me to get intimate with our rudimentary objects. I shall end my Active Record case study, and jump right into Sinatra. Woot!

I have this feeling of finally setting myself one level up from plain old Ruby objects (PORO) onto these highly matured objects. I am able to articulate relationships amongst classes without having to worry about building low level programming methods. With `Active Record`, we are operating on a higher level of abstraction. 

External Sources:
[Active Record Basics](https://guides.rubyonrails.org/active_record_basics.html#the-active-record-pattern)
[Los Angeles Lakers](https://www.basketball-reference.com/teams/LAL/)

***
<center>[**fentybit**](https://fentybit.me/) | [GitHub](https://github.com/fentybit) | [Twitter](https://twitter.com/fentybit) | [LinkedIn](https://www.linkedin.com/in/fentybit/)</center>