Skip to main content

Mobomo webinars-now on demand! | learn more.

As a new developer to Ruby you might wonder how certain methods seem to be magically available without being strictly defined. Rails's dynamic finders (e.g. find_by_name) are one example of this kind of magic. It's very simple to implement magic such as this in Ruby, but it's also easy to implement things in a way that doesn't entirely mesh with standard Ruby object expectations.

Your Friend method_missing

The way that many magic methods are implemented is by overriding method_missing. This special method in Ruby is automatically called by the interpreter whenever a method is called that cannot be found. The default behavior of method_missing is to raise a NoMethodError letting the user know that the method that was called does not exist. However, by overriding this behavior we can allow the user to call methods that aren't strictly defined but rather programatically determined at runtime. Let's look at a simple example:

     class Nullifier       def method_missing(*args)         nil       end     end      nullifier = Nullifier.new     nullifier.some_method     # => nil     nullifier.foo(:bar, :baz) # => nil 

Here we simply told method_missing to immediately return nil, regardless of the method name or arguments passed. This essentially means that, for this class, any method call that is not defined on Object (the default superclass for new classes) will return nil.

While this example is certainly interesting, it doesn't necessarily give us more use in the real world. Let's take another example that actually does something useful. Let's make a hash that allows us to access its keys by making method calls:

     class SuperHash  'def'     h.something_else # => NoMethodError 

This behavior gives us something pretty simple yet powerful: we have manipulated the foundation of the class to give us runtime methods. There's a problem, though: using method_missing alone is only half the story.

Quack Check With respond_to?

In Ruby, you can call respond_to? with a symbol method name on any object and it should tell you whether or not that method exists on the object in question. This is part of what makes Ruby's duck-typing work so well. So in our example, we also want to be able to know if a method is there using respond_to?. So let's add a new override for the respond_to? method of our example above:

     class SuperHash < Hash       def respond_to?(symbol, include_private=false)         return true if key?(symbol.to_s)         super       end     end 

Well, that was easy enough. Now our SuperHash will return hash keys based on method_missing and even tell you if the method is there with respond_to?. But there's still one more thing we can do to clean things up a bit: notice how we have repeated functionality in that we check key? in both methods? Now that we have a respond_to? we can use that as a guard for method_missing to make it more confident:

     class SuperHash < Hash       def method_missing(method_name, *args)         return super unless respond_to?(method_name)         self[method_name].to_s       end     end 

Wait, that can't be right, can it? Can we just assume that we can call the key like that? Of course! We already know that no existing method was called if method_missing is activated. That means that if respond_to? is true but no existing method was called, there must be a key in our hash that caused respond_to? to return true. Therefore we can confidently assume that the key exists and simply return it, removing the conditional and cleaning up the method_missing substantially.

Now that you know how method_missing and respond_to? can work together to add functionality to an object at runtime, you have a powerful new tool in your metaprogramming arsenal. Enjoy it!

Categories
Author

RSpec is a great tool that has come to replace Test::Unit for many Rails developers. Autotest makes it go even faster, and has become an indispensable part of my development environment. However, it has always been somewhat-to-extremely difficult to use RSpec when developing Rails plugins. In this post I will walk through step-by-step how to get RSpec and Autotest working with your plugin.

This plugin is assuming that you are running Rails >= 2.1 and have already installed RSpec and RSpec::Rails as plugins in your Rails project like so:

script/plugin install git://github.com/dchelimsky/rspec.git script/plugin install git://github.com/dchelimsky/rspec-rails.git

And also gotten RSpec up and running by calling script/generate rspec.

Generate It

Luckily, I wasn’t the first person who ever wanted to create a plugin that was tested with RSpec. The Rspec Plugin Generator will do most of the heavy lifting for us when we start out. Just install it like so:

script/plugin install git://github.com/pat-maddox/rspec-plugin-generator.git

And you’re ready to get started. I’m assuming here that this is a brand new plugin, if it’s already in development you may need to run this in a fresh directory and then copy/paste files as needed to glue it together. Let’s say I’m writing a plugin called new_fu. I can generate an RSpec-ready plugin simply by calling:

script/generate rspec_plugin new_fu

This will generate the standard plugin structure as well as some extra files:

create  vendor/plugins/new_fu/spec create  vendor/plugins/new_fu/spec/spec_helper.rb create  vendor/plugins/new_fu/spec/new_fu_spec.rb

You can take a look at these to see how they work, but pretty simply they hook your plugin up so that it can be run with rake spec:plugins. Let’s add a simple example to our new_fu_spec.rb file:

require File.dirname(__FILE__) + '/spec_helper'  describe "NewFu" do   it "should have a pending spec" end

Now if you run rake spec:plugins you should see one pending spec. Congratulations, your plugin is now running on RSpec!

Autotest Like a Champ

Ok, so now we’re up and running with RSpec on our plugin. That’s great, but if you have several plugins in the same Rails app that all have specs, it starts to get messy when you run that rake spec:plugins. Not to mention how long it takes between runs! We need to get an autotest setup like we have for our main Rails app!

I struggled with getting this to work for a long time, so thanks to this post on Rails Symphonies for finally pointing me in the right direction. First we need to create an autotest/discover.rb file in our plugin’s lib directory. In that file, put this code:

$:.push(File.join(File.dirname(__FILE__), %w[.. .. rspec]))       Autotest.add_discovery do     "rspec"  end

This gets us almost exactly where we want to be. However, the first time I ran it I had two problems: some specs that I had written were strangely failing, and it wasn’t in color or following the rest of my spec.opts preferences from my main app!

To remedy this, we need a spec.opts in the spec directory of the plugin. You can either copy and paste it in from your Rails app (my recommendation if you are publishing your plugin) or you can just create a softlink back to it:

ln -s ../../../../spec/spec.opts spec.opts

That’s it! Now if you run autotest you should be running all of the specs for your plugin just as you would if you were running them for your app. Note that this doesn’t hook in to your app’s autotest, which may be desirable or undesirable to your specific needs.

Categories
Author

The new Gem Dependencies of Rails 2.1 give developers an easier-than-ever ability to keep track of and maintain the various library dependencies inherent with any project. However, a much-overlooked additional feature of the Gem Dependencies is the ability to package traditional Rails plugins as a gem and have them hooked in properly. This article is designed as an introduction to how to write and use plugins as gems in Rails projects.

The Basics

The basic method by which this is achievable is that any plugin included through a config.gem command will automatically have the gem-packed file rails/init.rb run upon Rails’s initialization. All it takes is a little bit of effort, and any Rails plugin can be packaged as a gem and easily depended upon through gem dependencies.

You may be wondering why this is a “big deal.” Plugins are already dead simple to install in Rails (and you can even script/plugin install straight from Git now!), why do we need GemPlugins? It’s simple, really: RubyGems are a rock-solid established way of easily distributing versioned reusable bits of code. Using gems for plugins allows for a greater standardization of the way in which plugins are maintained and distributed, as well as a simple path for version-locking to ensure compatibility with legacy code etc.

Another reason that GemPlugins are important is that they provide a level of abstraction from Rails: by releasing a gem rails/init.rb you could also use the same exact code to release a Merb plugin or any other framework that supports gemified add-ons. I think you will begin to see a number of cross-framework plugins be developed as Rails gets some company and shares alike.

Using a GemPlugin

First, let’s go through the process required to use an existing gem plugin. I’m going to be using my Acts As Taggable On plugin as an example throughout because I just recently went through the process of making it available as a gem.

First, you will need to include the dependency in your environment.rb file. I’m assuming here that most plugins are going to be hosted on GitHub, but the same should be true for any gem source.

# in environment.rb    config.gem "mbleigh-acts-as-taggable-on", :source => "http://gems.github.com", :lib => "acts-as-taggable-on"

This is the standard usage of gem dependencies, and for more info on this you can see Ryan Daigle’s post or watch the RailsCast on the subject. Now assuming that you don’t already have the gem in question installed, it’s simple to grab it:

rake gems:install

This will automatically install any gem dependencies in your project, and will tell you what’s happening the same as if you had run gem install from the command line.

That’s it! Once you have successfully installed the necessary gem, you can simply start up your Rails server and the plugin will be loaded and initialized as though it were living in your vendor/plugins directory.

Now that you know how to use a GemPlugin, I’ll show you how you can take an existing plugin and gemify it quickly and painlessly.

Making a GemPlugin

Let’s say I have a plugin called awesome_fu that lives on GitHub at mbleigh/awesome-fu. I’ve already released this plugin, it works great, and now I want to make it compatible with GemPlugins.

First, let’s create a gemspec called awesome-fu.gemspec in line with the requirements for the GitHub Gem Repository. In order to make the file list, I usually find it’s easiest to “find **” in the plugin directory, then copy it into TextMate, make the modifications I need for manifest (using a regular expression to quote each of the files), and saving it in the spec. If you have only a few files in your plugin, it may be easier just to add them by hand.

Next we need to add rails/init.rb. This is a little bit troublesome, because we still want our plugin to work if installed through the traditional method, so we also need init.rb to run the same code (this is automatically fine in edge Rails). What I did for my plugin is copy all of my init.rb code into rails/init.rb and then change init.rb to the following:

require File.dirname(__FILE__) + "/rails/init"

Now they both run the same code without any kind of replication, great! This means that now I have set up my plugin to work equally as a GemPlugin or a traditional plugin with just a couple minutes of work.

All that’s left to do is switch on the RubyGem setting for my GitHub project, update the README, and push! Now anyone will be able to easily require the plugin as a gem dependency and you will get all of the accolades associated with releasing your plugin the “new and hip” way.

Caveat Coder

The one problem with GemPlugins that I have run into is that if you unpack your gems using “rake gems:unpack” the rails/init.rb file is not run on initialization. This is a known issue that is supposed (?) to be resolved but I have still experienced this problem in my experiments. Hopefully this issue will be fully resolved in edge Rails soon and the glorious future of GemPlugins can begin.

Categories
Author
1
Subscribe to How-To