xapit 0.2.7 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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