Create a new rails app from scratch

Welcome! If you are on this page then you are most likely new to PowerRuby and maybe even RubyOnRails. Because of that there will be a number of digressions during this tutorial that may prove useful. This tutorial assumes you have already installed PowerRuby.  If not, please go here: Install/Update Instructions

It would be good to first learn about what exists on your server now that PowerRuby is installed.  There is a new root IFS folder named /PowerRuby and library named POWER_RUBY.  The IFS folder contains three child folders described below:

  • /PowerRuby/oss - This contains additional open source software (i.e. git, bash, joe)
  • /PowerRuby/prV2R0 - Contains Ruby language version 2.0.x and Rails web framework version 4.0.x

The POWER_RUBY library contains both product definitions and PowerRuby commands.  We will be using a number of the commands in this tutorial.

To start things off make sure POWER_RUBY is in your library list.

ADDLIBLE POWER_RUBY

Now make sure we are using the latest version of the PowerRuby stack (*V2R0 is Ruby 2.0.x + Rails 4.0.x).  This effectively changes symbolic links in /QOpenSys/usr/bin to point at same named binaries in /PowerRuby/prV2R0/bin

SETPOWRBY VRM(*V20)

The 65535 Issue

Before running the RAILSNEW command in the next section it is important to know whether your QCCSID value is set to 65535.  If it is then you need to change your current job's CCSID.

Use DSPSYSVAL SYSVAL(QCCSID) to learn whether your system is defaulted to CCSID 65535.  If it is then use the following command to change the current CCSID for your current job:

CHGJOB CCSID(37)

Creating the App Structure

The RAILSNEW command is meant to get you up and running with a preconfigured application folder in the IFS.  Note that this approach is considered as doing application development right on the IBM i platform.  Other approaches, found in future tutorials, will show how to develop on your laptop and deploy it to IBM i.  Run the following command from a 5250 telnet session.  Note you need *SECADM authority as this will create a new user profile.


RAILSNEW VRM(*V20) PLACE(*WWW) RAILSAPP(A2222) DBUSR(A2222) DBPASS(A2222) DBROOT(A2222) 
HTTPSRV(A2222) EXTPATH('') EXTPORT(2222) INTPATH('') INTPORT(2202)

The RAILSNEW command will do a number of things for you:

  • Create a new Apache instance at /www/A2222 listening on port 2222.  This is based on the HTTPSRV keyword.
  • Create a new RubyOnRails application folder at /www/A2222/htdocs/A2222.  This is based on the combination of the PLACE and RAILSAPP keywords.
  • Creates three DB2 collections (a.k.a libraries) named A2222_D, A2222_T, and A2222_P for development, testing, and production respectively.  This is based on the DBROOT keyword.
  • Creates an IBM i user profile with a name of A2222 and password of A2222 based on DBUSR and DBPASS respectively.
  • Starts the A2222 Apache instance on port 2222 based on EXTPORT keyword.
  • Starts the A2222 Thin instance on port 2202 based on INTPORT keyword.

You should see the following in your job log upon success completion of the RAILSNEW command:


Bundle install job completedRails new job completed

Open a browser on your network and enter the following: http://:2222

If you see the below screenshot in your browser then everything worked!

Welcome Aboard

RubyOnRails source is stored in the IFS.  The RAILSNEW command created the following folder structure in the IFS /www/A2222/htdocs/A2222 directory.  Below we see a screenshot of the database.yml file being viewed in the RPGNextGen editor.  When the RAILSNEW command started your app it set the RAILS_ENV PASE environment variable to a value of 'development'.  That means when the Rails app starts it will operate the development portion of the database.yml file.  Nothing needs to change here.

database.yml

The application doesn't do anything as of yet other than display the "Welcome Aboard" message we saw earlier.  Next we need to add a DB2 table.  We won't be using SQL or DDS.  Instead we will use one of the Rails commands named generate.  First open either an SSH session to PASE on IBM i (i.e. from a terminal enter "ssh myprofile@192.168.0.123") or from a 5250 telnet session do CALL QP2TERM.

Next, change to the directory of your Rails application as you need to be in there when you run the various Rails commands so it knows what application to apply them to.


$ cd /www/A2222/htdocs/A2222

Now run the "rails generate..." command shown in the below screenshot. Note how it documents the created files.


rails generate model Post title:string body:text

rails generate model Post

At this point we still don't have a DB2 table because we need to run what's called an ActiveRecord database migration using the rake db:migrate command.  Running that command will have the Rails app look at the DB2 table A2222_D/SCHEMA_MIGRATIONS to see what files in db/migrate/*.rb have yet to be processed.  Once the command is run you can do a WRKOBJ A2222_D/POSTS and it should exist.  Wait.  Didn't we call it "Post" (singular) above?  Yes, this is a Rails naming convention that you should be aware of where tables are created as plural by default.  Note you can override this in the post.rb model using self.table_name = 'POST'.

rake db:migrate

Now that a DB2 table exists and is wrapped by the app/models/post.rb we need some web UI stuff to be generated so we can add/change/delete DB2 rows from a web browser. The "UI stuff" is more formally called the View and the Controller and is created using the rails generate ('g' for short) command.  Because our model is also named 'post', this will match up by default.  That's a taste of the Rails Convention over Configuration concept.  Notice again the various files that were created.  Take a moment to review the files if you like.


rails g scaffold_controller post title:string, body:text

rails g scaffold_controller

Go to your browser again and add /posts to the end as shown in the below screenshot.  An error is produced declaring that, while there may be controllers in existence, we have yet to give Rails permission to invoke it.  We first need to setup a route.

Routing Error

To setup a route you first need to locate the A2222/config/routes.rb file and open it for editing.  Then add resources :posts as an entry, as shown below

routes.rb

Now go and refresh your browser.  It works!

After selecting "Create Post" you should see the below success screen.

Always good to check for proof that the data made it into the database.  One of the "Rails ways" of viewing data that's real handy is the Rails Console.  Simply enter rails console (or 'c' for short) within the root of your Rails application and you will be entered into the console.  Then use the model accessors (i.e. Post.last) to retrieve the last row in the A2222_D/POSTS table.  A post.rb object will be returned and will be dumped back to your terminal.  There is a lot you can do from the Rails Console including running convenience scripts for general maintenance tasks.

rails c
If you'd prefer a more traditional approach of seeing the DB2 results then head over to 5250 telnet and do a STRSQL:


STRSQL

Then run the following SELECT:


SELECT * FROM A2222_D/POSTS

STRSQL

Another Feature

Having a single table with create, edit, show and delete application functionality is a good quick way to learn how Rails can work, but we'd sell Rails short if we stopped there.  A trademark of Rails is how the various generators can be used even after the initial application has been developed.  To show what is meant by that let's add a comments feature to the blog post app we've created.

The first thing to do is generate the comments model as shown below.  This is very similar to generating the Post model except we want to declare that comments reference the post model.  We do this by specifying post:references.

rails generate Comment

As with the post.rb generation, the above will generate a comment.rb file in the app/models directory as shown below.  You'll notice the belongs_to :post syntax in comment.rb.  The belongs_to says a comment can belong to a post, or maybe better said, many comments can belong to a single post.  You'll then need to open post.rb and manually add has_many :comments. These lines of code allows us to do method chaining to access a post's comments and a comment's post, as we'll see in just a bit.

comment.rb RPG Next Gen

Before moving on please be sure to run rake db:migration so the COMMENTS table is created in library A2222_D.

rake db:migrate comments

The next thing we need to declare is how comments will be allowed to be accessed concerning Rails routes.  We want comments to be a child of posts.  To do this we open the routes.rb file and modify the existing resources :posts line to instead be a block**.

**Ruby blocks are a new concept to most programmers.  For that reason, here are a handful of tutorials that will aid in learning about them:

routes.rb comments

Next we need to generate a controller for the comment model but in this case we do NOT need to generate views because we will be adding the comment feature into the existing post views.

rail generate controller Comments

Opening the app/controllers/comments_controller.rb reveals it doesn't yet have any action methods, which is the default for the rails generate controller command.  In this case we only need a single action method named create because the only other aspect of interacting with comments (displaying them) will be done through the post's model relationship with its comments (i.e. post.rb contains has_many :comments).

comments_controller.rb


  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create(params[:comment].permit(:commenter, :body))
    redirect_to post_path(@post)
  end

Next the views/posts/show.html.erb needs to be modified to include an "Add comment:" area so those viewing an existing post have an HTML form where they can enter commentary as shown below.

The form_for API aids in building a form for a specific model.  Normally you'd pass in a single instance of a model (i.e. @post) but in this case we want to make sure to associate the new comment to an existing object (which is essentially a row in the database).  This is accomplished by passing an array as the first form_for parameter and then the second array element will invoke the build method on the comments array.  You can search for "collection.build" on this page to learn about this feature but essentially what it will do is learn of the model type being stored in the comments array (i.e. comment.rb) and build an empty instance of it that has not yet been saved to the database.  That last bit is important because the other option would be to use the "collection.create" method from that same page, but that would actually save a record in the database and in this case we don't want to create a database entry before the form is sent to the screen because we aren't certain the user will in fact want to create a comment (they might simply want to view more information about a given post).

We digressed a little more on the form_for to show that not only does Rails have big picture concepts in mind, but also the more minuscule, though both can be big time savers.

show.html.erb

<pre>
<code>

<h2>Add a comment:</h2>

<%= form_for([@post, @post.comments.build]) do |f| %>

  <p>

    <%= f.label :commenter %><br />

    <%= f.text_field :commenter %>

  </p>

  <p>

    <%= f.label :body %><br />

    <%= f.text_area :body %>

  </p>

  <p>

    <%= f.submit %>

  </p>
<% end %>
</code>
</pre>

At this point you can successfully add comments to an existing post.  The issue is the comments entered aren't displayed anywhere (yet).  Below is a section of code from views/posts/show.html.erb that will display existing comments (commenter and body).  Here we can again see the post to comments relationship shining through.  The @post.comments.each line of code is making use of the has_many :comments entry we previously added to post.rb and effectively retrieves all of the comments for that specific post into an array.  In RPG terms this would effectively be a CHAIN to table POSTS followed by a priming READE on table COMMENTS followed by a DOW loop.

<pre>
<code>
<h2>Add a comment:</h2>

<%= form_for([@post, @post.comments.build]) do |f| %>

  <p>

    <%= f.label :commenter %><br />

    <%= f.text_field :commenter %>

  </p>

  <p>

    <%= f.label :body %><br />

    <%= f.text_area :body %>

  </p>

  <p>

    <%= f.submit %>

  </p>

<% end %>
</code>
</pre>

If the above database-access-method-chaining has you scratching your head then it might be good to load up the rails console to learn what exactly is going on when@post.comments is invoked.  The below screenshot is showing the Post.find(1).comments command being invoked.  Since the post.rb file inherits from ActiveRecord::Base (open the file and have a look) it has a variety of available methods including find.  The find method is a lot like RPG's CHAIN in that you specify the unique key you are going after as the parameter (in this case the number one (1) ).  As you can see, ActiveRecord first does a SELECT statement to the POSTS table to retrieve a particular post, and then it does a SELECT to the COMMENTS table and returns an ActiveRecord collection (aka array).

It is also worthy to note that ActiveRecord has mechanisms for caching data base results in the same request.

Post.find.comments

Refactoring

At this point the comment feature for posts is complete.  As you can see, there is not only a lot of generated and automated functionality built into Rails but also there is a level of detailed features (i.e. form_for) that keeps things flexible, and believe us when we say we haven't even scratched the surface of capabilities.
 
One thing that usually happens as an application grows is code from one page gets copied to another, and if the destination code isn't changed then you have an exact duplicate of code in two locations - not a good thing for maintenance.  The Rails framework addresses this with something called partials.  
 
To make use of partials we will cut the comment display code from views/posts/show.html.erb and paste it into a new file named views/comments/_comment.html.erb.  Note the underscore at the beginning of that file name declares it is a partial vs. full template.  This affords us some special capabilities, one of which is streamlined processing of displaying the comments for a given post.  Below we have a screenshot that shows <%= render @post.comments %> being invoked.  This is a short hand call that makes some assumptions.  First, it assumes there will be a default partial of the same name as the model (i.e. comment) in the same named, though plural, folder (i.e. views/comments).  Then it assumes the content of the partial (_comment.html.erb) is expecting to be called multiple times for each array entry and be provided a singularly named variable of "comment".  This is another convention over configuration pattern.  You can learn more by searching for "Rendering The Default Case" on this page.

Too Many Rows

At this point you might go and show the fruits of your labor to co-workers, who subsequently start adding records.  It is soon realized that the /posts index page displays all rows from the database and doesn't have any form of a paging mechanism.  This next part of the tutorial will show how to proceed with locating, installing and implementing a result set paging mechanism.

Before doing that please make sure you have a number of rows in the POSTS database table.  You can easily add them from the Rails Console by running this statement:

10.times { Post.create(title: 'my title', body: 'my text') }

The above code is basically saying to iterate over the Post.create statement 10 times.  The curly braces are what's called a block in Ruby.  Search for "Block" on this page to learn more.

Now back to locating a paging mechanism.  First head over to Google and search for "rails paging".  The first result displays a gem named will_paginate as shown below.

Navigating to the will_paginate URL takes us to its GitHub page.  The Ruby on Rails community makes extensive use of GitHub for "social coding" (aka developing code in a public community).  Most GitHub projects have what's called a Readme.md (md = Markdown) in their root directory and the content of that file is displayed below the file structure and usually includes installation and usage instructions.  The will_paginate code base has conformed to the RubyGems and Bundler standards which basically means obtaining and installing this gem will be incredibly simple.

The install instructions on the will_paginate page guide us on how to install.  First we open the Gemfile located at the root of our project and add the line gem 'will_paginate', '~> 3.0' as shown below.

will_paginate Gemfile

Next we need to run the bundle install command from the root directory of our Rails application.  This will cause Bundler to review the Gemfile, recognize will_paginate doesn't exist, and then download and install it.  In the below screenshot you'll notice lines beginning with "Using" and "Installing".  The latter is fairly obvious, but "Using" might not be.  That means Bundler found a local Gem that matched the name and version criteria on the local system.  In this case all those other Gems were included in the default install of PowerRuby and most are either Rails or Rails required. 

NOTE: If this command errors out on you it is most likely because of IFS authority issues.

bundle install

Next we continue following the installation instructions by modifying the app/controllers/posts_controller.rb, app/models/post.rb, and views/posts/index.html.erb files to have the changes shown below.

posts_controller.rb

For these changes to take affect we need to restart our app, and you can do that with the following two commands.  You can see whether your app is running by reviewing the jobs in subsystem QSYSWRK.


===> RAILSSVR ACTION(*END) APP('/www/A2222/htdocs/A2222') PID('server')
===> RAILSSVR ACTION(*START) APP('/www/A2222/htdocs/A2222') VRM(*V20) PORT(2202) HOST('0.0.0.0')

Now if we direct our browser to http://ip_address:2222/posts we will see the fruits of our labor as shown below.

If you are like many when first learning Rails you are probably saying "Wow!  Cool!  But how in the heck did that work?  I don't see how the paginate method became available to the Post model object?"

That's a very good question!  Basically what happens is the will_paginate Gem modifies the ActiveRecord::Base functionality on application start-up to include additional methods as shown in the will_paginate/active_record.rb GitHub file.  If modifying an existing program at runtime is a foreign concept to you then I'd highly recommend you read Metaprogramming Ruby: Program Like the Ruby Pros by Paolo Perrotta where this is described.

Start and Stop Servers

It's always good to know how to start and stop the servers.  Remember from earlier we have two servers in play, the Apache server which is used for static files** and routing to Thin, and then the Thin server which is what runs the Rails application.
 
**At this point your Apache server instance isn't configured to serve up static files.
 
Before ending a job I like to see it in WRKACTJOB.

WRKACTJOB SBS(QHTTPSVR)

Now end the Apache HTTP server:

ENDTCPSVR SERVER(*HTTP) HTTPSVR(A2222)

Now end the Rails Thin application server:

RAILSSVR ACTION(*END) APP('/www/A2222/htdocs/A2222')

If you go back to the browser and refresh you should get a site not found type message.
 
Now start Thin back up:
 

RAILSSVR ACTION(*START) APP('/www/A2222/htdocs/A2222') VRM(*V20) PORT(2202)

And start Apache back up:

STRTCPSVR SERVER(*HTTP) HTTPSVR(A2222)

Clean Up

If you'd like to try that tutorial again, or want to leave no trace, then you can run the following commands to clean up everything that was created:


ENDTCPSVR SERVER(*HTTP)
HTTPSVR(A2222)
RAILSSVR ACTION(*END)
APP('/www/A2222/htdocs/A2222')
QSH CMD('rm -R /www/A2222')
RMVM FILE(QUSRSYS/QATMHINSTC) MBR(A2222)
DLTLIB LIB(A2222_D)
DLTLIB LIB(A2222_T)
DLTLIB LIB(A2222_P)
DLTUSRPRF USRPRF(A2222)

Have any questions? Email support@powerruby.com