Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 16, 2020 06:17 pm GMT

Lakers < ActiveRecord::Base

Hello again, World!

It has been a while since I wrote my last blog on API > CLI > Space Exploration. 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. 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

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

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.

source 'https://rubygems.org'gem 'sqlite3'gem 'pry'gem 'rake'gem 'activerecord', '<6.0.0'gem 'sinatra-activerecord', :require => 'active_record'
Enter fullscreen mode Exit fullscreen mode

Moving on to our config/environment.rb, we need to establish connection in between our sqlite database and Ruby OOP models.

require 'rake'require 'active_record'require 'date'require 'sinatra/activerecord'require 'bundler/setup'Bundler.requireDir[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)
Enter fullscreen mode Exit fullscreen mode

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.

require_relative './config/environment.rb'require 'sinatra/activerecord/rake'desc 'drop into the Pry console'task :console do     Pry.start end
Enter fullscreen mode Exit fullscreen mode

The sinatra/activerecord/rake provides a set of common administration tasks. When you type rake -T into the Terminal, you will see the following:

rake console                # drop into the Pry consolerake 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 databaserake db:fixtures:load       # Loads fixtures into the current environment's databaserake db:migrate             # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE...rake db:migrate:status      # Display status of migrationsrake db:rollback            # Rolls the schema back to the previous version (specify steps w...rake db:schema:cache:clear  # Clears a db/schema_cache.yml filerake db:schema:cache:dump   # Creates a db/schema_cache.yml filerake 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 databaserake db:seed                # Loads the seed data from db/seeds.rbrake db:setup               # Creates the database, loads the schema, and initializes with t...rake db:structure:dump      # Dumps the database structure to db/structure.sqlrake db:structure:load      # Recreates the databases from the structure.sql filerake db:version             # Retrieves the current schema version number
Enter fullscreen mode Exit fullscreen mode

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.

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   endendclass 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   endendclass CreateFans < ActiveRecord::Migration[5.2]  def change    create_table :fans do |t|      t.string :name      t.integer :age      t.integer :player_id    end   endend
Enter fullscreen mode Exit fullscreen mode

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.

database: db/lakers.sqlite Status   Migration ID    Migration Name--------------------------------------------------   up     20201013230646  Create lakers   up     20201013230742  Create players   up     20201014025021  Create fans
Enter fullscreen mode Exit fullscreen mode

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.

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"  endend
Enter fullscreen mode Exit fullscreen mode

ActiveRecord Associations

I created a new folder directory app/models/ for our Ruby classes laker.rb, player.rb, and fan.rb.

class Laker < ActiveRecord::Base     has_many :players    has_many :fans, through: :playerendclass Player < ActiveRecord::Base    belongs_to :laker     has_many :fansendclass Fan < ActiveRecord::Base     belongs_to :playerend
Enter fullscreen mode Exit fullscreen mode

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.

[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=]
Enter fullscreen mode Exit fullscreen mode

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].

[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">]
Enter fullscreen mode Exit fullscreen mode

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.

[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>]
Enter fullscreen mode Exit fullscreen mode

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.

[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>
Enter fullscreen mode Exit fullscreen mode

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.

[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>
Enter fullscreen mode Exit fullscreen mode

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.

[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>]
Enter fullscreen mode Exit fullscreen mode

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.

> 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 
Enter fullscreen mode Exit fullscreen mode

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.

...
Post Scriptum:

I am currently enrolled in Flatiron's part-time Software Engineering program, and have decided to blog my coding journey. I believe one of the catalyst to becoming a good programmer is to welcome constructive criticism. Feel free to drop a message.

Keep Calm, and Code On.

External Sources:
Active Record Basics
Los Angeles Lakers


Original Link: https://dev.to/codinghall/lakers-activerecord-base-2nj9

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To