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,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
|