Whether you use puts, print, echo, WriteLine or println to return "Hello World" in the terminal, you're probably familiar with plugins. It's that cool sophisticated subset of classes, objects and all the other object-oriented jibber jabber that many coders use to speed up their development, implement difficult features and make it easier to focus on the core functionalities of an application. All of the programming languages have a unique way of naming their plugins and gem is the term for plugins used in Ruby. Ruby is one of the programming languages that is considered having the largest number of plugins, which is a key factor in agile development. Don't worry, in this blog post we won't be just using gems, but rather implement our own gem.
Mining gem knowledge
With the data collected from module counts, we can see that RubyGems are at the top of the graph. With more than a hundred thousand gems the database is growing incredibly fast with an average growth of 47 gems per day and a total of seven million downloads (still counting).
The best thing about it is that it is open source and every developer can contribute by creating a new gem, reporting a problem in existing gems, fixing bugs, finding information or by learning from other developers. Honestly, just try it and feel good about being a part of the community. It's been a long way since I started web development and I can safely say that the ruby community is my favorite because people are helpful, funny and kind...also, you can find snippets of code very easily if you ever get stuck with the syntax or anything down the development lifecycle.
Best place for finding gems is rubygems.org, home of all the cool goodies that automagically get things done with just a few clicks on the keyboard. All of the data is well documented, described and tested and there is no reason why any developer shouldn't use third party code. Don't reinvent the wheel is one of the idioms of programming in Ruby, which makes development more agile and less stressful.
Deep underground
As far as my story goes, it took a long time before I created my own gem. Mostly it was because I didn't know how much faster things get done if reused code is extracted into a gem. Also, there was this small dose of stage fright. I wasn't confident enough to show the world my coding skills, but that feeling passed as soon as I started looking at the code of other people's gems. If you feel that way, remember that you probably write better code since you're thriving for perfection and don't want others to see your 'bad' coding style. Also, nothing is perfect and if your gem doesn't help anyone else, it will help you!
The second reason why it took me quite some time to dive in and craft gems is because I didn't understand how they work and what methods to use. Even googling it didn't help, because the majority of hits on google regarding ruby on rails are feature/syntax specific, not crafting gem specific. The best resource for learning about crafting gems is, again, RubyGems, official ruby on rails guides and a handful of blog posts which are mostly really old, so be careful with those. If you like reading books the best one out there is Crafting Rails 4 Applications which has an advanced level of Ruby on Rails information provided.
Finally, you can learn a lot about how gems work when you dive into one and see it for yourself. It's the method that always works and you'll start reading more professionally written code and learn not just about gems, but writing Ruby in general. If you're a beginner Rubyist and don't know where to find gems, they're located where your Ruby Version Manager is installed like this:
.rvm/gems/ruby-2.1.2/bundler/gems/
note: I prefer rvm over rbenv since it's easier to set up and maintain.
Pick up that pickaxe
There're a couple of ways to create a gem. You can either hold all the methods, classes and workflow in the gem itself, install some of the code in the host application, or a mix of the two. In my last blog post, I wrote about implementing server-side inline validations in Rails from scratch, using no jquery plugins. You'll notice that the code is adjusted to work for any model with any set of attributes since a gem wouldn't be really useful if it only worked on one model.
The key components of the whole feature are:
- assets - javascript code snippet that handles the request through ajax and a stylesheet for fields with errors (you can override my CSS from within your application)
- controller - the create method that simulates creating an instance of the model, so that ActiveRecord validates the instance and returns the result again to the javascript file
- the route for the controller method - creates a new resource in the routes file
The process of building a gem starts with a simple terminal command:
bundle gem meditate
where meditate is the name of the gem. After a few seconds, you already have the skeleton.
The meditate.gemspec contains metadata about the gem, such as the name, description, author and gem dependencies required to work properly.
meditate.gemspec# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'meditate/version'
Gem::Specification.new do |spec|
spec.name = "meditate"
spec.version = Meditate::VERSION
spec.authors = ["Marko Cilimkovic"]
spec.email = ["marko.cilimkovic@bamboolab.eu"]
spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.}
spec.description = %q{TODO: Write a longer description or delete this line.}
spec.homepage = "TODO: Put your gem's website or public repo URL here."
spec.license = "MIT"
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
# delete this section to allow pushing this gem to any host.
if spec.respond_to?(:metadata)
spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
else
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
end
spec.files = `git ls-files -z`.split("x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.9"
spec.add_development_dependency "rake", "~> 10.0"
end
Now we'll look at lib/meditate.rb and lib/meditate/version.rb. Firstly, the fairly simple one is version.rb:
lib/meditate/version.rbmodule Meditate
VERSION = "1.4.2"
end
which encapsulates the version number in the base module. The format goes in an orderly fashion - MAJOR.MINOR.PATCH. There're a few things to keep in mind when changing the version number:
- when any new change is presented in the gem, change the version number
- if you fix a typo, or a small backwards-compatible bug, change the last number
- creating a new feature or functionality inside a feature, that doesn't break the API, change the second number
- change the first number only when you make changes that break the API with the current version
The lib/meditate.rb is the file where we'll make our first changes:
lib/meditate.rbrequire "meditate/version"
module Meditate
class Engine < ::Rails::Engine; end # the only change
end
This is the file that's going to get called first by bundler. It initially contains only the version number and an empty module where you can place your code, or where you can require other files that are needed for the gem to work. Depending on the complexity of your gem you can choose how you'll tailor it. The only change in this file was the one line where we created the Engine class which inherits the Engine class from Rails. This basically means that when rails loads this gem it's going to look for this engine class and if it's defined then it's going to treat the gems directory structure to work like a rails app inside a rails app. Bit confusing, I know. :)
Implementing the functionalities
Now comes the interesting part. Remember how I said, that the gem 'becomes' a rails app. Well, you can treat it as one. If you have any third party code you can open up a new directory called vendors and place your javascript and stylesheet files accordingly. Same goes with the models, controllers, views or routes, but with a small change in the tree structure:
Before you copy all the views in the views directory, you need to store them in a folder that's named as the gem. Same goes for the controllers and models.
Now that I've shown you how to make gems work properly when all the code is in the gem, it's time to show you how to install the code from the gem templates into the rails host application. This one got me scratching my head for quite some time after I started learning how installing gem templates works. The official guides are packed with too much unnecessary (for begginers) information and when you're unfamiliar with the workflow it might confuse you to the point where you give up. My way of learning how to install gems was with analyzing the devise gem.
The files necessary for installing classes, injecting strings and copying templates are kept in the lib directory:
The templates keep the controllers, models, views, helpers or any other template required for copying into the host application. Since this blog post is about how gems work, we won't get into the controller template and the process of how it works. Rather, we'll focus on the install_generator.rb
meditate/lib/generators/install/install_generator.rbrequire 'rails/generators/base'
module Meditate
module Generators
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path("../../../templates/controllers", __FILE__)
def add_javascripts
append_file 'app/assets/javascripts/application.js', "//= require meditaten"
end
def add_stylesheets
inject_into_file 'app/assets/stylesheets/application.scss', "*= require meditaten", before: /*//, verbose: true
end
def setup_routes
route "resources :inline_validations, only: [:create]"
end
def create_controllers
template "inline_validations_controller.rb", "app/controllers/inline_validations_controller.rb"
end
end
end
end
This class holds 4 methods where each method does one of the mentioned operations needed for the gem to work. append_file does what it says, it appends a string into a file which we pinpoint with its path. inject_into_file inserts the given content into a file, but is different than append_file, because it can take more arguments and is more powerful. template gets the file and makes a copy of it at the relative destination. All of these methods belong to thor, a toolkit for building powerful command-line interfaces. More detailed information can be found on the ruby doc.
When the gem is complete and the source code available on GitHub, with nice descriptive notes and a tutorial on how to use it, it is ready to meet the RubyGems world. After you create your account, just type this command into the terminal and share your code with others:
gem push meditate.gem
Fruits of labor
For testing purposes let's create a new project from scratch and see how to install the newly created gem into the codebase. I'll generate a model that is going to represent a message a user can submit.
rails generate scaffold message subject:string email:string content:text
After the generator finishes with creating the files a few validations need to be placed within the message.rb model:
app/models/message.rb# == Schema Information
#
# Table name: messages
#
# id :integer not null, primary key
# subject :string
# email :string
# content :text
# created_at :datetime not null
# updated_at :datetime not null
#
class Message < ActiveRecord::Base
validates_presence_of :subject, :content
validates_format_of :email, with: /A[w+-.]+@[a-zd-.]+.[a-z]+z/i
validates_length_of :content, maximum: 1500
end
Time to install the gem! We include the gem name into the Gemfile:
Gemfilegem 'meditate', git: 'git://github.com/cilim/meditate'
and after running the bundle command to install it, we need to run the install generator, so all the files from the gem get included into the existing code base.
rails generate meditate:install
After adding the necessary HTML classes, that work as javascript hooks, we fire off the server and visit localhost:3000/messages/new. When we focus out of a field that has validations it should highlight red:
It works! We have successfully created a gem, deployed it and used it on a fresh app. Yay! :)
Now every time when inline validations are needed I will just need to install the meditate gem, add the appropriate classes and continue with the implementation of core features and focus on the primary business logic of the application. And that's why gems are so popular. It is possible to speed up your development tenfold, keep focus on the main features of the application, learn a lot about programming in Ruby or maybe even start new friendships with other Rubyists around the world :)