Err the Blog Atom Feed Icon
Err the Blog
Rubyisms and Railities
  • “A Rails Toolbox”
    – Chris on October 17th, 2006

    This post is tagged with rails.
    This post is tagged with ruby.
    It has one comment

    We’ve been finished with Chow for about a month now, and that’s cool. (PJ wrote about the launch back in the day.) The site is stable and it’s our second from-scratch Rails site at CNET, the first being Chowhound. Party time, right? Right.

    Since it seems like we did something correct, I’d like to share with you our toolbox. We have no golden hammer, but we sure use some kickass open source software. This is just a high level overview, but who knows? Maybe a hammer or tape measure you find within will be new and useful to you. I can only hope.

    tools!

    Servers

    The muscle, scar tissue, and bone running our site are nothing unusual. Rails convention: a hardware load balancer to Apache 2.0 to Pen to a cluster of Mongrels. Coda Hale has an infamous tutorial on setting up a similar architecture and Zed maintains a stellar documentation page over at the Mongrel site. We have had very few problems with Mongrel and Pen is so quiet that I often forget it exists.

    Behind the backend we run an installation of Solr as our search engine. It’s Java, but a nice XML-RPC API helps alleviate the pain. A dedicated wiki page is available with Ruby help (which you probably won’t need). A cron or two is needed to insert items into the index, mind you.

    We (of course) run memcached and use it to cache tons of data. This helps keep our site speedy and takes the load off our largeish database. In case you missed it, a previous Err post touches on memcached. It’s a lot easier to use than you might think.

    Finally, we use MySQL for our database. Big surprise there.

    Deployment

    Like any red blooded America, we use Capistrano for deployment. There’s really no other option. The mailing list is invaluable, as are the various tidbits scattered across the internet. There’s even a great cheat sheet.

    We also use monit to handle our production environment. monit is a nice program which provides a web interface for monitoring your apps. It will restart troubled processes and e-mail you when trouble arises. On top of that, it can also watch to make sure daemons don’t take up too much CPU or memory and kill them when they do. It uses a nice little DSL for its config files and, as long as you have a pid file, is easy to get up and running with. There’s a nice starter monit config file here and plenty of resources out there. monit can even replace mongrel cluster. Highly recommended.

    Plugins

    Okay, we use a lot of plugins. I’m going to make this a bullet list. Just pretend it’s a PowerPoint presentation and it should seem more familiar. Er, on second though, don’t.

    acts_as_cached

    Already mentioned further up, we use memcached to cache many database queries. This plugin supports model caching, session caching, and fragment caching out of the box. A simply memcached.yml is added to our config/ directory and we can forget about it. The README file provides a wealth of common idioms and a mailing list keeps patches (and bug reports) flowing. Essential.

    query_trace

    Completely essential. This plugin attaches a short backtrace to every SQL query in your log files. The first time I heard this I thought, ‘so what?’ Well it’s a big deal. Suddenly you know exactly what is going on with your ActiveRecord code—you know the exact line number from which queries are being spawned. When you’re trying to cache everything using memcached, this goes a huge way in helping you lock down your database.

    exception_notification

    Again, essential. E-mails us whenever an exception is raised on the site. Some days I just gather up the exceptions in my Inbox and bang away on those bugs for an hour or two. No more e-mails. It’s very gratifying. Best part is, you can control whether certain exceptions show a 500 or a 404, and you can even set the prefix of the e-mails you receive. For instance, I can set the e-mail prefixes then setup mailbox filters to have all the e-mails prefixed with [CHOW] go to my Inbox and all the e-mails prefixed with [CHOWHOUND] go into my trash can. Very slick.

    acts_as_textiled

    The first ‘official’ plugin extracted from our work, acts_as_textiled lets us forget about rendering Textile. Instead of having to ask ‘should I render Textile?’, the question becomes ‘should I not render Textile?’ If a story description uses Textile, we always want it rendered—except in special circumstances. It’s nice to not have to use the textile helper all over our app and instead focus on things that matter. Like pastels.

    acts_as_solr

    This is a bit of a white lie. We’re currently using a plugin PJ rolled called ‘acts_as_indexed,’ but he is in the process of transferring our stuffs over to acts_as_solr. It’s a fine piece of work and written by people who know Lucence (and solr) inside and out. One of those dudes even wrote a book, I hear.

    debug_view_helpers

    This is a real life saver. If you’ve ever used Smarty, you know how great that ‘debug’ window is that pops up. There’s no ambiguity for your CSS people: all the available variables are shown right there in a pop-up window. We installed this plugin and set the ‘debug’ link to appear a) in development mode b) if you’re on the VPN. That means, even in production mode, authorized elite personnel can always see the variables available to the view. So awesome for debugging. (Plus it means I don’t have to talk to our CSS guy, Todd, so much. He is weird.)

    has_many_polymorphs

    Evan Weaver’s crazy plugin lets us forget about all the black magic we learned from Josh Susser. Little o’ this:

    class Tag < ActiveRecord::Base
      has_many_polymorphs :tagged_things, :from => [:posts, :files, :tags]
    end
    
    just a normal
    test okay
    

    Yup. Very nice. Also lets you do Tag.find(:all, :include => :tagged_things) and fetches the result in one query. That is, if you’re using MySQL or Postgres.

    request_routing

    Hey, check out this code:

    map.connect '', :controller => 'mobile', :conditions => { :subdomain => 'm' }
    

    Can you believe that’s not in core? It’s so great, and thanks to the request_routing plugin we’re able to use it for Chow’s mobile site. Plus, Dan Webb’s blog is hot. There. I said it.

    redbox

    You just have to see it for yourself. Real nice code which lets you pull off that whole ‘lightbox dialog’ thing. We’re using it for the ‘E-mail to a Friend’ links. Nice.

    unicode_hacks

    Right now, Unicode seems to fall down when you need to pattern match against it or do any sort of manipulation. Lucky for us we just do input and output. Until the new Rails hits the streets, we rely on the unicode_hacks plugin to get those crazy characters in and out of our database correctly.

    browser_filters

    Safari crashing randomly? We (correctly) suspected Unicode. This Super Official Rails Plugin solved our problems and may solve yours. Also provides a few more fixes and tweaks.

    composite_migrations

    Yes, we’re crazy enough to use composite primary keys. That’s just the way some of us were raised. This home rolled plugin allows us to create the dastardly fellows in a migration so that we may use the composite primary key gem freely and openly in our code. Hey, we’ve got opinions, too.

    Libraries

    First and foremost, hpricot is an amazing invention and a huge help. Scraping and parsing HTML/XML isn’t something we think twice about anymore. We use hpricot on more than XML import feed because it’s just so damn easy. If you’ve never given it a shot, I recommend trying it soon.

    Both Dr Nic’s Magic Models and Camping are used by us to prototype all sorts of ideas and internal tools. Some of our top secret monitoring tools run using a combination of the two. Being fluent in Camping, and knowing some tricks to get it working with Magic Models, is a must.

    RFuzz, available as a standard Ruby gem, is a library which makes it trivial to test web sites. We run periodic RFuzz tests against our live sites to make sure they’re alive, bullet proof, and speedy. The tests run just like unit tests and help us feel that much better about our babies. During the big security fiasco RFuzz was instrumental, and since I’ve fallen in love.

    When it comes to testing, nothing gives me more pleasure than color. Thanks to Pat Eyler, the redgreen gem makes all those little repetitive dots that much more bearable.

    Use autotest. It takes your mind off the testing and lets you focus on the tests.

    FasterCSV is a gem you can get from the standard repository. One of our data feeds is delivered as Excel, which is basically completely useless. So we export it to CSV then import it with FasterCSV, which can nail about 5000 records in 2 seconds. Awesome.

    Mocha is a library for stubbing and mocking tests. Because of it, I’ve slowly been moving over to a fixture-less state of mind. There’s all sorts of information out there on using Mocha, so dive right in.

    First made known to me by technoweenie, Piston is an incredible tool for managing Rails plugins. We had troubles when deploying with svn:externals set: our server would try to make too many connections to the external SVN server too quickly. As a result, the connections would die and the deploy would fail. Piston solved this issue: we can keep plugins in our local repository and sync them up with their remote repository whenever we want. So far it’s been working out wonderfully, and so easy! If you haven’t started yet, get on the Piston bandwagon immediately.

    Editing

    Sorry, I admit it: I’m not a TextMate guy. Sure, I’m writing this post in TextMate, but I still prefer vim for coding. I know there’s at least one duder out there who can side with me. For that one guy (you know who you are), here’s a few classy vim links: the wiki page, the gem, the Rails plugin, and the html-macros plugin. All essential.

    Outliers

    Like everyone else, we use Subversion and Trac to manage our source code and tickets. We also use Marshmallow and a polling Ruby process to notify us of changes to Subversion. It’s helpful to see the commits in Campfire because they’re always there, whether you are or not.

    We recently started using Crazy Egg to check out clicks on our site and wow wow wow. Everyone has already talked about it, but I suggest trying it out. So easy, and it gives you an entirely new perspective on your site.

    Hammers and Tape

    So that’s my toolbox. Maybe I missed a few screws or rulers, sure, but you get the general idea. Have any really great levels you want to share? Hit me in the comments.

  • “Cheat Again!”
    – admin on October 15th, 2006

    This post is tagged with ruby.
    It has zero comments

    It wasn’t so long ago that Cheat! was loosed upon the world. We started with five (5) sheets. That’s one hand. Hold up one hand. We now have forty-two (42). That’s eight hands and two fingers. Hold up eight hands and then another hand, but only put up two fingers on the ninth hand. Ha! You can’t! That’s not nearly possible.

    forty two

    Today marks the proud release of Cheat 1.1.0. A few things have changed, but don’t let that get in your way. Upgrade immediately:

    $ sudo gem update cheat

    or

    $ sudo gem install cheat

    Command Line Editing

    This is the real big change. It separates the men from the boys, the 1.1.0s from the 1.0.3s, etc. You can now edit and create cheat sheets straight from the command line.

    Edit the rjs cheat sheet:

    $ cheat rjs --edit

    Create the nonsense cheat sheet:

    $ cheat nonsense --add

    By default, Cheat will try to use your VISUAL or EDITOR environment variable to determine which editor to launch. If it can’t find either variable, it tries to launch vim.

    To edit using TextMate:

    $ export VISUAL=’mate -w’

    Go ahead and stick that in .bashrc or whatever init script your shell uses to ensure $ cheat --edit and $ cheat --add always use TextMate.

    Windows users: this new functionality could use some testing, bugfixing, and patching. All I knows is it works on POSIX.

    One million thanks to the mysterious commenter known only as nornagon who submitted the patch. Who is this phantom coder?

    Tabbin’ Around

    King of bash Kevin Marsh, perhaps inspired by a bit of past rakery, conjured some tab completion magic for Cheat.

    # Cache, and complete, Cheats
    if [ ! -r ~/.cheats ] | [[ ! "" == `find ~ '.cheats' -ctime 1 -maxdepth 0` ]]; then
      echo "Rebuilding Cheat cache... " 
      cheat sheets | egrep '^ ' | awk {'print $1'} > ~/.cheats
    fi
    complete -W "$(cat ~/.cheats)" cheat
    

    POSIXers can stick that in your .bashrc (or wherever) and enjoy sweet, sweet tab completion. Go ahead and try it: restart you shell then do $ cheat m<TAB>. You should see a glorious list of sheets.

    Thanks to Evan, the tab completion list will refresh itself every day.

    Printing

    Once again in the news, up-and-coming all star Kevin Marsh fandangled a print style sheet for our lovely cheat sheets. There’s nothing to it: surf to a cheat sheet and hit ‘print.’ Like normal.

    print

    That totally defeats the purpose, but I don’t care. Print away!

    Captcha

    Using Andreas Schwarz’s Captchator web service, the Cheat wiki now has a captcha. The damn spammers kept reverting and messing with sheets so, for now, this seems the best solution (next to the other solution I’ll talk about in a second).

    captcha

    It shouldn’t really get in your way all that much. Let me know if it does. Hats off to Andreas for the simple service.

    Remember!

    We still have TextMate and jEdit integration, among other tricks, at the original Cheat post. And then there’s the RSS feed, too.

    rss

    As always, it’s never too late to write about or contribute to Cheat. You might think this is an ego thing, but it’s not: the more cheat sheets everyone makes, the easier all our lives become. What would you rather be doing, drinking tequila on a secluded Mexican beach or slaving away in your cubicle? That’s what I thought. Get to work.

  • “Rake Around MySQL”
    – admin on October 14th, 2006

    This post is tagged with rails.
    It has zero comments

    In the dark ages of web programming, we’d write various Perl, PHP, and shell scripts to maintain our application. Something to clean up the sessions, something to send out our newsletter every night, something to rename files en masse. We’d scatter them throughout our directory tree, like ninja stars thrown blindly at that old hickory down by the river. Then, one day, the brash new kid up and duplicates all of them on his own because he has no idea they exist. How could he?! We have no one to blame but ourselves.

    Fortunately, this is the renaissance. We have Rake. Aside from freezing gems, managing Subversion, and rake remigrating, we can also use Rake to help make our shellin’ lives easier.

    While you probably use a gooey to manhandle your database (I fancy YourSQL), sometimes you just need to hit the mysql shell and hit it hard. For those occasions I really want to not worry about the host, port, or showing my credentials. I just want to shell in and get on with my business. Something like $ rake production mysql would be nice.

    Two steps, here they are.

    environments.rake

    So, you can chain rake tasks. Run $ rake task1 task2 and the tasks will be executed in that order. As a result, I want to create some environment tasks which set the RAILS_ENV for me. That way I can $ rake production migrate instead of having to $ rake migrate RAILS_ENV=production. I switch environments so much that this little nicety is worth it. And it has another advantage, which I’ll tell you about later.

    Anyway, define a task for each of your environments and slip them into something like, say, RAILS_ROOT/lib/tasks/environments.rake.

    Here’s my environments.rake:

    desc "Runs the following task in the development environment" 
    task :development do
      RAILS_ENV = ENV['RAILS_ENV'] = 'development'
    end
    
    desc "Runs the following task in the production environment" 
    task :production do
      RAILS_ENV = ENV['RAILS_ENV'] = 'production'
    end
    
    desc "Runs the following task in the staging environment" 
    task :staging do
      RAILS_ENV = ENV['RAILS_ENV'] = 'staging'
    end
    
    desc "Runs the following task in the test environment" 
    task :test do
      RAILS_ENV = ENV['RAILS_ENV'] = 'test'
    end
    
    task :dev do
      Rake::Task["development"].invoke
    end
    
    task :prod do
      Rake::Task["production"].invoke
    end
    

    Not super-DRY, but there’s a reason for that. I promise.

    Now we can prefix any task with an environment instead of having to fumble with RAILS_ENV ourselves. Joy.

    mysql.rake

    The second part of our $ rake production mysql task (which we want to launch us into a shell connected to our production MySQL server) is the actual mysql task.

    Here’s RAILS_ROOT/lib/tasks/mysql.rake, which we use to build and execute a mysql shell command using the configuration options set in database.yml. Using the config file ensures our mysql rake task is always synced up with our Rails application’s db settings.

    def sh_mysql(config)
      returning '' do |mysql|
        mysql << mysql_command << ' '
        mysql << "-u#{config['username']} " if config['username']
        mysql << "-p#{config['password']} " if config['password']
        mysql << "-h#{config['host']} "     if config['host']
        mysql << "-P#{config['port']} "     if config['port']
        mysql << config['database']         if config['database']
      end
    end
    
    def mysql_command
      'mysql'
    end
    
    desc "Launch mysql shell.  Use with an environment task (e.g. rake production mysql)" 
    task :mysql do
      system sh_mysql(YAML.load(open(File.join('config', 'database.yml')))[RAILS_ENV])
    end
    

    If you need to use something other than `mysql’ (maybe you need to include the full path?), just replace the return value of the mysql_command method. Lucky for us this is easy, fast, and fairly safe: a the mysql shell command is nice enough to hide the password from ps aux. That part of the command will show as ‘-pXXXXX’. Fort Knox style.

    That’s it. Now you can $ rake prod mysql or $ rake mysql or whatever, all day long. You barely need to code anymore.

    Tab-leeting It

    Since tab completion is all the rage these days, I thought it’d be nice to dig through the Internet and find out how to get it rolling on rake.

    What I came across was a chain letter ending at this onrails.org post. It rules! But unfortunately, it’s slow. Real slow. Is my laptop really that old already? Damn.

    The problem is that rake is being run each time we hit tab. Here’s my sped up version:

    #!/usr/bin/env ruby
    
    # Save this somewhere, chmod 755 it, then add
    #   complete -C path/to/this/script -o default rake
    # to your ~/.bashrc
    #
    # If you update your tasks, just $ rm ~/.raketabs*
    #
    # Adapted from 
    # http://onrails.org/articles/2006/08/30/namespaces-and-rake-command-completion
    
    exit 0 unless File.file?(File.join(Dir.pwd, 'Rakefile'))
    exit 0 unless /^rake\b/ =~ ENV["COMP_LINE"]
    
    def rake_silent_tasks
      if File.exists?(dotcache = File.join(File.expand_path('~'), ".raketabs-#{Dir.pwd.hash}"))
        File.read(dotcache)
      else
        tasks = `rake --silent --tasks`
        File.open(dotcache, 'w') { |f| f.puts tasks }
        tasks
      end
    end
    
    after_match = $'
    task_match = (after_match.empty? || after_match =~ /\s$/) ? nil : after_match.split.last
    tasks = rake_silent_tasks.split("\n")[1..-1].map { |line| line.split[1] }
    tasks = tasks.select { |t| /^#{Regexp.escape task_match}/ =~ t } if task_match
    
    # handle namespaces
    if task_match =~ /^([-\w:]+:)/
      upto_last_colon = $1
      after_match = $'
      tasks = tasks.map { |t| (t =~ /^#{Regexp.escape upto_last_colon}([-\w:]+)$/) ? "#{$1}" : t }
    end
    
    puts tasks
    exit 0
    

    You can grab it with wget http://pastie.caboo.se/17743/text.

    Next stick this into .bashrc or whatever init script your shell hits:

    complete -C path/to/script/you/downloaded -o default rake

    Now restart your shell, then cd to a Rails project. Next type rake db:<tab>. Wait a second. Did all the tasks show up? Awesome. Hit tab again. Only the first time should be slow.

    Improvements? Bug fixes? Let’s hear ‘em.

    Love It

    Rake is real cool. Beyond having namespaces and method_missing, it’s got built in support for building gems. And hey, if you look in rake/lib/rake/contrib you can even find files such as rubyforgepublisher.rb for, you guessed it, publishing to Rubyforge automatically. What can’t this kid do?

  • “Strut Your Structs”
    – admin on September 26th, 2006

    This post is tagged with ruby.
    It has zero comments

    I am sitting in the airport right now, waiting for my twice delayed flight to board. The security man took my Scope. He does not want my teeth to be clean or my breath to be fresh. So be it.

    Here’re two classes you may or may not have seen before. Unlike, say, a small bottle of refreshing mouth wash, you can definitely take these guys on an airplane.

    Struct

    Struct is like Ruby on Rails for classes. Scaffolding. It lets you quickly cobble together a new class with some gettable-settable attributes. You can then instantiate your struct’d class and go nuts. You don’t even need to require anything—Struct is right there, ready and willing, all the time.

    When I was little we had a black lab named Tucker. He was a dog with four legs and two eyeballs.

    >> Animal = Struct.new(:name, :legs, :eyeballs)
    => Animal
    >> tucker = Animal.new('dog', 4, 2)
    => #<struct Animal name="dog", legs=4, eyeballs=2>
    >> tucker.legs
    => 4
    >> tucker.legs = 3
    => 3
    >> tucker.legs
    => 3
    

    Wow, that’s quick. We didn’t have to set up a class definition or fumble with attr_accessor. But there’s more.

    >> tucker[0]
    => "dog" 
    >> tucker['name']
    => "dog" 
    >> tucker[:name]
    => "dog" 
    >> tucker[:name] = 'doggy'
    => "doggy" 
    >> tucker.name  
    => "doggy" 
    

    It’s like Array and Hash got married and had a little classling. Okay, that’s not true, but it sure looks that way from the outside. I wonder if that means Struct objects are enumerable?

    >> tucker.is_a? Enumerable
    => true
    >> tucker.methods.grep /each/
    => ["each_with_index", "each", "each_pair"]
    

    How unexpected. And it’s ordered, too—the order you passed your keys when creating your struct’d class is maintained. In this case the order is name, legs, then eyeballs. Always.

    >> tucker.each_pair { |k,v| puts "key: #{k}, value: #{v}" } 
    key: name, value: dog
    key: legs, value: 4
    key: eyeballs, value: 2
    => #<struct Animal name="dog", legs=4, eyeballs=2>
    >> tucker.each { |v| puts v } 
    dog
    4
    2
    => #<struct Animal name="dog", legs=4, eyeballs=2>
    

    For reference:

    >> (tucker.methods & Enumerable.instance_methods).sort
    => ["all?", "any?", "collect", "detect", "each_with_index", "entries", 
    "find", "find_all", "grep", "include?", "inject", "map", "max", "member?",
    "min", "partition", "reject", "select", "sort", "sort_by", "to_a", "zip"]
    

    Since a struct’d class is not actually a Hash or an Array, there are differences. A Struct instantiated object has no keys—it has members.

    >> tucker.members
    => ["name", "legs", "eyeballs"]
    >> tucker.values
    => ["doggy", 4, 2]
    

    Also: you can not add new members arbitrarily as you can with an Array or a Hash. However many members you instantiate your struct’d class with is however many you’re stuck with. As a result: no merge, update, or concat (+). This is a class, after all. Not a fancy faux-primitive.

    If you need to turn a Struct object into a Hash, here’s an easy way:

    >> hash = Hash[*tucker.members.zip(tucker.values).flatten]
    => {"name"=>"doggy", "eyeballs"=>2, "legs"=>4}
    

    Maybe wrap that in a to_hash if you’d like. For to_a, just use values.

    It’s worth noting that since everything in Ruby is an object, we can inherit straight from a call to Struct.new. Maybe we want to add some questions?

    class Primate < Struct.new(:name, :legs, :eyeballs)
      def legs?
        (legs && legs.to_i > 0) ? true : false
      end
    end
    

    Imagine the possibilities. Fake-out ActiveRecord objects in your tests! Mock hashes that are super indifferent! Create resources which can only be described as active! Amaze your friends!

    OpenStruct

    OpenStruct is a bit more liberal than Struct, with less structure. Like that hippie cousin everyone has. (in my family it’s me)

    Usage: pass a hash to OpenStruct.new, receive an object with full-on getters and setters. Because this behavior is a bit suspect we must explicitly require OpenStruct (ostruct) from the standard library. Like YAML.

    >> require 'ostruct'
    => true
    >> scope = OpenStruct.new(:brand => 'Scope', :owner => 'P&G', :flavor => 'Cinnamon Ice')
    => #<OpenStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    >> scope.brand
    => "Scope" 
    >> scope.brand = 'Scoped'
    => "Scoped" 
    >> scope.brand
    => "Scoped" 
    

    Unlike Struct, we can add new members to an OpenStruct object whenever we’d like.

    >> scope.tasty
    => nil
    >> scope.tasty = true
    => true
    >> scope.tasty
    => true
    

    This is accomplished through some method_missing-like magic. Unfortunately, this means all your NoMethodError exceptions go away.

    >> scope.something_fake
    => nil
    

    Not the biggest deal. That’s how hashes work when you try to access a non-existent key, after all. And there’s always good ol’ respond_to:

    >> scope.respond_to? :something_fake
    => false
    

    So that’s all pretty cool. I’ve been playing with this class in my code lately. Mainly in tests to fake out ActiveRecord stuffs, as I suggested with Struct. There is one major disadvantage to OpenStruct: it’s not enumerable. Less structure, see. You can not each, you can not members, you can’t even values. Well. I mean. Out of the box you can’t.

    Enum-o-Struct

    What we’re going to do is make OpenStruct enumerable by adding the each method, the members method, and including the Enumerable module.

    require 'ostruct'
    
    class EnumoStruct < OpenStruct
      include Enumerable
    
      def members
        methods(false).grep(/=/).map { |m| m[0...-1] }
      end
    
      def each
        members.each do |method|
          yield send(method)
        end
        self
      end
    end
    

    Cryptic? Hardly.

    We want an each method. How are we going to enumerate over an OpenStruct? Well, we could always just pass the return value of each getter-method to whatever block is passed to our each method. If that’s our approach, it looks like we need a way to find all the members. All the getters.

    While an OpenStruct object may have a ton of arbitrary methods, it has setters which end in an equal sign and almost always correspond to a getter. The members method here finds all methods ending in = on an object by grabbing an array of methods excluding those defined on parent/ancestor classes (methods(false)) and creating an array of methods which match a regular expression, using grep. It then strips the last character from each found method, the =, returning our array of members.

    The members from our scope example: [“flavor”, “owner”, “brand”]

    The Enumerable module depends on your class implementing each to work its magic. The concept is simple: go through each of the member methods and call the method, passing its value into the block passed to each using yield. In this case, send(method) is the same as, for example, self.flavor or self.brand.

    Notice we return self in our each method: it’s polite for each to return self so that calls to it may be chained.

    In action:

    >> require 'enumostruct'
    => true
    >> scope = EnumoStruct.new(:brand => 'Scope', :owner => 'P&G', :flavor => 'Cinnamon Ice')
    => #<EnumoStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    >> scope.each { |v| puts "value: #{v}" }
    value: Cinnamon Ice
    value: P&G
    value: Scope
    => #<EnumoStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    

    Oh that’s delicious. Remember, now we have all the Enumerable methods.

    >> scope.map { |v| v }
    => ["Cinnamon Ice", "P&G", "Scope"]
    >> scope.detect { |v| v =~ /&/ }
    => "P&G" 
    

    Hey, I liked that each_pair method that Struct gives you. Let’s add it before we go.

    class EnumoStruct < OpenStruct
      def each_pair
        members.each do |method|
          yield method, send(method)
        end
        self
      end
    end
    
    >> scope.each_pair { |k,v| puts "key: #{k}, value: #{v}" } 
    key: flavor, value: Cinnamon Ice
    key: owner, value: P&G
    key: brand, value: Scope
    => #<EnumoStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    

    Same as each, really. The only difference is we pass the name of the current member into the block as the first argument. Cake.

    Bye!

    Ah, we’ve accomplished so much. My only hope is you can truly find a use for one of these humble classes. They are not flammable and pose no threat to your fellow passengers.

  • “Content For Whom?”
    – admin on September 16th, 2006

    This post is tagged with rails.
    It has zero comments

    Larry is building a personal home page. He’s using Ruby on Rails and fla-fla-flying. Why, at the rate he’s going, link groups and banner exchanges will be begging him to join in no time flat.

    Meet Larry.

    Just recently he was Googling and came across a post on Err the Blog titled Content for Whom? While the writer of said blog is a bit of a flake, Larry was able to make his site more dynamic and DRY using content_for after reading the post. Here’s how.

    The Sidebar

    We all know Rails compiles views before it compiles the layout. That is, if you set @page_title in your view, you may reference it in your layout. Something like <title>My Site :: <%= @page_title %></title>. content_for is no different: it allows you to ‘assign’ a chunk of HTML or Ruby to a symbol which may be referenced in your layout.

    Here’s Larry’s layout:
    <html>
    <head><title>Larry's Personal Home Page</title></head>
    <body>
      <table width="100%">
        <tr><td>
          <%= yield %>
        </td><td>
          <%= yield :sidebar %>
        </td></tr>
        <tr><td colspan="2" align="center">
          Copyright (c) 2006 Larry
        </td></tr>
      </table>
    </body>
    </html>
    

    The first yield, clearly, will be replaced with Larry’s view when rendered. The second yield is a bit different: he’s passing it a symbol, saying he wants to yield :sidebar at that point in time—whatever :sidebar may be.

    Let’s look at Larry’s view:
    <font color="red">Welcome to my website.</font><br>
    <img src="under_construction.gif"><br>
    <font color="blue">It was coded by hand in notepad.</font><br>
    <font color="green">Tell me what you <blink>think!!</blink></font><br>
    
    <% content_for :sidebar do %>
      Hot Links<br>
      <a href="http://www.netscape.com">Netscape</a><br>
      <a href="http://www.lycos.com">Lycos</a><br>
      <a href="http://www.walmart.com">Wal Mart</a><br>
    <% end %>
    

    You got it, right? yield :sidebar is going to be replaced by whatever block is passed to content_for :sidebar. In this case, Larry’s sidebar.

    Ah, solidarity:

    submit_to_popup form

    That’s fairly useful, but it means Larry has to set :sidebar in every view if he wants one to always show up. A nice thing to do is provide a default.

    The Conditional

    Larry just needs to replace, in his layout,
    <%= yield :sidebar %>
    

    with

    <%= (sidebar = yield :sidebar) ? sidebar : render(:partial => 'shared/sidebar') %>
    

    Now shared/sidebar will always be rendered unless a view defines :sidebar with content_for. So, you can override a default element of the layout. Nice.

    As an aside, you can access @content_for_sidebar directly but that’s not legit. Deprecated.

    The Faux Nested Layout

    Imagine Larry has a whole bunch of controllers. He is happy with the default sidebar trick but there’s still an issue: he wants all the HomeController views to have a standard sidebar and all the UsersController to have a different standard sidebar.

    One idea is to remove the shared/ from the conditional in his layout:

    <%= (sidebar = yield :sidebar) ? sidebar : render(:partial => 'sidebar') %>
    

    Now either home/_sidebar.rhtml or users/_sidebar.rhtml will be rendered depending on which controller you’re in. The problem is Larry loses his ‘catch-all’ default sidebar and is forced to create a _sidebar.rhtml file for every controller. Not cool.

    I tell Larry, “Throw away that code and change it back to the conditional which uses shared/sidebar. Now, think about layouts. If you have a HomeController and home.rhtml exists in your views/layouts/ directory, Rails will render that. Perfect place to define a home-wide sidebar, yeah?”

    views/layouts/home.rhtml:
    <% content_for :sidebar do %>
      Cool Pages on My Site:<br>
      <a href="/home/lonely">LonelyGrrl Videos</a><br>
      <a href="/home/pagerank">My Google Pagerank</a><br>
      <a href="/home/web2">Web 2.0 Logo Generator (lol)</a>
    <% end %>
    <%= render :file => 'layouts/application' %>
    

    This ‘layout’ file is setting :sidebar then rendering the application layout. Larry just needs to do the same thing for views/layouts/users.rhtml and he’s all set.

    Let’s recap. Larry can now define content_for :sidebar in a view, define it for a whole set of controller views, and also have a site-wide default. All at the same time. Talk about power!

    One caveat here is that content_for appends content. It don’t replace it. If you’re defining :sidebar in a view under views/home/, using the above example, you’re going to end up with what looks like two sidebars: the one in the home.rhtml layout file and the one in your specific view.

    And hey, if you want real nested layouts there’s a plugin for Ruby on Rails available. So get that.

    The Stylesheet

    Years pass. Larry learns a bit of CSS and begins working for a big, corporate website. Special promotions are demanded by advertisers: they want to advertise on pages which have their own unique style with which “the kids” can identify. Something street. Like those PSP graffiti ads.

    Larry, now a seasoned Rails pro, knows this is not a problem. content_for, after all. He comes up with this:

    <html>
    <head>
    <title>Big Corporate Website 4.0 Beta</title>
    
    <%= stylesheet_link_tag 'main' %>
    <%= yield :stylesheet %>
    </head>
    <body>
      <%= yield %>
    </body>
    </html>
    

    See it? If there’s no content_for :stylesheet, everything works. However, Larry can also bust out a content_for :stylesheet in any view to add a bit more spice to that particular page. Like:

    <% content_for :stylesheet do %>
      <%= stylesheet_link_tag 'railsconf' -%>
    <% end %>
    
    <h1>Welcome to our RailsConf information page!</h1>
    

    Now his h1’s can be as nutty as all hell and won’t affect the other h1’s in his meticulously crafted website. He can even change the background color. Something pastel, I’d imagine.

    The Fin

    All of this content_for magic lovingly wraps ActionView’s capture method. The Rails API has the scoop. Just try to remember :layout is off limits when dealing with content_for. If you use it, you’ll just be messing with what gets displayed in your layout’s vanilla <%= yield %> call.

    The web’s really all about HTML, amirite? I dig the stuff Bruce is doing and hey, SimplyHelpful looks like a step in the right direction. My memories of Smarty are cherised, but ActionView has stolen my heart.

  • Older

Projects

  • Cheat! Sheets
  • require 'errtheblog'
  • acts_as_cached
  • acts_as_textiled
  • tagurit [micrOformats]

Past Wanderings

  • first post!
  • Block to Partial
  • acts_as_textiled
  • Colored Tests
  • Accessor Missing
This is Err, the weblog of PJ Hyett and Chris Wanstrath.
All original content copyright ©2006 the aforementioned.