datastax_rails 1.0.14.10 → 1.0.15

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.
@@ -0,0 +1,9 @@
1
+ module DatastaxRails
2
+ class GroupedCollection < Hash
3
+ attr_accessor :total_entries, :total_groups, :total_for_all
4
+
5
+ def inspect
6
+ "<DatastaxRails::GroupedCollection##{object_id} contents: #{super} matching_records #{total_for_all}>"
7
+ end
8
+ end
9
+ end
@@ -96,15 +96,30 @@ module DatastaxRails
96
96
  end
97
97
  end
98
98
 
99
- # Group results by one or more attributes only returning the top result
100
- # for each group.
99
+ # Group results by a given attribute only returning the top results
100
+ # for each group. In Lucene, this is often referred to as Field Collapsing.
101
+ #
102
+ # This modifies the behavior of pagination. When using a group, +per_page+ will
103
+ # specify the number of results returned *for each group*. In addition, +page+
104
+ # will move all groups forward by one page possibly resulting in some groups
105
+ # getting dropped off if they have fewer matching entires than others.
106
+ #
107
+ # When grouping is being used, the sort values will be used to sort results within
108
+ # a given group. Any sorting of the groups themselves will need to be handled
109
+ # after-the-fact as the groups are returned as hash of Collection objects.
110
+ #
111
+ # Because SOLR is doing the grouping work, we can only group on single-valued
112
+ # fields (i.e., not +text+ or +array+ attributes). In the future, SOLR may
113
+ # support grouping on multi-valued fields.
114
+ #
115
+ # NOTE: Group names will be lower-cased
101
116
  #
102
117
  # Model.group(:program_id)
103
- def group(*attrs)
104
- return self if attrs.blank?
118
+ def group(attribute)
119
+ return self if attribute.blank?
105
120
 
106
121
  clone.tap do |r|
107
- r.group_values += attrs.flatten
122
+ r.group_value = attribute
108
123
  end
109
124
  end
110
125
 
@@ -2,8 +2,8 @@ require 'rsolr'
2
2
 
3
3
  module DatastaxRails
4
4
  class Relation
5
- MULTI_VALUE_METHODS = [:group, :order, :where, :where_not, :fulltext, :greater_than, :less_than, :select]
6
- SINGLE_VALUE_METHODS = [:page, :per_page, :reverse_order, :query_parser, :consistency, :ttl, :use_solr, :escape]
5
+ MULTI_VALUE_METHODS = [:order, :where, :where_not, :fulltext, :greater_than, :less_than, :select]
6
+ SINGLE_VALUE_METHODS = [:page, :per_page, :reverse_order, :query_parser, :consistency, :ttl, :use_solr, :escape, :group]
7
7
 
8
8
  SOLR_CHAR_RX = /([\+\!\(\)\[\]\^\"\~\:\'\=]+)/
9
9
 
@@ -74,6 +74,9 @@ module DatastaxRails
74
74
  # If the relation has not been populated yet, a limit of 1 will be
75
75
  # placed on the query before it is executed.
76
76
  #
77
+ # For a grouped query, this still returns the total number of
78
+ # matching documents
79
+ #
77
80
  # Compare with #size.
78
81
  #
79
82
  # XXX: Count via CQL is useless unless criteria has been applied.
@@ -164,9 +167,12 @@ module DatastaxRails
164
167
  # the number of results in the current page. DatastaxRails models
165
168
  # can have a +default_page_size+ set which will cause them to be
166
169
  # paginated all the time.
170
+ #
171
+ # For a grouped query, this returns the size of the largest group.
172
+ #
167
173
  # Compare with #count
168
174
  def size
169
- return @results.size if loaded?
175
+ return @results.size if loaded? && !@group_value
170
176
  total_entries = count
171
177
  (per_page_value && total_entries > per_page_value) ? per_page_value : total_entries
172
178
  end
@@ -184,7 +190,7 @@ module DatastaxRails
184
190
  return @results if loaded?
185
191
  if use_solr_value
186
192
  @results = query_via_solr
187
- @count = @results.total_entries
193
+ @count = @group_value ? @results.total_for_all : @results.total_entries
188
194
  else
189
195
  @results = query_via_cql
190
196
  end
@@ -245,7 +251,8 @@ module DatastaxRails
245
251
  # Runs the query with a limit of 1 just to grab the total results attribute off
246
252
  # the result set.
247
253
  def count_via_solr
248
- limit(1).select(:id).to_a.total_entries
254
+ results = limit(1).select(:id).to_a
255
+ @group_value ? results.total_for_all : results.total_entries
249
256
  end
250
257
 
251
258
  # Escapes values that might otherwise mess up the URL or confuse SOLR.
@@ -325,24 +332,44 @@ module DatastaxRails
325
332
 
326
333
  select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
327
334
 
328
- #TODO Need to escape URL stuff (I think)
329
- response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
335
+ if(@group_value)
336
+ results = DatastaxRails::GroupedCollection.new
337
+ params[:group] = 'true'
338
+ params['group.field'] = @group_value
339
+ params['group.limit'] = @per_page_value
340
+ params['group.offset'] = (@page_value - 1) * @per_page_value
341
+ params['group.ngroups'] = 'true'
342
+ response = rsolr.post('select', :params => params)["grouped"][@group_value.to_s]
343
+ results.total_groups = response['ngroups'].to_i
344
+ results.total_for_all = response['matches'].to_i
345
+ results.total_entries = 0
346
+ response['groups'].each do |group|
347
+ results[group['groupValue']] = parse_docs(group['doclist'], select_columns)
348
+ results.total_entries = results[group['groupValue']].total_entries if results[group['groupValue']].total_entries > results.total_entries
349
+ end
350
+ else
351
+ response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
352
+ results = parse_docs(response, select_columns)
353
+ end
354
+ results
355
+ end
356
+
357
+
358
+ def parse_docs(response, select_columns)
330
359
  results = DatastaxRails::Collection.new
331
360
  results.total_entries = response['numFound'].to_i
332
- if @consistency_value
333
- response['docs'].each do |doc|
334
- id = doc['id']
361
+ response['docs'].each do |doc|
362
+ id = doc['id']
363
+ if(@consistency_value)
335
364
  obj = @klass.with_cassandra.consistency(@consistency_value).find_by_id(id)
336
365
  results << obj if obj
337
- end
338
- else
339
- response['docs'].each do |doc|
340
- key = doc.delete('id')
341
- results << @klass.instantiate(key,doc, select_columns)
366
+ else
367
+ results << @klass.instantiate(id, doc, select_columns)
342
368
  end
343
369
  end
344
370
  results
345
371
  end
372
+ protected(:parse_docs)
346
373
 
347
374
  def inspect(just_me = false)
348
375
  just_me ? super() : to_a.inspect
@@ -109,6 +109,7 @@ module DatastaxRails
109
109
  solr_url = "#{DatastaxRails::Base.solr_base_url}/resource/#{DatastaxRails::Base.config[:keyspace]}.#{model.column_family}"
110
110
  uri = URI.parse(solr_url)
111
111
  Net::HTTP.start(uri.host, uri.port) do |http|
112
+ http.read_timeout(300)
112
113
  if force || solrconfig_digest != sm_digests['solrconfig']
113
114
  puts "Posting Solr Config file to '#{solr_url}/solrconfig.xml'"
114
115
  http.post(uri.path+"/solrconfig.xml", solrconfig)
@@ -1,4 +1,4 @@
1
1
  module DatastaxRails
2
2
  # The current version of the gem
3
- VERSION = "1.0.14.10"
3
+ VERSION = "1.0.15"
4
4
  end
@@ -15,6 +15,7 @@ module DatastaxRails
15
15
  autoload :Collection
16
16
  autoload :Connection
17
17
  autoload :Cql
18
+ autoload :GroupedCollection
18
19
  autoload :Identity
19
20
  autoload :Migrations
20
21
  autoload :Persistence
@@ -8,7 +8,7 @@ describe DatastaxRails::Cql::Update do
8
8
  it "should generate valid CQL" do
9
9
  cql = DatastaxRails::Cql::Update.new(@model_class, "12345")
10
10
  cql.using(DatastaxRails::Cql::Consistency::QUORUM).columns(:name => 'John', :age => '23')
11
- cql.to_cql.should == "update users using consistency QUORUM SET name = 'John', age = '23' WHERE KEY IN ('12345')"
11
+ cql.to_cql.should match(/update users using consistency QUORUM SET (name = 'John', age = '23'|age = '23', name = 'John') WHERE KEY IN \('12345'\)/)
12
12
  end
13
13
 
14
14
  it_has_behavior "default_consistency"
@@ -127,4 +127,32 @@ describe DatastaxRails::Relation do
127
127
  relation.total_pages.should == 4
128
128
  end
129
129
  end
130
+
131
+ describe "grouped queries" do
132
+ before(:each) do
133
+ Person.create(:name => 'John', :nickname => 'J')
134
+ Person.create(:name => 'Jason', :nickname => 'J')
135
+ Person.create(:name => 'James', :nickname => 'J')
136
+ Person.create(:name => 'Kathrine', :nickname => 'Kat')
137
+ Person.create(:name => 'Kathy', :nickname => 'Kat')
138
+ Person.create(:name => 'Steven', :nickname => 'Steve')
139
+ Person.commit_solr
140
+ end
141
+
142
+ it "should return matching documents grouped by an attribute" do
143
+ results = Person.group(:nickname).all
144
+ results['j'].should have(3).items
145
+ results['kat'].should have(2).items
146
+ results['steve'].should have(1).item
147
+ end
148
+
149
+ it "should return total_entires as the highest value of any group" do
150
+ results = Person.group(:nickname).all
151
+ results.total_entries.should eq(3)
152
+ end
153
+
154
+ it "should still return a total count when using the count method" do
155
+ results = Person.group(:nickname).count.should eq(6)
156
+ end
157
+ end
130
158
  end