Entries tagged with “Development”
- All (51)
- Entries (22)
- Links (29)
- Photos (0)
Requiring SSL Using Route Constraints in Rails 3
The new router in Rails 3 makes it super easy to require SSL for certain routes. Just use the following in your config/routes.rb:
MyApp::Application.routes.draw do
class SslConstraint
def self.matches?(request)
request.ssl?
end
end
scope :constraints => SslConstraint do
resources :payments
# Other SSL routes go in here
end
end
Now, this is a pretty simple example–you’ll likely want to also have routes to redirect if a user tries to access without SSL, but it definitely shows off the power of the new router.
Easy Deploys to Multiple Environments with Capistrano
At Viddler, we’re switching to using Git and Capistrano for our internal projects, and I was tasked with setting up the system to deploy our code to both staging and production environments. Capistrano doesn’t have built-in environment switching, but it’s dead simple to add it yourself.
The key to getting this to work is to utilize Capistrano’s ability to chain tasks–if you run cap task1 task2, it will run the two commands in the order you listed them. Variables are shared between the tasks, so if you set a variable in task1, task2 will be able to access it. To get multiple environments working, you just create tasks to set environment variables, which you call before the actual task you want to run.
The first step is to figure out which variables you need to change, and then move them into their own tasks. Here’s an example of the tasks we added:
desc "Run on staging server"
task :staging do
server "staging.myserver.com", :app, :web, :db, :primary => true
end
desc "Run on production server"
task :production do
server "myserver.com", :app, :web, :db, :primary => true
end
Now, if you want to deploy to staging, just run cap staging deploy, and for production run cap production deploy. Easy.
The Perfect Git Workflow for a One Person Project
A few months ago, I started investigating Git, and I fell in love with how much easier it made managing my code. I’m managing the source code to this site with Git, and along the way I’ve come up with a pretty good workflow for myself. The basic steps are:
- Branch
- Commit
- Rebase
- Merge
- Deploy
Let’s go through each one to see how it all works together.
Branch
With SVN, I found myself hating branching–it was always a complicated procedure, and I could never remember how to do it. In Git it’s as easy as git checkout -b <branch-name>, and you’re ready to go. Once you have a branch, you can modify it however you want, and you don’t have to worry about interfering with the master branch. In order to keep your master branch bug free, commit only to branches, not to master itself.
In addition to creating new branches for major features, I always have a few branches around that I pop into for certain things:
- “design” - any changes I want to make to the design go here
- “optimize” - optimizations to the site
- “bugfixes” - a place to work on minor bugfixes
Having these branches allows me to make small fixes to the site, and if it turns out it’s more than just a small fix, it doesn’t interfere with anything else.
Commit
In the Subversion world, it’s a pretty common practice to make very large commits, consisting of many changes. With Git, you should constantly be committing. By making many commits, you make it easier to find bugs you may have introduced, and it makes it a lot easier to track your progress. If you don’t like the thought of wading through long lists of commits in your logs, don’t worry–before bringing it over to the master branch, you can consolidate things with interactive rebasing, but while you’re hacking away on a branch, it really is advantageous to have many small commits.
Rebase
Rebasing is one of the harder things to grasp when you’re first learning about Git. For in-depth coverage of the topic, check out the Rebasing page in the Git Community Book. In a nutshell, doing git rebase master takes any commits to master and inserts them into your current branch, so you can then make sure your new code still works, and it’s a lot less hazardous than doing a merge. Rebasing your branch before putting into master is really important because it allows you to deal with any merge issues before the code goes to your main branch. To rebase, just run git rebase master.
Merge
After rebasing the branch, it’s safe to merge it into master. Since I’ve already dealt with any merge issues with the previous step, it’s as simple as checking out the master branch and running git merge <branch-name>
Deploy
I use Capistrano to deploy my code, so I’m constantly typing git push followed by cap deploy to deploy changes to my server. To make it easier, I just put both commands into one git alias:
deploy = !git push && cap deploy
Now I just need to run git deploy and it automatically pushes all of my changes to the remote Git repository and then deploys the site using Capistrano. Here’s some more information about Git aliases.
Useful tools
Though I primarily use Git through the command line, I really like using GitX to visualize branches. To host my repositories on my server, I use Gitosis, though if I had a few more projects, I’d dole out the money for a paid account at GitHub.
Have a great Git workflow? Think mine’s terrible? Let me know in the comments!
Sass Without the Hass(le)
For a recent project, I decided to try out Sass, a “meta-language on top of CSS,” which allows to do all sorts of neat things like use variables, do math, and have mixins. The only problem: since you’re not writing CSS, it has to be compiled whenever you want to view your page, which can be pretty annoying if you’re just working on a static template (which I was).
At first, I was using the standard command: sass input.sass output.css, but that became too tiresome, and I was struck with an idea–what if I just created a server solely for serving the compiled CSS file? Using Sinatra, I was able to make a dead easy way to use Sass while working on a static template. Follow along to see how you can do this yourself.
First, make sure you’ve got the haml and sinatra gems installed:
gem install haml sinatra
Now, in your development directory, create a file called app.rb and paste the following code:
require 'rubygems'
require 'sinatra'
require 'haml'
get '/style.css' do
headers 'Content-Type' => 'text/css; charset=utf-8'
sass :style
end
Now, create a views/ directory in that same folder, and drop a style.sass file in there. This is where you’ll be writing your Sass. Now, you need to start up your Sinatra server, and to do that, just run the following command:
ruby app.rb
Now, if you go to http://localhost:4567/style.css, you’ll see your compiled CSS, and every time you update your Sass file, the code is recompiled. Just change the CSS <link> tag in your HTML template to point to the style.css file, and you’re ready to go!
Improving on Related Entries
A little while back, I posted about how I was determining related entries for my site. That method worked, but once I redid my site and added my 250+ Flickr photos, it started to really slow down when finding related photos, because of the increase in tags and posts. The real issue was that I was doing most of the work in Ruby, when it really should have been done with SQL. So, I decided to rewrite it.
Note: If you haven’t looked at my previous entry on the subject, you might want to take a look at it, just for the general idea of what I’m trying to accomplish. Essentially, I’m trying to find related posts by comparing tags. Here’s my new Post#related code:
def related(limit=5)
return [] if tags.empty?
join_array = tags.collect {|tag| "posts_tags.id = #{tag.id}"}
tags_join = "AND (#{join_array.join(' OR ')})"
self.class.find(:all,
:joins => "INNER JOIN taggings posts_taggings ON posts_taggings.taggable_id = posts.id" +
"INNER JOIN tags posts_tags ON posts_tags.id = posts_taggings.tag_id #{tags_join}",
:conditions => ["posts.id != ?", id], :group => "posts.id",
:order => "COUNT(*) DESC",
:limit => limit)
end
You can see, as I mentioned, that all the work is being done in the SQL now. I first create a list of tags from the current post, which I then feed into the query to search for other posts with similar tags. The SQL instructs the database to search for any posts with any of these tags, and then orders them based on how many tags match between the 2 posts. The SQL ended up being fairly complicated, with a lot of joins, but it’s now a whole lot faster, because I’m not creating a lot of overhead by dealing with the computation in Ruby. If you’re interested, here’s an example related entry query:
SELECT `posts`.* FROM `posts`
INNER JOIN taggings posts_taggings ON posts_taggings.taggable_id = posts.id
INNER JOIN tags posts_tags ON posts_tags.id = posts_taggings.tag_id
AND (posts_tags.id = 695
OR posts_tags.id = 192
OR posts_tags.id = 195)
WHERE (posts.id != 4322) AND ( (`posts`.`type` = 'FlickrPhoto' ) )
GROUP BY posts.id
ORDER BY COUNT(*) DESC
LIMIT 5
Recalibration
As you may or may not have noticed, I recently redesigned my site, only about six months after my last redesign. I did this for a variety of reasons, the biggest of which was how outdated and restrictive my old design was starting to feel. The new design brings more flexibility and represents a slight change in the focus of my site. It’s perhaps one of the more complete designs I’ve ever done, which is surprising, since I had it done in only about 4 days.
A Little More Personal
Having “web design + development” in my header restricted my post topics.
One issue that always plagued me with my previous design was that I felt compelled to stick to one topic: web development. I remember showing Colin Devroe the design for the first time, and he mentioned that I should take out the “web design + development” from my header. At the time, I brushed the comment aside, thinking it was not much of an issue. As time went on, however, it became more constraining, and, now, with the new design, I’m hoping to make this into more of a personal site. Sure, I’m still going to be posting mostly about the web, since that’s one of my primary interests, but I also hope to have more posts about other things as well. I also imported all of my Flickr photos into a brand new section on the site, which I’m really excited about.
Easier to Navigate
My old archives were painful to browse.
With my old site, it was almost infuriating how difficult it was to discover old posts. The main archive page (pictured above) was just about useless, and it was hard to find related posts once you started browsing. With the new design, I’ve significantly cleaned up my notebook section to make it a whole lot easier to browse. I’ve even found myself browsing my old Flickr photos this way, because it’s so enjoyable. My absolute favorite part is the “filter bar” that appears on each archive page (e.g. the 2008 archives), which allows you to quickly filter out what type of post (entries, links, or photos) you want to see, no matter whether you’re browsing by tag or date. I’ve also improved the related entries algorithm, and it now is more accurate and efficient. (I posted my old algorithm a little while back.)
My New Favorite Font: Calibri
The new archive filter really help to drill down what you’re looking for, and it looks great in Calibri.
When I first started mocking up the new design in Photoshop, I used Calibri, one of the new fonts in Microsoft Office, for the text, expecting to change it later. As I kept working, I eventually fell in love with the font and realized I couldn’t have the site set in anything else. However, this posed a problem, as most people do not have Calibri installed, and, because it’s sized slightly smaller than other fonts, having a fallback of Helvetica or Arial didn’t look very good. To combat that, I put together a simple jQuery plugin to detect if Calibri is installed, which allows me to serve two different versions of the site really easily. If you don’t have Calbri, however, I’d really, really recommend getting it, because it truly is a beautiful font. Check out my about page for info on where you might find it.
Separate Admin Area FTW
Back when I was working on rewriting my site, I toyed with the idea of having all of the site administration inline–that is, instead of having a separate admin area, I would be able to edit a post from the same page everyone else views it, with a sort of WYSIWYG interface. After thinking it over, I ended up keeping the two parts separate, and I’m really glad I did. When I went to put the new design into my Rails project, I didn’t have to worry about making sure my administration functions still worked, because I didn’t even touch that code–I coul d go ahead and change the design completely without breaking the core of the site. I also didn’t have to have the extra hassle of designing to accommodate an administration area as well.
Feedback
Of course, I’m constantly tweaking the site. Right now, I’ve got a list of about 15 things I still need to do, and I’m sure that will keep growing. If you have any thoughts on the new design, please let me know either by commenting on this post or by sending me an email. If you’re reading this through a feed reader, please come and check out my site and see what you’re missing!
The Wonderful World of Web Hooks
I just read through a presentation on web hooks by Jeff Lindsay, and the concept really excited me. They allow developers to build powerful web services with little overhead and a low barrier to entry. Hopefully, over the next year or so, more applications will begin to use hooks to create a more connected and powerful web.
What are Web Hooks?
The best example of web hooks I’ve found is GitHub, a social code hosting service. In each project you create, you can specify a “post-receive hook”, which is just a URL of your choosing. Whenever someone pushes new code to the project, GitHub sends an HTTP POST to this URL, and you can do whatever you want with the data. For instance, say you wanted to send an email to your team, notifying them of the changes. You could write a simple PHP script like this:
$data = $_POST['payload'];
$subject = count($data['commits']) . " new commits"
$body = "New code has been committed to the project: \n";
foreach($data['commit'] as $commit) {
$body .= "Commit: " . $commit['id'] . "\n";
$body .= $commit['message'] . "\n\n";
}
mail($recipients, $subject, $body);
Now, if you set the URL of this script as your post-receive hook, whenever someone pushes code, GitHub POSTs the data to the URL, and an email is sent to your team. Not only is this really powerful, but it’s also much more efficient than traditional methods.
Web Hooks = Efficiency
Suppose you wanted to build an app with the same functionality as described above, but without using hooks. You would need to create a script that grabbed your project’s RSS feed, parsed it, and sent an email if there’s new information. This wouldn’t work nearly as well as before. Look at the script above: 8 lines of code. With the added steps of fetching the feed and parsing it, you’re adding considerable complexity to your code, plus you need a way to tell what content is new, so you don’t send out duplicate emails. Not only is this a lot of steps, but you also need to schedule it to run on a consistent basis using a cron job or something similar.
This all means you’re putting a greater load on both your application and on GitHub. Much of the time, when your script is run, it won’t find any new data, wasting both GitHub’s and your resources. With a web hook, your script waits patiently until something happens, and is only run when there’s a reason. It’s also run immediately after the action occurs, i.e. when GitHub receives a commit, it pings your script right away. If you have to poll an RSS feed, you might not know for a while, depending on how often the script is run.
The Future
Where hooks will really succeed, I think, are as lightweight web services, like Jeff’s MailHook, a service that receives emails and POSTs the information to your hook. Jeff took it down, since it was more of a proof-of-concept than anything else, but applications like this are really exciting. I’d definitely recommend looking through Jeff’s presentation (if you haven’t already), as he has some great examples of existing sites that use web hooks, as well as insight into the future of this method of interaction.
The aspect that excites me the most is how easy it is to write a web hook. All you need to have is a cheap PHP host and some basic knowledge about handling POST data, and you’re ready to go. By shortening the learning curve, web hooks make it possible for more people to build on top of existing platforms, leading to a more connected and interesting internet.
Making Better Use of named_scope
In Ruby on Rails 2.1, a great little feature called named_scope was added that really makes complicated finds a whole lot easier. I’m going to walk through one way you can use scopes to clean up your code, and if you want more information, Ryan Daigle’s post is a good starting point.
The Situation
For a project I’m working on, users can submit reviews, and I needed a way to access a user’s friends’ reviews. At first, I considered adding a method like the following to my User model:
class User < ActiveRecord::Base
def friends_reviews
Review.find(:all,
:joins => "JOIN friendships ON user_id = #{self.id}",
:conditions => "friendships.friend_id = reviews.user_id"
)
end
end
Named Scopes to the Rescue
While this method would work fine, there are several issues. For one, what if I want to change parameters on the find, like limiting it to 5 entries, or sorting by date? I would have to add parameters to the method, and it would start to get complicated. Instead, I created a scope in my Review model:
class Review < ActiveRecord::Base
named_scope :by_friends_of, lambda { |user|
{
:joins => "JOIN friendships ON user_id = #{user.id}",
:conditions => "friendships.friend_id = reviews.user_id"
}
}
end
Now, to get the reviews I want, I can call Review.by_friends_of(user) and it will get me reviews by user’s friends. What’s even better, since it’s a scope, I can modify it. For instance, Review.by_friends_of(user).all(:limit => 10, :order => "created_at DESC") will limit it to 10 reviews, sorted by creation date. I can even use other scopes I might have created, like: Review.by_friends_of(user).published.all(:limit => 20), and so on.
Scopes are super useful, and recently I’ve started to use them for just about everything. Definitely try them out, they’ll make your code a lot more efficient and useful.
Why I won't be buying Versions
Today, Versions, a Subversion app for OS X, moved to version 1.0, marking the end of its free beta period. While many people will be shelling out the €39 (~$49) for a license, I won’t be doing the same. Having used Versions for a couple months now, it just isn’t for me.
I don’t need a fancy UI.
When I loaded Versions for the first time, I was impressed by the amount of work that went into the UI. It looked nice, and it worked pretty well too. However, I found myself working harder to accomplish simple tasks than when I used the command line. For me, it’s a lot easier to cd to the directory in the Terminal and do a svn stat rather than take the time to load up Versions and do a similar command. Even though it might be nicer to look at, Versions was just never quite as quick or intuitive as the terminal commands I had grown used to. In addition, I almost always have the Terminal open, as I’m constantly running commands during development. Having to open up another app just got in the way of my workflow.
I already use TextMate.
TextMate is the center of my coding world. It is quite simply the greatest text editor I have ever used, and it’s where I spend the vast majority of my time when I’m coding. Built into TextMate is a great SVN plugin that really makes Versions unnecessary. Without changing applications, I just hit CTRL+SHIFT+A and I’m greeted with a list of SVN commands. I tap 5, and I see a dialog where I can set my SVN commit message and choose exactly which items I want to commit from a list of changed files. Why would I need anything more sophisticated than that? If I wanted to go ahead and commit a few files with Versions, I’d have to fire up the application, locate the files I want, and hit commit. The worst part is, at least in the beta version I used, there’s no good way to select multiple files in Versions without resorting to holding the command key and clicking each file. In TextMate, it’s as simple as checking boxes.
Git is the future.
My biggest problem with spending close to $50 on a Subversion app is that I really don’t want to use SVN any more. I’ve fallen in love with Git, a distributed version control system that puts Subversion to shame. Just about every new personal project I start will be using Git, so there’s really no point in buying a program just to manage my older projects. If Versions supported Git as well, I’d definitely be considering adding it to my arsenal.
A Grain of Salt
Now, just because Versions hasn’t worked its way into my routine doesn’t mean it can’t be part of yours. I personally know several people that really enjoy Versions, and it has really improved their workflow. It’s definitely a well-made product, it just doesn’t work for me.
Also, if you’re in the market for an OS X client for SVN, definitely check out Cornerstone as well. While I haven’t used it personally, I’ve heard good things about it.
Finding Related Entries Using Tags with Ruby on Rails
One of the cool features that I built into my new site is the “related” sidebar box on every entry and link. Using a not-so-sophisticated algorithm, my site automatically picks out other entries that seem to be related to the current entry, which hopefully helps readers navigate to my other content. It really wasn’t too difficult to implement, so I figured I’d go through my thought process and the code that makes it happen.
The Not So Fancy Algorithm
It took me a little while to come up with a way to determine if an entry is “related” that was both accurate and relatively efficient–I could have used some complicated tool that parses the content of my entries, but instead I decided use something that’s a little simpler: tags. To understand how it works, pretend I have three entries:
- Entry A - Tagged with: Turkey, Roast Beef, Cheese, Bread
- Entry B - Tagged with: Bread, Baking
- Entry C - Tagged with: Turkey, Cheese, Bread, Lettuce
Let’s say we’re looking for entries related to Entry A. From just looking at B and C, it’s clear that C should be closest, as they A and C both have something to do with sandwiches, whereas B only talks about bread. To rank the entriesprogramatically, I first do a query to find entries with any of the tags from Entry A. Then, once I have that list, I sort the entries by how many of Entry A’s tags are used. So, in the above example, Entry C would have 3 matched tags, and Entry B would have 2. It’s not a perfect system, but so far, it seems to be working pretty well.
The Code
So, here’s the code that’s performing all the magic:
class Post < ActiveRecord::Base
def related(limit=5)
@related ||=
returning self.class.find_tagged_with(tag_list, :conditions => ['posts.id != ?', self.id], :limit => limit) do |posts|
posts.sort_by do |p|
matched_tags = p.tags.find_all {|t| self.tags.include?(t)}
matched_tags.size
end.reverse
end
end
end
The real meat of the method is in the returning block, so let’s take a look at that:
returning self.class.find_tagged_with(tag_list, :conditions => ['posts.id != ?', self.id], :limit => limit) do |posts|
posts.sort_by do |p|
matched_tags = p.tags.find_all {|t| self.tags.include?(t)}
matched_tags.size
end.reverse
end
What’s happening here is I’m first searching for any entries tagged with the current entry’s tags (I’m using acts_as_taggable_on_steroids), then, with the data that’s returned, I use Ruby to sort the entries by the number of “matched” tags, which then gets returned from the method. Conventional wisdom suggests moving the matched tags part into SQL, since MySQL is more efficient than Ruby at handling data. However, I’m using relatively small sets of information, and I haven’t run into any performance issues yet.
Overall, this method’s working pretty well for me, but I’m sure as I accumulate more posts, I’ll need to refine it some. I’d really like to incorporate some sort of popularity ranking, based on number of comments and views, but that’s not something I’m too worried about at the present.
Simple PHP Caching Using Output Buffering
I’ve worked on quite a few PHP projects recently, and all of them have required some form of caching. From working with each, I’ve come up with a pretty efficient method for caching code using PHP’s output buffering. It ends up being really quick and super flexible.
Output Buffering Basics
Output buffering is a pretty simple concept: instead of letting PHP return data to your user’s browser, you capture it and store it in a “buffer,” and you can decide what to do with it. Here’s a simple example:
ob_start();
echo "Hello! This is buffered.";
$buffer = ob_get_clean();
Let’s go through the code line-by-line. The first line calls ob_start() (docs), which starts output buffering. The next line normally would be sent to the browser. Instead, since I called ob_start(), it gets stored in our buffer. The third line takes the current buffer and assigns it to the $buffer variable and stops the current buffer, all using the ob_get_clean() function (docs). It’s really simple stuff, and it becomes very powerful when used correctly.
How Caching Will Work
For this post, I’m going to be caching a simple API, and the general process will work like this:
- A user makes an API call, something like
http://mysite.com/api/?method=myapp.search&type=people&query=Kyle - If a cache file exists for the call, and it is younger than 15 minutes, skip to #6.
- Start buffering PHP’s output.
- Run the code to process the request.
- Save the contents of the buffer to a file, with a unique filename.
- Return result to the user.
Where Cached Output Will Be Saved
To save the output, I’m going to be creating a file for each unique request. For this application, the request will be unique based on the GET parameters passed. To do this, I’ll be creating an MD5 hash of an alphabetical list of GET keys and values. Here’s the function:
function cache_key() {
$keys = array();
foreach($_GET as $key => $value) {
$keys[] = $key . "=" . $value;
}
sort($keys);
return md5(implode('&', $keys));
}
function cache_filename() {
globals $cache_dir;
return $cache_dir . '/' . cache_key() . '.cache';
}
Please note that this will have to be customized based on what exactly you’re caching. For instance, if you’re caching individual pages, you may want to create the key using the path to the page. Whatever it is you’re using, just make sure it is unique and consistent for each page.
Checking The Cache
When a request is made, it’s necessary to first check to see if it has already been cached, and, if it has, whether the cache hasn’t expired. I’ll be using the filesystem to achieve this:
$cache_time = 15*60; // 15 minutes in seconds
function cache_exists() {
globals $cache_time;
if(@file_exists(cache_filename()) && time() - $cache_time < @filemtime(cache_filename())) {
return true;
} else {
return false;
}
}
So what exactly is going on here? If you take a close look, we’re using the file_exists() (docs) and filemtime() (docs) functions to see if the cache file already exists and, if it does, whether it’s recent enough to serve (in this case, if it’s less than 15 minutes old, the function returns true). I’m placing @ signs before these two functions so that, if they fail, it doesn’t return an error. Instead, the function will just return false and the code will run as if no cache file exists.
Putting It All Together
Now, it’s time to get everything working together. First, a couple of necessary functions for saving and reading the cache:
function read_cache() {
return file_get_contents(cache_filename());
}
function save_cache($value) {
$fp = @fopen(cache_filename(), 'w');
@fwrite($fp, $value);
@fclose($fp);
}
Now, a few calls to wrap around your code:
function start_cache() {
if(cache_exists()) {
echo read_cache();
exit();
} else {
ob_start();
}
}
function stop_cache() {
$data = ob_get_clean();
save_cache($data);
echo $data;
}
And to implement it, this is all you need to do:
start_cache();
// Your code that needs to be cached
stop_cache();
And you’re done! All-in-all, it’s a very simple way to achieve a very powerful result.
Drawbacks to This Method
The first thing you want to keep in mind when using this caching method is that it caches the entire page. This can be good: if everyone visiting the page sees the same content anyways, why not cache it for everyone? However, if you’re serving a page that appears different to different users, it can be a bad idea. For instance, what if an administrator visits the page, and it gets cached? When the next non-administrator visits, they’re going to see all the administration information. Bad news.
Also, due to the simplicity of this method, there’s no way to easily expire the cache of a single page. Let’s go back to the blog entry example. If you decide to make a change to the entry, you’ll have to wait at least 15 minutes before the cache is cleared, or you have to go in and delete all the cache files (since it’s difficult to determine which file goes with which page). For many applications, this probably won’t an issue, but it’s something to keep in mind.
Download the Source
Hopefully, this was helpful. If you’d like to download the entire source, you can grab it here.
RESTfully Forward Users to FeedBurner in Rails
After launching my new site, I realized I had forgotten to use FeedBurner to track my RSS feed subscribers. Because FeedBurner requires that you forward users to your feed on their servers, I needed a way to forward users along, but I also wanted to make sure that if FeedBurner’s spider came to the same feed URL, they would be served the source RSS instead of being forwarded. My solution ended up like this:
class NotebooksController < ApplicationController
def show
respond_to do |format|
format.html
format.rss do
unless request.env['HTTP_USER_AGENT'] =~ /feedburner/i
redirect_to 'http://feeds.feedburner.com/KyleSlattery'
end
end
end
end
end
If you take a close look at the, all I’m doing is checking the user agent of the request, and unless it’s FeedBurner, I redirect the user to the “burned” feed. If it is FeedBurner, things go ahead normally, and the RSS feed gets rendered.
This means that instead of creating separate actions, one that forwards to FeedBurner, and one that renders the RSS, I just have one action/URL that does both: http://kyleslattery.com/notebook.rss. So far, it’s worked great, and doing it this way really helped to keep my code clean and RESTful.
