pg_search 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.travis.yml CHANGED
@@ -12,3 +12,5 @@ rvm:
12
12
 
13
13
  before_script:
14
14
  - "psql -c 'create database pg_search_test;' -U postgres >/dev/null"
15
+
16
+ script: "bundle exec rspec spec"
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  = PgSearch
2
2
 
3
+ == 0.5.2
4
+
5
+ * Don't save twice if pg_search_document is missing on update
6
+
3
7
  == 0.5.1
4
8
 
5
9
  * Add ability to override multisearch rebuild SQL
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-11 Case Commons, LLC
1
+ Copyright (c) 2010-12 Case Commons, LLC
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/lib/pg_search.rb CHANGED
@@ -3,6 +3,16 @@ require "active_support/concern"
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
4
 
5
5
  module PgSearch
6
+ autoload :Configuration, "pg_search/configuration"
7
+ autoload :Document, "pg_search/document"
8
+ autoload :Features, "pg_search/features"
9
+ autoload :Multisearch, "pg_search/multisearch"
10
+ autoload :Multisearchable, "pg_search/multisearchable"
11
+ autoload :Normalizer, "pg_search/normalizer"
12
+ autoload :Scope, "pg_search/scope"
13
+ autoload :ScopeOptions, "pg_search/scope_options"
14
+ autoload :Version, "pg_search/version"
15
+
6
16
  extend ActiveSupport::Concern
7
17
 
8
18
  mattr_accessor :multisearch_options
@@ -10,10 +20,15 @@ module PgSearch
10
20
 
11
21
  module ClassMethods
12
22
  def pg_search_scope(name, options)
13
- self.scope(
14
- name,
15
- PgSearch::Scope.new(name, self, options).to_proc
16
- )
23
+ scope = PgSearch::Scope.new(name, self, options)
24
+
25
+ method_proc = scope.method(:build_relation)
26
+
27
+ if respond_to?(:define_singleton_method)
28
+ define_singleton_method name, &method_proc
29
+ else
30
+ (class << self; self; end).send :define_method, name, &method_proc
31
+ end
17
32
  end
18
33
 
19
34
  def multisearchable(options = {})
@@ -40,21 +55,15 @@ module PgSearch
40
55
  end
41
56
 
42
57
  def multisearch_enabled?
43
- Thread.current.key?("PgSearch.enable_multisearch") ? Thread.current["PgSearch.enable_multisearch"] : true
58
+ if Thread.current.key?("PgSearch.enable_multisearch")
59
+ Thread.current["PgSearch.enable_multisearch"]
60
+ else
61
+ true
62
+ end
44
63
  end
45
64
  end
46
65
 
47
66
  class NotSupportedForPostgresqlVersion < StandardError; end
48
67
  end
49
68
 
50
- require "pg_search/configuration"
51
- require "pg_search/document"
52
- require "pg_search/features"
53
- require "pg_search/multisearch"
54
- require "pg_search/multisearchable"
55
- require "pg_search/normalizer"
56
- require "pg_search/scope"
57
- require "pg_search/scope_options"
58
- require "pg_search/version"
59
-
60
69
  require "pg_search/railtie" if defined?(Rails)
@@ -1,13 +1,16 @@
1
- require "pg_search/configuration/association"
2
- require "pg_search/configuration/column"
3
-
4
1
  module PgSearch
5
2
  class Configuration
3
+ autoload :Association, "pg_search/configuration/association"
4
+ autoload :Column, "pg_search/configuration/column"
5
+ autoload :ForeignColumn, "pg_search/configuration/foreign_column"
6
+
7
+ attr_reader :model
8
+
6
9
  def initialize(options, model)
7
- options = options.reverse_merge(default_options)
8
- assert_valid_options(options)
9
- @options = options
10
+ @options = default_options.merge(options)
10
11
  @model = model
12
+
13
+ assert_valid_options(@options)
11
14
  end
12
15
 
13
16
  class << self
@@ -32,8 +35,7 @@ module PgSearch
32
35
  def associations
33
36
  return [] unless @options[:associated_against]
34
37
  @options[:associated_against].map do |association, column_names|
35
- association = Association.new(@model, association, column_names)
36
- association
38
+ Association.new(@model, association, column_names)
37
39
  end.flatten
38
40
  end
39
41
 
@@ -75,20 +77,26 @@ module PgSearch
75
77
  {:using => :tsearch}
76
78
  end
77
79
 
78
- def assert_valid_options(options)
79
- valid_keys = [:against, :ranked_by, :ignoring, :using, :query, :associated_against, :order_within_rank]
80
- valid_values = {
81
- :ignoring => [:accents]
82
- }
80
+ VALID_KEYS = %w[
81
+ against ranked_by ignoring using query associated_against order_within_rank
82
+ ].map(&:to_sym)
83
83
 
84
+ VALID_VALUES = {
85
+ :ignoring => [:accents]
86
+ }
87
+
88
+ def assert_valid_options(options)
84
89
  unless options[:against] || options[:associated_against]
85
90
  raise ArgumentError, "the search scope #{@name} must have :against#{" or :associated_against" if defined?(ActiveRecord::Relation)} in its options"
86
91
  end
87
- raise ArgumentError, ":associated_against requires ActiveRecord 3 or later" if options[:associated_against] && !defined?(ActiveRecord::Relation)
88
92
 
89
- options.assert_valid_keys(valid_keys)
93
+ if options[:associated_against] && !defined?(ActiveRecord::Relation)
94
+ raise ArgumentError, ":associated_against requires Active Record 3 or later"
95
+ end
96
+
97
+ options.assert_valid_keys(VALID_KEYS)
90
98
 
91
- valid_values.each do |key, values_for_key|
99
+ VALID_VALUES.each do |key, values_for_key|
92
100
  Array.wrap(options[key]).each do |value|
93
101
  unless values_for_key.include?(value)
94
102
  raise ArgumentError, ":#{key} cannot accept #{value}"
@@ -9,7 +9,7 @@ module PgSearch
9
9
  @model = model
10
10
  @name = name
11
11
  @columns = Array(column_names).map do |column_name, weight|
12
- Column.new(column_name, weight, @model, self)
12
+ ForeignColumn.new(column_name, weight, @model, self)
13
13
  end
14
14
  end
15
15
 
@@ -18,20 +18,30 @@ module PgSearch
18
18
  end
19
19
 
20
20
  def join(primary_key)
21
- selects = columns.map do |column|
22
- case @model.connection.send(:postgresql_version)
21
+ "LEFT OUTER JOIN (#{relation(primary_key).to_sql}) #{subselect_alias} ON #{subselect_alias}.id = #{primary_key}"
22
+ end
23
+
24
+ def subselect_alias
25
+ Configuration.alias(table_name, @name, "subselect")
26
+ end
27
+
28
+ private
29
+
30
+ def selects
31
+ postgresql_version = @model.connection.send(:postgresql_version)
32
+
33
+ columns.map do |column|
34
+ case postgresql_version
23
35
  when 0..90000
24
- "array_to_string(array_agg(#{column.full_name}), ' ') AS #{column.alias}"
36
+ "array_to_string(array_agg(#{column.full_name}::text), ' ') AS #{column.alias}"
25
37
  else
26
38
  "string_agg(#{column.full_name}::text, ' ') AS #{column.alias}"
27
39
  end
28
40
  end.join(", ")
29
- relation = @model.joins(@name).select("#{primary_key} AS id, #{selects}").group(primary_key)
30
- "LEFT OUTER JOIN (#{relation.to_sql}) #{subselect_alias} ON #{subselect_alias}.id = #{primary_key}"
31
41
  end
32
42
 
33
- def subselect_alias
34
- Configuration.alias(table_name, @name, "subselect")
43
+ def relation(primary_key)
44
+ @model.joins(@name).select("#{primary_key} AS id, #{selects}").group(primary_key)
35
45
  end
36
46
  end
37
47
  end
@@ -3,34 +3,35 @@ require 'digest'
3
3
  module PgSearch
4
4
  class Configuration
5
5
  class Column
6
- attr_reader :weight, :association
6
+ attr_reader :weight
7
7
 
8
- def initialize(column_name, weight, model, association = nil)
8
+ def initialize(column_name, weight, model)
9
9
  @column_name = column_name.to_s
10
10
  @weight = weight
11
11
  @model = model
12
- @association = association
13
- end
14
-
15
- def table
16
- foreign? ? @association.table_name : @model.table_name
12
+ @connection = model.connection
17
13
  end
18
14
 
19
15
  def full_name
20
- "#{@model.connection.quote_table_name(table)}.#{@model.connection.quote_column_name(@column_name)}"
16
+ "#{table_name}.#{column_name}"
21
17
  end
22
18
 
23
19
  def to_sql
24
- name = if foreign?
25
- "#{@association.subselect_alias}.#{self.alias}"
26
- else
27
- full_name
28
- end
29
- "coalesce(#{name}::text, '')"
20
+ "coalesce(#{expression}::text, '')"
21
+ end
22
+
23
+ private
24
+
25
+ def table_name
26
+ @connection.quote_table_name(@model.table_name)
27
+ end
28
+
29
+ def column_name
30
+ @connection.quote_column_name(@column_name)
30
31
  end
31
32
 
32
- def foreign?
33
- @association.present?
33
+ def expression
34
+ full_name
34
35
  end
35
36
 
36
37
  def alias
@@ -0,0 +1,28 @@
1
+ require 'digest'
2
+
3
+ module PgSearch
4
+ class Configuration
5
+ class ForeignColumn < Column
6
+ attr_reader :weight
7
+
8
+ def initialize(column_name, weight, model, association)
9
+ super(column_name, weight, model)
10
+ @association = association
11
+ end
12
+
13
+ def alias
14
+ Configuration.alias(@association.subselect_alias, @column_name)
15
+ end
16
+
17
+ private
18
+
19
+ def expression
20
+ "#{@association.subselect_alias}.#{self.alias}"
21
+ end
22
+
23
+ def table_name
24
+ @connection.quote_table_name(@association.table_name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,4 @@
1
1
  require "logger"
2
- require "pg_search/scope"
3
2
 
4
3
  module PgSearch
5
4
  class Document < ActiveRecord::Base
@@ -19,9 +18,10 @@ module PgSearch
19
18
  options = if PgSearch.multisearch_options.respond_to?(:call)
20
19
  PgSearch.multisearch_options.call(*args)
21
20
  else
22
- PgSearch.multisearch_options.reverse_merge(:query => args.first)
21
+ {:query => args.first}.merge(PgSearch.multisearch_options)
23
22
  end
24
- options.reverse_merge(:against => :content)
23
+
24
+ {:against => :content}.merge(options)
25
25
  }
26
26
 
27
27
  private
@@ -1,7 +1,9 @@
1
1
  module PgSearch
2
2
  module Features
3
+ autoload :Feature, "pg_search/features/feature"
4
+
5
+ autoload :DMetaphone, "pg_search/features/dmetaphone"
6
+ autoload :Trigram, "pg_search/features/trigram"
7
+ autoload :TSearch, "pg_search/features/tsearch"
3
8
  end
4
9
  end
5
- require 'pg_search/features/dmetaphone'
6
- require 'pg_search/features/trigram'
7
- require 'pg_search/features/tsearch'
@@ -1,15 +1,18 @@
1
- require "active_support/core_ext/module/delegation"
2
-
3
1
  module PgSearch
4
2
  module Features
5
3
  class DMetaphone
6
- delegate :conditions, :rank, :to => :'@tsearch'
7
-
8
- # config is temporary as we refactor
9
- def initialize(query, options, config, model, normalizer)
4
+ def initialize(query, options, columns, model, normalizer)
10
5
  dmetaphone_normalizer = Normalizer.new(normalizer)
11
6
  options = (options || {}).merge(:dictionary => 'simple')
12
- @tsearch = TSearch.new(query, options, config, model, dmetaphone_normalizer)
7
+ @tsearch = TSearch.new(query, options, columns, model, dmetaphone_normalizer)
8
+ end
9
+
10
+ def conditions
11
+ @tsearch.conditions
12
+ end
13
+
14
+ def rank
15
+ @tsearch.rank
13
16
  end
14
17
 
15
18
  # Decorates a normalizer with dmetaphone processing.
@@ -0,0 +1,28 @@
1
+ module PgSearch
2
+ module Features
3
+ class Feature
4
+ def initialize(query, options, columns, model, normalizer)
5
+ @query = query
6
+ @options = options || {}
7
+ @columns = columns
8
+ @model = model
9
+ @normalizer = normalizer
10
+ end
11
+
12
+ private
13
+
14
+ def document
15
+ if @columns.length == 1
16
+ @columns.first.to_sql
17
+ else
18
+ expressions = @columns.map { |column| column.to_sql }.join(", ")
19
+ "array_to_string(ARRAY[#{expressions}], ' ')"
20
+ end
21
+ end
22
+
23
+ def normalize(expression)
24
+ @normalizer.add_normalization(expression)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,28 +1,18 @@
1
- require "active_support/core_ext/module/delegation"
2
-
3
1
  module PgSearch
4
2
  module Features
5
- class Trigram
6
- def initialize(query, options, columns, model, normalizer)
7
- @query = query
8
- @options = options
9
- @columns = columns
10
- @model = model
11
- @normalizer = normalizer
12
- end
13
-
3
+ class Trigram < Feature
14
4
  def conditions
15
- ["(#{@normalizer.add_normalization(document)}) % #{@normalizer.add_normalization(":query")}", {:query => @query}]
5
+ [
6
+ "(#{normalize(document)}) % #{normalize(":query")}",
7
+ {:query => @query}
8
+ ]
16
9
  end
17
10
 
18
11
  def rank
19
- ["similarity((#{@normalizer.add_normalization(document)}), #{@normalizer.add_normalization(":query")})", {:query => @query}]
20
- end
21
-
22
- private
23
-
24
- def document
25
- @columns.map { |column| column.to_sql }.join(" || ' ' || ")
12
+ [
13
+ "similarity((#{normalize(document)}), #{normalize(":query")})",
14
+ {:query => @query}
15
+ ]
26
16
  end
27
17
  end
28
18
  end
@@ -2,15 +2,11 @@ require "active_support/core_ext/module/delegation"
2
2
 
3
3
  module PgSearch
4
4
  module Features
5
- class TSearch
5
+ class TSearch < Feature
6
6
  delegate :connection, :quoted_table_name, :to => :'@model'
7
7
 
8
8
  def initialize(query, options, columns, model, normalizer)
9
- @query = query
10
- @options = options || {}
11
- @model = model
12
- @columns = columns
13
- @normalizer = normalizer
9
+ super
14
10
 
15
11
  if @options[:prefix] && @model.connection.send(:postgresql_version) < 80400
16
12
  raise PgSearch::NotSupportedForPostgresqlVersion.new(<<-MESSAGE.gsub /^\s*/, '')
@@ -33,24 +29,20 @@ module PgSearch
33
29
  {:query => @query.to_s, :dictionary => dictionary.to_s}
34
30
  end
35
31
 
36
- def document
37
- @columns.map { |column| column.to_sql }.join(" || ' ' || ")
38
- end
39
-
40
32
  DISALLOWED_TSQUERY_CHARACTERS = /['?\\:]/
41
33
 
42
34
  def tsquery_for_term(term)
43
35
  sanitized_term = term.gsub(DISALLOWED_TSQUERY_CHARACTERS, " ")
44
36
 
45
- term_sql = @normalizer.add_normalization(connection.quote(sanitized_term))
37
+ term_sql = normalize(connection.quote(sanitized_term))
46
38
 
47
39
  # After this, the SQL expression evaluates to a string containing the term surrounded by single-quotes.
48
40
  # If :prefix is true, then the term will also have :* appended to the end.
49
41
  tsquery_sql = [
50
- connection.quote("' "),
51
- term_sql,
52
- connection.quote(" '"),
53
- (connection.quote(':*') if @options[:prefix])
42
+ connection.quote("' "),
43
+ term_sql,
44
+ connection.quote(" '"),
45
+ (connection.quote(':*') if @options[:prefix])
54
46
  ].compact.join(" || ")
55
47
 
56
48
  "to_tsquery(:dictionary, #{tsquery_sql})"
@@ -58,7 +50,9 @@ module PgSearch
58
50
 
59
51
  def tsquery
60
52
  return "''" if @query.blank?
61
- @query.split(" ").compact.map { |term| tsquery_for_term(term) }.join(@options[:any_word] ? ' || ' : ' && ')
53
+ query_terms = @query.split(" ").compact
54
+ tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
55
+ tsquery_terms.join(@options[:any_word] ? ' || ' : ' && ')
62
56
  end
63
57
 
64
58
  def tsdocument
@@ -67,8 +61,12 @@ module PgSearch
67
61
  "#{quoted_table_name}.#{column_name}"
68
62
  else
69
63
  @columns.map do |search_column|
70
- tsvector = "to_tsvector(:dictionary, #{@normalizer.add_normalization(search_column.to_sql)})"
71
- search_column.weight.nil? ? tsvector : "setweight(#{tsvector}, #{connection.quote(search_column.weight)})"
64
+ tsvector = "to_tsvector(:dictionary, #{normalize(search_column.to_sql)})"
65
+ if search_column.weight.nil?
66
+ tsvector
67
+ else
68
+ "setweight(#{tsvector}, #{connection.quote(search_column.weight)})"
69
+ end
72
70
  end.join(" || ")
73
71
  end
74
72
  end
@@ -1,56 +1,14 @@
1
1
  module PgSearch
2
2
  module Multisearch
3
- REBUILD_SQL_TEMPLATE = <<-SQL
4
- INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
5
- SELECT :model_name AS searchable_type,
6
- :model_table.id AS searchable_id,
7
- (
8
- :content_expressions
9
- ) AS content,
10
- :current_time AS created_at,
11
- :current_time AS updated_at
12
- FROM :model_table
13
- SQL
3
+ autoload :Rebuilder, "pg_search/multisearch/rebuilder"
14
4
 
15
5
  class << self
16
6
  def rebuild(model, clean_up=true)
17
7
  model.transaction do
18
8
  PgSearch::Document.where(:searchable_type => model.name).delete_all if clean_up
19
- if model.respond_to?(:rebuild_pg_search_documents)
20
- model.rebuild_pg_search_documents
21
- else
22
- model.connection.execute(rebuild_sql(model))
23
- end
9
+ Rebuilder.new(model).rebuild
24
10
  end
25
11
  end
26
-
27
- def rebuild_sql(model)
28
- connection = model.connection
29
-
30
- unless model.respond_to?(:pg_search_multisearchable_options)
31
- raise ModelNotMultisearchable.new(model)
32
- end
33
-
34
- columns = Array.wrap(
35
- model.pg_search_multisearchable_options[:against]
36
- )
37
-
38
- content_expressions = columns.map { |column|
39
- %Q{coalesce(:model_table.#{column}::text, '')}
40
- }.join(" || ' ' || ")
41
-
42
- REBUILD_SQL_TEMPLATE.gsub(
43
- ":content_expressions", content_expressions
44
- ).gsub(
45
- ":model_name", connection.quote(model.name)
46
- ).gsub(
47
- ":model_table", model.quoted_table_name
48
- ).gsub(
49
- ":documents_table", PgSearch::Document.quoted_table_name
50
- ).gsub(
51
- ":current_time", connection.quote(connection.quoted_date(Time.now))
52
- )
53
- end
54
12
  end
55
13
 
56
14
  class ModelNotMultisearchable < StandardError
@@ -0,0 +1,75 @@
1
+ module PgSearch
2
+ module Multisearch
3
+ class Rebuilder
4
+ REBUILD_SQL_TEMPLATE = <<-SQL
5
+ INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
6
+ SELECT :model_name AS searchable_type,
7
+ :model_table.id AS searchable_id,
8
+ (
9
+ :content_expressions
10
+ ) AS content,
11
+ :current_time AS created_at,
12
+ :current_time AS updated_at
13
+ FROM :model_table
14
+ SQL
15
+
16
+ def initialize(model)
17
+ unless model.respond_to?(:pg_search_multisearchable_options)
18
+ raise ModelNotMultisearchable.new(model)
19
+ end
20
+
21
+ @model = model
22
+ end
23
+
24
+ def rebuild
25
+ if @model.respond_to?(:rebuild_pg_search_documents)
26
+ @model.rebuild_pg_search_documents
27
+ else
28
+ @model.connection.execute(rebuild_sql)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def connection
35
+ @model.connection
36
+ end
37
+
38
+ def rebuild_sql
39
+ replacements.inject(REBUILD_SQL_TEMPLATE) do |sql, key|
40
+ sql.gsub ":#{key}", send(key)
41
+ end
42
+ end
43
+
44
+ def replacements
45
+ %w[content_expressions model_name model_table documents_table current_time]
46
+ end
47
+
48
+ def content_expressions
49
+ columns.map { |column|
50
+ %Q{coalesce(:model_table.#{column}::text, '')}
51
+ }.join(" || ' ' || ")
52
+ end
53
+
54
+ def columns
55
+ Array.wrap(@model.pg_search_multisearchable_options[:against])
56
+ end
57
+
58
+ def model_name
59
+ connection.quote(@model.name)
60
+ end
61
+
62
+ def model_table
63
+ @model.quoted_table_name
64
+ end
65
+
66
+ def documents_table
67
+ PgSearch::Document.quoted_table_name
68
+ end
69
+
70
+ def current_time
71
+ connection.quote(connection.quoted_date(Time.now))
72
+ end
73
+ end
74
+ end
75
+ end
@@ -19,8 +19,11 @@ module PgSearch
19
19
  end
20
20
 
21
21
  def update_pg_search_document
22
- create_pg_search_document unless self.pg_search_document
23
- self.pg_search_document.save
22
+ if self.pg_search_document
23
+ self.pg_search_document.save
24
+ else
25
+ create_pg_search_document
26
+ end
24
27
  end
25
28
  end
26
29
  end
@@ -4,19 +4,19 @@ module PgSearch
4
4
  @config = config
5
5
  end
6
6
 
7
- def add_normalization(original_sql)
8
- normalized_sql = original_sql
7
+ def add_normalization(sql_expression)
9
8
  if @config.ignore.include?(:accents)
10
9
  if @config.postgresql_version < 90000
11
10
  raise PgSearch::NotSupportedForPostgresqlVersion.new(<<-MESSAGE.gsub /^\s*/, '')
12
- Sorry, {:ignoring => :accents} only works in PostgreSQL 9.0 and above.
13
- #{@config.inspect}
11
+ Sorry, {:ignoring => :accents} only works in PostgreSQL 9.0 and above.
12
+ #{@config.inspect}
14
13
  MESSAGE
15
14
  else
16
- normalized_sql = "unaccent(#{normalized_sql})"
15
+ "unaccent(#{sql_expression})"
17
16
  end
17
+ else
18
+ sql_expression
18
19
  end
19
- normalized_sql
20
20
  end
21
21
  end
22
22
  end
@@ -6,26 +6,22 @@ module PgSearch
6
6
  @options_proc = build_options_proc(scope_options_or_proc)
7
7
  end
8
8
 
9
- def to_proc
10
- lambda { |*args|
11
- config = Configuration.new(@options_proc.call(*args), @model)
12
- ScopeOptions.new(@name, @model, config).to_relation
13
- }
9
+ def build_relation(*args)
10
+ config = Configuration.new(@options_proc.call(*args), @model)
11
+ scope_options = ScopeOptions.new(@name, config)
12
+ scope_options.apply(@model)
14
13
  end
15
14
 
16
15
  private
17
16
 
18
- def build_options_proc(scope_options_or_proc)
19
- case scope_options_or_proc
20
- when Proc
21
- scope_options_or_proc
22
- when Hash
23
- lambda { |query|
24
- scope_options_or_proc.reverse_merge(:query => query)
25
- }
26
- else
27
- raise ArgumentError, "A PgSearch scope expects a Proc or Hash for its options"
17
+ def build_options_proc(scope_options)
18
+ return scope_options if scope_options.respond_to?(:call)
19
+
20
+ unless scope_options.respond_to?(:merge)
21
+ raise ArgumentError, "pg_search_scope expects a Hash or Proc"
28
22
  end
23
+
24
+ lambda { |query| {:query => query}.merge(scope_options) }
29
25
  end
30
26
  end
31
27
  end
@@ -2,31 +2,25 @@ require "active_support/core_ext/module/delegation"
2
2
 
3
3
  module PgSearch
4
4
  class ScopeOptions
5
- attr_reader :model
5
+ delegate :connection, :quoted_table_name, :sanitize_sql_array, :to => :@model
6
6
 
7
- delegate :connection, :quoted_table_name, :sanitize_sql_array, :to => :model
8
-
9
- def initialize(name, model, config)
7
+ def initialize(name, config)
10
8
  @name = name
11
- @model = model
12
9
  @config = config
13
-
14
- @feature_options = @config.features.inject({}) do |features_hash, (feature_name, feature_options)|
15
- features_hash.merge(
16
- feature_name => feature_options
17
- )
18
- end
19
- @feature_names = @config.features.map { |feature_name, feature_options| feature_name }
10
+ @model = config.model
11
+ @feature_options = Hash[config.features]
20
12
  end
21
13
 
22
- def to_relation
23
- @model.select("#{quoted_table_name}.*, (#{rank}) AS pg_search_rank").where(conditions).order("pg_search_rank DESC, #{order_within_rank}").joins(joins)
14
+ def apply(scope)
15
+ scope.select("#{quoted_table_name}.*, (#{rank}) AS pg_search_rank").where(conditions).order("pg_search_rank DESC, #{order_within_rank}").joins(joins)
24
16
  end
25
17
 
26
18
  private
27
19
 
28
20
  def conditions
29
- @feature_names.map { |feature_name| "(#{sanitize_sql_array(feature_for(feature_name).conditions)})" }.join(" OR ")
21
+ @config.features.map do |feature_name, feature_options|
22
+ "(#{sanitize_sql_array(feature_for(feature_name).conditions)})"
23
+ end.join(" OR ")
30
24
  end
31
25
 
32
26
  def order_within_rank
@@ -34,7 +28,7 @@ module PgSearch
34
28
  end
35
29
 
36
30
  def primary_key
37
- "#{quoted_table_name}.#{connection.quote_column_name(model.primary_key)}"
31
+ "#{quoted_table_name}.#{connection.quote_column_name(@model.primary_key)}"
38
32
  end
39
33
 
40
34
  def joins
@@ -57,7 +51,13 @@ module PgSearch
57
51
 
58
52
  normalizer = Normalizer.new(@config)
59
53
 
60
- feature_class.new(@config.query, @feature_options[feature_name], @config.columns, @model, normalizer)
54
+ feature_class.new(
55
+ @config.query,
56
+ @feature_options[feature_name],
57
+ @config.columns,
58
+ @config.model,
59
+ normalizer
60
+ )
61
61
  end
62
62
 
63
63
  def rank
@@ -1,3 +1,3 @@
1
1
  module PgSearch
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
@@ -105,22 +105,21 @@ describe PgSearch::Multisearch do
105
105
  PgSearch::Document.last(2).map(&:searchable).map(&:title).should =~ new_models.map(&:title)
106
106
  end
107
107
  end
108
- end
109
-
110
- describe ".rebuild_sql" do
111
- let(:now) { Time.now }
112
108
 
113
- before do
114
- Time.stub(:now => now)
115
- end
109
+ describe "the generated SQL" do
110
+ let(:now) { Time.now }
116
111
 
117
- context "with one attribute" do
118
112
  before do
119
- model.multisearchable :against => [:title]
113
+ Time.stub(:now => now)
120
114
  end
121
115
 
122
- it "should generate the proper SQL code" do
123
- expected_sql = <<-SQL
116
+ context "with one attribute" do
117
+ before do
118
+ model.multisearchable :against => [:title]
119
+ end
120
+
121
+ it "should generate the proper SQL code" do
122
+ expected_sql = <<-SQL
124
123
  INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content, created_at, updated_at)
125
124
  SELECT #{connection.quote(model.name)} AS searchable_type,
126
125
  #{model.quoted_table_name}.id AS searchable_id,
@@ -132,17 +131,22 @@ INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable
132
131
  FROM #{model.quoted_table_name}
133
132
  SQL
134
133
 
135
- PgSearch::Multisearch.rebuild_sql(model).should == expected_sql
136
- end
137
- end
134
+ statements = []
135
+ connection.stub(:execute) { |sql| statements << sql }
138
136
 
139
- context "with multiple attributes" do
140
- before do
141
- model.multisearchable :against => [:title, :content]
137
+ PgSearch::Multisearch.rebuild(model)
138
+
139
+ statements.should include(expected_sql)
140
+ end
142
141
  end
143
142
 
144
- it "should generate the proper SQL code" do
145
- expected_sql = <<-SQL
143
+ context "with multiple attributes" do
144
+ before do
145
+ model.multisearchable :against => [:title, :content]
146
+ end
147
+
148
+ it "should generate the proper SQL code" do
149
+ expected_sql = <<-SQL
146
150
  INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content, created_at, updated_at)
147
151
  SELECT #{connection.quote(model.name)} AS searchable_type,
148
152
  #{model.quoted_table_name}.id AS searchable_id,
@@ -152,9 +156,15 @@ INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable
152
156
  #{connection.quote(connection.quoted_date(now))} AS created_at,
153
157
  #{connection.quote(connection.quoted_date(now))} AS updated_at
154
158
  FROM #{model.quoted_table_name}
155
- SQL
159
+ SQL
160
+
161
+ statements = []
162
+ connection.stub(:execute) { |sql| statements << sql }
156
163
 
157
- PgSearch::Multisearch.rebuild_sql(model).should == expected_sql
164
+ PgSearch::Multisearch.rebuild(model)
165
+
166
+ statements.should include(expected_sql)
167
+ end
158
168
  end
159
169
  end
160
170
  end
data/spec/spec_helper.rb CHANGED
@@ -28,11 +28,13 @@ begin
28
28
  postgresql_version = connection.send(:postgresql_version)
29
29
  connection.execute("SELECT 1")
30
30
  rescue error_class => e
31
- puts "-" * 80
32
- puts "Unable to connect to database. Please run:"
33
- puts
34
- puts " createdb pg_search_test"
35
- puts "-" * 80
31
+ at_exit do
32
+ puts "-" * 80
33
+ puts "Unable to connect to database. Please run:"
34
+ puts
35
+ puts " createdb pg_search_test"
36
+ puts "-" * 80
37
+ end
36
38
  raise e
37
39
  end
38
40
 
@@ -56,9 +58,11 @@ rescue => e
56
58
  puts $!.message
57
59
  end
58
60
  rescue => e2
59
- puts "-" * 80
60
- puts "Please install the #{name} contrib module"
61
- puts "-" * 80
61
+ at_exit do
62
+ puts "-" * 80
63
+ puts "Please install the #{name} contrib module"
64
+ puts "-" * 80
65
+ end
62
66
  raise e2
63
67
  end
64
68
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-01 00:00:00.000000000 Z
12
+ date: 2012-09-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -65,9 +65,11 @@ files:
65
65
  - lib/pg_search/configuration.rb
66
66
  - lib/pg_search/configuration/association.rb
67
67
  - lib/pg_search/configuration/column.rb
68
+ - lib/pg_search/configuration/foreign_column.rb
68
69
  - lib/pg_search/document.rb
69
70
  - lib/pg_search/features.rb
70
71
  - lib/pg_search/features/dmetaphone.rb
72
+ - lib/pg_search/features/feature.rb
71
73
  - lib/pg_search/features/trigram.rb
72
74
  - lib/pg_search/features/tsearch.rb
73
75
  - lib/pg_search/migration/associated_against_generator.rb
@@ -77,6 +79,7 @@ files:
77
79
  - lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb
78
80
  - lib/pg_search/migration/templates/create_pg_search_documents.rb
79
81
  - lib/pg_search/multisearch.rb
82
+ - lib/pg_search/multisearch/rebuilder.rb
80
83
  - lib/pg_search/multisearchable.rb
81
84
  - lib/pg_search/normalizer.rb
82
85
  - lib/pg_search/railtie.rb
@@ -111,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
114
  version: '0'
112
115
  segments:
113
116
  - 0
114
- hash: -1351986722427483921
117
+ hash: -1173780423204059667
115
118
  required_rubygems_version: !ruby/object:Gem::Requirement
116
119
  none: false
117
120
  requirements:
@@ -120,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
123
  version: '0'
121
124
  segments:
122
125
  - 0
123
- hash: -1351986722427483921
126
+ hash: -1173780423204059667
124
127
  requirements: []
125
128
  rubyforge_project:
126
129
  rubygems_version: 1.8.24