pg_searchable 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d0244ce37cfcef0e4b9391ef9751d01a146660bf
4
+ data.tar.gz: 235f975076ef6ba068f78f6135d792c78e6e882b
5
+ SHA512:
6
+ metadata.gz: 9293c648bdd76a2b81fe062ebfa787b72c03a74abe2a24ea3af433fdef62dca73fd2dfc7e2ffe49e1d0e679a2429fa9582e44c874d487ec8dc3360e1d7c77685
7
+ data.tar.gz: a7093702d6aa93692140325374e1f5a5f209baeabaf4d7fbd7d0c2b54c57eb1c321b41676b14a7a3bd3abf3958f29dedba40a1b7ec10cd218abe4b3eae316b69
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ tags
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pg_searchable.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stephen St. Martin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # PgSearchable
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pg_searchable'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pg_searchable
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'lib/pg_searchable'
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,10 @@
1
+ require 'pg_searchable/version'
2
+ require 'pg_searchable/active_record'
3
+ require 'pg_searchable/arel'
4
+ require "pg_searchable/railtie" if defined?(Rails)
5
+
6
+ # TODO:
7
+ # 1) add ranking functions for full text search
8
+ # 2) write tests for migration helpers
9
+ # 3) add support for postgis?
10
+ # 4) add Relation methods
@@ -0,0 +1,3 @@
1
+ require 'pg_searchable/active_record/extensions'
2
+ require 'pg_searchable/active_record/migration'
3
+ require 'pg_searchable/active_record/relation'
@@ -0,0 +1,43 @@
1
+ require 'active_record'
2
+ require 'active_support/concern'
3
+
4
+ module PgSearchable
5
+ module ActiveRecord
6
+ module Extensions
7
+ extend ActiveSupport::Concern
8
+
9
+ DEFAULT_WEIGHTS = [0.1, 0.2, 0.4, 1.0]
10
+ DEFAULT_DICTIONARY = 'english'
11
+ DEFAULT_OPTIONS = { weights: DEFAULT_WEIGHTS, dictionary: DEFAULT_DICTIONARY }
12
+
13
+ module ClassMethods
14
+ def pg_searchable (name, options = {})
15
+ pg_searchable_configs[name.to_sym] = {
16
+ tgrm: _pg_searchable_options,
17
+ dmetaphone: _pg_searchable_options,
18
+ tsearch: _pg_searchable_options
19
+ }.deep_merge(options)
20
+ end
21
+
22
+ def pg_searchable_configs
23
+ @pg_searchable_configs ||= {}
24
+ end
25
+
26
+ def search_for(term, options = {})
27
+ scoped.search_for(term, options)
28
+ end
29
+
30
+ private
31
+ def _pg_searchable_options
32
+ @_pg_searchable_options ||= DEFAULT_OPTIONS.merge(columns: _pg_searchable_columns)
33
+ end
34
+
35
+ def _pg_searchable_columns
36
+ @_pg_searchable_columns ||= columns_hash.find_all {|k,v| [:string, :text].include?(v.type) }.map {|k,v| v.name }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ ActiveRecord::Base.send(:include, PgSearchable::ActiveRecord::Extensions)
@@ -0,0 +1,98 @@
1
+ require 'active_record/migration'
2
+
3
+ module ActiveRecord
4
+ class Migration
5
+ def add_pg_searchable_tsearch_trigger(table_name, column_name, options = {})
6
+ options.reverse_merge({ dictionary: 'simple', columns: nil })
7
+ column_data = case options[:columns]
8
+ when Array then "to_tsvector('#{options['dictionary']}', #{options[:columns].map {|column_name| "coalesce(new.#{column_name}, '')" }.join(" || ' ' || ") });"
9
+ when Hash then "#{options[:columns].map {|column_name, weight| "setweight(to_tsvector('#{options[:dictionary]}', coalesce(new.#{column_name}, '')), '#{weight}')" }.join(" || ")};"
10
+ end
11
+
12
+ _add_pg_searchable_trigger(table_name, column_name, :tsearch, column_data)
13
+ end
14
+
15
+ def remove_pg_searchable_tsearch_trigger(table_name, column_name)
16
+ _remove_pg_searchable_trigger(table_name, column_name, :tsearch)
17
+ end
18
+
19
+ def add_pg_searchable_dmetaphone_trigger(table_name, column_name, options = {})
20
+ options.reverse_merge({ dictionary: 'simple', columns: nil })
21
+ column_data = case options[:columns]
22
+ when Array then "to_tsvector('#{options['dictionary']}', #{options[:columns].map {|column_name| "pg_searchable_dmetaphone(coalesce(new.#{column_name}, ''))" }.join(" || ' ' || ") });"
23
+ when Hash then "#{options[:columns].map {|column_name, weight| "setweight(to_tsvector('#{options[:dictionary]}', pg_searchable_dmetaphone(coalesce(new.#{column_name}, ''))), '#{weight}')" }.join(" || ")};"
24
+ end
25
+
26
+ _add_pg_searchable_trigger(table_name, column_name, :dmetaphone, column_data)
27
+ end
28
+
29
+ def remove_pg_searchable_dmetaphone_trigger(table_name, column_name)
30
+ _remove_pg_searchable_trigger(table_name, column_name, :dmetaphone)
31
+ end
32
+
33
+ def add_pg_searchable_dictionary(dictionary, options = {})
34
+ remove_pg_searchable_dictionary(dictionary)
35
+
36
+ options = {
37
+ catalog: 'english',
38
+ template: 'ispell',
39
+ dict_file: 'english',
40
+ aff_file: 'english',
41
+ stop_words: 'english',
42
+ mappings: 'asciiword, asciihword, hword_asciipart, word, hword, hword_part'
43
+ }.merge(options)
44
+
45
+ execute <<-EOS
46
+ CREATE TEXT SEARCH CONFIGURATION #{dictionary} ( COPY = pg_catalog.#{options[:catalog]} );
47
+ CREATE TEXT SEARCH DICTIONARY #{dictionary}_dict (
48
+ TEMPLATE = #{options[:template]},
49
+ DictFile = #{options[:dict_file]},
50
+ AffFile = #{options[:aff_file]},
51
+ StopWords = #{options[:stop_words]}
52
+ );
53
+ ALTER TEXT SEARCH CONFIGURATION #{dictionary}
54
+ ALTER MAPPING FOR #{options[:mappings]}
55
+ WITH unaccent, #{dictionary}_dict, english_stem;
56
+
57
+ EOS
58
+ end
59
+
60
+ def remove_pg_searchable_dictionary(dictionary)
61
+ execute <<-SQL
62
+ DROP TEXT SEARCH CONFIGURATION IF EXISTS #{dictionary};
63
+ DROP TEXT SEARCH DICTIONARY IF EXISTS #{dictionary}_dict;
64
+ SQL
65
+ end
66
+
67
+ private
68
+
69
+ def _add_pg_searchable_trigger(table_name, column_name, trigger_type = 'tsearch', column_data = '')
70
+ trigger_name = "#{table_name}_#{column_name}_#{trigger_type}"
71
+
72
+ # be sure to remove trigger / function before recreating it
73
+ _remove_pg_searchable_trigger(table_name, column_name, trigger_type)
74
+
75
+ execute <<-SQL
76
+ CREATE OR REPLACE FUNCTION #{trigger_name}_update() RETURNS trigger AS $$
77
+ begin
78
+ new.#{column_name} := #{column_data}
79
+ return new;
80
+ end
81
+ $$ LANGUAGE plpgsql;
82
+
83
+ CREATE TRIGGER #{trigger_name} BEFORE INSERT OR UPDATE
84
+ ON #{table_name}
85
+ FOR EACH ROW EXECUTE PROCEDURE #{trigger_name}_update();
86
+ SQL
87
+ end
88
+
89
+ def _remove_pg_searchable_trigger(table_name, column_name, trigger_type = 'tsearch')
90
+ trigger_name = "#{table_name}_#{column_name}_#{trigger_type}"
91
+
92
+ execute <<-SQL
93
+ DROP TRIGGER IF EXISTS #{trigger_name} ON #{table_name};
94
+ DROP FUNCTION IF EXISTS #{trigger_name}_update();
95
+ SQL
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_record/relation'
2
+
3
+ module PgSearchable
4
+ module ActiveRecord
5
+ module Relation
6
+ def search_for(term, options = {})
7
+ using = Array(options.delete(:using) || :default)
8
+ models = Array(options[:in]).map {|association| klass.reflect_on_association(association).klass }
9
+ conditions = [self.klass, models].flatten.inject([]) do |conditions, klass|
10
+ name = using.find {|name| klass.pg_searchable_configs.key?(name.to_sym) }
11
+ config = klass.pg_searchable_configs[name]
12
+
13
+ Array(config[:tgrm][:columns]).each {|name| conditions << klass.arel_table[name].tgrm(term).to_sql }
14
+ Array(config[:tsearch][:columns]).each {|name| conditions << klass.arel_table[name].tsearch(term, config[:tsearch][:dictionary]).to_sql }
15
+ Array(config[:dmetaphone][:columns]).each {|name| conditions << klass.arel_table[name].dmetaphone(term, config[:dmetaphone][:dictionary]).to_sql }
16
+ conditions
17
+ end
18
+
19
+ p conditions
20
+
21
+ joins(options[:in]).where("#{conditions.join(' OR ')}")
22
+ end
23
+
24
+ def near(latitude, longitude)
25
+ puts "searching near #{latitude},#{longitude}"
26
+ self
27
+ end
28
+
29
+ def rank_by(rank)
30
+ # TODO: add ranks to projections
31
+ puts "ranking by #{rank}"
32
+ self
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ ActiveRecord::Relation.send(:include, PgSearchable::ActiveRecord::Relation)
@@ -0,0 +1,4 @@
1
+ require 'arel'
2
+ require 'pg_searchable/arel/nodes'
3
+ require 'pg_searchable/arel/predications'
4
+ require 'pg_searchable/arel/visitors'
@@ -0,0 +1,6 @@
1
+ require 'pg_searchable/arel/nodes/dmetaphone'
2
+ require 'pg_searchable/arel/nodes/pg_searchable_dmetaphone'
3
+ require 'pg_searchable/arel/nodes/tgrm'
4
+ require 'pg_searchable/arel/nodes/tsearch'
5
+ require 'pg_searchable/arel/nodes/to_tsquery'
6
+ require 'pg_searchable/arel/nodes/to_tsvector'
@@ -0,0 +1,20 @@
1
+ require 'arel/nodes/infix_operation'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class Dmetaphone < Arel::Nodes::InfixOperation
6
+ def initialize(attribute, query, dictionary)
7
+ relation = attribute.relation
8
+ columns = relation.engine.connection.columns(relation.name)
9
+ left = case columns.find {|c| c.name == attribute.name.to_s }.type
10
+ when :tsvector
11
+ attribute
12
+ else
13
+ Arel::Nodes::ToTsvector.new(attribute, dictionary)
14
+ end
15
+
16
+ super(:'@@', left, Arel::Nodes::ToTsquery.new(Arel::Nodes::PgSearchableDmetaphone.new(query), dictionary))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'arel/nodes/named_function'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class PgSearchableDmetaphone < Arel::Nodes::NamedFunction
6
+ def initialize(query)
7
+ super(:pg_searchable_dmetaphone, [query])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'arel/nodes/infix_operation'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class Tgrm < Arel::Nodes::InfixOperation
6
+ def initialize(left, right)
7
+ super(:%, left, right)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'arel/nodes/named_function'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class ToTsquery < Arel::Nodes::NamedFunction
6
+ def initialize(query, dictionary = 'simple')
7
+ super(:to_tsquery, [dictionary, query])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'arel/nodes/named_function'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class ToTsvector < Arel::Nodes::NamedFunction
6
+ def initialize(query, dictionary = 'simple')
7
+ super(:to_tsvector, [dictionary, query])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ require 'arel/nodes/infix_operation'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class Tsearch < Arel::Nodes::InfixOperation
6
+ def initialize(attribute, query, dictionary)
7
+ relation = attribute.relation
8
+ columns = relation.engine.connection.columns(relation.name)
9
+ left = case columns.find {|c| c.name == attribute.name.to_s }.type
10
+ when :tsvector
11
+ attribute
12
+ else
13
+ Arel::Nodes::ToTsvector.new(attribute, dictionary)
14
+ end
15
+
16
+ super(:'@@', left, Arel::Nodes::ToTsquery.new(query, dictionary))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ require 'arel/predications'
2
+
3
+ module Arel
4
+ module Predications
5
+ def tgrm(text)
6
+ Nodes::Tgrm.new(self, text)
7
+ end
8
+
9
+ def tsearch(text, dictionary = 'simple')
10
+ Nodes::Tsearch.new(self, text, dictionary)
11
+ end
12
+
13
+ def dmetaphone(text, dictionary = 'simple')
14
+ Nodes::Dmetaphone.new(self, text, dictionary)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ require 'arel/visitors/to_sql'
2
+
3
+ module Arel
4
+ module Visitors
5
+ class ToSql
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails/generators/base'
2
+
3
+ module PgSearchable
4
+ module Migrations
5
+ class DmetaphoneGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def create_migration
9
+ now = Time.now.utc
10
+ filename = "#{now.strftime('%Y%m%d%H%M%S')}_add_pg_searchable_dmetaphone_support_function.rb"
11
+ template 'add_pg_searchable_dmetaphone_support_function.rb.erb', "db/migrate/#{filename}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ class AddPgSearchableDmetaphoneSupportFunction < ActiveRecord::Migration
2
+ def self.up
3
+ say_with_time("Adding support function for pg_searchable :dmetaphone") do
4
+ sql = <<-SQL
5
+ CREATE OR REPLACE FUNCTION pg_searchable_dmetaphone(text) RETURNS text LANGUAGE SQL IMMUTABLE STRICT AS $function$
6
+ SELECT array_to_string(ARRAY(SELECT dmetaphone(unnest(regexp_split_to_array($1, E'\\s+')))), ' ')
7
+ $function$;
8
+ SQL
9
+
10
+ execute sql.strip
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ say_with_time("Dropping support function for pg_searchable :dmetaphone") do
16
+ execute <<-SQL
17
+ DROP FUNCTION IF EXISTS pg_searchable_dmetaphone(text)
18
+ SQL
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module PgSearchable
2
+ class Railtie < Rails::Railtie
3
+ generators do
4
+ require 'pg_searchable/migrations/dmetaphone_generator'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module PgSearchable
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pg_searchable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pg_searchable"
8
+ spec.version = PgSearchable::VERSION
9
+ spec.authors = ["Stephen St. Martin"]
10
+ spec.email = ["steve@stevestmartin.com"]
11
+ spec.description = %q{Simple ActiveRecord PostgreSQL full text backed by Arel}
12
+ spec.summary = %q{Simple ActiveRecord PostgreSQL full text backed by Arel}
13
+ spec.homepage = "https://github.com/stevestmartin/pg_searchable"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "arel", ">= 3.0.0"
22
+ spec.add_dependency "activerecord", ">= 3.0.0"
23
+ spec.add_dependency "activesupport", ">= 3.0.0"
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "minitest", "~> 4.6"
26
+ spec.add_development_dependency "rake"
27
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe "migration helpers" do
4
+ describe "add tsearch trigger" do
5
+ end
6
+
7
+ describe "remove tsearch trigger" do
8
+ end
9
+
10
+ describe "add dmetaphone trigger" do
11
+ end
12
+
13
+ describe "remove dmetaphone trigger" do
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe "text search predications" do
4
+ describe "tgrm" do
5
+ it "should compare using tgrm operator" do
6
+ table = Arel::Table.new :articles
7
+ table[:title].tgrm("query").to_sql.must_be_like %{"articles"."title" % 'query'}
8
+ end
9
+ end
10
+
11
+ describe "tsearch" do
12
+ it "should compare column as tsvector when a string" do
13
+ table = Arel::Table.new :articles
14
+ table[:title].tsearch("query", "dictionary").to_sql.must_be_like %{to_tsvector('dictionary', "articles"."title") @@ to_tsquery('dictionary', 'query')}
15
+ end
16
+
17
+ it "should compare column as is when a tsvector" do
18
+ table = Arel::Table.new :articles
19
+ table[:tsvector].tsearch("query", "dictionary").to_sql.must_be_like %{"articles"."tsvector" @@ to_tsquery('dictionary', 'query')}
20
+ end
21
+ end
22
+
23
+ describe "dmetaphone" do
24
+ it "should compare column as tsvector when a string" do
25
+ table = Arel::Table.new :articles
26
+ table[:title].dmetaphone("query", "dictionary").to_sql.must_be_like %{to_tsvector('dictionary', "articles"."title") @@ to_tsquery('dictionary', pg_searchable_dmetaphone('query'))}
27
+ end
28
+
29
+ it "should compare column as tsvector when a string" do
30
+ table = Arel::Table.new :articles
31
+ table[:tsvector].dmetaphone("query", "dictionary").to_sql.must_be_like %{"articles"."tsvector" @@ to_tsquery('dictionary', pg_searchable_dmetaphone('query'))}
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,121 @@
1
+ module FakeRecord
2
+ class Column < Struct.new(:name, :type)
3
+ end
4
+
5
+ class Connection
6
+ attr_reader :columns_hash, :tables
7
+ attr_accessor :visitor
8
+
9
+ def initialize(visitor = nil)
10
+ @tables = %w{ articles categories comments users}
11
+ @columns = {
12
+ 'articles' => [
13
+ Column.new('id', :integer),
14
+ Column.new('title', :string),
15
+ Column.new('body', :text),
16
+ Column.new('created_at', :date),
17
+ Column.new('tsvector', :tsvector)
18
+ ],
19
+ 'categories' => [
20
+ Column.new('id', :integer),
21
+ Column.new('name', :string),
22
+ Column.new('tsvector', :tsvector)
23
+ ]
24
+ }
25
+ @columns_hash = {
26
+ 'articles' => Hash[@columns['articles'].map { |x| [x.name, x] }],
27
+ 'categories' => Hash[@columns['categories'].map { |x| [x.name, x] }]
28
+ }
29
+ @primary_keys = {
30
+ 'articles' => 'id',
31
+ 'categories' => 'id'
32
+ }
33
+ @visitor = visitor
34
+ end
35
+
36
+ def primary_key name
37
+ @primary_keys[name.to_s]
38
+ end
39
+
40
+ def table_exists? name
41
+ @tables.include? name.to_s
42
+ end
43
+
44
+ def columns name, message = nil
45
+ @columns[name.to_s]
46
+ end
47
+
48
+ def quote_table_name name
49
+ "\"#{name.to_s}\""
50
+ end
51
+
52
+ def quote_column_name name
53
+ "\"#{name.to_s}\""
54
+ end
55
+
56
+ def schema_cache
57
+ self
58
+ end
59
+
60
+ def quote thing, column = nil
61
+ if column && column.type == :integer
62
+ return 'NULL' if thing.nil?
63
+ return thing.to_i
64
+ end
65
+
66
+ case thing
67
+ when true
68
+ "'t'"
69
+ when false
70
+ "'f'"
71
+ when nil
72
+ 'NULL'
73
+ when Numeric
74
+ thing
75
+ else
76
+ "'#{thing}'"
77
+ end
78
+ end
79
+ end
80
+
81
+ class ConnectionPool
82
+ class Spec < Struct.new(:config)
83
+ end
84
+
85
+ attr_reader :spec, :connection
86
+
87
+ def initialize
88
+ @spec = Spec.new(:adapter => 'america')
89
+ @connection = Connection.new
90
+ @connection.visitor = Arel::Visitors::ToSql.new(connection)
91
+ end
92
+
93
+ def with_connection
94
+ yield connection
95
+ end
96
+
97
+ def table_exists? name
98
+ connection.tables.include? name.to_s
99
+ end
100
+
101
+ def columns_hash
102
+ connection.columns_hash
103
+ end
104
+
105
+ def schema_cache
106
+ connection
107
+ end
108
+ end
109
+
110
+ class Base
111
+ attr_accessor :connection_pool
112
+
113
+ def initialize
114
+ @connection_pool = ConnectionPool.new
115
+ end
116
+
117
+ def connection
118
+ connection_pool.connection
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+ require 'minitest/pride'
4
+ require_relative '../lib/pg_searchable'
5
+ require_relative 'support/fake_record'
6
+
7
+ Arel::Table.engine = Arel::Sql::Engine.new(FakeRecord::Base.new)
8
+
9
+ class Object
10
+ def must_be_like other
11
+ gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe PgSearchable do
4
+ it "must be defined" do
5
+ PgSearchable::VERSION.wont_be_nil
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_searchable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Stephen St. Martin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: arel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '4.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '4.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Simple ActiveRecord PostgreSQL full text backed by Arel
98
+ email:
99
+ - steve@stevestmartin.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - lib/pg_searchable.rb
110
+ - lib/pg_searchable/active_record.rb
111
+ - lib/pg_searchable/active_record/extensions.rb
112
+ - lib/pg_searchable/active_record/migration.rb
113
+ - lib/pg_searchable/active_record/relation.rb
114
+ - lib/pg_searchable/arel.rb
115
+ - lib/pg_searchable/arel/nodes.rb
116
+ - lib/pg_searchable/arel/nodes/dmetaphone.rb
117
+ - lib/pg_searchable/arel/nodes/pg_searchable_dmetaphone.rb
118
+ - lib/pg_searchable/arel/nodes/tgrm.rb
119
+ - lib/pg_searchable/arel/nodes/to_tsquery.rb
120
+ - lib/pg_searchable/arel/nodes/to_tsvector.rb
121
+ - lib/pg_searchable/arel/nodes/tsearch.rb
122
+ - lib/pg_searchable/arel/predications.rb
123
+ - lib/pg_searchable/arel/visitors.rb
124
+ - lib/pg_searchable/migrations/dmetaphone_generator.rb
125
+ - lib/pg_searchable/migrations/templates/add_pg_searchable_dmetaphone_support_function.rb.erb
126
+ - lib/pg_searchable/railtie.rb
127
+ - lib/pg_searchable/version.rb
128
+ - pg_searchable.gemspec
129
+ - test/active_record/migration_test.rb
130
+ - test/arel/predication_test.rb
131
+ - test/support/fake_record.rb
132
+ - test/test_helper.rb
133
+ - test/version_test.rb
134
+ homepage: https://github.com/stevestmartin/pg_searchable
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.0.3
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Simple ActiveRecord PostgreSQL full text backed by Arel
158
+ test_files:
159
+ - test/active_record/migration_test.rb
160
+ - test/arel/predication_test.rb
161
+ - test/support/fake_record.rb
162
+ - test/test_helper.rb
163
+ - test/version_test.rb
164
+ has_rdoc: