Let me share my thoughts on the first item of the six important values we care about. This is the value that can have the maximum impact with all the "User Experience" (UX) initiatives that is going on in our group. Among many, there are three main things that can help us in delighting our users....
1. Simplicity.... it is very powerful! Simplicity fuels many elements of good design, including ease of use, visual appeal, and accessibility. But simplicity starts with the design of a product's fundamental functions. We shouldn't set out to create feature-rich products; our best designs should include only the features that people need to accomplish their goals. Ideally, even solutions that require large feature sets and complex visual designs should appear to be simple as well as powerful. Our teams should think twice before sacrificing simplicity in pursuit of a less important feature. Our product Owners have a big role to play here to ensure that this happens in every project. Our hope should be to evolve products in new directions instead of just adding more features
2. Every millisecond counts. Nothing is more valuable than our application user’s time. Like Google, our pages need to load quickly, with the help of slim code and carefully selected image files. The most essential features and text are placed in the easiest-to-find locations. Unnecessary clicks, typing, steps, and other actions should be eliminated. Our products ask for information only once and include smart defaults. Companies like Google consider speed as a competitive advantage that it doesn't sacrifice without a good reason. Good thing is that we have started to target less than 1 second for a web page to load from New York. But, how about the users in Moscow or Singapore... we need to ensure that they also get faster response time.
3. Focus on our users – their work, their needs. We need to discover our users actual needs, including needs they can't always articulate. With that information, we should create products that solve real user problems. Improving users lives, not just easing step-by-step tasks, should be our goal. More importantly, a well-designed product should be valuable in daily work life. It is not just user interviews that would get us there. Interviews are very important, but we need to get better at analytics (usage stats, support tickets etc.) so that we understand the needs they can’t articulate and that's how we build valuable solutions.
To summarize, we need to delight our users consistently with simple, fast and valuable solutions. We have some good initiatives like the UX interest groups internally that is helping everyone understand this important value. But we need to get to a state where each Scrum team and its PO are thinking in this way.
How are you or your organization thinking about this?
Let me share my thoughts on the first item of the six important values we care about. This is the value that can have the maximum impact with all the "User Experience" (UX) initiatives that is going on in our group. Among many, there are three main things that can help us in delighting our users....
1. Simplicity.... it is very powerful! Simplicity fuels many elements of good design, including ease of use, visual appeal, and accessibility. But simplicity starts with the design of a product's fundamental functions. We shouldn't set out to create feature-rich products; our best designs should include only the features that people need to accomplish their goals. Ideally, even solutions that require large feature sets and complex visual designs should appear to be simple as well as powerful. Our teams should think twice before sacrificing simplicity in pursuit of a less important feature. Our product Owners have a big role to play here to ensure that this happens in every project. Our hope should be to evolve products in new directions instead of just adding more features
2. Every millisecond counts. Nothing is more valuable than our application user’s time. Like Google, our pages need to load quickly, with the help of slim code and carefully selected image files. The most essential features and text are placed in the easiest-to-find locations. Unnecessary clicks, typing, steps, and other actions should be eliminated. Our products ask for information only once and include smart defaults. Companies like Google consider speed as a competitive advantage that it doesn't sacrifice without a good reason. Good thing is that we have started to target less than 1 second for a web page to load from New York. But, how about the users in Moscow or Singapore... we need to ensure that they also get faster response time.
3. Focus on our users – their work, their needs. We need to discover our users actual needs, including needs they can't always articulate. With that information, we should create products that solve real user problems. Improving users lives, not just easing step-by-step tasks, should be our goal. More importantly, a well-designed product should be valuable in daily work life. It is not just user interviews that would get us there. Interviews are very important, but we need to get better at analytics (usage stats, support tickets etc.) so that we understand the needs they can’t articulate and that's how we build valuable solutions.
To summarize, we need to delight our users consistently with simple, fast and valuable solutions. We have some good initiatives like the UX interest groups internally that is helping everyone understand this important value. But we need to get to a state where each Scrum team and its PO are thinking in this way.
How are you or your organization thinking about this?
(Update October 2009)
I just updated a gem I wrote called View Mapper that will generate all of the code I describe below… you can use View Mapper to generate working scaffolding code that uploads/downloads files using Paperclip, or only view scaffolding code that works with an existing model in your app; for more details see: http://patshaughnessy.net/2009/10/16/paperclip-scaffolding
I love scaffolding. Many experienced Rails developers scoff at the idea of using scaffolding to generate Rails code: it’s ugly; it probably means you don’t understand how to write the code yourself; it generates a lot more code than you need, etc., etc. However, for a beginning Rails developer working on her/his own like me who isn’t surrounded by a team of Ruby experts, scaffolding is an essential tool and can help to get started in the right direction. Also, even for experienced Rubyists scaffolding can be a great way to quickly (minutes, not hours or days) get a simple app up and running to use for demos, UI wireframes, spiking some technical issue, etc.
This post will demonstrate how to use scaffolding to create a new Rails app from scratch that uses the Paperclip plugin to upload and display an image file. Feel free to copy/paste pieces of code from the narrative below and use them in your app, or you can just skip to the chase and get the finished version from github and run that on your machine.
There are a lot of other good tutorials out there about this; see:
I’ll take on the risk of repeating material that’s already out there in order to show how easy it is to get a working Paperclip application up and running using scaffolding. The fact that just a few commands and lines of code are required illustrates just how simple and powerful Paperclip’s design is. In my next post, I’ll proceed to change this sample app to demonstrate how to save the uploaded files in a database column instead of on the web server’s file system, using my modified version of Paperclip.
FYI At the time I wrote this, Rails was at version 2.3.2:
$ rails --version Rails 2.3.2
Let’s get started by creating a new Rails application:
$ rails paperclip-sample-app
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create config/initializers
create config/locales
create db
create doc
create lib
create lib/tasks
create log
etc...
Before we go any farther, let’s setup our database.yml file and create a new MySQL database to use with the sample app. Replace the contents of config/database.yml with this:
development:
adapter: mysql
database: paperclip_sample_app_development
username: root
password:
host: localhost
Enter the proper username and password for MySQL if they are not “root” and null. And then run this from the command line:
$ cd paperclip-sample-app $ rake db:create (in /Users/pat/rails-apps/paperclip-sample-app)
Ok, now we have a MySQL database to work with. Next, let’s go ahead and install the Paperclip plugin. The best thing to do is just to get the latest version from github; Thoughtbot frequently updates it with bug fixes, enhancements, etc.:
$ ./script/plugin install git://github.com/thoughtbot/paperclip.git Initialized empty Git repository in /Users/pat/rails-apps/paperclip-sample-app/vendor/plugins/paperclip/.git/ remote: Counting objects: 62, done. remote: Compressing objects: 100% (50/50), done. remote: Total 62 (delta 6), reused 39 (delta 4) Unpacking objects: 100% (62/62), done. From git://github.com/thoughtbot/paperclip * branch HEAD -> FETCH_HEAD
Now that we have an empty, shell application created and the Paperclip plugin installed, we can use scaffolding to add some working code to it. Let’s use the same “user” and “avatar” example Thoughtbot does on the Paperclip project page. The idea is that the sample will contain a table of users, and each user will have an avatar image displayed in the web site. So to get started, I’ll just create a new “user” model with string columns for the name and email address:
$ ./script/generate scaffold user name:string email:string
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/users
exists app/views/layouts/
exists test/functional/
exists test/unit/
create test/unit/helpers/
exists public/stylesheets/
create app/views/users/index.html.erb
create app/views/users/show.html.erb
etc...
Now we need to generate the database columns necessary for Paperclip on our new model object using script/generate:
$ ./script/generate paperclip user avatar
exists db/migrate
create db/migrate/20090430084151_add_attachments_avatar_to_user.rb
And let’s go ahead and create the users table using db:migrate:
$ rake db:migrate (in /Users/pat/rails-apps/paperclip-sample-app) == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.0031s == CreateUsers: migrated (0.0032s) =========================================== == AddAttachmentsAvatarToUser: migrating ===================================== -- add_column(:users, :avatar_file_name, :string) -> 0.0063s -- add_column(:users, :avatar_content_type, :string) -> 0.0069s -- add_column(:users, :avatar_file_size, :integer) -> 0.0085s -- add_column(:users, :avatar_updated_at, :datetime) -> 0.0081s == AddAttachmentsAvatarToUser: migrated (0.0311s) ============================
You can see that the Paperclip generator created columns in the users table called “avatar_file_name,” “avatar_content_type,” “avatar_file_size” and “avatar_updated_at.” Now we have our database schema setup. The next step is to just modify the code that was generated for us by the scaffolding and make the changes necessary for Paperclip. The first thing to do is to add a line to the user model and indicate that it has a file attachment called “avatar.” To do this, open app/models/user.rb and just add this one line:
class User < ActiveRecord::Base has_attached_file :avatar end
And then edit the new user form (app/views/users/new.html.erb) and add a file field to use to upload files. There are actually two code changes you need to make: first you need to set the HTML form to encode the uploaded file data (and other fields) using MIME multiple part syntax, and then second you need to actually add the file upload field. Here’s the finished new.html.erb file with these two changes in bold:
<h1>New user</h1>
<% form_for(@user, :html => { :multipart => true }) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :email %><br />
<%= f.text_field :email %>
</p>
<p>
<%= f.label :avatar %><br />
<%= f.file_field :avatar %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', users_path %>
Also make the same changes to the edit form that was generated by the scaffolding: app/views/users/edit.html.erb. The best thing to do would be to include the same ERB file (maybe called “_form.html.erb”) in both the new and edit form files. Ideally the scaffolding generator would have done this for us…
Now if we run our application we can upload an image file and attach it to a user:

If you submit this form, the image file will be uploaded to the server and saved on the file system. By default, Paperclip saves files inside a “system” folder it creates in your Rails app’s public folder. Let’s take a look at my public folder and see where the file went:
$ find public/system public/system public/system/avatars public/system/avatars/1 public/system/avatars/1/original public/system/avatars/1/original/mickey-mouse.jpg
This is one of the nice things about Paperclip: it just works. I don’t have to think about or worry about where the files are going to go; Thoughtbot has chosen simple default values that make sense. Here we can see that there are a series of folders created that correspond to the attachment name, model primary key and also the “style” of the attachment (more on that below).
If you want to or need to save the files in some other place on your server’s file system you can specify different options to has_attached_file in your model; see this write up for an example: http://travisonrails.com/2009/01/11/Changing-Paperclip-File-Storage-Location. Paperclip also supports saving the files in Amazon’s S3 storage service, and in my next post I’ll demonstrate how to save the file data inside the database itself, right in the users table in this example.
I’m almost done; now I just need to display the uploaded image somewhere; the simplest thing to do is just to add an image tag to the users show page. Again, my changes to the standard scaffolding code are in bold:
<p> <b>Name:</b> <%=h @user.name %> </p> <p> <b>Email:</b> <%=h @user.email %> </p> <p> <b>Avatar:</b> <%= image_tag @user.avatar.url %> </p> <%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>
Now we can see the image for our new user:

Since this image is bigger that what I would like, I can take advantage of Paperclip “styles” feature to generate a smaller version of it. To do that you will need to be sure you have ImageMagick installed on your server, which is what Paperclip uses behind the scenes to modify image files. Then all you need to do is just add two “styles” to your model, like this:
class User < ActiveRecord::Base
has_attached_file :avatar,
:styles => {
:thumb => "75x75>",
:small => "150x150>"
}
end
The strings we pass in are actually options for ImageMagick's "convert" command; see it’s documentation for more details. And now in the show ERB we can just specify the “small” style in the image tag instead:
<%= image_tag @user.avatar.url(:small) %>
To see it, first I need to re-edit and re-upload the image (remember to add the file field code to edit.html.erb just like for new.html.erb):

Now when this form is submitted we will see the smaller image:

$ find public/system public/system public/system/avatars public/system/avatars/1 public/system/avatars/1/original public/system/avatars/1/original/mickey-mouse.jpg public/system/avatars/1/small public/system/avatars/1/small/mickey-mouse.jpg public/system/avatars/1/thumb public/system/avatars/1/thumb/mickey-mouse.jpg
Again, this is very simple and just works! As a last step, let’s add the thumbnail image to the users index page so we can see Mickey without even clicking on that user record. This is as simple as editing app/views/users/index.html.erb and adding a new table column:
<table>
<tr>
<th>Photo</th>
<th>Name</th>
<th>Email</th>
</tr>
<% @users.each do |user| %>
<tr>
<td><%= image_tag user.avatar.url(:thumb) %></td>
<td><%=h user.name %></td>
<td><%=h user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
And now we just need to refresh the index page since the thumb image file was already generated:

And there you have it: a working file upload web site written in minutes. This was made possible by Rails scaffolding, and Paperclip's simple, elegant design.
OK, confession time.
I just watched the preview for Julie and Julia, and I cried. Not a lot, but enough.
The movie is based on two books. One is a book that inspired, moved and still drives me (Julia Child's My Life in France), and one is a book that made me go, eh (Julie Powell's Julie and Julia).
Nora Ephron has taken both books and adapted them into one movie. It stars two of my favorite actors (Meryl Streep and Amy Adams), and it'a story about two women (one in her 40s and one in her 30s) finding themselves in the kitchen.
And so, despite my dislike of Powell's memoir, that's still a movie I can get behind, sight unseen. Though you probably won't want to come with me, since it appears I'll be blubbering away half the time.
Enjoy.
Mom always told me, "It's what's inside that counts." Companies are finally paying attention to how social media affects their business outside the company walls. They recognize the extent to which Twitter, Facebook, Wikipedia, and other mass-collaboration forums present both...
Mom always told me, "It's what's inside that counts." Companies are finally paying attention to how social media affects their business outside the company walls. They recognize the extent to which Twitter, Facebook, Wikipedia, and other mass-collaboration forums present both...
I consider myself a realistic idealist, if that makes any sense. I have a few - but not too many (that's the realist part) - core principles to which I adhere with all my might, and I try to live them every single day.
One of those is living in a reasonably eco-friendly way. Being a New Yorker makes this a lot easier - haven't owned a car in eight years, don't buy bottled water (NYC's being the best tap water ever), live in a small space, use mass transit, donate old belongings to Housing Works, buy as much food locally as I possibly can, and so on.
One more thing I've started doing in the last 18 months or so is carrying re-usable shopping bags everywhere I go. I carry a combination of canvas tote bags (which fold up nice and small) and sturdy nylon shopping bags. The canvas bags are great for books, clothes, and greenmarketing (keeping your purchases slung over your shoulder makes it a lot easier to check out the next stand), and the nylon ones are perfect for toting your lunch or an all-in-one grocery run.
My favorite nylon bags are the ones by Baggu - there's just something stylin' about their shape, and they come in every color under the sun. When it comes to canvas, my favorite sources are the Etsy sellers The Craft Pantry (who made the bird tote and French produce sacks above) and Jose Pulido. (You can also find re-usable produce bags made of netting on Etsy.)
So go on, take the plunge - you'll look cool, and feel cooler. Seriously.
Photos courtesy of The Craft Pantry and The Monterey Garden Club.
On Saturday, the last day of my staycation not devoted to laundry and checking work emails, Cristin and I piled into the car and headed up to Blue Hill at Stone Barns, a working farm (and restaurant, and cafe, and educational center) located on the old Rockefeller estate in Pocantico Hills.
The last time I visited, back in October, the chickens were out to pasture, and the leaves were turning. Life on the farm was quiet, everyone hunkering down for a long winter of root vegetables. On this visit, on the first warm day of spring, the farm was abuzz with energy and excitement, lettuces were blooming in the greenhouses, and......the lambs were arriving! Just that morning, three lambs were born on the farm, joining many of their cousins, who arrived earlier in the week. The two and three day old lambs were hanging out with the rest of the sheep, testing their limits with Stella, the giant Italian sheepdog, and generally enjoying themselves. The newborns (by which I mean, born just that morning) were asleep in the birthing pens, exhausted from the morning's efforts, while their mothers hovered protectively.
But happiest of all was this guy (furthest on the right), a full-grown sheep. The sheep snack on hay stored in a wire pen. The pen has a few head-shaped holes in it, and the sheep stick their faces through to get to the good stuff. Somehow, this one managed to get his whole self into the middle of the haystack. He stood there, no doubt warm and cozy, eyes shut, and chomping away. The hay pushed itself into his mouth - he didn't even have to reach out to grab a bite. He looked even happier than the pigs did in their shit.
Visiting the lambs was fun, and they are completely huggable. They're soft and cuddly and goofy and funny. None of which makes them any less delicious. Now, I know - I'm ruining a perfectly adorable post by bringing up the fact that the majority of these lambs are being raised for one reason: food.
But it's visits like this - where I get to see the process from the start, where I see how well cared-for these animals are - that reinforce my decision to eat ethically-raised meat. The idea mass-produced chicken makes me squirm with nausea, but the idea of a meal raised at Stone Barns? That I can do.
Finally we came up with the values to support our vision as an Application Development organization (Note that this is different from our firm's values).
To take us to “Excellence in Software Development” we would like to base our activities on the following core values…
1. Delight our users with simple, fast and valued solutions
2. Consistently exceed sponsor expectations
3. Inspire our colleagues
4. Understand best practices. Embrace next practices
5. Demonstrate software craftsmanship
6. Create the best place to work, rich with fun and passion
But, what are values? They are … what’s really important to an organization, as Konrad Knell explains in his blog. They are the essential and enduring beliefs – a small set of general guiding principles, not to be compromised for short-term expediency. Each value should be a piercing simplicity that provides substantial guidance to the members of the organization. They cannot be copied or dictated; they are what is authentically believed by the most in the organization.
Every word in each of the six sentences above matters. Now you can imagine how difficult it is to come up with it. The good thing is that we were able to share and confirm with the whole group that these are the right values for us to focus on.
I will write about these values and what it means for us in my upcoming posts…
Finally we came up with the values to support our vision as an Application Development organization (Note that this is different from our firm's values).
To take us to “Excellence in Software Development” we would like to base our activities on the following core values…
1. Delight our users with simple, fast and valued solutions
2. Consistently exceed sponsor expectations
3. Inspire our colleagues
4. Understand best practices. Embrace next practices
5. Demonstrate software craftsmanship
6. Create the best place to work, rich with fun and passion
But, what are values? They are … what’s really important to an organization, as Konrad Knell explains in his blog. They are the essential and enduring beliefs – a small set of general guiding principles, not to be compromised for short-term expediency. Each value should be a piercing simplicity that provides substantial guidance to the members of the organization. They cannot be copied or dictated; they are what is authentically believed by the most in the organization.
Every word in each of the six sentences above matters. Now you can imagine how difficult it is to come up with it. The good thing is that we were able to share and confirm with the whole group that these are the right values for us to focus on.
I will write about these values and what it means for us in my upcoming posts…
Welcome to second edition of Queenie's Take! This week, we're talking travel!
Hall (no stranger to first-class travel experiences) is headed to Napa for a vacation in June, and is looking for some off-the-beaten-path spots to visit. He's been to Napa before, and he's done most of my favorites - Schramsberg, Far Niente, and so on. This time, he wants to try something new. And so I racked my brain, consulted my tweeps, and got down to business.
The question is where to go next? Any 'cult' vineyards that are worth seeing? Any over-the-top oenological experiences there that are hiding under my nose? Or, is there something else that is a must do? - Hall, Christchurch, New Zealand
After rigorous research and vetting (read: visiting a couple of these myself and asking some trusted friends), I think I have some ideas for you, Hall. First up, Frank Family Vineyards. I'm not really sure that Frank Family qualifies as a hidden treasure, since so many people mention them to me, but I love them all the same. Tucked away on a dusty side road just south of Calistoga, Frank Family produces delicious wines (I am particularly fond of their sparkling wines and their cabernet). They also have one of the livelier tasting rooms I've visited, with a casual, easy-going atmosphere.
Next, Reynolds Family. (Apparently, I have a thing for families.) I visited Reynolds Family with - appropriately enough - my mother, when we went to Napa in 2007. Our friends Rick and Aimee had alerted us to the awesomeness to be found at Reynolds, and they were right. The staff were some of the most knowledgable I've encountered in Napa, and the thirty-minute tasting was pure fun. They specialize in reds, and I actually have a bottle of their cabernet that needs drinkin'. Hmmm...
If you are hankering for a twisty drive, impressive views, and a gorgeous tasting room, head for Pride Mountain, which straddles the borders of Sonoma and Napa counties (and has the taxation headaches to prove it, it seems). Perched at the peak of the hill, the tasting room has expansive views of the valleys below, and the wines ain't bad, either. Just remember to designate a driver for the trek back down. (Safety first, kids!)
My friends Caroline and Brian are the ultimate Napa experts, so I trust their every word. They both (separately, so I REALLY trust it) recomment Vincent Arroyo, where the wine is so good that you really buy futures, as opposed to bottles. It sells out before it's out of the barrels. Seriously. And Brian says that August Brigg's Dijon Clones Pinot Noir is the best thing he tasted on his last trip.
So, Hall - I hope that'll do ya good for this trip. Don't forget to stop in at Bouchon Bakery for a caramel macaron, and you're golden.
As for the rest of you - please chime in with your favorite Napa and Sonoma destinations, and, also, with any questions you'd like to see me take on in a future edition of Queenie's Take!
In one of my ruby applications, there was a requirement to monitor application log file. We did it very quickly with sinatra web application framework. The requests made are checking whether application is running, viewing log file, etc.
http://www.sinatrarb.com/intro.html
Sinatra is a DSL for quickly creating web-applications in Ruby with minimal effort. In Sinatra, a route is an HTTP method paired with an URL matching pattern. Each route is associated with a block. Routes are matched in the order they are defined. The first route that matches the request is invoked.
Sinatra rides on Rack, a minimal standard interface for Ruby web frameworks. One of Rack’s most interesting capabilities for application developers is support for “middleware” — components that sit between the server and your application monitoring and/or manipulating the HTTP request/response to provide various types of common functionality.
Just do this and you will be on track :-)
gem install sinatra
ruby myapp.rb
In myapp.rb :-
get '/checklog' do
Some code here ...
[200, {"Content-Type" => "text/html"},
"Log is ok?:true or false]
end
I came to know about blogs and wikis two years ago, when I first heard of Web 2.0. Since then, I saw drastic changes in web. Last month I heard something about Web 3.0, I realized that the way web is going, it's obvious to have various versions coming so frequently :) But still I had some unanswered questions :
With the advent of IT, every organization has been investing into the adding/upgrading and probably phasing out technology. However as organization are growing, the needs of their IT systems have been growing. The systems have gone through manifold changes with the changing business needs. Every application have gone through the hands of multiple people. Not only the application but also the IT infrastructure side of things also goes thru a drastic changes. With new set of hardware purchases to license purchases. At the end of the day, there is high degree of possibility that on a given day it is very difficult to estimate what our true cost towards technology should be.
In the current economic scenario, every organization is under pressure reduce the costs at each and every cost centre. IT and systems areas are the ones who would always face the highest pressure as they are mostly not the mainstream business for the organization. With that in mind I think it is necessary for every person working in this area to look at the following aspects
a. Work on getting the legacy pieces weeded out from the application(Basically Refactor) and reduce the cost to maintain/support application
b. Consolidate apps to make sure that the no two application is trying to provide the same feature. Also work on reducing redundant features
c. Phase out apps that are no longer being used or are being rarely used. For apps that are being used only some of the times or by a very limited set of people, there is a need to evaluate if it is required to continue having a separate app or can the features be consolidated some where else
d. Look at the complete license costs in the organization - Review in terms of re-use of surplus licenses, remove all together or strategically extend/buy new licenses
e. Analysis the utilization and distribution of infrastructure - Look at options to Consolidate/Phase out/or move to platforms that could reduce TCO.
The basic idea is to rationalize the cost towards building and maintaining the Applications, Infrastructure and Licenses used in the organization
Those who follow me on Twitter may recall that I had cocktails at Ouest on Thursday night, and that they were, well, HUGE. Now, I love a good cocktail, and, occasionally, I love a big cocktail - though it's probably best for me to avoid the latter when I've not had dinner.
I was surprised by how affected I was by the two Manhattans - so surprised that I stayed up till 1:30, amused by the fact that I was, to put it frankly, still tipsy. Needless to say, when I woke up on Friday, I was a bit out of sorts.
The solution? A homemade version of every lush's best friend: the fried egg sandwich.
I toasted a crumpet and spread it with a bit of Wednesday's homemade mayonnaise. Then, I fried up a couple of slices of slab bacon. I topped those off with a fried Knoll Crest egg (sunny side up, thanks), sprinkled some chives over the whole bit, and plopped down to enjoy it all with an ice cold Diet Coke.
It cured what ailed me.
Professional development 1.0
Professional development is a focus for many organizations and over the years, we've witnessed this responsibility move from the hands of human resources to our managers. This change made it much more relevant and actionable because of frequent (from every year to every month) and contextual (from generic to patterns) interactions.
Professional development 2.0
The next leap forward in professional development could be by opening it up, by making it a social process (peer-led professional development). Our colleagues who we work with day and night (if you party together) can give us most relevant and actionable coaching and feedback. This is different from seeking their input once/twice a year as in Professional development 1.0. In 2.0, its a continuous process to make interactions more frequent (from monthly to daily) and contextual (from patterns to concrete).
Why restrict professional development coaching and feedback to a single person (manager)? Why not tap into the entire pool of "coaches" we have in our teams?
This is a big shift for a group and will need experimentation to test and fine tune. I plan to conduct a mini-experiment internally (democratize coaching and feedback for myself) and will keep you posted...
Some considerations
Some might argue that such a process will become a finger-pointing, criticism match. And I agree. The prerequisite for social professional development is the right culture, an environment where people care about each other, want to see each other succeed. A group will need to invest in creating such a culture before trying this. The group will to be trained as well, similar to the manager training in organizations practicing professional development 1.0.
If what you work on isn't your dream job, then read on...
Yes, most of us aren't in our dream jobs yet, whether it is running a school in India, opening a restaurant in backwaters of Kerala, becoming a professional soccer player/photographer/dog groomer/painter).
My dream job is to run a school in India and it'll take me few more years to get there. The question I ask myself everyday is what should I do till then? How can I make the most of the next 5 - 10 years "working" towards my dream job?
Here's how I look at it...
1. Find work I want to do
Find my organization's priorities and
- get involved in areas that I'm passionate about (maximize my time here)
- develop in areas that I'm not as good at or as interested in (its work after all!)
2. Make work I want to do
Make my interests public and "create" opportunities at work by influencing/exploring unchartered territories - "create my own workplace here"
3. Ignore everything else
Why should I spend any minute on something that's not important to me and the organization
I want to run a school in India one day. What excites me is the opportunity to help shape the future of bright kids.
In my school, there will be a good mix of formal education and sports (more on using sports as a tool to teach key behaviors like team work, leading, coaching, helping others, in my future posts).
What is your dream job?
No visit to (or staycation in) New York would be complete without a visit to Central Park! On Friday afternoon, after a lunch of steak tartare at the soon-to-close La Goulue (where the steak tartare isn't bad, but pales in comparison to the delectable version to be had at Camille), I wandered into Central Park just north of the zoo.
About halfway across the park, just past the Bandshell, I plopped down on an empty, sun-soaked bench, cracked open a book (The Story of Edgar Sawtelle, my newborn book club's first selection), and passed the next three hours in a haze of contented happiness.
I ask you, though - is this Central Park, or an enchanted forest? The tangle of elm and oak trees had me mesmerized.Once I'd been thoroughly lazy for long enough, I walked home up Madison Avenue, where I admired the ridiculously blue skies and piece of gorgeous couture in the Vera Wang window. All in all, a nice way to finish a lazy afternoon.

It's no secret to anyone, least of all to my loyal readers, that I am a caffeine addict. Over the years, we've enjoyed many a caffeinated treat together, including coffee in Paris, hot chocolate in Prague, and Diet Coke on hungover New York mornings.
My latest caffeine-laced obsession is Joe The Art of Coffee. I'd visited Joe on Waverly a couple of times before, but obviously the Upper East Side to Greenwich Village trip is not always the most convenient trek for a cup of coffee. And so, I was pretty damn psyched when I heard that Joe was opening a new branch on Columbus Avenue, between 84th and 85th - just a crosstown bus ride away, and open in time for Staycation 2009.
On Wednesday afternoon, after my visit to Pearl Oyster Bar, I hopped on the C train from West 4th and headed uptown. The day, which had been hopelessly gray and dreary when I scrambled down the subway stairs, seemed noticeably brighter when I emerged at the corner of 86th and Central Park West.It became brighter still when I spotted Joe - and, more importantly, a free table. I snagged the table, claiming it for my laptop, and walked up to the counter to order my first drink - a cappuccino, to stay. The barista punched a fresh frequent drinker card and got to work. Every single drink at Joe is made fresh, and every single one is a labor of love.
My cappuccino arrived complete with milk art (a heart, aw), and I retreated to my spot by the window to enjoy it properly. And what a cappuccino it was. The milk was silky and smooth, with a fine mousse (as opposed to the fluffier milk you get at, ahem, inferior coffee shops) and a nutty, toasty flavor. And the espresso - my god. Round and full in the mouth, smoky and sweet, almost like a good bourbon.Next up, a cafe au lait, my breakfast beverage of choice. Made with Joe's house drip, this was less potent than the cappuccino, a bit gentler all around. That said, it went down just as easily and was just as delicious - just not quite as grand.
I ended up spending three hours at Joe that afternoon - and two hourse the next. The staff are happy to let you chill and hang as long as you like - though there isn't free WiFi provided by the house, which is pretty much the only even close to bad thing I can think of to say about it. So get your uptown bums over there now, ok? OK.
Oh, and - for you out-of-towners? Joe offers their beans for sale online!
Joe The Art of Coffee
514 Columbus Avenue
Between 84th and 85th Streets
212-875-0100

"It is literally true that you can succeed best and quickest by helping others to succeed" - Napoleon Hill
And the drive we just completed at my work place only proves the saying above.
I work with an IT consulting FIRM and lead a high performing team that excels in providing BI solutions to clients.
Work usually becomes really hectic for people working in a delivery model such that one does not have time to even catch up with personal activities on a given day. Also the rat race in the IT industry is such that it needs an IT professional to constantly upgrade his/her skill set.
So the question my team (like all other IT professionals) had is how do we achieve this and at the same time maintain the right Balance with Hectic Work Schedules?????
well the obvious and the simplest solution to this query is to keep reading and completing certifications as and when new technologies invade the market. And all this is expected to be done after work hours. BUt this is usually too taxing for an individual and it is seen that personal development always takes a back seat in the bargain. Not all individuals are motivated to invest extra hours for personal growth and at the end of the day some do it and some dont.
This leaves us with a bigger Question - How DO we Inspire all members in a team to constantly upgarde themselves such that the team as a single UNIT is pushed to the next level of Excellence?????
My answer to this would be "COHESIVE INSPIRATION" (as I call it).
Cohesive because the team bonds together to achieve a common Goal and every person in the team acts as an inspiration to every other person, in their march towards this goal to top the competency chart as an individual and together as a team.
The next Question we now need to answer is - How do we get this Mania kicking within the team ?????
To do this we started a small initiative within 2 teams where we asked every individual working on the same technology stack to enroll for a common exam on a chosen day. Surprisingly we had 19 individuals voluteering to pick on a certification of their interest (me inclusive). On grouping them we saw that there were atleast 6 individuals who had opted for a common exam and all on the same day. So this invariably created a "College Environment" after work hours. People started collaborating within sub groups to share their experiences and study together. All this made learning more fun like we did in school, THis is what I meant by "COHESIVE INSPIRATION" above where people gel together and motivate each other to achieve a common goal.
End result - On the given day all 19 members who had enrolled for their respective certifications came out with flying colors and this resulted in team (of course with individuals) upgrading to next level of competency.
This actully worked for my team. want to try it ?????
Well, dear readers, when it rains, it apparently pours!
I entered my apartment in Apartment Therapy's annual Small Cool contest, and it's been posted for voting! This is just the first round - if I get enough "Thumbs Up" votes this time around, I will then proceed to the final round.
So, pretty please - click through to my entry and click the "Thumbs Up" button as soon as possible. My entry is only eligible for voting till Sunday afternoon, so don't dilly-dally!
And many, many thanks in advance. You guys are the best!
I m back to blogging, huh ! and after a gap of 2 months :( Feeling bad about it as I couldn't write since past couple of months. I was going crazy buzy in projects and operational activities within organisation. I need to learn a lot on "Time Management" :)
Nevertheless, was atleast able to resume my quest on learning jQuery and writting advance aggregator which I left in middle (for version 1.2) during Feb'09. Made lot of mistakes today while working on my code but learnt quite a few aspects on widgets, events customization, minified code, costomizing plugins etc. I hope to release v.1.2 beta by mid May'09 as I am still struggling towards time management :)
BTW did you use "Fire Eagle" ? Today I logged on my Fire Eagle account and explored few apps. Fire Eagle is a Yahoo! owned service that acts as a store for user location information. The genius of Fire Eagle is its sheer simplicity. It does absolutely nothing beyond storing your current location, and disseminating it to your choice of sites and applications.
Read more -> http://www.pointbeing.net/weblog/2008/04/fire-eagle.html
Fire Eagle have already implemented many of the items discussed in the link above. Fire Eagle now have tons of applications including DBpedia Mobile, Bloggy, Tweets, Feeds, findme, Geoupdater and many more.
One of my absolute favorite things to read is the Dear Prudence column over at Slate. Every week, Prudie (really Emily Yoffe, a Slate staff writer) takes on questions of etiquette and interpersonal relationships, always with a healthy dose of humor - and horror, when appropriate.
It was while reading Prudie a couple of weeks ago that it hit me - ever since I started blogging as Queenie, I've gotten tons of emails (and phone calls - you know who you are) asking for advice about where to eat, how to entertain, and what to cook. I (try to) answer each question, but I don't share the advice terribly widely.
And, so, I'm very excited to announce "Queenie's Take," a new weekly feature here at Queenie Takes Manhattan! Each Thursday, I'll be taking on a question or three with wit and verve. Now, while I definitely have opinions about relationships, romantic and otherwise, I'll be keeping the official Queenie advice to questions about cooking & baking, entertaining, culinary history, travel, and dining out.
This week, in the spirit of things just starting, I'll be answering questions about jump-starting your kitchen and cookbook library.
And, if you have a question you'd like to see answered in a future "Queenie's Take," just leave it in the comments!
What are the first five cookbooks any beginning cook should buy, and why? - Louisa, Sandusky, OH
Obviously, there's an element of personal taste involved here. For instance, my great culinary loves are French and Italian foods, with a big emphasis on vegetables and pastry. But you might want to cook mainly Vietnamese food or have a thing for traditional English pub grub. So, what I'm going to do is recommend investing in three robust classics, and tell you about two of my favorite add-ons, books that speak directly to my own sensibilities.
1. The Gourmet Cookbook, Ruth Reichl
Setting my Ruth Reichl fascination aside for a moment, this is still a remarkably wonderful cookbook. It's huge (1,056 pages, 1,000+ recipes), but well-indexed and easy to use. The recipes are detailed but straightforward, and the dishes range in difficulty from very, very simple to fairly complex. It's decidedly American in its eclecticism - there are recipes for French classics like bouillabaisse, Spanish ones like paella, and even one for pad thai. More importantly, the book seeks to educate you about the origins of its recipes and techniques. Following in the steps of the great Julia Child, Ruth Reichl knows that cooking is not just about blindly following a recipe, but about understanding why you're doing what you're doing. Which brings us to...
2. Mastering the Art of French Cooking, Volume I, Simone Beck, Louisette Bertholle, and Julia Child
It's hard to overestimate the impact Julia Child and her fellow authors had on the American palate. Though she's certainly not solely responsible (Alice Waters comes to mind here), Julia is a huge part of the reason why we eat so many fresh vegetables with delicious, light vinaigrettes; why we make apple pies with fresh apples and homemade crusts; why we once again have chicken that tastes like chicken. Her cooking show and cookbooks exploded the mid-century obsession with factory-based cooking, and are as relevant today as they were forty years ago. Julia will teach you how to roast a chicken, how to make a salad, how to turn a vegetable - and then how to gradually build on top of those basic skills. Along with her husband Paul's impeccable line drawings, her words will teach you more than I can possibly explain. And, lest you be one of those people out there who still believe that French food = fussy, Julia will set you straight, once and for all.
3. The Joy of Cooking, Irma Rombauer
The veritable Bible of American cooking. Where Gourmet is a decidedly modern take on the American canon, Rombauer's book retains the flavor of pre-Julia America, with fantastic, easy recipes for classics like brownies, pancakes, and casseroles. The merits of the latest edition, re-worked for a more contemporary cook in celebration of the original's 75th anniversary, have been hotly debated. For my money, the original is still the best, in all its 1930s frugal practicality.
4. Barefoot in Paris, Ina Garten -and- 5. Lidia's Italian-American Kitchen, Lidia Bastianich
Ina's French cookbook simply can't be beat for foolproof, delicious bistro classics. Her gougeres? Simple as can be to make, incredibly impressive when served. The brownie tart makes grown men swoon, and there's little that makes me happier than her creme brulee. She takes the straightest, easiest route to delicious, entertaining-friendly French food, which is pretty miuch my favorite thing to cook, ever. And Lidia! If she isn't the Nonna we all wish we had teaching us to cook, I don't know who is. Like Ina, her food is simple to prepare, but tastes anything but, and is meant to serve a crowd.
What cookbooks would you guys recommend?
What do you consider essential food items/ingredients (spices, etc.) to keep in your kitchen such that you don't have to buy every single ingredient when preparing a particular recipe? - Richard, New York, NY
Like the cookbook question, this depends largely on the kinds of food you like to prepare. I recently unearthed a bottle of chili powder that's survived eight years and three moves without being opened, but you might be someone who'd use it all the time. However, here's a list of things I try to never be without:
Kosher salt & freshly ground black pepper: Kosher salt is easier to handle with your hands, and is the salt most often used by cookbook authors and recipe testers. So use it, wouldja? As for pepper - those blends of red, green and black peppercorns are pretty, but the clear, true flavor of black pepper is what you want for cooking.
Butter & canola oil: Unsalted butter, please - you can always add salt to taste, but you can't take it out. Canola oil is more versatile than olive oil - neutral in flavor and with a high smoking point, it can be used for cooking or for salad dressings.
Flour & sugar: Even if you're not a big baker, you'll want to have at least a small box of flour on hand for thickening sauces and soups. Sugar, well, duh!
Tomato paste & Dijon mustard: Both are great secret weapons for creating deep flavor with very little effort, and the mustard is essential for vinaigrettes, too.
Eggs: An absolute must for bakers, these are also great for creating quick, easy dinners (omelets, fritattas, and so on).
Garlic & shallots: Garlic is God's gift to mankind; shallots are more versatile and less acidic than onions.
Sherry & champagne vinegars: Between these two, you can make a huge variety of salad dressings, mayonnaises, and so on - and also add a teaspoon or two to sauces for a bit of contrast.
Baking soda, baking powder, and vanilla extract: These are really for those who plan to bake. Vanilla pops up in pretty much every baked good known to man, so invest in some good stuff. Baking soda and baking powder ARE different, so buy both. They cost about a buck apiece, so you can splurge here.
What do you think? Did I leave anything out that you consider essential?
By the way, a HUGE thank you to all the friends who sent me questions for the first edition of "Queenie's Take." If I didn't answer you this week, have no fear - your question will be included in a future edition!
Yesterday started off gloomy, with spitting, noncommittal rain and low, gray clouds. Suitably depressed, I spent the morning catching up on emails, checking in with my tweeps, and charging up my laptop. I trudged out the door at about noon and took the (oddly crowded with children) bus across town, and then the C train down to the West Village.
My destination? Pearl Oyster Bar, the legendary (and legendarily tiny - at least before its recent expansion into space next door) seafood restaurant on Cornelia Street. I'd been hearing tell of Pearl's obscenely delicious lobster roll for years now, but the accompanying descriptions of ridiculously long lines pretty much killed my curiosity.I figured, though, that a weekday afternoon was probably a pretty good time to avoid the crowds and decided to give it a shot. I was right! When I walked into the restaurant, it was about half-full, and since I was alone, the bartender invited me to sit at any of the open bar stools. I hopped up to a spot right in the middle, pulled out my book, and ordered a glass of Pinot Blanc and a lobster roll.
The lobster roll arrived about five minutes later, accompanied by piping hot shoestring fries. Huge chunks of lobster tossed with mayonnaise, salt, pepper and lemon juice graced a freshly-toasted (and buttered) brioche hot dog bun. Topped with a shower of chives, it was pretty much one of the best things I've ever tasted. I can't explain it - I think it must be either something in the lobsters' cooking liquid or some kind of alchemy, but this salad was just...insane.I doused the fries with malt vinegar and dug in. I thought the ridiculous deliciousness would wear off as I made my way through the roll, but I was wrong - the very last bite was just as good as the very first. Possibly more so, since it involved the buttered bun.
Now, I'm a New Englander. I have my loyalties, and I am of course of the mind that seafood - particularly lobster and, say, fried clams - are best enjoyed as close to the ocean as possible, preferably at a picnic table within spraying distance of the waves. However. I am not exaggerating when I say this was, without a doubt, the best lobster roll of my life.I finished the meal off with two mugs of coffee and a few more pages of my book, reluctant to leave the calming rythms of the semi-open kitchen and gentle hum of conversation at the bar. Eventually, though, I packed up my things and headed back out into the gray New York afternoon - which, I have to admit, seemed a whole lot brighter.
Pearl Oyster Bar
18 Cornelia Street
Between Bleecker and West 4th
212-621-8211 (No Reservations)
Morwena asked this question today, and it's a fair one - chances are, if you don't live in the northeastern U.S. or eastern Canada, you've never tasted a ramp.
So, here's my answer: to me, ramps taste like spring onions crossed with garlic, with a slightly meaty flavor thrown in for good measure.
Do you guys agree?
On Monday morning, I had a work call at 9:30 (that's right, I'm very dedicated). After that, though, it was staycation all the way.
Since my mom was staying with me, and had meetings of her own all morning (over the phone, natch), I decided to vacate the premises as best I could. I took the opportunity to hit the market for some chicken, cucumbers and bacon (the better to fuel up for my staycation, of course).
On Tuesday morning, I seasoned said chicken with salt, pepper, and thyme (from my windowsill - it's so nice to have access to fresh herbs at a moment's notice again) and roasted it. It's well-documented on this blog how much I like to have cold chicken lying around at all times. In this case, though, I will admit I had a plan in mind for at least some of the meat.
While the chicken cooled, I set out some vinegar, oil, mustard and an egg yolk on the counter. Once they'd reached room temperature, I whisked the yolk, mustard and vinegar together, adding a pinch of salt and a grind or two of pepper. As I whisked, I drizzled in the oil - drop by drop at first, then in a thin stream, until the egg yolk had taken on all the oil and had become...mayonnaise.
Honestly, if there's a pleasure more basic or delicious than homemade mayonnaise folded with warm chicken, cucumber, chopped shallots, and finished with some chervil and chives, I don't know it. I truly don't.
Queenie's Chicken Salad
Breast of one roasted chicken, cut into 3/4-inch cubes
1 Kirby cucumber, seeded and cut into 1/2-inch chunks
1 small shallot, finely chopped
1 tbs. chervil, finely chopped
1 tbs. chives, finely chopped
1 to 2 tbs. homemade mayonnaise
Salt and pepper, to taste
Combine the chicken, shallot, cucumber, chervil and half the chives in a medium bowl. Add the mayonnaise a bit at a time, folding the ingredients into the sauce, until your salad has the desired consistency. Season to taste with salt and pepper, sprinkle with the remaining chives, and eat as soon as humanly possible.
Serves one, generously.
If you can't find chervil, substitute a teaspoon of flat-leaf parsley. If you don't want to make homemade mayo, substitute two tablespoons of commercial mayonnaise mixed with one teaspoon of sherry vinegar.
Saturday was the first day of my staycation, and I decided to get things off to a fitting and proper start with a trip to the Union Square Greenmarket. It was absolutely pouring - and before 9 AM - so the market was fairly empty.
This is a good thing for two reasons: first, for someone who absolutely loves living in Manhattan, I get a little ticked off in large crowds (particularly when I'm trying to get my grocery shopping done and the other people are just oohing and aahing at the wares). Second, it boded well for me getting my paws on some ramps.
Sure enough, I succeeded! I heard whispers running through the crowd from the second I crossed 14th Street - people asking the various vendors if they had any ramps, and the farmers shaking their heads and pointing toward the west side of the park.
And there, just north of the subway entrance on Union Square West, were ramps. Piles and piles of them. A bit small and short, but ramps nonetheless. I reached in, grabbed five bunches, forked over $12.50, and sped off to get some fresh Knoll Crest pasta.
When I got home, I put away the day's purchases (spring onions, chives, the ramps, duck legs, tulips) and sped off to do the rest of my errands. When my friend Ellie called to let me know she wouldn't be coming into the city to meet up, I knew what I had to do.I cleaned a few of the ramps, in all their stinky goodness, and made my favorite (well, one of the top two) "welcome, spring!" recipes - ramps with bacon, fettucine, and an egg.
And even though it was a rainy, cold day, I knew spring was on its way. Proof? It's supposed to be 65 and sunny tomorrow. Finally!
Back in February I wrote an implementation of a new storage module for Paperclip that supports saving file attachments in a database table. My original implementation saved the file attachments in a separate database table, which was internally managed using a “has_many” relationship from the target model.
This month I decided to rewrite the code to use a simpler, single table approach. Instead of saving the files in a separate table, each file is saved in a BLOB column in the same table as the target model. This makes the database storage module easier and more intuitive to use, and moves it closer to the original intent of the Paperclip design.
Code: http://github.com/patshaughnessy/paperclip
New usage:
script/plugin install git://github.com/patshaughnessy/paperclip.git
has_attached_file :avatar, :storage => :databaseThe file will be stored in a column called [attachment name]_file (e.g. "avatar_file") by default. To specify a different BLOB column name, use :column, like this:
has_attached_file :avatar,
:storage => :database,
:column => 'avatar_data'
has_attached_file :avatar,
:storage => :database,
:styles => {
:medium => {
:geometry => "300x300>",
:column => 'medium_file'
},
:thumb => {
:geometry => "100x100>",
:column => 'thumb_file'
}
}
add_column :users, :avatar_file, :binary add_column :users, :avatar_medium_file, :binary add_column :users, :avatar_thumb_file, :binaryNote the migration for the "binary" column type will not work for LONGBLOBs in MySQL. Here's an example migration for MySQL:
execute 'ALTER TABLE users ADD COLUMN avatar_file LONGBLOB' execute 'ALTER TABLE users ADD COLUMN avatar_medium_file LONGBLOB' execute 'ALTER TABLE users ADD COLUMN avatar_thumb_file LONGBLOB'
{:select=>"id,name,avatar_file_name,avatar_content_type,..."}
If you’re using Rails 2.3, you can specify this as a default scope:
default_scope select_without_file_columns_for(:avatar)Or if you’re using Rails 2.1 or 2.2 you can use it to create a named scope:
named_scope :without_file_data, select_without_file_columns_for(:avatar)
/:relative_root/:class/:attachment/:id?style=:styleExample:
/app-root-url/users/avatars/23?style=originalThe idea here is that to retrieve a file from the database storage, you will need some controller's code to be executed. Once you pick a controller to use for downloading, you can add this line to generate the download action for the default URL/action (the plural attachment name), "avatars" in this example:
downloads_files_for :user, :avatarOr you can write a download method manually if there are security, logging or other requirements. If you prefer a different URL for downloading files you can specify that in the model; e.g.:
has_attached_file :avatar,
:storage => :database,
:url =>'/users/show_avatar/:id/:style'
For now, I’ve overwritten my code from February. I believe this implementation is cleaner and easier to use; if anyone did download and use my code from February let me know and I can help you migrate the file data from the separate table back to columns in primary table. I will be writing a script to do this for my own application.
When I have time in the next few days or weeks, I’ll post a sample application that illustrates all of these steps and shows exactly how to do all of this. If anyone has any questions or suggestions please let me know.
Back in February I wrote an implementation of a new storage module for Paperclip that supports saving file attachments in a database table. My original implementation saved the file attachments in a separate database table, which was internally managed using a “has_many” relationship from the target model.
This month I decided to rewrite the code to use a simpler, single table approach. Instead of saving the files in a separate table, each file is saved in a BLOB column in the same table as the target model. This makes the database storage module easier and more intuitive to use, and moves it closer to the original intent of the Paperclip design.
Code: http://github.com/patshaughnessy/paperclip
New usage:
script/plugin install git://github.com/patshaughnessy/paperclip.git
has_attached_file :avatar, :storage => :databaseThe file will be stored in a column called [attachment name]_file (e.g. "avatar_file") by default. To specify a different BLOB column name, use :column, like this:
has_attached_file :avatar,
:storage => :database,
:column => 'avatar_data'
has_attached_file :avatar,
:storage => :database,
:styles => {
:medium => {
:geometry => "300x300>",
:column => 'medium_file'
},
:thumb => {
:geometry => "100x100>",
:column => 'thumb_file'
}
}
add_column :users, :avatar_file, :binary add_column :users, :avatar_medium_file, :binary add_column :users, :avatar_thumb_file, :binaryNote the migration for the "binary" column type will not work for LONGBLOBs in MySQL. Here's an example migration for MySQL:
execute 'ALTER TABLE users ADD COLUMN avatar_file LONGBLOB' execute 'ALTER TABLE users ADD COLUMN avatar_medium_file LONGBLOB' execute 'ALTER TABLE users ADD COLUMN avatar_thumb_file LONGBLOB'
{:select=>"id,name,avatar_file_name,avatar_content_type,..."}
If you’re using Rails 2.3, you can specify this as a default scope:
default_scope select_without_file_columns_for(:avatar)Or if you’re using Rails 2.1 or 2.2 you can use it to create a named scope:
named_scope :without_file_data, select_without_file_columns_for(:avatar)
/:relative_root/:class/:attachment/:id?style=:styleExample:
/app-root-url/users/avatars/23?style=originalThe idea here is that to retrieve a file from the database storage, you will need some controller's code to be executed. Once you pick a controller to use for downloading, you can add this line to generate the download action for the default URL/action (the plural attachment name), "avatars" in this example:
downloads_files_for :user, :avatarOr you can write a download method manually if there are security, logging or other requirements. If you prefer a different URL for downloading files you can specify that in the model; e.g.:
has_attached_file :avatar,
:storage => :database,
:url =>'/users/show_avatar/:id/:style'
For now, I’ve overwritten my code from February. I believe this implementation is cleaner and easier to use; if anyone did download and use my code from February let me know and I can help you migrate the file data from the separate table back to columns in primary table. I will be writing a script to do this for my own application.
When I have time in the next few days or weeks, I’ll post a sample application that illustrates all of these steps and shows exactly how to do all of this. If anyone has any questions or suggestions please let me know.
I'm not really terribly gifted when it comes to gardening - but I don't kill everything I touch, either. Over the last couple of years, I've become braver and bolder when it comes to choosing and growing my herbs, and this year will, I think, be the best year yet!
Last week, I visited the Greenmarket with my friend Cristin to pick out my herbs for the year, and this morning I re-potted them - now all that's left to do is water and spritz them regularly, and make sure that they get plenty of that New York sunlight.
I branched a little out of my usual thyme-basil-sage trinity this year, adding some lemon basil, lemon thyme (which smells absolutely insane) and chervil (which I can never, ever find at the grocery store) to the mix.
Wish me luck!From left to right, we have sage, sweet basil, lemon basil, chervil, thyme, lemon thyme.
A woman who drinks Manhattans, especially a younger woman, is someone to be reckoned with.
I looked at the calendar today and realized something pretty damn exciting: I have all of next week off from work! I carried over four weeks of vacation from 2008, which means I have a total of nine weeks for 2009. I'll never be able to get through all of it (and, yes, I know, I am wicked spoiled by the vacation policy of my current employer), but I have to use those four weeks or I'll lose them forever!
And, so, back in January, I decided to kick things off with a week-long staycation here in New York. I have some travel planned for later this year (Ohio, Napa and possibly France), but this week will be totally and completely about spending time with my city.
One of the craziest things about New Yorkers is that we tend to spend so much time working our butts off (we have to, if we want to afford the rents) that we rarely take time to pause and explore our city. I can't remember the last time I just wandered around the Village, browsing through shops, or struck out on an exploration of a new neighborhood in Brooklyn, or spent hours wandering the Metropolitan Museum of Art.
Next week, I will spend my time doing the kinds of things I moved here to do - and I'd love to hear if you have any ideas for me to try! Here's a few things that are already on the list...
- Tickets to 9 to 5 for Tuesday night - Allison Janney FTW!
- Banh Mi sandwich
- Dinner or lunch at Marlow & Sons
- Shopping for furniture/house stuff in Williamsburg
- Lots and lots of Greenmarket-based cooking
- Lunch and shopping in Chinatown
- An outing to Stone Barns
- Lobster roll at Pearl Oyster Bar
What am I missing, people? What do you want to see? Let me know!
When Pegu Club opened on Houston Street in 2005, New York's cocktail renaissance was in its prime. Milk & Honey and Flatiron Lounge had been slinging classic, perfectly crafted concoctions for a few years already, Little Branch opened that same year, and hot on Pegu's heels came Death & Company, followed by PDT.
Almost four years into its young life, Pegu has two personalities - sedate, sophisticated cocktail lounge, and obnxiously loud SoHo hotspot. The former is in effect on weeknights and early on the weekends, while the latter is on impenetrable display on Friday and Saturday evenings.
If you go on a weeknight, though, the emphasis is still on the cocktails, which are as good as ever.
Last Wednesday, after battling the rain and in the delightfully, intellectually bawdy company of Louisa and Deidre, I enjoyed a few of Pegu's creations - one was an old favorite, and two were new (to me, at least). First up, a Fleur de Paradis: gin, Champagne, St. Germain (an elderflower liquer) and lemon juice. To. Die. For. The gin and champagne nicely balanced out the sweet, floral St. Germain, and the lemon juice kept things nice and tart. Plus, it was garnished with pansies!
Next up was a delicious take on my go-to, the Manhattan. This version is called the Little Italy, and features the traditional rye and sweet vermouth, but adds a dash of Cynar, an artichoke-flavored liqueur (which lends some savory complexity). Served up with two delightfully boozy cherries, it's a Manhattan for people who find Manhattans a bit too sweet.
Finally, to finish things off, I went for a Zelda, named for Zelda Fitzgerald, and appropriately tart and tangy. Gin, muddled cherries (which lent an opaque, pink color) and lemon - heaven on earth, and a nice contrast to the rich bitterness of the Little Italy. Don't let the girly color fool you - this drink is all woman.
"The Power of Less" - was the theme in the Web 2.0 Expo this time. The theme couldn't have been more apt given that the world is facing it's worst ever financial crisis since the Great Depressions in 1930s.
Focus was to present ideas on how to leverage web as an platform, introduce lightweight tools, improve user experience, help organizations to maximize resources and streamline productivity.
There are a number of interesting discussions, given below are my personal picks..
- Web 2.0 - Five Years on - by Tim O'Reilly (O'Reilly Media)
- The Year of Mobile Computer - by Anssi Vanjoki (Nokia)
- Building your First Android experience - by Tony Hillerson
http://assets.en.oreilly.com/1/event/22/Building%20Your%20First%20Android%20Experience%20Presentation.pdf
- Designing Social interfaces: Principles best practices and patterns for designing social web - by Erin Malone (Tangible UX), Christian Crumlish (Yahoo!)
So, it's been kind of an exciting few days for me. My apartment has been featured on a couple of design blogs, and since I'm a bit of an interior design nerd, that makes me very, very happy.
The lovely and amazing Ana has posted a full-on house tour on her blog, Rearranged Design. Given some of the ridiculously beautiful interiors she's featured in the past (click here for a full set of her house tours), I am very flattered that she asked me to share my home with her readers.
Additionally, as part of their small space month, Apartment Therapy posted about my use of a large bookshelf as a room divider in my teeny tiny (350 square feet, people!) studio.
Finally, if you haven't gotten enough, you can go check out my entire apartment (including the family photo-bedecked bathroom and tons of kitchen shots) on Flickr.
Hopefully, these photos will convince all the doubters, once and for all, that you really can entertain in any space. I have dinner parties for up to six people in this apartment - and if I can do it, so can you. Seriously!
By the way, most of these photos were taken by the incredibly talented Miya Hirabayashi, my friend and fellow design nerd. Miya is a great artist in her own right - check out her blog to see some of her amazing graphic design work. She rocks, doesn't she?
After my last post, i was wondering if really complex programs/systems/applications could be written using simple, easy-to-understand code. Code that does not span more than 10 lines a method. The most complex program that i could think of to experiment with was a Chess server – an app that would evaluate a position and suggest the [...]
This morning, looking through my files for the photos of the ramps stand, I found a whole bunch of meals I made last spring, when the Greenmarket was in full swing and onions, eggs and herbs were spilling out of my shopping bags.
It's been a while since I felt seized by the urge to cook (as opposed to cooking for a purpose or reason, such as - you know - feeding myself), but I can feel it coming back now. I can't wait to make some of these recipes, and try out some new ones, too.
Links to the recipes pictured above:
It's official - spring is here! The first ramps of the season (well, the first ones that didn't head right for the restaurants) are here! Chances are they're already sold out, but if I have any luck when I visit the market this afternoon, I'll let you know!
Via Lucy's Greenmarket Report.
One of Google's Open Source projects, Eyes-Free, is enabling eyes-free use of mobile devices running Android. Now the glassy screen of a touch-sreen phone will be accessible to the blind. The phone will also be useful to anyone who may need to operate a phone without looking at the screen. Hopefully, that doesn't translate into more careless driving out there!
I just updated my customized version of the auto_complete plugin to allow you to provide a named scope to auto_complete_for, in order to filter the auto_complete pick list options differently than the plugin does by default. The updated code is now on github:
http://github.com/patshaughnessy/auto_complete
This is based on the ideas from my last post, Andrew Ng’s original post and my friend Alex’s suggestion to use named scopes instead of manually modifying the find options. Here’s an example of how to use it taken from the auto_complete sample app I posted in January:
class Task < ActiveRecord::Base
belongs_to :project
named_scope :by_project,
lambda { |project_name| {
:include => :project,
:conditions => [ "projects.name = ?", project_name ]
} }
endauto_complete_for :task, :name do | items, params | items.by_project(params['project']) end
<% fields_for_task task do |f| -%>
…
<%= f.text_field_with_auto_complete :task,
:name,
{},
{
:method => :get,
:with =>"value + '&project=' + $F('project_name')"
} %>
…
<% end -%>So how does this work? Let’s take a look at my new implementation of auto_complete_for:
def auto_complete_for(object, method, options = {})
define_method("auto_complete_for_#{object}_#{method}") do
model = object.to_s.camelize.constantize
find_options = {
:conditions => [ "LOWER(#{model.quoted_table_name}.#{method}) LIKE ?",
'%' + params[object][method].downcase + '%' ],
:order => "#{model.quoted_table_name}.#{method} ASC",
:limit => 10 }.merge!(options)
@items = model.scoped(find_options)
@items = yield(@items, params) if block_given?
render :inline => "<%= auto_complete_result @items, '#{method}' %>"
end
end
One minor change I made here was to call “quoted_table_name” on the given model to specify the table name in the SQL generated to retrieve the auto complete results later. This was needed in case, like in my sample application, the controller specifies a named scope that joins with another table containing columns with the same name as the target model. If this isn’t the case, adding the table name to the SQL is harmless.
However, the most important 2 lines here are in bold: first we call a function called “scoped” to create an anonymous named scope based on the default auto_complete options “find_options:”
@items = model.scoped(find_options)
The exciting thing about this line, which Alex explained in his blog post, is that the use of named scopes delays the corresponding SQL statement from being executed until later when we actually access the query results in auto_complete_result. What happens instead is that an ActiveRecord:: NamedScope::Scope object is created, containing a temporary cache of the find options.
A good way to understand how this works is to try it in the Rails console:
complex-form-examples pat$ ./script/console
Loading development environment (Rails 2.1.0)
>> find_options = {
?> :conditions => [ "LOWER(`tasks`.name) LIKE ?", '%t%' ],
?> :order => "`tasks`.name ASC",
?> :limit => 10 }
=> {:order=>"name ASC", :conditions=>["LOWER(name) LIKE ?", "%t%"], :limit=>10}
>> Task.scoped(find_options)
=> [#<Task id: 4, project_id: 2, name: "Task 2a", due_at: nil, created_at: "2009-04-02 16:21:54",
updated_at: "2009-04-02 16:21:54">, #<Task id: 5, project_id: 2, name: "Task 2b", due_at: nil,
created_at: "2009-04-02 16:21:54", updated_at: "2009-04-02 16:21:54">, #<Task id: 6, project_id: 2,
name: "Task 2c", due_at: nil, created_at: "2009-04-02 16:21:54", updated_at: "2009-04-02
16:21:54">, #<Task id: 1, project_id: 1, name: "Task One", due_at: nil, created_at: "2009-04-02
16:21:30", updated_at: "2009-04-02 16:21:30">, #<Task id: 3, project_id: 1, name: "Task
Three", due_at: nil, created_at: "2009-04-02 16:21:30", updated_at: "2009-04-02 16:21:30">,
#<Task id: 2, project_id: 1, name: "Task Two", due_at: nil, created_at: "2009-04-02 16:21:30",
updated_at: "2009-04-02 16:21:30">]
Wait a minute! I thought the actual SQL execution was delayed by named scopes until I needed to access the results? Here the console has already displayed the query results, so the SQL statement must have been executed already. How and why did this happen? In this case, when you enter an expression into the Rails console and press ENTER, the expression is evaluated and then the “inspect” method is called on it. The problem is that the named scopes implementation has delegated the “inspect” method to another method, which executes the SQL statement and loads the query results.
We can use a trick in the console to open the ActiveRecord::NamedScope::Scope class and override the inspect method so the SQL is not executed, and prove to ourselves that “scoped()” actually does return a named scope object without executing the SQL statement:
>> module ActiveRecord
>> module NamedScope
>> class Scope
>> def inspect
>> super # Avoids calling ActiveRecord::Base.find and calls Object.inspect
>> end
>> end
>> end
>> end
=> nil
>> Task.scoped(find_options)
=> #<ActiveRecord::NamedScope::Scope:0x21e65d4
@proxy_options={:conditions=>["LOWER(`tasks`.name) LIKE ?", "%t%"],
:order=>"`tasks`.name ASC", :limit=>10},
@proxy_scope=Task(id: integer, project_id: integer, name: string,
due_at: datetime, created_at: datetime, updated_at: datetime)>
So here we can see that “scoped” returns an ActiveRecord::NamedScope::Scope object, and that it has two interesting instance variables: proxy_scope and proxy_options. The first of these, proxy_options, contains the find options that were passed into the scoped() function, or into the “named_scope” declaration in your model. The second value, proxy_scope, indicates the parent scope or context in which this named scope object’s SQL statement should be run. In this example, that is the Task model itself. The named scope object is essentially a cache of the query options that will be user later when the query is executed.
Let’s see how this works in the auto_complete plugin. Back again to the new implementation of auto_complete_for, we have:
@items = model.scoped(find_options) @items = yield(@items, params) if block_given?
The first line generates a ActiveRecord::NamedScope::Scope object, which is then passed into the block provided by the controller code, if any. Let’s take a look at my sample app’s implementation of the controller:
auto_complete_for :task, :name do | items, params | items.by_project(params['project']) end
This is a good example of the second very cool feature of named scopes: that they are composable… in other words, that two or more named scopes can be combined together to form a single SQL statement that is executed only once! Let’s return to the same Rails console session with our redefined “inspect” method and see if we can understand a bit more about this:
>> Task.scoped(find_options).by_project 'Project One'
=> #<ActiveRecord::NamedScope::Scope:0x21d1864
@proxy_options={
:conditions=>["projects.name = ?", "Project One"],
:include=>:project},
@proxy_scope=
#<ActiveRecord::NamedScope::Scope:0x21d1a30
@proxy_options={
:conditions=>["LOWER(`tasks`.name) LIKE ?", "%t%"],
:order=>"`tasks`.name ASC",
:limit=>10},
@proxy_scope=Task(id: integer, project_id: integer, name: string, due_at: datetime, created_at: datetime, updated_at: datetime)
>
>
Now we can see that calling scoped(find_options).by_project just returns a chain of two named scopes: the first scope object with @proxy_scope set to the second one, and the second one with @proxy_scope set to the base model class. Later when this SQL query is executed, the code in NamedScope and ActiveRecord::Base will simply walk this chain of objects, accumulate the options into a single hash, convert the hash to SQL and execute it.
In auto_complete_for after the controller’s block returns, the “@items” value in auto_complete_for above is set to the parent/child named scope chain of objects, and then passed into auto_complete_result:
render :inline => "<%= auto_complete_result @items, '#{method}' %>"
Inside of auto_complete_result the @items value is used as if it were an array… like this:
def auto_complete_result(entries, field, phrase = nil)
return unless entries
items = entries.map { |entry|
content_tag("li",
phrase ? highlight(entry[field], phrase) : h(entry[field]))
}
content_tag("ul", items.uniq)
end
… to generate the HTML needed for the Prototype library’s implementation of the auto complete drop down box. The interesting thing here is that the SQL statement is executed as soon as the call to “map” is executed, accessing the elements of the “@items” array. This works because the ActiveRecord::NamedScope::Scope class redirects or delegates the [] and other array methods to ActiveRecord::Base.find. Here's the single SQL that is executed with the combined, accumulated query options:
SELECT `tasks`.`id` AS t0_r0, `tasks`.`project_id` AS t0_r1, `tasks`.`name` AS t0_r2, `tasks`.`due_at` AS t0_r3, `tasks`.`created_at` AS t0_r4, `tasks`.`updated_at` AS t0_r5, `projects`.`id` AS t1_r0, `projects`.`name` AS t1_r1, `projects`.`created_at` AS t1_r2, `projects`.`updated_at` AS t1_r3 FROM `tasks` LEFT OUTER JOIN `projects` ON `projects`.id = `tasks`.project_id WHERE ((projects.name = 'Project One') AND (LOWER(`tasks`.name) LIKE '%t%')) ORDER BY `tasks`.name ASC LIMIT 10
In fact, in the original version of the auto_complete plugin before my changes to it for named scopes, the value passed into auto_complete_result was a simple array. The fact that named scopes are used now is entirely hidden from this code!
One last note here about named scope: as described in a comment in named_scope.rb from the Rails source code, the “proxy_options” method provides a convenient way to test the behavior of named scopes without actually checking the results of an actual SQL query. Here’s one of the tests I wrote for my new version of auto_complete_for:
def test_default_auto_complete_for
get :auto_complete_for_some_model_some_field,
:some_model => { :some_field => "some_value" }
default_auto_complete_find_options = @controller.items.proxy_options
assert_equal "`some_models`.some_field ASC",
default_auto_complete_find_options[:order]
assert_equal 10, default_auto_complete_find_options[:limit]
assert_equal ["LOWER(`some_models`.some_field) LIKE ?", "%some_value%"],
default_auto_complete_find_options[:conditions]
end
Since I didn’t want to go to the trouble of setting up an actual in-memory database using SQLite, or to introduce mocha or some other mocking framework to the auto_complete tests, all I had to do was just call @controller.items.proxy_options and check that the find options are as expected. (I also had to expose “items” in the mock controller using attr_reader.) I have another test that checks that the controller’s block is called and it’s named scope options are present as expected… this test uses the proxy_scope method to walk up the chain to the parent named scope and get it’s proxy_options. See auto_complete_test.rb for details.
I just updated my customized version of the auto_complete plugin to allow you to provide a named scope to auto_complete_for, in order to filter the auto_complete pick list options differently than the plugin does by default. The updated code is now on github:
http://github.com/patshaughnessy/auto_complete
This is based on the ideas from my last post, Andrew Ng’s original post and my friend Alex’s suggestion to use named scopes instead of manually modifying the find options. Here’s an example of how to use it taken from the auto_complete sample app I posted in January:
class Task < ActiveRecord::Base
belongs_to :project
named_scope :by_project,
lambda { |project_name| {
:include => :project,
:conditions => [ "projects.name = ?", project_name ]
} }
endauto_complete_for :task, :name do | items, params | items.by_project(params['project']) end
<% fields_for_task task do |f| -%>
…
<%= f.text_field_with_auto_complete :task,
:name,
{},
{
:method => :get,
:with =>"value + '&project=' + $F('project_name')"
} %>
…
<% end -%>So how does this work? Let’s take a look at my new implementation of auto_complete_for:
def auto_complete_for(object, method, options = {})
define_method("auto_complete_for_#{object}_#{method}") do
model = object.to_s.camelize.constantize
find_options = {
:conditions => [ "LOWER(#{model.quoted_table_name}.#{method}) LIKE ?",
'%' + params[object][method].downcase + '%' ],
:order => "#{model.quoted_table_name}.#{method} ASC",
:limit => 10 }.merge!(options)
@items = model.scoped(find_options)
@items = yield(@items, params) if block_given?
render :inline => "<%= auto_complete_result @items, '#{method}' %>"
end
end
One minor change I made here was to call “quoted_table_name” on the given model to specify the table name in the SQL generated to retrieve the auto complete results later. This was needed in case, like in my sample application, the controller specifies a named scope that joins with another table containing columns with the same name as the target model. If this isn’t the case, adding the table name to the SQL is harmless.
However, the most important 2 lines here are in bold: first we call a function called “scoped” to create an anonymous named scope based on the default auto_complete options “find_options:”
@items = model.scoped(find_options)
The exciting thing about this line, which Alex explained in his blog post, is that the use of named scopes delays the corresponding SQL statement from being executed until later when we actually access the query results in auto_complete_result. What happens instead is that an ActiveRecord:: NamedScope::Scope object is created, containing a temporary cache of the find options.
A good way to understand how this works is to try it in the Rails console:
complex-form-examples pat$ ./script/console
Loading development environment (Rails 2.1.0)
>> find_options = {
?> :conditions => [ "LOWER(`tasks`.name) LIKE ?", '%t%' ],
?> :order => "`tasks`.name ASC",
?> :limit => 10 }
=> {:order=>"name ASC", :conditions=>["LOWER(name) LIKE ?", "%t%"], :limit=>10}
>> Task.scoped(find_options)
=> [#<Task id: 4, project_id: 2, name: "Task 2a", due_at: nil, created_at: "2009-04-02 16:21:54",
updated_at: "2009-04-02 16:21:54">, #<Task id: 5, project_id: 2, name: "Task 2b", due_at: nil,
created_at: "2009-04-02 16:21:54", updated_at: "2009-04-02 16:21:54">, #<Task id: 6, project_id: 2,
name: "Task 2c", due_at: nil, created_at: "2009-04-02 16:21:54", updated_at: "2009-04-02
16:21:54">, #<Task id: 1, project_id: 1, name: "Task One", due_at: nil, created_at: "2009-04-02
16:21:30", updated_at: "2009-04-02 16:21:30">, #<Task id: 3, project_id: 1, name: "Task
Three", due_at: nil, created_at: "2009-04-02 16:21:30", updated_at: "2009-04-02 16:21:30">,
#<Task id: 2, project_id: 1, name: "Task Two", due_at: nil, created_at: "2009-04-02 16:21:30",
updated_at: "2009-04-02 16:21:30">]
Wait a minute! I thought the actual SQL execution was delayed by named scopes until I needed to access the results? Here the console has already displayed the query results, so the SQL statement must have been executed already. How and why did this happen? In this case, when you enter an expression into the Rails console and press ENTER, the expression is evaluated and then the “inspect” method is called on it. The problem is that the named scopes implementation has delegated the “inspect” method to another method, which executes the SQL statement and loads the query results.
We can use a trick in the console to open the ActiveRecord::NamedScope::Scope class and override the inspect method so the SQL is not executed, and prove to ourselves that “scoped()” actually does return a named scope object without executing the SQL statement:
>> module ActiveRecord
>> module NamedScope
>> class Scope
>> def inspect
>> super # Avoids calling ActiveRecord::Base.find and calls Object.inspect
>> end
>> end
>> end
>> end
=> nil
>> Task.scoped(find_options)
=> #<ActiveRecord::NamedScope::Scope:0x21e65d4
@proxy_options={:conditions=>["LOWER(`tasks`.name) LIKE ?", "%t%"],
:order=>"`tasks`.name ASC", :limit=>10},
@proxy_scope=Task(id: integer, project_id: integer, name: string,
due_at: datetime, created_at: datetime, updated_at: datetime)>
So here we can see that “scoped” returns an ActiveRecord::NamedScope::Scope object, and that it has two interesting instance variables: proxy_scope and proxy_options. The first of these, proxy_options, contains the find options that were passed into the scoped() function, or into the “named_scope” declaration in your model. The second value, proxy_scope, indicates the parent scope or context in which this named scope object’s SQL statement should be run. In this example, that is the Task model itself. The named scope object is essentially a cache of the query options that will be user later when the query is executed.
Let’s see how this works in the auto_complete plugin. Back again to the new implementation of auto_complete_for, we have:
@items = model.scoped(find_options) @items = yield(@items, params) if block_given?
The first line generates a ActiveRecord::NamedScope::Scope object, which is then passed into the block provided by the controller code, if any. Let’s take a look at my sample app’s implementation of the controller:
auto_complete_for :task, :name do | items, params | items.by_project(params['project']) end
This is a good example of the second very cool feature of named scopes: that they are composable… in other words, that two or more named scopes can be combined together to form a single SQL statement that is executed only once! Let’s return to the same Rails console session with our redefined “inspect” method and see if we can understand a bit more about this:
>> Task.scoped(find_options).by_project 'Project One'
=> #<ActiveRecord::NamedScope::Scope:0x21d1864
@proxy_options={
:conditions=>["projects.name = ?", "Project One"],
:include=>:project},
@proxy_scope=
#<ActiveRecord::NamedScope::Scope:0x21d1a30
@proxy_options={
:conditions=>["LOWER(`tasks`.name) LIKE ?", "%t%"],
:order=>"`tasks`.name ASC",
:limit=>10},
@proxy_scope=Task(id: integer, project_id: integer, name: string, due_at: datetime, created_at: datetime, updated_at: datetime)
>
>
Now we can see that calling scoped(find_options).by_project just returns a chain of two named scopes: the first scope object with @proxy_scope set to the second one, and the second one with @proxy_scope set to the base model class. Later when this SQL query is executed, the code in NamedScope and ActiveRecord::Base will simply walk this chain of objects, accumulate the options into a single hash, convert the hash to SQL and execute it.
In auto_complete_for after the controller’s block returns, the “@items” value in auto_complete_for above is set to the parent/child named scope chain of objects, and then passed into auto_complete_result:
render :inline => "<%= auto_complete_result @items, '#{method}' %>"
Inside of auto_complete_result the @items value is used as if it were an array… like this:
def auto_complete_result(entries, field, phrase = nil)
return unless entries
items = entries.map { |entry|
content_tag("li",
phrase ? highlight(entry[field], phrase) : h(entry[field]))
}
content_tag("ul", items.uniq)
end
… to generate the HTML needed for the Prototype library’s implementation of the auto complete drop down box. The interesting thing here is that the SQL statement is executed as soon as the call to “map” is executed, accessing the elements of the “@items” array. This works because the ActiveRecord::NamedScope::Scope class redirects or delegates the [] and other array methods to ActiveRecord::Base.find. Here's the single SQL that is executed with the combined, accumulated query options:
SELECT `tasks`.`id` AS t0_r0, `tasks`.`project_id` AS t0_r1, `tasks`.`name` AS t0_r2, `tasks`.`due_at` AS t0_r3, `tasks`.`created_at` AS t0_r4, `tasks`.`updated_at` AS t0_r5, `projects`.`id` AS t1_r0, `projects`.`name` AS t1_r1, `projects`.`created_at` AS t1_r2, `projects`.`updated_at` AS t1_r3 FROM `tasks` LEFT OUTER JOIN `projects` ON `projects`.id = `tasks`.project_id WHERE ((projects.name = 'Project One') AND (LOWER(`tasks`.name) LIKE '%t%')) ORDER BY `tasks`.name ASC LIMIT 10
In fact, in the original version of the auto_complete plugin before my changes to it for named scopes, the value passed into auto_complete_result was a simple array. The fact that named scopes are used now is entirely hidden from this code!
One last note here about named scope: as described in a comment in named_scope.rb from the Rails source code, the “proxy_options” method provides a convenient way to test the behavior of named scopes without actually checking the results of an actual SQL query. Here’s one of the tests I wrote for my new version of auto_complete_for:
def test_default_auto_complete_for
get :auto_complete_for_some_model_some_field,
:some_model => { :some_field => "some_value" }
default_auto_complete_find_options = @controller.items.proxy_options
assert_equal "`some_models`.some_field ASC",
default_auto_complete_find_options[:order]
assert_equal 10, default_auto_complete_find_options[:limit]
assert_equal ["LOWER(`some_models`.some_field) LIKE ?", "%some_value%"],
default_auto_complete_find_options[:conditions]
end
Since I didn’t want to go to the trouble of setting up an actual in-memory database using SQLite, or to introduce mocha or some other mocking framework to the auto_complete tests, all I had to do was just call @controller.items.proxy_options and check that the find options are as expected. (I also had to expose “items” in the mock controller using attr_reader.) I have another test that checks that the controller’s block is called and it’s named scope options are present as expected… this test uses the proxy_scope method to walk up the chain to the parent named scope and get it’s proxy_options. See auto_complete_test.rb for details.
This morning, Thomas and I were discussing how somethings can take longer than usual in our organization, like design and security reviews.
We could continue as-is but what's the fun in that...
So, I challenged him to finish his project's security review in one session. Doing this in only one session will earn him EE (exceeds expectations) on this effort.
What should Thomas think about? What's your advice for him?