datastax_rails 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +62 -0
  3. data/Rakefile +34 -0
  4. data/config/schema.xml +266 -0
  5. data/config/schema.xml.erb +70 -0
  6. data/config/solrconfig.xml +1564 -0
  7. data/config/stopwords.txt +58 -0
  8. data/lib/datastax_rails/associations/association.rb +224 -0
  9. data/lib/datastax_rails/associations/association_scope.rb +25 -0
  10. data/lib/datastax_rails/associations/belongs_to_association.rb +64 -0
  11. data/lib/datastax_rails/associations/builder/association.rb +56 -0
  12. data/lib/datastax_rails/associations/builder/belongs_to.rb +30 -0
  13. data/lib/datastax_rails/associations/builder/collection_association.rb +48 -0
  14. data/lib/datastax_rails/associations/builder/has_and_belongs_to_many.rb +36 -0
  15. data/lib/datastax_rails/associations/builder/has_many.rb +54 -0
  16. data/lib/datastax_rails/associations/builder/has_one.rb +52 -0
  17. data/lib/datastax_rails/associations/builder/singular_association.rb +56 -0
  18. data/lib/datastax_rails/associations/collection_association.rb +274 -0
  19. data/lib/datastax_rails/associations/collection_proxy.rb +118 -0
  20. data/lib/datastax_rails/associations/has_and_belongs_to_many_association.rb +44 -0
  21. data/lib/datastax_rails/associations/has_many_association.rb +58 -0
  22. data/lib/datastax_rails/associations/has_one_association.rb +68 -0
  23. data/lib/datastax_rails/associations/singular_association.rb +58 -0
  24. data/lib/datastax_rails/associations.rb +86 -0
  25. data/lib/datastax_rails/attribute_methods/definition.rb +20 -0
  26. data/lib/datastax_rails/attribute_methods/dirty.rb +43 -0
  27. data/lib/datastax_rails/attribute_methods/typecasting.rb +50 -0
  28. data/lib/datastax_rails/attribute_methods.rb +104 -0
  29. data/lib/datastax_rails/base.rb +587 -0
  30. data/lib/datastax_rails/batches.rb +35 -0
  31. data/lib/datastax_rails/callbacks.rb +37 -0
  32. data/lib/datastax_rails/collection.rb +9 -0
  33. data/lib/datastax_rails/connection.rb +21 -0
  34. data/lib/datastax_rails/consistency.rb +33 -0
  35. data/lib/datastax_rails/cql/base.rb +15 -0
  36. data/lib/datastax_rails/cql/column_family.rb +38 -0
  37. data/lib/datastax_rails/cql/consistency.rb +13 -0
  38. data/lib/datastax_rails/cql/create_column_family.rb +63 -0
  39. data/lib/datastax_rails/cql/create_keyspace.rb +30 -0
  40. data/lib/datastax_rails/cql/delete.rb +41 -0
  41. data/lib/datastax_rails/cql/drop_column_family.rb +13 -0
  42. data/lib/datastax_rails/cql/drop_keyspace.rb +13 -0
  43. data/lib/datastax_rails/cql/insert.rb +53 -0
  44. data/lib/datastax_rails/cql/select.rb +51 -0
  45. data/lib/datastax_rails/cql/truncate.rb +13 -0
  46. data/lib/datastax_rails/cql/update.rb +68 -0
  47. data/lib/datastax_rails/cql/use_keyspace.rb +13 -0
  48. data/lib/datastax_rails/cql.rb +25 -0
  49. data/lib/datastax_rails/cursor.rb +90 -0
  50. data/lib/datastax_rails/errors.rb +16 -0
  51. data/lib/datastax_rails/identity/abstract_key_factory.rb +26 -0
  52. data/lib/datastax_rails/identity/custom_key_factory.rb +36 -0
  53. data/lib/datastax_rails/identity/hashed_natural_key_factory.rb +10 -0
  54. data/lib/datastax_rails/identity/natural_key_factory.rb +37 -0
  55. data/lib/datastax_rails/identity/uuid_key_factory.rb +23 -0
  56. data/lib/datastax_rails/identity.rb +53 -0
  57. data/lib/datastax_rails/log_subscriber.rb +37 -0
  58. data/lib/datastax_rails/migrations/migration.rb +15 -0
  59. data/lib/datastax_rails/migrations.rb +36 -0
  60. data/lib/datastax_rails/mocking.rb +15 -0
  61. data/lib/datastax_rails/persistence.rb +133 -0
  62. data/lib/datastax_rails/railtie.rb +20 -0
  63. data/lib/datastax_rails/reflection.rb +472 -0
  64. data/lib/datastax_rails/relation/finder_methods.rb +184 -0
  65. data/lib/datastax_rails/relation/modification_methods.rb +80 -0
  66. data/lib/datastax_rails/relation/search_methods.rb +349 -0
  67. data/lib/datastax_rails/relation/spawn_methods.rb +107 -0
  68. data/lib/datastax_rails/relation.rb +393 -0
  69. data/lib/datastax_rails/schema/migration.rb +106 -0
  70. data/lib/datastax_rails/schema/migration_proxy.rb +25 -0
  71. data/lib/datastax_rails/schema/migrator.rb +212 -0
  72. data/lib/datastax_rails/schema.rb +37 -0
  73. data/lib/datastax_rails/scoping.rb +394 -0
  74. data/lib/datastax_rails/serialization.rb +6 -0
  75. data/lib/datastax_rails/tasks/column_family.rb +162 -0
  76. data/lib/datastax_rails/tasks/ds.rake +63 -0
  77. data/lib/datastax_rails/tasks/keyspace.rb +57 -0
  78. data/lib/datastax_rails/timestamps.rb +19 -0
  79. data/lib/datastax_rails/type.rb +16 -0
  80. data/lib/datastax_rails/types/array_type.rb +77 -0
  81. data/lib/datastax_rails/types/base_type.rb +26 -0
  82. data/lib/datastax_rails/types/binary_type.rb +15 -0
  83. data/lib/datastax_rails/types/boolean_type.rb +22 -0
  84. data/lib/datastax_rails/types/date_type.rb +17 -0
  85. data/lib/datastax_rails/types/float_type.rb +18 -0
  86. data/lib/datastax_rails/types/integer_type.rb +18 -0
  87. data/lib/datastax_rails/types/string_type.rb +16 -0
  88. data/lib/datastax_rails/types/text_type.rb +16 -0
  89. data/lib/datastax_rails/types/time_type.rb +17 -0
  90. data/lib/datastax_rails/types.rb +9 -0
  91. data/lib/datastax_rails/validations/uniqueness.rb +119 -0
  92. data/lib/datastax_rails/validations.rb +48 -0
  93. data/lib/datastax_rails/version.rb +3 -0
  94. data/lib/datastax_rails.rb +87 -0
  95. data/lib/solr_no_escape.rb +28 -0
  96. data/spec/datastax_rails/associations/belongs_to_association_spec.rb +7 -0
  97. data/spec/datastax_rails/associations/has_many_association_spec.rb +37 -0
  98. data/spec/datastax_rails/associations_spec.rb +22 -0
  99. data/spec/datastax_rails/attribute_methods_spec.rb +23 -0
  100. data/spec/datastax_rails/base_spec.rb +15 -0
  101. data/spec/datastax_rails/cql/select_spec.rb +12 -0
  102. data/spec/datastax_rails/cql/update_spec.rb +0 -0
  103. data/spec/datastax_rails/relation/finder_methods_spec.rb +54 -0
  104. data/spec/datastax_rails/relation/modification_methods_spec.rb +41 -0
  105. data/spec/datastax_rails/relation/search_methods_spec.rb +117 -0
  106. data/spec/datastax_rails/relation/spawn_methods_spec.rb +28 -0
  107. data/spec/datastax_rails/relation_spec.rb +130 -0
  108. data/spec/datastax_rails/validations/uniqueness_spec.rb +41 -0
  109. data/spec/datastax_rails_spec.rb +5 -0
  110. data/spec/dummy/Rakefile +8 -0
  111. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  112. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  113. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  114. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  115. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  116. data/spec/dummy/config/application.rb +47 -0
  117. data/spec/dummy/config/boot.rb +10 -0
  118. data/spec/dummy/config/database.yml +25 -0
  119. data/spec/dummy/config/datastax.yml +18 -0
  120. data/spec/dummy/config/environment.rb +5 -0
  121. data/spec/dummy/config/environments/development.rb +30 -0
  122. data/spec/dummy/config/environments/production.rb +60 -0
  123. data/spec/dummy/config/environments/test.rb +39 -0
  124. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  125. data/spec/dummy/config/initializers/inflections.rb +10 -0
  126. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  127. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  128. data/spec/dummy/config/initializers/session_store.rb +8 -0
  129. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  130. data/spec/dummy/config/locales/en.yml +5 -0
  131. data/spec/dummy/config/routes.rb +58 -0
  132. data/spec/dummy/config/sunspot.yml +17 -0
  133. data/spec/dummy/config.ru +4 -0
  134. data/spec/dummy/ks/migrate/20111117224534_models.rb +20 -0
  135. data/spec/dummy/ks/schema.json +180 -0
  136. data/spec/dummy/log/development.log +298 -0
  137. data/spec/dummy/log/production.log +0 -0
  138. data/spec/dummy/log/test.log +20307 -0
  139. data/spec/dummy/public/404.html +26 -0
  140. data/spec/dummy/public/422.html +26 -0
  141. data/spec/dummy/public/500.html +26 -0
  142. data/spec/dummy/public/favicon.ico +0 -0
  143. data/spec/dummy/script/rails +6 -0
  144. data/spec/spec.opts +5 -0
  145. data/spec/spec_helper.rb +29 -0
  146. data/spec/support/datastax_test_hook.rb +14 -0
  147. data/spec/support/models.rb +72 -0
  148. 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