xapit 0.2.7 → 0.3.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.
Files changed (105) hide show
  1. data/{CHANGELOG → CHANGELOG.rdoc} +7 -2
  2. data/Gemfile +19 -0
  3. data/LICENSE +4 -4
  4. data/README.rdoc +61 -108
  5. data/Rakefile +11 -10
  6. data/features/facets.feature +93 -82
  7. data/features/finding.feature +196 -138
  8. data/features/indexing.feature +35 -37
  9. data/features/remote_server.feature +10 -0
  10. data/features/step_definitions/xapit_steps.rb +53 -25
  11. data/features/suggestions.feature +20 -14
  12. data/features/support/env.rb +13 -6
  13. data/features/support/xapit_helpers.rb +8 -9
  14. data/lib/generators/xapit/install_generator.rb +14 -0
  15. data/lib/generators/xapit/templates/xapit.ru +6 -0
  16. data/lib/generators/xapit/templates/xapit.yml +11 -0
  17. data/lib/xapit.rb +106 -64
  18. data/lib/xapit/client/collection.rb +150 -0
  19. data/lib/xapit/client/facet.rb +11 -0
  20. data/lib/xapit/client/facet_option.rb +29 -0
  21. data/lib/xapit/client/index_builder.rb +67 -0
  22. data/lib/xapit/client/membership.rb +46 -0
  23. data/lib/xapit/client/model_adapters/abstract_model_adapter.rb +30 -0
  24. data/lib/xapit/client/model_adapters/active_record_adapter.rb +27 -0
  25. data/lib/xapit/client/model_adapters/default_model_adapter.rb +7 -0
  26. data/lib/xapit/client/railtie.rb +18 -0
  27. data/lib/xapit/client/remote_database.rb +21 -0
  28. data/lib/xapit/client/tasks.rb +18 -0
  29. data/lib/xapit/server/app.rb +27 -0
  30. data/lib/xapit/server/database.rb +47 -0
  31. data/lib/xapit/server/indexer.rb +138 -0
  32. data/lib/xapit/server/query.rb +240 -0
  33. data/spec/fixtures/blankdb/flintlock +0 -0
  34. data/spec/fixtures/blankdb/iamchert +1 -0
  35. data/spec/fixtures/blankdb/postlist.DB +0 -0
  36. data/spec/fixtures/blankdb/postlist.baseA +0 -0
  37. data/spec/fixtures/blankdb/record.DB +0 -0
  38. data/spec/fixtures/blankdb/record.baseA +0 -0
  39. data/spec/fixtures/blankdb/termlist.DB +0 -0
  40. data/spec/fixtures/blankdb/termlist.baseA +0 -0
  41. data/spec/fixtures/xapit.ru +13 -0
  42. data/spec/fixtures/xapit.yml +4 -0
  43. data/spec/spec_helper.rb +8 -9
  44. data/spec/support/spec_macros.rb +6 -0
  45. data/spec/{xapit_member.rb → support/xapit_member.rb} +14 -16
  46. data/spec/xapit/client/collection_spec.rb +63 -0
  47. data/spec/xapit/client/facet_option_spec.rb +26 -0
  48. data/spec/xapit/client/facet_spec.rb +13 -0
  49. data/spec/xapit/client/index_builder_spec.rb +66 -0
  50. data/spec/xapit/client/membership_spec.rb +43 -0
  51. data/spec/xapit/client/model_adapters/active_record_adapter_spec.rb +62 -0
  52. data/spec/xapit/client/model_adapters/default_model_adapter_spec.rb +7 -0
  53. data/spec/xapit/client/remote_database_spec.rb +19 -0
  54. data/spec/xapit/server/app_spec.rb +22 -0
  55. data/spec/xapit/server/database_spec.rb +37 -0
  56. data/spec/xapit/server/indexer_spec.rb +82 -0
  57. data/spec/xapit/server/query_spec.rb +43 -0
  58. data/spec/xapit/xapit_spec.rb +28 -0
  59. metadata +124 -93
  60. data/Manifest +0 -60
  61. data/features/sorting.feature +0 -29
  62. data/init.rb +0 -1
  63. data/install.rb +0 -8
  64. data/lib/xapit/adapters/abstract_adapter.rb +0 -47
  65. data/lib/xapit/adapters/active_record_adapter.rb +0 -20
  66. data/lib/xapit/adapters/data_mapper_adapter.rb +0 -10
  67. data/lib/xapit/collection.rb +0 -187
  68. data/lib/xapit/config.rb +0 -84
  69. data/lib/xapit/facet.rb +0 -67
  70. data/lib/xapit/facet_blueprint.rb +0 -59
  71. data/lib/xapit/facet_option.rb +0 -56
  72. data/lib/xapit/index_blueprint.rb +0 -147
  73. data/lib/xapit/indexers/abstract_indexer.rb +0 -116
  74. data/lib/xapit/indexers/classic_indexer.rb +0 -29
  75. data/lib/xapit/indexers/simple_indexer.rb +0 -38
  76. data/lib/xapit/membership.rb +0 -137
  77. data/lib/xapit/query.rb +0 -89
  78. data/lib/xapit/query_parsers/abstract_query_parser.rb +0 -174
  79. data/lib/xapit/query_parsers/classic_query_parser.rb +0 -29
  80. data/lib/xapit/query_parsers/simple_query_parser.rb +0 -75
  81. data/lib/xapit/rake_tasks.rb +0 -13
  82. data/rails_generators/xapit/USAGE +0 -13
  83. data/rails_generators/xapit/templates/setup_xapit.rb +0 -1
  84. data/rails_generators/xapit/templates/xapit.rake +0 -4
  85. data/rails_generators/xapit/xapit_generator.rb +0 -20
  86. data/spec/xapit/adapters/active_record_adapter_spec.rb +0 -31
  87. data/spec/xapit/adapters/data_mapper_adapter_spec.rb +0 -10
  88. data/spec/xapit/collection_spec.rb +0 -176
  89. data/spec/xapit/config_spec.rb +0 -62
  90. data/spec/xapit/facet_blueprint_spec.rb +0 -29
  91. data/spec/xapit/facet_option_spec.rb +0 -80
  92. data/spec/xapit/facet_spec.rb +0 -73
  93. data/spec/xapit/index_blueprint_spec.rb +0 -112
  94. data/spec/xapit/indexers/abstract_indexer_spec.rb +0 -111
  95. data/spec/xapit/indexers/classic_indexer_spec.rb +0 -35
  96. data/spec/xapit/indexers/simple_indexer_spec.rb +0 -69
  97. data/spec/xapit/membership_spec.rb +0 -55
  98. data/spec/xapit/query_parsers/abstract_query_parser_spec.rb +0 -60
  99. data/spec/xapit/query_parsers/classic_query_parser_spec.rb +0 -20
  100. data/spec/xapit/query_parsers/simple_query_parser_spec.rb +0 -86
  101. data/spec/xapit/query_spec.rb +0 -60
  102. data/tasks/spec.rb +0 -9
  103. data/tasks/xapit.rake +0 -1
  104. data/uninstall.rb +0 -5
  105. data/xapit.gemspec +0 -30
data/Manifest DELETED
@@ -1,60 +0,0 @@
1
- CHANGELOG
2
- features/facets.feature
3
- features/finding.feature
4
- features/indexing.feature
5
- features/sorting.feature
6
- features/step_definitions/common_steps.rb
7
- features/step_definitions/xapit_steps.rb
8
- features/suggestions.feature
9
- features/support/env.rb
10
- features/support/xapit_helpers.rb
11
- init.rb
12
- install.rb
13
- lib/xapit/adapters/abstract_adapter.rb
14
- lib/xapit/adapters/active_record_adapter.rb
15
- lib/xapit/adapters/data_mapper_adapter.rb
16
- lib/xapit/collection.rb
17
- lib/xapit/config.rb
18
- lib/xapit/facet.rb
19
- lib/xapit/facet_blueprint.rb
20
- lib/xapit/facet_option.rb
21
- lib/xapit/index_blueprint.rb
22
- lib/xapit/indexers/abstract_indexer.rb
23
- lib/xapit/indexers/classic_indexer.rb
24
- lib/xapit/indexers/simple_indexer.rb
25
- lib/xapit/membership.rb
26
- lib/xapit/query.rb
27
- lib/xapit/query_parsers/abstract_query_parser.rb
28
- lib/xapit/query_parsers/classic_query_parser.rb
29
- lib/xapit/query_parsers/simple_query_parser.rb
30
- lib/xapit/rake_tasks.rb
31
- lib/xapit.rb
32
- LICENSE
33
- Manifest
34
- rails_generators/xapit/templates/setup_xapit.rb
35
- rails_generators/xapit/templates/xapit.rake
36
- rails_generators/xapit/USAGE
37
- rails_generators/xapit/xapit_generator.rb
38
- Rakefile
39
- README.rdoc
40
- spec/spec_helper.rb
41
- spec/xapit/adapters/active_record_adapter_spec.rb
42
- spec/xapit/adapters/data_mapper_adapter_spec.rb
43
- spec/xapit/collection_spec.rb
44
- spec/xapit/config_spec.rb
45
- spec/xapit/facet_blueprint_spec.rb
46
- spec/xapit/facet_option_spec.rb
47
- spec/xapit/facet_spec.rb
48
- spec/xapit/index_blueprint_spec.rb
49
- spec/xapit/indexers/abstract_indexer_spec.rb
50
- spec/xapit/indexers/classic_indexer_spec.rb
51
- spec/xapit/indexers/simple_indexer_spec.rb
52
- spec/xapit/membership_spec.rb
53
- spec/xapit/query_parsers/abstract_query_parser_spec.rb
54
- spec/xapit/query_parsers/classic_query_parser_spec.rb
55
- spec/xapit/query_parsers/simple_query_parser_spec.rb
56
- spec/xapit/query_spec.rb
57
- spec/xapit_member.rb
58
- tasks/spec.rb
59
- tasks/xapit.rake
60
- uninstall.rb
@@ -1,29 +0,0 @@
1
- Background:
2
- Given an empty database at "tmp/xapiandatabase"
3
-
4
- Scenario: Query for All Records Sorted by Name
5
- Given indexed records named "Zebra, Apple, Banana"
6
- When I query "" sorted by name
7
- Then I should find records named "Apple, Banana, Zebra"
8
-
9
- Scenario: Query for All Records Sorted by Age then Name
10
- Given the following indexed records
11
- | name | age |
12
- | Banana | 23 |
13
- | Zebra | 17 |
14
- | Apple | 17 |
15
- When I query "" sorted by age, name
16
- Then I should find records named "Apple, Zebra, Banana"
17
-
18
- Scenario: Query for All Records Sorted by Name Descending
19
- Given indexed records named "Zebra, Apple, Banana"
20
- When I query "" sorted by name descending
21
- Then I should find records named "Zebra, Banana, Apple"
22
-
23
- Scenario: Query for Records Sorted Numerically
24
- Given the following indexed records
25
- | name | age |
26
- | Banana | 9 |
27
- | Zebra | 10 |
28
- When I query "" sorted by age, name
29
- Then I should find records named "Banana, Zebra"
data/init.rb DELETED
@@ -1 +0,0 @@
1
- require 'xapit'
data/install.rb DELETED
@@ -1,8 +0,0 @@
1
- require 'ftools'
2
-
3
- source = File.join(File.dirname(__FILE__), '/rails_generators/xapit/templates/setup_xapit.rb')
4
- destination = "#{Rails.root}/config/initializers/setup_xapit.rb"
5
- unless File.exist? destination
6
- puts "Adding config/initializers/setup_xapit.rb"
7
- File.copy(source, destination)
8
- end
@@ -1,47 +0,0 @@
1
- module Xapit
2
- # Adapters are used to support multiple ORMs (ActiveRecord, Datamapper, Sequel, etc.).
3
- # It abstracts out all find calls so they can be handled differently for each ORM.
4
- # To create your own adapter, subclass AbstractAdapter and override the placeholder methods.
5
- # See ActiveRecordAdapter for an example.
6
- class AbstractAdapter
7
- def self.inherited(subclass)
8
- @subclasses ||= []
9
- @subclasses << subclass
10
- end
11
-
12
- # Returns all adapter classes.
13
- def self.subclasses
14
- @subclasses
15
- end
16
-
17
- # Sets the @target instance, this is the class the adapter needs to forward
18
- # its messages to.
19
- def initialize(target)
20
- @target = target
21
- end
22
-
23
- # Used to determine if the given adapter should be used for the passed in class.
24
- # Usually one will see if it inherits from another class (ActiveRecord::Base)
25
- def self.for_class?(member_class)
26
- raise "To be implemented in subclass"
27
- end
28
-
29
- # Fetch a single record by the given id.
30
- # The args are the same as those passed from the XapitMember#xapit call.
31
- def find_single(id, *args)
32
- raise "To be implemented in subclass"
33
- end
34
-
35
- # Fetch multiple records from the passed in array of ids.
36
- def find_multiple(ids)
37
- raise "To be implemented in subclass"
38
- end
39
-
40
- # Iiterate through all records using the given parameters.
41
- # It should yield to the block and pass in each record individually.
42
- # The args are the same as those passed from the XapitMember#xapit call.
43
- def find_each(*args, &block)
44
- raise "To be implemented in subclass"
45
- end
46
- end
47
- end
@@ -1,20 +0,0 @@
1
- module Xapit
2
- # This adapter is used for all ActiveRecord models. See AbstractAdapter for details.
3
- class ActiveRecordAdapter < AbstractAdapter
4
- def self.for_class?(member_class)
5
- member_class.ancestors.map(&:to_s).include? "ActiveRecord::Base"
6
- end
7
-
8
- def find_single(id, *args)
9
- @target.find_by_id(id, *args)
10
- end
11
-
12
- def find_multiple(ids)
13
- @target.find(ids)
14
- end
15
-
16
- def find_each(*args, &block)
17
- @target.find_each(*args, &block)
18
- end
19
- end
20
- end
@@ -1,10 +0,0 @@
1
- module Xapit
2
- # This adapter is used for all DataMapper models. See AbstractAdapter for details.
3
- class DataMapperAdapter < AbstractAdapter
4
- def self.for_class?(member_class)
5
- member_class.ancestors.map(&:to_s).include? "DataMapper::Resource"
6
- end
7
-
8
- # TODO override the rest of the methods here...
9
- end
10
- end
@@ -1,187 +0,0 @@
1
- module Xapit
2
- # This is the object which is returned when performing a search. It behaves like an array, so you do not need
3
- # to worry about fetching the results separately. Just loop through this collection.
4
- #
5
- # The results are lazy loading, meaning it does not perform the query on the database until it has to.
6
- # This allows you to string queries onto one another.
7
- #
8
- # Article.search("kite").search("sky") # only performs one query
9
- #
10
- # This class is compatible with will_paginate; you can pass it to the will_paginate helper in the view.
11
- class Collection
12
- NON_DELEGATE_METHODS = %w(nil? send object_id class extend size paginate first last empty? respond_to?).to_set
13
- [].methods.each do |m|
14
- delegate m, :to => :results unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
15
- end
16
- delegate :query, :base_query, :base_query=, :extra_queries, :extra_queries=, :to => :@query_parser
17
-
18
- def self.search_similar(member, *args)
19
- collection = new(member.class, *args)
20
- indexer = SimpleIndexer.new(member.class.xapit_index_blueprint)
21
- terms = indexer.text_terms(member) + indexer.field_terms(member)
22
- query = collection.base_query.and_query(terms, :or).not_query("Q#{member.class}-#{member.id}")
23
- collection.base_query = query
24
- collection
25
- end
26
-
27
- def initialize(*args)
28
- @query_parser = Config.query_parser.new(*args)
29
- end
30
-
31
- # Returns an array of results. You should not need to call this directly because most methods are
32
- # automatically delegated to this array.
33
- def results
34
- @results ||= fetch_results
35
- end
36
-
37
- # The number of total records found despite any pagination settings.
38
- def size
39
- @query_parser.query.count
40
- end
41
- alias_method :total_entries, :size # alias to total_entries to support will_paginate
42
-
43
- # Returns true if no results are found.
44
- def empty?
45
- @results ? @results.empty? : size.zero?
46
- end
47
-
48
- # The first record in the result set.
49
- def first
50
- fetch_results(:offset => 0, :limit => 1).first
51
- end
52
-
53
- # The last record in the result set.
54
- def last
55
- fetch_results(:offset => size-1, :limit => 1).last
56
- end
57
-
58
- # Perform another search on this one, inheriting all options already passed.
59
- # See Xapit::Membership for search options.
60
- #
61
- # Article.search("kite").search("sky") # only performs one query
62
- #
63
- def search(*args)
64
- options = args.extract_options!
65
- collection = Collection.new(@query_parser.member_class, args[0].to_s, @query_parser.options.merge(options))
66
- collection.base_query = @query_parser.query
67
- collection
68
- end
69
-
70
- # Chain another search returning all records matched by either this search or the previous search
71
- # Inherits all options passed in earlier search (such as :page and :order)
72
- # See Xapit::Membership for search options.
73
- #
74
- # Article.search("kite").or_search(:conditions => { :priority => 1 })
75
- #
76
- def or_search(*args)
77
- options = args.extract_options!
78
- collection = Collection.new(@query_parser.member_class, args[0].to_s, @query_parser.options.merge(options))
79
- collection.base_query = @query_parser.base_query
80
- collection.extra_queries = @query_parser.extra_queries
81
- collection.extra_queries << @query_parser.primary_query
82
- collection
83
- end
84
-
85
- # The page number we are currently on.
86
- def current_page
87
- @query_parser.current_page
88
- end
89
-
90
- # How many records to display on each page, defaults to 20. Sets with :per_page option when performing search.
91
- def per_page
92
- @query_parser.per_page
93
- end
94
-
95
- # The offset for the current page
96
- def offset
97
- @query_parser.offset
98
- end
99
-
100
- # Total number of pages with found results.
101
- def total_pages
102
- (size / per_page.to_f).ceil
103
- end
104
-
105
- # The previous page number. Returns nil if on first page.
106
- def previous_page
107
- current_page > 1 ? (current_page - 1) : nil
108
- end
109
-
110
- # The next page number. Returns nil if on last page.
111
- def next_page
112
- current_page < total_pages ? (current_page + 1): nil
113
- end
114
-
115
- # Xapit::Facet objects matching this search query. See class for details.
116
- def facets
117
- all_facets.select do |facet|
118
- facet.options.size > 0
119
- end
120
- end
121
-
122
- # Xapit::FacetOption objects which are currently applied to search (through :facets option). Use this to
123
- # display the facets which are currently applied.
124
- #
125
- # <% for option in @articles.applied_facet_options %>
126
- # <%=h option.name %>
127
- # <%= link_to "remove", :overwrite_params => { :facets => option } %>
128
- # <% end %>
129
- #
130
- # If you set :breadcrumb_facets option to true in Config#setup the link will drop leftover facets
131
- # instead of removing the current one. This makes it easy to add a breadcrumb style interface.
132
- #
133
- # Xapit.setup(:breadcrumb_facets => true)
134
- # <% for option in @articles.applied_facet_options %>
135
- # <%= link_to h(option.name), :overwrite_params => { :facets => option } %> &gt;
136
- # <% end %>
137
- #
138
- def applied_facet_options
139
- @query_parser.facet_identifiers.map do |identifier|
140
- option = FacetOption.find(identifier)
141
- option.existing_facet_identifiers = @query_parser.facet_identifiers
142
- option
143
- end
144
- end
145
-
146
- # Includes a suggested variation of a term which will get many more results. Returns nil if no suggestion.
147
- #
148
- # <% if @articles.spelling_suggestion %>
149
- # Did you mean <%= link_to h(@articles.spelling_suggestion), :overwrite_params => { :keywords => @articles.spelling_suggestion } %>?
150
- # <% end %>
151
- #
152
- def spelling_suggestion
153
- @query_parser.spelling_suggestion
154
- end
155
-
156
- # All Xapit::Facet objects, even if they do not include options.
157
- # Usually you'll want to call Collection#facets
158
- def all_facets
159
- @query_parser.member_class.xapit_index_blueprint.facets.map do |facet_blueprint|
160
- Facet.new(facet_blueprint, @query_parser.query, @query_parser.facet_identifiers)
161
- end
162
- end
163
-
164
- private
165
-
166
- # TODO this could use some refactoring
167
- # See issue #11 for why this is so complex.
168
- def fetch_results(options = {})
169
- matches = @query_parser.matchset(options).matches
170
- records_by_class = {}
171
- matches.each do |match|
172
- class_name, id = match.document.data.split('-')
173
- records_by_class[class_name] ||= []
174
- records_by_class[class_name] << id
175
- end
176
- records_by_class.each do |class_name, ids|
177
- records_by_class[class_name] = class_name.constantize.xapit_adapter.find_multiple(ids)
178
- end
179
- matches.map do |match|
180
- class_name, id = match.document.data.split('-')
181
- member = records_by_class[class_name].detect { |m| m.id == id.to_i }
182
- member.xapit_relevance = match.percent
183
- member
184
- end
185
- end
186
- end
187
- end
@@ -1,84 +0,0 @@
1
- module Xapit
2
- # Singleton class for storing Xapit configuration settings. Currently this only includes the database path.
3
- class Config
4
- class << self
5
- attr_reader :options
6
-
7
- # See Xapit#setup
8
- def setup(options = {})
9
- if @options && options[:database_path] != @options[:database_path]
10
- @database = nil
11
- @writable_database = nil
12
- end
13
- @options = options.reverse_merge(default_options)
14
- end
15
-
16
- def default_options
17
- {
18
- :indexer => SimpleIndexer,
19
- :query_parser => ClassicQueryParser,
20
- :spelling => true,
21
- :stemming => "english"
22
- }
23
- end
24
-
25
- # See if setup options are already set.
26
- def setup?
27
- @options
28
- end
29
-
30
- # The configured path to the database.
31
- def path
32
- @options[:database_path]
33
- end
34
-
35
- def query_parser
36
- @options[:query_parser]
37
- end
38
-
39
- def indexer
40
- @options[:indexer]
41
- end
42
-
43
- def spelling?
44
- @options[:spelling]
45
- end
46
-
47
- def stemming
48
- @options[:stemming]
49
- end
50
-
51
- def breadcrumb_facets?
52
- @options[:breadcrumb_facets]
53
- end
54
-
55
- # Fetch Xapian::Database object at configured path. Database is stored in memory.
56
- def database
57
- @writable_database || (@database ||= Xapian::Database.new(path))
58
- end
59
-
60
- # Fetch Xapian::WritableDatabase object at configured path. Database is stored in memory.
61
- # Creates the database directory if needed.
62
- def writable_database
63
- FileUtils.mkdir_p(File.dirname(path)) unless File.exist?(File.dirname(path))
64
- @writable_database ||= Xapian::WritableDatabase.new(path, Xapian::DB_CREATE_OR_OPEN)
65
- end
66
-
67
- # Removes the configured database file and clears the stored one in memory.
68
- def remove_database
69
- FileUtils.rm_rf(path) if File.exist? File.join(path, "iamflint")
70
- @database = nil
71
- @writable_database = nil
72
- end
73
-
74
- # Clear the current database from memory. Unfortunately this is a hack because
75
- # Xapian doesn't provide a "close" method on the database. We just have to hope
76
- # no other references are lying around.
77
- def close_database
78
- @database = nil
79
- @writable_database = nil
80
- GC.start
81
- end
82
- end
83
- end
84
- end