Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
570 views
in Technique[技术] by (71.8m points)

ruby on rails 3 - How to use `to_sql` in AREL when using `average()`?

I am trying to the get SQL from AREL, but it does not work in case I use average(:stars) :

This works:

Review.where("reviewed_user_id = ?", self.reviewed_user_id).to_sql
#=> "SELECT `reviews`.* FROM `reviews` WHERE (reviewed_user_id = 3)"

This causes NoMethodError:

Review.where("reviewed_user_id = ?", self.reviewed_user_id).average(:stars).to_sql
#=> undefined method `to_sql' for 3:Fixnum

So that means that to_sql is getting called on the result of the AREL instead of on the AREL object - but why?

How to get the generated SQL ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The reason this is happening is because the average method is on ActiveRecord::Relation, not Arel, which forces the computation.

m = Review.where('id = ?', 42).method(:average)
#=> #<Method: ActiveRecord::Relation(ActiveRecord::Calculations)#average>
m.source_location  # or m.__file__ if you're on a different version of Ruby
#=> ["/Users/jtran/.rvm/gems/ruby-1.9.2-p0/gems/activerecord-3.0.4/lib/active_record/relation/calculations.rb", 65]

By checking out the internals of ActiveRecord::Calculations, you can derive how to get at the SQL that it uses.

my_reviewed_user_id = 42
relation = Review.where('reviewed_user_id = ?', my_reviewed_user_id)
column = Arel::Attribute.new(Review.unscoped.table, :stars)
relation.select_values = [column.average]
relation.to_sql
#=> "SELECT AVG("reviews"."stars") AS avg_id FROM "reviews" WHERE (reviewed_user_id = 42)"

Careful if you're working at the console. ActiveRecord::Relation caches things so if you type the above into the console line by line, it will actually not work, because pretty-printing forces the relation. Separating the above by semicolons and no new lines, however, will work.

Alternatively, you can use Arel directly, like so:

my_reviewed_user_id = 42
reviews = Arel::Table.new(:reviews)
reviews.where(reviews[:reviewed_user_id].eq(my_reviewed_user_id)).project(reviews[:stars].average).to_sql
#=> "SELECT AVG("reviews"."stars") AS avg_id FROM "reviews" WHERE "users"."reviewed_user_id" = 42"

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...