Tweetegy On the edge of chaos with Ruby, Rails, JavaScript and AngularJS.

| About | Search | Archive | Github | RSS |

Using Railtie and Rails Engine in Gems

A (private) Gem I made recently required some code modifications that required digging into Railties and Rails Engines a little. The requirements were to inject some Javascript variables into the view on each request and to include a static Javascript file in the Asset Pipeline of the consuming Rails App (I know that this Gem will be used in Rails 3.x).

Problem 1: Inject Javascript vars into the view on each render using a Gem

So for the first problem I naturally chose to extend the Rails middleware stack. For problems like this I tend to work with a test Rails application to try things out first before moving the solution into a Gem. The solution for this problem (if putting the ruby file directly in the Rails lib directory) is as follows:

 1 require 'rack'
 2 
 3   class JSVars
 4 
 5     def initialize(app, options = {})
 6       @app = app
 7     end
 8 
 9     def call(env)
10       status, headers, response = @app.call(env)
11 
12       if status != 301 && response.respond_to?(:request)
13         response_string = inject_vars(response, headers)
14 
15         response = Rack::Response.new(response_string, status, headers)
16       else
17         Rack::Response.new(response, status, headers)
18       end
19     end
20 
21     ## inject_vars implementation not shown
22     ## all it does is add a <script> tag containing inline Javascript within the <head> tag of the html response string
23 end

In addition to this code living in lib/js_vars.rb it was necessary to add a line to configure the middleware of the application to use this, as follows:

1 config.middleware.use "JSVars"

This solution worked well directly inside the Rails app but I needed to share this functionality with numerous Rails apps so I needed it in a Gem. How can I configure middleware in a Rails app from my Gem? The answer is with a Railtie!

Solution 1: Use Railties!

Using Railties the solution is fortunately very simple. After moving the js_vars.rb file into the gems lib directory and removing the middleware configuration from the app, I added the following Railtie class to my Gem as follows:

1 class Railtie < Rails::Railtie
2   initializer "my_gem.insert_middleware" do |app|
3     app.config.middleware.use "MyGem::JSVars"
4   end
5 end

After adding the necessary require statements in my Gem, that was it! When I restarted my Rails app, the same functionality was working - the variables were being injected into the page as expected! Great! Now onto the next problem!

Problem 2: Dynamically add assets to the Rails Asset Pipeline from a Gem

I had a Javascript file that made use of these variables and I needed that to also reside within the Gem, but how to get that Javascript into the host Rails Asset Pipeline? The answer is use Rails Engines!

1 class Engine < Rails::Engine
2 end

After adding the necessary require statements for this Engine in my Gem, you guessed it, everything worked (again)! By simply including this class in the Gem it means that the Rails app the Gem is loaded into will search for assets inside the Gem! So if we add a .js file to app -> assets -> javascripts path (in the Gem) then this file can be referenced (and found!) in the Rails app as follows:

1 //= require my_gems_javascript

Conclusion

Rails 3.x brings us a whole set of wonderful and powerful tools: Engines, Railties, Middleware and the Asset Pipeline (as well as many other goodies!). If we want to split off and reuse functionality we can use Gems together with a mixture of these new tools. In this example, I used:

Of course this is only a small subset of the power of these tools and I highly recommend reading more about this subject.

Recommended Further Reading / Watching