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 +4 -0
- data/LICENSE +56 -190
- data/README.rdoc +35 -24
- data/examples/direct.rb +2 -2
- data/examples/http.rb +2 -2
- data/lib/core_ext.rb +8 -0
- data/lib/mash.rb +149 -0
- data/lib/rsolr/connection/adapter/common_methods.rb +0 -1
- data/lib/rsolr/connection/base.rb +2 -2
- data/lib/rsolr/connection/search_ext.rb +3 -2
- data/lib/rsolr/response/base.rb +12 -20
- data/lib/rsolr/response/index_info.rb +9 -8
- data/lib/rsolr/response/query.rb +9 -20
- data/lib/rsolr.rb +2 -2
- data/test/connection/direct_test.rb +1 -1
- data/test/connection/test_methods.rb +3 -5
- data/test/message_test.rb +3 -1
- data/test/test_helpers.rb +6 -2
- metadata +2 -1
- data/test/connection/search_ext_test_methods.rb +0 -17
- data/test/pagination_test.rb +0 -58
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
|
-
|
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
|
-
=
|
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
|
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 =
|
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 =
|
29
|
+
solr = RSolr.connect(:direct, :home_dir=>'/path/to/solr/home', :dist_dir=>'/path/to/solr/distribution')
|
19
30
|
|
20
|
-
You can set
|
31
|
+
You can set RSolr params that will be sent on every request:
|
21
32
|
|
22
|
-
solr =
|
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
|
-
|
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
|
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
|
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.
|
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
|
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 =
|
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,
|
164
|
+
There is currently one built in mapper, RSolr::Mapper::RSS. Here's an example usage:
|
154
165
|
|
155
|
-
mapper =
|
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
|
-
|
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 =
|
171
|
-
i =
|
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
|
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
|
-
|
183
|
-
|
193
|
+
RSolr::Connection::Adapter::HTTP.client_adapter = :curb
|
194
|
+
|
184
195
|
Example of using the HTTP client only:
|
185
196
|
|
186
|
-
hclient =
|
187
|
-
hclient =
|
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?(
|
18
|
-
puts doc
|
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?(
|
14
|
-
puts doc
|
13
|
+
if doc.has?(:timestamp)
|
14
|
+
puts doc[:timestamp]
|
15
15
|
end
|
16
16
|
end
|
data/lib/core_ext.rb
CHANGED
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).
|
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(
|
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(
|
39
|
+
params[:q] = build_query(params[:q])
|
39
40
|
self.query params
|
40
41
|
end
|
41
42
|
|
data/lib/rsolr/response/base.rb
CHANGED
@@ -3,31 +3,23 @@
|
|
3
3
|
# So far, all response classes extend this
|
4
4
|
class RSolr::Response::Base
|
5
5
|
|
6
|
-
|
6
|
+
# the object that contains the original :body, :params, full solr :query, post :data etc.
|
7
|
+
attr_reader :input
|
7
8
|
|
8
|
-
attr_reader :
|
9
|
+
attr_reader :data, :header, :params, :status, :query_time
|
9
10
|
|
10
|
-
def initialize(
|
11
|
-
if
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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[
|
13
|
-
@directory = @data[
|
14
|
-
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
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
|
data/lib/rsolr/response/query.rb
CHANGED
@@ -2,15 +2,7 @@
|
|
2
2
|
module RSolr::Response::Query
|
3
3
|
|
4
4
|
# module for adding helper methods to each Hash document
|
5
|
-
|
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.
|
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[
|
160
|
-
@docs = @response[
|
161
|
-
@
|
162
|
-
|
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.
|
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', '
|
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
|
-
|
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
|
-
|
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.
|
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
|
data/test/pagination_test.rb
DELETED
@@ -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
|