pduey-sunspot 1.2.1.1

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 (166) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +5 -0
  3. data/History.txt +225 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +15 -0
  6. data/TODO +13 -0
  7. data/VERSION.yml +4 -0
  8. data/bin/sunspot-installer +19 -0
  9. data/installer/config/schema.yml +95 -0
  10. data/lib/light_config.rb +40 -0
  11. data/lib/sunspot.rb +568 -0
  12. data/lib/sunspot/adapters.rb +265 -0
  13. data/lib/sunspot/composite_setup.rb +202 -0
  14. data/lib/sunspot/configuration.rb +46 -0
  15. data/lib/sunspot/data_extractor.rb +50 -0
  16. data/lib/sunspot/dsl.rb +5 -0
  17. data/lib/sunspot/dsl/adjustable.rb +47 -0
  18. data/lib/sunspot/dsl/field_query.rb +279 -0
  19. data/lib/sunspot/dsl/fields.rb +103 -0
  20. data/lib/sunspot/dsl/fulltext.rb +243 -0
  21. data/lib/sunspot/dsl/function.rb +14 -0
  22. data/lib/sunspot/dsl/functional.rb +44 -0
  23. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  24. data/lib/sunspot/dsl/paginatable.rb +28 -0
  25. data/lib/sunspot/dsl/query_facet.rb +36 -0
  26. data/lib/sunspot/dsl/restriction.rb +25 -0
  27. data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
  28. data/lib/sunspot/dsl/scope.rb +217 -0
  29. data/lib/sunspot/dsl/search.rb +30 -0
  30. data/lib/sunspot/dsl/standard_query.rb +121 -0
  31. data/lib/sunspot/field.rb +193 -0
  32. data/lib/sunspot/field_factory.rb +129 -0
  33. data/lib/sunspot/indexer.rb +131 -0
  34. data/lib/sunspot/installer.rb +31 -0
  35. data/lib/sunspot/installer/library_installer.rb +45 -0
  36. data/lib/sunspot/installer/schema_builder.rb +219 -0
  37. data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
  38. data/lib/sunspot/installer/task_helper.rb +18 -0
  39. data/lib/sunspot/java.rb +8 -0
  40. data/lib/sunspot/query.rb +11 -0
  41. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  42. data/lib/sunspot/query/boost_query.rb +24 -0
  43. data/lib/sunspot/query/common_query.rb +85 -0
  44. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  45. data/lib/sunspot/query/connective.rb +206 -0
  46. data/lib/sunspot/query/date_field_facet.rb +14 -0
  47. data/lib/sunspot/query/dismax.rb +128 -0
  48. data/lib/sunspot/query/field_facet.rb +41 -0
  49. data/lib/sunspot/query/filter.rb +38 -0
  50. data/lib/sunspot/query/function_query.rb +52 -0
  51. data/lib/sunspot/query/geo.rb +53 -0
  52. data/lib/sunspot/query/highlighting.rb +55 -0
  53. data/lib/sunspot/query/more_like_this.rb +61 -0
  54. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  55. data/lib/sunspot/query/pagination.rb +38 -0
  56. data/lib/sunspot/query/query_facet.rb +16 -0
  57. data/lib/sunspot/query/restriction.rb +262 -0
  58. data/lib/sunspot/query/scope.rb +9 -0
  59. data/lib/sunspot/query/sort.rb +95 -0
  60. data/lib/sunspot/query/sort_composite.rb +33 -0
  61. data/lib/sunspot/query/standard_query.rb +16 -0
  62. data/lib/sunspot/query/text_field_boost.rb +17 -0
  63. data/lib/sunspot/schema.rb +151 -0
  64. data/lib/sunspot/search.rb +9 -0
  65. data/lib/sunspot/search/abstract_search.rb +335 -0
  66. data/lib/sunspot/search/date_facet.rb +35 -0
  67. data/lib/sunspot/search/facet_row.rb +27 -0
  68. data/lib/sunspot/search/field_facet.rb +88 -0
  69. data/lib/sunspot/search/highlight.rb +38 -0
  70. data/lib/sunspot/search/hit.rb +150 -0
  71. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  72. data/lib/sunspot/search/paginated_collection.rb +55 -0
  73. data/lib/sunspot/search/query_facet.rb +67 -0
  74. data/lib/sunspot/search/standard_search.rb +21 -0
  75. data/lib/sunspot/session.rb +260 -0
  76. data/lib/sunspot/session_proxy.rb +87 -0
  77. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  78. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  79. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  80. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  81. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
  82. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  83. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  84. data/lib/sunspot/setup.rb +350 -0
  85. data/lib/sunspot/text_field_setup.rb +29 -0
  86. data/lib/sunspot/type.rb +372 -0
  87. data/lib/sunspot/util.rb +243 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/pduey-sunspot.gemspec +38 -0
  90. data/script/console +10 -0
  91. data/spec/api/adapters_spec.rb +33 -0
  92. data/spec/api/binding_spec.rb +50 -0
  93. data/spec/api/indexer/attributes_spec.rb +149 -0
  94. data/spec/api/indexer/batch_spec.rb +46 -0
  95. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  96. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  97. data/spec/api/indexer/fulltext_spec.rb +43 -0
  98. data/spec/api/indexer/removal_spec.rb +53 -0
  99. data/spec/api/indexer/spec_helper.rb +1 -0
  100. data/spec/api/indexer_spec.rb +14 -0
  101. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  102. data/spec/api/query/connectives_examples.rb +189 -0
  103. data/spec/api/query/dsl_spec.rb +18 -0
  104. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  105. data/spec/api/query/faceting_examples.rb +397 -0
  106. data/spec/api/query/fulltext_examples.rb +313 -0
  107. data/spec/api/query/function_spec.rb +70 -0
  108. data/spec/api/query/geo_examples.rb +68 -0
  109. data/spec/api/query/highlighting_examples.rb +223 -0
  110. data/spec/api/query/more_like_this_spec.rb +140 -0
  111. data/spec/api/query/ordering_pagination_examples.rb +95 -0
  112. data/spec/api/query/scope_examples.rb +275 -0
  113. data/spec/api/query/spec_helper.rb +1 -0
  114. data/spec/api/query/standard_spec.rb +28 -0
  115. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  116. data/spec/api/query/types_spec.rb +20 -0
  117. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  118. data/spec/api/search/faceting_spec.rb +360 -0
  119. data/spec/api/search/highlighting_spec.rb +69 -0
  120. data/spec/api/search/hits_spec.rb +131 -0
  121. data/spec/api/search/paginated_collection_spec.rb +26 -0
  122. data/spec/api/search/results_spec.rb +66 -0
  123. data/spec/api/search/search_spec.rb +23 -0
  124. data/spec/api/search/spec_helper.rb +1 -0
  125. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  126. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  127. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  128. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  129. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  130. data/spec/api/session_proxy/spec_helper.rb +9 -0
  131. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
  132. data/spec/api/session_spec.rb +220 -0
  133. data/spec/api/spec_helper.rb +3 -0
  134. data/spec/api/sunspot_spec.rb +18 -0
  135. data/spec/ext.rb +11 -0
  136. data/spec/helpers/indexer_helper.rb +29 -0
  137. data/spec/helpers/query_helper.rb +38 -0
  138. data/spec/helpers/search_helper.rb +80 -0
  139. data/spec/integration/dynamic_fields_spec.rb +57 -0
  140. data/spec/integration/faceting_spec.rb +238 -0
  141. data/spec/integration/highlighting_spec.rb +24 -0
  142. data/spec/integration/indexing_spec.rb +33 -0
  143. data/spec/integration/keyword_search_spec.rb +317 -0
  144. data/spec/integration/local_search_spec.rb +64 -0
  145. data/spec/integration/more_like_this_spec.rb +43 -0
  146. data/spec/integration/scoped_search_spec.rb +354 -0
  147. data/spec/integration/spec_helper.rb +7 -0
  148. data/spec/integration/stored_fields_spec.rb +12 -0
  149. data/spec/integration/test_pagination.rb +32 -0
  150. data/spec/mocks/adapters.rb +32 -0
  151. data/spec/mocks/blog.rb +3 -0
  152. data/spec/mocks/comment.rb +21 -0
  153. data/spec/mocks/connection.rb +126 -0
  154. data/spec/mocks/mock_adapter.rb +30 -0
  155. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  156. data/spec/mocks/mock_record.rb +52 -0
  157. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  158. data/spec/mocks/photo.rb +11 -0
  159. data/spec/mocks/post.rb +85 -0
  160. data/spec/mocks/super_class.rb +2 -0
  161. data/spec/mocks/user.rb +13 -0
  162. data/spec/spec_helper.rb +28 -0
  163. data/tasks/rdoc.rake +27 -0
  164. data/tasks/schema.rake +19 -0
  165. data/tasks/todo.rake +4 -0
  166. metadata +369 -0
@@ -0,0 +1,89 @@
1
+ module Sunspot
2
+ module SessionProxy
3
+ #
4
+ # A concrete implementation of ShardingSessionProxy that determines the
5
+ # shard for a given object based on the hash of its class and ID.
6
+ #
7
+ # <strong>If you change the number of shard sessions that this proxy
8
+ # encapsulates, all objects will point to a different shard.</strong> If you
9
+ # plan on adding more shards over time, consider your own
10
+ # ShardingSessionProxy implementation that does not determine the session
11
+ # using modular arithmetic (e.g., IDs 1-10000 go to shard 1, 10001-20000 go
12
+ # to shard 2, etc.)
13
+ #
14
+ # This implementation will, on average, yield an even distribution of
15
+ # objects across shards.
16
+ #
17
+ # Unlike the abstract ShardingSessionProxy, this proxy supports the
18
+ # #remove_by_id method.
19
+ #
20
+ class IdShardingSessionProxy < ShardingSessionProxy
21
+ #
22
+ # The shard sessions encapsulated by this class.
23
+ #
24
+ attr_reader :sessions
25
+ alias_method :all_sessions, :sessions #:nodoc:
26
+
27
+ #
28
+ # Initialize with a search session (see ShardingSessionProxy.new) and a
29
+ # collection of one or more shard sessions. See note about changing the
30
+ # number of shard sessions in the documentation for this class.
31
+ #
32
+ def initialize(search_session, shard_sessions)
33
+ super(search_session)
34
+ @sessions = shard_sessions
35
+ end
36
+
37
+ #
38
+ # Return a session based on the hash of the class and ID, modulo the
39
+ # number of shard sessions.
40
+ #
41
+ def session_for(object) #:nodoc:
42
+ session_for_index_id(Adapters::InstanceAdapter.adapt(object).index_id)
43
+ end
44
+
45
+ #
46
+ # See Sunspot.remove_by_id
47
+ #
48
+ def remove_by_id(clazz, id)
49
+ session_for_index_id(
50
+ Adapters::InstanceAdapter.index_id_for(clazz, id)
51
+ ).remove_by_id(clazz, id)
52
+ end
53
+
54
+ #
55
+ # See Sunspot.remove_by_id!
56
+ #
57
+ def remove_by_id!(clazz, id)
58
+ session_for_index_id(
59
+ Adapters::InstanceAdapter.index_id_for(clazz, id)
60
+ ).remove_by_id!(clazz, id)
61
+ end
62
+
63
+ private
64
+
65
+ def session_for_index_id(index_id)
66
+ @sessions[id_hash(index_id) % @sessions.length]
67
+ end
68
+
69
+ #
70
+ # This method is implemented explicitly instead of using String#hash to
71
+ # give predictable behavior across different Ruby interpreters.
72
+ #
73
+ if "".respond_to?(:bytes) # Ruby 1.9
74
+ def id_hash(id)
75
+ id.bytes.inject { |hash, byte| hash * 31 + byte }
76
+ end
77
+ else
78
+ def id_hash(id)
79
+ hash, i, len = 0, 0, id.length
80
+ while i < len
81
+ hash = hash * 31 + id[i]
82
+ i += 1
83
+ end
84
+ hash
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
2
+
3
+ module Sunspot
4
+ module SessionProxy
5
+ #
6
+ # This session proxy implementation allows Sunspot to be used with a
7
+ # master/slave Solr deployment. All write methods are delegated to a master
8
+ # session, and read methods are delegated to a slave session.
9
+ #
10
+ class MasterSlaveSessionProxy < AbstractSessionProxy
11
+ #
12
+ # The session that connects to the master Solr instance.
13
+ #
14
+ attr_reader :master_session
15
+ #
16
+ # The session that connects to the slave Solr instance.
17
+ #
18
+ attr_reader :slave_session
19
+
20
+ delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty,
21
+ :config, :delete_dirty?, :dirty?, :index, :index!, :optimize, :remove,
22
+ :remove!, :remove_all, :remove_all!, :remove_by_id,
23
+ :remove_by_id!, :to => :master_session
24
+ delegate :new_search, :search, :new_more_like_this, :more_like_this, :to => :slave_session
25
+
26
+ def initialize(master_session, slave_session)
27
+ @master_session, @slave_session = master_session, slave_session
28
+ end
29
+
30
+ #
31
+ # By default, return the configuration for the master session. If the
32
+ # +delegate+ param is +:slave+, then return config for the slave session.
33
+ #
34
+ def config(delegate = :master)
35
+ case delegate
36
+ when :master then @master_session.config
37
+ when :slave then @slave_session.config
38
+ else raise(ArgumentError, "Expected :master or :slave")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,222 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
2
+
3
+ module Sunspot
4
+ module SessionProxy
5
+ #
6
+ # This is a generic abstract implementation of a session proxy that allows
7
+ # Sunspot to be used with a distributed (sharded) Solr deployment. Concrete
8
+ # subclasses should implement the #session_for method, which takes a
9
+ # searchable object and returns a Session that points to the appropriate
10
+ # Solr shard for that object. Subclasses should also implement the
11
+ # #all_sessions object, which returns the collection of all sharded Session
12
+ # objects.
13
+ #
14
+ # The class is initialized with a session that points to the Solr instance
15
+ # used to perform searches. Searches will have the +:shards+ param injected,
16
+ # containing references to the Solr instances returned by #all_sessions.
17
+ #
18
+ # For more on distributed search, see:
19
+ # http://wiki.apache.org/solr/DistributedSearch
20
+ #
21
+ # The following methods are not supported (although subclasses may in some
22
+ # cases be able to support them):
23
+ #
24
+ # * batch
25
+ # * config
26
+ # * remove_by_id
27
+ # * remove_by_id!
28
+ # * remove_all with an argument
29
+ # * remove_all! with an argument
30
+ #
31
+ class ShardingSessionProxy < AbstractSessionProxy
32
+ not_supported :batch, :config, :remove_by_id, :remove_by_id!
33
+
34
+ #
35
+ # +search_session+ is the session that should be used for searching.
36
+ #
37
+ def initialize(search_session = Sunspot.session.new)
38
+ @search_session = search_session
39
+ end
40
+
41
+ #
42
+ # Return the appropriate shard session for the object.
43
+ #
44
+ # <strong>Concrete subclasses must implement this method.</strong>
45
+ #
46
+ def session_for(object)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ #
51
+ # Return all shard sessions.
52
+ #
53
+ # <strong>Concrete subclasses must implement this method.</strong>
54
+ #
55
+ def all_sessions
56
+ raise NotImplementedError
57
+ end
58
+
59
+ #
60
+ # See Sunspot.index
61
+ #
62
+ def index(*objects)
63
+ using_sharded_session(objects) { |session, group| session.index(group) }
64
+ end
65
+
66
+ #
67
+ # See Sunspot.index!
68
+ #
69
+ def index!(*objects)
70
+ using_sharded_session(objects) { |session, group| session.index!(group) }
71
+ end
72
+
73
+ #
74
+ # See Sunspot.remove
75
+ #
76
+ def remove(*objects)
77
+ using_sharded_session(objects) { |session, group| session.remove(group) }
78
+ end
79
+
80
+ #
81
+ # See Sunspot.remove!
82
+ #
83
+ def remove!(*objects)
84
+ using_sharded_session(objects) { |session, group| session.remove!(group) }
85
+ end
86
+
87
+ #
88
+ # If no argument is passed, behaves like Sunspot.remove_all
89
+ #
90
+ # If an argument is passed, will raise NotSupportedError, as the proxy
91
+ # does not know which session(s) to which to delegate this operation.
92
+ #
93
+ def remove_all(clazz = nil)
94
+ if clazz
95
+ raise NotSupportedError, "Sharding session proxy does not support remove_all with an argument."
96
+ else
97
+ all_sessions.each { |session| session.remove_all }
98
+ end
99
+ end
100
+
101
+ #
102
+ # If no argument is passed, behaves like Sunspot.remove_all!
103
+ #
104
+ # If an argument is passed, will raise NotSupportedError, as the proxy
105
+ # does not know which session(s) to which to delegate this operation.
106
+ #
107
+ def remove_all!(clazz = nil)
108
+ if clazz
109
+ raise NotSupportedError, "Sharding session proxy does not support remove_all! with an argument."
110
+ else
111
+ all_sessions.each { |session| session.remove_all! }
112
+ end
113
+ end
114
+
115
+ #
116
+ # Commit all shards. See Sunspot.commit
117
+ #
118
+ def commit
119
+ all_sessions.each { |session| session.commit }
120
+ end
121
+
122
+ #
123
+ # Optimize all shards. See Sunspot.optimize
124
+ #
125
+ def optimize
126
+ all_sessions.each { |session| session.optimize }
127
+ end
128
+
129
+ #
130
+ # Commit all dirty sessions. Only dirty sessions will be committed.
131
+ #
132
+ # See Sunspot.commit_if_dirty
133
+ #
134
+ def commit_if_dirty
135
+ all_sessions.each { |session| session.commit_if_dirty }
136
+ end
137
+
138
+ #
139
+ # Commit all delete-dirty sessions. Only delete-dirty sessions will be
140
+ # committed.
141
+ #
142
+ # See Sunspot.commit_if_delete_dirty
143
+ #
144
+ def commit_if_delete_dirty
145
+ all_sessions.each { |session| session.commit_if_delete_dirty }
146
+ end
147
+
148
+ #
149
+ # Instantiate a new Search object, but don't execute it. The search will
150
+ # have an extra :shards param injected into the query, which will tell the
151
+ # Solr instance referenced by the search session to search across all
152
+ # shards.
153
+ #
154
+ # See Sunspot.new_search
155
+ #
156
+ def new_search(*types)
157
+ shard_urls = all_sessions.map { |session| session.config.solr.url }
158
+ search = @search_session.new_search(*types)
159
+ search.build do
160
+ adjust_solr_params { |params| params[:shards] = shard_urls.join(',') }
161
+ # I feel a little dirty doing this.
162
+ end
163
+ search
164
+ end
165
+
166
+ #
167
+ # Build and execute a new Search. The search will have an extra :shards
168
+ # param injected into the query, which will tell the Solr instance
169
+ # referenced by the search session to search across all shards.
170
+ #
171
+ # See Sunspot.search
172
+ #
173
+ def search(*types, &block)
174
+ new_search(*types).execute
175
+ end
176
+
177
+ def more_like_this(object, &block)
178
+ #FIXME should use shards
179
+ new_more_like_this(object, &block).execute
180
+ end
181
+
182
+ def new_more_like_this(object, &block)
183
+ @search_session.new_more_like_this(object, &block)
184
+ end
185
+
186
+ #
187
+ # True if any shard session is dirty. Note that directly using the
188
+ # #commit_if_dirty method is more efficient if that's what you're
189
+ # trying to do, since in that case only the dirty sessions are committed.
190
+ #
191
+ # See Sunspot.dirty?
192
+ #
193
+ def dirty?
194
+ all_sessions.any? { |session| session.dirty? }
195
+ end
196
+
197
+ #
198
+ # True if any shard session is delete-dirty. Note that directly using the
199
+ # #commit_if_delete_dirty method is more efficient if that's what you're
200
+ # trying to do, since in that case only the delete-dirty sessions are
201
+ # committed.
202
+ #
203
+ def delete_dirty?
204
+ all_sessions.any? { |session| session.delete_dirty? }
205
+ end
206
+
207
+ private
208
+
209
+ #
210
+ # Group the objects by which shard session they correspond to, and yield
211
+ # each session and is corresponding group of objects.
212
+ #
213
+ def using_sharded_session(objects)
214
+ grouped_objects = Hash.new { |h, k| h[k] = [] }
215
+ objects.flatten.each { |object| grouped_objects[session_for(object)] << object }
216
+ grouped_objects.each_pair do |session, group|
217
+ yield(session, group)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
2
+
3
+ module Sunspot
4
+ module SessionProxy
5
+ class SilentFailSessionProxy < AbstractSessionProxy
6
+
7
+ attr_reader :search_session
8
+
9
+ delegate :new_search, :search, :config,
10
+ :new_more_like_this, :more_like_this,
11
+ :delete_dirty, :delete_dirty?,
12
+ :to => :search_session
13
+
14
+ def initialize(search_session = Sunspot.session)
15
+ @search_session = search_session
16
+ end
17
+
18
+ def rescued_exception(method, e)
19
+ $stderr.puts("Exception in #{method}: #{e.message}")
20
+ end
21
+
22
+ SUPPORTED_METHODS = [
23
+ :batch, :commit, :commit_if_dirty, :commit_if_delete_dirty, :dirty?,
24
+ :index!, :index, :optimize, :remove!, :remove, :remove_all!, :remove_all,
25
+ :remove_by_id!, :remove_by_id
26
+ ]
27
+
28
+ SUPPORTED_METHODS.each do |method|
29
+ module_eval(<<-RUBY)
30
+ def #{method}(*args, &block)
31
+ begin
32
+ search_session.#{method}(*args, &block)
33
+ rescue => e
34
+ self.rescued_exception(:#{method}, e)
35
+ end
36
+ end
37
+ RUBY
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ require 'monitor'
2
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
3
+
4
+ module Sunspot
5
+ module SessionProxy
6
+ #
7
+ # This class implements a session proxy that creates a different Session
8
+ # object for each thread. Any multithreaded application should use this
9
+ # proxy.
10
+ #
11
+ class ThreadLocalSessionProxy < AbstractSessionProxy
12
+ FINALIZER = Proc.new do |object_id|
13
+ Thread.current[:"sunspot_session_#{object_id}"] = nil
14
+ end
15
+
16
+ # The configuration with which the thread-local sessions are initialized.
17
+ attr_reader :config
18
+ @@next_id = 0
19
+
20
+ delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :optimize, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :more_like_this, :new_more_like_this, :to => :session
21
+
22
+ #
23
+ # Optionally pass an existing Sunspot::Configuration object. If none is
24
+ # passed, a default configuration is used; it can then be modified using
25
+ # the #config attribute.
26
+ #
27
+ def initialize(config = Sunspot::Configuration.new)
28
+ @config = config
29
+ ObjectSpace.define_finalizer(self, FINALIZER)
30
+ end
31
+
32
+ def session #:nodoc:
33
+ Thread.current[:"sunspot_session_#{object_id}"] ||= Session.new(config)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,350 @@
1
+ module Sunspot
2
+ #
3
+ # This class encapsulates the search/indexing setup for a given class. Its
4
+ # contents are built using the Sunspot.setup method.
5
+ #
6
+ class Setup #:nodoc:
7
+ attr_reader :class_object_id
8
+ def initialize(clazz)
9
+ @class_object_id = clazz.object_id
10
+ @class_name = clazz.name
11
+ @field_factories, @text_field_factories, @dynamic_field_factories,
12
+ @field_factories_cache, @text_field_factories_cache,
13
+ @dynamic_field_factories_cache = *Array.new(6) { Hash.new }
14
+ @stored_field_factories_cache = Hash.new { |h, k| h[k] = [] }
15
+ @more_like_this_field_factories_cache = Hash.new { |h, k| h[k] = [] }
16
+ @dsl = DSL::Fields.new(self)
17
+ add_field_factory(:class, Type::ClassType.instance)
18
+ end
19
+
20
+ def type_names
21
+ [@class_name]
22
+ end
23
+
24
+ #
25
+ # Add field factory for scope/ordering
26
+ #
27
+ def add_field_factory(name, type, options = {}, &block)
28
+ stored, more_like_this = options[:stored], options[:more_like_this]
29
+ field_factory = FieldFactory::Static.new(name, type, options, &block)
30
+ @field_factories[field_factory.signature] = field_factory
31
+ @field_factories_cache[field_factory.name] = field_factory
32
+ if stored
33
+ @stored_field_factories_cache[field_factory.name] << field_factory
34
+ end
35
+ if more_like_this
36
+ @more_like_this_field_factories_cache[field_factory.name] << field_factory
37
+ end
38
+ end
39
+
40
+ #
41
+ # Add field_factories for fulltext search
42
+ #
43
+ # ==== Parameters
44
+ #
45
+ # field_factories<Array>:: Array of Sunspot::Field objects
46
+ #
47
+ def add_text_field_factory(name, options = {}, &block)
48
+ stored, more_like_this = options[:stored], options[:more_like_this]
49
+ field_factory = FieldFactory::Static.new(name, Type::TextType.instance, options, &block)
50
+ @text_field_factories[name] = field_factory
51
+ @text_field_factories_cache[field_factory.name] = field_factory
52
+ if stored
53
+ @stored_field_factories_cache[field_factory.name] << field_factory
54
+ end
55
+ if more_like_this
56
+ @more_like_this_field_factories_cache[field_factory.name] << field_factory
57
+ end
58
+ end
59
+
60
+ #
61
+ # Add dynamic field_factories
62
+ #
63
+ # ==== Parameters
64
+ #
65
+ # field_factories<Array>:: Array of dynamic field objects
66
+ #
67
+ def add_dynamic_field_factory(name, type, options = {}, &block)
68
+ stored, more_like_this = options[:stored], options[:more_like_this]
69
+ field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
70
+ @dynamic_field_factories[field_factory.signature] = field_factory
71
+ @dynamic_field_factories_cache[field_factory.name] = field_factory
72
+ if stored
73
+ @stored_field_factories_cache[field_factory.name] << field_factory
74
+ end
75
+ if more_like_this
76
+ @more_like_this_field_factories_cache[field_factory.name] << field_factory
77
+ end
78
+ end
79
+
80
+ #
81
+ # Add a document boost to documents at index time. Document boost can be
82
+ # static (the same for all documents of this class), or extracted on a per-
83
+ # document basis using either attribute or block extraction as per usual.
84
+ #
85
+ def add_document_boost(attr_name, &block)
86
+ @document_boost_extractor =
87
+ if attr_name
88
+ if attr_name.respond_to?(:to_f)
89
+ DataExtractor::Constant.new(attr_name)
90
+ else
91
+ DataExtractor::AttributeExtractor.new(attr_name)
92
+ end
93
+ else
94
+ DataExtractor::BlockExtractor.new(&block)
95
+ end
96
+ end
97
+
98
+ #
99
+ # Builder method for evaluating the setup DSL
100
+ #
101
+ def setup(&block)
102
+ Util.instance_eval_or_call(@dsl, &block)
103
+ end
104
+
105
+ #
106
+ # Return the Field with the given (public-facing) name
107
+ #
108
+ def field(field_name)
109
+ if field_factory = @field_factories_cache[field_name.to_sym]
110
+ field_factory.build
111
+ else
112
+ raise(
113
+ UnrecognizedFieldError,
114
+ "No field configured for #{@class_name} with name '#{field_name}'"
115
+ )
116
+ end
117
+ end
118
+
119
+ #
120
+ # Return one or more text fields with the given public-facing name. This
121
+ # implementation will always return a single field (in an array), but
122
+ # CompositeSetup objects might return more than one.
123
+ #
124
+ def text_fields(field_name)
125
+ text_field =
126
+ if field_factory = @text_field_factories_cache[field_name.to_sym]
127
+ field_factory.build
128
+ else
129
+ raise(
130
+ UnrecognizedFieldError,
131
+ "No text field configured for #{@class_name} with name '#{field_name}'"
132
+ )
133
+ end
134
+ [text_field]
135
+ end
136
+
137
+ #
138
+ # Return one or more stored fields (can be either attribute or text fields)
139
+ # for the given name.
140
+ #
141
+ def stored_fields(field_name, dynamic_field_name = nil)
142
+ @stored_field_factories_cache[field_name.to_sym].map do |field_factory|
143
+ if dynamic_field_name
144
+ field_factory.build(dynamic_field_name)
145
+ else
146
+ field_factory.build
147
+ end
148
+ end
149
+ end
150
+
151
+ #
152
+ # Return one or more more_like_this fields (can be either attribute or text fields)
153
+ # for the given name.
154
+ #
155
+ def more_like_this_fields(field_name)
156
+ @more_like_this_field_factories_cache[field_name.to_sym].map do |field_factory|
157
+ field_factory.build
158
+ end
159
+ end
160
+
161
+ #
162
+ # Return the DynamicFieldFactory with the given base name
163
+ #
164
+ def dynamic_field_factory(field_name)
165
+ @dynamic_field_factories_cache[field_name.to_sym] || raise(
166
+ UnrecognizedFieldError,
167
+ "No dynamic field configured for #{@class_name} with name '#{field_name}'"
168
+ )
169
+ end
170
+
171
+ #
172
+ # Return all attribute fields
173
+ #
174
+ def fields
175
+ field_factories.map { |field_factory| field_factory.build }
176
+ end
177
+
178
+ #
179
+ # Return all text fields
180
+ #
181
+ def all_text_fields
182
+ text_field_factories.map { |text_field_factory| text_field_factory.build }
183
+ end
184
+
185
+ #
186
+ # Return all more_like_this fields
187
+ #
188
+ def all_more_like_this_fields
189
+ @more_like_this_field_factories_cache.values.map do |field_factories|
190
+ field_factories.map { |field_factory| field_factory.build }
191
+ end.flatten
192
+ end
193
+
194
+ #
195
+ # Get the field_factories associated with this setup as well as all inherited field_factories
196
+ #
197
+ # ==== Returns
198
+ #
199
+ # Array:: Collection of all field_factories associated with this setup
200
+ #
201
+ def field_factories
202
+ collection_from_inheritable_hash(:field_factories)
203
+ end
204
+
205
+ #
206
+ # Get the text field_factories associated with this setup as well as all inherited
207
+ # text field_factories
208
+ #
209
+ # ==== Returns
210
+ #
211
+ # Array:: Collection of all text field_factories associated with this setup
212
+ #
213
+ def text_field_factories
214
+ collection_from_inheritable_hash(:text_field_factories)
215
+ end
216
+
217
+ #
218
+ # Get all static, dynamic, and text field_factories associated with this setup as
219
+ # well as all inherited field_factories
220
+ #
221
+ # ==== Returns
222
+ #
223
+ # Array:: Collection of all text and scope field_factories associated with this setup
224
+ #
225
+ def all_field_factories
226
+ all_field_factories = []
227
+ all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
228
+ all_field_factories
229
+ end
230
+
231
+ #
232
+ # Get all dynamic field_factories for this and parent setups
233
+ #
234
+ # ==== Returns
235
+ #
236
+ # Array:: Dynamic field_factories
237
+ #
238
+ def dynamic_field_factories
239
+ collection_from_inheritable_hash(:dynamic_field_factories)
240
+ end
241
+
242
+ #
243
+ # Return the class associated with this setup.
244
+ #
245
+ # ==== Returns
246
+ #
247
+ # clazz<Class>:: Class setup is configured for
248
+ #
249
+ def clazz
250
+ Util.full_const_get(@class_name)
251
+ end
252
+
253
+ #
254
+ # Get the document boost for a given model
255
+ #
256
+ def document_boost_for(model)
257
+ if @document_boost_extractor
258
+ @document_boost_extractor.value_for(model)
259
+ end
260
+ end
261
+
262
+ protected
263
+
264
+ #
265
+ # Get the nearest inherited setup, if any
266
+ #
267
+ # ==== Returns
268
+ #
269
+ # Sunspot::Setup:: Setup for the nearest ancestor of this setup's class
270
+ #
271
+ def parent
272
+ Setup.for(clazz.superclass)
273
+ end
274
+
275
+ def get_inheritable_hash(name)
276
+ hash = instance_variable_get(:"@#{name}")
277
+ parent.get_inheritable_hash(name).each_pair do |key, value|
278
+ hash[key] = value unless hash.has_key?(key)
279
+ end if parent
280
+ hash
281
+ end
282
+
283
+ private
284
+
285
+ def collection_from_inheritable_hash(name)
286
+ get_inheritable_hash(name).values
287
+ end
288
+
289
+ class <<self
290
+ #
291
+ # Retrieve or create the Setup instance for the given class, evaluating
292
+ # the given block to add to the setup's configuration
293
+ #
294
+ def setup(clazz, &block) #:nodoc:
295
+ self.for!(clazz).setup(&block)
296
+ end
297
+
298
+ #
299
+ # Retrieve the setup instance for the given class, or for the nearest
300
+ # ancestor that has a setup, if any.
301
+ #
302
+ # ==== Parameters
303
+ #
304
+ # clazz<Class>:: Class for which to retrieve a setup
305
+ #
306
+ # ==== Returns
307
+ #
308
+ # Sunspot::Setup::
309
+ # Setup instance associated with the given class or its nearest ancestor
310
+ #
311
+ def for(clazz) #:nodoc:
312
+ setups[clazz.name.to_sym] || self.for(clazz.superclass) if clazz
313
+ end
314
+
315
+ protected
316
+
317
+ #
318
+ # Retrieve or create a Setup instance for this class
319
+ #
320
+ # ==== Parameters
321
+ #
322
+ # clazz<Class>:: Class for which to retrieve a setup
323
+ #
324
+ # ==== Returns
325
+ #
326
+ # Sunspot::Setup:: New or existing setup for this class
327
+ #
328
+ def for!(clazz) #:nodoc:
329
+ setup = setups[clazz.name.to_sym]
330
+ if setup && setup.class_object_id == clazz.object_id
331
+ setup
332
+ else
333
+ setups[clazz.name.to_sym] = new(clazz)
334
+ end
335
+ end
336
+
337
+ private
338
+
339
+ # Singleton hash of class names to Setup instances
340
+ #
341
+ # ==== Returns
342
+ #
343
+ # Hash:: Class names keyed to Setup instances
344
+ #
345
+ def setups
346
+ @setups ||= {}
347
+ end
348
+ end
349
+ end
350
+ end