Autoloading and Utility Methods – Part 3
This is the third part of Building an MVC Framework with Ruby. The topics we’ll cover are:
-
Part 1 – Rack Deep Dive
-
Part 2 – Set up a Basic Framework
-
Part 3 – Autoloading and Utility Method
-
Part 4 – Better Routing
-
Part 5 – Render, Redirect & Before_Action Methods in Controllers
-
Part 6 – Extract Complexities out of View with Helpers
-
Part 7 – Reduce Rework with Generators
-
Part 8 – Set up ORM for Read/Write to Database
-
Part 9 – Generate Database from Schema
-
Part 10 – Set up Migrations
The source code for this post is available on github.
In this post, we will look at how to autoload classes and we will create some utility methods that will be used throughout this series
- Set up code quality check tools
- Utility Methods
- Autoloading classes
1. Set up Code Quality Check Tools
We will be using rubocop and code climate to ensure we are shipping quality code.
1.1 Rubocop
RuboCop is a Ruby static code analyzer. Out of the box it will enforce many of the guidelines outlined in the community Ruby Style Guide. The gem reports style violations through the command line, with lots of useful code refactoring goodies such as useless variable assignment, redundant use of Object#to_s in interpolation or even unused method argument.
It’s divided into 4 sub-analyzers (called cops): Style, Lint, Metrics and Rails. You can define which cops to use, as well as which files to exclude/include and tweak various other configuration options in a .rubocop.yml file.
You can run rubocop using bundler in your project by adding the gem to your gemfile.
Note: You don’t need to require it.
1 |
gem 'rubocop', require: false |
Check out this link for more information about rubocop.
1.2 CodeClimate
Code Climate consolidates the results from a suite of static analysis tools into a single, real-time report, giving your team the information it needs to identify hotspots, evaluate new approaches, and improve code quality.
Check out this link and this link to know more about code climate and how it works.
2 Utility Methods
Let’s create a few utility methods that we will be using throughout our framework. Our utility methods will live in a file called utility.rb
.
2.1 snakize
Open lib/utility.rb and paste this code inside it.
1 2 3 4 5 6 7 8 9 10 |
class String def snakize gsub!("::", "/") gsub!(/([A-Z]+)([A-Z][a-z])/, '1_2') gsub!(/([a-zd])([A-Z])/, '1_2') tr!("-", "_") downcase! self end end |
We are basically opening the String class and adding a method called snakize to it. This method converts any string(in camelcase) to it’s snake case equivalent. It achieves this by following the rules below.
First, snakize calls gsub (replace-all) on double-colons with slashes. This means a constant like “Namespace::Controller” means you want a subdirectory. In which case, it will transform the string to “Namespace/Controller”
Next, it gsubs any two or more consecutive capital letters followed by a lowercase letter… And replaces it with 1_2. If youʼve used regular expressions, you know that 1 means “the first thing in parentheses” and 2 means “the second thing in parentheses”. In which case, it will transform “REGULARExpression” to REGULAR_expression”
Next, it gsubs from lowercase/number followed by uppercase to lowercase/number-underscore-uppercase. In which case, it will transform “PersonController” to “Person_Controller” or transform “Person8Controller” to “Person8_Controller”
Finally it turns all dashes into underscores, and converts everything to lowercase.
2.1.1 write test for the snakize method
Create a folder called unit in your spec folder. All your unit test will recide there.
Open spec/unit/utility_spec.rb and paste this code inside it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
require "spec_helper" describe "Utility Methods" do context "#snakize" do context "PersonController" do it { expect("PersonController".snakize).to eq "person_controller" } end context "Person" do it { expect("Person".snakize).to eq "person" } end context "Todo::Person" do it { expect("Todo::Person".snakize).to eq "todo/person" } end context "PERSONController" do it { expect("PERSONController".snakize).to eq "person_controller" } end context "Person8Controller" do it { expect("Person8Controller".snakize).to eq "person8_controller" } end context "personcontroller" do it { expect("personcontroller".snakize).to eq "personcontroller" } end context "person" do it { expect("person".snakize).to eq "person" } end end end |
Commit your changes
1 2 |
$ git add --all $ git commit -m "add snakize method to String class" |
2.2 camelize
open lib/utility.rb and add this method to String class.
1 2 3 4 |
def camelize return self if self !~ /_/ && self =~/[A-Z]+.*/ split('_').map{ |str| str.capitalize }.join end |
This method converts a snakecase string to it’s camel case equivalent. It first checks if the string is a snake case string and returns it otherwise. If the string is a snake case string, it split’s it by “_”, capitalize each word and joins the string back. So a string “person_controller” will be converted to “PersonController”.
2.2.1 write test for camelize method
Open spec/unit/utility_spec.rb and add this code inside it.
1 2 3 4 5 6 7 8 9 10 11 |
context "#camelize" do context "person_controller" do it { expect("person_controller".camelize).to eq "PersonController" } end context "person__todo_app" do it { expect("person__todo_app".camelize).to eq "PersonTodoApp" } end context "person" do it { expect("person".camelize).to eq "Person" } end end |
Commit your changes
1 2 |
$ git add --all $ git commit -m "add camelize method to String class" |
2.3 constantize
open lib/utility.rb and add this method to String class.
1 2 3 |
def constantize Object.const_get(self) end |
This method convert’s any string to it’s constant equivalent. eg “PersonController” will be converted to PersonController
constant. It achieves this using a little bit of ruby metaprogramming magic Object.const_get
which finds a constant with the same name as the string passed as it’s argument.
2.3.1 write test for constantize method
Open spec/unit/utility_spec.rb and add this code inside it.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
context "#constantize" do context "`Hash`" do it { expect("Hash".constantize).to eq Hash } end context "`String`" do it { expect("String".constantize).to eq String } end context "`Array`" do it { expect("Array".constantize).to eq Array } end end |
Commit your changes
1 2 |
$ git add --all $ git commit -m "add constantize method to String class" |
2.4 pluralize
open lib/utility.rb and add this method to String class.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def pluralize gsub!(/([^aeiouy]|qu)y$/i, '1ies') gsub!(/(ss|z|ch|sh|x)$/i, '1es') gsub!(/(is)$/i, 'es') gsub!(/(f|fe)$/i, 'ves') gsub!(/(ex|ix)$/i, 'ices') gsub!(/(a)$/i, 'ae') gsub!(/(um|on)$/i, 'a') gsub!(/(us)$/i, 'i') gsub!(/(eau)$/i, 'eaux') gsub!(/([^saeix])$/i, '1s') self end |
This method convert’s a string to it’s plural form. The rules I used to pluralize a string is found here.
2.4.1 write test for pluralize method
Open spec/unit/utility_spec.rb and add this code inside it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
context "#pluralize" do context "girl" do it { expect("girl".pluralize).to eq "girls" } end context "buzz" do it { expect("buzz".pluralize).to eq "buzzes" } end context "story" do it { expect("story".pluralize).to eq "stories" } end context "toy" do it { expect("toy".pluralize).to eq "toys" } end context "scarf" do it { expect("scarf".pluralize).to eq "scarves" } end context "analysis" do it { expect("analysis".pluralize).to eq "analyses" } end context "curriculum" do it { expect("curriculum".pluralize).to eq "curricula" } end context "criterion" do it { expect("criterion".pluralize).to eq "criteria" } end context "amoeba" do it { expect("amoeba".pluralize).to eq "amoebae" } end context "focus" do it { expect("focus".pluralize).to eq "foci" } end context "bureau" do it { expect("bureau".pluralize).to eq "bureaux" } end end |
Commit your changes
1 2 |
$ git add --all $ git commit -m "add plralize method to String class" |
You can test utility_spec.rb
using
1 |
$ rspec spec/unit/utility_spec.rb |
or you can use this
1 |
$ rspec spec/unit/utility_spec.rb --format documentation |
to run the test in documentation format.
You can check for style violations at this point using rubocop
. Check the github repo for the .rubocop.yml
configuration used.
3.0 Autoloading Classes
Let’s add a neat automatic loading for our gem for cases when it sees something it thinks it recognizes. If it sees PersonController and doesnʼt have one yet it loadsapp/controllers/ person_controller.rb
.
You may already know about Rubyʼs method_missing. When you call a method that doesnʼt exist on an object, Ruby tries calling method_missing instead. This lets you make invisible methods with unusual names.
It turns out that Ruby also has const_missing, which does the same thing for constants that donʼt exist. Class names in Ruby… are just constants.
Now that you can convert a camel-case constant name to a snake case file name, letʼs add magic constant loading to Zucy.
Open lib/zucy/dependencies.rb
and paste this code.
1 2 3 4 5 6 7 |
class Object def self.const_missing(c) const = c.to_s require c.to_s.snakize const.constantize end end |
3.1 Write test for the const_missing method
Create a folder called helpers
in spec/unit
folder and create the following files and their corresponding classes inside the file.
- person.rb
- todo.rb
- person_controller.rb
Open spec/unit/dependencies_spec.rb
and paste this code.
1 2 3 4 5 6 7 8 9 10 11 12 |
require "spec_helper" $LOAD_PATH.unshift File.expand_path("../helpers", __FILE__) describe "Helpers Methods" do context "#const_missing" do it { expect("Person".constantize).to eq Person } it { expect("Todo".constantize).to eq Todo } it { expect("PersonController".constantize).to eq PersonController } end end |
Notice that we added the helpers folder to our load_path
on line 2.
1 2 |
$ git add --all $ git commit -m "add const_missing method to our framework" |
With this, we have added a bunch of utility and helper methods to . However we still have a long way to go. Keep calm and wait for the next post.
The source code for this post is available on github.
In the next post, we will implement a more sophisticated routing. Something similar to the routing we have in rails.
Please, if you have any questions or observations, let me know in the comments section below:

- Ruby on Steroids(DSLs): The Powerful Spell Called DSL - March 14, 2016
- Ruby on Steroids: The Magic of MetaProgramming – Method Spells - March 12, 2016
- Ruby on Steroids: The Magic of MetaProgramming – Fellowship of Spells - February 28, 2016
- Ruby on Steroids: The Magic of MetaProgramming – An Unexpected Journey - February 27, 2016
- How to Delegate Like a Boss - February 24, 2016
- Better Routing – Part 4 - January 28, 2016
- Autoloading and Utility Methods – Part 3 - January 11, 2016
- Set up a Basic MVC Framework – Part 2 - December 22, 2015
- Rack Deep Dive – Part 1 - December 17, 2015