mwmitchell-rsolr 0.5.7 → 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.txt CHANGED
@@ -1,3 +1,7 @@
1
+ 0.5.7 - January 5, 2009
2
+ Changed name from Solr to RSolr, changed all references to Solr to RSolr
3
+ Added new tests for RSolr::Mapper and RSolr::Message
4
+
1
5
  0.5.6 - December 30, 2008
2
6
  solr.gemspec cleanedup thanks to shairontoledo on github! :)
3
7
  Added Solr::Response::Query::Facet module with helpers from the delsolr project
data/LICENSE CHANGED
@@ -1,192 +1,4 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- 1. Definitions.
8
-
9
- "License" shall mean the terms and conditions for use, reproduction,
10
- and distribution as defined by Sections 1 through 9 of this document.
11
-
12
- "Licensor" shall mean the copyright owner or entity authorized by
13
- the copyright owner that is granting the License.
14
-
15
- "Legal Entity" shall mean the union of the acting entity and all
16
- other entities that control, are controlled by, or are under common
17
- control with that entity. For the purposes of this definition,
18
- "control" means (i) the power, direct or indirect, to cause the
19
- direction or management of such entity, whether by contract or
20
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
- outstanding shares, or (iii) beneficial ownership of such entity.
22
-
23
- "You" (or "Your") shall mean an individual or Legal Entity
24
- exercising permissions granted by this License.
25
-
26
- "Source" form shall mean the preferred form for making modifications,
27
- including but not limited to software source code, documentation
28
- source, and configuration files.
29
-
30
- "Object" form shall mean any form resulting from mechanical
31
- transformation or translation of a Source form, including but
32
- not limited to compiled object code, generated documentation,
33
- and conversions to other media types.
34
-
35
- "Work" shall mean the work of authorship, whether in Source or
36
- Object form, made available under the License, as indicated by a
37
- copyright notice that is included in or attached to the work
38
- (an example is provided in the Appendix below).
39
-
40
- "Derivative Works" shall mean any work, whether in Source or Object
41
- form, that is based on (or derived from) the Work and for which the
42
- editorial revisions, annotations, elaborations, or other modifications
43
- represent, as a whole, an original work of authorship. For the purposes
44
- of this License, Derivative Works shall not include works that remain
45
- separable from, or merely link (or bind by name) to the interfaces of,
46
- the Work and Derivative Works thereof.
47
-
48
- "Contribution" shall mean any work of authorship, including
49
- the original version of the Work and any modifications or additions
50
- to that Work or Derivative Works thereof, that is intentionally
51
- submitted to Licensor for inclusion in the Work by the copyright owner
52
- or by an individual or Legal Entity authorized to submit on behalf of
53
- the copyright owner. For the purposes of this definition, "submitted"
54
- means any form of electronic, verbal, or written communication sent
55
- to the Licensor or its representatives, including but not limited to
56
- communication on electronic mailing lists, source code control systems,
57
- and issue tracking systems that are managed by, or on behalf of, the
58
- Licensor for the purpose of discussing and improving the Work, but
59
- excluding communication that is conspicuously marked or otherwise
60
- designated in writing by the copyright owner as "Not a Contribution."
61
-
62
- "Contributor" shall mean Licensor and any individual or Legal Entity
63
- on behalf of whom a Contribution has been received by Licensor and
64
- subsequently incorporated within the Work.
65
-
66
- 2. Grant of Copyright License. Subject to the terms and conditions of
67
- this License, each Contributor hereby grants to You a perpetual,
68
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
- copyright license to reproduce, prepare Derivative Works of,
70
- publicly display, publicly perform, sublicense, and distribute the
71
- Work and such Derivative Works in Source or Object form.
72
-
73
- 3. Grant of Patent License. Subject to the terms and conditions of
74
- this License, each Contributor hereby grants to You a perpetual,
75
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
- (except as stated in this section) patent license to make, have made,
77
- use, offer to sell, sell, import, and otherwise transfer the Work,
78
- where such license applies only to those patent claims licensable
79
- by such Contributor that are necessarily infringed by their
80
- Contribution(s) alone or by combination of their Contribution(s)
81
- with the Work to which such Contribution(s) was submitted. If You
82
- institute patent litigation against any entity (including a
83
- cross-claim or counterclaim in a lawsuit) alleging that the Work
84
- or a Contribution incorporated within the Work constitutes direct
85
- or contributory patent infringement, then any patent licenses
86
- granted to You under this License for that Work shall terminate
87
- as of the date such litigation is filed.
88
-
89
- 4. Redistribution. You may reproduce and distribute copies of the
90
- Work or Derivative Works thereof in any medium, with or without
91
- modifications, and in Source or Object form, provided that You
92
- meet the following conditions:
93
-
94
- (a) You must give any other recipients of the Work or
95
- Derivative Works a copy of this License; and
96
-
97
- (b) You must cause any modified files to carry prominent notices
98
- stating that You changed the files; and
99
-
100
- (c) You must retain, in the Source form of any Derivative Works
101
- that You distribute, all copyright, patent, trademark, and
102
- attribution notices from the Source form of the Work,
103
- excluding those notices that do not pertain to any part of
104
- the Derivative Works; and
105
-
106
- (d) If the Work includes a "NOTICE" text file as part of its
107
- distribution, then any Derivative Works that You distribute must
108
- include a readable copy of the attribution notices contained
109
- within such NOTICE file, excluding those notices that do not
110
- pertain to any part of the Derivative Works, in at least one
111
- of the following places: within a NOTICE text file distributed
112
- as part of the Derivative Works; within the Source form or
113
- documentation, if provided along with the Derivative Works; or,
114
- within a display generated by the Derivative Works, if and
115
- wherever such third-party notices normally appear. The contents
116
- of the NOTICE file are for informational purposes only and
117
- do not modify the License. You may add Your own attribution
118
- notices within Derivative Works that You distribute, alongside
119
- or as an addendum to the NOTICE text from the Work, provided
120
- that such additional attribution notices cannot be construed
121
- as modifying the License.
122
-
123
- You may add Your own copyright statement to Your modifications and
124
- may provide additional or different license terms and conditions
125
- for use, reproduction, or distribution of Your modifications, or
126
- for any such Derivative Works as a whole, provided Your use,
127
- reproduction, and distribution of the Work otherwise complies with
128
- the conditions stated in this License.
129
-
130
- 5. Submission of Contributions. Unless You explicitly state otherwise,
131
- any Contribution intentionally submitted for inclusion in the Work
132
- by You to the Licensor shall be under the terms and conditions of
133
- this License, without any additional terms or conditions.
134
- Notwithstanding the above, nothing herein shall supersede or modify
135
- the terms of any separate license agreement you may have executed
136
- with Licensor regarding such Contributions.
137
-
138
- 6. Trademarks. This License does not grant permission to use the trade
139
- names, trademarks, service marks, or product names of the Licensor,
140
- except as required for reasonable and customary use in describing the
141
- origin of the Work and reproducing the content of the NOTICE file.
142
-
143
- 7. Disclaimer of Warranty. Unless required by applicable law or
144
- agreed to in writing, Licensor provides the Work (and each
145
- Contributor provides its Contributions) on an "AS IS" BASIS,
146
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
- implied, including, without limitation, any warranties or conditions
148
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
- PARTICULAR PURPOSE. You are solely responsible for determining the
150
- appropriateness of using or redistributing the Work and assume any
151
- risks associated with Your exercise of permissions under this License.
152
-
153
- 8. Limitation of Liability. In no event and under no legal theory,
154
- whether in tort (including negligence), contract, or otherwise,
155
- unless required by applicable law (such as deliberate and grossly
156
- negligent acts) or agreed to in writing, shall any Contributor be
157
- liable to You for damages, including any direct, indirect, special,
158
- incidental, or consequential damages of any character arising as a
159
- result of this License or out of the use or inability to use the
160
- Work (including but not limited to damages for loss of goodwill,
161
- work stoppage, computer failure or malfunction, or any and all
162
- other commercial damages or losses), even if such Contributor
163
- has been advised of the possibility of such damages.
164
-
165
- 9. Accepting Warranty or Additional Liability. While redistributing
166
- the Work or Derivative Works thereof, You may choose to offer,
167
- and charge a fee for, acceptance of support, warranty, indemnity,
168
- or other liability obligations and/or rights consistent with this
169
- License. However, in accepting such obligations, You may act only
170
- on Your own behalf and on Your sole responsibility, not on behalf
171
- of any other Contributor, and only if You agree to indemnify,
172
- defend, and hold each Contributor harmless for any liability
173
- incurred by, or claims asserted against, such Contributor by reason
174
- of your accepting any such warranty or additional liability.
175
-
176
- END OF TERMS AND CONDITIONS
177
-
178
- APPENDIX: How to apply the Apache License to your work.
179
-
180
- To apply the Apache License to your work, attach the following
181
- boilerplate notice, with the fields enclosed by brackets "[]"
182
- replaced with your own identifying information. (Don't include
183
- the brackets!) The text should be enclosed in the appropriate
184
- comment syntax for the file format. We also recommend that a
185
- file or class name and description of purpose be included on the
186
- same "printed page" as the copyright notice for easier
187
- identification within third-party archives.
188
-
189
- Copyright [yyyy] [name of copyright owner]
1
+ Copyright 2008-2009 Matt Mitchell
190
2
 
191
3
  Licensed under the Apache License, Version 2.0 (the "License");
192
4
  you may not use this file except in compliance with the License.
@@ -198,4 +10,58 @@ Unless required by applicable law or agreed to in writing, software
198
10
  distributed under the License is distributed on an "AS IS" BASIS,
199
11
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
12
  See the License for the specific language governing permissions and
201
- limitations under the License.
13
+ limitations under the License.
14
+
15
+ ========================================================================
16
+
17
+ For use of the lib/Mash/extlib code:
18
+
19
+ ========================================================================
20
+
21
+ Copyright (c) 2008 Sam Smoot.
22
+
23
+ Permission is hereby granted, free of charge, to any person obtaining
24
+ a copy of this software and associated documentation files (the
25
+ "Software"), to deal in the Software without restriction, including
26
+ without limitation the rights to use, copy, modify, merge, publish,
27
+ distribute, sublicense, and/or sell copies of the Software, and to
28
+ permit persons to whom the Software is furnished to do so, subject to
29
+ the following conditions:
30
+
31
+ The above copyright notice and this permission notice shall be
32
+ included in all copies or substantial portions of the Software.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
+
42
+ ---
43
+ ---
44
+
45
+ Some portions of blank.rb and mash.rb are verbatim copies of software
46
+ licensed under the MIT license. That license is included below:
47
+
48
+ Copyright (c) 2005-2008 David Heinemeier Hansson
49
+
50
+ Permission is hereby granted, free of charge, to any person obtaining
51
+ a copy of this software and associated documentation files (the
52
+ "Software"), to deal in the Software without restriction, including
53
+ without limitation the rights to use, copy, modify, merge, publish,
54
+ distribute, sublicense, and/or sell copies of the Software, and to
55
+ permit persons to whom the Software is furnished to do so, subject to
56
+ the following conditions:
57
+
58
+ The above copyright notice and this permission notice shall be
59
+ included in all copies or substantial portions of the Software.
60
+
61
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
62
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
63
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
64
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
65
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
66
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
67
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc CHANGED
@@ -1,25 +1,36 @@
1
- =Solr
1
+ =RSolr
2
2
 
3
3
  A Ruby client for Apache Solr. Has transparent JRuby support by using "org.apache.solr.servlet.DirectSolrConnection" as a connection adapter.
4
4
 
5
+ ==Installation:
6
+ gem sources -a http://gems.github.com
7
+ sudo gem install mwmitchell-rsolr
8
+
9
+ Simple usage:
10
+ require 'rubygems'
11
+ require 'rsolr'
12
+ rsolr = RSolr.connect(:http)
13
+ response = rsolr.query(:q=>'*:*')
14
+
15
+
5
16
  To run tests:
6
17
 
7
- Copy a Solr 1.3.0 (or later) distribution into this directory and rename to "apache-solr"
8
- Start Solr HTTP: rake start_test_server
18
+ Copy an Apache Solr 1.3.0/or later (http://apache.seekmeup.com/lucene/solr/1.3.0/) distribution into this directory and rename to "apache-solr"
19
+ Start Solr HTTP: rake rsolr:start_test_server
9
20
  MRI Ruby: rake
10
21
  JRuby: jruby -S rake
11
22
 
12
23
  To get a connection in MRI/standard Ruby:
13
24
 
14
- solr = Solr.connect(:http)
25
+ solr = RSolr.connect(:http)
15
26
 
16
- To get a direct connection in jRuby using DirectSolrConnection:
27
+ To get a direct connection (no http) in jRuby using DirectSolrConnection:
17
28
 
18
- solr = Solr.connect(:direct, :home_dir=>'/path/to/solr/home', :dist_dir=>'/path/to/solr/distribution')
29
+ solr = RSolr.connect(:direct, :home_dir=>'/path/to/solr/home', :dist_dir=>'/path/to/solr/distribution')
19
30
 
20
- You can set Solr params that will be sent for every request:
31
+ You can set RSolr params that will be sent on every request:
21
32
 
22
- solr = Solr.connect(:http, :global_params=>{:wt=>:ruby, :echoParams=>'EXPLICIT'})
33
+ solr = RSolr.connect(:http, :global_params=>{:wt=>:ruby, :echoParams=>'EXPLICIT'})
23
34
 
24
35
 
25
36
  == Requests
@@ -30,13 +41,13 @@ Once you have a connection, you can execute queries, updates etc..
30
41
  response = solr.query(:q=>'washington', :facet=>true, :facet.limit=>-1, :facet.field=>'cat', :facet.field=>'inStock')
31
42
  response = solr.find_by_id(1)
32
43
 
33
- * thanks to a little Ruby magic, we can chain symbols to create Solr "dot" syntax: :facet.field=>'cat'
44
+ Thanks to a little Ruby magic, we can chain symbols to create Solr "dot" syntax: :facet.field=>'cat'
34
45
 
35
46
  Using the #search method makes building more complex Solr queries easier:
36
47
 
37
48
  response = solr.search 'my search', :filters=>{:price=>(0.00..10.00)}
38
49
  response.docs.each do |doc|
39
- doc.price
50
+ doc[:price]
40
51
  end
41
52
 
42
53
  ====Pagination
@@ -89,12 +100,12 @@ Commit & Optimize
89
100
 
90
101
 
91
102
  ==Response Formats
92
- The default response format is Ruby. When the :wt param is set to :ruby, the response is eval'd and wrapped up in a nice Solr::Response class. You can get an unwrapped response by setting the :wt to "ruby" - notice, the string -- not a symbol. All other response formats are available as expected, :wt=>'xml' etc.. Currently, the only response format that gets eval'd and wrapped is :ruby.
103
+ The default response format is Ruby. When the :wt param is set to :ruby, the response is eval'd and wrapped up in a nice RSolr::Response class. You can get an unwrapped response by setting the :wt to "ruby" - notice, the string -- not a symbol. All other response formats are available as expected, :wt=>'xml' etc.. Currently, the only response format that gets eval'd and wrapped is :ruby.
93
104
 
94
- You can access the original request context (path, params, url etc.) from response.source. The response.source is a hash that contains the generated params, url, path, post data, headers etc.. This could be useful for debugging and testing.
105
+ You can access the original request context (path, params, url etc.) from response.request. The response.request is a hash that contains the generated params, url, path, post data, headers etc., very useful for debugging and testing.
95
106
 
96
107
  ==Data Mapping
97
- The Solr::Mapper::Base class provides some nice ways of mapping data. You provide a hash mapping and a "data source". The keys of the hash mapping become the Solr field names. The values of the hash mapping get processed differently based on the value. The data source must be an Enumerable type object. The hash mapping is processed for each item in the data source.
108
+ The RSolr::Mapper::Base class provides some nice ways of mapping data. You provide a hash mapping and a "data source". The keys of the hash mapping become the Solr field names. The values of the hash mapping get processed differently based on the value. The data source must be an Enumerable type object. The hash mapping is processed for each item in the data source.
98
109
 
99
110
  ===Hash Map Processing
100
111
  If the value is a string, the value of the String is used as the final Solr field value. If the value is a Symbol, the Symbol is used as a key on the data source. An Enumerable type does the same as the Symbol, but for each item in the set. The most interesting and flexible processing occurs when the value is a Proc. When a Proc is used as a hash mapping value, the mapper executes the Proc's #call method, passing in the current data source item.
@@ -128,7 +139,7 @@ If the value is a string, the value of the String is used as the final Solr fiel
128
139
  }
129
140
  ]
130
141
 
131
- mapper = Solr::Mapper::Base(mapping)
142
+ mapper = RSolr::Mapper::Base(mapping)
132
143
  mapped_data = mapper.map(data_source)
133
144
 
134
145
  # the following would be true...
@@ -150,9 +161,9 @@ If the value is a string, the value of the String is used as the final Solr fiel
150
161
  ]
151
162
 
152
163
  ===RSS Mapper
153
- There is currently one built in mapper, Solr::Mapper::RSS. Here's an example usage:
164
+ There is currently one built in mapper, RSolr::Mapper::RSS. Here's an example usage:
154
165
 
155
- mapper = Solr::Mapper::RSS.new
166
+ mapper = RSolr::Mapper::RSS.new
156
167
  mapping = {
157
168
  :channel=>:'channel.title',
158
169
  :url=>:'channel.link',
@@ -165,26 +176,26 @@ There is currently one built in mapper, Solr::Mapper::RSS. Here's an example usa
165
176
  mapped_data = m.map('http://site.com/feed.rss')
166
177
 
167
178
  ==Indexing
168
- Solr (ruby) comes with a simple indexer that makes use of the Solr mapper. Here's an example, using the "mapping" and "mapped_data" variables above (RSS mapper):
179
+ RSolr comes with a simple indexer that makes use of the Solr mapper. Here's an example, using the "mapping" and "mapped_data" variables above (RSS mapper):
169
180
 
170
- solr = Solr.connect(:http)
171
- i = Solr::Indexer.new(solr, mapping)
181
+ solr = RSolr.connect(:http)
182
+ i = RSolr::Indexer.new(solr, mapping)
172
183
  i.index(mapped_data)
173
184
 
174
185
 
175
186
  ==HTTP Client Adapter
176
- You can specify the Ruby http client to use by setting Solr::Connection::Adapter::HTTP.client_adapter to one of:
187
+ You can specify the http client adapter to use by setting RSolr::Connection::Adapter::HTTP.client_adapter to one of:
177
188
  :net_http uses the standard Net::HTTP library
178
189
  :curb uses the Ruby "curl" bindings
179
190
 
180
191
  Example:
181
192
 
182
- Solr::Connection::Adapter::HTTP.client_adapter = :curb
183
-
193
+ RSolr::Connection::Adapter::HTTP.client_adapter = :curb
194
+
184
195
  Example of using the HTTP client only:
185
196
 
186
- hclient = Solr::HTTPClient.connect(url, :curb)
187
- hclient = Solr::HTTPClient.connect(url, :net_http)
197
+ hclient = RSolr::HTTPClient.connect(url, :curb)
198
+ hclient = RSolr::HTTPClient.connect(url, :net_http)
188
199
 
189
200
  After reading this http://apocryph.org/2008/11/09/more_indepth_analysis_ruby_http_client_performance/ - I would recommend using the :curb adapter. NOTE: You can't use the :curb adapter under jRuby. To install curb:
190
201
 
data/examples/direct.rb CHANGED
@@ -14,7 +14,7 @@ response = solr.search 'ipod', :filters=>{:price=>(0..50)}, :per_page=>2, :page=
14
14
  solr.delete_by_query('*:*')
15
15
 
16
16
  response.docs.each do |doc|
17
- if doc.has?('timestamp')
18
- puts doc.timestamp
17
+ if doc.has?(:timestamp)
18
+ puts doc[:timestamp]
19
19
  end
20
20
  end
data/examples/http.rb CHANGED
@@ -10,7 +10,7 @@ response = solr.search 'ipod', :filters=>{:price=>(0..50)}, :per_page=>2, :page=
10
10
  solr.delete_by_query('*:*')
11
11
 
12
12
  response.docs.each do |doc|
13
- if doc.has?('timestamp')
14
- puts doc.timestamp
13
+ if doc.has?(:timestamp)
14
+ puts doc[:timestamp]
15
15
  end
16
16
  end
data/lib/core_ext.rb CHANGED
@@ -5,4 +5,12 @@ class Symbol
5
5
  [self.to_s, m.to_s].join('.').to_sym
6
6
  end
7
7
 
8
+ end
9
+
10
+ class Hash
11
+
12
+ def to_mash
13
+ self.is_a?(Mash) ? self : Mash.new(self)
14
+ end
15
+
8
16
  end
data/lib/mash.rb ADDED
@@ -0,0 +1,149 @@
1
+
2
+ # From the ExtLib gem library: http://github.com/sam/extlib/tree/master
3
+
4
+ class Mash < Hash
5
+
6
+ # @param constructor<Object>
7
+ # The default value for the mash. Defaults to an empty hash.
8
+ #
9
+ # @details [Alternatives]
10
+ # If constructor is a Hash, a new mash will be created based on the keys of
11
+ # the hash and no default value will be set.
12
+ def initialize(constructor = {})
13
+ if constructor.is_a?(Hash)
14
+ super()
15
+ update(constructor)
16
+ else
17
+ super(constructor)
18
+ end
19
+ end
20
+
21
+ # @param key<Object> The default value for the mash. Defaults to nil.
22
+ #
23
+ # @details [Alternatives]
24
+ # If key is a Symbol and it is a key in the mash, then the default value will
25
+ # be set to the value matching the key.
26
+ def default(key = nil)
27
+ if key.is_a?(Symbol) && include?(key = key.to_s)
28
+ self[key]
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
35
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
36
+
37
+ # @param key<Object> The key to set.
38
+ # @param value<Object>
39
+ # The value to set the key to.
40
+ #
41
+ # @see Mash#convert_key
42
+ # @see Mash#convert_value
43
+ def []=(key, value)
44
+ regular_writer(convert_key(key), convert_value(value))
45
+ end
46
+
47
+ # @param other_hash<Hash>
48
+ # A hash to update values in the mash with. The keys and the values will be
49
+ # converted to Mash format.
50
+ #
51
+ # @return <Mash> The updated mash.
52
+ def update(other_hash)
53
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
54
+ self
55
+ end
56
+
57
+ alias_method :merge!, :update
58
+
59
+ # @param key<Object> The key to check for. This will be run through convert_key.
60
+ #
61
+ # @return <TrueClass, FalseClass> True if the key exists in the mash.
62
+ def key?(key)
63
+ super(convert_key(key))
64
+ end
65
+
66
+ # def include? def has_key? def member?
67
+ alias_method :include?, :key?
68
+ alias_method :has_key?, :key?
69
+ alias_method :member?, :key?
70
+
71
+ # @param key<Object> The key to fetch. This will be run through convert_key.
72
+ # @param *extras<Array> Default value.
73
+ #
74
+ # @return <Object> The value at key or the default value.
75
+ def fetch(key, *extras)
76
+ super(convert_key(key), *extras)
77
+ end
78
+
79
+ # @param *indices<Array>
80
+ # The keys to retrieve values for. These will be run through +convert_key+.
81
+ #
82
+ # @return <Array> The values at each of the provided keys
83
+ def values_at(*indices)
84
+ indices.collect {|key| self[convert_key(key)]}
85
+ end
86
+
87
+ # @param hash<Hash> The hash to merge with the mash.
88
+ #
89
+ # @return <Mash> A new mash with the hash values merged in.
90
+ def merge(hash)
91
+ self.dup.update(hash)
92
+ end
93
+
94
+ # @param key<Object>
95
+ # The key to delete from the mash.\
96
+ def delete(key)
97
+ super(convert_key(key))
98
+ end
99
+
100
+ # @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
101
+ #
102
+ # @return <Mash> A new mash without the selected keys.
103
+ #
104
+ # @example
105
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
106
+ # #=> { "two" => 2, "three" => 3 }
107
+ def except(*keys)
108
+ super(*keys.map {|k| convert_key(k)})
109
+ end
110
+
111
+ # Used to provide the same interface as Hash.
112
+ #
113
+ # @return <Mash> This mash unchanged.
114
+ def stringify_keys!; self end
115
+
116
+ # @return <Hash> The mash as a Hash with string keys.
117
+ def to_hash
118
+ Hash.new(default).merge(self)
119
+ end
120
+
121
+ protected
122
+ # @param key<Object> The key to convert.
123
+ #
124
+ # @param <Object>
125
+ # The converted key. If the key was a symbol, it will be converted to a
126
+ # string.
127
+ #
128
+ # @api private
129
+ def convert_key(key)
130
+ key.kind_of?(Symbol) ? key.to_s : key
131
+ end
132
+
133
+ # @param value<Object> The value to convert.
134
+ #
135
+ # @return <Object>
136
+ # The converted value. A Hash or an Array of hashes, will be converted to
137
+ # their Mash equivalents.
138
+ #
139
+ # @api private
140
+ def convert_value(value)
141
+ if value.class == Hash
142
+ value.to_mash
143
+ elsif value.is_a?(Array)
144
+ value.collect { |e| convert_value(e) }
145
+ else
146
+ value
147
+ end
148
+ end
149
+ end
@@ -39,7 +39,6 @@ module RSolr::Connection::Adapter::CommonMethods
39
39
 
40
40
  # send a request to the adapter (allows requests like /admin/luke etc.)
41
41
  def send_request(handler_path, params={}, data=nil)
42
- params = map_params(params)
43
42
  @adapter.send_request(handler_path, params, data)
44
43
  end
45
44
 
@@ -19,13 +19,13 @@ class RSolr::Connection::Base
19
19
  :debugQuery=>true
20
20
  }
21
21
  opts[:global_params] = default_global_params.merge(opts[:global_params])
22
- @opts=opts
22
+ @opts = opts
23
23
  end
24
24
 
25
25
  # sets default params etc.. - could be used as a mapping hook
26
26
  # type of request should be passed in here? -> map_params(:query, {})
27
27
  def map_params(params)
28
- opts[:global_params].dup.merge(params).dup
28
+ opts[:global_params].dup.merge(params).to_mash
29
29
  end
30
30
 
31
31
  # send request to the select handler
@@ -1,6 +1,7 @@
1
1
  module RSolr::Connection::SearchExt
2
2
 
3
- def search(q_param, params={})
3
+ def search(params={})
4
+ params = params.to_mash
4
5
  if params[:fields]
5
6
  fields = params.delete :fields
6
7
  params[:fl] = fields.is_a?(Array) ? fields.join(' ') : fields
@@ -35,7 +36,7 @@ module RSolr::Connection::SearchExt
35
36
  end
36
37
  end
37
38
  #params[:qt] ||= :dismax
38
- params[:q] = build_query(q_param)
39
+ params[:q] = build_query(params[:q])
39
40
  self.query params
40
41
  end
41
42
 
@@ -3,31 +3,23 @@
3
3
  # So far, all response classes extend this
4
4
  class RSolr::Response::Base
5
5
 
6
- attr_reader :source
6
+ # the object that contains the original :body, :params, full solr :query, post :data etc.
7
+ attr_reader :input
7
8
 
8
- attr_reader :raw_response, :data, :header, :params, :status, :query_time
9
+ attr_reader :data, :header, :params, :status, :query_time
9
10
 
10
- def initialize(data)
11
- if data.is_a?(Hash) and data.has_key?(:body)
12
- @raw_response = data[:body]
13
- @data = Kernel.eval(@raw_response)
14
- @source = data
15
- else
16
- if data.is_a?(String)
17
- @raw_response = data
18
- @data = Kernel.eval(@raw_response)
19
- elsif data.is_a?(Hash)
20
- @data = data
21
- end
22
- end
23
- @header = @data['responseHeader']
24
- @params = @header['params']
25
- @status = @header['status']
26
- @query_time = @header['QTime']
11
+ def initialize(input)
12
+ input = {:body=>input} if input.is_a?(String)
13
+ @input = input
14
+ @data = Kernel.eval(input[:body]).to_mash
15
+ @header = @data[:responseHeader]
16
+ @params = @header[:params]
17
+ @status = @header[:status]
18
+ @query_time = @header[:QTime]
27
19
  end
28
20
 
29
21
  def ok?
30
- self.status==0
22
+ self.status == 0
31
23
  end
32
24
 
33
25
  end
@@ -9,14 +9,15 @@ class RSolr::Response::IndexInfo < RSolr::Response::Base
9
9
 
10
10
  def initialize(data)
11
11
  super(data)
12
- @index = @data['index']
13
- @directory = @data['directory']
14
- @has_deletions = @index['hasDeletions']
15
- @optimized = @index['optimized']
16
- @current = @index['current']
17
- @max_doc = @index['maxDoc']
18
- @num_docs = @index['numDocs']
19
- @version = @index['version']
12
+ @index = @data[:index]
13
+ @directory = @data[:directory]
14
+ # index fields
15
+ @has_deletions = @index[:hasDeletions]
16
+ @optimized = @index[:optimized]
17
+ @current = @index[:current]
18
+ @max_doc = @index[:maxDoc]
19
+ @num_docs = @index[:numDocs]
20
+ @version = @index[:version]
20
21
  end
21
22
 
22
23
  end
@@ -2,15 +2,7 @@
2
2
  module RSolr::Response::Query
3
3
 
4
4
  # module for adding helper methods to each Hash document
5
- module DocExt
6
-
7
- def self.extended(base)
8
- base.keys.each do |k,v|
9
- base.instance_eval <<-EOF
10
- def #{k}; self['#{k.to_s}']; end
11
- EOF
12
- end
13
- end
5
+ class Doc < Mash
14
6
 
15
7
  # Helper method to check if value/multi-values exist for a given key.
16
8
  # The value can be a string, or a RegExp
@@ -20,7 +12,7 @@ module RSolr::Response::Query
20
12
  # doc.has?(:id, 'h009', /^u/i)
21
13
  def has?(k, *values)
22
14
  return if self[k].nil?
23
- return true if self.has_key?(k) and values.empty?
15
+ return true if self.key?(k) and values.empty?
24
16
  target = self[k]
25
17
  if target.is_a?(Array)
26
18
  values.each do |val|
@@ -30,7 +22,7 @@ module RSolr::Response::Query
30
22
  return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
31
23
  end
32
24
  end
33
-
25
+
34
26
  end
35
27
 
36
28
  # from the delsolr project -> http://github.com/avvo/delsolr/tree/master/lib/delsolr/response.rb
@@ -150,19 +142,16 @@ module RSolr::Response::Query
150
142
  include RSolr::Response::Query::Facets
151
143
 
152
144
  attr_reader :response, :docs, :num_found, :start
153
-
145
+
154
146
  alias :total :num_found
155
147
  alias :offset :start
156
-
148
+
157
149
  def initialize(data)
158
150
  super(data)
159
- @response = @data['response']
160
- @docs = @response['docs']
161
- @docs.each do |d|
162
- d.extend RSolr::Response::Query::DocExt
163
- end
164
- @num_found = @response['numFound']
165
- @start = @response['start']
151
+ @response = @data[:response]
152
+ @docs = @response[:docs].collect{ |d| Doc.new(d) }
153
+ @num_found = @response[:numFound]
154
+ @start = @response[:start]
166
155
  end
167
156
 
168
157
  end
data/lib/rsolr.rb CHANGED
@@ -3,11 +3,11 @@
3
3
  proc {|base, files|
4
4
  $: << base unless $:.include?(base) || $:.include?(File.expand_path(base))
5
5
  files.each {|f| require f}
6
- }.call(File.dirname(__FILE__), ['core_ext'])
6
+ }.call(File.dirname(__FILE__), ['core_ext', 'mash'])
7
7
 
8
8
  module RSolr
9
9
 
10
- VERSION = '0.5.7'
10
+ VERSION = '0.5.8'
11
11
 
12
12
  autoload :Message, 'rsolr/message'
13
13
  autoload :Response, 'rsolr/response'
@@ -11,7 +11,7 @@ class ConnectionDirectTest < RSolrBaseTest
11
11
  def setup
12
12
  base = File.expand_path( File.dirname(__FILE__) )
13
13
  dist = File.join(base, '..', '..', 'apache-solr')
14
- home = File.join(dist, 'example', 'rsolr')
14
+ home = File.join(dist, 'example', 'solr')
15
15
  @solr = RSolr.connect(:direct, :home_dir=>home, :dist_dir=>dist)
16
16
  @solr.delete_by_query('*:*')
17
17
  @solr.commit
@@ -62,20 +62,18 @@ module ConnectionTestMethods
62
62
  assert r.is_a?(RSolr::Response::Query::Base)
63
63
  assert_equal Array, r.docs.class
64
64
  first = r.docs.first
65
- assert first.respond_to?(:price)
66
- assert first.respond_to?(:cat)
67
- assert first.respond_to?(:id)
68
- assert first.respond_to?(:timestamp)
69
65
 
70
66
  # test the has? method
71
67
  assert first.has?('price', 1.00)
68
+ assert ! first.has?('price', 10.00)
72
69
  assert first.has?('cat', 'electronics')
73
70
  assert first.has?('cat', 'something else')
71
+ assert first.has?(:cat, 'something else')
74
72
 
75
73
  assert first.has?('cat', /something/)
76
74
 
77
75
  # has? only works with strings at this time
78
- assert_nil first.has?(:cat)
76
+ assert first.has?(:cat)
79
77
 
80
78
  assert false == first.has?('cat', /zxcv/)
81
79
  end
data/test/message_test.rb CHANGED
@@ -17,7 +17,9 @@ class MessageTest < RSolrBaseTest
17
17
  result = RSolr::Message.add({:id=>1}, :boost=>200.00) do |hash_doc, doc_xml_attrs|
18
18
  doc_xml_attrs[:boost] = 10
19
19
  end
20
- assert_equal '<add boost="200.0"><doc><field name="id" boost="10">1</field></doc></add>', result
20
+ assert result =~ /add boost="200.0"/
21
+ assert result =~ /boost="10"/
22
+ #assert_equal '<add boost="200.0"><doc><field name="id" boost="10">1</field></doc></add>', result
21
23
  end
22
24
 
23
25
  def test_delete_by_id
data/test/test_helpers.rb CHANGED
@@ -4,6 +4,10 @@ require 'test/unit'
4
4
 
5
5
  class RSolrBaseTest < Test::Unit::TestCase
6
6
 
7
+ def assert_class(expected, instance)
8
+ assert_equal expected, instance.class
9
+ end
10
+
7
11
  def default_test
8
12
 
9
13
  end
@@ -17,7 +21,7 @@ end
17
21
  #end
18
22
 
19
23
  def mock_query_response
20
- {'responseHeader'=>{
24
+ %({'responseHeader'=>{
21
25
  'status'=>0,'QTime'=>43,'params'=>{
22
26
  'q'=>'*:*','wt'=>'ruby','echoParams'=>'EXPLICIT'
23
27
  }
@@ -35,5 +39,5 @@ def mock_query_response
35
39
  {'id'=>'3007WFP','inStock'=>true,'includes'=>'USB cable','manu'=>'Dell, Inc.','name'=>'Dell Widescreen UltraSharp 3007WFP','popularity'=>6,'price'=>2199.0,'sku'=>'3007WFP','timestamp'=>'2008-11-21T17:21:55.724Z','weight'=>401.6,'cat'=>['electronics','monitor'],'spell'=>['Dell Widescreen UltraSharp 3007WFP'],'features'=>['30" TFT active matrix LCD, 2560 x 1600, .25mm dot pitch, 700:1 contrast']},
36
40
  {'id'=>'VA902B','inStock'=>true,'manu'=>'ViewSonic Corp.','name'=>'ViewSonic VA902B - flat panel display - TFT - 19"','popularity'=>6,'price'=>279.95,'sku'=>'VA902B','timestamp'=>'2008-11-21T17:21:55.734Z','weight'=>190.4,'cat'=>['electronics','monitor'],'spell'=>['ViewSonic VA902B - flat panel display - TFT - 19"'],'features'=>['19" TFT active matrix LCD, 8ms response time, 1280 x 1024 native resolution']}]
37
41
  }
38
- }
42
+ })
39
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mwmitchell-rsolr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.5.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Mitchell
@@ -36,6 +36,7 @@ files:
36
36
  - examples/http.rb
37
37
  - examples/direct.rb
38
38
  - lib/core_ext.rb
39
+ - lib/mash.rb
39
40
  - lib/rsolr.rb
40
41
  - lib/rsolr/connection/adapter/common_methods.rb
41
42
  - lib/rsolr/connection/adapter/direct.rb
@@ -1,17 +0,0 @@
1
- raise 'Not yet implemented!'
2
-
3
- module SearchExtTestMethods
4
-
5
- def test_facet_response_methods
6
- @response.facets
7
- @response.facet_fields
8
- @response.facet_queries
9
- @response.facet_fields_by_hash
10
- @response.facet_field(:feed_language_facet)
11
- @response.facet_field_values(:feed_language_facet)
12
- @response.facet_field_by_hash(:feed_language_facet)
13
- @response.facet_field_by_hash(:feed_language_facet)
14
- @response.facet_field_count(:feed_title_facet, 'ScienceDaily: Latest Science News')
15
- end
16
-
17
- end
@@ -1,58 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helpers')
2
-
3
- class PaginationTest < RSolrBaseTest
4
-
5
- def create_response(params={})
6
- response = RSolr::Response::Query::Base.new(mock_query_response)
7
- response.params.merge! params
8
- response
9
- end
10
-
11
- # test the Solr::Connection pagination methods
12
- def test_connection_calculate_start
13
- dummy_connection = RSolr::Connection::Base.new(nil)
14
- assert_equal 15, dummy_connection.send(:calculate_start, 2, 15)
15
- assert_equal 450, dummy_connection.send(:calculate_start, 10, 50)
16
- assert_equal 0, dummy_connection.send(:calculate_start, 0, 50)
17
- end
18
-
19
- def test_connection_modify_params_for_pagination
20
- dummy_connection = RSolr::Connection::Base.new(nil)
21
- p = dummy_connection.send(:modify_params_for_pagination, {:page=>1})
22
- assert_equal 0, p[:start]
23
- assert_equal 10, p[:rows]
24
- #
25
- p = dummy_connection.send(:modify_params_for_pagination, {:page=>10, :per_page=>100})
26
- assert_equal 900, p[:start]
27
- assert_equal 100, p[:rows]
28
- end
29
-
30
- def test_math
31
- response = create_response({'rows'=>5})
32
- assert_equal response.params['rows'], response.per_page
33
- assert_equal 26, response.total
34
- assert_equal 1, response.current_page
35
- assert_equal 6, response.total_pages
36
-
37
- # now switch the rows (per_page)
38
- # total and current page should remain the same value
39
- # page_count should change
40
-
41
- response = create_response({'rows'=>2})
42
- assert_equal response.params['rows'], response.per_page
43
- assert_equal 26, response.total
44
- assert_equal 1, response.current_page
45
- assert_equal 13, response.total_pages
46
-
47
- # now switch the start
48
-
49
- response = create_response({'rows'=>3})
50
- response.instance_variable_set '@start', 4
51
- assert_equal response.params['rows'], response.per_page
52
- assert_equal 26, response.total
53
- # 2 per page, currently on the 10th item
54
- assert_equal 2, response.current_page
55
- assert_equal 9, response.total_pages
56
- end
57
-
58
- end