`

Ambition

    博客分类:
  • RoR
阅读更多

原文出处:http://railsontherun.com/2007/9/16/ambition-why-should-you-care

By now, you should have heard about ambition if not read the latest post from the author.

Ambition has a simple goal: making you stop writing SQL in your queries and only stick to Ruby. (who cares if you use ActiveRecord, Sequel, DataMapper or another ORM)

I'm so used to the ActiveRecord way of querying the database that I was not fully convinced that Ambition would help me in my daily tasks. I still gave it a try:

Testing Ambition

$ sudo gem install ambition -y

Started my console

$ script/console

and required Ambition

require 'ambition'

I started by doing a query the AR way:

1
2
 Photo.find(:all, :conditions => "photos.title IS NULL AND photos.width > 250 
AND photos.height > 200 AND users.name = 'test'", :include => :user) 

And I converted it into an Ambition call:


Photo.select {|p| p.title == nil && p.width > 250 && p.height > 200  && p.user.name == 'test'}.entries

145 vs 102 keystrokes. 30% less typing with Ambition! I don't know about you, but I REALLY prefer the Ruby only query, much cleaner and much "DRYer". However, that's not always true:


Photo.find_by_title(nil)

(24chars)


Photo.detect{|p| p.title == nil}
(32 chars)

But what's going on behind the scene? Do we have the exact same SQL query sent to our DB?

Well, Ambition doesn't generate any SQL, it uses AR to do so. You want to make sure Ambition is not messing with you, try that:

1
2
 >> Photo.select {|p| p.title == nil && p.width > 250 && p.height > 200  && p.user.name == 'test'}.to_hash
 => {:conditions=>"(photos.`title` IS NULL AND (photos.`width` > 250 AND (photos.`height` > 200 AND users.name = 'test')))", :include=>[:user]}

That's pretty hot. Especially when you have to use eager loading!

Obviously you can still do stuff like that:

1
2
3
Photo.select {|p| p.title == nil && p.width > 250 && p.height > 200  && p.user.name == 'test'}.each do |photo|
 puts photo.filename
end

(note the query will only be made once)

Another cool thing, is to do simple sorting:


>> Photo.select {|p| p.title == nil && p.user.name == 'test'}.sort_by { |p| [p.created_at, -p.size] }

creates the following:


=> {:order=>"photos.created_at, photos.size DESC", :conditions=>"(photos.`title` IS NULL AND users.name = 'test')", :include=>[:user]}

or


=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND users.name = 'test') ORDER BY photos.created_at, photos.size DESC"

That's cool, and you can still sort on relationships:

1
2
3
4
5
6
7
>> Photo.select {|p| p.title == nil }.sort_by { |p| p.user.name }
=> "SELECT * FROM photos JOIN user WHERE photos.`title` IS NULL ORDER BY users.name"</macro:code >
    
Or directly on the model:

<macro:code lang="ruby">>> Photo.sort_by(&:title)
=> "SELECT * FROM photos ORDER BY photos.title"

To finish, another detail which makes Ambition a great library

1
2
3
>> Photo.any? {|p| p.title =~ /ambition/ }
=> "SELECT count(*) AS count_all FROM photos WHERE (photos.`title` REGEXP 'ambition')" 
=> true

And if you were worried that it wouldn't work with utf8, check this out:

1
2
3
4
5
6
>> Photo.any? {|p| p.title == 'école'}
=> SET NAMES 'utf8'
=> SET SQL_AUTO_IS_NULL=0
=> SHOW FIELDS FROM photos
=> SELECT count(*) AS count_all FROM photos WHERE (photos.`title` = 'école') 
=> false

Limitations

The only limitation I found in Ambition is that Ruby code won't work in the block, for instance:


>> Photo.select {|p| p.title == nil && p.created_at < 1.week.ago && p.user.name == 'test'}.entries

won't work at the moment. To inspect what's going simply try:

1
2
>> Photo.select {|p| p.title == nil && p.created_at < 1.week.ago && p.user.name == 'test'}.to_sql
=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND (photos.`created_at` < 1.`week`.`ago` AND users.name = 'test'))"

You can see that photos.created_at < 1.week.ago is the problem.

The recommended way to achieve the same result is to use variables:

1
2
3
>> date = 1.week.ago
>> Photo.select {|p| p.title == nil && p.created_at < date && p.user.name == 'test'}.to_sql
=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND (photos.`created_at` < '2007-09-08 19:38:48' AND users.name = 'test'))"

However, note that method calls will work just fine:

1
2
3
4
5
6
>> def time_now_please
>> Time.now
>> end
    
>> Photo.select {|p| p.title == nil && p.created_at < time_now_please && p.user.name == 'test'}.to_sql
=> "SELECT * FROM photos JOIN user WHERE (photos.`title` IS NULL AND (photos.`created_at` < '2007-09-15 19:41:37' AND users.name = 'test'))"   

Conclusion

For now, Ambition is still just wrapping ActiveRecord::Base#find but the plan is to actually generate SQL. Hopefully we'll also be able to use Ruby code from within an Ambition block. Kickers methods are very interesting and could become a really nice way of speeding up your app and keep your code clean.

Ambition is a great query library, I think I'll start using it whenever I have "find" calls with multiple conditions especially if my conditions are related to another model. However I still didn't figure out how to use an inner join with Ambition.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics