r_c, meet RSpec 2

Posted by james
on Friday, June 20

Jonathan Barket just sent me a patch that gets resource_controller's scaffold_resource generator to spit out RSpec specs! I quickly merged it in to master, and wanted to announce it to the world, since it's something I've been hoping to get added to the plugin for a while.

r_c's scaffold_resource generator now supports vanilla test/unit, Shoulda, and RSpec for tests, and erb, and haml for templating. It will sense which of those plugins or gems you have installed in your app, and generate accordingly — it's absolutely 0 configuration.

So, if you're an RSpec user, grab the latest master from github, and get generating! :)

Introducing has_browser: Parameterized browse interfaces for your AR models. 8

Posted by james
on Monday, May 19

Pretty soon after starting to use has_finder, I needed to write a browse interface. That is, given a set of parameters, return all the models that match. With has_finder, it is possible to compartmentalize those parameters in to small chunks, each with names that make sense in your model's domain. You can then chain them together to get their combined scope. The only problem iswas calling them when you aren't aware, in advance, of which finders need to be called.

Having had this problem a few times, and being sick of solving it over and over again, I decided to extract a general solution from a project I'm working on.

has_browser

It's a simple plugin, with a simple syntax. Using the canonical blog example, let's imagine we want to create a browse interface to posts. We'd want the user to be able to browse by category, author, or tags, but not to be able to access any of the other finders on the Post model for obvious security reasons. To set up has_browser, we'd do something like this:

has_browser :category, :tags, :author

Then, assuming the has_finders are already written, the posts can be browsed as follows:

Post.browse(:category => 'Testing', :tags => 'activerecord', :author => 'james')

In that example, each of the finders requires an argument; has_browser also supports finders that don't. As long as the argumentless finder is present in the browse hash, it will be called:

has_browser :category, :tags, :author, :without_args => [:order_by_date, :order_by_number_of_comments]

Post.browse(:category => 'Testing', :tags => 'activerecord', :author => 'james', :order_by_number_of_comments => 'true')

Browse can also be called from association_proxies. For a multi-blog platform, we could easily restrict browsing of posts to the current blog:

@blog.posts.browse(:category => 'Testing', :tags => 'activerecord', :author => 'james', :order_by_number_of_comments => 'true')

Since has_browser returns the same proxy as has_finder, it is possible to further restrict the results of a browse by chaining finders after the browse call. With our blog, for example, we'd probably want to restrict browsing to published posts.

@blog.posts.browse(:category => 'Testing', :tags => 'activerecord', :author => 'james', :order_by_number_of_comments => 'true').published

Note: It is not possible to chain finders before the browse call.

Finally, like has_finder, has_browser is compatible with will_paginate out of the box.

Get It

Releases of has_browser will be available as a gem, which you can freeze in to your rails app (init.rb included):

$ sudo gem install has_browser

Development, of course, is at github.

Experimental resource_controller Feature: Custom Action Discovery 4

Posted by james
on Wednesday, May 07

A few weeks ago, Nate Wiger emailed me to ask whether I was interested in a patch for r_c. Evidently, it is possible to determine which controller actions need to be created by examining the routes that are pointing to it. Nate had also noticed that most controller actions follow a pattern:

  1. Load or build an object or a collection (i.e. Post.find(1) or Post.build(params[:post])).
  2. Optionally do something with the object(s) (i.e. @post.save).
  3. Optionally set the flash.
  4. Render a response.

Put those pieces together, and it's possible to create a controller abstraction that is aware of what's routed to it, and sets up a set of sensible defaults, even for custom actions. As you might have guessed, Nate and I have done exactly that.

For the 7 default RESTful actions, things haven't changed much. The only real difference is that you can now change step 2 (refer to list above), by passing a block to action.action.

create.action do
  @post.my_custom_save_method
end

The action block's return value determines whether the success or failure callbacks (i.e. success.flash vs failure.wants) are triggered.

For custom actions, you'll now be able to use the same DSL you're used to for customizing default actions. For example, if you had a custom action called update_collection, you might put something like this in your routes:

map.connect '/posts', :controller => 'posts', :action => 'update_collection', :conditions => {:method => :put}

Based on that route, r_c will automatically be aware of your action, and create a basic skeleton for it, following the pattern of the list above. For a collection update method, you might want to customize your action like this:

update_collection do
  before { @posts.each { |p| p.update_attributes params[:posts] } }
  update_collection.action { @posts.save }
end

That's all you'd have to do. r_c would automatically load the objects (including any parent, respecting polymorphism), and render using its internal helpers.

Note: It is also possible to modify loading by setting a block for the build accessor, which corresponds to step 1.

Highly Experimental

This is highly experimental software. I'm not sure how much I love it, since it's a bit on the magical side for my tastes. However, it is kinda neat to have your controller be aware of which actions to create, based on what is routed to it. I have been having a bit of fun playing around with this.

So, please give it a shot, and let us know how you like it on the mailing list.

Get it from github (checkout the automatic_route_discovery branch). If you aren't using git, you can get a tarball here.

Rails Training w/ James Golick & Other Rails Ninjas 0

Posted by james
on Tuesday, April 29

This past December, a friend of mine, Peter, wanted to improve his rails skills. I had been asked by a few people about teaching some rails training sessions, but wanted to give it a beta test first. When I was in Toronto over the holidays, Peter and I gave it a trial run, which went great. Since then, I've been dying to offer ruby / rails training to a wider audience.

How It'll Work

The sessions will be based on the format that I tried out in my beta session with Peter. We'll start by covering some advanced rails fundamentals, with time for questions, and plenty of time to go off on long tangents about whatever you might want to learn (like, how to write a plugin, or how some of the rails internals work). The remainder of the session will be spent coding.

During the coding portion of the sessions, the instructor(s) will pair program with each of the participants in a rotation. Think of it as an opportunity to work extremely closely with a rails expert. We'll be able to apply what we've learned in the earlier portion of the session. We'll work on your real problems; that way, we'll be teaching a custom course that's custom tailored to you, and you'll walk away from the experience with some code, to boot!

Participants will be asked to bring two projects: one in progress (or finished), and one idea.

  • The project in progress serves as a tool for analysis of where you're at, and what kind of coding practices you have in general. We'll work out some possible refactorings for that project, and talk about how you'd make use of advanced rails techniques to improve its readability, maintainability, etc.
  • Next, we'll work on your project idea. The rest of the session will be spent architecting, and actually coding this project. We'll help you pick the right plugin set, model your data, and anything else.

Logistics

  • I'd like to offer the first session some time in early July, most likely over a weekend.
  • The sessions will be held in Montreal, unless there's a compelling reason to offer them somewhere else, like, a group of participants who all live in the same city.
  • Price is still TBD; I've got some potential sponsors (which would offset the price for participants), but nothing confirmed yet. There would certainly be a discount for groups or companies.

Finally, I intend to keep the instructor to participant ratio extremely low to support plenty of one-on-one time. So, if there is sufficient interest, I'll get some of my fellow Montreal rails ninjas in as additional instructors. I've got some awesome people in mind. Who knows — you might even get scouted by one of the local rails shops!

If you're interested, send me an email (top right) for more info.

Introducing Action Messager: Dead simple IM notifications for your app! 5

Posted by james
on Sunday, April 06

Sometimes email is the wrong choice for webapp notifications. Our inboxes are becoming increasingly cluttered, and especially for those of us who carry PDAs, it can be a pain to scroll through twelve facebook notifications just to get to the mail that we actually need to read. What's more, email just isn't that great of a tool for receiving short messages, since you have to open them each individually. At the very least, it's nice to have another choice.

Particularly with shorter notifications, instant messaging can be a great alternative to email. The messages don't clutter up your inbox, or need to be opened individually. Most people already use it on a daily basis. The only problem is implementation.

ActionMessager

ActionMessager is a simple framework for creating IM-based notifiers. It is structured like ActionMailer, so it's got virtually zero learning curve for most rails developers. What's more, because the syntax is the same, it's pretty easy to create a delegate class that acts like your mailer, but sends IMs as well. That means drop-in replacement!

All you have to do start sending IM notifications to your users is subclass ActionMessager::Base. Then, create a method that sets an array of recipients, and returns the message you'd like to send:

class JabberNotifier < ActionMessager::Base
  def friendship_request(friendship_request)
    @recipients = friendship_request.receiver.jabber_contact
    
    "You have received a friendship request from #{friendship_request.sender.name}! Click here to accept or decline: #{friendship_request.url}"
  end
end

Then, wherever you'd like to send the notification:

JabberNotifier.deliver_friendship_request(friendship_request)

That's all there is to it.

Caveats

Currently, only jabber is supported. It is possible to access other IM services over jabber, but I'm not 100% clear how it works, and I don't yet have need for it, so that may or may not come later.

The bigger caveat, though, is speed. Sending a notification takes anywhere from 1-2s (whether you send 1 or 5). It may be possible to improve performance by using a different jabber library, and I'll probably investigate that in the near future. Even with a performance boost, though, you should still take your notifications out of the request cycle, by using something like workling to process them asynchronously.

Get It!

Get action_messager in gem form:

$ sudo gem install action_messager

Or get the source, from github:

$ git clone git://github.com/giraffesoft/action_messager.git

attribute_fu and jQuery shake hands 3

Posted by james
on Wednesday, April 02

This blog has been quiet of late, because I'm working on a couple of exciting, but still top-secret projects. Anyway, I recently moved one of those projects over to jQuery, because of its speed, syntax, and general awesomeness. Tonight, when I went to create a multi-model form with attribute_fu, I was stopped dead in my tracks by its heavy dependency on prototype. A few minutes of hacking later, a_f and jQuery are playing rather nicely together.

Get it from the jquery branch of a_f's git repo. If you aren't using git yet, this might be just the excuse you need to check it out! Or, download a tarball from github. After you install the plugin, you'll have to copy its one javascript dependency (jQuery templates) from the javascripts directory over in to your public/javascripts and require it in your layout.

Plugins I've Known and Loved #3: Ultrasphinx 22

Posted by james
on Monday, March 03

If you've ever implemented searching in your rails app, you probably noticed that it's a major hassle (unless, of course, you already know about Ultrasphinx). Ferret is probably the most commonly used indexing solution, but, it isn't anywhere near production ready. acts_as_solr, another option, is so full of show stopper bugs that it really isn't even worth bothering with. The worst part about both of those solutions is that they usually work great in your development environment. Try to put them in to production, though, and you're in for big problems.

Enter Ultrasphinx, by Evan Weaver. Sphinx is a super high performance search daemon, designed to suck information out of a mysql or pgsql database (though, it isn't limited to that). Normally, one would configure Sphinx with SQL queries that it uses to fetch the data. Apparently, the configuration file can be a major hassle to work with — I wouldn't know. Ultrasphinx provides you with a declarative API that it ultimately uses to generate the Sphinx configuration file for you, making the process quick and painless. A few lines of code, a couple of rake commands, and you're searching.

The Sphinx approach has several advantages over ferret, and acts_as_solr. First of all, Sphinx is extremely stable. They're nearing a 1.0 release, and its stability certainly merits that. You don't even really need to worry about monitoring the search daemon, because it just isn't going to crash. Also, if you're running any rails apps in production, you know that long running ActiveRecord callbacks can lead to your app performing very poorly. The ferret and solr solutions both rely on active record callbacks to inform the indexer of new data. Moreover, if the search daemon goes down for any period of time, or, say, the index becomes corrupted (ferret, I'm looking at you), your entire app is going to be down for the count. You set the Sphinx indexer to run every half hour on a cron job, and since it works with the db directly, its performance and stability characteristics have absolutely zero impact on your app.

Trying it Out

So, let's implement a search for our blog. First, we'll want to edit the paths to the index and logs in the default config file. Note that Ultrasphinx uses two types of config files: one that the programmer edits, and one that it generates; both are necessary on all machines that are accessing the index (seriously, not having the generated config on a slave machine caused me some trouble). Then, we'll want to declare our post model as indexed (note that you must declare the fields as strings, not symbols):

class Post < ActiveRecord::Base
  is_indexed :fields => ['title', 'body']
end

Then, we'll need to ask Ultrasphinx to generate a configuration file. You've got to re-run this rake task any time you make changes to the definition of your models' is_indexed declaration. Make sure to .gitignore (svn:ignore for people still stuck in svn land) the generated file in development. Evan recommends checking the production file in to version control, but I have it set to generate automatically on deploy with a cap recipe. That way, if I make changes to the indexing, I won't forget to re-generate the config file. All you have to do is run:

$ rake ultrasphinx:configure

Then, since it's our first time, we've got to run the indexer:

$ rake ultrasphinx:index

Finally, we'll need to start the search daemon:

$ rake ultrasphinx:daemon:start

Now, we can start searching.

@search = Ultrasphinx::Search.new(:query => params[:query])
@search.run
@search.results

So, it's really easy to build a basic search engine using Ultrasphinx. There are some gotchas, though.

Gotchas & Notes

  • More complex indexing with Ultrasphinx can be slightly more verbose, and SQL-focused than it would be with a solution that relies on AR callbacks to do its indexing.
  • Transforming data with Ruby is impossible; the data you index must be in your database (unless you use a stored procedure, which can actually be written in ruby if you're using pgsql, but I digress).
  • Ultrasphinx preloads your indexed models when it is initialized. So, if your models depend on any monkey patches that live in your app's lib directory, they must be loaded before the Ultrasphinx (in my experience, this has meant pluginizing my monkey patches). Because of the way that exceptions are caught in the preloading routine, you won't see the actual error that is stopping your model from loading. Instead, you'll just get a constant loading error, or a name error, or something. If you see something like that, and your models load fine without Ultrasphinx installed, look for dependency issues.
  • Ultrasphinx attempts to preload your indexed models using a regex that doesn't respect commenting (at the time of writing). If you're struggling with issues mentioned in the last gotcha, you'll probably try commenting out the is_indexed call to see whether that's what's causing the problem. That won't work. You can either delete the is_indexed call entirely when debugging, or pull from my git repo, where I've modified the regex to respect #-style commenting (but not the =begin/=end style).
  • If you see DivideByZeroErrors in production, it's probably because you're missing the generated configuration file on one or more of your app server machines.

Check it Out

You'll need to grab Sphinx. Then, the plugin...

Get it from svn:

$ svn export svn://rubyforge.org/var/svn/fauna/ultrasphinx/trunk vendor/plugins/ultrasphinx

Or pull from my git repo (for the change described in the gotchas section):

git clone git://github.com/giraffesoft/ultrasphinx.git

To Sum Up

Ultrasphinx is by far the most effective rails searching solution I've come across. Unlike most of the other options, the search daemon is incredibly stable, and the index never seems to become corrupted (I'm running it in a relatively high load production environment with absolutely zero trouble so far). Also, since Ultrasphinx doesn't rely on AR callbacks for indexing, your application isn't quite as coupled to your search daemon; if it dies, search functionality will break, but the rest of your app will still function. It's not without problems, and complex indexing can be trickier, but Ultrasphinx's stability and performance make the choice a no-brainer.

Plugins I've Known and Loved #2: has_finder 10

Posted by james
on Monday, February 25

Plugins I've Known and Loved started out as a would-be series of presentations at Montreal on Rails. I've been bad about getting to the second presentation, so I thought I'd continue PLIKaL here. No, I'm not going to talk about any of my plugins. Instead, over the next week or two, I'd like to tell you about some wonderful plugins that I only recently discovered (though, none of them are particularly new). Today — a plugin that belongs in everybody's toolbox: has_finder.

The basic idea of this school of plugins is creating reusable scopes for your models. For instance, if you had a blogging application with a post model, you might want to query for published posts. To that end, you might write something like this in your controller:

Post.find(:all, :conditions => {:published => true})

Hopefully, though, you're familiar with the skinny controller, fat model best practice, so you'd write it like this:

class Post < ActiveRecord::Base
  def self.published
    find(:all, :conditions => {:published => true})
  end
end

That's a lot better, but far from perfect. If you were to write several such methods, for example, you would not be able to combine them easily. So, looking for all published posts written by James would require writing a second method; not very DRY. Of course, ideally, you'd use an association proxy to accomplish that goal anyhow. That would work with our hand-written finder, but we'd lose all the benefits of the association_proxy like nested finds (...published.find(:all, :order => 'created_at DESC')), and we certainly wouldn't be able to chain two of them together (...published.order_by_recent). has_finder solves those problems, and more.

I Can Has Finder?

In order to reproduce the finder we wrote earlier, you'd write the following:

has_finder :published, :conditions => {:published => true}

Should you want to find all published blog posts by James, you could then make use of association proxy:

User.find_by_name('James').published.find(:all, :order => 'created_at DESC')

I have come across a good number of associations used for similar purposes, but defined on the associated model. For instance (omitting irrelevant details):

has_many :published_posts, :conditions => {:published => true}

Having discovered the wonders of has_finder, I now consider this to be an anti-pattern. The has_finder method is definitely DRYer. Far more importantly, however, the definition of what makes a post published (in our case, :published => true) belongs in the post model. The same rule applies to ordering. Following this best practice ensures there's only one point of change for refactoring, and that your models and their tests tell a more complete story about the data they represent.

Finally, has_finder supports parameterization of finders. You can wrap your conditions hash in a lambda that accepts an arbitrary number of arguments. Continuing with our blog post example, you might wish to query your posts table for all posts this week, this month, or this year, depending on the situation. To keep DRY, you could define your finder as follows:

has_finder :since, lambda { |date| {:conditions => ['created_at > ?', date]} }

Using it like this:

Post.since(1.week.ago)

To put it all together, let's query for all James's published posts in the last week:

User.find_by_name('James').posts.published.since(1.week.ago)

It reads just like a sentence!

Oh yeah, and all of this is compatible out of the box with will_paginate. I heard a rumor that it's going to be sucked down in to rails, too. Really, how could you not check it out (get it here)?

resource_controller 0.2: maintenance release - no more edge_compatible branch 2

Posted by james
on Friday, February 15

This is mainly a maintenance release.

  • The helpers have been broken out in to four separate files internally, to help with managing the deep nesting branch.
  • There have also been a few little refactorings in preparation for some new features to come shortly.
  • The biggest thing to note for users is that there is no longer an edge_compatible branch. Since rails 1.2.6 generates the same style of named routes as 2.0.2 (edit_tag_photo_path instead of tag_edit_photo_path), there is no longer a need to continue two separate streams of development (yay)!.
  • The generator has been updated to spit out the right filenames for templates (rhtml/haml vs html.erb/html.haml), and old-style migrations (t.column instead of t.string) for any users still stuck on 1.2.6, so the transition shouldn't be a problem.

Get It!

You can get the new version by exporting from svn:

svn export http://svn.jamesgolick.com/resource_controller/tags/stable vendor/plugins/resource_controller

Or, if you're using piston, you may need to switch to the new url if you were previously on edge compatible (this is untested, so it may be slightly wrong).

  piston switch http://svn.jamesgolick.com/resource_controller/tags/stable
  piston update

As always, everybody is encouraged to come join the discussion on the increasingly lively mailing list.

A Response to Dreamhost 1

Posted by james
on Saturday, January 12

Dreamhost sure stirred things up with their article rant about rails deployment and performance issues. Their complaint is roughly that they are having to work too hard to support rails; it needs performance and deployment improvements. Dreamhost voiced all of these complaints on the company's blog.

As they put it...

What I do have personal knowledge of is how difficult it can be to get a Rails application up and running and to keep it running. DreamHost has over 10 years of experience running applications in most of the most popular web programming frameworks and Rails has and continues to be one of the most frustrating.
...the solution from the Rails community itself was quite honestly, stupid.
That suggestion shows either a complete lack of understanding of how web hosting works, or an utter disregard for the real world.
Ruby on Rails needs to be a helluva lot faster.
Ruby on Rails needs to more or less work in ANY environment.

Forgive me for chopping up their arguments. If you have the time, please read the article. These quotes don't quite do it justice; they serve merely to provide the tone of the piece.

Frankly, I think DHH responded well: rails core team is there to serve their own purposes. Ruby on Rails doesn't need to do anything, despite what a Dreamhost blogger might suggest. The people hacking on rails core don't target platforms like Dreamhost's. So, in fact, rails is not designed to run on shared hosting.

It's not just shared hosting, either. Dreamhost is trying to support rails on massively oversold servers. Configuring rails in a shared environment (particularly under apache) is difficult; configuring, and maintaining their servers to support rails probably costs Dreamhost a lot of money. Rails doesn't perform well in that environment, which probably costs them even more money in support calls from frustrated customers. So, their profit margins on rails service are likely narrowing.

Why is that the rails community's problem to solve? Dreamhost has a business challenge. Rails deployment isn't perfect — it has more than its fair share of problems; but, there are plenty of rails apps running just fine in production. This blog, a rails app, runs on a 20$ per month plan from slicehost. The identical app barely ran at Dreamhost. It's anecdotal evidence, sure, but the point is that rails deployment (at least on this scale) is far from impossible. Dreamhost just can't seem to squeeze it in to their oversold offerings. And, they want somebody else to fix the problem for them.

There are plenty of shared hosting offerings available that support rails nicely. Those companies don't seem to be having any trouble creating an environment that works. None of them have published angry articles pointing the finger at "the rails community". But, I digress.

If it were an individual complaining — a noobie who was having a tough time deploying his rails app on a server he could afford — then perhaps there would be a reason to look at this differently. But, Dreamhost is a corporation; they are simply "...looking to capitalize on a framework that's driving a lot of demand...", to borrow some words from DHH.

If this had been a PC manufacturer, complaining loudly (and rudely) that linux doesn't run on cheap enough hardware for them to sell PCs for 50$/each... If Linksys had posted a rant on their company blog, complaining that linux needed to be faster, because their routers weren't performing well enough... If IBM had posted a rant on their company blog, complaining that the linux community showed "...either a complete lack of understanding of how web hosting works, or an utter disregard for the real world."... Everybody would have said the same thing: so, fix it.

Plenty of businesses are capitalizing on open source technologies. But, part of the deal in the real world is that when something doesn't work right, you might have to fix it yourself; but you can, and that's part of what's so great about open source. Engine Yard just hired the entire rubinius team (and some people to hack merb, so I hear). Linux kernel developments are facilitated in large part by corporate sponsorship, bounties, etc. The system works, because companies can solve their own problems, while leveraging the work of countless others. That's how it works in the real world.

Introducing AttributeFu: Multi-Model Forms Made Easy 33

Posted by james
on Wednesday, December 05

Ever needed to create a form that works with two models? If you have, you know that it's a major pain. It really seems like it should be easier, doesn't it? Powerful, SQL-free associations join our models; helpers build our forms in fewer keystrokes than it takes to call somebody a fanboy in a digg comment. But, as soon as you want to put the pieces together — as soon as you seek multi-model form bliss, the whole system falls apart.

Ryan Bates has popularized a few recipes for cooking up just such a dish (we all owe that man a debt of gratitude, don't we?). Great solutions (thanks Ryan!). There are only two flaws to speak of.

  • AJAX-friendliness: What is this - web1? Maybe we should all build our sites in frames, and pepper them with blinking text.
  • It's not a plugin: You mean I have to write code!? I thought rails was going to wash my dishes, walk my dog, and build my websites, while I sat outside on the porch smoking cigars, praying to DHH.

Being the compulsive pluginizer that I am, I simply couldn't resist this great opportunity.

attribute_fu

attribute_fu makes multi-model forms so easy you'll be telling all of your friends about it. Unless your friends are serious geeks, though, most of them will probably hang up on you. So, I recommend resisting that urge, unless you're looking for a good bedtime story.

To get started, enable attributes on your association (only has_many for now).

class Project < ActiveRecord::Base
  has_many :tasks, :attributes => true
end

Your model will then respond to task_attributes, which accepts a hash; the format of the which is a little bit different from what Ryan describes in his tutorials. Here's an example.

@project.task_attributes => { task_one.id => {:title => "Wash the dishes"},
                              task_two.id => {:title => "Go grocery shopping"},
                              :new => {
                                "0" => {:title => "Write a blog post about AttributeFu"}
                              }}

Follow the rules, though, and attribute_fu will shield you from the ugly details.

Form Helpers

The plugin comes with a set of conventions (IM IN UR PROJECT, NAMIN UR PARTIALZ). Follow them, and building associated forms is as easy as it should be. Take note of the partial's name, and the conspicuously absent fields_for call.

## _task.html.erb
<p class="task">
  <label for="task_title">Title:</label><br/>
  <%= f.text_field :title %>
</p>

Rendering one or more of the above forms is just one call away.

## _form.html.erb
<div id="tasks">
  <%= f.render_associated_form(@project.tasks) %>
</div>

You may want to add a few blank tasks to the bottom of your form — formerly requiring an extra line in your controller.

<%= f.render_associated_form(@project.tasks, :new => 3) %>

This being web2.0, removing elements from the form with Javascript (but your boss calls it AJAX) is a must.

## _task.html.erb
<p class="task">
  <label>Task</label><br/>
  <%= f.text_field :title %>
  <%= f.remove_link "remove" %>
</p>

Adding elements to the form via Javascript doesn't require any heavy lifting either.

## _form.html.erb
<%= f.add_associated_link('Add Task', @project.tasks.build) %>

It's that easy.

Last but not least (okay, maybe least), if you're one of those people who just has to be different - who absolutely refuses to follow the rules, you can still use attribute_fu (I guess...). See the RDoc for how.

Plugins, Plugins, Get Your Plugins

$ piston import http://svn.jamesgolick.com/attribute_fu/tags/stable vendor/plugins/attribute_fu

Check out the RDoc for more details.

Come join the discussion on the mailing list.

Noisy Backtraces Got You Down? 3

Posted by james
on Monday, December 03

Bothered by all the noise in my tests' backtraces, I was thrilled when I first saw a thread on the shoulda mailing list with some discussion around making them a little bit easier on the eyes. Assuming that creating such a filter would be a long and tedious process of monkey-patching test/unit, I forgot about the idea, assuming the job better left for somebody with more time to spare than myself.

When Dan Croak revived the thread with some sample code, cooked up at a Boston.rb hackfest, it occurred to me that the job was far more manageable than I had originally conceived. I quickly fired Dan an email asking whether he'd be interested in a pluginization of their concept. With a resounding yes! from Dan, we set off to create quiet_stacktracebacktrace.

The rest of this post is cross-posted on GIANT ROBOTS

90% of this typical backtrace will not help you hunt and kill bad code:

1) Failure:
test: logged in on get to index should only show projects for the user's account. (ProjectsControllerTest)
[/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:48:in `assert_block'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:500:in `_wrap_assertion'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:46:in `assert_block'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:63:in `assert'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:495:in `_wrap_assertion'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:61:in `assert'
test/functional/projects_controller_test.rb:31:in `__bind_1196527660_342195'
/Users/james/Documents/railsApps/projects/vendor/plugins/shoulda/lib/shoulda/context.rb:98:in `call'
/Users/james/Documents/railsApps/projects/vendor/plugins/shoulda/lib/shoulda/context.rb:98:in `test: logged in on get to index should only show projects for the user's account. '
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testcase.rb:78:in `__send__'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testcase.rb:78:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `run_suite'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:67:in `start_mediator'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:41:in `start'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:216:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:278
test/functional/projects_controller_test.rb:36]:
one or more projects shown does not belong to the current user's account.
&lt;false&gt; is not true.

Noisy backtraces must be ruthlessly silenced like political dissidents in Stalinist Russia. This much is clear.

Quiet Backtrace

Install the gem:

  sudo gem install quietbacktrace

Require quietbacktrace:

## test_helper.rb
require 'quietbacktrace'

Run your Test::Unit tests:

1) Failure:
test: logged in on get to index should only show projects for the user's account. (ProjectsControllerTest)
[test/functional/projects_controller_test.rb:31
vendor/plugins/shoulda/lib/shoulda/context.rb:98
vendor/plugins/shoulda/lib/shoulda/context.rb:98
test/functional/projects_controller_test.rb:36]:
one or more projects shown does not belong to the current user's account.
&lt;false&gt; is not true.

Ooh la la! Now we’re cooking with gas. However, those shoulda-related lines are cluttering an otherwise perfect backtrace. Luckily, Quiet Backtrace is designed to be extended by calling two types of blocks that yield one line of the backtrace at a time.

Silencers and filters

  1. Silencers let you specify conditions that, if true, will remove the line from the backtrace.
  2. Filters let you use Ruby’s succulent set of String methods to modify a line by slicing and stripping and chomping away at anything you deem ugly and unnecessary. (such as the :in `__bind_1196527660_342195’ in the original example)

Say you want to remove Shoulda-related lines… you create a new silencer and add it the Array of backtrace_silencers:

class Test::Unit::TestCase
  self.new_backtrace_silencer :shoulda do |line| 
    line.include? 'vendor/plugins/shoulda'
  end
  
  self.backtrace_silencers << :shoulda
end

Re-run your tests and bask in the sweet sounds of silence:

1) Failure:
test: logged in on get to index should only show projects for the user's account. (ProjectsControllerTest)
[test/functional/projects_controller_test.rb:31
test/functional/projects_controller_test.rb:36]:
one or more projects shown does not belong to the current user's account.
&lt;false&gt; is not true.

Exquisitely sparse. Quiet Backtrace clears distractions from the “getting to green” TDD process like a Buddhist monk keeping his mind clear during meditation.

Getting noisy again

On occasion, you’ll want to see the noisy backtrace. Easy:

class Test::Unit::TestCase
  self.quiet_backtrace = false
end

You can set Test::Unit::TestCase.quiet_backtrace to true or false at any level in your Test::Unit code. Stick it in your test_helper.rb file or get noisy in an individual file or test. More flex than a rubber band.

Using Quiet Bactrace with Rails

After you have installed the gem on your local machine, I recommend using the excellent gemsonrails plugin to freeze it to your vendor/gems directory and automatically add it to your load path.

Install gemsonrails and add it your Rails app if you don’t already have it:

gem install gemsonrails
cd rails-app-folder
gemsonrails

Then freeze quietbacktrace:

rake gems:freeze GEM=quietbacktrace

Quiet Backtrace will now work with your tests, but because this gem is meant to work on any Ruby project with Test::Unit, it does turn any Rails-specific silencers or filters on by default. However, there is one of each, ready to be switched on, that remove the most dastardly lines.

Add these lines to your /test/test_helper.rb file to get perfectly clean Rails backtraces:

class Test::Unit::TestCase
  self.backtrace_silencers << :rails_vendor
  self.backtrace_filters   << :rails_root
end

Ongoing development

Bug reports and patches are welcome at RubyForge. Talk about it on the mailing list. It’s is a safe place. A place where we can feel free sharing our feelings. A nest in a tree of trust and understanding.

resource_controller 0.1.6: API Enhancements 2

Posted by james
on Sunday, November 25

It's been a while since the last r_c release; I have been busy. I'm still busy, so this isn't a huge release, but these changes have been in SVN for a while, so I thought I'd share them.

There was a cool thread over at RefactorMyCode.com where we discussed some potential API improvements for r_c (it's still open - come share your ideas!). Here's what made it in to this release...

respond_to

respond_to now accepts symbols, and has a few extra names. All of these examples have the same effect...

create.response :html do |format|
  format.js
end

create.respond_to :html, :js

create.responds_to do |format|
  format.html
  format.js
end

create.wants.html
create.wants.js

Callbacks Accept Symbols

I've always liked that the filters, and callbacks in rails accept the name of a method or a block. Now, all the callbacks in resource_controller accept them both, too!

create.before { @post.author = current_user }

create.before :set_user

private
  def set_user
    @post.author = current_user
  end

Configuration-Style Syntax for model_name, route_name, and object_name

Now, rather than overriding a method to provide alternate model, object or route names, you can set them through a configuration-style interface (though, you can still override the methods if you prefer).

model_name  :post_tag
route_name  :tag
object_name :tag

Note: resource_name is gone as of this release. If you were overriding that in your controllers, you'll have to use a combination of the other helpers to get the same effect.

LOTS More Helper Methods Exposed

If you've had a problem with r_c helper methods not being exposed in your views, it should be solved now. Just about every possible r_c method is now available in your views!

Get It!

1.2.3+ Compatible

svn export http://svn.jamesgolick.com/resource_controller/tags/stable vendor/plugins/resource_controller

Edge/Rails 2.0 Compatible

svn export http://svn.jamesgolick.com/resource_controller/tags/edge_compatible/stable vendor/plugins/resource_controller

If you're looking for older versions, try browsing http://svn.jamesgolick.com/resource_controller/tags.

For more info, see the resource_controller section of my blog.

Come join the discussion on the mailing list.

Looking for Rails Work?

Posted by james
on Friday, November 09

I've posted this in a few places, and gotten some great responses. I don't know why I didn't think of posting it here earlier. (Of course, I give preference to anybody who reads my blog :)). Our little job blurb looks like this:

GiraffeSoft is looking to add an awesome rails developer to our team. If you live and breathe ruby & rails, are test(or spec)-obsessed (this one is absolutely 100% critical), want to work on a team that is actually agile, and get paid to work on our open-source projects (and maybe some of your own), we want to talk to you. We work from home, so, self-motivation, and management skills are critical. Where you are, and when you do the work, however, are not, so long as things get done on time. It will start out as a contract position, with the potential to become full-time down the line. If you're interested, send a short note, resume, and code samples to noticeme at giraffesoft dot ca.

Looking forward to hearing from you.

resource_controller: Redesign My API at refactormycode.com 1

Posted by james
on Monday, November 05

Now that I've gotten a few maintenance releases out the door, I'm starting to feel like r_c's feature set is pretty solid; I think most people's needs should be covered (still a few things left to include, but we're getting there). The next step in improving resource_controller is evolving the API. What better place to hold the discussion than Marc-André's refactormycode.com?

First Refactoring designing

In the comments of the last release post, Luigi Montanez brought up an issue that's been bugging me since I first released the plugin. Currently, some customizations (before/after callbacks, response blocks, etc) are made with a "DSL"-style API. Some customizations (like alternate model names), however, are made by overriding helper methods. It's not that I don't like overriding methods, but I find the API a bit inconsistent in that way.

Currently, providing an alternate model name looks like this...

class PostsController < ResourceController::Base
  private
    def model_name
      'blog_post'
    end
end

It would be really neat, though, if it was possible to set the model_name the same way you set the flash...

class PostsController < ResourceController::Base
  model_name :blog_post
end

Please, come join the discussion at RmC, and put some of your ideas on the table for resource_controller's API.


Clicky Web Analytics