acts_as_indexed 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ # WillPaginateSearch
2
+ # Copyright (c) 2007 - 2010 Douglas F Shearer.
3
+ # http://douglasfshearer.com
4
+
5
+ module WillPaginate
6
+
7
+ module Finder
8
+
9
+ module ClassMethods
10
+
11
+ # DEPRECATED. Use chained pagination instead.
12
+ def paginate_search(query, options)
13
+ warn "[DEPRECATION] `paginate_search` is deprecated and will be removed in a later release. Use `with_query(query).paginate()` instead."
14
+ page, per_page, total_entries = wp_parse_options(options)
15
+
16
+ total_entries ||= find_with_index(query,{},{:ids_only => true}).size
17
+
18
+ returning WillPaginate::Collection.new(page, per_page, total_entries) do |pager|
19
+ options.update :offset => pager.offset, :limit => pager.per_page
20
+
21
+ options = options.delete_if {|key, value| [:page, :per_page].include?(key) }
22
+
23
+ pager.replace find_with_index(query, options)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,2 @@
1
+ require 'acts_as_indexed'
2
+ require 'will_paginate_search'
@@ -0,0 +1,52 @@
1
+ require 'test/unit'
2
+ require 'fileutils'
3
+ require 'rubygems'
4
+ require 'active_record'
5
+ require 'active_record/fixtures'
6
+ require 'mocha'
7
+
8
+ # Mock out the required environment variables.
9
+ # Do this before requiring AAI.
10
+ class Rails
11
+ def self.root
12
+ Dir.pwd
13
+ end
14
+ def self.env
15
+ 'test'
16
+ end
17
+ end
18
+
19
+ require File.dirname(__FILE__) + '/../lib/acts_as_indexed'
20
+
21
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/test.log')
22
+ ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
23
+ ActiveRecord::Base.establish_connection(ENV['DB'] || 'sqlite3')
24
+
25
+ # Load Schema
26
+ load(File.dirname(__FILE__) + '/schema.rb')
27
+
28
+ # Load model.
29
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/fixtures/')
30
+
31
+ class ActiveSupport::TestCase #:nodoc:
32
+ include ActiveRecord::TestFixtures
33
+ self.fixture_path = File.dirname(__FILE__) + '/fixtures/'
34
+ self.use_transactional_fixtures = true
35
+ self.use_instantiated_fixtures = false
36
+
37
+ def destroy_index
38
+ `rm -rdf #{index_loc}`
39
+ end
40
+
41
+ def build_index
42
+ # Makes a query to invoke the index build.
43
+ assert_equal [], Post.find_with_index('badger')
44
+ assert File.exists?(index_loc)
45
+ true
46
+ end
47
+
48
+ def index_loc
49
+ File.join(Rails.root,'index')
50
+ end
51
+
52
+ end
@@ -0,0 +1,133 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+
3
+ class ActsAsIndexedTest < ActiveSupport::TestCase
4
+ fixtures :posts
5
+
6
+ def teardown
7
+ destroy_index
8
+ end
9
+
10
+ def test_adds_to_index
11
+ original_post_count = Post.count
12
+ assert_equal [], Post.find_with_index('badger')
13
+ p = Post.new(:title => 'badger', :body => 'Thousands of them!')
14
+ assert p.save
15
+ assert_equal original_post_count+1, Post.count
16
+ assert_equal [p.id], Post.find_with_index('badger',{},{:ids_only => true})
17
+ end
18
+
19
+ def test_removes_from_index
20
+ original_post_count = Post.count
21
+ assert_equal [posts(:wikipedia_article_4).id], Post.find_with_index('album',{},{:ids_only => true})
22
+ assert Post.find(posts(:wikipedia_article_4).id).destroy
23
+ assert_equal [], Post.find_with_index('album',{},{:ids_only => true})
24
+ assert_equal original_post_count-1, Post.count
25
+ end
26
+
27
+ def test_search_returns_posts
28
+ Post.find_with_index('album').each do |p|
29
+ assert_equal Post, p.class
30
+ end
31
+ end
32
+
33
+ def test_scoped_search_returns_posts
34
+ Post.with_query('album').each do |p|
35
+ assert_equal Post, p.class
36
+ end
37
+ end
38
+
39
+ def test_search_returns_post_ids
40
+ Post.find_with_index('album',{},{:ids_only => true}).each do |pid|
41
+ assert p = Post.find(pid)
42
+ assert_equal Post, p.class
43
+ end
44
+ end
45
+
46
+ # After a portion of a record has been removed
47
+ # the portion removes should no longer be in the index.
48
+ def test_updates_index
49
+ p = Post.create(:title => 'A special title', :body => 'foo bar bla bla bla')
50
+ assert Post.find_with_index('title',{},{:ids_only => true}).include?(p.id)
51
+ p.update_attributes(:title => 'No longer special')
52
+ assert !Post.find_with_index('title',{},{:ids_only => true}).include?(p.id)
53
+ end
54
+
55
+ def test_simple_queries
56
+ assert_equal [], Post.find_with_index(nil)
57
+ assert_equal [], Post.find_with_index('')
58
+ assert_equal [5, 6], Post.find_with_index('ship',{},{:ids_only => true}).sort
59
+ assert_equal [6], Post.find_with_index('foo',{},{:ids_only => true})
60
+ assert_equal [6], Post.find_with_index('foo ship',{},{:ids_only => true})
61
+ assert_equal [6], Post.find_with_index('ship foo',{},{:ids_only => true})
62
+ end
63
+
64
+ def test_scoped_simple_queries
65
+ assert_equal [], Post.find_with_index(nil)
66
+ assert_equal [], Post.with_query('')
67
+ assert_equal [5, 6], Post.with_query('ship').map(&:id).sort
68
+ assert_equal [6], Post.with_query('foo').map(&:id)
69
+ assert_equal [6], Post.with_query('foo ship').map(&:id)
70
+ assert_equal [6], Post.with_query('ship foo').map(&:id)
71
+ end
72
+
73
+ def test_negative_queries
74
+ assert_equal [5, 6], Post.find_with_index('crane',{},{:ids_only => true}).sort
75
+ assert_equal [5], Post.find_with_index('crane -foo',{},{:ids_only => true})
76
+ assert_equal [5], Post.find_with_index('-foo crane',{},{:ids_only => true})
77
+ assert_equal [], Post.find_with_index('-foo') #Edgecase
78
+ end
79
+
80
+ def test_scoped_negative_queries
81
+ assert_equal [5, 6], Post.with_query('crane').map(&:id).sort
82
+ assert_equal [5], Post.with_query('crane -foo').map(&:id)
83
+ assert_equal [5], Post.with_query('-foo crane').map(&:id)
84
+ assert_equal [], Post.with_query('-foo') #Edgecase
85
+ end
86
+
87
+ def test_quoted_queries
88
+ assert_equal [5], Post.find_with_index('"crane ship"',{},{:ids_only => true})
89
+ assert_equal [6], Post.find_with_index('"crane big"',{},{:ids_only => true})
90
+ assert_equal [], Post.find_with_index('foo "crane ship"')
91
+ assert_equal [], Post.find_with_index('"crane badger"')
92
+ end
93
+
94
+ def test_scoped_quoted_queries
95
+ assert_equal [5], Post.with_query('"crane ship"').map(&:id)
96
+ assert_equal [6], Post.with_query('"crane big"').map(&:id)
97
+ assert_equal [], Post.with_query('foo "crane ship"')
98
+ assert_equal [], Post.with_query('"crane badger"')
99
+ end
100
+
101
+ def test_negative_quoted_queries
102
+ assert_equal [6], Post.find_with_index('crane -"crane ship"',{},{:ids_only => true})
103
+ assert_equal [], Post.find_with_index('-"crane big"',{},{:ids_only => true}) # Edgecase
104
+ end
105
+
106
+ def test_scoped_negative_quoted_queries
107
+ assert_equal [6], Post.with_query('crane -"crane ship"').map(&:id)
108
+ assert_equal [], Post.with_query('-"crane big"') # Edgecase
109
+ end
110
+
111
+ def test_find_options
112
+ all_results = Post.find_with_index('crane',{},{:ids_only => true})
113
+ first_result = Post.find_with_index('crane',{:limit => 1})
114
+
115
+ assert_equal 1, first_result.size
116
+ assert_equal all_results.first, first_result.first.id
117
+
118
+ second_result = Post.find_with_index('crane',{:limit => 1, :offset => 1})
119
+ assert_equal 1, second_result.size
120
+ assert_equal all_results[1], second_result.first.id
121
+ end
122
+
123
+ # When a atom already in a record is duplicated, it removes
124
+ # all records with that same atom from the index.
125
+ def test_update_record_bug
126
+ assert_equal 2, Post.find_with_index('crane',{},{:ids_only => true}).size
127
+ p = Post.find(6)
128
+ assert p.update_attributes(:body => p.body + ' crane')
129
+ assert_equal 2, Post.find_with_index('crane',{},{:ids_only => true}).size
130
+ assert_equal 2, Post.find_with_index('ship',{},{:ids_only => true}).size
131
+ end
132
+
133
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ include ActsAsIndexed
3
+
4
+ class ConfigurationTest < ActiveSupport::TestCase
5
+
6
+ def test_default_index_file_should_be_set
7
+ assert_equal [Rails.root, 'index'], config.index_file
8
+ end
9
+
10
+ def test_default_index_file_depth_should_be_set
11
+ assert_equal 3, config.index_file_depth
12
+ end
13
+
14
+ def test_default_min_word_size_should_be_set
15
+ assert_equal 3, config.min_word_size
16
+ end
17
+
18
+ def test_index_file_should_be_writeable
19
+ config.index_file = [Rails.root, 'my_index']
20
+ assert_equal [Rails.root, 'my_index'], config.index_file
21
+ end
22
+
23
+ def test_index_file_depth_should_be_writeable
24
+ config.index_file_depth = 5
25
+ assert_equal 5, config.index_file_depth
26
+ end
27
+
28
+ def test_index_file_depth_should_raise_on_lower_than_1_value
29
+ assert_nothing_raised(ArgumentError) { config.index_file_depth = 1 }
30
+
31
+ e = assert_raise(ArgumentError) { config.index_file_depth = 0 }
32
+ assert_equal 'index_file_depth cannot be less than one (1)', e.message
33
+
34
+ assert_raise(ArgumentError) { config.index_file_depth = -12 }
35
+ end
36
+
37
+ def test_min_word_size_should_be_writeable
38
+ config.min_word_size = 7
39
+ assert_equal 7, config.min_word_size
40
+ end
41
+
42
+ def test_min_word_size_should_raise_on_lower_than_1_value
43
+ assert_nothing_raised(ArgumentError) { config.min_word_size = 1 }
44
+
45
+ e = assert_raise(ArgumentError) { config.min_word_size = 0 }
46
+ assert_equal 'min_word_size cannot be less than one (1)', e.message
47
+
48
+ assert_raise(ArgumentError) { config.min_word_size = -12 }
49
+ end
50
+
51
+ private
52
+
53
+ def config
54
+ @config ||=ActsAsIndexed::Configuration.new
55
+ end
56
+
57
+ end
@@ -0,0 +1,10 @@
1
+ mysql:
2
+ :adapter: mysql
3
+ :host: localhost
4
+ :username: root
5
+ :password:
6
+ :database: rails_plugin_test
7
+
8
+ sqlite3:
9
+ :adapter: sqlite3
10
+ :database: ':memory:'
@@ -0,0 +1,5 @@
1
+ class Post < ActiveRecord::Base
2
+ acts_as_indexed :fields => [:title, :body]
3
+
4
+ validates_presence_of :title, :body
5
+ end
@@ -0,0 +1,31 @@
1
+ # Content generated using the random article feature on Wikipedia, http://en.wikipedia.org/wiki/Special:Random
2
+ # Wikipedia content may be redistributed under the GNU Free Documentation License; http://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License
3
+ wikipedia_article_1:
4
+ id: 1
5
+ title: Body Count (video game)
6
+ body: Body Count is a 1994 First-person shooter for the Sega Mega Drive. It is one of the few games that make use of the Menacer light gun and the Mega Mouse. \n In the U.S. the game was released on the Sega Channel.
7
+
8
+ wikipedia_article_2:
9
+ id: 2
10
+ title: Julien Ellis
11
+ body: Julien Ellis is a ice hockey goalie, born in Sorel, Quebec on January 27, 1986. He is currently 6'0" and weighs approximately 177 pounds. He wears number 34 and catches left. \n Julien played his entire junior hockey career in the QMJHL with the Shawinigan Cataractes. He was there from 2002 through the 2006 season, and played a total of 173 regular season games for them. During his time there, he recorded eight shutouts, as well as a career high .921 save percentage and 2.41 goals against average. \n Julien was chosen in round six of the 2004 NHL Entry Draft by the Vancouver Canucks, making him 189th overall pick and the 5th pick for Vancouver. \n His 2006-07 season was spent with the Victoria Salmon Kings of the ECHL, where he played 37 games and made 1,212 saves. Julien was called up the the Manitoba Moose of the AHL several times, where he played eight games.
12
+
13
+ wikipedia_article_3:
14
+ id: 3
15
+ title: Tuen Mun River
16
+ body: The Tuen Mun River is a river in Tuen Mun, New Territories, Hong Kong. It has many tributaries, with major ones coming from Lam Tei, Kau Keng Shan, Hung Shui Hang and Nai Wai. It flows south, splitting Tuen Mun into a west side and an east side. It eventually feeds into the Tuen Mun Typhoon Shelter, which is part of Castle Peak Bay.
17
+
18
+ wikipedia_article_4:
19
+ id: 4
20
+ title: So Happily Unsatisfied
21
+ body: So Happily Unsatisfied is an album that was recorded by the band Nine Days. It was intended to be the follow-up to their successful major-label debut, The Madding Crowd from 2000. The release date of the album was repeatedly delayed by Sony until the band was ultimately dropped. In the interim, the album had leaked onto the internet. The band has also put the whole album on their official website for the public to download.
22
+
23
+ wikipedia_article_5:
24
+ id: 5
25
+ title: SS Cornhusker State (T-ACS-6)
26
+ body: SS Cornhusker State (T-ACS-6) is a crane ship in ready reserve for the United States Navy. She is stationed at Cheatham Annex in Williamsburg, Virginia and is in ready reserve under the Military Sealift Command. The ship was named for the state of Nebraska, which is also known as the Cornhusker State. \n The ship was built by the Bath Iron Works. Her keel was laid on 27 November 1967, launched on 2 November 1968, and delivered 20 June 1969 as CV Stag Hound (MA 207). \n Stag Hound was acquired by the US Navy from the Maritime Administration in 1986 and was converted throughout 1987. She re-entered service as Cornhusker State on 12 March 1988, and has been in ready reserve since 1993.
27
+
28
+ article_similar_to_5:
29
+ id: 6
30
+ title: An article I made up by myself!
31
+ body: crane crane big ship foo
@@ -0,0 +1,6 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table :posts, :force => true do |t|
3
+ t.column :title, :string
4
+ t.column :body, :text
5
+ end
6
+ end
@@ -0,0 +1,98 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ include ActsAsIndexed
3
+
4
+ class SearchAtomTest < ActiveSupport::TestCase
5
+
6
+ def test_should_create_a_new_instance
7
+ assert SearchAtom.new
8
+ end
9
+
10
+ def test_include_record_should_return_false
11
+ assert ! SearchAtom.new.include_record?(123)
12
+ end
13
+
14
+ def test_include_record_should_return_true
15
+ assert build_search_atom.include_record?(123)
16
+ end
17
+
18
+ def test_add_record_should_add_record
19
+ search_atom = SearchAtom.new
20
+ search_atom.add_record(456)
21
+
22
+ assert search_atom.include_record?(456)
23
+ end
24
+
25
+ def test_add_record_should_leave_positions_untouched
26
+ search_atom = build_search_atom
27
+ original_records_count = search_atom.record_ids.size
28
+
29
+ search_atom.add_record(123)
30
+ assert_equal original_records_count, search_atom.record_ids.size
31
+ assert_equal [2,23,78], search_atom.positions(123)
32
+ end
33
+
34
+ def test_add_position_should_add_position
35
+ search_atom = build_search_atom
36
+ search_atom.expects(:add_record).with(123)
37
+
38
+ search_atom.add_position(123,98)
39
+ assert search_atom.positions(123).include?(98)
40
+ end
41
+
42
+ def test_record_ids_should_return_obvious
43
+ assert_equal [123], build_search_atom.record_ids
44
+ end
45
+
46
+ def test_positions_should_return_positions
47
+ assert_equal [2,23,78], build_search_atom.positions(123)
48
+ end
49
+
50
+ def test_positions_should_return_nil
51
+ assert_equal nil, build_search_atom.positions(456)
52
+ end
53
+
54
+ def test_remove_record
55
+ search_atom = build_search_atom
56
+ search_atom.remove_record(123)
57
+ assert ! search_atom.include_record?(123)
58
+ end
59
+
60
+ def test_preceded_by
61
+ former = build_search_atom({ 1 => [1], 2 => [1] })
62
+ latter = build_search_atom({ 1 => [2], 2 => [3] })
63
+ result = latter.preceded_by(former)
64
+ assert_equal [1], result.record_ids
65
+ assert_equal [2], result.positions(1)
66
+ end
67
+
68
+ def test_weightings
69
+ # 5 documents.
70
+ weightings = build_search_atom({ 1 => [1, 8], 2 => [1] }).weightings(5)
71
+ assert_in_delta(1.832, weightings[1], 2 ** -10)
72
+ assert_in_delta(0.916, weightings[2], 2 ** -10)
73
+
74
+ # Empty positions.
75
+ weightings = build_search_atom({ 1 => [1, 8], 2 => [] }).weightings(5)
76
+ assert_in_delta(1.832, weightings[1], 2 ** -10)
77
+ assert_in_delta(0.0, weightings[2], 2 ** -10)
78
+
79
+ # 10 documents.
80
+ weightings = build_search_atom({ 1 => [1, 8], 2 => [1] }).weightings(10)
81
+ assert_in_delta(3.219, weightings[1], 2 ** -10)
82
+ assert_in_delta(1.609, weightings[2], 2 ** -10)
83
+ end
84
+
85
+ private
86
+
87
+ def build_search_atom(records = { 123 => [2,23,78] })
88
+ search_atom = SearchAtom.new
89
+ records.each do |record_id, positions|
90
+ search_atom.add_record(record_id)
91
+ positions.each do |position|
92
+ search_atom.add_position(record_id, position)
93
+ end
94
+ end
95
+ search_atom
96
+ end
97
+
98
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ include ActsAsIndexed
3
+
4
+ class SearchIndexTest < ActiveSupport::TestCase
5
+
6
+ def teardown
7
+ destroy_index
8
+ end
9
+
10
+ def test_should_check_for_non_existing_index
11
+ SearchIndex.any_instance.expects(:exists?).returns(false)
12
+ File.expects(:open).never
13
+ assert build_search_index
14
+ end
15
+
16
+ def test_should_check_for_existing_index
17
+ SearchIndex.any_instance.expects(:exists?).returns(true)
18
+ SearchIndex.any_instance.expects(:load_record_size).returns(0)
19
+ assert build_search_index
20
+ end
21
+
22
+ def test_add_record
23
+ search_index = build_search_index
24
+ mock_record = mock(:id => 123)
25
+ mock_condensed_record = ['mock','condensed','record']
26
+
27
+ search_index.expects(:condense_record).with(mock_record).returns(mock_condensed_record)
28
+ search_index.expects(:load_atoms).with(mock_condensed_record)
29
+ search_index.expects(:add_occurences).with(mock_condensed_record,123)
30
+
31
+ search_index.add_record(mock_record)
32
+ end
33
+
34
+ def test_add_records
35
+ search_index = build_search_index
36
+ mock_records = ['record0', 'record1']
37
+
38
+ search_index.expects(:add_record).with('record0')
39
+ search_index.expects(:add_record).with('record1')
40
+
41
+ search_index.add_records(mock_records)
42
+ end
43
+
44
+ private
45
+
46
+ def build_search_index(root = index_loc, index_depth = 2, fields = [:title, :body], min_word_size = 3)
47
+ SearchIndex.new([root], index_depth, fields, min_word_size)
48
+ end
49
+
50
+ end