Learning Rails (and Ruby)
I know PHP. I mean, I really know PHP. Not just the syntax, or the idioms and idiosyncrasies, but why. I can tell you why something works the way it does, under the hood; and I was probably around when the decision was made to do it that way. Thirteen years with any language is a long time.
But it hasn’t always been PHP. Two years into my PHP journey, I took a small detour and taught myself ColdFusion, which had just transitioned to running on top of the Java EE platform. Which also meant that I dug into Java because you could extend ColdFusion with Java components.
And then of course there was the inevitable delving into JavaScript, add in a healthy dose of CSS, semantic web technologies (RDF, OWL, and SPARQL), XML, XPath, and XSL (XSL:FO and XSLT) and lets not forget SQL. Heck, I can write (and have written) DTDs!
More recently, after starting to work for Engine Yard on the Orchestra PHP Platform, I learned Python. (Yes, we use Python for parts of our PHP stack. Why? It’s the best tool for the job.)
I didn’t list most of the keywords on my resumé to make myself sound fancy, I did it because I think it’s fair to say at this point, I qualify as a Polyglot.
I have always explored new tools (be they daemons, utilities, libraries, languages or services) and judged them on a few criteria:
- How well is the tool written?
- What is its security track record?
- How many open bugs does it have?
- How has the community responded to previous issues (are they open, friendly, courteous, prompt)?
- Does it have enough features for what I need?
- Does it have too many features for what I need?
Ultimately, it comes down to: Is it the right tool for the task?
Because of this, ultimately when I come to write a web site, PHP is my tool of choice. Know thy tool well, and it shall treat you well.
Then along came Engine Yard, and I was exposed to just a ton of fantastic engineers who happen to choose Ruby as their tool of choice.
Even still, more than a year after working for Engine Yard, I had yet to pick up Ruby, or Rails. Sure, I’ve read a lot of Ruby code, for code reviews and out of interest for how something has been done. I’ve even hacked a little on some Rails stuff, but it was mostly copy-and-paste-and-hum-a-few-bars.
Then along came Distill, and we needed a site. With about 3 weeks to work on it, while still working on other tasks, and with no requirements for technology choice, I would normally have just picked up PHP, and probably Zend Framework 2 and knocked it out in a few days.
Instead, given that I’ve been discussing Distill, and what we want to achieve (a focus on solutions, not technologies) for months, I decided to get into the spirit with what looked like an opportunity to try out Rails (and Ruby). This was a small project, with limited feature set and scope that I could quickly fall back to PHP if I ran into too many issues. Luckily, surrounded by literally dozens of fantastic experienced developers, I had a lot of folks I could ask questions of — but as you’ll see, I didn’t really need much help.
Implementation Details
This blog post is not meant to focus on how I learned Ruby or Rails, but what I learned from the experience. However, I did want to cover some of this too.
Coming from PHP, I definitely encountered some WTFs:
- Parentheses are optional on method calls, and often not used: but in some cases [such as nested calls] you need them.
- There are lots of ways to do “if not”. These include: if
!<condition>
,if not <condition>
andunless <condition>
. - Method names can contain ? and !, and there is a convention of ending method names that return boolean with
?
.It’s not a operator, it’s part of the name, e.g.foo.empty?
. Methods ending in!
usually indicate they modify the object they are called upon e.g.foo.downcase!
modifies foo, whilefoo.downcase
returns the result, for this reason they are known as destructive methods. - Returns can be implicit, a method returns the result of the last statement run (and most code I’ve seen does this)
Then you get something like this (actual code at some point for the Distill website. It may have changed by the time it goes live):
class Speaker < ActiveRecord::Base
belongs_to :user
has_many :proposals
attr_accessible :user, :bio, :email, :name, :id, :website, :photo
has_attached_file :photo, :styles => { :medium => "300x300>", :thumb => "120x120>" }
validates_attachment_content_type :photo, :content_type => /^image\/(png|gif|jpeg)/
validates :bio, :email, :name, :photo, :presence => true
validates :email, :format => {
:with => /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/,
:message => "Must be a valid email address"
}
end
Lets break this down:
- Line 1: We define a class,
Speaker
that extends (<
) theBase
class in (::
) theActiveRecord
module. - Lines 2, 3, 5, 7, 8, 9, 10: are all method calls
- Line 15: we close the class definition (
end
)
“But wait, you said method calls?” Indeed I did! “That’s crazy talk! We’re still in the class definition!”
First, it’s important to note that Ruby has an implicit self
, which is like self::
in PHP, it calls methods statically (that’s the easiest equivalency). This means that belongs_to :user
can also be expressed as self.belongs_to :user
. What’s weird here, is that these (which are inherited) are being called during definition of the class. These methods can be defined (e.g. def self.foo
) and called (after definition) within the definition of that same class, or inherited from it’s parent. These methods modify the class object itself.
Aside: while writing this blog post, I actually fully realized what the previous section means, and I had a tweet exchange with fellow Engine Yarder @mkb which helped solidify what’s going on which you can see here — to summarize: classes are defined by executing code, this means you can programmatically define classes, and that you can even work with it during definition.
So what I thought was a property (validates
), that was somehow magically defined twice, is actually a method call - there’s that lack of parenthesis on method calls that I mentioned earlier.
So what happened?
Well, I built a website. A secure, readable (code), usable website. Nothing more than I could have built in PHP, but there were several things that I did that sort of blew my mind.
For the majority of this app, we’re talking simple CRUD. Show a form, take the input, store the input, and display it later. There was no complex data structures that I had to worry about or anything. Ruby/Rails or PHP/Zend Framework 2; didn’t really matter. I could’ve written this in bash script probably.
I read through most of the Getting Started with Rails, adapting it to my needs as a I went along.
The two parts I thought would be the most challenging:
- OAuth with multiple backends (Github, Facebook, and Twitter)
- Storing uploaded images on S3.
OAuth with Multiple Backends
To solve this challenge, I use the time-honored practice of Googling. In doing so, I stumbled across Devise and Omniauth; two gems that implement user authentication and OAuth, respectively.
Integrating these gems for someone who barely knows Ruby or Rails was actually quite tricky, but I got it done and was quite amazed! It didn’t just handle the OAuth, it handled routes, views, forms, database schema - pretty much everything. I did have to write custom handlers to deal with pushing the user into the database and handling the data sent back by the service (e.g. name/email), but nothing too difficult.
S3 Image Uploads
Again, I solved this with Google, and came up with the Paperclip gem. A file “attachment” extension to ActiveRecord.
Through paperclip, I went from not knowing how to implement file uploads in Ruby/Rails, to handling it, storing the details in the database, creating multiple thumbnails, and pushing it to S3 within minutes. Essentially, after config (which is just specifying the storage adapter of S3, the credentials, and path), and a rake call, these few lines handled everything:
has_attached_file :photo, :styles => { :medium => "300x300>", :thumb => "120x120>" }
validates_attachment_content_type :photo, :content_type => /^image\/(png|gif|jpeg)/
This names the attachment as “photo”, specifies the versions we want (300x300 and 120x120) and validates that they uploaded a png
, gif
or jpeg
.
What did I get out of this?
Well, I still have a ton to learn. Ruby isn’t just a different syntax, which was (mostly) my experience with Python. That being said, I feel I can now intelligently talk about some of the benefits of Rails compared to it’s PHP counterparts.
One of the most significant advantages is the library of amazing gems that work out of the box with Rails and can bring a lot more than more generic PHP libraries, both due to the widespread usage of Rails in the Ruby community and the fact that folks tend to stick to it’s standard tooling. For example, there is an OAuth component for Zend Framework 2, but it doesn’t presume that you’re using ZF2 as your controller/router and setup the routes, nor does it generate views, or hook into a specific auth mechanism or database adapter. This, I believe, it what makes Rails (and therefore Ruby) a great tool for rapid development. (If you’re hunting for gems, The Ruby Toolbox lists gems by what they do, and their popularity, which is quite handy!) I also think we need to separate the framework from the language. Just like PHP, or Python, Ruby is a general purpose language. PHP however was built from the ground up with a primary focus on running in a web environment. But that really doesn’t mean much more than it just provides easy access to the web environment (GET/POST/Cookies, built in session handling, PUT/POST raw data, server environment, etc). What this means to me is that I could have built the Distill site in any of these three.
One major factor PHP has going for it for the web, is that it’s shared-nothing architecture is great for horizontal scaling, and it seems better at handling concurrency out of the box — though Ruby is making great strides in this area (with projects like rubinius, and jruby). That isn’t to say Ruby or Python can’t, or don’t scale, it’s just that it’s much further along in the learning curve because there’s more to getting it right. Of course, working with (and deploying on) the Engine Yard Cloud means this was a non-issue for me. So, the language doesn’t matter all that much, but what about the framework? Could I have built the same website using Zend Framework 2? Yes. Would it have been easier? In some aspects, specifically the fact I don’t know Ruby or Rails that well, sure. However_ I don’t think I could have built out the OAuth and S3 storage as covered here as quickly and easily given all other factors were equal_. Now, going back to Zend Framework 2, I find I’m doing a lot more busy-work, such as generating forms, scaffolding, schema updates, etc, at least as a starting point.
Does this mean I’m switching to Ruby/Rails? Unlikely. PHP is still the preferred ink in my pen, simply because knowing it so well means it’s an effortless tool to transform my ideas into reality.
Will I turn to Ruby/Rails again? Maybe not for my own projects — I tend to work with friends from the PHP community. But when I’m working with my excellent teammates at Engine Yard, absolutely — for me, the strongest thing Ruby/Rails has going for it, is the community knowledge I have access to. No matter the answer to this question however, learning a new language — and more importantly, learning best practices with that language — hopefully makes me a better developer. It is very easy to latch on to a language, and a community, and think we are learning, because we’re looking at periphery technologies like database servers, cache storage and web services… and we are learning, but it’s through a single point of view (“The PHP Way” or “The Ruby Way”); a set of blinders that are based on everything we are comfortable with.
Getting out of your comfort zone — learning a new language — the core part of what brings all of our technologies together, capturing, and discussing ideas with other communities is where you’ll really grow your ability to solve problems: with the best solutions, and the best tools. So get out to meetups, or conferences, listen to podcasts, and read articles on other languages, even if you’re not interested in using that language.
I’m excited to have finally put enough time into learning enough about this great tool to work on some amazing technology with my fellow Engine Yarders, and to be able to apply the general concepts I’ve learned throughout my journey into another medium with more people. Distill is a conference to explore the development of inspired applications. The call for papers is now open and tickets go on sale in April.
Share your thoughts with @engineyard on Twitter