Writing C extensions in RubyGems using newgem generators (plus a free TextMate bundle)
Already know C extensions in RubyGems? Cool - then just run the following cmds and see what can be generated for you; plus check out the TextMate bundle at the bottom.
sudo gem install newgem newgem pickaxe cd pickaxe script/generate extconf my_test rake test
For everyone else…
Its 15000km from Brisbane AU to Prague CZ where Euruko2008 - the European Ruby Conf - was held. I came ready to talk, to met lots of cool multi-lingual Rubyists, and to learn. Ooh, I learnt something alright.
Tim Becker was introducing Native C Extensions for Ruby, and fortunately he said “now, everyone, follow along with this example”. I’d never done native C extensions, but I’d received lots of requests from RubyGem developers on how to do it. I had no idea.
So I was typing in everything Tim told me to type in. When Tim changed slides too quickly, I may have yelled at him to slow down. Perhaps I was the only one doing his tutorial out of 300 people, but I didn’t care. This was gold.
After he finished his session, I dragged him off into the corridor with Jonas Pfenniger (zimbatm), and the three of us mapped out a generic layout for how native C extensions work within RubyGems. I didn’t know any of this, but Tim and Jonas did, and we probably looked silly sitting at a small table in the middle of the narrow corridor.
But at the end, we had a working RubyGem with native C extensions that were built: when the tests were executed via rake, and when the gem was installed. The next day I figured out how to get the C extension built via autotest.
Thanks to Tim and Jonas I was able to then write a extconf generator for RubyGems so that its super-super easy to get started writing native C extensions within RubyGems.
Tutorial
This tutorial is for *nix, as I’m still investigating win32 extensions, and jruby + .net/ironruby extensions. So when I figure that out - hopefully with the help of other people currently at RubyFools conferences, I’ll get back to you.
The code comes from the Pickaxe book - p264, and we’ll insert it into a new RubyGem using newgem (version 0.20.1+):
sudo gem install newgem newgem pickaxe cd pickaxe script/generate extconf my_test
Create a test for a class MyTest that doesn’t exist yet:
# test/test_my_test_extn.rb
require "test/unit"
require 'pickaxe'
class TestMyTestExtn < Test::Unit::TestCase
def test_working
t = MyTest.new
assert_equal(Object, MyTest.superclass)
assert_equal(MyTest, t.class)
t.add(1)
t.add(2)
assert_equal([1,2], t.instance_eval(”@arr”))
end
end
Run rake to build the C extension and run the tests. You can also run autottest and it will automatically build the C extension before running the tests.
To create the MyTest class, using the code from p262 of the Pickaxe book:
#include "ruby.h"
static int id_push;
static VALUE t_init(VALUE self)
{
VALUE arr;
arr = rb_ary_new();
rb_iv_set(self, "@arr", arr);
return self;
}
static VALUE t_add(VALUE self, VALUE obj)
{
VALUE arr;
arr = rb_iv_get(self, "@arr");
rb_funcall(arr, id_push, 1, obj);
return arr;
}
VALUE cTest;
void Init_my_test() {
cTest = rb_define_class("MyTest", rb_cObject);
rb_define_method(cTest, "initialize", t_init, 0);
rb_define_method(cTest, "add", t_add, 1);
id_push = rb_intern("push");
}
To lib/pickaxe.rb:
require "my_test.so" # or require 'my_test' if its unique
The last line will import the generated shared library. If the RubyGem is tested or installed on Windows, then the .dll file will be automatically loaded instead. The “.so” notation is merely a placeholder to explicitly specify the shared C-extension, rather than any Ruby library of the same name.
Now run tests (rake), the C extension will be rebuilt and the tests will pass.
Build and install RubyGem
rake manifest:refresh rake install_gem irb -rubygems -rpickaxe > a = MyTest.new > a.add 3
You have successfully created a C-extension within RubyGems, using TDD.
TextMate bundle for Ruby C extensions
I’ve started a TextMate bundle to give syntax highlighting + some simple snippets for developing the C files for Ruby extensions.
To install:
cd ~/Library/Application Support/TextMate/Bundles git clone git://github.com/drnic/ruby-c-extensions-tmbundle.git "Ruby C Extensions.tmbundle" or wget http://github.com/drnic/ruby-c-extensions-tmbundle/tarball/master tar xfv drnic-ruby-c-extensions-tmbundle-master.tar.gz mv drnic-ruby-c-extensions-tmbundle-master "Ruby C Extensions.tmbundle"
Then restart TextMate or “Reload Bundles”.
You can clone/fork the source via http://github.com/drnic/ruby-c-extensions-tmbundle/tree/master
RejectConf tonight - RailsConfEurope 2007
Tonight only. Live in Berlin. One show only. RejectConf. Woohoo!
Who? What?
Well, it could be you. If you are in Berlin tonight, come to RejectConf.
Time: 9pm (get there early to ensure a seat or at least a space indoors)
Location: NOT NEAR THE VENUE
The remaining details are documented so you can figure out which U-Bahn lines to take and which stops to get off.
MyConfPlan for Sale - for Charity
MyConfPlan allows conference attendees to view conference sessions, pick which ones they are attending, see what everyone else is attending, and share what they are attending via personal URLs and cool widgets. Its written in Rails, currently uses Hobo, and a nice slab of Javascript for the cool bits.
And it could all be yours, with an auction on eBay. The auction starts at $1 with NO RESERVE.
But, I’d like to think it could raise $75.
Why? Because all the money paid by the winner is going to the Chad Fowler and Marcel Molina Jr fund for all-things Humanitarian, and so I can go to their Testing Training Day on the 17th of September in Berlin. Minimum donation is $75.
That’s right, the winner’s money could be entirely tax-deductible as its all going to charity. (You get the site’s code base and the myconfplan.com domain (cost of transfer is yours) are yours. I get entry to the workshop.
If it raises $500 then you are donating $500. If it raises $2,000,000 then you are donating $2×10^6.
The site needs an owner/occupier/developer. It needs support leading to upto RailsConf Europe, for example.
At some time in the future, it probably needs to be rewritten. I never wrote (m)any tests. It was built with Hobo, and I built it so fast I crossed the line of “playing with Hobo” and “developing and launching an app with Hobo” very quickly. If you don’t know Hobo, you’ll probably feel more confortable starting some parts from scratch, writing your tests, writing normal Rails controllers and views, etc.
Cool things you can do to MyConfPlan (please add more in the comments if can think of them):
- For the users:
- Let users have “friends” - Show their friends’ on the main session page so they can see immediately which sessions their friends are going to
- Allow Speakers to “own” their session, and allow them to update their session descriptions etc.
- Allow uploading of slides, audio, etc after a session has been completed.
- Give each session a forum before and after the session so people can discuss the session
- Internally:
- Allow re-importing of conference sessions from their source. Conferences keep changing/adding sessions.
- Import hCalendar HTML pages directly. O’Reilly conferences post their sessions in hCalendar microformat, so the data should be directly accessible without the ugly rake task I wrote
- Caching - its got none where it counts
So, get bidding, and own yourself a very cool website.
NewGem Generator - now with script/generate
The New Gem Generator (0.13.0)’s newgem command now behaves like the rails command thanks to RubiGen - a new project that is an extraction of the rails_generator.
Developing a RubyGem? You can now have a script/generate command, just like Rails. Other projects, say like Merb, can do this too.
Awesomeness with a Gold Star. (disclosure: Dr Nic awarded this Gold Star to himself)
Warning
This article can get confusing.
Especially around the heading “Generating Generators”.
Plus, this article talks about two projects - an update to New Gem Generator (0.13.0) and the new RubiGen project.
So, give yourself time to digest it all, and then perhaps come back and read it again.
And then perhaps re-write the article for me.
“A generator that can provide generators to your projects, so that you can write generators for your other projects.”
Re-write it - I dare you.
Background
One of the killer features of Rails is the Rails Generator.
It does three things:
- generates an entire scaffold for your application, thus sharing with you its conventions (over configuration) methodology; and
- it then allows you to generate more stuff, like models, controllers, plugins etc. Things that are relevant to a Rails app. Finally,
- it allows you to write your own generators for Rails apps.
Now you can do all this with New Gem.
Killer Feature #1 - upgrade path
Never before have you been able to run ‘newgem’ on top of an existing RubyGem. Why? It used to blow away the entire folder, and then start writing new files… not very friendly.
Now, just like the rails command, you can go into your RubyGem development directory, and run newgem . and you will be asked which files to override.
NOTE: Copy your Rakefile to Rakefile.old, and after running newgem . copy the configuration information into the new file config/hoe.rb.
If you’re using newgem already, I dare you to upgrade as above. (Ok, perhaps make a backup copy of your work first…)
Killer Feature #2 - script/generate
Once you’ve upgraded, or create a new RubyGem (using newgem gemname), you’ll now have two scripts: script/generate and script/destroy, just like Rails has. (and matching generate.cmd and destroy.cmd for Windows users)
Try them out:
script/generate ... Installed Generators Rubygems: application_generator, component_generator, executable, install_jruby, install_rspec, install_website Builtin: test_unit
Oh yeah, I’ve had some fun extracting things into generators.
- Install the dubious-looking NewGem website -
script/generate install_website. - Install RSpec support -
script/generate install_rspec. - Make the RubyGem a JRuby gem -
script/generate install_jruby(the generated gem will have-jruby.gemin its name) - Create an executable Ruby app -
script/generate executable appname
These generators are also reused via the newgem commands various options (Run newgem to see them.)
Killer Feature #3 - generate generators
I’ll post more about RubiGen later, but you can create a new rails-like command-line app that generates a whole stack load of directories and files using script/generate application_generator appname.
There is a large USAGE rundown if you run script/generate application_generator.
Want to create your own generators for developing RubyGems? (similar to creating a generator for a Rails app, but for RubyGems)
script/generate component_generator foobar rubygems
create rubygems_generators/foobar/templates
exists test
create rubygems_generators/foobar/foobar_generator.rb
create test/test_foobar_generator.rb
create test/test_generator_helper.rb
create rubygems_generators/foobar/USAGE
readme readme
Firstly, note test/test_generatorname_generator.rb - that’s right, you get a test stub for your new generator. Start there, write tests, then write your generator. There’s inline help for useful assertions.
Secondly, note rubygems_generators folder. This folder is the “scope” of the generator. As it starts with “rubygems” the generator will only be available when you are developing rubygems. It will not show up in Rails, nor Merb or Camping or any other place that may support RubiGen one day.
Similarly, if you want to write a Rails generator using the component_generator then specify the scope as rails.
script/generate component_generator booya rails
create rails_generators/booya/templates
exists test
create rails_generators/booya/booya_generator.rb
create test/test_booya_generator.rb
identical test/test_generator_helper.rb
create rails_generators/booya/USAGE
readme readme
In Edge Rails (and any Rails version after 1.2.3), the script/generate command in Rails will search all RubyGems for /rails_generator/* folders in addition to the existing search paths (ticket).
Thanks goes to Rails Generator
I’ve long been in love with the Rails Generator for what it does, the beautiful syntax for specifying a generator, etc. I don’t know who wrote what bit, e.g. if DHH wrote all of it, or others wrote nice chunks, but its awesome. Thanks DHH and co. Thanks to Jeremy Kemper
RubiGen is 95% Rails Generator code, with extensions to support scoping. Setting Rails Generator free of its Rails constraints is a tribute to it.
Now RubyGems can use generators, and any other frameworks can integrate generators and their developers can write and distribute additional generators for those frameworks.
I award myself 2 gold stars, and a scratch-n-sniff
Sample Rails app: multi-OpenIDs per user
Last time, on “Dr Nic loves OpenID”…
Dr Nic had watched a video by Simon Willison glorifying the delights of OpenID to some Googlers.
Dr Nic decided that each User might like to link multiple OpenIDs to their application account.
Of course, if a user wanted multiple accounts then they could use their different OpenIDs to do that too.
All Dr Nic had to do now, was write some code…
In the mean-whilst…
Later that very same day, Dr Nic reads a tutorial by Joseph Smarr at Plaxo - A Recipe for OpenID-Enabling Your Site. He is joyed that Joseph had said the same thing:
It’s a many-to-one relationship (each user can have multiple OpenIDs attached to their account,
but a given OpenID can only be claimed by a single user)
“Champion!” Exclaims Dr Nic in the seclusion of his underground lair 300m isolated tower kitchen.
This article also lays out an implementation plan. It is in excruciating detail, including table schemas, stylesheet snippets and form suggestions.
Dr Nic rubbed his hands together and gets busy…
[/end of 3rd person]
Sample app
As a developer, the only reason you wouldn’t want to support “multiple OpenIDs per User” is because it is a PITA to implement, in that its an administrative bonus feature for your app. Its not really adding any real uber value.
So I followed his instructions - more or less - an have created a sample app. The README includes a demonstration of the app. You can also download it as a ZIP or TAR bundle.
Now you can add multiple OpenIDs per user and be lazy at the same time.
The app builds on top of Ben Curtis’ OpenID sample app, and uses a variation of Ryan Bates’ Railscasts theme, because its sexy.
NOTE: The code is built for Rails Edge, but the two bundles do not include rails edge, so you need to rake rails:freeze:edge after unpacking it (all this and more in the README). If you want to work with Rails 1.2.3 gems, then.. um, I didn’t write a list of changes, but it might still work, I haven’t tested it. Ben’s original app was built for 1.2.3, so at its very heart it should still work. Perhaps.
Deviations from the Joesph’s tutorial
I want my users to be able to throw their OpenID around on any old page where they see an OpenID field.
That is, allow users to login or register with OpenID from either the “login” form or the “registration/signup” form. Within Rails, these are traditionally two separate controllers (sessions and users respectively).
So, I extracted out this functionality in to a module (helper user_openids_helper) and its used by the two controllers.
How’s it work?
Same way that Ben’s sample app worked, more or less, so read his article a couple times, then read through the source code for the multi-OpenID sample app.
I guess a couple tourist highlights might be:
app/helpers/user_openids_helper.rb- the extract fancy codeconfig/routes.rb- we need more routes to support alternate callbacks from OpenID providerssessions_controller.rbandusers_controller.rb- they are emptier and cleaner nowtest\functionals\sessions_controller.rb- at the bottom are some test cases for the login/registration features ofuser_openids_helper
So, grab your Code Camera…



