pg_search_scope 0.1.0

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/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
6
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in pg_search_scope.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Ivan Efremov, Ilia Ablamonov, Cloud Castle Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ ### PG Search Scope
2
+
3
+ PostgreSQL full text search using Rails 3 scopes
4
+
5
+ ## Basic usage
6
+
7
+ Include `gem pg_search_scope` in your `Gemfile`
8
+
9
+ In model, use `search_scope_for` class method to create full text search scope.
10
+
11
+ Examples:
12
+
13
+ search_scope_for :name
14
+ -->
15
+ search_by_name("Ivan")
16
+
17
+
18
+ In migration, use `add_fulltext_index` / `remove_fulltext_index` the same way as `add_index` / `remove_index`
19
+
20
+ ## Advanced usage
21
+
22
+ You can set additional search options:
23
+
24
+ :as - Scope name
25
+
26
+ :normalization - Controls rank behaviour, see http://www.postgresql.org/docs/9.0/static/textsearch-controls.html#TEXTSEARCH-RANKING
27
+
28
+ :wildcard - Controls search words modification:
29
+ true - add :* to ends of each search word
30
+ false - do not modify search words
31
+ :last - add :* to end of last word
32
+
33
+ :operator - Boolean operator (:and or :or) which combines search query
34
+
35
+ :select_rank - Include rank in select statement, as {scope_name}_rank
36
+
37
+ :language - Search language, e.g. 'simple' (without magic), 'english'
38
+
39
+ If you use `:language` option, you need to use the same option for `add_fulltext_index`
40
+
41
+ Examples:
42
+
43
+ search_scope_for :name, :address,
44
+ :wildcard => :last
45
+ -->
46
+ search_by_name_and_address("Ivan, Aurora st.", :select_rank => true)
47
+
48
+
49
+ ## To do
50
+
51
+ ...
52
+
53
+ ## Copyright
54
+
55
+ Copyright (c) 2011 Ivan Efremov, Ilia Ablamonov, Cloud Castle Inc.
56
+ See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,22 @@
1
+ module PgSearchScope
2
+ module MigrationHelper
3
+ def add_fulltext_index table_name, column_name, options = {}
4
+ options[:language] ||= PgSearchScope::ModelHelper::DEFAULT_OPTIONS[:language]
5
+ column_names = Array.wrap(column_name)
6
+ index_name = options[:name].presence || index_name(table_name, :column => column_names) + '_ftx'
7
+ execute(<<-"eosql".strip)
8
+ CREATE INDEX #{index_name} ON #{table_name}
9
+ USING GIN(TO_TSVECTOR('#{options[:language]}', #{column_names.map { |name| "COALESCE(\"#{table_name}\".\"#{name}\", '')" }.join(" || ' ' || ")}))
10
+ eosql
11
+ end
12
+
13
+ def remove_fulltext_index table_name, options = {}
14
+ index_name = index_name(table_name, options) + '_ftx'
15
+ execute(<<-"eosql".strip)
16
+ DROP INDEX IF EXISTS #{index_name}
17
+ eosql
18
+ end
19
+ end
20
+ end
21
+
22
+ ActiveRecord::Migration.send :extend, PgSearchScope::MigrationHelper
@@ -0,0 +1,90 @@
1
+ module PgSearchScope
2
+ module ModelHelper
3
+ DEFAULT_OPTIONS = {
4
+ :as => nil,
5
+ :wildcard => true,
6
+ :operator => :and,
7
+ :normalization => 0,
8
+ :select_rank => false,
9
+ :language => 'simple'
10
+ }
11
+ OPERATORS = {
12
+ :and => '&',
13
+ :or => '|'
14
+ }
15
+
16
+ # Creates fulltext search scope
17
+ #
18
+ # == Options
19
+ #
20
+ # * <tt>:as</tt> - Scope name
21
+ #
22
+ # * <tt>:normalization</tt> - Controls rank behaviour, see http://www.postgresql.org/docs/9.0/static/textsearch-controls.html#TEXTSEARCH-RANKING
23
+ #
24
+ # * <tt>:wildcard</tt> - Controls search words modification:
25
+ # true - add :* to ends of each search word
26
+ # false - do not modify search words
27
+ # :last - add :* to end of last word
28
+ #
29
+ # * <tt>:operator</tt> - Boolean operator (:and or :or) which combines search query
30
+ #
31
+ # * <tt>:select_rank</tt> - Include rank in select statement, as {scope_name}_rank
32
+ #
33
+ # * <tt>:language</tt> - Search language, e.g. 'simple' (without magic), 'english'
34
+ #
35
+ # == Usage
36
+ #
37
+ # search_scope_for :name
38
+ # -->
39
+ # search_by_name("Ivan")
40
+ #
41
+ # search_scope_for :name, :address,
42
+ # :wildcard => :last
43
+ # -->
44
+ # search_by_name_and_address("Ivan, Aurora st.", :select_rank => true)
45
+ #
46
+ def search_scope_for *column_names
47
+ scope_options = DEFAULT_OPTIONS.merge column_names.extract_options!
48
+
49
+ scope_name = scope_options[:as] || "search_by_#{column_names.join('_and_')}"
50
+
51
+ scope scope_name, Proc.new { |search_string, options|
52
+ options = scope_options.merge(options || {})
53
+ search_string ||= ''
54
+
55
+ terms = search_string.strip.split(/[^\w\d\.']+/).map { |s| s.gsub /'/, "''" }
56
+
57
+ if terms.present?
58
+ prefix = arel_table.table_alias || arel_table.name
59
+ document = column_names.map { |n| "coalesce(#{prefix}.#{n}, '')" }.join(" || ' ' || ")
60
+
61
+ case options[:wildcard]
62
+ when true then
63
+ terms.map! { |s| "#{s}:*" }
64
+ when :last then
65
+ terms[-1] = "#{terms[-1]}:*"
66
+ end
67
+
68
+ tsvector = "to_tsvector('#{options[:language]}', #{document})"
69
+ tsquery = "to_tsquery('#{options[:language]}', '#{terms.join(" #{OPERATORS[options[:operator]]} ")}')"
70
+
71
+ rank = "ts_rank(#{tsvector}, #{tsquery}, #{options[:normalization]})"
72
+
73
+ search_scope = scoped
74
+
75
+ if options[:select_rank]
76
+ search_scope = search_scope.select("#{rank} #{scope_name}_rank")
77
+ end
78
+
79
+ search_scope.where("#{tsvector} @@ #{tsquery}").order("#{rank} DESC")
80
+ else
81
+ if options[:select_rank]
82
+ scoped.select("0 #{scope_name}_rank")
83
+ end
84
+ end
85
+ }
86
+ end
87
+ end
88
+ end
89
+
90
+ ActiveRecord::Base.send :extend, PgSearchScope::ModelHelper
@@ -0,0 +1,3 @@
1
+ module PgSearchScope
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require 'pg_search_scope/version'
2
+ require 'pg_search_scope/model_helper'
3
+ require 'pg_search_scope/migration_helper'
4
+
5
+ module PgSearchScope
6
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "pg_search_scope/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "pg_search_scope"
7
+ s.version = PgSearchScope::VERSION
8
+ s.authors = ["Ivan Efremov, Ilia Ablamonov, Cloud Castle Inc."]
9
+ s.email = ["ilia@flamefork.ru", "st8998@gmail.com"]
10
+ s.homepage = "https://github.com/cloudcastle/pg_search_scope"
11
+ s.summary = %q{PostgreSQL full text search using Rails 3 scopes}
12
+ s.description = %q{}
13
+
14
+ s.rubyforge_project = "pg_search_scope"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_search_scope
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ivan Efremov, Ilia Ablamonov, Cloud Castle Inc.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-11 00:00:00.000000000 +04:00
13
+ default_executable:
14
+ dependencies: []
15
+ description: ''
16
+ email:
17
+ - ilia@flamefork.ru
18
+ - st8998@gmail.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - .gitignore
24
+ - Gemfile
25
+ - LICENSE
26
+ - README.md
27
+ - Rakefile
28
+ - lib/pg_search_scope.rb
29
+ - lib/pg_search_scope/migration_helper.rb
30
+ - lib/pg_search_scope/model_helper.rb
31
+ - lib/pg_search_scope/version.rb
32
+ - pg_search_scope.gemspec
33
+ has_rdoc: true
34
+ homepage: https://github.com/cloudcastle/pg_search_scope
35
+ licenses: []
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project: pg_search_scope
54
+ rubygems_version: 1.6.2
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: PostgreSQL full text search using Rails 3 scopes
58
+ test_files: []