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.
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,472 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/object/inclusion'
3
+
4
+ # This is shamelessly ripped from Active Record 3.1
5
+ module DatastaxRails
6
+ # = DatastaxRails Reflection
7
+ module Reflection # :nodoc:
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_attribute :reflections
12
+ self.reflections = {}
13
+ end
14
+
15
+ # Reflection enables interrogation of DatastaxRails classes and objects
16
+ # about their associations and aggregations. This information can,
17
+ # for example, be used in a form builder that takes an DatastaxRails object
18
+ # and creates input fields for all of the attributes depending on their type
19
+ # and displays the associations to other objects.
20
+ module ClassMethods
21
+ def create_reflection(macro, name, options, datastax_rails)
22
+ klass = options[:through] ? ThroughReflection : AssociationReflection
23
+ reflection = klass.new(macro, name, options, datastax_rails)
24
+ self.reflections = self.reflections.merge(name => reflection)
25
+ reflection
26
+ end
27
+
28
+ # Returns an array of AssociationReflection objects for all the
29
+ # associations in the class. If you only want to reflect on a certain
30
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
31
+ # <tt>:belongs_to</tt>) as the first parameter.
32
+ #
33
+ # Example:
34
+ #
35
+ # Account.reflect_on_all_associations # returns an array of all associations
36
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
37
+ #
38
+ def reflect_on_all_associations(macro = nil)
39
+ association_reflections = reflections.values.grep(AssociationReflection)
40
+ macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
41
+ end
42
+
43
+ # Returns the AssociationReflection object for the +association+ (use the symbol).
44
+ #
45
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
46
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
47
+ #
48
+ def reflect_on_association(association)
49
+ reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
50
+ end
51
+
52
+ # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
53
+ def reflect_on_all_autosave_associations
54
+ reflections.values.select { |reflection| reflection.options[:autosave] }
55
+ end
56
+ end
57
+
58
+
59
+ # Abstract base class for AggregateReflection and AssociationReflection. Objects of
60
+ # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
61
+ class MacroReflection
62
+ # Returns the name of the macro.
63
+ #
64
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
65
+ # <tt>has_many :clients</tt> returns <tt>:clients</tt>
66
+ attr_reader :name
67
+
68
+ # Returns the macro type.
69
+ #
70
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
71
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
72
+ attr_reader :macro
73
+
74
+ # Returns the hash of options used for the macro.
75
+ #
76
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
77
+ # <tt>has_many :clients</tt> returns +{}+
78
+ attr_reader :options
79
+
80
+ attr_reader :datastax_rails
81
+
82
+ # Returns a hash of all the denormalizations for this relationship (if any)
83
+ attr_reader :denorms
84
+
85
+ attr_reader :plural_name # :nodoc:
86
+
87
+ def initialize(macro, name, options, datastax_rails)
88
+ @macro = macro
89
+ @name = name
90
+ @options = options
91
+ @datastax_rails = datastax_rails
92
+ @plural_name = name.to_s.pluralize
93
+ end
94
+
95
+ # Returns the class for the macro.
96
+ #
97
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
98
+ # <tt>has_many :clients</tt> returns the Client class
99
+ def klass
100
+ @klass ||= class_name.constantize
101
+ end
102
+
103
+ # Returns the class name for the macro.
104
+ #
105
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
106
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
107
+ def class_name
108
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
109
+ end
110
+
111
+ # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +datastax_rails+ attribute,
112
+ # and +other_aggregation+ has an options hash assigned to it.
113
+ def ==(other_aggregation)
114
+ super ||
115
+ other_aggregation.kind_of?(self.class) &&
116
+ name == other_aggregation.name &&
117
+ other_aggregation.options &&
118
+ datastax_rails == other_aggregation.datastax_rails
119
+ end
120
+
121
+ # XXX: Do we need to sanitize our query?
122
+ def sanitized_conditions #:nodoc:
123
+ @sanitized_conditions ||= options[:conditions]
124
+ end
125
+
126
+ private
127
+ def derive_class_name
128
+ name.to_s.camelize
129
+ end
130
+ end
131
+
132
+ # Holds all the meta-data about an association as it was specified in the
133
+ # DatastaxRails class.
134
+ class AssociationReflection < MacroReflection #:nodoc:
135
+ # Returns the target association's class.
136
+ #
137
+ # class Author < DatastaxRails::Base
138
+ # has_many :books
139
+ # end
140
+ #
141
+ # Author.reflect_on_association(:books).klass
142
+ # # => Book
143
+ #
144
+ # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
145
+ # a new association object. Use +build_association+ or +create_association+
146
+ # instead. This allows plugins to hook into association object creation.
147
+ def klass
148
+ @klass ||= datastax_rails.send(:compute_type, class_name)
149
+ end
150
+
151
+ def initialize(macro, name, options, datastax_rails)
152
+ super
153
+ @collection = macro.in?([:has_many, :has_and_belongs_to_many])
154
+ end
155
+
156
+ # Returns a new, unsaved instance of the associated class. +options+ will
157
+ # be passed to the class's constructor.
158
+ def build_association(*options, &block)
159
+ klass.new(*options, &block)
160
+ end
161
+
162
+ def column_family
163
+ @column_family ||= klass.column_family
164
+ end
165
+
166
+ def quoted_column_family
167
+ column_family
168
+ end
169
+
170
+ def foreign_key
171
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key
172
+ end
173
+
174
+ def foreign_type
175
+ @foreign_type ||= options[:foreign_type] || "#{name}_type"
176
+ end
177
+
178
+ def type
179
+ @type ||= options[:as] && "#{options[:as]}_type"
180
+ end
181
+
182
+ def association_foreign_key
183
+ @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
184
+ end
185
+
186
+ # klass option is necessary to support loading polymorphic associations
187
+ # def association_primary_key(klass = nil)
188
+ # options[:primary_key] || primary_key(klass || self.klass)
189
+ # end
190
+ #
191
+ # def datastax_rails_primary_key
192
+ # @datastax_rails_primary_key ||= options[:primary_key] || primary_key(solandra_object)
193
+ # end
194
+
195
+ def check_validity!
196
+ check_validity_of_inverse!
197
+ end
198
+
199
+ def check_validity_of_inverse!
200
+ unless options[:polymorphic]
201
+ if has_inverse? && inverse_of.nil?
202
+ raise InverseOfAssociationNotFoundError.new(self)
203
+ end
204
+ end
205
+ end
206
+
207
+ def through_reflection
208
+ nil
209
+ end
210
+
211
+ def source_reflection
212
+ nil
213
+ end
214
+
215
+ # A chain of reflections from this one back to the owner. For more see the explanation in
216
+ # ThroughReflection.
217
+ def chain
218
+ [self]
219
+ end
220
+
221
+ # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
222
+ # in the #chain. The inside arrays are simply conditions (and each condition may itself be
223
+ # a hash, array, arel predicate, etc...)
224
+ def conditions
225
+ [[options[:conditions]].compact]
226
+ end
227
+
228
+ alias :source_macro :macro
229
+
230
+ def has_inverse?
231
+ @options[:inverse_of]
232
+ end
233
+
234
+ def inverse_of
235
+ if has_inverse?
236
+ @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
237
+ end
238
+ end
239
+
240
+ # Returns whether or not this association reflection is for a collection
241
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
242
+ # +has_and_belongs_to_many+, +false+ otherwise.
243
+ def collection?
244
+ @collection
245
+ end
246
+
247
+ # Returns whether or not the association should be validated as part of
248
+ # the parent's validation.
249
+ #
250
+ # Unless you explicitly disable validation with
251
+ # <tt>:validate => false</tt>, validation will take place when:
252
+ #
253
+ # * you explicitly enable validation; <tt>:validate => true</tt>
254
+ # * you use autosave; <tt>:autosave => true</tt>
255
+ # * the association is a +has_many+ association
256
+ def validate?
257
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
258
+ end
259
+
260
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
261
+ def belongs_to?
262
+ macro == :belongs_to
263
+ end
264
+
265
+ def association_class
266
+ case macro
267
+ when :belongs_to
268
+ Associations::BelongsToAssociation
269
+ when :has_and_belongs_to_many
270
+ Associations::HasAndBelongsToManyAssociation
271
+ when :has_many
272
+ if options[:through]
273
+ Associations::HasManyThroughAssociation
274
+ else
275
+ Associations::HasManyAssociation
276
+ end
277
+ when :has_one
278
+ if options[:through]
279
+ Associations::HasOneThroughAssociation
280
+ else
281
+ Associations::HasOneAssociation
282
+ end
283
+ end
284
+ end
285
+
286
+ private
287
+ def derive_class_name
288
+ class_name = name.to_s.camelize
289
+ class_name = class_name.singularize if collection?
290
+ class_name
291
+ end
292
+
293
+ def derive_foreign_key
294
+ if belongs_to?
295
+ "#{name}_id"
296
+ elsif options[:as]
297
+ "#{options[:as]}_id"
298
+ else
299
+ datastax_rails.name.foreign_key
300
+ end
301
+ end
302
+
303
+ # def primary_key(klass)
304
+ # klass.key || raise(UnknownPrimaryKey.new(klass))
305
+ # end
306
+ end
307
+
308
+ # Holds all the meta-data about a :through association as it was specified
309
+ # in the DatastaxRails class.
310
+ class ThroughReflection < AssociationReflection #:nodoc:
311
+ delegate :foreign_key, :foreign_type, :association_foreign_key,
312
+ :datastax_rails_primary_key, :type, :to => :source_reflection
313
+
314
+ # Gets the source of the through reflection. It checks both a singularized
315
+ # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
316
+ #
317
+ # class Post < ActiveRecord::Base
318
+ # has_many :taggings
319
+ # has_many :tags, :through => :taggings
320
+ # end
321
+ #
322
+ def source_reflection
323
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
324
+ end
325
+
326
+ # Returns the AssociationReflection object specified in the <tt>:through</tt> option
327
+ # of a HasManyThrough or HasOneThrough association.
328
+ #
329
+ # class Post < DatastaxRails::Base
330
+ # has_many :taggings
331
+ # has_many :tags, :through => :taggings
332
+ # end
333
+ #
334
+ # tags_reflection = Post.reflect_on_association(:tags)
335
+ # taggings_reflection = tags_reflection.through_reflection
336
+ #
337
+ def through_reflection
338
+ @through_reflection ||= datastax_rails.reflect_on_association(options[:through])
339
+ end
340
+
341
+ # Returns an array of reflections which are involved in this association. Each item in the
342
+ # array corresponds to a table which will be part of the query for this association.
343
+ #
344
+ # The chain is built by recursively calling #chain on the source reflection and the through
345
+ # reflection. The base case for the recursion is a normal association, which just returns
346
+ # [self] as its #chain.
347
+ def chain
348
+ @chain ||= begin
349
+ chain = source_reflection.chain + through_reflection.chain
350
+ chain[0] = self # Use self so we don't lose the information from :source_type
351
+ chain
352
+ end
353
+ end
354
+
355
+ # Consider the following example:
356
+ #
357
+ # class Person
358
+ # has_many :articles
359
+ # has_many :comment_tags, :through => :articles
360
+ # end
361
+ #
362
+ # class Article
363
+ # has_many :comments
364
+ # has_many :comment_tags, :through => :comments, :source => :tags
365
+ # end
366
+ #
367
+ # class Comment
368
+ # has_many :tags
369
+ # end
370
+ #
371
+ # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
372
+ # but only Comment.tags will be represented in the #chain. So this method creates an array
373
+ # of conditions corresponding to the chain. Each item in the #conditions array corresponds
374
+ # to an item in the #chain, and is itself an array of conditions from an arbitrary number
375
+ # of relevant reflections, plus any :source_type or polymorphic :as constraints.
376
+ def conditions
377
+ @conditions ||= begin
378
+ conditions = source_reflection.conditions.map { |c| c.dup }
379
+
380
+ # Add to it the conditions from this reflection if necessary.
381
+ conditions.first << options[:conditions] if options[:conditions]
382
+
383
+ through_conditions = through_reflection.conditions
384
+
385
+ if options[:source_type]
386
+ through_conditions.first << { foreign_type => options[:source_type] }
387
+ end
388
+
389
+ # Recursively fill out the rest of the array from the through reflection
390
+ conditions += through_conditions
391
+
392
+ # And return
393
+ conditions
394
+ end
395
+ end
396
+
397
+ # The macro used by the source association
398
+ def source_macro
399
+ source_reflection.source_macro
400
+ end
401
+
402
+ # A through association is nested iff there would be more than one join table
403
+ def nested?
404
+ chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
405
+ end
406
+
407
+ # We want to use the klass from this reflection, rather than just delegate straight to
408
+ # the source_reflection, because the source_reflection may be polymorphic. We still
409
+ # need to respect the source_reflection's :primary_key option, though.
410
+ def association_primary_key(klass = nil)
411
+ # Get the "actual" source reflection if the immediate source reflection has a
412
+ # source reflection itself
413
+ source_reflection = self.source_reflection
414
+ while source_reflection.source_reflection
415
+ source_reflection = source_reflection.source_reflection
416
+ end
417
+
418
+ source_reflection.options[:primary_key] || primary_key(klass || self.klass)
419
+ end
420
+
421
+ # Gets an array of possible <tt>:through</tt> source reflection names:
422
+ #
423
+ # [:singularized, :pluralized]
424
+ #
425
+ def source_reflection_names
426
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
427
+ end
428
+
429
+ def source_options
430
+ source_reflection.options
431
+ end
432
+
433
+ def through_options
434
+ through_reflection.options
435
+ end
436
+
437
+ def check_validity!
438
+ if through_reflection.nil?
439
+ raise HasManyThroughAssociationNotFoundError.new(datastax_rails.name, self)
440
+ end
441
+
442
+ if through_reflection.options[:polymorphic]
443
+ raise HasManyThroughAssociationPolymorphicThroughError.new(datastax_rails.name, self)
444
+ end
445
+
446
+ if source_reflection.nil?
447
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
448
+ end
449
+
450
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
451
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(datastax_rails.name, self, source_reflection)
452
+ end
453
+
454
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
455
+ raise HasManyThroughAssociationPolymorphicSourceError.new(datastax_rails.name, self, source_reflection)
456
+ end
457
+
458
+ if macro == :has_one && through_reflection.collection?
459
+ raise HasOneThroughCantAssociateThroughCollection.new(datastax_rails.name, self, through_reflection)
460
+ end
461
+
462
+ check_validity_of_inverse!
463
+ end
464
+
465
+ private
466
+ def derive_class_name
467
+ # get the class_name of the belongs_to association of the through reflection
468
+ options[:source_type] || source_reflection.class_name
469
+ end
470
+ end
471
+ end
472
+ end
@@ -0,0 +1,184 @@
1
+ module DatastaxRails
2
+ module FinderMethods
3
+ # Find operates with four different retrieval approaches:
4
+ #
5
+ # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
6
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
7
+ # * Find first - This will return the first record matched by the options used. These options can either be specific
8
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
9
+ # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
10
+ # * Find last - This will return the last record matched by the options used. These options can either be specific
11
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
12
+ # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
13
+ # * Find all - This will return all the records matched by the options used.
14
+ # If no records are found, an empty array is returned. Use
15
+ # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
16
+ #
17
+ # All approaches accept an options hash as their last parameter.
18
+ #
19
+ # ==== Options
20
+ #
21
+ # * <tt>:conditions</tt> - See conditions in the intro.
22
+ # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
23
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
24
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
25
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
26
+ # it would skip rows 0 through 4.
27
+ #
28
+ # ==== Examples
29
+ #
30
+ # # find by id
31
+ # Person.find(1) # returns the object for ID = 1
32
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
33
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
34
+ # Person.find([1]) # returns an array for the object with ID = 1
35
+ # Person.where(:administrator => 1).order(:created_on => :desc).find(1)
36
+ #
37
+ # Note that the returned order is undefined unless you give a specific +:order+ clause.
38
+ # Further note that order is handled in memory and so does suffer a performance penalty.
39
+ #
40
+ # ==== Examples
41
+ #
42
+ # # find first
43
+ # Person.first # returns the first object fetched by SELECT * FROM people
44
+ # Person.where(:user_name => user_name).first
45
+ # Person.order(:created_on => :desc).offset(5).first
46
+ #
47
+ # # find last
48
+ # Person.last # returns the last object in the column family
49
+ # Person.where(:user_name => user_name).last
50
+ # Person.order(:created_at => :desc).offset(5).last
51
+ #
52
+ # # find all
53
+ # Person.all # returns an array of objects for all the rows in the column family
54
+ # Person.where(["category IN (?)", categories]).limit(50).all
55
+ # Person.where(:friends => ["Bob", "Steve", "Fred"]).all
56
+ # Person.offset(10).limit(10).all
57
+ # Person.group("category").all
58
+ def find(*args)
59
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
60
+
61
+ options = args.extract_options!
62
+
63
+ if options.present?
64
+ apply_finder_options(options).find(*args)
65
+ else
66
+ case args.first
67
+ when :first, :last, :all
68
+ send(args.first)
69
+ else
70
+ self.use_solr_value = false
71
+ find_with_ids(*args)
72
+ end
73
+ end
74
+ end
75
+
76
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
77
+ # same arguments to this method as you can to <tt>find(:first)</tt>.
78
+ def first(*args)
79
+ if args.any?
80
+ if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
81
+ limit(*args).to_a
82
+ else
83
+ apply_finder_options(args.first).first
84
+ end
85
+ else
86
+ find_first
87
+ end
88
+ end
89
+
90
+ # Same as +first+ but raises <tt>DatastaxRails::RecordNotFound</tt> if no record
91
+ # is found. Note that <tt>first!</tt> accepts no arguments.
92
+ def first!
93
+ first or raise RecordNotFound
94
+ end
95
+
96
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
97
+ # same arguments to this method as you can to <tt>find(:last)</tt>.
98
+ def last(*args)
99
+ if args.any?
100
+ if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
101
+ if order_values.empty? && reorder_value.nil?
102
+ order(:id => :desc).limit(*args).reverse
103
+ else
104
+ to_a.last(*args)
105
+ end
106
+ else
107
+ apply_finder_options(args.first).last
108
+ end
109
+ else
110
+ find_last
111
+ end
112
+ end
113
+
114
+ # Same as +last+ but raises <tt>DatastaxRails::RecordNotFound</tt> if no record
115
+ # is found. Note that <tt>last!</tt> accepts no arguments.
116
+ def last!
117
+ last or raise RecordNotFound
118
+ end
119
+
120
+ private
121
+ def find_with_ids(*ids)
122
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
123
+
124
+ expects_array = ids.first.kind_of?(Array)
125
+ return ids.first if expects_array && ids.first.empty?
126
+
127
+ ids = ids.flatten.compact.uniq
128
+
129
+ case ids.size
130
+ when 0
131
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
132
+ when 1
133
+ result = find_one(ids.first)
134
+ expects_array ? [ result ] : result
135
+ else
136
+ find_some(ids)
137
+ end
138
+ end
139
+
140
+ def find_one(id)
141
+ with_cassandra.where(:key => id).first || raise(RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}")
142
+ end
143
+
144
+ def find_some(ids)
145
+ result = with_cassandra.where(:key => ids).all
146
+
147
+ expected_size =
148
+ if @limit_value && ids.size > @limit_value
149
+ @limit_value
150
+ else
151
+ ids.size
152
+ end
153
+
154
+ # 11 ids with limit 3, offset 9 should give 2 results.
155
+ if @offset_value && (ids.size - @offset_value < expected_size)
156
+ expected_size = ids.size - @offset_value
157
+ end
158
+
159
+ if result.size == expected_size
160
+ result
161
+ else
162
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
163
+ error << "(#{ids.join(", ")}) (found #{result.size} results, but was looking for #{expected_size})"
164
+ raise RecordNotFound, error
165
+ end
166
+ end
167
+
168
+ def find_first
169
+ if loaded?
170
+ @results.first
171
+ else
172
+ @first ||= limit(1).to_a[0]
173
+ end
174
+ end
175
+
176
+ def find_last
177
+ if loaded?
178
+ @results.last
179
+ else
180
+ @last ||= reverse_order.limit(1).to_a[0]
181
+ end
182
+ end
183
+ end
184
+ end