Our first Rails plugin- ActsAsModerated!
Disclaimer: This post is first about my experience writing a plugin and then about the plugin itself. If you just want to learn about the plugin scroll down to the âusing the pluginâ part.
Well, after a while documenting, updating and polishing here is our first Rails plugin (in fact it’s an ActiveRecord plugin =D ). It isn’t something big or something that you couldn’t live without, but it was something helpful on our last project and it doesn’t hurt to give something back to the community after everything that they gave us.
A little history
My last task was building a social community (yeah, another one, but this one is somewhat different than the others in their target audience) which I can’t talk about right now (once they have launched I promise to comment about it here), but one of the features was a moderation queue. The client wanted that every create/update/deletion were held for moderation, in the beginning of the project it looked good enough, we didn’t have a lot of models and I could write a simple moderation queue for those two or so objects.But it’s never as easy as it seems :).
Not too long later the client sent a document asking for a lot of other models (like jumping from two to a dozen) and I couldn’t just write a different moderation queue for every model, so, time to look for a plugin (search first, build later). I couldn’t find anything that worked like I wanted and the closest one needed a âcloneâ table to keep the models under moderation, so I thought it was too much for something as simple as a moderation queue. Afterall, this is Ruby, not Java =P
So, I couldn’t find anything that did what I wanted (and the way that I wanted).
On the shoulders of giants
Coming from my Java/C# background I was really expecting something âhardâ or maybe âchallengingâ (I wrote a JavaServer Faces component last year, so all my sins are paid until 2010). First, I spent some time reading the âacts_as_taggable_on_steroidsâ and âattachment_fuâ plugins.
After some time reading them I was starting to form an idea about how an acts_as plugin should behave. When you are building an acts_as plugin you have to imagine what new behaviour you will add to your models, my idea was that instead of saving/destroying the object, I could send it to the moderation queue, so, every model that acted as moderated would have a method called to_moderation that would send it to the moderation queue without touching it’s original state, only the moderation object should be saved here, not the moderated model itself.
So, I wrote the Moderation model containing the information that I needed, my idea was to keep it as simple and flexible as possible, I needed to store a lot of different models and I didn’t wanted a separate âcloneâ table for all of them, so the moderations table would have to store the model.But how could I do it without saving the model?There is an interesting feature on ActiveRecord that many people don’t know about that is the âserializableâ attributes. You can âserializeâ a complex object into a text column storing it in a YAML representation, so, instead of creating a new table for every model, I would serialize the attributes hash of every ActiveRecord model into the text column. Multiple tables problem solved
Going on with the development I noticed that it was really hard to test the plugin using RSpec and the way that plugins are packaged has always bothered me. In Java we did our builds using Ant or (lately) Maven and when you’re using Maven you define the version of your dependencies, so that whenever you have to switch to another machine (yep, I know this is coming in Rails 2.1), it was just a matter of installing Maven and telling it to download the dependencies again.When you’re using a Rails plugin you usually can’t find out which version it is, specially if you’re running on someone else’s code, and this isn’t really good when you just happen to fall on legacy code that you have to deal with.
Fortunately, I saw the âPlugin Patternsâ e-book from Andrew Stewart and gave it a spin. Even if you are not planning to write a Rails plugin, you should absolutely check this book, there are great insights about how Rails works and you will figure out that building a plugin is as easy as anything in Ruby, it’s just a matter of knowing where to place your code.And Andrew’s book gave me the idea that was missing, bundle the plugin as a gem!
So, I set out to use Hoe (another nice tool from the Seattle.rb guys) to build my gem. Testing with RSpec became easier and I didn’t need to make crazy black magic to run my specs, it was just a spec file, a helper and a migration, ârake specsâ and be done with it. Now, as I had a gem ready to go, I wrote the gem docs and also bundled a simple example application to help people figure out their way when trying to use the plugin.It wasn’t really easy because it was my first time doing it, but now that I’ve learned the quirks and have found plenty of documentation the next time it will be just another plugin.
If you find that something in your app could be moved out from it, take some of your time to move it out, you might learn a lot about your coding style and how to create reusable code in a simple and easy way.
Using the plugin
Enough talking!
Lets see how you can use this plugin to build your own moderation queue. First you have to install the gem (our Rubyforge account hasn’t been enabled yet, so you will have to get the gem here). After installing it you can start to use it in your own rails project or take a look at the example application that is available at the gem directory.Once your app is ready, you have to unpack the gem at your vendor/plugins dir, do it using the following command:
gem unpack acts_as_moderated
This should create an âacts_as_moderated-0.5.0â. After this you have to generate the moderations object migration, just type:
ruby script/generate acts_as_moderated_migration
A migration for the Moderation model object will be created. You can run your migrations to create the table:
rake db:migrations
With the table created, time to use it in your models. Imagine that you have an Article model that you want to be moderated, here’s what you have to do:
class Article
acts_as_moderated
end
I hope you haven’t typed too much
With this call, instances of the Article class will now have two new methods added: to_moderation and to_moderation!. Both of these methods will create a new moderation object containing the current state of the moderated model (in this case, the Article object). The only difference is that the one ending with an exclamation sign will throw an exception if the moderation could not be created.The to_moderation object receives an options hash where you can pass complementary attributes to the moderation object. The default to actions in moderations is “save”, so, if you want to place a destroy action on the moderation queue, you will have to add it to the to_moderation method call, like this:
@article.to_moderation :action => 'destroy'
When this moderation is applied it will remove the moderated object. The only actions accepted right now are âsaveâ and âdestroyâ.And here comes some real world action, that’s how your new controller actions would look:
def create
@article = Article.new( params[:article] )
if @article.valid?
@article.to_moderation # sends this object to the moderation queue
# the object attributes are saved with the moderation object
flash[:notice] = 'Your change was received and placed on our moderation queue'
redirect_to articles_path
else
render :action => 'new'
end
end
And then you can âmoderateâ a change calling the moderate method on a Moderation object (try to say this fast!):
def moderate
@moderation = Moderation.find( params[:id] )
@moderation.moderate! #moderates the change, adding/updating/removing the moderated model
flash[:notice] = 'The changes have been applied'
redirect_to moderations_path
end
Imagine now that you wanted to know which user tried to peform this change, how could you do that?The to_moderation method receives an array of attributes as a param and those attributes are sent to the Moderation object, so you could just create a new migration adding a new field to the Moderation model:
add_column :moderations, :user_id, :integer
And send the parameter to the to_moderation object, just like this:
@article.to_moderation( :user_id => current_user.id )
To enable this on your app, just create a file called moderation.rb on your app/models folder and add the belongs_to declaration (classes in Ruby are always open, remember?):
class Moderation
belongs_to :user
end
And you’re done, you have your own moderation queue working for anything and you can even add the user who performed the change. Take a look at the sample app and readme that comes bundled with the gem to learn more about how to code (and even view a âdiffâ of the moderation object).
Scotland on Rails
I’ve just returned from the excellent Scotland on Rails conference, held in Edinburgh over 2 days.
There was a good line-up of talks and the ones I attended were all interesting in one way or another. Some were nice and practical, and others were more abstract and esoteric.
Day 1:
- Michael Koziarski talked about beautiful code, by giving examples of not so beautiful code that is in Rails source. For someone that has never looked much at Rails source code, this was pretty interesting, particularly the good explanation of how alias_method_chain is used. He also mentioned this rule-of-thumb, which is something I also subscribe to:
- First make it work
- Then make it beautiful
- Know when to stop
- Richie McMahon and Maria Gutierrez talked about integrating Rails with an existing suite of Java applications. Their experience covered using web services integration, message queuing and even sharing the database directly.
- Andy Stewart gave practical coverage of all the options for background processing with Rails, before settling on BackgroundJob as his preferred choice.
- Jim Weirich & Joe O’Brien did a 3-act play explaining the benefits of mocks and stubs in your tests, in particular with FlexMock.
- Giles Bowkett gave a great talk on meta-programming, based on the underlying premise of code=data and data=code. What could have been a really dry presentation was spiced up with random insertions of Jessica Alba and Darth Vader. Hilarious!
- Bruce Williams then explained the new features coming in Ruby 1.9. I must admit that I didn’t pay full attention, since its likely to be a while before these changes filter through to Rails.
- Day 1 was wrapped up by Gordon Guthrie introducing Erlang and how it supports parallelism right from the insides.
Day 2:
- David Black opened with an allegory comparing music with programming. Although it was interesting, I must say that the analogy didn’t quite work for me.
- Jonathan Weiss gave a very straightforward talk on common Rails patterns, which you could see he’d had proper experience with. These covered handling long running tasks, dealing with large files, when to use plugins, handling dependencies, and avoiding denial-of-service attacks
- Paul Dix did an interesting talk on collective intelligence. His example showed giving movie recommendations. Some very clever maths in use here.
- Jim Weirich talked on “Advanced Ruby Class Design”. His target audience for this one was Java programmers, showing how to accomplish things in the Ruby Way. But the message was universal.
- Joe O’Brien explained domain-specific languages. Not really from a technical point of view, but more from a philosophical angle.
- And the last talk was Charles Oliver Nutter and Thomas Enebo on JRuby. As someone who’d never had a proper look at JRuby before, this presentation was really eye-opening. They gave a good intro to all the reasons why one should consider JRuby, and there are many. JRuby is definitely something we will be having a serious look at.
All in all a great conference. The organisers did a fantastic job, I learned a heck of a lot, and met some cool people. I really hope to come again next year.
PS. For slides of the various presentations have a look here, and click on the slides links.
Rails deployment with Apache and mod_rails on Ubuntu Gutsy (7.10)
If you have ever deployed a Rails app, you have probably used a mongrel cluster running behind a proxy server (usually Apache, Lighttpd or, not so probably, Nginx) while this isnât something painfully difficult, it makes Rails applications harder to deploy when compared to PHP, where you just send your file to the server, or Java, where you bundle your app in a .war file and place it on the serverâs deployment folder.
The biggest problem with the mongrel cluster approach is that you have to take care of at least two processes. Although it was possible to have only an Apache server to deploy your Rails applications (using FCGI) this wasnât a good approach as it will hurt your application performance.
But now, we have a true option to run our Rails applications using just an Apache server, and the option is called (tadĂĄ!) mod_rails!
mod_rails (or Passenger) is an Apache module that aims to enable seamless deployment of your Rails applications using only an Apache server. No proxies, no (visible) clusters, no other processes to handle, just copy your rails application to folder defined at the Apacheâs virtual host configuration and be done with it.
But how does it works?
What mod_rails does is automatically manage a cluster of rails applications inside your Apache server, so you will have all functionalities and performance advantages of running a mongrel cluster without having to manage one. And something that really makes mod_rails special is that your rails applications are independent from the Apache server, if your application blows, the main server wonât go down the tubes. If you want to have a full architectural overview of how it works, take a look at their âPassenger architecture documentâ.
And now, enough of talking, letâs get our hands dirty and prepare the environment to deploy a rails application to mod_rails. First, this tutorial is aimed at preparing an Ubuntu 7.10, but it should probably work if youâre running 7.04 or maybe even a 6.x, but I canât guarantee that.
If you donât have Ruby yetâŚ
If youâre going to do this in a brand new (aka. virgin) server, you will have to install some things (like Ruby
) first, login to the machine and start typing:
sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get autoremove
This will update your system and take the garbage away. Then you go:
sudo apt-get install build-essential ây
This will install the software needed to build other things (like your native gems). After installing this, itâs time to install your database (in our case, itâs MySQL, but you can take another one, I promise I wonât feel bad about it) and Ruby, you can do this typing:
sudo apt-get install mysql-server mysql-client libmysqlclient15-dev libmysql-ruby1.8 ruby1.8-dev ruby1.8 ri1.8 rdoc1.8 irb1.8 libreadline-ruby1.8 libruby1.8 libopenssl-ruby irb1.8 libdbd-mysql-perl libdbi-perl libmysql-ruby1.8 libmysqlclient15-dev libmysqlclient15off libnet-daemon-perl libopenssl-ruby libopenssl-ruby1.8 libplrpc-perl libreadline-ruby1.8 libruby1.8 mysql-client mysql-client-5.0 mysql-common mysql-server mysql-server-5.0 rdoc1.8 ri1.8 ruby1.8 ruby1.8-dev zlib1g-dev
Itâs possible that the ruby installer hasnât added the symlinks, so, if typing ârubyâdoesnât work, try this:
sudo ln -s /usr/bin/ruby1.8 /usr/local/bin/ruby
sudo ln -s /usr/bin/rdoc1.8 /usr/local/bin/rdoc
sudo ln -s /usr/bin/ri1.8 /usr/local/bin/ri
sudo ln -s /usr/bin/irb1.8 /usr/local/bin/irb
With ruby installed, itâs time to install RubyGems. You can install RubyGems from apt, but itâs better to download it and perform a manual installation. There you go:
wget http://rubyforge.org/frs/download.php/35283/rubygems-1.1.1.tgz
tar xvzf rubygems-1.1.1.tgz
cd rubygems-1.1.1
sudo ruby setup.rb
After installing it, you also have to add a symlink:
sudo ln -s /usr/bin/gem1.8 /usr/bin/gem
Alfter all this typing, you must be really tired, so now comes the easy part..
As youâre planning to perform a Rails deployment, you are probably using Capistrano (why wouldnât you use it?), so I have some recipes to make you type less, a LOT less. First, install the Apache 2 server, Apacheâs development headers, the Apache Common Runtime, and finally the Rails and Passenger gems:
desc 'Installs apache 2 and development headers to compile passenger'
task :install, :roles => :web do
puts 'Preparing the environment'
puts 'Installing apache 2'
sudo 'apt-get install apache2 apache2.2-common apache2-mpm-prefork apache2-utils libexpat1 ssl-cert libapr1 libapr1-dev libaprutil1 libmagic1 libpcre3 libpq5 openssl apache2-prefork-dev -y'
puts 'Installing needed gems'
sudo 'gem install fastthread rake rails passenger'
end
This task will install the Apache 2 server (even if you already have Apache 2 installed, you should run this task to be sure that you also have the development libraries installed) and the required gems. After this, youâre almost there, login again to your server and type:
passenger-install-apache2-module
You will answer some questions (and probably you wonât need to install anything, as we have already installed all software needed) and when the script is done take note of what heâs saying, which means copy the values to your /etc/apache2/httpd.conf file, it should look like this:
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/ext/apache2/mod_passenger.so
RailsSpawnServer /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/bin/passenger-spawn-server
RailsRuby /usr/bin/ruby1.8
RailsMaxPoolSize 2
On the first line, we are telling Apache to load the passenger_module (this is mod_rails), the next ones are mod_rails configurations. RailsSpawnServer is the path to the executable that starts the Rails servers, RailsRuby is where your ruby executable is and RailsMaxPoolSize is how many application instances (just like the mongrel instances) you want mod_rails to start. Donât leave the RailsMaxPoolSize blank, as itâs default value is 20 (yeah, TWENTY) and you probably donât have enough memory for all 20 rails applications.
And weâre done!
Well, almost
Now that you have apache configured and mod_rails (Passenger) being loaded, we have to tell Apache about our application, we do this using a virtual host configuration, but we are not going to write it with our own hands, oh no, so there is another task to do this for us:
desc 'Creates a virtual server configuration on apache to your application'
task :create_server_config, :roles => :web do template = File.read( File.dirname(__FILE__) + '/vhost_config.erb' )
buffer = ERB.new(template).result(binding)
puts 'Rendering template file'
put buffer, "#{shared_path}/#{application}-vhost"
puts 'Copying virtual server config to apache folder'
sudo "cp #{shared_path}/#{application}-vhost /etc/apache2/sites-available/#{application}-vhost"
puts 'Enabling the site on apache'
sudo "a2ensite #{application}-vhost"
end
This task uses an .erb file called vhost_config.erb that should be on the same directory of the file where this task is defined, hereâs the template:
<VirtualHost <%= domain %>:80>
ServerName <%= server_name %>
DocumentRoot <%= deploy_to + '/current/public' %>
<Directory "<%= deploy_to + '/current/public' %>">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
It uses our own configuration (from deploy.rb) to define the virtual server. When we call this task, it will not only generate this virtual host config, but also copy it to the sites-available and then call âa2ensite application-vhostâ installing the application on apache.
Then, you can just restart apache with the following task:
task :restart_apache do
puts 'Restarting the apache server'
sudo 'apache2ctl restart'
end
And youâre done, your Ubuntu server is running your rails application without any mongrels or anything else to manage besides Apache. Once your application is running, you can restart it with the following task:
desc 'Restarting the application'
task :restart_app do
puts 'Restarting the application'
run "touch #{deploy_to}/current/tmp/restart.txt"
end
Whenever you want to restart you app without restarting apache, itâs just a matter of touching the “/tmp/restart.txt” file (you have to create this file manually under your “/tmp” folder, it’s just a blank text file).
After all this, when once you have another application to deploy to the same Apache server, you will just generate a new virtual host file for it and it will be running without any other configuration or anything to manage. Could this be any better?
So, what about the good old mongrels?
mod_rails isnât going to replace Mongel all over the world, because itâs a Rails only solution (although it probably can be tweaked to run other frameworks). What the guys at mod_rails are doing is integrating the a rails cluster inside Apache itself, without the need to run a separate server cluster, but as this is a necessity generated by Railsâ mono-threaded model, other frameworks, like Merb, will keep on using mongrel as their application server.
Another reason not to just look at mod_rails is when you already have a cluster of rails applications running on many computers and proxied by a common HTTP server, as you really need to distribute the load through many machines using one as a load balancer, it will not be so easy as Iâm showing here, but even with a proxy, using apache at the application servers might help the performance of static content delivery.
Acknowledgements and references
Most of the installation instructions that you found here where taken from the following post by Vince Wadhwani (Thanks Vince!).
If you are not going to install mod_rails in an Ubuntu machine, checkout the mod_rails documentation.