Articles tagged with rails
So by now we all know how to do wicked-cool search with acts_as_ferret. (If not, the RailsEnvy guys can lend a hand, but the tutorial is a little outdated for the latest trunk version of acts_as_ferret. Just replace #find_by_contents with #find_with_ferret and you should be good.)
But searching a single model is so last year. All the cool kids are getting promiscuous with their searches and involving multiple models. Fortunately for us, recent revisions of acts_as_ferret makes this easy-peasy.
The key to making this happen is a shared index: we want all of our models indexed in one place so ferret only has to do one search. We can do it without a shared index, but then we have to do a ferret search and thus a SQL select for each model. Plus, if we want your search results interspersed and sorted by rank, we have to have a shared index.
Enough chit-chat, show us how!
Ok, I’m getting to it. Grab the latest version of acts as ferret from trunk:
script/plugin install svn://projects.jkraemer.net/acts_as_ferret/trunk/plugin/acts_as_ferret
Now, instead of defining acts_as_ferret in our models, we define them all in config/aaf.rb
ActsAsFerret::define_index('shared',
:models => {
Person => {:fields => [:first_name, :last_name, :phone, :bio]},
Company => {:fields => [:name, :description]},
Post => {:fields => [:title, :body]}
},
:ferret => {
:default_fields => [:first_name, :last_name, :phone, :bio, :name, :description, :title, :body]
}
)
This defines a new index, called “shared”, and then defines the acts_as_ferret configuration for each model.
Now for the fun part: searching our shiny new index.
def search
@results = ActsAsFerret.find(params[:q], 'shared')
end
This will give us one array with any models that matched the search query, ordered by rank. And for those times when we only want to search one model, we can still do that.
def search
@people = Person.find_with_ferret(params[:q])
end
How do we display the results?
Update: Sorry, originally I had an example using resources, but that doesn’t work as-is; I was doing something a little different in the app that this example came from.
To display our search results, we just render a partial for each model in the result:
<% @results.each do |result| %>
<%= render :partial => "search/#{dom_class(result)}" %>
<% end %>
This will just look for a partial for each model (like search/_person.html.erb).
So there you have it. Now you too can have promiscuous searching.
Update: I’ve put together an example rails app that uses the shared index. It uses sqlite and has some date pre-populated. Start up script/server and do a search for “John”.
At Collective Idea, we have a plugin called awesomeness that is…well, awesome. It’s a collection of things that we use in almost every project that aren’t generic enough to go into individual plugins (although some things may have evolved enough to be worthy of plugin status).
A while ago, I blogged a little snippet for backing up your remote database. Well, that snippet as evolved quite a bit, into it’s own set of rake and Capistrano tasks.
First, the rake tasks. You Can easily create a new local backup:
$ rake db:backup:create
This creates a backups directory in your project, with a subdirectory for each backup based on the timestamp. A backup consists of the schema.rb file and then a fixture for each table to hold the data. Why fixtures? Good question. Because we wanted the backups to be database independent.
You can easily restore your local database to the latest backup, or a specific version:
$ rake db:backup:restore VERSION=20080427214315
That’s nice, but what good are local backups? That’s where Capistrano comes in. Just add this to your config/deploy.rb:
load 'awesomeness/backup'
This adds some nifty remote backup support. Now, whenever cap deploy:migrations is run, a backup of your remote database will automagically be created and stored in the shared directory on the server. You can also have them transferred to your local machine by adding a callback in your deploy.rb:
after "backup:create", "backup:download"
Sometimes, you just want to take a snapshot of the server and plop it into your local database.
$ cap backup:mirror
How do I get this backup awesomeness?
Awesomeness now lives on Github (like the rest of the world). Fork it and let us know what you think.
I’ll be leading another hands-on, interactive workshop exploring the ins and outs of Ruby on Rails in San Francisco on April 1–4. Experience the Rails way of approaching web applications, starting with the basics of Ruby and Rails, and then diving into the full Rails’ MVC stack, testing techniques, Ajax and even web services. In the 4 day class, we’ll work through the full life-cycle of a Rails project, giving you experience with all facets of a typical app, lead by people that have been working with Rails every day for 2 years.
Sign up now on Marakana’s website and receive $150 off using the coupon code “collectiveidea”.
Maybe you’ve heard of it, but there’s this fancy web framework called Ruby on Rails. Apparently it’s all the rage these days. It’s only 2.0 right now, so the big kids say it still belongs on the playground, but there’s rumors of people using it to do real work. The word on the street is that it makes you more productive and makes programming fun again. If that sounds like something you’d like to learn, you can get more information and signup on Marakana’s website.
Update: Marakana is offering $150 off when you sign up using coupon code “collectiveidea”!
Several people have reported that acts_as_audited does not work with ActiveScaffold. I don’t use ActiveScaffold, so I had no motivation to fix it. But thanks to a tip from Aaron, this has now been fixed.
To make acts_as_audited work with ActiveScaffold, only enable auditing for only the :create, :update, and :destroy actions.
class ApplicationController < ActionController::Base
audit User, Thing, :only => [:create, :update, :destroy]
end
I’m not sure exactly why it breaks or why this fixes it, but from this thread it sounds like ActiveScaffold just doesn’t support polymorphic associations, which is what acts_as_audited uses to associated audits with models.
I will be leading another 4 day Ruby on Rails course in San Francisco on November 26-29. Since Rails 2.0 is almost out (and may be by the end of the month), the training will be based on Rails 2.0, including the wonderful world of resources.
So whether you’re new to Ruby and Rails and want to see what all the fuss is about, or you’ve been playing with Rails 1.x and want to see what 2.0 has to offer, head on over to Marakana’s website to sign up.
One of more annoying ActiveRecord bugs has finally been fixed in edge rails after 2 years. Table names in SQL queries are now properly escaped.
On nearly every project it seems I end up having a table name that doesn’t work in either MySQL or sqlite. While they usually are SQL reserved words, most databases allow you to use reserved words if they’re properly quoted.
The following, which doesn’t work in MySQL:
DELETE FROM values;
will now be:
DELETE FROM `values`;
ecounseling.com, a project that we worked on at Collective Idea, is looking for a Rails programmer to implement some new features and continue to expand their site.
The site is a Rails application with lightweight CMS, live chat using Campfire, membership management and credit card processing. The client has been fantastic to work with, the problem is just that there aren’t enough hours in the day. So we’re looking for someone that is interested in full-time or consulting work. The client would ideally like around 40 hours/week, but I could contribute up to half of those.
Let me know at the email on the right if you’re interested.
Here’s a little nugget to add to acts_as_ferret to make your searches paginate with will_paginate.
module ActsAsFerret
module ClassMethods
def paginate_search(query, options = {})
page, per_page, total = wp_parse_options(options)
pager = WillPaginate::Collection.new(page, per_page, total)
options.merge!(:offset => pager.offset, :limit => per_page)
result = find_by_contents(query, options)
returning WillPaginate::Collection.new(page, per_page, result.total_hits) do |pager|
pager.replace result
end
end
end
end
Updated from Behrang’s comment based on changes to will_paginate.
There was a slight challenge in that will_paginate expects that you do one query to get the count, create a new collection object based on that count, and then perform the actual search. But acts_as_ferret does it all in one method call, so I have to create a temporary collection object to get the offset, then do the search and create the collection object. It’s a little messier than it needs to be, but it works.
Product.paginate_search params[:q], :page => params[:page]
Edge Rails picked up a new feature today we’ve often longed for at Collective Idea. Partials can have a layout:
<%= render :partial => "user", :layout => "admin", :collection => @users %>
This would wrap the user partial with the admin layout, which in this example adds an edit link for the admin user:
<% content_tag_for :li, user do %>
<%= yield %>
<%= link_to 'Edit', edit_user_path(user) %>
<% end %>
Another cool feature in this patch is the ability to add a layout to any block in a template, for those one-off situations, or to just tidy up repeated markup:
<% render :layout => "admin", :locals => { :user => owner } do %>
<%= render :partial => "user", :collection => @users %>
(owner)
<% end %>
I will be leading a 4 day Ruby on Rails training course in San Francisco on September 4-8 through Marakana.
From Markana’s website:
In Ruby on Rails Training course you will learn to put your web development on Rails using Test-Driven Development to create clean Ruby code that works, developing the confidence you need to fearlessly refactor and enhance your Rails application going forward…Students will build a Rails application, learning the TDD lifecycle from the beginning. Time spent in lecture will focus on an introduction to Ruby, a thorough discussion of Test-Driven Development, and an in-depth exploration of the Rails framework, including a final day focused on using Ajax with Rails. The majority of the course will involve intense, hands-on learning to provide students with opportunities to encounter more realistic obstacles as they familiarize themselves with the rhythm of development on Rails.
If you can’t make it to San Francisco in early September, or are interested in private training for your company/group from developers with 2 years of real world Rails experience, feel free to contact me.
With an app that I’m working on, the client wants to have several buttons for doing different actions on every form: “Save”, “Save & Continue Editing”, “Save & Add Another”, and “Cancel”. HTML only allows you to have one action defined per form (instead of per button), and Rails pretty much assumes that if you submit a request to a specific action, you expect to execute it.
So, instead of littering my code with all kinds of if/else statements, I decided to wrap up the functionality into a little plugin that makes it a little cleaner.
with_action is a respond_to style helper for executing different blocks based on presence of certain request parameters.
def create
with_action do |a|
a.cancel { redirect_to articles_path }
a.any do
@article = Article.new(params[:article])
if @article.save
a.save { redirect_to article_path(@article) }
a.edit { redirect_to article_path(@article) }
a.approve do
@article.approve!
redirect_to article_path(@article)
end
else
render :action => 'new'
end
end
end
end
A block is invoked if a parameter with the same name exists and is not blank. Here is an example of the form that submits to this action:
<%= submit_tag 'Save', :name => 'save' %>
<%= submit_tag 'Save & Continue Editing', :name => 'edit' %>
<%= submit_tag 'Save & Approve', :name => 'approve' %>
<%= submit_tag 'Cancel', :name => 'cancel' %>
If an any block is present and no parameter that matches one of the other blocks, it is called by default, otherwise the first block will be called. The any block is the only one that can have nesting and be called multiple times.
I realize this could be considered trivial, but this looks a lot cleaner than the alternative, and more importantly, gave me a way to standardize on how I handle these actions. Let me know what you think.
http://github.com/collectiveidea/with_action
posted by brandon
| updated June 19th 07:19 PM
If for some reason you use one of those other browsers, then you don’t have the pleasure of using Firebug for debugging (Safari nuts, I know you secretly debug your javascript and stylesheets with Firebug in Firefox, despite it “not looking like a Mac app”).
Fortunately for you, there’s Firebug Lite. From the website:
Firebug is an extension for Firefox, but what happens when you need to test your pages in Internet Explorer, Opera, and Safari? If you are using console.log() to write to Firebug’s console, you’ll wind up with JavaScript errors in these other browsers, and that’s no fun.
The solution is Firebug Lite, a JavaScript file you can insert into your pages to simulate the Firebug console in browsers that are not named “Firefox”.
To ease the pain of debugging Javascript in those other browsers, I’ve thrown together a little plugin that automagically includes Firebug Lite into your application in development mode. All you need to do is install the plugin and include the default javascripts in your layout (which you’re probably already doing):
<%= javascript_include_tag :defaults %>
Just hit F12 (Ctrl+F12 on Mac) to open Firebug Lite on any page, or jump the focus directly to the command line with Ctrl+Shift+L (or ⌘+Shift+L on Mac).

http://source.collectiveidea.com/public/rails/plugins/firebug
The theory is that tests are supposed to be agnostic of the implementation. This leads to less brittle tests and actually tests the outcome (or behavior).
With RSpec, I feel like the common approach of completely mocking your models to test your controllers ends up forcing you to look too much into the implementation of your controller.
Here is an example of a spec that is generated by the rspec_scaffold generator:
describe ThingsController, "handling POST /things" do
before do
@params = {}
@thing = mock_model(Thing, :to_param => "1", :save => true)
Thing.stub!(:new).and_return(@thing)
end
def do_post
post :create, :thing => @params
end
it "should create a new thing" do
Thing.should_receive(:new).with(@params).and_return(@thing)
do_post
end
it "should redirect to the new thing" do
do_post
response.should redirect_to(@thing)
end
end
This by itself is not too bad, but the problem is that it peers too much into the controller to dictate how the model is used. Why does it matter if my controller calls Thing.new? What if my controller decides to take the Thing.create! and rescue route? What if my model has a special initializer method, like Thing.build_with_foo? My spec for behavior should not fail if I change the implementation.
This problem gets even worse when you have nested resources and are creating multiple models per controller. Some of my setup methods end up being 15 or more lines long and VERY fragile.
RSpec’s intention is to completely isolate your controller logic from your models, which sounds good in theory, but almost runs against the grain for an integrated stack like Rails. Especially if you practice the skinny controller/fat model discipline, the amount of logic in the controller becomes very small, and the setup becomes huge.
So what’s a BDD-wannabe to do? Taking a step back, the behavior that I really want to test is not that my controller calls Thing.new, but that given parameters X, it creates a new thing and redirects to it.
Here’s the approach I’ve been taking:
# spec_helper.rb
def valid_thing_attrs(attrs = {})
{:name => 'Foo'}.merge(attrs)
end
# things_controller_spec.rb
describe ThingsController, "handling POST /things" do
def do_post
post :create, :thing => valid_thing_attrs
end
it "should create a new thing" do
lambda { do_post }.should change { Thing.count }.by(1)
end
it "should redirect to the new thing" do
do_post
response.should redirect_to(@thing)
end
end
My spec is now testing that posting certain attributes creates a new thing (in the database) and redirects to it. You’ll notice that I have a method called valid_thing_attrs. I don’t remember where I picked up this pattern, but it is something that has allowed me to minimize all of my test’s dependencies on the model.
My spec is now somewhat dependent on my model–and thus will break if my model gets hosed–but that is a small price to pay, in my opinion, for an implementation-agnostic spec with significantly less overhead.
What do you think?
When I first created acts_as_audited over a year ago, I intended to add versioning/revisioning capabilities, but found I never really had a need. Well, it just so happened that I finally had a need on an app that we are working on. So, acts_as_audited now allows you to revert back to previous revisions.
You can get all the revisions of a model:
article.revisions.each |revision|
puts revision.class #=> Article
puts revision.version #=> 7, 6, 5, 4...
end
or a specific revision:
revision = article.revision(5)
puts revision.title #=> "Old Title"
or the previous revision. Reverting is as simple as saving a revision:
previous = article.revision(:previous)
previous.save # revert to previous revision
See the original post for more details about installing and using the plugin.
How is this different from acts_as_versioned?
I’ve never used acts_as_versioned1 (because I’ve never had a need to do versioning), but I do know that it has a few annoying limitations: 1) it requires a separate table (and model) for each model that you want to version, and 2) it saves every attribute, even if it’s not changed.
acts_as_audited already stories all the changes (and only the changes) in one table, so adding versioning was rather trivial. Revisions are created by walking backward through the audits, collecting the changes, and returning an instance of the model.
Upgrading
To use the versioning support, you must add a version field in the audits table:
add_column :audits, :version, :integer, :default => 0
Note: if you already have audit records, the version field will have to be initialized.
Feedback
This code is fairly specific to what I needed, so if you’re using it, I would love to hear how it is working for you. Suggestions and patches welcome.