Friday, May 13, 2011

RSpec 2 matcher to test layout rendering in Rails 3

In Rails 3 there seems to be no way to determine which layout(s) were rendered. There is assert_template but it doesn't seem to accept the :layout option like the source code would suggest. And where do you call that method anyway? So I wrote a custom matcher along with a bit that integrates into ActionView so that we can detect when a layout is rendered and store a reference to it on the controller.

Not that this doesn't support multiple layout rendering. Only the last layout rendered is stored.

Take a look at the Example Usage in the comments above the matcher for usage information.

Monday, March 28, 2011

[solved] installing passenger 3.0.5 apache2 module on OS X gives 'missing required architecture ppc64 in file'

I was getting odd compilation errors trying to install the Passenger 3.0.5 apache2 module on my Mac running OS X. I worked through the problems and you just need to edit a couple of files to solve the problem.
gem install passenger rvmsudo passenger-install-apache2-module
mkdir -p ext/apache2/module_libboost_oxt
g++ -Iext -fPIC -fvisibility=hidden -DVISIBILITY_ATTRIBUTE_SUPPORTED -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -I/usr/include/apr-1 -I/usr/include/apr-1 -I/usr/include/apache2 -arch i386 -arch ppc -arch x86_64 -arch ppc64 -D_REENTRANT -I/usr/local/include -DHASH_NAMESPACE="__gnu_cxx" -DHASH_FUN_H="" -DHAS_SFENCE -DHAS_LFENCE -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS -o ext/apache2/module_libboost_oxt/aggregate.o -c ext/apache2/module_libboost_oxt/aggregate.cpp
mkdir -p ext/apache2/module_libboost_oxt
rm -rf ext/apache2/module_libboost_oxt.a
ar cru ext/apache2/module_libboost_oxt.a ext/apache2/module_libboost_oxt/aggregate.o
ranlib ext/apache2/module_libboost_oxt.a
gcc -Iext -Iext/common -fPIC -fvisibility=hidden -DVISIBILITY_ATTRIBUTE_SUPPORTED -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -I/usr/include/apr-1 -I/usr/include/apr-1 -I/usr/include/apache2 -arch i386 -arch ppc -arch x86_64 -arch ppc64 -D_REENTRANT -I/usr/local/include -DHASH_NAMESPACE="__gnu_cxx" -DHASH_FUN_H="" -DHAS_SFENCE -DHAS_LFENCE -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS -o ext/apache2/mod_passenger.o -c ext/apache2/mod_passenger.c
g++ -Iext -Iext/common -fPIC -fvisibility=hidden -DVISIBILITY_ATTRIBUTE_SUPPORTED -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -I/usr/include/apr-1 -I/usr/include/apr-1 -I/usr/include/apache2 -arch i386 -arch ppc -arch x86_64 -arch ppc64 -D_REENTRANT -I/usr/local/include -DHASH_NAMESPACE="__gnu_cxx" -DHASH_FUN_H="" -DHAS_SFENCE -DHAS_LFENCE -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS -o ext/apache2/Configuration.o -c ext/apache2/Configuration.cpp
g++ -Iext -Iext/common -fPIC -fvisibility=hidden -DVISIBILITY_ATTRIBUTE_SUPPORTED -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -I/usr/include/apr-1 -I/usr/include/apr-1 -I/usr/include/apache2 -arch i386 -arch ppc -arch x86_64 -arch ppc64 -D_REENTRANT -I/usr/local/include -DHASH_NAMESPACE="__gnu_cxx" -DHASH_FUN_H="" -DHAS_SFENCE -DHAS_LFENCE -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS -o ext/apache2/Bucket.o -c ext/apache2/Bucket.cpp
g++ -Iext -Iext/common -fPIC -fvisibility=hidden -DVISIBILITY_ATTRIBUTE_SUPPORTED -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -I/usr/include/apr-1 -I/usr/include/apr-1 -I/usr/include/apache2 -arch i386 -arch ppc -arch x86_64 -arch ppc64 -D_REENTRANT -I/usr/local/include -DHASH_NAMESPACE="__gnu_cxx" -DHASH_FUN_H="" -DHAS_SFENCE -DHAS_LFENCE -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS -o ext/apache2/Hooks.o -c ext/apache2/Hooks.cpp
g++ -flat_namespace -bundle -undefined dynamic_lookup ext/apache2/Configuration.o ext/apache2/Bucket.o ext/apache2/Hooks.o ext/apache2/mod_passenger.o -fPIC -o ext/apache2/mod_passenger.so -fPIC -fvisibility=hidden -DVISIBILITY_ATTRIBUTE_SUPPORTED -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -I/usr/include/apr-1 -I/usr/include/apr-1 -I/usr/include/apache2 -arch i386 -arch ppc -arch x86_64 -arch ppc64 -D_REENTRANT -I/usr/local/include -DHASH_NAMESPACE="__gnu_cxx" -DHASH_FUN_H="" -DHAS_SFENCE -DHAS_LFENCE -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS ext/apache2/module_libpassenger_common.a ext/apache2/module_libboost_oxt.a -fPIC -L/usr/lib -lapr-1 -L/usr/lib -laprutil-1 -lpthread  
ld: warning: in /usr/lib/bundle1.o, missing required architecture ppc64 in file
ld: warning: in /usr/lib/libapr-1.dylib, missing required architecture ppc64 in file
ld: warning: in /usr/lib/libaprutil-1.dylib, missing required architecture ppc64 in file
ld: warning: in /usr/lib/libpthread.dylib, missing required architecture ppc64 in file
ld: warning: in /usr/lib/gcc/powerpc-apple-darwin10/4.2.1/libstdc++.dylib, missing required architecture ppc64 in file
ld: warning: in /usr/lib/libSystemStubs.a, missing required architecture ppc64 in file
ld: warning: in /usr/lib/libSystem.dylib, missing required architecture ppc64 in file
ld: symbol dyld_stub_binding_helper not defined (usually in crt1.o/dylib1.o/bundle1.o) for architecture ppc64
collect2: ld returned 1 exit status
lipo: can't open input file: /var/tmp//ccPTqixF.out (No such file or directory)
rake aborted!
Command failed with status (1): [g++ -flat_namespace -bundle -undefined dyn...]

Fix the compilation errror

cd /Users/karl/.rvm/gems/ree-1.8.7-2010.02/gems/passenger-3.0.5 vi ./lib/phusion_passenger/platform_info/apache.rb
Change line 215 to: architectures = ["-arch i386 -arch x86_64"] Then restart Apache. Now in your apache logs you might see the following error:
/Users/karl/.rvm/gems/ree-1.8.7-2010.02/gems/passenger-3.0.5/lib/phusion_passenger/platform_info/ruby.rb:166:in `expand_path': couldn't find HOME environment -- expanding `~/.rvm' (ArgumentError)
 from /Users/karl/.rvm/gems/ree-1.8.7-2010.02/gems/passenger-3.0.5/lib/phusion_passenger/platform_info/ruby.rb:166:in `_unmemoized_rvm_path'
 from /Users/karl/.rvm/gems/ree-1.8.7-2010.02/gems/passenger-3.0.5/lib/phusion_passenge

Fix the missing HOME env variable

Hard-code the path to your HOME directory.
vi /Users/karl/.rvm/gems/ree-1.8.7-2010.02/gems/passenger-3.0.5/lib/phusion_passenger/platform_info/ruby.rb
Edit #164 to [ENV['rvm_path'], "/Users/karl/.rvm", "/usr/local/rvm"].each do |path| Now restart Apache with sudo apachectl -k restart and everything should be working fine!

To find the location of your gem files you can echo $GEM_HOME. Keep in mind you will need to substitute your own HOME path in the above command.

Tuesday, December 15, 2009

Authenticate your Authlogic users in Rails Metal

Update 2010-03-17: Don't fail in test mode if we can't find a user

I haven't been able to find any information on authenticating users in Rails Metal controllers. So I took a look at what was available in the session and worked with that. I'm not an expert on Authlogic (I did write an Authlogic Add-On a while ago) but this seems like a good solution. Let me know if you know of any problems with this approach.

I added a method to UserSession (or add it to whatever class you use for your sessions) that takes the Metal env and checks the authentication parameters and returns the user record. I don't bother with updating the user's last_request_at attributes or anything. I had to bypass the Authlogic session entirely because Authlogic needs to be activated with a controller before you can use the session stuff. I considered dummying the controller by including authlogic/test_case but I don't know enough about what the dummy controller does regarding authentication to go this route.

And the code...

Here's the Metal "controller":

A couple neat things in the Metal controller are the use of ActionController::Request.new(env) so we have access to request.remote_ip and other helper methods this class provides.

The part where I validate the user is:

Tuesday, December 8, 2009

Silence the Rails logger as well as the SQL logs

It's one thing to turn off logging in Rails, it's another to prevent SQL statements from appearing in your logs.

I've written a Rake helper to silence both. You can adapt it for other uses, but I think it's mostly relevant in Rake tasks.

# lib/rake_helpers.rb
module Rake
  module Helpers
    # Silence the Rails logger as well as the SQL logger
    # Call with a block e.g
    # silent { my block code }
    def silent
      ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
        alias_method :original_log_info, :log_info
        def silent_log_info(*args); end
        alias_method :log_info, :silent_log_info
      end
      @@old_logger = ActiveRecord::Base.logger
      ActiveRecord::Base.logger = Class.new { def method_missing(*args); end; }.new
      yield
      ActiveRecord::Base.logger = @@old_logger
      ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
        alias_method :log_info, :original_log_info
      end 
    end
  end
end
And sample usage:
# lib/tasks/db.rake
require 'rake_helpers'
include Rake::Helpers

namespace :db do
  namespace :update do

    desc "Update counter caches"
    task :counters => :environment do
      silent do
        ActiveRecord::Base.transaction do
          [Artist, Album].each do |model| 
            model.find_in_batches(:batch_size => 5000) do |batch|
              batch.each { |record| record.update_counts }
            end
          end
        end
      end
    end
  end
end

Friday, December 4, 2009

Rails Metal real-world example with RSpec integration test

There's not that much information out there about Rails Metal, and even less about how to test Metal classes using RSpec. If I had time I would create a new RSpec ExampleGroup with a lot of the goodness of the ControllerExampleGroup, but alas I don't.

So integration tests will have to do. I had problems with using response.should be_success. The docs say it exists, but it doesn't for me. YMMV.

Anyways, here is my Metal class along with the integration test:

Thursday, December 3, 2009

Bash script to easily check and switch between Rails environments. Usage: renv [p|d|t|s]

When I'm on our deployment server I'm constantly checking the RAILS_ENV environment variable to make sure that I don't accidentally do something on the production database when I meant to do it on the staging database. (Incidentally I’ve added some stuff to the Rakefile to guard against such a thing: Rakefile: Prevent destructive rake tasks in the production environment.). So I created this handy Bash function to check and switch between your Rails environments easily.

Sample usage:

$ renv
RAILS_ENV is unset
$ renv p
RAILS_ENV set to production
$ renv d
RAILS_ENV set to development
$ renv s
RAILS_ENV set to staging
$ renv t
RAILS_ENV set to test
$ renv
RAILS_ENV is test

And the code:

# Put this in your ~/.profile
function renv() {
  rails_env_p="production" 
  rails_env_d="development" 
  rails_env_s="staging" 
  rails_env_t="test" 

  if [ $1 ];then
    eval 'rails_env=${rails_env_'"${1}"'}' 
    export RAILS_ENV=$rails_env
    echo "RAILS_ENV set to $RAILS_ENV" 
  elif [ $RAILS_ENV ];then
    echo "RAILS_ENV is $RAILS_ENV" 
  else
    echo "RAILS_ENV is unset" 
  fi
}

Rakefile: Prevent destructive rake tasks in the production environment.

We are using the same server for our staging and production deploys. The problem is that the default RAILS_ENV is set to production so when I go to the staging app and start messing about with rake db:seed (or worse, rake db:reset) I am almost always a hair's breadth away from imminent death.

(Here's a handy bash script I wrote to easily switch between your Rails environments.)

So I decided to do something about it and protect destructive or database altering rake tasks from being run in the production environment. I really like this approach and I think I'll use it in future projects regardless of the setup. The fact is you only do a rake db:reset in production once, during the cold deploy of your app. It allows rake db:migrate and its variants, but not rake db:migrate:reset. Look at the code for the full list of restricted rake tasks. The tasks in PROTECT_SOME are matched explicitly, PROTECT_ALL restricts all variants of that task.

This shows the warning message and confirmation that appear. If you know what you are doing, or you want to automate things with Capistrano and need to run one of the protected tasks just pass FORCE=true to the task.

[deployer@rails1 ~/rails_apps/rails1.creagency.com.au/current] echo $RAILS_ENV
production
[deployer@rails1 ~/rails_apps/rails1.creagency.com.au/current] rake db:reset
(in /www/rails_apps/rails1.creagency.com.au/releases/20091204033227)
****************************************************************************
* WARNING! You are in the PRODUCTION environment and are running a Rake task 
* that will DESTROY your PRODUCTION database!
* 
* If you know what you are doing you can run this task with FORCE=true to
* prevent this message appearing.
****************************************************************************

Are you sure? (Yes|No) [No]

Quitting.
[deployer@rails1 ~/rails_apps/rails1.creagency.com.au/current] 
And here is the code. Just add it to the bottom of your application's Rakefile:

Solved: Capistro deploy fails with 'Permission denied (publickey).' but you know you have your permissions setup correctly

I spent a couple hours scratching me head one day when I couldn't deploy to the production server, but I could a little while ago.

The Capistrano deployment recipe had been changed to forward the ssh agent (which means that my local ssh key is the one that will be used by the deployment server to checkout the updated code from Git):

  ssh_options[:forward_agent] = true
Capistrano was complaining:
    servers: ["10.5.23.203"]
    [10.5.23.203] executing command
 ** [10.5.23.203 :: out] Permission denied (publickey).
 ** [10.5.23.203 :: out] fatal: The remote end hung up unexpectedly
The solution was just to add my SSH key to my local SSH agent:
$ ssh-add
Simple enough. Turns out you have to do that every time you restart your machine tho, which is a bit annoying. Any way to avoid that?

Cheers, Karl

Wednesday, December 2, 2009

Git: how to keep the master branch tidy, even if you've been working on it

In order to keep the master branch clean we want to avoid merges and "noisy" commits.

As you probably know, the best thing to do is to develop in a branch and rebase against master often (never rebase a remote branch). This ensures that your changes are always the latest changes applied to the index. And because they're applied on top of the index, when you merge them into the master branch and push, it's just a fast-forward update and you will never have to do a merge.

But what happens if you've been working on the master branch for a while? Is it too late? I just encountered this myself, and here's how you can keep the master merge-free in this situation.

On master

git fetch # have there been changes to master since you last fetched/pulled?  if not, you can just commit and push, otherwise...

git commit -a -m "awesome message here" # commit your changes
git branch wip           # create a new branch with your changes
git reset --hard HEAD~1  # reset the master branch to before your commit(s)
git checkout wip
continue working...
git fetch origin master
git rebase origin/master # rebase your changes on top of master often
all done...
git rebase -i origin/master # preferably squash your commits
git checkout master
git pull                 # make sure master has the latest code
git merge wip            # apply your fast-forward changes to the index
git push                 # quick before someone else does!

Can this process be shortened? Let me know if you have ideas.

Here are some excellent articles explaining some Git best-practices. There is some excellent info here, even from Linus himself!

Let me know if you have good Git articles. I'm still learning about Git, but now that I'm getting my head around it, it's getting better and better!

Karl

Sunday, November 15, 2009

Sass sprite mixin: making sprites super easy to use!

If you're like me, you love Haml and Sass!. And if you're a serious web developer trying to squeeze every last bit of performance out of your web application (let's pretend :), then you are already using image sprites, right?

I won't go into the benefits of using sprites, but apart from the obvious, I kinda like how they keep things organized. I don't need to worry about adding another small image to my application because it's not going to be yet another resource to bog things down, it goes in the sprite and probably only takes 1K. So that's nice. But they're just not as easy to use as a simple image...until now!

With my Sass mixin you can use a sprite image with just two lines of Sass.

The mixin:

// Add styles to an element to show the image in *image* at the given *x_pos* and *y_pos*.
// If the optional *x_size* or *y_size* arguments are passed in, the width and/or height 
// is set to that value.
=sprite(!image, !x_pos, !y_pos, !x_size=0, !y_size=0)
  :background-image = url(!image)
  :background-position = !x_pos !y_pos
  @if !x_size > 0
    :width = !x_size
  @if !y_size > 0
    :height = !y_size  
I'd recommend wrapping the mixin for your sprite image, because you're going to use it a lot. For example:
=player_sprite(!x_pos, !y_pos, !x_size=0, !y_size=0)
  +sprite("/images/player-sprite.png", !x_pos, !y_pos, !x_size, !y_size)
  
// Now define some styles using the player sprite
.playlists-icon
  +player_sprite(-600px, 0, 18px, 18px)
.recently-played-icon
  +player_sprite(-700px, 0, 18px, 18px)
.playing-now-icon
  +player_sprite(-800px, 0, 18px, 18px)
This will generate the following CSS
.playlists-icon {
  background-image: url('/images/player-sprite.png');
  background-position: -600px 0;
  width: 18px;
  height: 18px; }

.recently-played-icon {
  background-image: url('/images/player-sprite.png');
  background-position: -700px 0;
  width: 18px;
  height: 18px; }

.playing-now-icon {
  background-image: url('/images/player-sprite.png');
  background-position: -800px 0;
  width: 18px;
  height: 18px; }  
And your Haml to display the divs with the images is as simple as:
.playlists-icon
.recently-played-icon
.playing-now-icon
Awesome! Now that's easy!

If you found this useful, let me know!