Go further into the Rails generators and Convention over Configuration. It just keeps getting better. Saving time never felt so good.
In the January 2016 article "Riding the Rails, Part 1," I introduced you to how a Rails project is created. We walked through the various files and directories that were generated. We also made it through starting the Rails web server and ended with being able to see a "Hello World" type of screen. In this article, we'll pick up where we left off by generating a Create, Read, Update, Delete (CRUD) application.
We'll be using the rails generate scaffold command to create the CRUD application. The Rails Guides documentation describes scaffolding as follows:
A scaffold in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above.
The CRUD application will be for maintaining a very simple customer table with columns for first/last name and address. Below is the rails generate scaffold command being run and the corresponding output.
$ rails generate scaffold customer first_name:string last_name:string address:string city:string state:string zip:integer
route resources :customers
Let's dissect the command that was run.
First, we have the rails command. It's important to know that each level of these commands has its own "help" to gain insight about the syntax. For example, here are the commands to run to get help at each of the levels:
$ rails --help
$ rails generate --help
$ rails generate scaffold --help
The scaffold portion of the command requires we pass the name of the model as the first parameter, in this case customer. The "model" is what communicates to the DB2 table and also where we can store additional code relating to customers (business logic). After the model name come the columns and their data types, separated by a colon. The data types of string and integer correlate to data types in the ActiveRecord ibm_db adapter, maintained by IBM and KrengelTech as an open-source project on Bitbucket. You can see the specific lines of code doing the correlation here.
Going back to the output, created first is db/migrate/20160130001913_create_customers.rb, which holds what's called a database migration, a way to record the changes needing to be done to the database. We will be covering database migrations in a subsequent article.
Next we see file app/models/customer.rb created, shown below.
---- app/models/customer.rb ----
class Customer < ActiveRecord::Base
As you can see, there isn't much to it. That's the beautiful part! This is Convention over Configuration, discussed in the first article, at its best. When we start the Rails server, it will make a query to the DB2 customer table, asking for the column meta data. It will then dynamically insert code into the customer.rb model so we can access each column. This is called Ruby open classes. It’s a phenomenally powerful feature because it keeps you from having to manually enter and subsequently maintain code in the model. In short, a very big timesaver.
Next in the output, we have test/models/customer_test.rb and test/fixtures/customers.yml that are created for unit testing purposes. The goal is for every time you create a method in app/models/customer.rb, a unit test in test/models/customer_test.rb should also be created. Every time someone doesn't write a unit test, a politician lays an egg. So let's write some unit tests!
Next in the output, we have resources :customers being added as what's called a route. This means that line was added in the config/routes.rb file to configure how inbound requests should be mapped to your Ruby code. Think of this as being similar to an RPG green-screen program that needs to route processing based on the key a user selects. So if the user presses the F4 key, you would EXSR to CUSTPRMPT, for example. More on routing in future articles.
Next in the output, we have app/controllers/customers_controller.rb which is the controller. The controller facilitates deciding what should be done based on the user's input. For example, when a user submits an HTML form to add a customer row to the database, the controller will receive in the form, call on the validation routines, and, depending on the validation success or failure, redirect the user accordingly.
Next in the output we have the views, as shown below.
The views are similar in nature to *DSPF DDS in RPG. It's where screen definitions are defined. But here, HTML is used instead of DDS. As you can see, there are five different files, each serving a CRUD purpose. File index.html.erb displays a listing of existing customers. File edit.html.erb shows a form to edit an existing customer. File show.html.erb is for (you guessed it) conveying a customer's information in read-only fashion. File new.html.erb is for creating a new customer. The last one, _form.html.erb, is what's called a "partial." Partials are named with an underscore as their first character—a convention of Rails. A partial is a lot like a copy book in RPG: a way to modularize code for reuse in other source files. In this case, _form.html.erb is used in both edit.html.erb and new.html.erb because they both need to display the same HTML form fields, just in a different scenario—editing a customer vs. creating a new one.
Next in the output, we have test/controllers/customers_controller_test.rb, which is another unit-testing skeleton, this time for the controller portion of the customer CRUD application.
Next in the output, we have app/helpers/customers_helper.rb, which is a place to put code that doesn't necessarily belong in the model layer (because it has to do with the view layer). You don't want to put it into the view layer because it would just muddy the code and make it hard to read. For example, let's say you wanted to display a customer's credit in red if they were nearing the danger zone. Something like that would go in a helper because you wouldn't want HTML in your model. You also wouldn't want the "if" statement logic muddying up your view layer.
Next in the output, we have app/views/customers/index.json.jbuilder and app/views/customers/show.json.jbuilder, which are used to offer up the same data as a normal HTML view but instead as a JSON response. For example, if the browser invokes URL http://domain.com/customers, it will return an HTML listing of customers. But if the browser invokes URL http://domain.com/customers.json, it will return a JSON listing of customers. No logic on the server side needed to change. This is obviously great for when you need to offer web services (e.g., AJAX requests from the browser).
OK, now it's time to see the app that was created with the rails generate scaffold command. Use the rails server command to start the server, as shown below. The -p60128 is specifying a specific port if you don't want the default of 3000.
$ rails server -p60128
NOTE: Make sure you are in the root of the Rails project's folder when you are running rails commands.
Now open your browser and enter the IP address of your IBM i and the port you specified, and you should see the error shown in Figure 1.
Figure 1: Pending database migrations
This error is conveying another very cool feature of Ruby on Rails. When we started the server, it made an SQL query to table SCHEMA_MIGRATIONS to check and see if there were any migrations in our application folder db/migrate that hadn't yet been invoked on the database. In this case, it recognized that database migration 20160130001913_create_customers.rb hadn't yet been run and informed us accordingly in the browser error. To remedy the issue, we can run the below rake db:migrate command.
$ rake db:migrate
== 20160130001913 CreateCustomers: migrating ==================================
== 20160130001913 CreateCustomers: migrated (0.3371s) =========================
The rake db:migrate command does the same SQL query to SCHEMA_MIGRATIONS to check for migrations that need to be run. We can see it found the CreateCustomers migration and executed it against the database, effectively creating the CUSTOMERS table with an SQL CREATE TABLE statement.
Now start the Rails server again and direct your browser to http://ibmi_ip:port/customers to see the result in Figure 2.
Figure 2: Customer listing page
By specifying /customers in the URL, and given the entry in routes.rb, the Rails web server knew to route the request to the index Ruby method in the customers_controller.rb file, as shown below.
@customers = Customer.all
What you see above is Ruby code that's making a request to DB2 using the following SQL syntax.
SELECT customers.* FROM customers
What's interesting about the index Ruby method is it doesn't declare what HTML file should be conveyed back to the browser. This is "Convention over Configuration" happening again. If you don't specify a specific HTML file to render the results, then Rails will use the default. The default is to look for a file with the same name as the method (e.g., index.html.erb) in the folder with the same name as the controller (e.g., app/views/customers). This was initially frustrating to me because I came from a more raw RPG-CGI background where I had to do everything. I've grown to greatly appreciate these various conventions because they really do save time.
Select the "New Customer" link and you will see the screen in Figure 3.
Figure 3: Add a new customer
Notice the "/new" portion at the end of the URL? Again, that helps Rails to know which controller method to route this request to. If you look inside customers_controller.rb, you can see it has a new Ruby method. We won't digress into that right now, but know Figure 3 is the new.html.erb file being rendered in the browser, using the _form.html.erb partial for the form.
Filling out the form and selecting the "Create Customer" button produces the screen in Figure 4.
Figure 4: Showing the created customer
File show.html.erb is used to convey the page in Figure 4. You'll notice the "/1" at the end of the URL. This is the unique surrogate (not composite) key for the CUSTOMERS table.
Selecting the "Back" link will take you back to the customer listing, as shown in Figure 5.
Figure 5: Customer listing with data
I could go on further, but eventually this article needs to end. Right?
There's so much more to cover from what was created with the rails generate scaffold command. We’ll pick up where we left off in next month's article.