J. Pablo Fernández wrote:
> Hello Railists,
> 
> I have a piece of Ruby on Rails code that has a complex SQL query (well, 
> not
> that complex, but as far as I know beyond the ORM capabilities) and for 
> my
> taste it has too many strings and harcoded values. I'd like to improve 
> it as
> much as possible, so my question is open ended, what else can I do to
> improve it?

In this case, break it into two.  See below.

> 
> Some particular issues I have
> 
> - Is there a way to get a table name to use it in a query in the same
> escaped way as the ORM does? I think this is database independent,
> being `items` for MySQL but not for other databases.
> - In the same vein, is there a way to get a field name the same way 
> Rail's
> ORM would put it in a SQL query?
> - Maybe there's a way to get both, the table name and the field name in 
> one
> operation. I'm imaging something like Item.voteable_id.for_query
> => "`items`.`voteable`".

Check out quote_table_name and quote_column_name.

> - How do I escape code to avoid SQL injection when not in conditions? 
> I'm
> using the user_id variable directly in a query and although it's 
> impossible
> for a user to put anything in it, I'd rather escape it properly. In a
> condition I would do ['user_id = ?', user_id], but in a join or a 
> select,
> how do I do it?

You really shouldn't be using user_id in a join as you're doing in the 
query below -- it belongs in a WHERE clause.  And at that point, 
find_by_sql will handle it.  Check the docs.

> - Does my use of class constants here make sense?

You're not using class constants.

> - Is there any chance at all of using the ORM and less string?

Yes.  See my suggestions below.

> - Any other thing to do to it?
> 
> The code is this one
> 
> class Item < ActiveRecord::Base
>   has_many :votes, :as => :voteable
> 
>   def self.ranking(user_id)
>     Item.find(:all,
>       # items.* for all the Item attributes, score being the sum of 
> votes,
> user_vote is the vote of user_id (0 if no vote) and voter_id is just 
> user_id
> for latter reference.
>       :select => "items.*,
>                   IFNULL(sum(all_votes.value), 0) as score,
>                   user_votes.value as user_vote,
>                   \"#{user_id}\" as voter_id",
>       # The first join gets all the votes for a single item (to be 
> summed
> latter).
>       # The second join gets the vote for a single user for a single 
> item.
>       :joins => ["LEFT JOIN votes as all_votes ON
>                     all_votes.voteable_id = items.id and
>                     all_votes.voteable_type = \"Item\"",
>                  "LEFT JOIN votes as user_votes ON
>                     user_votes.voteable_id = items.id and
>                     user_votes.user_id = \"#{user_id}\" and
>                     user_votes.voteable_type = \"Item\""
>                 ],
>       :group => :id,
>       :order => "score DESC")
> 
>     # This is the query it should generate
>     # SELECT items.*, user_votes.value as user_vote, 
> sum(all_votes.value) as
> score
>     # FROM items
>     # LEFT JOIN votes as all_votes ON
>     #     all_votes.voteable_id = items.id and
>     #     all_votes.voteable_type = "Item"
>     # LEFT JOIN votes as user_votes ON
>     #     user_votes.voteable_id = items.id and
>     #     user_votes.user_id = 2 and
>     #     user_votes.voteable_type = "Item"
>     # GROUP BY items.id
>     # ORDER BY score DESC
>   end

I think your logic is poor.  Although it's tempting, it feels wrong to 
join both all the votes (in the first join) and a subset of votes (in 
the second join).  I usually advocate one big query, but in this case I 
think it should probably be two queries:
Vote.sum(:value, :conditions => {:voteable_type => 'Item'}, :group => 
'voteable_id', :joins => 'left join items on (item.id = voteable_id)', 
:order => 'sum(value) desc')
Item.find(:all, :joins => :votes, :conditions => {:votes => {:user_id => 
user_id}})

Best,
--
Marnen Laibow-Koser
http://www.marnen.org
[email protected]
-- 
Posted via http://www.ruby-forum.com/.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to