Connecting a Windows VM to a Mac-based localhost Ruby on Rails site (Passenger)

Posted: January 20th, 2011 | Author: | Filed under: posts | Tags: , , , | 5 Comments »

Supporting Internet Explorer for your web app is a pain. At least, trying to test your Ruby on Rails (or other localhost-based site) in IE while developing on the Mac should be easy, given the prevalence of virtual machines. Right?? You’d think. However, trying to setup our front-end developer to access a local instance of Passenger running on Mac OS X from a Windows VM proved to be more of an issue than it needed to be. Adding to this, most of the top Google results for relevant keywords for this configuration made the process more overly difficult than it needed to be.

So, to help ease your pain in testing your Mac-based Rails site from a Windows virtual machine, here is an easy configuration for the three main VMs.

We need to do two main things:

  1. Trying to serve port-based or subdomain-based sites over to the Windows VM is going to be more challenging. Let’s make things easy – we’ll set the default http://localhost site for our Mac to our Rails site.
  2. We need to get the VM’s network to communicate with the Mac’s network correctly so that we can access this site.

On my team, we’re using Passenger on the Mac (running on top of the Mac’s base install of Apache). We’re using Passenger Pane to configure Apache easily. We also have this hooked into RVM, but that won’t be relevant to what we’re doing here. For the purposes of this post, I’ll assume that you have Passenger successfully serving a site that you can access via http://somedomain.local I’ll also assume that for whichever VM you’re opting to use, you have Windows installed and running in it.

** Do yourself a favor. Make sure that VM Ware Tools, Parallel Tools, or VirtualBox Guest Additions are installed – this ensures that the network connectivity will work as expected.

 

Let’s get http://localhost serving our Rails site

At the bottom of /etc/apache2/httpd.conf when using Passenger Pane, you’ll find the following configuration:

<IfModule passenger_module>
  NameVirtualHost *:80
  <VirtualHost *:80>
    ServerName _default_
  </VirtualHost>
  Include /private/etc/apache2/passenger_pane_vhosts/*.conf
</IfModule>

 

To make our Passenger site the default localhost site, go into passenger_pane_vhosts/, view the Apache config file for the site you want to be the default, and you’ll see something along the lines of the following:

$ cat yoursite.local.vhost.conf
<VirtualHost *:80>
  ServerName yoursite.local
  DocumentRoot "/Users/username/rails/yoursite/public"
  RackEnv development
  <Directory "/Users/username/rails/yoursite/public">
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>

 

Copy the lines from inside the VirtualHost block and paste them back into /etc/apache2/httpd.conf so that it looks like the following:

$ cd ..
$ sudo mate httpd.conf
# Added by the Passenger preference pane
# Make sure to include the Passenger configuration (the LoadModule,
# PassengerRoot, and PassengerRuby directives) before this section.
<IfModule passenger_module>
  NameVirtualHost *:80
  <VirtualHost *:80>
    ServerName _default_
	####
	DocumentRoot "/Users/username/rails/yoursite/public"
	RackEnv development
	<Directory "/Users/username/rails/yoursite/public">
	  Order allow,deny
	  Allow from all
	</Directory>
	####
  </VirtualHost>
  Include /private/etc/apache2/passenger_pane_vhosts/*.conf
</IfModule>

 

Restart Apache by going to System Preferences > Sharing and uncheck & recheck Web Sharing.

Also, if you’re using VMWare Fusion or Parallels, note the IP address that’s shown here.

Configuring VirtualBox

Let’s start with Virtual Box, which was the biggest pain to configure. Not because there’s a lot of work, but because they one critical detail doesn’t seem to be well known (most of the posts you’ll read online try to guide you through using the command-line network configuration tool). I should note that this was my first real opportunity to use Virtual Box, having only used Parallels and VMWare Fusion beforehand. It’s a little rough around the edges in a few places, but overall very impressive.

1) First, ensure that network settings for VirtualBox should be set to NAT – no port forwarding needed

 

2) Now, here’s what took a while to dig up: VirtualBox connects http://10.0.2.2 to the Mac’s localhost (thanks to this forum post) That is, just typing that into IE within the VM should connect you to http://localhost running on the Mac.

 

Configuring Parallels

1) For Parallels, you want to make sure that your VM is running on a Shared Network.

 

2) If you’re setup with this network configuration, you should be able to directly access the local IP address of your Mac, as shown in the System Preferences > Sharing screen:

 

Configuring VMWare Fusion

1) VMWare Fusion is much like Parallels. Configure your VM to “Share the Mac’s network connection” via NAT.

 

2) If you’re setup with this network configuration, you should be able to directly access the local IP address of your Mac, as shown in the System Preferences > Sharing screen, as with Parallels.

Hopefully, if you’ve made it to this point, for your respective VM, you have IE accessing your Passenger-based site. Success!

 

Hopefully, at least now you’ll be able to spend your time on all of the issues that poor users confined to IE are suffering through and get bug fixing quickly.


Quick fix for MacPorts Ruby version conflicts

Posted: May 26th, 2010 | Author: | Filed under: posts | Tags: , , | No Comments »

After configuring MacPorts, I was running into an issue on my Mac with Ruby on Rails where ‘/usr/bin/env ruby’ was giving me a different version of ruby than the one which has my gems installed. This was an issue for trying to run the ruby scripts with the shebang according to the best practices (i.e. /usr/bin/env ruby). So, trying to run script/console or script/server gave me lots of headaches. This may be a simple fix, but hopefully this will help others with similar issues.

$ /usr/bin/env ruby -v
ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-darwin10]
$ /usr/bin/ruby -v
ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0]

 

It took me a bit of fiddling and testing to figure it out, but I finally realized out that the MacPorts installation process had put its bin directories before the default bin directories. Ordering is fairly crucial – paths that are listed first take preference.

This is what I had after installing MacPorts:


export PATH=/opt/local/bin:/opt/local/sbin:$HOME/bin:$PATH:/usr/local/bin

This is the correct order (note – moved the /opt/local paths for MacPorts at the end to get things loaded in the right order):


export PATH=$HOME/bin:$PATH:/usr/local/bin:/opt/local/bin:/opt/local/sbin

With the order changed, now Ruby and Gem load from the same install (the MacPorts version of Ruby was loading overtop of the base Mac install).

Again, a simple fix, but hopefully, it might help someone running into the same issue.


Rails Facebook Development – Sharing some code for tracking active/inactive users…

Posted: January 9th, 2010 | Author: | Filed under: posts | Tags: , , , , | 14 Comments »

The next version of FanGamb will be a Facebook app.  It’s not a 1:1 translation of the current game to Facebook – we’ll be incorporating many of our game design learnings from running the first version of the game for NFL/NCAA football this fall. Since FanGamb is a Rails app, we’ve been working on setting up a collaborative development process around building Facebook apps in a team environment.  I’ll post more on this process to come, but for now, I wanted to contribute some code from our Rails Facebooker-based (that’s the main Rails Facebook API plugin) app.

Documentation on Facebooker is a little light, so there isn’t a whole lot to go on.  There is a fairly active Google group, though, and a list of the methods, so we’re not totally in the dark.  But as far as hunting for best practices and example code, it’s been a lot of trial and error.

One of the things that we wanted to implement in our application is making sure we know which users have our app installed.  It’s pretty common to store the user ids in the app’s database – you need to be able to link it to other data elements that you’re storing for them.  However, Facebooker doesn’t by default help you keep that user model in sync with which users actually have the app installed on Facebook.  You can tell which of a user’s friends have the app, but not a master list.  When you have an active Facebook user session, you can look at User#has_added_app, but we want to be able to tell if a user has the app added even if they aren’t on the app at that moment (for things like leaderboards, etc.).

So, we need to be able to track when a user uninstalls the application and set their user record to be inactive.  Facebook has the post_authorize and post_deauthorize call backs for this tracking.  As I said, there isn’t a lot of sample Facebooker code out there, so if you’re looking to implement the same thing, here are our callback actions for you to utilize.

The first thing you’re going to want to do is probably put your callback actions in a separate controller, so that you can limit which filters are run on the controller.  Facebooker has a before filter ensure_app_installed (suggested for your application_controller) which basically forces a user trying to use the app to install it.  This won’t work for your post_deauthorize action, so you need to turn that off:

skip_before_filter :ensure_app_installed, :except => :post_authorize

Also, because Facebook posts to these URLs, Rails treats it as a form submission and will try to verify the authenticity token.  So you need to turn that off for both post_authorize and post_deauthorize:

skip_before_filter :verify_authenticity_token

With this setup, you’re able to create the rest of the methods.  Here’s the stub code:

class CallbacksController < ApplicationController
  skip_before_filter :ensure_app_installed, :except => :post_authorize
  skip_before_filter :verify_authenticity_token
  def post_authorize
    if request.post?
      #do something here
      render :nothing => true
    end
  end
  def post_deauthorize
    if request.post?
      #do something here
      render :nothing => true
    end
  end
end

One important point – the above code will work, but we can’t forget to verify the signature to ensure that the request is actually from Facebook, since these actions are outside of the Facebooker plugin (which typically handles this validation).

Looking at the Facebook development wiki, you can see some sample code for RFacebook on how to validate this.  Implementing this, our callback controller now looks like the following:

class CallbacksController < ApplicationController
  skip_before_filter :ensure_app_installed, :except => :post_authorize
  skip_before_filter :verify_authenticity_token
  def post_authorize
    if request.post?
      if verify_uninstall_signature
        #set user's has_app indicator to 1 to indicate active
      end
    end
    render :nothing => true
  end
  def post_deauthorize
    if request.post?
      if verify_uninstall_signature
        #set user's has_app indicator to 0 to indicate inactive
      end
    end
    render :nothing => true
  end
  private
      #based on http://wiki.developers.facebook.com/index.php/Post-Remove_URL
      def verify_uninstall_signature
        signature = ''
        keys = params.keys.sort
        keys.each do |key|
          next if key == 'fb_sig'
          next unless key.include?('fb_sig')
          key_name = key.gsub('fb_sig_', '')
          signature += key_name
          signature += '='
          signature += params[key]
        end
        signature += FACEBOOK_YAML['secret_key']
        calculated_sig = Digest::MD5.hexdigest(signature)
        if calculated_sig != params[:fb_sig]
          logger.warn "\nWARNING :: potential spoofing :: expected signatures did not match"
          logger.info "\nSignature (fb_sig param from facebook) :: #{params[:fb_sig]}"
          logger.info "\nSignature String (pre-hash) :: #{signature}"
          logger.info "\nMD5 Hashed Sig :: #{calculated_sig}"
          #check to see if ip variables are nil
          if not request.env['HTTP_X_FORWARDED_FOR'].nil? and not request.env['HTTP_X_REAL_IP'].nil?
            ip = request.env['HTTP_X_FORWARDED_FOR'] || request.env['HTTP_X_REAL_IP']
          else
            ip = request.remote_ip
          end
          logger.info "\nRemote IP :: #{ip}"
          return false
        else
          #logger.warn "\n\nSUCCESS!! Signatures matched.\n"
        end
        return true
      end
end

Get the code in a Gist here.

Note the one line that references FACEBOOK_YAML[‘secret_key'].  This should refer to your secret Facebook key.  We put this into the initializer block in environment.rb:

#load facebooker configuration for usage
facebook_config = File.join(RAILS_ROOT, 'config', 'facebooker.yml')
FACEBOOK_YAML = YAML::load(ERB.new(File.read(facebook_config)).result)[RAILS_ENV]

Phew!  That should do it – you should now be able to track whether your users have the app installed or not via the post callbacks.  Hopefully, this will help someone looking to keep track of active/inactive users in their Rails Facebook app!

Big props to my co-founder Alan deLevie who’s figured out a big chunk of this Facebooker stuff for the FanGamb team.

EDIT: typo fixed.  Thanks Sting Tao!