Anyone else think that Godwin’s Law should be amended to include Rails?
Just a thought.
SUMMARY: I spent two years trying to make Rails do something it wasn’t meant to do, then realized my old abandoned language (PHP, in my case) would do just fine if approached with my new Rails-gained wisdom.
INTRO / BACKGROUND:
Back in January 2005, I announced on the O’Reilly blog that I was going to completely scrap over 100,000 lines of messy PHP code in my existing CD Baby (cdbaby.com) website, and rewrite the entire thing in Rails, from scratch.
I hired one of the best Rails programmers in the world (Jeremy Kemper aka bitsweat), and we set off on this huge task with intensity. The first few months showed good progress, and Jeremy could not have been more amazing, twisting the deep inner guts of Rails to make it do things it was never intended to do.
But at every step, it seemed our needs clashed with Rails’ preferences. (Like trying to turn a train into a boat. It’s do-able with a lot of glue. But it’s damn hard. And certainly makes you ask why you’re really doing this.)
Two years (!) later, after various setbacks, we were less than halfway done.* (To be fair to Jeremy’s mad skillz: many setbacks were because of tech emergencies that pulled our attention to other internal projects that were not the rewrite itself.) The entire music distribution world had changed, and we were still working on the same goddamn rewrite. I said fuckit, and we abandoned the Rails rewrite. Jeremy took a job with 37 Signals, and that was that.
I didn’t abandon the rewrite IDEA, though. I just asked myself one important question:
“Is there anything Rails can do, that PHP CAN’T do?”
The answer is no.
I threw away 2 years of Rails code, and opened a new empty Subversion respository.
Then in a mere TWO MONTHS, by myself, not even telling anyone I was doing this, using nothing but vi, and no frameworks, I rewrote CD Baby from scratch in PHP. Done! Launched! And it works amazingly well.
It’s the most beautiful PHP I’ve ever written, all wonderfully MVC and DRY, and and I owe it all to Rails.
Inspired by Rails:
*- all logic is coming from the models, one per database table, like Martin Fowler’s Active Record pattern.
*- no requires or includes needed, thanks to __autoload.
*- real MVC separation: controllers have no HTML or business-logic, and only use REST-approved HTTP. (GET is only get. Any destructive actions require POST.)
*- all HTML coming from a cute and powerful templating system I whipped up in 80 lines, all multi-lingual and caching and everything
*- … and much more. In only 12,000 lines of code, including HTML templates. (Down from 90,000, before.)
Though I’m not saying other people should do what I’ve done, I thought I should share my reasons and lessons-learned, here:
SEVEN REASONS I SWITCHED BACK TO PHP AFTER 2 YEARS ON RAILS:
#1 - “IS THERE ANYTHING RAILS/RUBY CAN DO THAT PHP CAN’T DO? … (thinking)… NO.”
For 2 years, I thought Rails is genius, PHP is shit. Rails is powerful, PHP is crap.
I was nearly killing my company in the name of blindly insisting Rails was the answer to all questions, timeframes be damned.
But when I took a real emotionless non-prejudiced look at it, I realized the language didn’t matter that much.
Ruby is prettier. Rails has nice shortcuts. But no big shortcuts I can’t code-up myself in a day if needed.
Looked at from a real practical point of view, I could do anything in PHP, and there were many business reasons to do so.
#2 - OUR ENTIRE COMPANY’S STUFF WAS IN PHP: DON’T UNDERESTIMATE INTEGRATION
By the old plan (ditching all PHP and doing it all in Rails), there was going to be this One Big Day, where our entire Intranet, Storefront, Members’ Login Area, and dozens of cron shell scripts were ALL going to have to change. 85 employees re-trained. All customers and clients calling up furious that One Big Day, with questions about the new system.
Instead, I was able to slowly gut the ugly PHP and replace it with beautiful PHP. Launch in stages. No big re-training.
#3 - DON’T WANT WHAT I DON’T NEED
I admire the hell out of the Rails core gang that actually understand every line inside Rails itself. But I don’t. And I’m sure I will never use 90% of it.
With my little self-made system, every line is only what’s absolutely necessary. That makes me extremely happy and comfortable.
#4 - IT’S SMALL AND FAST
One little 2U LAMP server is serving up a ton of cdbaby.com traffic damn fast with hardly any load.
#5 - IT’S BUILT TO MY TASTES
I don’t need to adapt my ways to Rails. I tell PHP exactly what I want to do, the way I want to do it, and it doesn’t complain.
I was having to hack-up Rails with all kinds of plugins and mods to get it to be the multi-lingual integration to our existing 95-table database.
My new code was made just for me. The most efficient possible code to work with our exact needs.
#6 - I LOVE SQL
Speaking of tastes: tiny but important thing : I love SQL. I dream in queries. I think in tables.
I was always fighting against Rails and its migrations hiding my beloved SQL from me.
#7 - PROGRAMMING LANGUAGES ARE LIKE GIRLFRIENDS: THE NEW ONE IS BETTER BECAUSE *YOU* ARE BETTER
Rails was an amazing teacher. I loved it’s “do exactly as I say” paint-by-numbers framework that taught me some great guidelines.
I love Ruby for making me really understand OOP. God, Ruby is so beautiful. I love you, Ruby.
But the main reason that any programmer learning any new language thinks the new language is SO much better than the old one is because he’s a better programmer now! You look back at your old ugly PHP code, compared to your new beautiful Ruby code, and think, “God that PHP is ugly!” But don’t forget you wrote that PHP years ago and are unfairly discriminating against it now.
It’s not the language (entirely). It’s you, dude. You’re better now. Give yourself some credit.
Ok. All that being said, I’m looking forward to using Rails some day when I start a brand new project from scratch, with Rails in mind from the beginning.
But I hope that this reaches someone somewhere thinking, “God our old code is ugly. If we only threw it all away and did it all over in Rails, it’d be so much easier!”
It’s often been said that Perl’s greatest strength is CPAN, Perl’s vast collection of free libraries contributed by developers from around the world. Recently I started to wonder about RubyForge and how RubyForge stacks up against CPAN in general.1
First, length of service. CPAN has been around for 12 years (October 1995). RubyForge has been in existence for just over 4 years (July, 2003).
Second, the number of users. RubyForge boasts over 20,300 users. CPAN, on the other hand, has far less at just over 6,150 registered users.2 Not every registered user is associated with a project, however. There are 3635 users are associated with a project on RubyForge, while on CPAN there are 3774 users associated with at least one library. Of the 3635 users on RubyForge, 849 are associated with more than one project.3
Third, the number of libraries. CPAN boasts approximately 13,500 separate libraries. RubyForge currently has approximately 5000 separate libraries, organized into about 4800 projects. That means, on average, most projects have one library, but some have multiple libraries per project.4 While RubyForge has far fewer libaries than CPAN, the ratio isn’t nearly as large as I would have thought.
Quick aside. I didn’t do any real analysis against Python, but the home page for the Vaults of Parnassus shows 2025 libraries.
Fourth, library quality and usefulness (more subjective here). There’s a lot of overlap and, well, cruft on CPAN.5 There are over 300 Acme (joke) modules. There are multiple wrappers for the same underlying library, such as many of the “Tiny” and “Simple” modules. There are libraries that should have been bundled together but weren’t, such as the various Chemistry::PointGroup libs. There are also libraries that have similar or identical functionality to other libraries, such as many of the List and/or Array libraries.
On top of that, a healthy chunk of the Perl libraries on CPAN are either unnecessary in Ruby or contain behavior that’s already baked into Ruby itself. Examples include a large collection of OO modules (Class::Accessors and the like), a large number of modules that create various IO, Number, File, Array, String and Hash classes, and related methods, that Ruby has builtin (Array, Array::List, File::chmod, and so on), over 200 “Tie” modules (Ruby doesn’t need ‘tie’), over 90 libraries for interacting with CPAN or the RT backend itself (I only know of one library for RubyForge), and equivalent libraries that are already part of Ruby’s standard library (e.g. libwww).
Last, release frequency. Between August 14th and September 14th there were 1133 releases from 690 distinct libraries on CPAN 6. RubyForge, by contrast, had 612 releases in the same date range (although I wasn’t sure how many of them were from distinct libraries at the time of this writing). So, slightly less than half of CPAN at the moment.
What do all these numbers mean? Good question. I think, at the very least, it means that the Ruby community is doing very well in terms of library development and releases. I give Tom Copeland a huge amount of credit for that, in that I think the very act of creating RubyForge fostered an atmosphere of development (collaborative or otherwise) and inspired programmers new to Ruby to take the step of releasing their own software. I can’t prove it, of course. It’s just a gut feeling I have after watching the Ruby community grow for seven years.
It also means that Perl is still going strong, cruft and all. You can’t really argue too much with their release rate, and some of it is really good stuff, too. I would say that CPAN still has the edge in database interfaces, Apache libraries and wrappers for 3rd party commercial libraries, among a few other things.[7]
But, we’re catching up, and fast. :)
See you next Wednesday.[8]
1 When I say “CPAN”, I’m generally referring to search.cpan.org plus RT.
2 I scraped the Authors pages to get the total. At the time of this writing it was 6152, although a handful of these appear to be generic logins.
3 CPAN isn’t a collaborative development environment, so there may be multiple users actively associated with a given library, but there’s no way to tell without manually digging through files.
4This number does not include a number of libraries listed on the RAA that are not on RubyForge. I’d make a very rough guess of about 200. Hard numbers welcome.
5 I should know. I own some of the cruft.
6 There were anywhere from 1 to 10 releases per library in that period. Some had multiple releases in the same day, a curious trend on CPAN.
7 Port a Perl module today! I should mention that I didn’t do any very long term trending, so I guess it’s possible that the releases per month have dropped, but I somehow doubt it. Feel free to prove me wrong (or right).
8Many thanks go to Tom Copeland for providing me with the RubyForge statistics.
This year is a good year for Ruby on the east coast. We kicked things off with a bang with the Gotham Ruby Conference in April, saw the Ruby Hoedown come and go in Raleigh bringing along with it some great videos of their talks, and of course, will see the Seventh International RubyConf in Charlotte in just a couple months.
However, these aren’t the only games in town. We’ve also got Ruby East 2007, which is a one day, multi-track, Rails friendly regional conference at the Penn State Great Valley Campus. They’ve managed to get an impressive list of speakers together, and even let two of those vagabonds from the Ruby Reports project sneak in to do their ranting.
If you’re local to the area and don’t think you’ll make RubyConf this year, this is a good chance to still get your fix. If you’ve been tinkering with Ruport and want to shake down me or Mike, this is also a chance for that.
From what I’ve seen so far, these regional Ruby conferences are always a lot of fun, especially because each one takes on its own unique flavor. If you want to go to this one, you should probably register soon because the event takes place on Friday, September 28, which is right around the corner.
Hope to see you there!
These days, it seems I hardly have the time for doing fun random hacks. So here I’ve started one, and if anyone finds it interesting, please take it from here and let me know how it turns out.
Loosely based off of AIML, kind of, but not really:
class Conversation def initialize(person) @person = person @response_id = 0 end attr_reader :response_id def say(msg) print "#{@person}: " @response_id = Response[@response_id].respond_to(msg) end class Response def self.responses @responses ||= {} end def self.[](id) responses[id] end def initialize(id) @id = id self.class.responses[id] = self @matchers = [] @messages = [] end attr_reader :id,:matchers def when(pattern,id) @matchersI’m looking for someone to take over PDF::Writer, color-tools, and Transaction::Simple. I do not have time to maintain these anymore. I should have done this months ago, but pride of ownership and a belief that more free time would be just around the corner got in the way.
You can read more details on my original blog posting at my personal blog.
Anyone interested? Anyone know anyone interested?
I think most Rubyists have picked up a good trick or two from Jim Weirich. Though it’s only a tiny part of his latest article (Using Flexmock to Test Computational Fluid Dynamics Code), I got excited to see his ‘Existence Test’ in his code:
def test_initial_conditions q = F3DQueue.new assert_not_nil q endLooks pretty simple, eh? You might be quick to say that this doesn’t do anything. However, it is actually a pretty clever practice. This test makes sure the tests themselves are working as expected. I was already in the habit of starting with a failure, usually something like:
def test_doomed flunk endThe purpose of the above is simply to make sure your tests are picked up within your suite, and aren’t being overlooked by your Rakefile, autotest, or whatever runner you’re using. But the existence test actually goes a little farther. Because you’re initializing an object, you’re making sure that the files you need to be loading are present, that you can build your objects, *and* that your tests are hooked up.
After you’ve got a couple tests passing, you can remove this sanity check or morph it into a setup(), whatever makes sense.
Many people think this is a little paranoid, and most of the time, it is. Still, all it takes is one bad experience coding under falsely passing tests, and you’ll be converted in no time. :)
This package is the most simple way to equip your Macintosh Apple OSX System with Ruby - similar to the Windows Ruby One-Click Installer. It replaces the broken Readline library, updates to a current version of SQLite3 and prepares your OSX for Rails, which needs at least Ruby 1.8.4 to run. The current Ruby Version is 1.8.6 (1.8.5 is recommended for Rails) and Rubygems 0.9.4.
This package’s intention is to remain small while being a Universal Binary that serves everything to deploy Ruby Applications on OSX Machines - Cheetah, Puma, Jaguar, Panther, Tiger or Leopard. You don’t need to compile anything and you don’t need Apple’s Developer Tools (xcode) installed.
Some people use fink or other ports software, others (myself included) take the time to read through the HiveLogic post and do everything from scratch.
Now there’s another option, and if it lives up to its claims, it should be A Good Thing.
Well, our first ever spotlighted project has been selected. For June, it’s Sequel, the Concise ORM for Ruby.
Development on this project has been like wildfire for the last couple months, with releases every few weeks. Support is available for PostgresSQL, MySQL, SQLite3, and ODBC. There is also a wrapper for RubyDBI, which means that you can hook into pretty much any mainstream database if needed.
Because code often speaks louder than words, we’ll take a look at Sharon’s proposal which won the June spotlight.
The Proposal require 'rubygems' require 'sequel/sqlite' # gem install sequel (and sqlite3 as well) DB = Sequel.open 'sqlite:/' # memory DB DB.create_table :items do text :name decimal :price end items = DB[:items] 1000.times {|i| items "item#{i}", :price => rand * 100}} puts "#{items.count} total items" puts "Average price: #{items.avg(:price)}" puts "3 most expensive items:" items.order(:price.DESC).limit(3).print(:name, :price) puts "#{items.filter {price < items.avg(:price)}.count} below the average" puts "Changing price for expensive items" items.filter {price > items.avg(:price)}.update(:price => 'price + 10'.expr) puts "Highest price: #{items.max(:price)}" puts "Updated average price: #{items.avg(:price)}" Output: 1000 total items Average price: 51.9679610670265 3 most expensive items: +-------+----------------+ |name |price | +-------+----------------+ |item725|99.9570494326071| |item179|99.9012407557774| |item626|99.6490915522042| +-------+----------------+ 478 below the average Changing price for expensive items Highest price: 109.957049432607 Updated average price: 57.1879610670265As you can see, it is indeed pretty concise.
Cool features- A little IRB wrapper that lets you load a database directly, a la sequel sqlite:///test.db
- Sequel Datasets are Enumerable, but hit the database when the methods are called
- Arbitrary SQL execution:
DB 'david') my_posts = posts.filter(:stamp => (2.weeks.ago)..(1.week.ago)) my_posts = posts.filter(:category => ['ruby', 'postgres', 'linux']) my_posts = posts.filter {category == ['ruby', 'postgres', 'linux']} my_posts = posts.filter {stamp > 1.month.ago} my_posts = posts.filter(:category => /ruby/i) my_posts = posts.exclude(:category => /ruby/i) my_posts.each {|row| p row} posts.filter('(stamp < ?) AND (author ?)', 3.days.ago, author_name)That’s basically just what jumped right out at me, it looks like things go a whole lot deeper than that.
Selfish InvestigationsI first gained interest in Sequel slightly before this contest started, as I thought it might jive nicely with Ruport. Turns out, that’s definitely the case. Here’s my naive hack that actually works for the simple cases:
class Sequel::Dataset def report_table Table(:column_names => columns, :data => all) end endThis lets you use Sequel for your data acquisition and Ruport for your formatting needs.
irb(main):017:0> puts DB[:payr_employees].report_table +---------------------------------------------------------------------------------------------+ | lunch_hours | lunch_time | start_time | avg_hours | code | name | is_manager | id | +---------------------------------------------------------------------------------------------+ | 0 | 12:00 | 08:00 | 40 | gsgi | Gregory Gibson | true | 1 | | 1 | 13:00 | 11:00 | 30 | jon | Jon Juraschka | false | 2 | +---------------------------------------------------------------------------------------------+ => nil irb(main):018:0> puts DB[:payr_employees].filter(:code => "jon").report_table +--------------------------------------------------------------------------------------------+ | lunch_hours | lunch_time | start_time | avg_hours | code | name | is_manager | id | +--------------------------------------------------------------------------------------------+ | 1 | 13:00 | 11:00 | 30 | jon | Jon Juraschka | false | 2 | +--------------------------------------------------------------------------------------------+ => nil irb(main):019:0> puts DB[:payr_employees].report_table.to_csv lunch_hours,lunch_time,start_time,avg_hours,code,name,is_manager,id 0,12:00,08:00 ,40,gsgi,Gregory Gibson,true,1 1,13:00,11:00,30,jon,Jon Juraschka,false,2Since Sequel::Dataset#all just returns an array of hashes, it was trivial to use with Ruport and I suspect that this structure is common in a lot of other Ruby libraries too, which means that Sequel offers a whole lot of shoehorn capabilities.
Final ImpressionsI really like what I see here. It’s really a different feel than full blown ORM like ActiveRecord, and seems like it might be the ideal choice for quick and dirty SQL hackery. I definitely think that you could pick up the basics for this lib in 10-15 minutes and already have it be useful for you. The fact that the project is active and relatively new means there is room for growth and improvement. The API doesn’t feel incredibly polished, but for the most part, is fairly intuitive.
I definitely think it’s a project to keep an eye on, and didn’t run into many problems with it while playing around with it. Hopefully, I’ll get a chance to work with it in some real projects soon.
Sequel Community ResourcesThe docs are halfway decent and the group has some posts worth checking out in the archive, for sure.
June Spotlight Honorable MentionThough I felt like Sequel had the strongest proposal, the other ones I received were excellent. I’d like to offer an honorable mention for MenTal guY’s Omnibus Concurrency Library. The coolest thing in his proposal was Array#parallel_map which works like map, but concurrently. There was some other sick stuff in their too, so it’s worth checking out for sure.
That’s all for this month. I’ll be accepting submissions for July until the end of the month. Until then, go give Sequel some love!
From Nick Sutterer, A Computer Science Undergraduate at Albert-Ludwigs University (Freiburg, Germany)
When writing an article about Apotomo I had to make a decision: either introduce it as a simple widget plugin for rails or - as the name Apotomo (”all power to the model”) implies - end up in monologues about model-driven component-oriented enterprize concepts. Today I will simply introduce Apotomo as a widget library for rails.
IntroductionApotomo is a widget library for rails. The concept is familiar to everyone who’s already worked with a GUI library: Take a window, draw some frames in it and throw in some buttons. Attach some logic to the buttons, hook a method to the frame and you’re done.
In Apotomo (that’s a widget library), the central place - where all this drawing and attaching happens - is the modeling tree. For your convenience I prepared a meaningful model which is the foundation of an imaginary drinking application: people can track their drinks in a database, can list what they drank and can view their current blood alcohol value. Useful? Not very.
Example def drinking_model_tree top_page = page("Top Page!", 'top_page') track_page = section("Tracking Page", 'tracking_page') tracking_notebook = notebook('tracking_tabs') track_tab = tab("Track a Drink", 'track_tab') list_tab = tab("List tracked", 'list_tab') level_page = section("Permille Page", 'permille_page') top_pageHey folks, I’ve picked a winner for June’s Ruby project spotlight and will have a post out within the next few days about it, but I’d like to remind folks that this is an ongoing project.
What that means is that I’m now accepting July submissions. Every submission we got for June was excellent, and if you were not selected, you can always resubmit for a later month. Here’s a recap of the rules, but see the original post for details.
Please email me if you’ve got a cool project to submit!
To answer a question on RubyTalk the other day, I had to reference Mauricio Fernandez’s nicely compiled list of Changes in Ruby 1.9. While I was there I took another walk through the whole thing.
There are of course some features I *don’t* like.
a = ->(b,c){ b + c } a.call(1,2) # => 3But there are quite a few that I do, and here I’ve listed ten I think will totally rock. I use Mauricio’s examples, so all credit goes to him. Also, this article is from February, so if you find any features below that have changed, shout and I’ll update.
1. Enumerator is in core and more tightly integratedThis means all your enumerable objects can return Enumerators without a require, and also avoids some use of enum_for
a = 4.times a = a.each a.inject{|s,x| s+x} # => 6I had to cross my eyes a couple times to understand what was going on there, but I came to the conclusion that ultimately, that is going to rock.
2. Enumerator#with_indexI think most people will at some point be look to do a map_with_index, and this brings you quite close:
[1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2} #=> [2, 4, 3, 8, 10, 6] 3. Better Array#to_s and Hash#to_s [1,2,3,4].to_s # => "[1, 2, 3, 4]" {1,2,3,4}.to_s # => "{1=>2, 3=>4}"IIRC, puts will still do its magic when used on Arrays.
4. Method#receiver and Method#owner class A; def foo; end end a = A.new a.method(:foo).receiver # => # class A; def foo; end end a = A.new a.method(:foo).owner # => AI’m sure we’ll find something evil to do with that. :)
5. Process.daemon Process.daemon() => fixnum Process.daemon(nochdir=nil,noclose=nil) => fixnumBy default, this will detach the process and change the working dir to the root. It’ll also redirect all output to /dev/null. Sounds like this will be a *nix only feature but having built in support for daemonizing scripts should be great.
6. Blocks can take block argument define_method(:foo){|&b| b.call(bar)}Hooray, a simultaneous win for higher order procedures and metaprogramming goodness!
7. Block arguments are always local a = 1 10.times{|a| } # !> shadowing outer local variable - a a # => 1Compared to the nasty behaviour on 1.8:
a = 1 10.times { |a| } a # => 9 8. New literal Hash syntaxThis is going to be great for making fake named arguments look even prettier
{ a: 1, b: 2 }is now equivalent to:
{ :a => 1, :b => 2 }which means you could easily do something like:
foo(a: 1, b: 2) 9. Class variables not inheritedSlightly weakening the case against them…
10. BasicObjectA lot of times, you want a minimalist object. There have been plenty of hacks to show how to construct one in Ruby 1.8, but we’ll get one for free in 1.9
BasicObject.instance_methods # => ["__send__", "funcall", "__id__", "==", "send", "respond_to?", "equal?", "object_id"] Object.ancestors # => [Object, Kernel, BasicObject] Honorable MentionsEnumerable#group_by looks like it rocks. Symbol#to_proc wasn’t mentioned here but it’s handy (lets you do something.map(&:some_attr)). Also, the best damn Regex engine ever, Oniguruma, is built into Ruby 1.9
I wonder if the core team is still on schedule for a Christmas release…. Should be interesting to see how people make use of all this new stuff.
Nice introductory tutorial on Rake from the guys @ Rails Envy, which provides a brief history, introduces concepts such as tasks and namespaces and talks about its role within Rails.
It really makes me happy to see the increasing international interest in Rails and in Ruby. As I reported earlier, the official Ruby site is available in many languages (currently English, French, Japanese, Korean, Polish and Spanish), with more in the works. My original Rolling with Ruby on Rails was translated into Japanese, French, and Spanish. And now Bill Walton’s updated version of Rolling with Ruby on Rails has been translate to Brazilian Portuguese thanks to Gabriel Bogéa Perez!
Of course there is much more. If you know of Ruby and Rails resources in other native languages, please post the links in a comment and tell the rest of us about it!
Consider this fact: Multi-core CPUs are not only the future, they’re the only way CPUs can continue to grow at their current pace. It’s also a hotly debated subject in the software world. Multi-threaded programming is different and not seen as often as procedural programming, and therefore it’s not yet as well understood. So the question is, how can programming languages (and Ruby in particular) make it easier to harness these systems?
As Ruby struggles to graduate from its current implementation into something more powerful, we’ve already seen several projects attempt to update Ruby to help developers cope. Those who’ve been working with Ruby for awhile may remember YARV, which promises to provide more threading support. JRuby offers all the power of Java’s threads to Ruby, if it can harness it. And Evan Phoenix’s small but rapidly growing project Rubinius is attempting to be the next big contender.
No matter what implementation becomes the next de-facto Ruby platform, one thing is clear: People are interested in taking advantage of their newer, more powerful multi-core systems (as the recent surge in interest in Erlang in recent RailsConf and RubyConfs has shown). As Ruby becomes increasingly part of solutions that deal in high volumes of data processing, this demand can only increase.
That’s why it’s so very surprising to see David Heinemeier Hansson dismiss the whole notion out of hand regarding Rails. His argument seems to be that Rails already scales to multiple cores in the same way it scales to multiple machines, via UNIX process distribution. After all, isn’t this the very crux of “Share Nothing?”
But the math says something different, because for a single server “Share Nothing” doesn’t really exist. Even if the processes don’t share state, they share the same pool of resources (e.g., system memory, disk and system bus bandwidth). Each one can be a serious issue. Consider a deployed Rails application, where each Rails process (running mongrel) weighs in at about 200 real megabytes of RAM. If we wanted to take advantage of 8 cores, we’d be using a bare minimum of 1.6gb of memory–not to mention an even more dire situation with system bus bandwidth. With a dual-processor setup, you could easily see a machine with 16gb of RAM being resource starved.
David talks about welcoming a 64-core chip, but the truth is that Rails’s process-level concurrency can already barely accommodate today’s top of the line. Within 6 months we will see machines with 32 and possibly 64 cores in a dual-processor configuration as a top of the line, and today’s best being commonplace. What scales for many machines doesn’t scale for one.
It isn’t surprising that many Ruby libraries prefer to scale at the process level. The argument for process-level concurrency is a good one: It’s dead simple. We’re already doing it, and it’s worked fairly well up until now. It’s also simple because some of the many Ruby libraries that Rails uses don’t play nice with threads. Changing that requires a lot of work, and it’s work that wouldn’t immediately yield up any new features. It’s a lot of work for a status quo, which can be hard to invest time in.
The most important thing to remember when thinking about the future of Ruby is that just because we don’t have convenient methods for threading Ruby today, it doesn’t mean we shouldn’t explore all the possible avenues. YARV, JRuby, or Rubinius may come along any day and blow us away with completely new ways to think about working with concurrency. If Rails is ready for this, it can continue to be on the forefront of web toolkits. If it is not it will rapidly fall behind, because ignoring the problem at this stage is ignoring problems that well-funded startups have already encounterd.
Talking to some major Rails developers for 5 minutes, ideas like simultaneous request-processing (something Ezra Zygmuntowicz’s Merb already does), parallelized partial rendering, and really crazy out-there future-talking ideas like stateful HTTP, or trivial implementations of Comet-like technology were mentioned immediately. Imagine what could be accomplished with a real implementation to play with?
Keep an eye on these new Ruby implementations. The first people to really innovate technically with them will have an enormous advantage over their competitors.
We’ve got yet another project announcement to share, as Google’s Summer of Code begins to pick up steam. The following announcement is from Andreas Launila about his project Gecode/R. Andreas is pursuing a masters in Computer Science at KTH (The Royal Institute of Technology - Sweden).
Announcing Gecode/R, by Andreas LaunilaHello, I’m working on Gecode/R, a Ruby interface to Gecode, allowing constraint programming in Ruby. I’m currently trying to set some goals/direction for the syntax, and would appreciate feedback from anyone that has the interest and time. The interface is intended for people with no previous experience of constraint programming, and should be fairly easy for an average ruby programmer (i.e. someone familiar with Ruby) to pick up and use.
I begin with a short introduction and example, I will then highlight some problems and things to consider. I have formulated some actual questions in the end of some paragraph, but those are mostly there as aid for the reader. Feel free to disregard them and comment on any thing that you think of, any sort of feedback is welcome.
Introduction and exampleUsing constraint programming basically means that you model the problem by specifying the constraints that have to hold for something to be a solution to the problem. The underlying engine then solves the problem by searching for solutions satisfying all the constraints. Lets take
Ruby Quiz #124 (Magic Squares) as an example. For something to be a solution to that problem we require that the elements are distinct, between 1 and n^2, and that the sum of the rows, columns and two main diagonals are equal. So we model that with some code and then let the underlying engine do a search.
The following is an example of how solving the problem might look:
# A naive model of the magic square problem with square size n. class MagicSquare < Gecode::Model # n is the size of the magic square. def initialize(n) # This models that all elements are in 1..(n**2) and are all # distinct. IntVar.matrix produces an instance of Matrix filled # with instances of IntVar. squares = IntVar.matrix(n, n, 1..(n**2)) all_distinct squares # The following models the part about various sums being equal. We # know the magic sum (row sum) since we know the sum of all # elements. magic_sum = n*(1 + n**2) / 2 n.times do |i| squares.row(i).to_a.sum == magic_sum squares.column(i).to_a.sum == magic_sum end squares.diagonal(0, 0).sum == magic_sum squares.diagonal(0, squares.column_size - 1).sum == magic_sum # We need to select a branching strategy, this is used when we can # no longer prune impossible values by deduction and have to make a # guess. branch_on squares, :variable => :min_size, :value => :min end end # A couple of utility methods for Array and Matrix to make the above a # bit more readable. class Array # Folds the elements in the array using the + method. def sum inject{ |sum, element| sum + element } end end class Matrix # Returns an array containing the element found in the diagonal which # contains the element in the specified row and column and contains no # element with a strictly smaller row-index. nil is returned if such a # diagonal does not exist. def diagonal(row, column) # Out of bounds. return nil unless row < row_size and column < column_size # Does not exist. return nil if row > 0 and column != 0 and column != column_size - 1 diag_length = [row_size - row, column_size - column].min elements = [] diag_length.times do |offset| elementsHere’s an open question that I’m hoping will get some interesting discussion going:
Why are there so few Ruby jobs out there?
Those of you who have been to RubyConf in recent years have been asked the question “How many people get paid to write Ruby?”, and you’d see that in the last year or two, the number of people who raise their hand has absolutely skyrocketed.
However, at Gotham Ruby Conference, someone asked “How many people are paid to write plain Ruby, no Rails”. I think like 3 hands went up, and I was one of them, out of 120 or so people. It’s possible that we just have a lot of Rails work in NYC, but I think the issue might be deeper than that.
I’ve seen so many of my friends say “Oh, I’d rather be writing pure Ruby, but at least working in Rails gets me close to that, and it’s better than working in ”.
Is Ruby really only viable for database driven web applications? I doubt it. I think it can stand its ground anywhere Perl or Python could. So why is it that most job postings you see are for some sort of “Web Rockstar”, and not like, a sysadmin with scripting experience, or an internal applications developer?
I guess it could be a lot of things, any of the below or a combination might be to blame:
I don’t really know what it is. I understand when corporate politics get involved, all things go out the window, but I think that Ruby’s success as a commercially viable language outside of Rails is less than what it should be at this point. Do other people feel the same way?
UPDATE: Buried deep within the mixed replies to this post is a great writeup by Andy Peters which states more assertively what I was implying here…