This tutorial shows you more about the power and simplicity of Rails 3 Engines. When I release a new feature for one of my production Rails apps (or an entirely new app) that needs to be beta tested by a select group of people, it is usually necessary to password protect part or all of the application. A quick and dirty way to do this is to use basic http authentication with Nginx. Previously, I would choose this method because it is fairly easy to set up and it allowed me to create a single username and password for an entire group of testers, without having to modify the user authentication system that I use inside my Rails apps. If you are using nginx, then you can add something like this to the nginx.conf file:
It is also necessary to create the htpasswd file and add the user name and password hash. The password hash can be generated using Ruby’s String crypt method. When users visit the protected URL, the basic http auth browser popup is displayed and if they enter the correct login information, they gain access to the protected URL. While this setup is fairly simple, I prefer staying within Rails to achieve this and having more control over the login page, etc. HTTP Basic Authentication is a bit too basic for me. I decided to create a simple Rails 3 Engine that would allow me to easily lock down controller/actions and customize the login and error pages. I call this one lock. Note that this will not protect your public assets, only the Rails actions… so you may want to lock down your server using http basic if you need public assets protected as well.
Here is how you use it in your own application:
1. Install the gem
2. Generate the password file
3. Add the lock configuration to application_controller.rb
4. Unlock the app
visit this URL and enter the password you created with the generator
5. Override the views:
Refused Access Page: /app/views/lock/refused.html.erb
Unlock Confirmation Page: /app/views/lock/unlock.html.erb
By default, these views will render inside your default layout. To create a custom layout for these files, just add /app/views/layouts/lock.html.erb The layout must contain a yield.
TECH DETAILS ABOUT THE GEM
(if you are interested in how the gem was created)
The design around this was simple. Use a class method within the application_controller that accepts controllers & actions as parameters. The class method creates a before_filter and that takes these controllers & actions and compares them against the requested URL. If the URL matches the controller/action and the user has not unlocked the app, then access to the page is denied. Using Rails 3 engines makes it easy to add views and a LockController to handle the login, failed login and unlock confirmation pages. Here are the gory details about how this gem was created:
A Note about TESTING
I am using RSpec and Cucumber to test this engine. Engines are a different beast than other gems, because they are so closely tied to Rails. As a result of this, I prefer to create a new Rails 3 app inside my gem called “test_app” for any testing. I have seen a few things out there about creating a dummy app, that I am guessing is a stripped down version of a real rails app, but I am not sure what the advantage is over a real Rails app for testing. The footprint of a blank Rails app is not that large, so I doubt it is a concern about gem file size (which should not matter, since the test files are not packaged with the gem). I still need to read up and give it a fair comparison, but for now I dont see a problem with using a real Rails app to test engines.
First, I bootstrap the “test_app” with rspec and cucumber. Then I include the local path to the gem in the Gemfile:
gem “lock”, :path=>”/my_plugins/lock”
Providing the path to the local gem, will tell Bundler to load the files in that directory as opposed to installed the packaged gem. This is necessary, in order to avoid building your gem after every code change. If I remember correctly, you will need to generate a gemspec in the root of the gem before you bundle install with the “path” option.
Start with a gem skelaton and make it an engine
The skelaton can be as simple as a “lib” directory and a file inside the lib directory named “lock.rb”. To make it an engine only one file is needed. I always call it “engine.rb” and place it within a subdirectory of lib that matches the name of my engine. So here is my directory structure so far:
When the gem is loaded, it will first execute lock.rb, so the engine.rb file must be required in lock.rb:
The engine.rb file needs to subclass Rails::Engine, require rails and lock itself, in order to make it an engine that Rails can load:
Now the gem can function as an engine. So what does that really mean? Well for one, you use the same directory structure as you would in a normal Rails app. And you can add initializers to engine.rb to interact with rails when it is loading at boot.
Add the Rails-like directory structure
Take a look at my Rails-like directory structure:
The helper & model directories are not necessary for this gem, but I went ahead and added them in case I need them later.
Add the views & controllers
As I mentioned earlier I have 3 views that I use for the login form, unlock confirmation, and refusal page… there is not much to these views. Rails Engines also allow you to add routes, so I placed mine in /lock/config/routes.rb. Most of the code (and its not really much) is in the controllers. I have a lock_controller that handles the login. As you can see, it just checks to see if the form password is equal to the generated password. I placed the “password_match?” method in /lib/lock.rb, to reduce the logic in the controller.
I wrote a few blogs in March about how Rails prevents you from reopening engine classes. Basically, if you have the file in the parent Rails app, then the one in the engine is ignored. For this reason, I was not able to create a file named “application_contoller.rb” and simply add my methods to it. In order to make this work, I have to use a different file name (lock_application_controller.rb) and include/extend the methods into action_controller using an initializer. Here is what the module looks like:
When you create a class method and put it in action_controller, then application_controller can call this method and make it look like a nice DSL (as you can see in step #3 of the gem installation tutorial). So this class method just adds a before_filter and an instance method is created that matches what the before_filter is calling. lock_filter checks to see if it is a locked action and if the user has already unlocked the app. It redirects them to the refusal URL if they have not unlocked and the action is on the lock list. Note that you may prefer to use ActiveSupport::Concern when coding a module that includes instance methods and extends class methods. I am not using it here, but many find the syntax a bit sweeter, so if you dont know about it then google it.
Use an initializer to include the instance methods and extend the class methods in action_controller
There is a pretty cool feature in Rails 3, which allows you to lazy load code in initializers. Check out Simone Carletti’s tutorial on Lazy Load Hooks for details on how this works. In short, when action_controller is loaded, our code can be placed in that class. So I want to include/extend my methods in action_controller. Here is what the engine.rb file looks like after I add this code:
I find that the engine.rb is the best place to put code that dynamically modifies classes at boot. It gives you a central location where you can place all of these calls, and IMO makes engines easier to read. You know exactly what modifications are being made to Rails, just by looking at the engine file.
Creating the password generator
The last piece of the puzzle is to create a Rails generator to spit out our hashed password file. When you make your gem an engine, Rails automatically picks up your generator if you put it in the default path that is expected from an engine. Here is what the directory structure looks like:
So Rails is expecting this path for the generator class (expressing variables in uppercase):
USAGE will allow rails to ouput some documentation for it, and the templates directory (which I don’t use in lock) can house any template files needed by the generator. The generator class is pretty basic:
Subclassing “Rails::Generators::Base” gives you the functionality you need to make this a generator. Any methods contained within this class will be executed. Notice that the argument class method is necessary to pull in the command line arg for the password. source_root will tell the generator where your templates are stored. This code generates a salt and a password hash from the password & salt combination and writes it to a file using the create_file method. You can read more about Rails generators HERE.
So there you have it. Pretty basic, but IMO useful. I hope you find this gem useful and also hope you learned something about Rails Engines. If you have any problems with this gem, leave me a comment or open a github issue. As always, suggestions are welcome, as well as criticism!