// Richard Hart / Hates_

Archive
rails

My current project www.viewshound.com requires different users to have different scopes for their Facebook authentication. Warden supports dynamic providers out of the box. To get it working with Devise was pretty easy, just a couple of minor changes were needed especially to support Facebook.

First up was creating the new route. As I’m using a controller called Omniauth and not Session, the :to attribute is different. The other thing to note is I’m matching /users/auth/facebook/setup, not /auth/facebook/setup like in the Warden documentation.

routes.rb

  devise_for :users,
      :controllers => { :omniauth_callbacks => "omniauth" },
      :skip => [:sessions] do
    match '/users/auth/facebook/setup', :to => 'omniauth#setup'
  end

Next we create the new setup action for Warden to call. The docs use consumer_key and consumer_secret, but facebook expects client_id and client_secret, so be sure to use those instead. This is where I make changes to the scope based on the current user if they are signed in or not. So a user can re-authenticate with Facebook to get more permissions for their account if they wish.

omniauth_controller.rb

  class OmniauthController < Devise::OmniauthCallbacksController

    def setup
      request.env['omniauth.strategy'].client_id =
           {your_facebook_id_here}
      request.env['omniauth.strategy'].client_secret =
           {your_facebook_secret_here}
      request.env['omniauth.strategy'].options =
           {:scope => your_facebook_scope_here}
      render :text => "Setup complete.", :status => 404
    end

  end

Finally change the devise.rb initializer to support the new setup.

devise.rb

  config.omniauth :facebook, nil, nil, :setup => true

Restart your application and you should be good to go.

Read More

A current requirement is that assets uploaded to Amazon S3 must be accessed via SSL and use an expiring URL. Using an expiring url with Paperclip is extremely easy.

  my_model.image.expiring_url(5)

The problem is expiring_url doesn’t let you choose a style and doesn’t use https, which breaks our SSL site. To add the behaviour was an easy “hack”. I added the file /config/initializers/paperclip.rb with the following code:

  module Paperclip
    module Storage
      module S3
       def expiring_url(style = default_style, time = 3600)
          AWS::S3::S3Object.url_for(path(style),
                                    bucket_name,
                                    :expires_in => time,
                                    :use_ssl => true)
       end
      end
    end
  end

Now calling my expiring image URL with a style and over https is just:

  my_model.image.expiring_url(:original, 5)
Read More

Autotest would constantly run my tests even though I hadn’t modified anything. Which made it impossible to actually use. Creating the following in a file called .autotest in my application’s root solved the problem for me. This is on Rails 3 RC, RSpec 2 beta 19 and Cucumber 0.8.5.

  Autotest.add_hook :initialize do |at|
    at.add_exception(%r{^\./\.git})
    at.add_exception(%r{^\./db})
    at.add_exception(%r{^\./log})
    at.add_exception(%r{^\./tmp})
    at.add_exception(%r{^\./rerun\.txt})
    at.add_exception(%r{^\./Gemfile\.lock})
  end
UPDATE: Updated with Wes' suggestion in the comments.
Read More

Having a play with mongomapper and wanted to get Clearance working with it. Seemed to just be a simple case of doing the following in my user model.

  def self.attr_accessible(*args)end
  include Clearance::User

My full user model ended up looking like this:

  class User

    include MongoMapper::Document

    def self.attr_accessible(*args)end
    include Clearance::User

    key :email, String
    key :encrypted_password, String
    key :salt, String
    key :confirmation_token, String
    key :remember_token, String
    key :firstname, String
    key :email_confirmed, Boolean
    key :lastname, String

    timestamps!

  end

Perhaps if I have time, I will look into adding attr_accessible support to mongomapper myself.

Update 13/03/2010: I believe the dependency on attr_accessible in clearance has since been removed. So this small hack is most probably defunct.

Read More

The Law of Demeter, or Principle of Least Knowledge is a fairly simple design pattern, which, simply put means that an object should only talk to it’s immediate “friends”

The law states that a method M of and object O may only invoke the methods of the following kind:

1. a method on O itself
2. any parameters passed to M
3. any objects instantiated within M
4. any direct components of O

The classic example coined by David Bock used a Paperboy (one object) delivering a paper, then extracting money from a Customer’s (another object) Wallet (and another):

  class Paperboy
    def get_payment(customer)
      self.money += customer.wallet.take_out(10)
    end
  end

In the “real world” the Paperboy would ask the customer for the money who would then take it out for them, rather then the Paperboy reaching into the customer’s back pocket and getting it for themself.

Really we want something as follows:

  class Paperboy
    def get_payment(customer)
      self.money += customer.get_payment(10)
    end
  end

  class Customer
    def get_payment(amount)
      wallet.take_out(10)
    end
  end

This may all seem trivial and a waste of time, but what happens if some Customers want to pay by cheque? Those decisions should have an impact on the Paperboy, otherwise we end up with:

  class Paperboy
    def get_payment(customer)
      if customer.pay_by_cash?
        self.money += customer.wallet.take_out(10)
      elsif customer.pay_by_cheque?
        self.money += customer.cheque_book.write_cheque(10)
      end
    end
  end

Where as it makes more sense for the change to be contained within the Customer:

  class Paperboy
    def get_payment(customer)
      self.money += customer.get_payment(10)
    end
  end

  class Customer
    def get_payment(amount)
      if pay_by_cash?
        wallet.take_out(10)
      elsif pay_by_cheque?
        cheque_book.write_cheque(10)
      end
    end
  end

So what does this have to do with Rails and the delegate method? The delegate method adds a quick and simple way of following the Law of Demeter without having to do very much at all.

  class Order
    belongs_to :customer
  end

  class Customer
    has_many :orders
    has_one :credit_card
    has_one :bank_account

    def payment_method
      if pay_by_card?
        credit_card
      elsif pay_by_account?
        bank_account
      end
    end
  end

  class CreditCard
    belongs_to :customer
  end

  class BankAccount
    belongs_to :customer
  end

This setup means to get an Order’s payment we would have to say:

  @order.customer.payment_method.withdraw(amount)

But if we simply change our objects as such:

  class Order
    belongs_to :customer
    delegate :withdraw_payment, :to => :customer
  end

  class Customer
    has_many :orders
    has_one :credit_card
    has_one :bank_account

    def withdraw_payment(amount)
      if pay_by_card?
        credit_card.withdraw(amount)
      elsif pay_by_account?
        bank_account.withdraw(amount)
      end
    end
  end

Now all we have to say is:

  @order.withdraw_payment(amount)

So at any time, the details of how a payment  is to be decided can be contained with the Customer. This is of course a simplistic example, but hopefully explains how you chould be using this handy feature.

Read More

For some reason my rspec fixtures were not loading into my development database when doing spec:db:fixtures:load. An extra parameter to db:fixtures:load is all that’s needed.

     >: rake spec:db:fixtures:load
     rake aborted!
     uninitialized constant Fixtures

Fixed by:

     >: rake db:fixtures:load FIXTURES_PATH=spec/fixtures
Read More

So I began looking at Ruby on Rails a few days ago and I am quite impressed. To go from a database structure to a simple web app structure takes less then 15 minutes. Rails will generate a model and a controller for each of your database tables, or if you choose to generate a scaffold, it will generate all the supporting rhtml for you instead of generating it on the fly.

I’ve got to get my head into it a bit more, so tonight I’m going to try setting up database associations and getting them to filter through, but from what I’ve read that shouldn’t be any problem at all.

Read More