datastax_rails 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|