Scope in rails

Lets work on a feature in rails, which is called scope. Scoping allows you to specify commonly-used ARel  (a relational algebra[2])queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method  such as where, joins and includes. All scope methods will return an ActiveRecord::Relation object which will allow for further methods (such as other scopes) to be called on it. [1]

Saying something about ARel, Arel is a SQL AST manager for Ruby. It

  1. Simplifies the generation complex of SQL queries
  2. Adapts to various RDBMS systems

It is intended to be a framework framework; that is, you can build your own ORM with it, focusing on innovative object and collection modeling as opposed to database compatibility and query generation.

Lets get it simple. Consider you have a table named user. So see the following bit of codes.

users = Arel::Table.new(:users)
query = users.project(Arel.sql('*'))
query.to_sql

in here, you construct a table relation and convert it to sql [4]

 

In previous versions of rails (1 or 2) named_scope  quickly made its way into my toolset as a great way to encapsulate reusable, chainable bits of query logic. While it had its downsides (namely its lack of first-class chain support for the likes of :joins and :include). Now in rails 3, named_scope has been renamed to scope.[3]

Now get it practical. Lets create a model named “Post” with published_at datetime field along with title and body.

To make this done, I am making a new project in rails. (well, it is possible to use any old project, just be sure there is no model named post, if exists, delete it in your favored way.)

I used the following command in command prompt.

rails new scope_project

simple, it makes a new project to works with!  Now lets create the Post model.

cd scope_project

rails g model Post published_at:datetime title:string body:string

lets configure the gems for this project.In GemFile , I added

gem ‘mysql’

to work with mysql.

Now in config/database.yml , I changed the follow,

development:

adapter: mysql

database: scope_project_development

pool: 5

timeout: 5000

username: root

password:

 

test:

adapter: mysql

database: scope_project_test

pool: 5

timeout: 5000

username: root

password:

 

production:

adapter: mysql

database: scope_project_production

pool: 5

timeout: 5000

username: root

password:

before going for more, I think the mysql server is on. If not , just open the mysql server (I use xampp for development, it is good if you have pure mysql server.)

lets get on some more cmd play. In command prompt, just type the following,

bundle install

rake db:create

rake db:migrate

if all are done (with no error) ,then lets open the post.rb in app/models . the file is just like this:-

class Post < ActiveRecord::Base

end

(here comes the famous (!) ActiveRecord::Base, if you don’t have any idea, please get an idea J )

I changed it with the following

class Post < ActiveRecord::Base

scope :published , lambda {

where(“posts.published_at IS NOT NULL AND posts.published_at <=?”,Time.zone.now)

}

scope :recent, order(“posts.published_at DESC”)

end

They are now built upon the very same query methods that you would use were you to execute a query directly. This consistency is now prevalent all throughout ActiveRecord.[3]

When we are done with it, lets focus on scope reusability. Suppose we want to update our recent scope to only include published posts. We’ve already defined what published means and shouldn’t have to redefine it to create a new scope. Well, you can also chain scopes within scope definitions themselves as we’ll do here with the new recent and published_since scopes.[3]

Lets change the Post model to following,

class Post < ActiveRecord::Base

scope :published , lambda {

where(“posts.published_at IS NOT NULL AND posts.published_at <=?”,Time.zone.now)

}

scope :published_since, lambda { |ago|

published.where(“posts.published_at >=?”,ago)

}

scope :recent, published.order(“posts.published_at DESC”)

end

now consider the following , to search our posts we can create this method which will return a scope for your caller to further filter

class Post < ActiveRecord::Base

class <<self

def search(q)

[:title,:body].inject(scoped) do |combined_scope,attr|

combined_scope.where(“posts.#{attr} LIKE ?”,”%#{q}%”)

end

end

end

end

The use of inject here somewhat obfuscates the intent of the method if you’re not used to looking at such iterations – here’s an easier to follow version with the searchable fields more hard coded [3]

def search(q)

query=”%#{q}%”

where(“posts.title LIKE ?”,query).where(“posts.body LIKE ?”,query)

end

lets see the Post model in rails console. Before going to these command, I guess that you give the posts table with some seed data. If not, give some seed data!

rails console --sandbox
Post.all.collect(&:title) 
Post.published.collect(&:title) 
Post.search('1').collect(&:title) 
Post.search('1').published.collect(&:title) 
Post.search('w').published_since(10.days.ago).collect(&:title) Post.search('w').order('created_at DESC').limit(2).collect(&:title)

 

Here, I used rails console –sandbox , it is cause any change in this console session can be rolled back.

For more information , please see the references below.

 

Reference:

[1] http://guides.rubyonrails.org/active_record_querying.html

[2] https://github.com/rails/arel

[3] http://edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/index.html

[4] http://rdoc.info/github/rails/arel/master/file/README.markdown

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s