Saturday, 1 April 2023

Ruby on Rails

I'm loving Ruby on Rails. If you're not aware of it, it's a framework for developing web sites. It's built around a scripting language called Ruby and is highly opinionated. This means, there's a Rails (Ruby on Rails) way to do things, and it makes your life easier if you forego what you know and learn the Rails way instead.

I'm a Comp Sci grad who has learned quite a few languages at different stages, none of them to expert level. Starting to learn Rails has been a real brain freeze because there's so much going on under the covers that I can't see and I have to trust that the framework is doing the right thing while I'm only touching the 'high level' stuff.

Here's an example. What if I'm building a web site that, in part, deals with a list of cars. If you know something about database normalisation, you'll know you should deal with marques (car brands, eg. Toyota) as their own thing. Then you can attach a car brand to a car model (Toyota Camry). So let's create a list of car brands (marques) in a web application using Rails. Sorry if you're a Windows user, this code is from my Mac.

For future reference, these are the software versions I'm using:

 ~                                         
 ▶ cd code     
   
 ~/code                                       
 ▶ cd Rails     
   
 ~/code/Rails                                    
 ▶ ruby -v     
 ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [arm64-darwin22]  
   
 ~/code/Rails                                    
 ▶ rails -v     
 Rails 7.0.4.2  
   
 ~/code/Rails                                    
 ▶ sqlite3   
 SQLite version 3.39.5 2022-10-14 20:58:05  
 Enter ".help" for usage hints.  
 Connected to a transient in-memory database.  
 Use ".open FILENAME" to reopen on a persistent database.  
 sqlite> ^D  
   
 ~/code/Rails                                    
 ▶   

Rails does all the heavy lifting for you. This is a long listing so feel free to scroll and read. Create a Rails web site like this.

 ~/code/Rails                                    
 ▶ rails new Cars  
    create   
    create README.md  
    create Rakefile  
    create .ruby-version  
    create config.ru  
    create .gitignore  
    create .gitattributes  
    create Gemfile  
      run git init from "."  
 Initialized empty Git repository in /Users/prawnhead/Code/rails/Cars/.git/  
    create app  
    create app/assets/config/manifest.js  
    create app/assets/stylesheets/application.css  
    create app/channels/application_cable/channel.rb  
    create app/channels/application_cable/connection.rb  
    create app/controllers/application_controller.rb  
    create app/helpers/application_helper.rb  
    create app/jobs/application_job.rb  
    create app/mailers/application_mailer.rb  
    create app/models/application_record.rb  
    create app/views/layouts/application.html.erb  
    create app/views/layouts/mailer.html.erb  
    create app/views/layouts/mailer.text.erb  
    create app/assets/images  
    create app/assets/images/.keep  
    create app/controllers/concerns/.keep  
    create app/models/concerns/.keep  
    create bin  
    create bin/rails  
    create bin/rake  
    create bin/setup  
    create config  
    create config/routes.rb  
    create config/application.rb  
    create config/environment.rb  
    create config/cable.yml  
    create config/puma.rb  
    create config/storage.yml  
    create config/environments  
    create config/environments/development.rb  
    create config/environments/production.rb  
    create config/environments/test.rb  
    create config/initializers  
    create config/initializers/assets.rb  
    create config/initializers/content_security_policy.rb  
    create config/initializers/cors.rb  
    create config/initializers/filter_parameter_logging.rb  
    create config/initializers/inflections.rb  
    create config/initializers/new_framework_defaults_7_0.rb  
    create config/initializers/permissions_policy.rb  
    create config/locales  
    create config/locales/en.yml  
    create config/master.key  
    append .gitignore  
    create config/boot.rb  
    create config/database.yml  
    create db  
    create db/seeds.rb  
    create lib  
    create lib/tasks  
    create lib/tasks/.keep  
    create lib/assets  
    create lib/assets/.keep  
    create log  
    create log/.keep  
    create public  
    create public/404.html  
    create public/422.html  
    create public/500.html  
    create public/apple-touch-icon-precomposed.png  
    create public/apple-touch-icon.png  
    create public/favicon.ico  
    create public/robots.txt  
    create tmp  
    create tmp/.keep  
    create tmp/pids  
    create tmp/pids/.keep  
    create tmp/cache  
    create tmp/cache/assets  
    create vendor  
    create vendor/.keep  
    create test/fixtures/files  
    create test/fixtures/files/.keep  
    create test/controllers  
    create test/controllers/.keep  
    create test/mailers  
    create test/mailers/.keep  
    create test/models  
    create test/models/.keep  
    create test/helpers  
    create test/helpers/.keep  
    create test/integration  
    create test/integration/.keep  
    create test/channels/application_cable/connection_test.rb  
    create test/test_helper.rb  
    create test/system  
    create test/system/.keep  
    create test/application_system_test_case.rb  
    create storage  
    create storage/.keep  
    create tmp/storage  
    create tmp/storage/.keep  
    remove config/initializers/cors.rb  
    remove config/initializers/new_framework_defaults_7_0.rb  
      run bundle install  
 Fetching gem metadata from https://rubygems.org/...........  
 Resolving dependencies...  
 Using rake 13.0.6  
 Fetching concurrent-ruby 1.2.2  
 Fetching minitest 5.18.0  
 Using builder 3.2.4  
 Using racc 1.6.2  
 Using nio4r 2.5.8  
 Using websocket-extensions 0.1.5  
 Using marcel 1.0.2  
 Using mini_mime 1.1.2  
 Using date 3.3.3  
 Using timeout 0.3.2  
 Using erubi 1.12.0  
 Using bundler 2.3.26  
 Fetching rack 2.2.6.4  
 Using bindex 0.8.1  
 Using crass 1.0.6  
 Using regexp_parser 2.7.0  
 Using public_suffix 5.0.1  
 Using matrix 0.4.2  
 Fetching msgpack 1.7.0  
 Using zeitwerk 2.6.7  
 Using rexml 3.2.5  
 Using rubyzip 2.3.2  
 Using websocket 1.2.9  
 Using io-console 0.6.0  
 Using method_source 1.0.0  
 Using websocket-driver 0.7.5  
 Using net-protocol 0.2.1  
 Using puma 5.6.5  
 Fetching addressable 2.8.2  
 Using thor 1.2.1  
 Fetching sqlite3 1.6.2 (arm64-darwin)  
 Using nokogiri 1.14.2 (arm64-darwin)  
 Using net-pop 0.1.2  
 Using net-smtp 0.3.3  
 Fetching reline 0.3.3  
 Using net-imap 0.3.4  
 Fetching selenium-webdriver 4.8.6  
 Installing rack 2.2.6.4  
 Installing msgpack 1.7.0 with native extensions  
 Installing minitest 5.18.0  
 Fetching loofah 2.20.0  
 Installing addressable 2.8.2  
 Installing reline 0.3.3  
 Using xpath 3.2.0  
 Using mail 2.8.1  
 Fetching rack-test 2.1.0  
 Installing concurrent-ruby 1.2.2  
 Fetching irb 1.6.3  
 Using i18n 1.12.0  
 Using tzinfo 2.0.6  
 Using sprockets 4.2.0  
 Fetching activesupport 7.0.4.3  
 Installing loofah 2.20.0  
 Using rails-html-sanitizer 1.5.0  
 Installing irb 1.6.3  
 Fetching debug 1.7.2  
 Installing rack-test 2.1.0  
 Using capybara 3.38.0  
 Installing activesupport 7.0.4.3  
 Fetching activemodel 7.0.4.3  
 Using rails-dom-testing 2.0.3  
 Using globalid 1.1.0  
 Fetching actionview 7.0.4.3  
 Fetching activejob 7.0.4.3  
 Installing debug 1.7.2 with native extensions  
 Installing activejob 7.0.4.3  
 Installing activemodel 7.0.4.3  
 Fetching activerecord 7.0.4.3  
 Installing actionview 7.0.4.3  
 Using jbuilder 2.11.5  
 Fetching actionpack 7.0.4.3  
 Installing activerecord 7.0.4.3  
 Installing actionpack 7.0.4.3  
 Fetching railties 7.0.4.3  
 Using sprockets-rails 3.4.2  
 Fetching activestorage 7.0.4.3  
 Fetching actioncable 7.0.4.3  
 Fetching actionmailer 7.0.4.3  
 Installing actioncable 7.0.4.3  
 Installing actionmailer 7.0.4.3  
 Installing sqlite3 1.6.2 (arm64-darwin)  
 Installing activestorage 7.0.4.3  
 Installing railties 7.0.4.3  
 Fetching actionmailbox 7.0.4.3  
 Fetching actiontext 7.0.4.3  
 Installing actionmailbox 7.0.4.3  
 Using importmap-rails 1.1.5  
 Using web-console 4.2.0  
 Using stimulus-rails 1.2.1  
 Fetching turbo-rails 1.4.0  
 Installing actiontext 7.0.4.3  
 Fetching rails 7.0.4.3  
 Installing rails 7.0.4.3  
 Installing turbo-rails 1.4.0  
 Installing selenium-webdriver 4.8.6  
 Using webdrivers 5.2.0  
 Using bootsnap 1.16.0  
 Bundle complete! 15 Gemfile dependencies, 72 gems now installed.  
 Use `bundle info [gemname]` to see where a bundled gem is installed.  
      run bundle binstubs bundler  
     rails importmap:install  
 Add Importmap include tags in application layout  
    insert app/views/layouts/application.html.erb  
 Create application.js module as entrypoint  
    create app/javascript/application.js  
 Use vendor/javascript for downloaded pins  
    create vendor/javascript  
    create vendor/javascript/.keep  
 Ensure JavaScript files are in the Sprocket manifest  
    append app/assets/config/manifest.js  
 Configure importmap paths in config/importmap.rb  
    create config/importmap.rb  
 Copying binstub  
    create bin/importmap  
     rails turbo:install stimulus:install  
 Import Turbo  
    append app/javascript/application.js  
 Pin Turbo  
    append config/importmap.rb  
 Run turbo:install:redis to switch on Redis and use it in development for turbo streams  
 Create controllers directory  
    create app/javascript/controllers  
    create app/javascript/controllers/index.js  
    create app/javascript/controllers/application.js  
    create app/javascript/controllers/hello_controller.js  
 Import Stimulus controllers  
    append app/javascript/application.js  
 Pin Stimulus  
 Appending: pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true"  
    append config/importmap.rb  
 Appending: pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true  
    append config/importmap.rb  
 Pin all controllers  
 Appending: pin_all_from "app/javascript/controllers", under: "controllers"  
    append config/importmap.rb  
   
   
 ~/code/Rails                                         
 ▶   

Then move into the directory you just created:

 ~/code/Rails                                         
 ▶ cd Cars  
   
 code/Rails/Cars main ✗                                ◒   
 ▶ ls  
 Gemfile   Rakefile   config    lib     storage   vendor  
 Gemfile.lock app     config.ru  log     test  
 README.md  bin     db      public    tmp  
   
 code/Rails/Cars main ✗                                ◒   
 ▶   

Then run that web site:

 code/Rails/Cars main ✗                                                                              ◒   
 ▶ rails server  
 => Booting Puma  
 => Rails 7.0.4.3 application starting in development   
 => Run `bin/rails server --help` for more startup options  
 Puma starting in single mode...  
 * Puma version: 5.6.5 (ruby 3.1.3-p185) ("Birdie's Version")  
 * Min threads: 5  
 * Max threads: 5  
 * Environment: development  
 *     PID: 77089  
 * Listening on http://127.0.0.1:3000  
 * Listening on http://[::1]:3000  
 Use Ctrl-C to stop  
   
Rails default web page image

In the following code, we'll create our first model in the web application, the Marque (car brand) and we'll define it as having only a name, which is a string.

 code/Rails/Cars main ✗                                                                              ◒   
 ▶ rails generate scaffold Marques name:string  
 [WARNING] The model name 'Marques' was recognized as a plural, using the singular 'Marque' instead. Override with --force-plural or setup custom inflection rules for this noun before running the generator.  
    invoke active_record  
    create  db/migrate/20230402030625_create_marques.rb  
    create  app/models/marque.rb  
    invoke  test_unit  
    create   test/models/marque_test.rb  
    create   test/fixtures/marques.yml  
    invoke resource_route  
     route  resources :marques  
    invoke scaffold_controller  
    create  app/controllers/marques_controller.rb  
    invoke  erb  
    create   app/views/marques  
    create   app/views/marques/index.html.erb  
    create   app/views/marques/edit.html.erb  
    create   app/views/marques/show.html.erb  
    create   app/views/marques/new.html.erb  
    create   app/views/marques/_form.html.erb  
    create   app/views/marques/_marque.html.erb  
    invoke  resource_route  
    invoke  test_unit  
    create   test/controllers/marques_controller_test.rb  
    create   test/system/marques_test.rb  
    invoke  helper  
    create   app/helpers/marques_helper.rb  
    invoke   test_unit  
    invoke  jbuilder  
    create   app/views/marques/index.json.jbuilder  
    create   app/views/marques/show.json.jbuilder  
    create   app/views/marques/_marque.json.jbuilder  
   
 code/Rails/Cars main ✗                                                                              ◒   
 ▶   
   

Again, we run the Rails server, updating the database first, then open the URL (http://127.0.0.1:3000/marques) to see what it does:

Marques page on a Rails web app

Using the functionality already created we can CRUD (create, read, update, delete) a list of marques.

CRUD the list of Marques

It doesn't take too much imagination to see we can create a list of Models too, so that for each model you need to select a marque (manufacturer) for each one. And so it goes. A whole AirBnB web site is only a million keystrokes away!

That is all

Ruby on Rails

I'm loving Ruby on Rails . If you're not aware of it, it's a framework for developing web sites. It's built around a scripti...