Monday 30 July 2012

Polymorphic strangeness

Polymorphic associations in Rails

and the hoops you sometimes have to jump through...


Recently I've run across this strange error in Rails:

ActiveRecord::HasManyThroughAssociationPolymorphicThroughError:
Cannot have a has_many :through association 'Info#shares' which goes through the polymorphic association 'Info#content'.


To explain what gives rise to this, let me shed some more light on the models involved.


class Info < ActiveRecord::Base
  #...
  belongs_to :content, :polymorphic => true
  #...
end


class SomeInfo < ActiveRecord::Base
  #...
  has_one :info, :as => :content
  has_many :shares, :as => :shareable
  #...
end


class Share < ActiveRecord::Base
  #...
  belongs_to :shareable, :polymorphic => true
  #...
end


So it turns out you can't do this:

class Info < ActiveRecord::Base
  #...
  belongs_to :content, :polymorphic => true
  has_many :shares, :through => :content
  #...
end

Which is a bit wierd. Rails has no problem loading the intermediatry model:
Info.content works just fine
Similarly, Content.shares works fine. I fail to see why Info.shares should be a problem.

To get around this is actually quite simple, given that content.shares works. Simply delegate...

class Info < ActiveRecord::Base
  #...
  belongs_to :content, :polymorphic => true
  delegate :shares, :to => :content, :prefix => false, :allow_nil => true
  #...
end

that's it. Now info.shares works fine.

You can also define a method like this inside Info:

def shares
  content.shares
end

but I find the delegate cleaner and less obtrusive.


Friday 02 March 2012

Internet Slow? Try an alternate DSN.

I have been having intermittent performance issues with my local ISP. All the online speed tests I had tried came out favourably, yet I still experienced lag and timeouts randomly across all the usual sites I frequent.

After doing some research I decided to try and alternate DSN as a possible solution. Finding and testing DSN servers manually sounds like allot of pain. So after some googling I found a really great tool called Namebench, which does DNS performance testing based on your browser history.

After running namebench I found out my ISP DSN servers we really slow and a better local alternate existed.


After configuring my router with the new DSN settings I noticed a remarkable improvement in my overall browsing experience. So perhaps a performance improvement is only a DSN update away!

Monday 17 October 2011

Adding a Custom Authentication Strategy to Warden based on Devise in Rails

We had a requirement recently where we had to authenticate users against a custom external API. We use Devise for our Rails authentication and it turns out there's a really neat way to do custom authentication in the form of Warden strategies. Warden uses the concept of cascading strategies to determine if a request should be authenticated. Warden will try strategies one after another until either, - One succeeds - No Strategies are found relevant - A strategy Fails.
Step 1 Create your own strategy implementation inheriting from Devise::Strategies::Base
myrailsapp/lib/custom_auth.rb
module CustomAuth
  module Devise
    module Strategies
      class FromSession < ::Devise::Strategies::Base
        def valid?
          # this strategy is only valid if there is a url_token
          # in the params hash. 
          # e.g. http://myapp?url_token=mysecrettoken 
          params[:url_token]
        end

        def authenticate!          
          # lookup session data with external api
          session_data = get_session_data_from_api(params[:url_token])
          
          # check if token was valid and authorise if so 
          if session_data['error']
            # session lookup failed so fail authentication with message from api  
            fail!(session_data['error'])
          else
            # we got some valid user data
            success!(User.find(session_data['user_id']))
          end
        end
      end
    end
  end
end

Step 2 Add this strategy to warden
myrailsapp/config/initializers/devise.rb
  config.warden do |manager|
    manager.strategies.add(:custom_auth, CustomAuth::Devise::Strategies::FromSession)
    manager.default_strategies(:scope => :user).unshift :custom_auth
  end
Thats it!

Monday 03 October 2011

IBM Cognos BI: Passing Arbitrary Values in Drill-Through Definitions for Crosstab Fact Cells

When you setup drill-through definitions on fact cells you loose the data properties attribute you usually get when you setup drill-throughs on rows/column members. Sometimes this can be fatal to the drill-through definition as you need to pass a value that cannot be surfaced on the crosstab or derived from a parameter value. Usually the value is a report level "variable" like current month etc.

A workaround I came up with is as follows:

1. Make sure the data item(s) exists in the crosstab query (they just need to be part of the query a not on the actual crosstab ).
2. Create a new query with the same data item(s) you want to pass with the exact same name.

3. Assign this new query to the Page object and  assign the data item(s) as data properties

4. Configure the drill-through definition as normal


The drill-through will use the value from the query assigned to the report object. You can test this by removing the data item from the Page object data properties, after which you will get a prompt for the value when you try and drill-through.

NB# I'm not sure this is the intended behaviour so use with caution, keeping in mind this may break in later releases of Cognos.

Tuesday 27 September 2011

The Power of MDX

MultiDimensional eXpressions (MDX) is a query language for OLAP databases. We often create Business Intelligence (BI) systems in Ruby on Rails that run on top of Mondrian. One of the great features of MDX is that it allows your create complex queries pretty easily thanks to the mechanics of MDX and the powerful built-in functions available. 


Sales Growth

 ([Time].CurrentMember, [Measures].[Sales]) - 
     ([Time].CurrentMember.PrevMember, [Measures].[Sales])    

Parallel Period Growth

 ([Time].CurrentMember, [Measures].[Sales]) - 
     (ParellelPeriod(Year, 1, [Time].CurrentMember), [Measures].[Sales])    

YTD Sales

sum(ytd([Time].CurrentMember), [Measures].[Sales])

Its definitely something worth adding to your toolkit!