datastax_rails 1.0.5
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +62 -0
- data/Rakefile +34 -0
- data/config/schema.xml +266 -0
- data/config/schema.xml.erb +70 -0
- data/config/solrconfig.xml +1564 -0
- data/config/stopwords.txt +58 -0
- data/lib/datastax_rails/associations/association.rb +224 -0
- data/lib/datastax_rails/associations/association_scope.rb +25 -0
- data/lib/datastax_rails/associations/belongs_to_association.rb +64 -0
- data/lib/datastax_rails/associations/builder/association.rb +56 -0
- data/lib/datastax_rails/associations/builder/belongs_to.rb +30 -0
- data/lib/datastax_rails/associations/builder/collection_association.rb +48 -0
- data/lib/datastax_rails/associations/builder/has_and_belongs_to_many.rb +36 -0
- data/lib/datastax_rails/associations/builder/has_many.rb +54 -0
- data/lib/datastax_rails/associations/builder/has_one.rb +52 -0
- data/lib/datastax_rails/associations/builder/singular_association.rb +56 -0
- data/lib/datastax_rails/associations/collection_association.rb +274 -0
- data/lib/datastax_rails/associations/collection_proxy.rb +118 -0
- data/lib/datastax_rails/associations/has_and_belongs_to_many_association.rb +44 -0
- data/lib/datastax_rails/associations/has_many_association.rb +58 -0
- data/lib/datastax_rails/associations/has_one_association.rb +68 -0
- data/lib/datastax_rails/associations/singular_association.rb +58 -0
- data/lib/datastax_rails/associations.rb +86 -0
- data/lib/datastax_rails/attribute_methods/definition.rb +20 -0
- data/lib/datastax_rails/attribute_methods/dirty.rb +43 -0
- data/lib/datastax_rails/attribute_methods/typecasting.rb +50 -0
- data/lib/datastax_rails/attribute_methods.rb +104 -0
- data/lib/datastax_rails/base.rb +587 -0
- data/lib/datastax_rails/batches.rb +35 -0
- data/lib/datastax_rails/callbacks.rb +37 -0
- data/lib/datastax_rails/collection.rb +9 -0
- data/lib/datastax_rails/connection.rb +21 -0
- data/lib/datastax_rails/consistency.rb +33 -0
- data/lib/datastax_rails/cql/base.rb +15 -0
- data/lib/datastax_rails/cql/column_family.rb +38 -0
- data/lib/datastax_rails/cql/consistency.rb +13 -0
- data/lib/datastax_rails/cql/create_column_family.rb +63 -0
- data/lib/datastax_rails/cql/create_keyspace.rb +30 -0
- data/lib/datastax_rails/cql/delete.rb +41 -0
- data/lib/datastax_rails/cql/drop_column_family.rb +13 -0
- data/lib/datastax_rails/cql/drop_keyspace.rb +13 -0
- data/lib/datastax_rails/cql/insert.rb +53 -0
- data/lib/datastax_rails/cql/select.rb +51 -0
- data/lib/datastax_rails/cql/truncate.rb +13 -0
- data/lib/datastax_rails/cql/update.rb +68 -0
- data/lib/datastax_rails/cql/use_keyspace.rb +13 -0
- data/lib/datastax_rails/cql.rb +25 -0
- data/lib/datastax_rails/cursor.rb +90 -0
- data/lib/datastax_rails/errors.rb +16 -0
- data/lib/datastax_rails/identity/abstract_key_factory.rb +26 -0
- data/lib/datastax_rails/identity/custom_key_factory.rb +36 -0
- data/lib/datastax_rails/identity/hashed_natural_key_factory.rb +10 -0
- data/lib/datastax_rails/identity/natural_key_factory.rb +37 -0
- data/lib/datastax_rails/identity/uuid_key_factory.rb +23 -0
- data/lib/datastax_rails/identity.rb +53 -0
- data/lib/datastax_rails/log_subscriber.rb +37 -0
- data/lib/datastax_rails/migrations/migration.rb +15 -0
- data/lib/datastax_rails/migrations.rb +36 -0
- data/lib/datastax_rails/mocking.rb +15 -0
- data/lib/datastax_rails/persistence.rb +133 -0
- data/lib/datastax_rails/railtie.rb +20 -0
- data/lib/datastax_rails/reflection.rb +472 -0
- data/lib/datastax_rails/relation/finder_methods.rb +184 -0
- data/lib/datastax_rails/relation/modification_methods.rb +80 -0
- data/lib/datastax_rails/relation/search_methods.rb +349 -0
- data/lib/datastax_rails/relation/spawn_methods.rb +107 -0
- data/lib/datastax_rails/relation.rb +393 -0
- data/lib/datastax_rails/schema/migration.rb +106 -0
- data/lib/datastax_rails/schema/migration_proxy.rb +25 -0
- data/lib/datastax_rails/schema/migrator.rb +212 -0
- data/lib/datastax_rails/schema.rb +37 -0
- data/lib/datastax_rails/scoping.rb +394 -0
- data/lib/datastax_rails/serialization.rb +6 -0
- data/lib/datastax_rails/tasks/column_family.rb +162 -0
- data/lib/datastax_rails/tasks/ds.rake +63 -0
- data/lib/datastax_rails/tasks/keyspace.rb +57 -0
- data/lib/datastax_rails/timestamps.rb +19 -0
- data/lib/datastax_rails/type.rb +16 -0
- data/lib/datastax_rails/types/array_type.rb +77 -0
- data/lib/datastax_rails/types/base_type.rb +26 -0
- data/lib/datastax_rails/types/binary_type.rb +15 -0
- data/lib/datastax_rails/types/boolean_type.rb +22 -0
- data/lib/datastax_rails/types/date_type.rb +17 -0
- data/lib/datastax_rails/types/float_type.rb +18 -0
- data/lib/datastax_rails/types/integer_type.rb +18 -0
- data/lib/datastax_rails/types/string_type.rb +16 -0
- data/lib/datastax_rails/types/text_type.rb +16 -0
- data/lib/datastax_rails/types/time_type.rb +17 -0
- data/lib/datastax_rails/types.rb +9 -0
- data/lib/datastax_rails/validations/uniqueness.rb +119 -0
- data/lib/datastax_rails/validations.rb +48 -0
- data/lib/datastax_rails/version.rb +3 -0
- data/lib/datastax_rails.rb +87 -0
- data/lib/solr_no_escape.rb +28 -0
- data/spec/datastax_rails/associations/belongs_to_association_spec.rb +7 -0
- data/spec/datastax_rails/associations/has_many_association_spec.rb +37 -0
- data/spec/datastax_rails/associations_spec.rb +22 -0
- data/spec/datastax_rails/attribute_methods_spec.rb +23 -0
- data/spec/datastax_rails/base_spec.rb +15 -0
- data/spec/datastax_rails/cql/select_spec.rb +12 -0
- data/spec/datastax_rails/cql/update_spec.rb +0 -0
- data/spec/datastax_rails/relation/finder_methods_spec.rb +54 -0
- data/spec/datastax_rails/relation/modification_methods_spec.rb +41 -0
- data/spec/datastax_rails/relation/search_methods_spec.rb +117 -0
- data/spec/datastax_rails/relation/spawn_methods_spec.rb +28 -0
- data/spec/datastax_rails/relation_spec.rb +130 -0
- data/spec/datastax_rails/validations/uniqueness_spec.rb +41 -0
- data/spec/datastax_rails_spec.rb +5 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/stylesheets/application.css +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +47 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/datastax.yml +18 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +60 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config/sunspot.yml +17 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/ks/migrate/20111117224534_models.rb +20 -0
- data/spec/dummy/ks/schema.json +180 -0
- data/spec/dummy/log/development.log +298 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/test.log +20307 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/datastax_test_hook.rb +14 -0
- data/spec/support/models.rb +72 -0
- metadata +353 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
require 'rsolr'
|
|
2
|
+
|
|
3
|
+
module DatastaxRails
|
|
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]
|
|
7
|
+
|
|
8
|
+
SOLR_CHAR_RX = /([\+\!\(\)\[\]\^\"\~\:\'\=]+)/
|
|
9
|
+
|
|
10
|
+
Relation::MULTI_VALUE_METHODS.each do |m|
|
|
11
|
+
attr_accessor :"#{m}_values"
|
|
12
|
+
end
|
|
13
|
+
Relation::SINGLE_VALUE_METHODS.each do |m|
|
|
14
|
+
attr_accessor :"#{m}_value"
|
|
15
|
+
end
|
|
16
|
+
attr_accessor :create_with_value, :default_scoped
|
|
17
|
+
|
|
18
|
+
include SearchMethods
|
|
19
|
+
include ModificationMethods
|
|
20
|
+
include FinderMethods
|
|
21
|
+
include SpawnMethods
|
|
22
|
+
|
|
23
|
+
attr_reader :klass, :column_family, :loaded, :cql
|
|
24
|
+
alias :loaded? :loaded
|
|
25
|
+
alias :default_scoped? :default_scoped
|
|
26
|
+
|
|
27
|
+
def initialize(klass, column_family) #:nodoc:
|
|
28
|
+
@klass, @column_family = klass, column_family
|
|
29
|
+
@loaded = false
|
|
30
|
+
@results = []
|
|
31
|
+
@default_scoped = false
|
|
32
|
+
@cql = DatastaxRails::Cql.for_class(klass)
|
|
33
|
+
|
|
34
|
+
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
|
|
35
|
+
MULTI_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_values", [])}
|
|
36
|
+
@per_page_value = @klass.default_page_size
|
|
37
|
+
@page_value = 1
|
|
38
|
+
@use_solr_value = true
|
|
39
|
+
@consistency_value = "QUORUM"
|
|
40
|
+
@extensions = []
|
|
41
|
+
@create_with_value = {}
|
|
42
|
+
@escape_value = true
|
|
43
|
+
apply_default_scope
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns true if the two relations have the same query parameters
|
|
47
|
+
def ==(other)
|
|
48
|
+
case other
|
|
49
|
+
when Relation
|
|
50
|
+
# This is not a valid implementation. It's a placeholder till I figure out the right way.
|
|
51
|
+
MULTI_VALUE_METHODS.each do |m|
|
|
52
|
+
return false unless other.send("#{m}_values") == self.send("#{m}_values")
|
|
53
|
+
end
|
|
54
|
+
SINGLE_VALUE_METHODS.each do |m|
|
|
55
|
+
return false unless other.send("#{m}_value") == self.send("#{m}_value")
|
|
56
|
+
end
|
|
57
|
+
return true
|
|
58
|
+
when Array
|
|
59
|
+
to_a == other
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns true if there are any results given the current criteria
|
|
64
|
+
def any?
|
|
65
|
+
if block_given?
|
|
66
|
+
to_a.any? { |*block_args| yield(*block_args) }
|
|
67
|
+
else
|
|
68
|
+
!empty?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
alias :exists? :any?
|
|
72
|
+
|
|
73
|
+
# Returns the total number of entries that match the given search.
|
|
74
|
+
# This means the total number of matches regardless of page size.
|
|
75
|
+
# If the relation has not been populated yet, a limit of 1 will be
|
|
76
|
+
# placed on the query before it is executed.
|
|
77
|
+
#
|
|
78
|
+
# Compare with #size.
|
|
79
|
+
#
|
|
80
|
+
# XXX: Count via CQL is useless unless criteria has been applied.
|
|
81
|
+
# Otherwise you get everything that has ever been in the CF.
|
|
82
|
+
def count
|
|
83
|
+
@count ||= self.use_solr_value ? count_via_solr : count_via_cql
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the current page for will_paginate compatibility
|
|
87
|
+
def current_page
|
|
88
|
+
self.page_value.try(:to_i)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# current_page - 1 or nil if there is no previous page
|
|
92
|
+
def previous_page
|
|
93
|
+
current_page > 1 ? (current_page - 1) : nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# current_page + 1 or nil if there is no next page
|
|
97
|
+
def next_page
|
|
98
|
+
current_page < total_pages ? (current_page + 1) : nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Gets a default scope with no conditions or search attributes set.
|
|
102
|
+
def default_scope
|
|
103
|
+
clone.tap do |r|
|
|
104
|
+
SINGLE_VALUE_METHODS.each {|v| r.instance_variable_set(:"@#{v}_value", nil)}
|
|
105
|
+
MULTI_VALUE_METHODS.each {|v| r.instance_variable_set(:"@#{v}_values", [])}
|
|
106
|
+
apply_default_scope
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns true if there are no results given the current criteria
|
|
111
|
+
def empty?
|
|
112
|
+
return @results.empty? if loaded?
|
|
113
|
+
|
|
114
|
+
c = count
|
|
115
|
+
c.respond_to?(:zero?) ? c.zero? : c.empty?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns true if there are multiple results given the current criteria
|
|
119
|
+
def many?
|
|
120
|
+
if block_given?
|
|
121
|
+
to_a.many? { |*block_args| yield(*block_args) }
|
|
122
|
+
else
|
|
123
|
+
count > 1
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Constructs a new instance of the class this relation points to with
|
|
128
|
+
# any criteria from this relation applied
|
|
129
|
+
def new(*args, &block)
|
|
130
|
+
scoping { @klass.new(*args, &block) }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Reloads the results from cassandra or solr as appropriate
|
|
134
|
+
def reload
|
|
135
|
+
reset
|
|
136
|
+
to_a
|
|
137
|
+
self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Empties out the current results. The next call to to_a
|
|
141
|
+
# will re-run the query.
|
|
142
|
+
def reset
|
|
143
|
+
@loaded = @first = @last = @scope_for_create = @count = nil
|
|
144
|
+
@results = []
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def initialize_copy(other) #:nodoc:
|
|
148
|
+
reset
|
|
149
|
+
@search = nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def clone #:nodoc:
|
|
153
|
+
dup.tap do |r|
|
|
154
|
+
MULTI_VALUE_METHODS.each do |m|
|
|
155
|
+
r.send("#{m}_values=", Marshal.load(Marshal.dump(self.send("#{m}_values"))))
|
|
156
|
+
end
|
|
157
|
+
SINGLE_VALUE_METHODS.each do |m|
|
|
158
|
+
r.send("#{m}_value=", Marshal.load(Marshal.dump(self.send("#{m}_value")))) if self.send("#{m}_value")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns the size of the total result set for the given criteria
|
|
164
|
+
# NOTE that this takes pagination into account so will only return
|
|
165
|
+
# the number of results in the current page. DatastaxRails models
|
|
166
|
+
# can have a +default_page_size+ set which will cause them to be
|
|
167
|
+
# paginated all the time.
|
|
168
|
+
# Compare with #count
|
|
169
|
+
def size
|
|
170
|
+
return @results.size if loaded?
|
|
171
|
+
total_entries = count
|
|
172
|
+
(per_page_value && total_entries > per_page_value) ? per_page_value : total_entries
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Returns the total number of pages required to display the results
|
|
176
|
+
# given the current page size. Used by will_paginate.
|
|
177
|
+
def total_pages
|
|
178
|
+
return 1 unless @per_page_value
|
|
179
|
+
(count / @per_page_value.to_f).ceil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Actually executes the query if not already executed.
|
|
183
|
+
# Returns a standard array thus no more methods may be chained.
|
|
184
|
+
def to_a
|
|
185
|
+
return @results if loaded?
|
|
186
|
+
if use_solr_value
|
|
187
|
+
@results = query_via_solr
|
|
188
|
+
@count = @results.total_entries
|
|
189
|
+
else
|
|
190
|
+
@results = query_via_cql
|
|
191
|
+
end
|
|
192
|
+
@loaded = true
|
|
193
|
+
@results
|
|
194
|
+
end
|
|
195
|
+
alias :all :to_a
|
|
196
|
+
alias :results :to_a
|
|
197
|
+
|
|
198
|
+
# Create a new object with all of the criteria from this relation applied
|
|
199
|
+
def create(*args, &block)
|
|
200
|
+
scoping { @klass.create(*args, &block) }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Like +create+ but throws an exception on failure
|
|
204
|
+
def create!(*args, &block)
|
|
205
|
+
scoping { @klass.create!(*args, &block) }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def respond_to?(method, include_private = false) #:nodoc:
|
|
209
|
+
Array.method_defined?(method) ||
|
|
210
|
+
@klass.respond_to?(method, include_private) ||
|
|
211
|
+
super
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# NOTE: This method does not actually run a count via CQL because it only
|
|
215
|
+
# works if you run against a secondary index. So this currently just
|
|
216
|
+
# delegates to the count_via_solr method.
|
|
217
|
+
def count_via_cql
|
|
218
|
+
with_solr.count_via_solr
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Constructs a CQL query and runs it against Cassandra directly. For this to
|
|
222
|
+
# work, you need to run against either the primary key or a secondary index.
|
|
223
|
+
# For ad-hoc queries, you will have to use Solr.
|
|
224
|
+
def query_via_cql
|
|
225
|
+
select_columns = select_values.empty? ? (@klass.attribute_definitions.keys - @klass.lazy_attributes) : select_values.flatten
|
|
226
|
+
cql = @cql.select(select_columns)
|
|
227
|
+
cql.using(@consistency_value) if @consistency_value
|
|
228
|
+
@where_values.each do |wv|
|
|
229
|
+
cql.conditions(wv)
|
|
230
|
+
end
|
|
231
|
+
results = []
|
|
232
|
+
CassandraCQL::Result.new(cql.execute).fetch do |row|
|
|
233
|
+
results << @klass.instantiate(row.row.key,row.to_hash)
|
|
234
|
+
end
|
|
235
|
+
results
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Runs the query with a limit of 1 just to grab the total results attribute off
|
|
239
|
+
# the result set.
|
|
240
|
+
def count_via_solr
|
|
241
|
+
limit(1).select(:id).to_a.total_entries
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def solr_escape(str)
|
|
245
|
+
if str.is_a?(String) && escape_value
|
|
246
|
+
str.gsub(SOLR_CHAR_RX, '\\\\\1')
|
|
247
|
+
else
|
|
248
|
+
str
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Constructs a solr query to run against SOLR. At this point, only where, where_not,
|
|
253
|
+
# fulltext, order and pagination are supported. More will be added.
|
|
254
|
+
#
|
|
255
|
+
# It's also worth noting that where and where_not make use of individual filter_queries.
|
|
256
|
+
# If that's not what you want, you might be better off constructing your own fulltext
|
|
257
|
+
# query and sending that in.
|
|
258
|
+
def query_via_solr
|
|
259
|
+
filter_queries = []
|
|
260
|
+
orders = []
|
|
261
|
+
@where_values.each do |wv|
|
|
262
|
+
wv.each do |k,v|
|
|
263
|
+
# If v is blank, check that there is no value for the field in the document
|
|
264
|
+
filter_queries << (v.blank? ? "-#{k}:[* TO *]" : "#{k}:(#{solr_escape(v)})")
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
@where_not_values.each do |wnv|
|
|
269
|
+
wnv.each do |k,v|
|
|
270
|
+
# If v is blank, check for any value for the field in document
|
|
271
|
+
filter_queries << (v.blank? ? "#{k}:[* TO *]" : "-#{k}:(#{solr_escape(v)})")
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
@greater_than_values.each do |gtv|
|
|
276
|
+
gtv.each do |k,v|
|
|
277
|
+
filter_queries << "#{k}:[#{solr_escape(v)} TO *]"
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
@less_than_values.each do |ltv|
|
|
282
|
+
ltv.each do |k,v|
|
|
283
|
+
filter_queries << "#{k}:[* TO #{solr_escape(v)}]"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
@order_values.each do |ov|
|
|
288
|
+
ov.each do |k,v|
|
|
289
|
+
if(@reverse_order_value)
|
|
290
|
+
orders << "#{k} #{v == :asc ? 'desc' : 'asc'}"
|
|
291
|
+
else
|
|
292
|
+
orders << "#{k} #{v == :asc ? 'asc' : 'desc'}"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
sort = orders.join(",")
|
|
298
|
+
|
|
299
|
+
if @fulltext_values.empty?
|
|
300
|
+
q = "*:*"
|
|
301
|
+
else
|
|
302
|
+
q = @fulltext_values.collect {|ftv| "(" + ftv[:query] + ")"}.join(' AND ')
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
#TODO highlighting and fielded queries of fulltext
|
|
306
|
+
|
|
307
|
+
params = {:q => q}
|
|
308
|
+
unless sort.empty?
|
|
309
|
+
params[:sort] = sort
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
unless filter_queries.empty?
|
|
313
|
+
params[:fq] = filter_queries
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
#TODO Need to escape URL stuff (I think)
|
|
317
|
+
response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
|
|
318
|
+
results = DatastaxRails::Collection.new
|
|
319
|
+
results.total_entries = response['numFound'].to_i
|
|
320
|
+
response['docs'].each do |doc|
|
|
321
|
+
key = doc.delete('id')
|
|
322
|
+
results << @klass.instantiate(key,doc)
|
|
323
|
+
end
|
|
324
|
+
results
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def inspect(just_me = false)
|
|
328
|
+
just_me ? super() : to_a.inspect
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Scope all queries to the current scope.
|
|
332
|
+
#
|
|
333
|
+
# ==== Example
|
|
334
|
+
#
|
|
335
|
+
# Comment.where(:post_id => 1).scoping do
|
|
336
|
+
# Comment.first # SELECT * FROM comments WHERE post_id = 1
|
|
337
|
+
# end
|
|
338
|
+
#
|
|
339
|
+
# Please check unscoped if you want to remove all previous scopes (including
|
|
340
|
+
# the default_scope) during the execution of a block.
|
|
341
|
+
def scoping
|
|
342
|
+
@klass.send(:with_scope, self, :overwrite) { yield }
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def where_values_hash #:nodoc:
|
|
346
|
+
where_values.inject({}) { |values,v| values.merge(v) }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def scope_for_create #:nodoc:
|
|
350
|
+
@scope_for_create ||= where_values_hash.merge(create_with_value)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Sends a commit message to SOLR
|
|
354
|
+
def commit_solr
|
|
355
|
+
rsolr.commit :commit_attributes => {}
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Everything that gets indexed into solr is downcased as part of the analysis phase.
|
|
359
|
+
# Normally, this is done to the query as well, but if your query includes wildcards
|
|
360
|
+
# then analysis isn't performed. This means that the query does not get downcased.
|
|
361
|
+
# We therefore need to perform the downcasing ourselves. This does it while still
|
|
362
|
+
# leaving boolean operations (AND, OR, NOT) upcased.
|
|
363
|
+
def self.downcase_query(value)
|
|
364
|
+
if(value.is_a?(String))
|
|
365
|
+
value.split(/\bAND\b/).collect do |a|
|
|
366
|
+
a.split(/\bOR\b/).collect do |o|
|
|
367
|
+
o.split(/\bNOT\b/).collect do |n|
|
|
368
|
+
n.downcase
|
|
369
|
+
end.join("NOT")
|
|
370
|
+
end.join("OR")
|
|
371
|
+
end.join("AND")
|
|
372
|
+
else
|
|
373
|
+
value
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
protected
|
|
378
|
+
|
|
379
|
+
def method_missing(method, *args, &block) #:nodoc:
|
|
380
|
+
if Array.method_defined?(method)
|
|
381
|
+
to_a.send(method, *args, &block)
|
|
382
|
+
elsif @klass.respond_to?(method)
|
|
383
|
+
scoping { @klass.send(method, *args, &block) }
|
|
384
|
+
else
|
|
385
|
+
super
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def rsolr #:nodoc:
|
|
390
|
+
@rsolr ||= RSolr.connect :url => "#{DatastaxRails::Base.config[:solr][:url]}/#{DatastaxRails::Base.connection.keyspace}.#{@klass.column_family}"
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module DatastaxRails
|
|
2
|
+
module Schema
|
|
3
|
+
|
|
4
|
+
class Migration
|
|
5
|
+
|
|
6
|
+
@@verbose = true
|
|
7
|
+
cattr_accessor :verbose
|
|
8
|
+
|
|
9
|
+
# Returns the raw connection to Cassandra
|
|
10
|
+
def self.connection
|
|
11
|
+
DatastaxRails::Base.connection
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.migrate(direction)
|
|
15
|
+
return unless respond_to?(direction)
|
|
16
|
+
|
|
17
|
+
case direction
|
|
18
|
+
when :up then announce "migrating"
|
|
19
|
+
when :down then announce "reverting"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
result = nil
|
|
23
|
+
time = Benchmark.measure { result = send("#{direction}") }
|
|
24
|
+
|
|
25
|
+
case direction
|
|
26
|
+
when :up then announce "migrated (%.4fs)" % time.real; write
|
|
27
|
+
when :down then announce "reverted (%.4fs)" % time.real; write
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Creates a new column family with the given name. Column family configurations can be set within
|
|
34
|
+
# a block like this:
|
|
35
|
+
#
|
|
36
|
+
# create_column_family(:users) do |cf|
|
|
37
|
+
# cf.comment = 'Users column family'
|
|
38
|
+
# cf.comparator_type = 'TimeUUIDType'
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# A complete list of available configuration settings is here:
|
|
42
|
+
#
|
|
43
|
+
# http://github.com/fauna/cassandra/blob/master/vendor/0.7/gen-rb/cassandra_types.rb
|
|
44
|
+
#
|
|
45
|
+
# Scroll down to the CfDef definition.
|
|
46
|
+
def self.create_column_family(name, &block)
|
|
47
|
+
say_with_time("create_column_family #{name}") do
|
|
48
|
+
column_family_tasks.create(name, &block)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Drops the given column family
|
|
53
|
+
def self.drop_column_family(name)
|
|
54
|
+
say_with_time("drop_column_family #{name}") do
|
|
55
|
+
column_family_tasks.drop(name)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Renames the column family from the old name to the new name
|
|
60
|
+
def self.rename_column_family(old_name, new_name)
|
|
61
|
+
say_with_time("rename_column_family #{name}") do
|
|
62
|
+
column_family_tasks.rename(old_name, new_name)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.write(text="")
|
|
67
|
+
puts(text) if verbose
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.announce(message)
|
|
71
|
+
version = defined?(@version) ? @version : nil
|
|
72
|
+
|
|
73
|
+
text = "#{version} #{name}: #{message}"
|
|
74
|
+
length = [0, 75 - text.length].max
|
|
75
|
+
write "== %s %s" % [text, "=" * length]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.say(message, subitem=false)
|
|
79
|
+
write "#{subitem ? " ->" : "--"} #{message}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.say_with_time(message)
|
|
83
|
+
say(message)
|
|
84
|
+
result = nil
|
|
85
|
+
time = Benchmark.measure { result = yield }
|
|
86
|
+
say "%.4fs" % time.real, :subitem
|
|
87
|
+
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.suppress_messages
|
|
92
|
+
save, self.verbose = verbose, false
|
|
93
|
+
yield
|
|
94
|
+
ensure
|
|
95
|
+
self.verbose = save
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def self.column_family_tasks
|
|
101
|
+
Tasks::ColumnFamily.new(DatastaxRails::Base.connection.keyspace)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module DatastaxRails
|
|
2
|
+
module Schema
|
|
3
|
+
|
|
4
|
+
# MigrationProxy is used to defer loading of the actual migration classes
|
|
5
|
+
# until they are needed
|
|
6
|
+
class MigrationProxy
|
|
7
|
+
|
|
8
|
+
attr_accessor :name, :version, :filename
|
|
9
|
+
|
|
10
|
+
delegate :migrate, :announce, :write, :to=>:migration
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def migration
|
|
15
|
+
@migration ||= load_migration
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load_migration
|
|
19
|
+
require(File.expand_path(filename))
|
|
20
|
+
name.constantize
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|