Clone Pastie with Sinatra & DataMapper 0.9
Waaaay back in November I wrote a DataMapper and Sinatra tutorial. DM was still pretty immature at the time, and it quickly became outdated. I promised I'd update it once 0.9 was released.
Better late than never, right? Anyway, without further ado, here's Toopaste Tutorial 2.0:
What We're Building
Today we're going to learn how to write a dirt simple pastie clone using Sinatra and Datamapper. For those of you unfamiliar with pastie systems, they're commonly used in IRC to paste bits of code to a globally-visible place outside of the channel. So you copy some code from your IDE, paste it into a web page, submit it, and the Pastie system fancies it up with some syntax highlighting or whatnot. You can then copy the resultant URL and paste it back into the channel so other people can view it.
Examples of this type of service include pastie.caboo.se and Nopaste @ rafb.net. Those sites do a lot of extra cool stuff, like allow you to select the language for different syntax highlighting rules, select themes for viewing, and so on. But the core concept itself isn't a terribly complex one, and our example is going to be about as barebones as they come.
The interesting thing isn't the application we're going to build here so much as it is the tools we'll use. That is, we'll be using the pastie example to introduce you to two cool new pieces of Ruby tech: Sinatra, a Ruby web micro-framework, and the DataMapper ORM package.
Sinatra
Sinatra, on the surface, is a lot like Camping, another Ruby web all-in-one-file micro-framework. Camping hasn't seen much active development lately though, and the syntax can be a bit strange for new users. Sinatra is much more straight-forward and accessible. It's really a kind of domain-specific language for writing simple web applications, used to define RESTful actions and how they should be handled. This makes it perfect for lightweight mini-apps. It's also ORM-agnostic, instead of being married to ActiveRecord like both Rails and Camping are.
Sinatra has been making cameo appearances supporting a number of high-profile Ruby-based web apps, including Heroku and GitHub. It runs on top of Rack, which means that it plays nice with a wide variety of Ruby web app servers, including Mongrel, Thin, and Ebb. It also means that you can host your Sinatra applications easily with Apache and Phusion Passenger, as of Passenger 2.0. For more information, check out the 'official' Sinatra tutorial (oops dead link). There's also an open source Sinatra book in the works.
DataMapper
DataMapper is the new ORM package on the block, and an alternative to ActiveRecord (and Og and Sequel and so on). It just moved into town but it's already sitting at the cool kid lunch table. Whereas AR implements the ActiveRecord Pattern, DataMapper (surprise!) implements the DataMapper pattern.
There are a number of reasons why I prefer this approach, but that's fodder for an entirely separate blog post, so I won't get into it here. Besides, the team has already written a great summary of why DataMapper rocks. Read it. It's good stuff. Oh, and performance kicks ass too.
Take that, non-believers!
Prerequisites
OK, anyway. Tutorial time. Found your plastic hat? Good. Let's go. First let's get the gems we'll need. As of this writing, DM is at v0.9.2 and Sinatra is at v0.2.2. We're also going to retrieve the Syntaxi gem, which we'll use for syntax highlighting.
sudo gem install sinatra data_mapper syntaxi
The data_mapper gem is a "meta gem" that includes the most commonly used gems from dm-core, dm-more, and others. We'll be using a couple of these additional libraries -- dm-timestamps and dm-validations -- in this tutorial.
Since DM uses the DataObjects.rb (DO) drivers, you'll want to install them too. For this tutorial we'll be using Sqlite3 as a data store. If you want to use MySQL or Postgres or any other database supported by DO, well that's cool too (just make sure to get do_mysql or do_postgres).
sudo gem install data_objects do_sqlite3
The Code
Now that our prerequisites are satisfied, let's get started by creating the file toopaste.rb:
require 'sinatra'
require 'dm-core'
require 'dm-validations'
require 'dm-timestamps'
require 'syntaxi'
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/toopaste.sqlite3")
class Snippet
include DataMapper::Resource
property :id, Integer, :serial => true # primary serial key
property :body, Text, :nullable => false # cannot be null
property :created_at, DateTime
property :updated_at, DateTime
# validates_present :body
# validates_length :body, :minimum => 1
Syntaxi.line_number_method = 'floating'
Syntaxi.wrap_at_column = 80
def formatted_body
replacer = Time.now.strftime('[code-%d]')
html = Syntaxi.new("[code lang='ruby']#{self.body.gsub('[/code]', replacer)}[/code]").process
"<div class=\"syntax syntax_ruby\">#{html.gsub(replacer, '[/code]')}</div>"
end
end
DataMapper.auto_upgrade!
# new
get '/' do
erb :new
end
# create
post '/' do
@snippet = Snippet.new(:body => params[:snippet_body])
if @snippet.save
redirect "/#{@snippet.id}"
else
redirect '/'
end
end
# show
get '/:id' do
@snippet = Snippet.get(params[:id])
if @snippet
erb :show
else
redirect '/'
end
end
Next we'll dissect this code listing to give you a brief look at how DataMapper and Sinatra work. We'll show the code listings for our views as we get to them.
Code Analysis: DataMapper
The first couple lines require the libraries we'll be using in this example. dm-core represents the DataMapper core libraries, and dm-validations and dm-timestamps both add extra bits of non-core functionality to DataMapper. In this case, requiring timestamps means that fields such as created_at and updated_at automatically get branded with the current Date/Time when a model is created or updated. As for validations, well we'll see them in use shortly.
Before we create out first DataMapper-backed model we have to set up the database. The following line sets the default database connection and tells it to use the Sqlite3 file-based database in the current working directory. It won't exist yet but don't worry, we'll be creating it automatically when we migrate.
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/toopaste.sqlite3")
Persistent Models and Properties
Next we define our model, Snippet. Since we want to persist snippets, we include the DataMapper::Resource module in the class definition. From an end user point of view this is the same as inheriting from ActiveRecord::Base in Active Record. It's a nice bonus that we don't have to use inheritance to accomplish this any more.
include DataMapper::Resource
The property declarations in our model are DataMapper's way of specifying attributes, which translate to database table columns. This might look a little bit odd at first if you're coming over from Active Record...
"OMG I don't have to put comments in my source file to remind me what attributes are available on my models?!"
It's like a little slice of heaven, isn't it?
property :id, Integer, :serial => true # primary serial key
property :body, Text, :nullable => false # cannot be null
property :created_at, DateTime
property :updated_at, DateTime
The only two required parameters are the name of the property and the type, but you can also add other options to mark a property as the primary serial key, prevent it from being null, and so on. Properties get public accessors by default, but you can modify that by setting the :accessor option to :private or :protected (:reader and :writer options are also supported for even more control).
Another interesting DM optimization worth noting: by default, the text field (body) is lazily loaded. Text columns are expensive in databases, and by using lazy loading, we only access them when they're needed. This speeds things up significantly in most cases. However, if you don't want to do this for whatever reason, you can just pass a :lazy => false option in the text column property declaration.
Validation
The validators we're specifying should look relatively familiar to anyone familiar with AR. They're only available to us because we required the dm-validations library, as mentioned. This is part of DM's Merb-like minimalist approach; start with just the bare essentials, and allow developers to mix in extra functionality as its desired. The kitchen sink is so fail, right?
# validates_present :body
# validates_length :body, :minimum => 1
So why are these lines commented out then, you ask?
Well, we could explicitly add the validates_present :body to specify that the body property must be present, but that's already handled for us in the property declaration since we specified :nullable => false. If dm-validations is not required, the nullable option will simply ensure that the database column isn't null, but if validations are in use it will augment this behavior to assume that we not only mean 'not null', but also 'not empty'.
Likewise, validates_length could be useful in certain situations, but in our case we just want to make sure that the paste body isn't empty, which is already handled for us by the nullable option on the body property.
There are lots of other validations that we can add in this manner, of course. For more information see the DataMapper API documentation.
Syntax Highlighting with Syntaxi
The only custom method we've added to our model, formatted_body, is pretty straightforward. It just takes the body of our snippet (a property on the model) and wraps it in some code that gives us pretty syntax highlighting and line numbering. Syntaxi leverages Jamis Buck's Syntax gem to mark up the specified code, wrapping CSS span tags around things that should be colored, adding line numbers, and some other goodies too -- you'll see the CSS in our layout shortly.
def formatted_body
replacer = Time.now.strftime('[code-%d]')
html = Syntaxi.new("[code lang='ruby']#{self.body.gsub('[/code]', replacer)}[/code]").process
"<div class=\"syntax syntax_ruby\">#{html.gsub(replacer, '[/code]')}</div>"
end
The replacer stuff just keeps us from stumbling on syntax coloring markup in our output HTML.
Auto-Migrations
Now that we're done defining our database-backed model, we're just about ready to move on to the web-serving portion of the tutorial. But first, we need to tell DataMapper to create the appropriate database table if it doesn't already exist. We do this by using the DataMapper.auto_upgrade! method, which checks to see if the database table that corresponds to our model(s) need upgrading and then creates or updates them as appropriate. It's a non-destructive way to do auto-migrations (the destructive equivalent is auto_migrate!)
Code Analysis: Sinatra
Moving on to the Sinatra portion of our tutorial, you'll see that routes and actions are intimately married in Sinatra. Although this may not be desirable for a larger application, it's great for smaller, simpler web apps like this pastie project. In this case we're talking uber-simple; we only have three actions and they're only ever available at these three URL patterns.
The Index Action / New Snippet Form
The first of our actions needs to display a form to create a new paste. It should always be available at '/', the application root.
get '/' do
erb :new
end
This tells Sinatra that when a GET request for '/' comes in, that we should use the erb helper to render the new.erb template, which is stored in the views/ subdirectory by convention and marked up with embedded Ruby (ERb). We can render our responses inline as well, which works for dirt-simple applications, but in most cases you'll prefer to keep the view templates outside of this file.
In any case, the new.erb template is shown below:
<div class="snippet">
<form action="/" method="POST">
<textarea name="snippet_body" id="snippet_body" rows="20"></textarea>
<br/><input type="submit" value="Save"/>
</form>
</div>
The Create Action
Our next action is analogous to a #create action in RESTful Rails. The action is requested by a POST to the application root. Sinatra supports the standard GET and POST HTTP methods as well as PUT and DELETE, meaning that you can build 100% RESTful applications with it.
post '/' do
@snippet = Snippet.new(:body => params[:snippet_body])
if @snippet.save
redirect "/#{@snippet.id}"
else
redirect '/'
end
end
Pretty standard stuff, right? We retrieve the parameter passed to us from the new paste form, instantiate a new model and try to save it. If the validations pass, we redirect to the #show action equivalent. If not, we're just going to dump you back to the #new form again. Since the only way the action will fail is if the body property is empty, we're not going to bother with any sort of error message at this time. We're not rendering anything here (merely redirecting), so no template is required.
The Show Action / Show Me The Snippets!
If our post is successful, we're going to be taken to the #show action, which lives at /:id, where :id is the primary key of the corresponding database record. This action will also get accessed directly when you paste that URL to the chat room, and people click to view your code.
get '/:id' do
@snippet = Snippet.get(params[:id])
if @snippet
erb :show
else
redirect '/'
end
end
In the code listing above, we look up the particular snippet specified in params[:id] and set an instance variable by calling DataMapper's get method (equivalent to Active Record's find and one of many ways to do record lookups with DataMapper). If the @snippet is not found we'll redirect back to the new snippet form. Otherwise we render an ERb template, which of course has access to that instance variable. Here's the code you'll want in /views/show.erb:
<div class="snippet">
<div class="sbody" id="content"><%= @snippet.formatted_body %></div>
<div class="sdate">Created on <%= @snippet.created_at.strftime("%B %d, %Y at %I:%M %p") %></div>
<br/><a href="/">New Paste!</a>
</div>
The Layout
We're pretty much done at this point. However, to dress up our output we'll use an ERb layout template with some CSS to handle the syntax highlighting that Syntaxi provides for us. If it exists, Sinatra will render a special view template (just like Rails does) named layout.erb in the views/ subdirectory. This layout will be used to wrap the output of the other views rendered when the erb helper method is called.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><%= @title || 'Toopaste!' %></title>
<style>
html {
background-color: #eee;
}
.snippet {
margin: 5px;
}
.snippet textarea, .snippet .sbody {
border: 5px dotted #eee;
padding: 5px;
width: 700px;
color: #fff;
background-color: #333;
}
.snippet textarea {
padding: 20px;
}
.snippet input, .snippet .sdate {
margin-top: 5px;
}
/* Syntax highlighting */
#content .syntax_ruby .normal {}
#content .syntax_ruby .comment { color: #CCC; font-style: italic; border: none; margin: none; }
#content .syntax_ruby .keyword { color: #C60; font-weight: bold; }
#content .syntax_ruby .method { color: #9FF; }
#content .syntax_ruby .class { color: #074; }
#content .syntax_ruby .module { color: #050; }
#content .syntax_ruby .punct { color: #0D0; font-weight: bold; }
#content .syntax_ruby .symbol { color: #099; }
#content .syntax_ruby .string { color: #C03; }
#content .syntax_ruby .char { color: #F07; }
#content .syntax_ruby .ident { color: #0D0; }
#content .syntax_ruby .constant { color: #07F; }
#content .syntax_ruby .regex { color: #B66; }
#content .syntax_ruby .number { color: #FF0; }
#content .syntax_ruby .attribute { color: #7BB; }
#content .syntax_ruby .global { color: #7FB; }
#content .syntax_ruby .expr { color: #909; }
#content .syntax_ruby .escape { color: #277; }
#content .syntax {
background-color: #333;
padding: 2px;
margin: 5px;
margin-left: 1em;
margin-bottom: 1em;
}
#content .syntax .line_number {
text-align: right;
font-family: monospace;
padding-right: 1em;
color: #999;
}
</style>
</head>
<body>
<%= yield %>
</body>
</html>
Hallo Pastie!
You can fire up your new Sinatra and DataMapper-powered application by issuing the following command:
ruby toopaste.rb
== Sinatra has taken the stage on port 4567!
Sinatra sits on top of Mongrel by default, making it super-easy to use (and thread-safe to boot!). If you open up a web browser and point it at http://localhost:4567 you'll see the results. Copy and paste away. You now have a fully functional (albeit slightly retarded) pastie clone with syntax highlighting for Ruby code snippets. And the core logic is all contained in a single file, with a few external ERb templates for cleanliness.
Wrapping Up
As we mentioned earlier, Sinatra is based on top of Rack which means you can deploy it in a number of ways. It's incredibly useful for lots of quick tasks and is definitely my "micro framework" of choice at the moment. DataMapper is well, just awesome. What else can I say? Lots of Merb developers already know this, of course.
OK enough blathering for now. Longest blog post. Ever. If you like, you can play with the finished app or check out the sources on GitHub. Enjoy, and please comment if you have any problems or suggestions for improvements. This code was written and tested on OS X 10.5 and Debian Etch. Special thanks to Jonathan Stott for the early review and fact checking.
Radiant Scoped Multi Update
Blog updates have been pretty infrequent lately. As have my OSS contributions. Sorry! I haven't had an awful lot of spare. Between client work and wrapping up my book (more on that soon), things have been a bit... insane.
However, I did find some time over the weekend to finally update the scoped multi-site Radiant extension fork to work with 0.6.7 and latest edge.
Note that the old URL is no longer accessible, as the Radiant Github presence was recently refactored to remove extensions from the core project repo.
Travel Plans
I’ve got my itinerary planned out for Railsconf. I’ll also be hitting up a couple other west coast cities before landing in Portland. First stop is SF to visit my friend Ankit on Friday. Then trekking up to Eugene on the Amtrak Coast Starlite (I love me my trains) late night Sunday to hang and brainstorm with our designer/big-thinker Ty for a few days. Then it’s off to Portland on Thursday.
If you’re in any one of those cities and want to grab a beer (or a smoothie), hit me up on Twitter. Hopefully I’ll see some of you at Railsconf.
Also, I’ll be giving a talk at NHRuby tonight (5/20) on Rack. If you’re in the area, stop by.
Mod_Rails Revealed
The hardworking hackers over at Phusion finally unveiled Passenger (mod_rails) earlier today. I just moved one of our staging servers over to it and will be playing around with it a bit more over the weekend. So far I'm happy to report that, as advertised, it's dead easy to use... and the performance seems solid.
I'd write up a tutorial but honestly it's so simple that it's completely unnecessary (imagine that!). Check out the updated screencast for all you need to know to get up and running and make sure to dig into the provided docs if you need more. They're very thorough.
In summary, it's great to see more Rails deployment options emerging, and it'll be interesting to see viable shared hosting options for the low-end market too. Although I'm a strong believer in the app server + frontend web server pairing, there are a whole class of applications for which it just seems like overkill.
A hearty 'nice work' to everyone involved! Now where's my mod_rack? :-)
UPDATED 4/12: this blog is now running on mod_rails and Mephisto 0.8!
2008 Rails Rumble Rumors
Just to clear up any possible confusion on that matter: Yes. There will be a 2008 Rails Rumble. And no. It will not be in May. Probably September or October.
We have a lot of great ideas (thanks to everyone that participated and commented) that will improve on last year’s experience dramatically and the whole team is looking forward to it, in a big way. More information coming soon, I promise, as well as an announcement, new blog, and specific details prior to Railsconf.
Oh, and speaking of Railsconf… Josh Owens, of TastyPlanner fame (last year’s grand prize winner), is giving what looks to be a fun talk about their experience building a kickass app in a 48 hour timeframe. If you’re going to be there, you ought to check it out. Especially if you’re interested in participating this year. If you haven’t yet registered for the conference, you’re running out of time, so get to it.
See you there!
Radiant Super Multi Go!
Here at Ubikorp we've used Radiant as the basis for a number of client projects who needed core CMS-like functionality. As anyone who's played with it knows, Radiant is very barebones; this is intentional and a welcome change from most packages, which attempt to throw the kitchen sink of clutter at each and every problem. Fortunately, it's easy to build on top of the basics by using a well thought-out extensions system -- there are a large number of stock extensions available (and it's also really easy to roll your own for custom app logic).
One of the more interesting extensions is the multi-site extension. To be honest, multi-site capability is something I really feel should be a core feature, but since it's not, this approach works surprisingly well (at least until you start adding other extensions that would ideally be multi-site-aware). Radiant school headmaster Sean Cribbs wrote an initial version of the extension back in November, but it didn't quite work for us on a particular project -- we needed to be able to scope individual user-level access to particular sites. So we extended it, and thus the scoped multi-site extension was born.
Well, actually, it's just a fork of the multi-site plugin. For now.
Features:
- scopes user level access to individual sites (admins and developers still have access to all sites)
- regular users can't see, edit, or access other user sites that they don't own
- optionally scopes layouts to sites
- snippets usable everywhere, but display and editing of snippets limited to admins/developers
Anyway, if you're interested in seeing our changes you can check it out on github in my fork of Sean's Radiant repo. Is this a candidate for integration into the existing extension, or should we spin it out as a separate extension? Let me know what you think.
UPDATED 6/24: The URL is out of date. See this blog post for more details and updates (the latest version requires Radiant 0.6.7 or later).
DDJ Code Talk Launches
The legendary Dr Dobb's Journal just announced the launch of their new blog and forum system, DDJ Code Talk. I was extremely honored when Jon asked me to contribute, as DDJ has long been one of my favorite industry pubs. Them peoples is hardcore.
In the coming months I'll be contributing articles about Ruby, web application development, and lightweight languages. Head on over to check out my first article for them, a quick survey of Alternative Ruby Web Frameworks. There are a few other Ruby geeks hiding in their trees too, and a broad range of topics, including Python, Java, .NET, D (!), databases and web service architecture and design. Thanks for reading!
Git Ur Radiant Extensions
I published a new Radiant extension yesterday: Database Form. It provides a new page type and tags for constructing contact and request info forms and will save user responses to a database table. Those responses can then be exported for use in another application (CRM, etc). See the README for usage and examples. It was extracted from some client work that we'll be deploying soon.
If this sounds good, you can download or clone it from GitHub. While you're there, spend a few minutes poking around; GitHub is pretty dang cool. They've definitely succeeded in making Git repository hosting stupid simple. Just click a button to create a project, follow a few commands on your local system to import your sources, and you're off and running. You can then view the repo history, browse the source, see diffs, download a tarball (for easy extension installation in your pre-existing Radiant project), and fork it if you want to add your own features. That's where things get sweet, of course: Fork the project, make some changes, and send me a pull request so we can merge them into the master branch. All this is possible without GitHub, but it sure does a swell job of streamlining things and abstracting the suck away.
It also exposes the links between developers and their project contributions in a pretty cool way. See the DataMapper project's "network" page to see what I'm talking about. Ryan Tomayko has pointed out that this sort of interaction starts to smell an awful lot like a MySpace for developers, where lines in the social graph are drawn based on OSS project work. Wow, that's a cool thought, ain't it?
Principle of Least Surprise
One of the things I love about Ruby is that it tends to follow the principle of least surprise; things just work the way you would expect them to, with precious few exceptions (cough cough inject cough). Horray for intuitiveness.
On the other hand, I've become so spoiled by The Principle that willful violations stand out like the sober guy at the all-inclusive beach resort. I know I'm being a nitpicky ass here, but this Rails bug^H^H^H ticket makes me kinda ill. Funny, because in my previous life as a Java/PHP developer, I wouldn't have even batted an eyelash at it.
Plugins Are Unnecessary
Plugins really are unnecessary -- Jay Fields is absolutely right. RubyGems is a great package management system and there's no reason it can't do double duty here, if we just impose a few extra restrictions on Gem/Plugin structure. There are other benefits too using Gems too, such as versioning and dependency management, which is somewhat painful in the world of Rails plugins.
Merb already uses Gems for plugins/extensions. Why doesn't Rails? Historic reasons, most likely. Rails itself predates the existence of Gems iirc. But seriously, how hard would it be to rewrite script/plugin to install a gem and unpack it into vendor? That's the first step.
ActiveRecord: Importing YAML
Backing up ActiveRecord models to YAML is easy to do, and can be convenient for creating 'templates' that can later be imported to restore basic project structure in a new database. However, the import process can be a little tricky, as the instantiated objects don't seem to respond correctly to new_record?
Here's what our YAML might look like. Note that it's a serialized CustomWidget, which is an ActiveRecord model that inherits from ActiveRecord::Base.
---
- !ruby/object:CustomWidget
attributes:
name: DoSomething
updated_at: 2008-01-28 05:02:54
description: Does something.
created_at: 2008-01-28 05:02:54
attributes_cache: {}
We can automatically instantiate the appropriate Ruby objects from this using the following code:
class CustomWidget < ActiveRecord::Base
end
YAML::add_private_type("CustomWidget") do |type, value|
CustomWidget.new(value)
end
obj = YAML::load(File.open("template.yml"))[0]
=> #<CustomWidget name: "DoSomething", created_at: "2008-01-28 05:02:54", updated_at: "2008-01-28 05:02:54", description: "Does something.">
obj.save
=> true
So what we've done is taken YAML from one project and tried to use it to import the same data into another database (note that IDs are stripped). We define a transfer type for the YAML, providing a mapping to the class, and then we call YAML::load to load it up. This returns an array, in this case it contains a single CustomWidget object. Everything looks swell. We call save on the new object and it returns true.
However, if we check the count of CustomWidgets that exist in the database, we'll find that it's unaltered; no new record has been saved. It turns out that this is because the imported CustomWidget doesn't report true when new_record? is called.
obj.new_record?
=> nil
In order to save itself to the database and get a new ID, the CustomWidget instance has to first respond correctly to inquiries as to whether or not it's new. We'd like new_record? to return false. The quick-fix secret is to manually toggle the @new_record instance variable. If we do that, calling save will return true and actually save the object to the database as well this time. Big yay, right?
CustomWidget.count
=> 0
obj.instance_variable_set("@new_record", true)
obj.new_record?
=> true
obj.save
=> true
CustomWidget.count
=> 1
Yes, this is an ugly hack. Did I say ugly? I meant gross.
Slacking
Like my pal bryanl over at smartic.us, I too want to apologize for being a bad blogger lately. I've just been stupid busy with both personal stuff and lotsa client work, which of course is both a blessing and a curse.
In any case, my head is almost back above water and I hope to be back on track writing regular (and hopefully interesting) posts within the next week or two. I've been working on some fun projects and I want to talk about it, really, but at the end of the day I'm just tired and don't know where to begin. I'll figure it out.
In the meantime, if you've got free cycles and some cash in your wallet to burn, pick up a copy of Dave Berube's new book on Ruby Reporting, which was just published by Apress. I served as a technical reviewer on it, which was a really great experience. Even though Dave and I don't see eye to eye on absolutely everything (cue groans about a particular chapter dealing with MS Access integration), it's full of fun Ruby code and unique reporting tips, including examples that interop with Google AdWords, SugarCRM, and PayPal. Congrats man, nice work!
Storing YAML in YAML Fixtures
If you ever find yourself in a situation where you have an attribute value that needs to be serialized (stored as YAML) and used within a (YAML) fixture, you can do something like:
FancyWidget:
id: 1
name: FancyWidget
serialized_hash: "<%= {:abc => {:xyz => 'foo'}}.to_yaml %>"
Or, if you want to embed YAML directly you can do:
FancyWidget:
id: 1
name: FancyWidget
serialized_hash: |
---
:abc:
:xyz: foo
This is probably old hat to a lot of you, but I figured I'd doc it here since the solution felt somewhat non-obvious at first. Rails stores serialized attributes in your database this way, so you might run into this if you're using ActiveRecord's serialize method.
Holiday Gifts: Ruby 1.9
Happy holidays everybody. In case you haven't heard, Ruby 1.9 was released yesterday. If you're not exhausted from overeating and traveling, I'd encourage you to spend a few minutes taking it for a spin.
Ruby now runs on top of YARV, and these benchmarks indicate that there are some pretty impressive performance increases over 1.8.6. That said, 1.9 is a development release; it's not currently intended for production use and don't expect Mongrel or Rails (or various other gems) to run on it just yet.
For more information, Dave Thomas has written a nice, concise list of pros and cons over on his blog, and Mauricio Fernandez has been maintaining a full list of changes found in 1.9. Enjoy!
DataMapper 0.2.5 Released
Sam released DataMapper 0.2.5 on Wednesday. It includes a bunch of tasty bugfixes before our next leap to 0.3.x. Give it a try if you haven't, I think you'll like it. And if you don't, well, you're... whatever.
One of the changes that I'm particularly fond of is proper method visibility for properties:
class Person < DataMapper::Base
property :ssn, :private => true
property :login, :protected => true
property :name
property :address
has_many :dogs
belongs_to :alien_overlord
end
The private and protected options are really just shortcuts for (:reader => :public, :writer => :private) and (:reader => :public, :writer => :protected), respectively. In most cases you'll want your reader to be public, but if you want to apply the same visibility modifier to both reader and writer, there's an ':accessor' option too. The property visibility is respected for mass assignment, which is a nice natural way to do things imo, unlike the hacky attr_protected stuff in ActiveRecord, which never felt right to me.
In any case, we've also updated the Website and Getting Started page, which was a little out of date. See those links for installation instructions and feel free to pop into the #datamapper channel on IRC, hit up the mailing list, or even leave blog comments here if you have any issues.
Other important changes (see the changelog for a complete list):
MyModel#[]only accepts a primary key now, not an options hash (use first, all)database.get(equivalent to AR'sfind_by_id) is approximately 25% faster than before!- Persistence module added (you no longer have to inherit from DM::Base, although I still prefer this approach)
- You can now set indexes with
:index => trueand:index => :unique - Validatable gem now used for handling validations