From af810f38dd6105cead20f73282cf60918ef6d59d Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sun, 10 Feb 2013 23:37:24 +1100 Subject: [PATCH] UserSearch refactor Added .sql_builder to all AR models --- app/models/user_search.rb | 39 +++++++++---------- config/initializers/sql_builder.rb | 1 + lib/sql_builder.rb | 19 +++++++--- spec/components/sql_builder_spec.rb | 58 ++++++++++++++++++----------- 4 files changed, 69 insertions(+), 48 deletions(-) create mode 100644 config/initializers/sql_builder.rb diff --git a/app/models/user_search.rb b/app/models/user_search.rb index 42a01fc08e0..7b27ccf0844 100644 --- a/app/models/user_search.rb +++ b/app/models/user_search.rb @@ -1,20 +1,19 @@ class UserSearch def self.search term, topic_id = nil - User.find_by_sql sql(term, topic_id) - end + sql = User.sql_builder( +"select id, username, name, email from users u +/*left_join*/ +/*where*/ +/*order_by*/") - private - - def self.sql term, topic_id - sql = "select id, username, name, email from users u " + if topic_id - sql << "left join (select distinct p.user_id from posts p where topic_id = :topic_id) s on - s.user_id = u.id " + sql.left_join "(select distinct p.user_id from posts p where topic_id = :topic_id) s on s.user_id = u.id", topic_id: topic_id end - - if term.present? - sql << "where username ilike :term_like or + + if term.present? + sql.where("username ilike :term_like or to_tsvector('simple', name) @@ to_tsquery('simple', regexp_replace( @@ -22,22 +21,18 @@ class UserSearch cast(plainto_tsquery(:term) as text) ,'\''(?: |$)', ':*''', 'g'), '''', '', 'g') - ) " + )", term: term, term_like: "#{term}%") + sql.order_by "case when username_lower = :term then 0 else 1 end asc" end - - sql << "order by case when username_lower = :term then 0 else 1 end asc, " + if topic_id - sql << " case when s.user_id is null then 0 else 1 end desc, " + sql.order_by "case when s.user_id is null then 0 else 1 end desc" end - sql << " case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)" + sql.order_by "case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)" - sanitize_sql_array(sql, topic_id: topic_id, term_like: "#{term}%", term: term) + sql.exec end - def self.sanitize_sql_array *args - ActiveRecord::Base.send(:sanitize_sql_array, args) - end - -end \ No newline at end of file +end diff --git a/config/initializers/sql_builder.rb b/config/initializers/sql_builder.rb new file mode 100644 index 00000000000..fd87e9c645e --- /dev/null +++ b/config/initializers/sql_builder.rb @@ -0,0 +1 @@ +require 'sql_builder' diff --git a/lib/sql_builder.rb b/lib/sql_builder.rb index 5bf295ed6c4..c0477c77b5c 100644 --- a/lib/sql_builder.rb +++ b/lib/sql_builder.rb @@ -1,9 +1,10 @@ class SqlBuilder - def initialize(template) + def initialize(template,klass=nil) @args = {} @sql = template @sections = {} + @klass = klass end [:set, :where2,:where,:order_by,:limit,:left_join,:join,:offset].each do |k| @@ -40,9 +41,17 @@ class SqlBuilder sql.sub!("/*#{k}*/", joined) end - - ActiveRecord::Base.exec_sql(sql,@args) + + if @klass + @klass.find_by_sql(ActiveRecord::Base.send(:sanitize_sql_array, [sql, @args])) + else + ActiveRecord::Base.exec_sql(sql,@args) + end + end +end + +class ActiveRecord::Base + def self.sql_builder(template) + SqlBuilder.new(template, self) end - - end diff --git a/spec/components/sql_builder_spec.rb b/spec/components/sql_builder_spec.rb index 5edb367b091..da46943e6d8 100644 --- a/spec/components/sql_builder_spec.rb +++ b/spec/components/sql_builder_spec.rb @@ -4,32 +4,48 @@ require_dependency 'sql_builder' describe SqlBuilder do - before do - @builder = SqlBuilder.new("select * from (select :a A union all select :b) as X /*where*/ /*order_by*/ /*limit*/ /*offset*/") + describe "attached" do + before do + @builder = Post.sql_builder("select * from posts /*where*/ /*limit*/") + end + + it "should find a post by id" do + p = Fabricate(:post) + @builder.where('id = :id and topic_id = :topic_id', id: p.id, topic_id: p.topic_id) + p2 = @builder.exec.first + p2.id.should == p.id + p2.should == p + end end - it "should allow for 1 param exec" do - @builder.exec(a: 1, b: 2).values[0][0].should == '1' - end + describe "detached" do + before do + @builder = SqlBuilder.new("select * from (select :a A union all select :b) as X /*where*/ /*order_by*/ /*limit*/ /*offset*/") + end - it "should allow for a single where" do - @builder.where(":a = 1") - @builder.exec(a: 1, b: 2).values[0][0].should == '1' - end + it "should allow for 1 param exec" do + @builder.exec(a: 1, b: 2).values[0][0].should == '1' + end - it "should allow where chaining" do - @builder.where(":a = 1") - @builder.where("2 = 1") - @builder.exec(a: 1, b: 2).to_a.length.should == 0 - end + it "should allow for a single where" do + @builder.where(":a = 1") + @builder.exec(a: 1, b: 2).values[0][0].should == '1' + end - it "should allow order by" do - @builder.order_by("A desc").limit(1) - .exec(a:1, b:2).values[0][0].should == "2" - end - it "should allow offset" do - @builder.order_by("A desc").offset(1) - .exec(a:1, b:2).values[0][0].should == "1" + it "should allow where chaining" do + @builder.where(":a = 1") + @builder.where("2 = 1") + @builder.exec(a: 1, b: 2).to_a.length.should == 0 + end + + it "should allow order by" do + @builder.order_by("A desc").limit(1) + .exec(a:1, b:2).values[0][0].should == "2" + end + it "should allow offset" do + @builder.order_by("A desc").offset(1) + .exec(a:1, b:2).values[0][0].should == "1" + end end end