Return join/aggregate/calculated columns in ActiveRecord finder
Ruby
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
CHANGELOG.markdown
MIT-LICENSE
README.markdown
init.rb
select_extra_columns.gemspec

README.markdown

select_extra_columns

Enhances the ActiveRecord finders to return join/aggregate/calculated columns along with standard table columns.

Installation

Use either the plugin or the gem installation method depending on your preference. If you're not sure, the plugin method is simpler.

As a Plugin

./script/plugin install git://github.com/kandadaboggu/select_extra_column.git 

As a Gem

Add the following to your application's environment.rb: config.gem "select_extra_column", :source => "http://gemcutter.org"

Install the gem: rake gems:install

Getting Started

Enable select_extra_column in your ActiveRecord model.

class User < ActiveRecord::Base
  select_extra_columns
  has_many :posts 
  has_one :address
end

class Address < ActiveRecord::Base
  belongs_to :user 
end

class Post < ActiveRecord::Base
  belongs_to :user 
end

Use the extra columns in your finders.

user = User.first(:joins => :address, :select => "*, addresses.street as street",
                :extra_columns => :street)
user.city 				# returns the street

users = User.find(:all, :joins => :posts, :select => "users.*, count(posts.id) as post_count",
                :extra_columns => {:post_count => :integer} )
users.first.post_count 	# returns the post count

user = User.first(:joins => :address, :select => "users.*, addresses.street as street, addresses.city as city",
                :extra_columns => [:street, :city] )
user.street 			# returns the street
user.city 				# returns the city

users = User.all(:joins => :address, :select => "*, addresses.active as active, addresses.city as city",
                :extra_columns => [:city, [:active, :boolean]]
users.first.street 		# returns the street
users.first.active 		# returns true/false

users = User.find_by_sql("SELECT A.*, count(B.id) as post_count 
							FROM   users A, posts B 
							WHERE  A.id = B.user_id AND B.ptype = 'public'
							GROUP BY A.id
								UNION 
							SELECT A.*, count(B.id) as post_count 
							FROM   users A, customer_posts B 
							WHERE  A.id = B.user_id AND B.ptype = 'private'
							GROUP BY A.id", 
						:extra_columns => :post_count)

Dynamically added column fields are read only. Any value set to these fields are ignored during save.

user = User.first(:joins => :address, :select => "*, addresses.street as street",
                :extra_columns => :street)
user.city  # => "San Francisco"
...
user.city = "Houston" # change the value
user.save

user = User.first(:joins => :address, :select => "*, addresses.street as street",
                :extra_columns => :street)
user.city  # => "San Francisco"

Input format for :extra_columns

This option accepts String/Symbol/Array/Hash as input.

String,Symbol format

:extra_columns => :first_name    # Single string field: `first_name`(type is inferred as string)

:extra_columns => "first_name"   # Single string field: `first_name`(type is inferred as string)

Hash format

:extra_columns => {              # Two string fields and a boolean field
                       :first_name => :string, 
                       :last_name  => :string, 
                       :has_flag   => :boolean
                  }

:extra_columns => {              # Two string fields and a boolean field
                       "first_name" => :string, 
                       "last_name"  => :string, 
                       "has_flag"   => :boolean
                  }

Array format

:extra_columns => [              # Two string fields and a boolean field
					[:first_name, :string], 
					[:last_name,  :string], 
					[:has_flag,   :boolean]
					]

:extra_columns => [:first_name, :last_name] # Two string fields

:extra_columns => [              # Two string fields and a boolean field
					:first_name, :last_name, # type is inferred as string
					[:has_flag,   :boolean]
					]

Sharing extra_columns definition across finders

You can declare the extra columns in your model and use them across finders class User < ActiveRecord::Base select_extra_columns

  extra_columns :address_info, :street, :city
  extra_columns :post_info, [:post_count, :integer], :last_post_at => :datetime
  
  has_many :posts 
  has_one :address
end

Now :user_info and :post_info can be used in finders.

users = User.all(:joins => :posts, :extra_columns => :post_info 
				:select => "users.*, count(posts.id) as post_count, max(posts.created_at) as last_post_at")

user = User.first(:joins => :address, :extra_columns => :address_info, 
				:select => "users.*, addresses.street as street, addresses.city as city")

Naming conflicts

When a symbol/string is passed as input to :extra_columns option, the finder uses cached extra_columns definition by the given name. If no definition is found, then finder creates a new extra_columns definition with the input as a column.

class User < ActiveRecord::Base
  select_extra_columns
  
  extra_columns :post_count, [:post_count, :integer], :last_post_at => :datetime
end

In the finder call below, post_count maps on to a column name and a extra_columns definition name. Finder chooses the extra_columns definition. users = User.all(:joins => :posts, :select => "users.*, count(posts.id) as post_count, max(posts.created_at) as last_post_at", :extra_columns => :post_count)

Accessing the extra column model

The gem creates and caches a model for every unique extra_column configuration. This model can be accessed using the find_extra_columns_class method. User.find_extra_columns_class(:post_count).find_by_sql("")

Valid data types for column fields in :extra_columns

:binary
:boolean
:date
:datetime
:decimal
:float
:integer
:string
:text
:time
:timestamp

Copyright (c) 2010 Kandada Boggu, released under the MIT license