Almost all the code enabling voting for the Posts and Comments in the PostIt app is common. Its original implementation screamed Don't Repeat Yourself, or, in short DRY.
The same thing is true for the slug-generation and use functionality as well. The slug code is repeated for not two, but three models, namely Post, Category and User. DRYing up the slug code however represents one small challenge as compared to the voting code: slugs are generated from different attributes in each model, :title
for Post, :username
for User, and :name
for Category.
This adds a degree of complexity to how the slug code can be DRYed, and that's why, this article uses slug as the example to explain the process. Voting DRYing up is not just similar, but much simpler.
1. 'DRY'ing up code with Modules
For the application to load modules from a common location (./lib), the following line needs to be added to ./config/application.rb under the Application class definition:
config.autoload_paths += %W(#{config.root}/lib)
The above creates an array with application_root/lib path in a string format and adds it to the pre-existing array stored in PostitTemplate::Application.config.autoload_paths
. This will ensure that all files stored in the ./lib folder will be automatically loaded at application startup.
Slugable.rb stored in this path will be populated by cutting the slug related code from any of the models that implements it (Post, Category, or User). In addition to that, the module file has a call to extend ActiveSupport::Concern
. This enables some extra features like capability to add Class methods to the including class and the included
hook to let some code to be executed only at the inclusion time.
Shown below is the above implemented for the slugging code, which is also repeated in the User, Post and Category models.
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 46 47 48 49 50 51 52 53 |
|
What makes the Slugable module more complex is the need to handle varying attribute names for the model. This has to be specified by the model including this module. This is implemented using the class_attribute :slug_column
and the class method set_slug_column_to(column)
.
So, apart from the include Slugable
call, the models have to call the set_slug_column_to
and pass the appropriate :attribute_name
to it.
Model | should include this call |
---|---|
User | set_slug_column_to :username |
Post | set_slug_column_to :title |
Category | set_slug_column_to :name |
2. 'DRY'ing up code with Gems
Once we have the Slugable module, it is fairly easy to create a gem from it. But why create a gem when we have DRY code already? The main reason is usability across multiple Rails projects. It will also enable other developers to use (a.k.a. 'consume') the gem if it is pushed to RubyGems.org.
Note: One prerequisite for creating a gem is having a gem called 'gemcutter' installed. If you don't already have it (check using gem list gemcutter
), install it by issuing gem install gemcutter
in your shell.
Since the gem is going to be a separate project, we need to create a separate folder to hold its code. The following steps highlight the process to create the slug gem.
A 'Slugable.gemspec' file needs to be in the root path of the gem folder and it should have the following code:
1
2
3
4
5
6
7
8
9
10
11
Gem::Specification.new do |s|
s.name = "sluggit-ppj"
s.version = "0.0.0"
s.date = "2014-10-20"
s.summary = "A slug generator gem"
s.description = "A cool gem for slugifying models."
s.author = "Prasanna Joshi"
s.email = "author@myemail.com"
s.files = ["lib/slugable_ppj.rb"]
s.homepage = "http://www.github.com"
end
s.name
: specifies the name of the gems.files
: specifies where the code for the gem residess.version
: versioning mechanism for the gem
The './lib/slugable_ppj.rb' could be an exact replica of the module file created earlier.
This is all we need to create our gem. Issue
gem build slugable.gemspec
1 2 3 4 5 |
|
- add the following lines in the Gemfile of your project to make it available:
gem 'sluggit-ppj', path: '% local_root_path_of_the_gem %'
- run
bundle install
after modifying the Gemfile - add
require slugable_ppj
before the features of the gem are called in your code (safest place to add it in is the file 'projec_root_path/config/application.rb' because this file gets loaded immediately after the application starts)
Once you have the gem working like you want to, you can push it to RubyGems.org using the 'gemcutter' command:
gem push sluggit-ppj-0.0.0.gem
The '.gem' file version should change based on your changes to the s.version
setting in the '.gemspec' file.
Note: The gem can be disabled (at RubyGems.org) by issuing the
gem yank sluggit-ppj -v '0.0.0'
command from the gem root path in the shell. As you can see, you can yank
a particular version of the gem from RubyGems.org.