datastax_rails 2.0.3 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/datastax_rails/callbacks.rb +274 -18
- data/lib/datastax_rails/column.rb +11 -10
- data/lib/datastax_rails/dynamic_model.rb +68 -28
- data/lib/datastax_rails/relation/search_methods.rb +17 -18
- data/lib/datastax_rails/schema/migrator.rb +16 -3
- data/lib/datastax_rails/serialization.rb +5 -0
- data/lib/datastax_rails/types/dynamic_map.rb +5 -6
- data/lib/datastax_rails/version.rb +1 -2
- data/spec/datastax_rails/base_spec.rb +16 -8
- data/spec/datastax_rails/column_spec.rb +9 -0
- data/spec/datastax_rails/dynamic_model_spec.rb +45 -0
- data/spec/datastax_rails/relation/search_methods_spec.rb +6 -3
- data/spec/spec_helper.rb +1 -1
- data/spec/support/models.rb +7 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06972ed110ef94f1e39ad303f9d83b3c012f2840
|
4
|
+
data.tar.gz: 633ea7c7ff603672a07bc70ecb5b62059d12d18d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b826619dcf22f443352173c26accb3f994a8b229739aa6ab71e64013a05fed9df1d59af3478f05bbcb21dc19637699ad009dc5c577abf1c87fe2984d29ee05db
|
7
|
+
data.tar.gz: f86a2dec43ce66e55c270b494554b15d4a33ace9e84481d459e5200f32a71c5045575befe6549a4147f04288ac6a28ff8c36ccf4fc256e4046a5bd70997ff180
|
@@ -1,4 +1,256 @@
|
|
1
1
|
module DatastaxRails
|
2
|
+
# = Datastax Rails Callbacks
|
3
|
+
#
|
4
|
+
# Callbacks are hooks into the life cycle of an Datastax Rails object that allow you to trigger logic
|
5
|
+
# before or after an alteration of the object state. This can be used to make sure that associated and
|
6
|
+
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
|
7
|
+
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
|
8
|
+
# the <tt>Base#save</tt> call for a new record:
|
9
|
+
#
|
10
|
+
# * (-) <tt>save</tt>
|
11
|
+
# * (-) <tt>valid</tt>
|
12
|
+
# * (1) <tt>before_validation</tt>
|
13
|
+
# * (-) <tt>validate</tt>
|
14
|
+
# * (2) <tt>after_validation</tt>
|
15
|
+
# * (3) <tt>before_save</tt>
|
16
|
+
# * (4) <tt>before_create</tt>
|
17
|
+
# * (-) <tt>create</tt>
|
18
|
+
# * (5) <tt>after_create</tt>
|
19
|
+
# * (6) <tt>after_save</tt>
|
20
|
+
# * (7) <tt>after_commit</tt>
|
21
|
+
#
|
22
|
+
# Additionally, an <tt>after_touch</tt> callback is triggered whenever an
|
23
|
+
# object is touched.
|
24
|
+
#
|
25
|
+
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
|
26
|
+
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
|
27
|
+
# are instantiated as well.
|
28
|
+
#
|
29
|
+
# There are eighteen callbacks in total, which give you immense power to react and prepare for each state in the
|
30
|
+
# Datastax Rails life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
|
31
|
+
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
|
32
|
+
#
|
33
|
+
# Examples:
|
34
|
+
# class CreditCard < DatastaxRails::Base
|
35
|
+
# # Strip everything but digits, so the user can specify "555 234 34" or
|
36
|
+
# # "5552-3434" and both will mean "55523434"
|
37
|
+
# before_validation(on: :create) do
|
38
|
+
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class Subscription < DatastaxRails::Base
|
43
|
+
# before_create :record_signup
|
44
|
+
#
|
45
|
+
# private
|
46
|
+
# def record_signup
|
47
|
+
# self.signed_up_on = Date.today
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# class Firm < DatastaxRails::Base
|
52
|
+
# # Destroys the associated clients and people when the firm is destroyed
|
53
|
+
# before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
|
54
|
+
# before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# == Inheritable callback queues
|
58
|
+
#
|
59
|
+
# Besides the overwritable callback methods, it's also possible to register callbacks through the
|
60
|
+
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
|
61
|
+
# queue that is kept intact down through an inheritance hierarchy.
|
62
|
+
#
|
63
|
+
# class Topic < DatastaxRails::Base
|
64
|
+
# before_destroy :destroy_author
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# class Reply < Topic
|
68
|
+
# before_destroy :destroy_readers
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
|
72
|
+
# run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
|
73
|
+
# where the +before_destroy+ method is overridden:
|
74
|
+
#
|
75
|
+
# class Topic < DatastaxRails::Base
|
76
|
+
# def before_destroy() destroy_author end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# class Reply < Topic
|
80
|
+
# def before_destroy() destroy_readers end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
|
84
|
+
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
|
85
|
+
# hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
|
86
|
+
# to decide whether they want to call +super+ and trigger the inherited callbacks.
|
87
|
+
#
|
88
|
+
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
|
89
|
+
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
|
90
|
+
# child before the parent has registered the callbacks and they won't be inherited.
|
91
|
+
#
|
92
|
+
# == Types of callbacks
|
93
|
+
#
|
94
|
+
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
|
95
|
+
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
|
96
|
+
# are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
|
97
|
+
# creating mix-ins), and inline eval methods are deprecated.
|
98
|
+
#
|
99
|
+
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
|
100
|
+
#
|
101
|
+
# class Topic < DatastaxRails::Base
|
102
|
+
# before_destroy :delete_parents
|
103
|
+
#
|
104
|
+
# private
|
105
|
+
# def delete_parents
|
106
|
+
# self.class.delete_all "parent_id = #{id}"
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
|
111
|
+
#
|
112
|
+
# class BankAccount < DatastaxRails::Base
|
113
|
+
# before_save EncryptionWrapper.new
|
114
|
+
# after_save EncryptionWrapper.new
|
115
|
+
# after_initialize EncryptionWrapper.new
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# class EncryptionWrapper
|
119
|
+
# def before_save(record)
|
120
|
+
# record.credit_card_number = encrypt(record.credit_card_number)
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# def after_save(record)
|
124
|
+
# record.credit_card_number = decrypt(record.credit_card_number)
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# alias_method :after_initialize, :after_save
|
128
|
+
#
|
129
|
+
# private
|
130
|
+
# def encrypt(value)
|
131
|
+
# # Secrecy is committed
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# def decrypt(value)
|
135
|
+
# # Secrecy is unveiled
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
140
|
+
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
|
141
|
+
# initialization data such as the name of the attribute to work with:
|
142
|
+
#
|
143
|
+
# class BankAccount < DatastaxRails::Base
|
144
|
+
# before_save EncryptionWrapper.new("credit_card_number")
|
145
|
+
# after_save EncryptionWrapper.new("credit_card_number")
|
146
|
+
# after_initialize EncryptionWrapper.new("credit_card_number")
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# class EncryptionWrapper
|
150
|
+
# def initialize(attribute)
|
151
|
+
# @attribute = attribute
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# def before_save(record)
|
155
|
+
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# def after_save(record)
|
159
|
+
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# alias_method :after_initialize, :after_save
|
163
|
+
#
|
164
|
+
# private
|
165
|
+
# def encrypt(value)
|
166
|
+
# # Secrecy is committed
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# def decrypt(value)
|
170
|
+
# # Secrecy is unveiled
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also
|
175
|
+
# pass a "method string", which will then be evaluated within the binding of the callback. Example:
|
176
|
+
#
|
177
|
+
# class Topic < DatastaxRails::Base
|
178
|
+
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
|
182
|
+
# is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
|
183
|
+
#
|
184
|
+
# class Topic < DatastaxRails::Base
|
185
|
+
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
|
186
|
+
# 'puts "Evaluated after parents are destroyed"'
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# == <tt>before_validation*</tt> returning statements
|
190
|
+
#
|
191
|
+
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
|
192
|
+
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
|
193
|
+
# DatastaxRails::RecordInvalid exception. Nothing will be appended to the errors object.
|
194
|
+
#
|
195
|
+
# == Canceling callbacks
|
196
|
+
#
|
197
|
+
# If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
|
198
|
+
# cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
|
199
|
+
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
|
200
|
+
# methods on the model, which are called last.
|
201
|
+
#
|
202
|
+
# == Ordering callbacks
|
203
|
+
#
|
204
|
+
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
|
205
|
+
# callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
|
206
|
+
#
|
207
|
+
# Let's look at the code below:
|
208
|
+
#
|
209
|
+
# class Topic < DatastaxRails::Base
|
210
|
+
# has_many :children, dependent: destroy
|
211
|
+
#
|
212
|
+
# before_destroy :log_children
|
213
|
+
#
|
214
|
+
# private
|
215
|
+
# def log_children
|
216
|
+
# # Child processing
|
217
|
+
# end
|
218
|
+
# end
|
219
|
+
#
|
220
|
+
# In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
|
221
|
+
# because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
|
222
|
+
#
|
223
|
+
# class Topic < DatastaxRails::Base
|
224
|
+
# has_many :children, dependent: destroy
|
225
|
+
#
|
226
|
+
# before_destroy :log_children, prepend: true
|
227
|
+
#
|
228
|
+
# private
|
229
|
+
# def log_children
|
230
|
+
# # Child processing
|
231
|
+
# end
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
|
235
|
+
#
|
236
|
+
# == Debugging callbacks
|
237
|
+
#
|
238
|
+
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
|
239
|
+
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
|
240
|
+
# defines what part of the chain the callback runs in.
|
241
|
+
#
|
242
|
+
# To find all callbacks in the before_save callback chain:
|
243
|
+
#
|
244
|
+
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
|
245
|
+
#
|
246
|
+
# Returns an array of callback objects that form the before_save chain.
|
247
|
+
#
|
248
|
+
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
|
249
|
+
#
|
250
|
+
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
|
251
|
+
#
|
252
|
+
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
|
253
|
+
#
|
2
254
|
module Callbacks
|
3
255
|
extend ActiveSupport::Concern
|
4
256
|
|
@@ -9,33 +261,37 @@ module DatastaxRails
|
|
9
261
|
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
|
10
262
|
]
|
11
263
|
|
264
|
+
module ClassMethods
|
265
|
+
include ActiveModel::Callbacks
|
266
|
+
end
|
12
267
|
|
13
268
|
included do
|
14
|
-
extend ActiveModel::Callbacks
|
15
269
|
include ActiveModel::Validations::Callbacks
|
270
|
+
|
16
271
|
define_model_callbacks :initialize, :find, :touch, :only => :after
|
17
272
|
define_model_callbacks :save, :create, :update, :destroy
|
18
273
|
end
|
19
274
|
|
20
275
|
def destroy(*) #:nodoc:
|
21
|
-
#_run_destroy_callbacks { super }
|
22
276
|
run_callbacks(:destroy) { super }
|
23
277
|
end
|
24
278
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
279
|
+
def touch(*) #:nodoc:
|
280
|
+
run_callbacks(:touch) { super }
|
281
|
+
end
|
282
|
+
|
283
|
+
private
|
284
|
+
|
285
|
+
def _create_or_update(*) #:nodoc:
|
286
|
+
run_callbacks(:save) { super }
|
287
|
+
end
|
288
|
+
|
289
|
+
def _create_record(*) #:nodoc:
|
290
|
+
run_callbacks(:create) { super }
|
291
|
+
end
|
292
|
+
|
293
|
+
def _update_record(*) #:nodoc:
|
294
|
+
run_callbacks(:update) { super }
|
295
|
+
end
|
40
296
|
end
|
41
|
-
end
|
297
|
+
end
|
@@ -146,10 +146,11 @@ module DatastaxRails
|
|
146
146
|
return coder.dump(value) if encoded?
|
147
147
|
|
148
148
|
case (dest_type || type)
|
149
|
-
when :uuid
|
150
|
-
when :
|
151
|
-
when :
|
152
|
-
when :
|
149
|
+
when :uuid then value.is_a?(::Cql::Uuid) ? value : self.class.value_to_uuid(value)
|
150
|
+
when :time, :datetime, :timestamp then value.to_time.utc
|
151
|
+
when :date then value.to_time.utc
|
152
|
+
when :list, :set then list_to_cql3_value(value)
|
153
|
+
when :map then map_to_cql3_value(value)
|
153
154
|
else value
|
154
155
|
end
|
155
156
|
end
|
@@ -163,7 +164,7 @@ module DatastaxRails
|
|
163
164
|
|
164
165
|
case (dest_type || type)
|
165
166
|
when :boolean then value ? 'true' : 'false'
|
166
|
-
when :date, :time, :datetime, :timestamp then value.strftime(Format::SOLR_TIME_FORMAT)
|
167
|
+
when :date, :time, :datetime, :timestamp then value.to_time.utc.strftime(Format::SOLR_TIME_FORMAT)
|
167
168
|
when :list, :set then self.list_to_solr_value(value)
|
168
169
|
when :map then self.map_to_solr_value(value)
|
169
170
|
else value
|
@@ -171,19 +172,19 @@ module DatastaxRails
|
|
171
172
|
end
|
172
173
|
|
173
174
|
def list_to_solr_value(value)
|
174
|
-
value.map {|v| type_cast_for_solr(v, @options[:holds])}
|
175
|
+
value.map {|v| type_cast_for_solr(v, @options[:holds].to_sym)}
|
175
176
|
end
|
176
177
|
|
177
178
|
def map_to_solr_value(value)
|
178
|
-
value.each {|k,v| value[k] = type_cast_for_solr(v, @options[:holds])}
|
179
|
+
value.each {|k,v| value[k] = type_cast_for_solr(v, @options[:holds].to_sym)}
|
179
180
|
end
|
180
181
|
|
181
182
|
def list_to_cql3_value(value)
|
182
|
-
value.map {|v| type_cast_for_cql3(v, @options[:holds])}
|
183
|
+
value.map {|v| type_cast_for_cql3(v, @options[:holds].to_sym)}
|
183
184
|
end
|
184
185
|
|
185
186
|
def map_to_cql3_value(value)
|
186
|
-
value.dup.each {|k,v| value[k] = type_cast_for_cql3(v, @options[:holds])}
|
187
|
+
value.dup.each {|k,v| value[k] = type_cast_for_cql3(v, @options[:holds].to_sym)}
|
187
188
|
value
|
188
189
|
end
|
189
190
|
|
@@ -210,7 +211,7 @@ module DatastaxRails
|
|
210
211
|
end
|
211
212
|
|
212
213
|
def full_solr_range
|
213
|
-
if
|
214
|
+
if %w[date uuid].include? solr_type
|
214
215
|
'[* TO *]'
|
215
216
|
else
|
216
217
|
'[\"\" TO *]'
|
@@ -33,17 +33,17 @@ module DatastaxRails
|
|
33
33
|
# ts_ prefix to differentiate it from texts.
|
34
34
|
#
|
35
35
|
# class Item < DatastaxRails::DynamicModel
|
36
|
-
# self.
|
36
|
+
# self.grouping = 'item'
|
37
37
|
# timestamps
|
38
38
|
# end
|
39
39
|
#
|
40
40
|
# class CoreMetadata < DatastaxRails::DynamicModel
|
41
|
-
# self.
|
41
|
+
# self.grouping = 'core'
|
42
42
|
# timestamps
|
43
43
|
# end
|
44
44
|
#
|
45
45
|
# class TeamMetadata < DatastaxRails::DynamicModel
|
46
|
-
# self.
|
46
|
+
# self.grouping = 'team'
|
47
47
|
# timestamps
|
48
48
|
# end
|
49
49
|
#
|
@@ -60,39 +60,79 @@ module DatastaxRails
|
|
60
60
|
# the collection so:
|
61
61
|
#
|
62
62
|
# Item.first.strings #=> {s_title: "Title"}
|
63
|
+
#
|
64
|
+
# If you would like to still define known attributes ahead of time, you can still do so:
|
65
|
+
#
|
66
|
+
# class TeamMetadata < DatastaxRails::DynamicModel
|
67
|
+
# self.grouping = 'team'
|
68
|
+
# string :name
|
69
|
+
# timestamps
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# TeamMetadata.new(name: 'John').name #=> 'John'
|
73
|
+
# TeamMetadata.new(name: 'John').strings #=> {'s_name' => 'John'}
|
74
|
+
#
|
75
|
+
# Getters and setters are automatically created to map to the attribute stored in the hash.
|
76
|
+
# In addition, there is a helper method to map column names to the field name in solr to
|
77
|
+
# assist with search.
|
63
78
|
class DynamicModel < WideStorageModel
|
64
79
|
self.abstract_class = true
|
65
80
|
|
66
|
-
|
67
|
-
|
68
|
-
def self.group_by=(group)
|
69
|
-
self.group_by_attribute = group
|
70
|
-
self.attribute_definitions['group'].default = group
|
71
|
-
default_scope -> {where('group' => group)}
|
72
|
-
end
|
81
|
+
PREFIXES = {string: :s_, text: :t_, boolean: :b_, date: :d_,
|
82
|
+
timestamp: :ts_, integer: :i_, float: :f_, uuid: :u_}.with_indifferent_access
|
73
83
|
|
84
|
+
class_attribute :group_by_attribute
|
85
|
+
class_attribute :declared_attributes
|
74
86
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
class << self
|
88
|
+
def grouping=(group)
|
89
|
+
self.group_by_attribute = group
|
90
|
+
self.attribute_definitions['group'].default = group
|
91
|
+
default_scope -> {where('group' => group)}
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :_attribute, :attribute
|
95
|
+
|
96
|
+
def attribute(name, options)
|
97
|
+
options.symbolize_keys!
|
98
|
+
return super if [:map,:list,:set].include?(options[:type].to_sym)
|
99
|
+
# Only type supported for now
|
100
|
+
options.assert_valid_keys(:type)
|
101
|
+
raise ArgumentError, "Invalid type specified for dynamic attribute: '#{name}: #{options[:type]}'" unless PREFIXES.has_key?(options[:type])
|
102
|
+
self.declared_attributes[name] = PREFIXES[options[:type]].to_s + name.to_s
|
103
|
+
define_method(name) do
|
104
|
+
self.send(PREFIXES[options[:type]])[name]
|
105
|
+
end
|
106
|
+
define_method("#{name.to_s}=") do |val|
|
107
|
+
self.send(PREFIXES[options[:type]])[name] = val
|
108
|
+
end
|
109
|
+
end
|
90
110
|
|
91
|
-
child
|
92
|
-
|
93
|
-
|
111
|
+
def inherited(child)
|
112
|
+
super
|
113
|
+
child.declared_attributes = child.declared_attributes.nil? ? {}.with_indifferent_access : child.declared_attributes.dup
|
114
|
+
child.column_family = 'dynamic_model'
|
115
|
+
child.primary_key = 'id'
|
116
|
+
child.cluster_by = 'group'
|
117
|
+
child._attribute :id, :type => :uuid
|
118
|
+
child._attribute :group, :type => :string
|
119
|
+
PREFIXES.each do |k,v|
|
120
|
+
child._attribute v, holds: k.to_sym, type: :map
|
121
|
+
child.instance_eval { alias_attribute k.to_s.pluralize, v}
|
94
122
|
end
|
95
123
|
end
|
124
|
+
|
125
|
+
def solr_field_name(attr, type = nil)
|
126
|
+
if type
|
127
|
+
PREFIXES[type].to_s + attr.to_s
|
128
|
+
else
|
129
|
+
declared_attributes[attr] || raise(UnknownAttributeError, "Unknown attribute: #{attr}. You must specify a type.")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def solr_field_name(attr, type = nil)
|
135
|
+
self.class.solr_field_name(attr, type)
|
96
136
|
end
|
97
137
|
end
|
98
138
|
end
|
@@ -399,7 +399,7 @@ module DatastaxRails
|
|
399
399
|
end
|
400
400
|
attributes.delete(k)
|
401
401
|
else
|
402
|
-
attributes[k] = solr_format(v)
|
402
|
+
attributes[k] = solr_format(k,v)
|
403
403
|
end
|
404
404
|
end
|
405
405
|
r.where_values << attributes unless attributes.empty?
|
@@ -443,7 +443,7 @@ module DatastaxRails
|
|
443
443
|
end
|
444
444
|
attributes.delete(k)
|
445
445
|
else
|
446
|
-
attributes[k] = solr_format(v)
|
446
|
+
attributes[k] = solr_format(k,v)
|
447
447
|
end
|
448
448
|
end
|
449
449
|
r.where_not_values << attributes unless attributes.empty?
|
@@ -536,27 +536,26 @@ module DatastaxRails
|
|
536
536
|
end
|
537
537
|
|
538
538
|
# Formats a value for solr (assuming this is a solr query).
|
539
|
-
def solr_format(value)
|
539
|
+
def solr_format(attribute, value)
|
540
540
|
return value unless use_solr_value
|
541
|
+
column = attribute.is_a?(DatastaxRails::Column) ? attribute : klass.column_for_attribute(attribute)
|
542
|
+
# value = column.type_cast_for_solr(value)
|
541
543
|
case
|
542
|
-
when value.is_a?(Time)
|
543
|
-
|
544
|
-
when value.is_a?(
|
545
|
-
value.
|
546
|
-
when value.is_a?(Date)
|
547
|
-
value.strftime(DatastaxRails::Column::Format::SOLR_TIME_FORMAT)
|
548
|
-
when value.is_a?(Array)
|
549
|
-
value.collect {|v| v.to_s.gsub(/ /,"\\ ") }.join(" OR ")
|
544
|
+
when value.is_a?(Time) || value.is_a?(DateTime) || value.is_a?(Date)
|
545
|
+
column.type_cast_for_solr(value)
|
546
|
+
when value.is_a?(Array) || value.is_a?(Set)
|
547
|
+
column.type_cast_for_solr(value).collect {|v| v.to_s.gsub(/ /,"\\ ") }.join(" OR ")
|
550
548
|
when value.is_a?(Fixnum)
|
551
549
|
value < 0 ? "\\#{value}" : value
|
552
550
|
when value.is_a?(Range)
|
553
|
-
"[#{solr_format(value.first)} TO #{solr_format(value.last)}]"
|
551
|
+
"[#{solr_format(attribute, value.first)} TO #{solr_format(attribute, value.last)}]"
|
554
552
|
when value.is_a?(String)
|
555
553
|
solr_escape(downcase_query(value.gsub(/ /,"\\ ")))
|
556
554
|
when value.is_a?(FalseClass), value.is_a?(TrueClass)
|
557
555
|
value.to_s
|
558
556
|
else
|
559
557
|
value
|
558
|
+
|
560
559
|
end
|
561
560
|
end
|
562
561
|
|
@@ -571,9 +570,9 @@ module DatastaxRails
|
|
571
570
|
def equal_to(value) #:nodoc:
|
572
571
|
@relation.clone.tap do |r|
|
573
572
|
if @invert
|
574
|
-
r.where_not_values << {@attribute => r.solr_format(value)}
|
573
|
+
r.where_not_values << {@attribute => r.solr_format(@attribute, value)}
|
575
574
|
else
|
576
|
-
r.where_values << {@attribute => r.solr_format(value)}
|
575
|
+
r.where_values << {@attribute => r.solr_format(@attribute, value)}
|
577
576
|
end
|
578
577
|
end
|
579
578
|
end
|
@@ -581,9 +580,9 @@ module DatastaxRails
|
|
581
580
|
def greater_than(value) #:nodoc:
|
582
581
|
@relation.clone.tap do |r|
|
583
582
|
if @invert
|
584
|
-
r.less_than_values << {@attribute => r.solr_format(value)}
|
583
|
+
r.less_than_values << {@attribute => r.solr_format(@attribute, value)}
|
585
584
|
else
|
586
|
-
r.greater_than_values << {@attribute => r.solr_format(value)}
|
585
|
+
r.greater_than_values << {@attribute => r.solr_format(@attribute, value)}
|
587
586
|
end
|
588
587
|
end
|
589
588
|
end
|
@@ -591,9 +590,9 @@ module DatastaxRails
|
|
591
590
|
def less_than(value) #:nodoc:
|
592
591
|
@relation.clone.tap do |r|
|
593
592
|
if @invert
|
594
|
-
r.greater_than_values << {@attribute => r.solr_format(value)}
|
593
|
+
r.greater_than_values << {@attribute => r.solr_format(@attribute, value)}
|
595
594
|
else
|
596
|
-
r.less_than_values << {@attribute => r.solr_format(value)}
|
595
|
+
r.less_than_values << {@attribute => r.solr_format(@attribute, value)}
|
597
596
|
end
|
598
597
|
end
|
599
598
|
end
|
@@ -16,10 +16,11 @@ module DatastaxRails
|
|
16
16
|
|
17
17
|
def migrate_all(force = false)
|
18
18
|
say_with_time("Migrating all models") do
|
19
|
-
|
20
|
-
|
21
|
-
require
|
19
|
+
|
20
|
+
FileList[rails_models].each do |model|
|
21
|
+
require model
|
22
22
|
end
|
23
|
+
|
23
24
|
count = 0
|
24
25
|
DatastaxRails::Base.models.each do |m|
|
25
26
|
if !m.abstract_class?
|
@@ -52,6 +53,18 @@ module DatastaxRails
|
|
52
53
|
end
|
53
54
|
|
54
55
|
private
|
56
|
+
|
57
|
+
# Determine all models to be included within the migration
|
58
|
+
# using Rails config paths instead of absolute paths.
|
59
|
+
# This enables Rails Engines to monkey patch their own
|
60
|
+
# models in, to be automatically included within migrations.
|
61
|
+
#
|
62
|
+
# @see http://pivotallabs.com/leave-your-migrations-in-your-rails-engines/
|
63
|
+
#
|
64
|
+
# @return [Array] list of configured application models
|
65
|
+
def rails_models
|
66
|
+
Rails.configuration.paths['app/models'].expanded.map { |p| p + '/*.rb' }
|
67
|
+
end
|
55
68
|
|
56
69
|
# Checks to ensure that the schema_migrations column family exists and creates it if not
|
57
70
|
def check_schema_migrations
|
@@ -20,13 +20,12 @@ module DatastaxRails
|
|
20
20
|
super(convert_key(key))
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
key = name + key.to_s
|
27
|
-
end
|
28
|
-
super(key)
|
23
|
+
def convert_key(key)
|
24
|
+
unless key.to_s.starts_with?(name)
|
25
|
+
key = name + key.to_s
|
29
26
|
end
|
27
|
+
super(key)
|
28
|
+
end
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
@@ -8,12 +8,18 @@ describe DatastaxRails::Base do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it "should run after_save" do
|
11
|
-
Person.
|
12
|
-
p
|
13
|
-
p.save!
|
11
|
+
p = Person.new(:name => "Steve")
|
12
|
+
p.save
|
14
13
|
p.instance_variable_get(:@after_save_ran).should == "yup"
|
15
14
|
end
|
16
15
|
|
16
|
+
it "should run after_create" do
|
17
|
+
p = Person.new(:name => "Tommy")
|
18
|
+
p.save
|
19
|
+
p.instance_variable_get(:@after_create_ran).should == "yup"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
17
23
|
it "should raise RecordNotFound when finding a bogus ID" do
|
18
24
|
lambda { Person.find("xyzzy") }.should raise_exception(DatastaxRails::RecordNotFound)
|
19
25
|
end
|
@@ -26,20 +32,22 @@ describe DatastaxRails::Base do
|
|
26
32
|
end
|
27
33
|
|
28
34
|
it "considers a new object to be unequal to a saved object" do
|
29
|
-
p1=Person.create!(:name => '
|
30
|
-
p2=Person.new(:name => '
|
35
|
+
p1=Person.create!(:name => 'Mike')
|
36
|
+
p2=Person.new(:name => 'Mike')
|
31
37
|
expect(p1).not_to eq(p2)
|
32
38
|
end
|
33
39
|
|
34
40
|
it "considers two persisted objects to be equal if their primary keys are equal" do
|
35
|
-
|
41
|
+
Person.commit_solr
|
42
|
+
p1=Person.create!(:name => 'Jim')
|
36
43
|
p2=Person.find(p1.id)
|
37
44
|
expect(p1).to eq(p2)
|
38
45
|
end
|
39
46
|
|
40
47
|
it "considers two persisted objects to be unequal if they have different primary keys" do
|
41
|
-
|
42
|
-
|
48
|
+
Person.commit_solr
|
49
|
+
p1=Person.create!(:name => 'Kirk')
|
50
|
+
p2=Person.create!(:name => 'Tiberious')
|
43
51
|
expect(p1).not_to eq(p2)
|
44
52
|
end
|
45
53
|
end
|
@@ -197,6 +197,7 @@ describe DatastaxRails::Column do
|
|
197
197
|
|
198
198
|
describe "map" do
|
199
199
|
let(:c) {DatastaxRails::Column.new("field_", nil, "map", :holds => :integer)}
|
200
|
+
let(:dc) {DatastaxRails::Column.new("field_", nil, "map", :holds => :date)}
|
200
201
|
|
201
202
|
it "casts map keys to strings" do
|
202
203
|
expect(c.type_cast({:field_key => 7}, record)).to eq({"field_key" => 7})
|
@@ -209,6 +210,14 @@ describe DatastaxRails::Column do
|
|
209
210
|
it "wraps map values in a DynamicMap" do
|
210
211
|
expect(c.type_cast({'field_key' => '7'}, record)).to be_a(DatastaxRails::Types::DynamicMap)
|
211
212
|
end
|
213
|
+
|
214
|
+
describe "to cql" do
|
215
|
+
it "casts map values to the appropriate type" do
|
216
|
+
date = Date.parse("1980-10-19")
|
217
|
+
time = Time.parse("1980-10-19 00:00:00 +0000")
|
218
|
+
expect(dc.type_cast_for_cql3({:field_key => date})).to eq(:field_key => time)
|
219
|
+
end
|
220
|
+
end
|
212
221
|
end
|
213
222
|
|
214
223
|
describe "list" do
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class DynamicTestModel1 < DatastaxRails::DynamicModel
|
4
|
+
self.grouping = 'test1'
|
5
|
+
string :name
|
6
|
+
timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
class DynamicTestModel2 < DatastaxRails::DynamicModel
|
10
|
+
self.grouping = 'test2'
|
11
|
+
end
|
12
|
+
|
13
|
+
describe DatastaxRails::DynamicModel do
|
14
|
+
let(:one) {DynamicTestModel1.new}
|
15
|
+
let(:two) {DynamicTestModel2.new}
|
16
|
+
|
17
|
+
it {expect(one).to respond_to(:created_at)}
|
18
|
+
it {expect(one).to respond_to(:created_at=)}
|
19
|
+
it {expect(two).not_to respond_to(:created_at)}
|
20
|
+
it {expect(two).not_to respond_to(:created_at=)}
|
21
|
+
|
22
|
+
it "sets the attribute in the dynamic collection" do
|
23
|
+
one.name = 'John'
|
24
|
+
expect(one.s_).to eq('s_name' => 'John')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "retrieves the attribute from the dynamic collection" do
|
28
|
+
one.strings[:name] = 'John'
|
29
|
+
expect(one.name).to eq('John')
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#solr_field_name" do
|
33
|
+
it "maps a attribute name to the underlying storage key" do
|
34
|
+
expect(one.solr_field_name(:name)).to eq('s_name')
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises DatastaxRails::UnknownAttributeError if an unknown attribute is mapped without a type" do
|
38
|
+
expect{two.solr_field_name(:name)}.to raise_exception(DatastaxRails::UnknownAttributeError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "maps an undeclared attribute if a type is given" do
|
42
|
+
expect(one.solr_field_name(:birthdate, :date)).to eq('d_birthdate')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -228,20 +228,23 @@ describe DatastaxRails::Relation do
|
|
228
228
|
describe '#solr_format' do
|
229
229
|
context 'when formatting Time' do
|
230
230
|
let(:time) { Time.new 2011, 10, 9, 8, 7, 6, "-05:00" }
|
231
|
+
let(:c) {DatastaxRails::Column.new("field", nil, "time")}
|
231
232
|
|
232
|
-
it { expect(@relation.solr_format(time)).to eq '2011-10-09T13:07:06Z' }
|
233
|
+
it { expect(@relation.solr_format(c,time)).to eq '2011-10-09T13:07:06Z' }
|
233
234
|
end
|
234
235
|
|
235
236
|
context 'when formatting Date' do
|
236
237
|
let(:date) { Date.new 2001, 2, 3 }
|
238
|
+
let(:c) {DatastaxRails::Column.new("field", nil, "date")}
|
237
239
|
|
238
|
-
it { expect(@relation.solr_format(date)).to eq '2001-02-03T00:00:00Z' }
|
240
|
+
it { expect(@relation.solr_format(c,date)).to eq '2001-02-03T00:00:00Z' }
|
239
241
|
end
|
240
242
|
|
241
243
|
context 'when formatting DateTime' do
|
242
244
|
let(:datetime) { DateTime.new 2001, 2, 3, 4, 5, 6, "-07:00" }
|
245
|
+
let(:c) {DatastaxRails::Column.new("field", nil, "timestamp")}
|
243
246
|
|
244
|
-
it { expect(@relation.solr_format(datetime)).to eq '2001-02-03T11:05:06Z' }
|
247
|
+
it { expect(@relation.solr_format(c,datetime)).to eq '2001-02-03T11:05:06Z' }
|
245
248
|
end
|
246
249
|
end
|
247
250
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -21,7 +21,7 @@ RSpec.configure do |config|
|
|
21
21
|
# Filter slow specs. Add a :slow tag to the spec to keep it from
|
22
22
|
# running unless the SLOW_SPECS environment variable is set.
|
23
23
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
24
|
-
config.filter_run_excluding :slow unless ENV['SLOW_SPECS']
|
24
|
+
# config.filter_run_excluding :slow unless ENV['SLOW_SPECS']
|
25
25
|
|
26
26
|
config.before(:each) do
|
27
27
|
DatastaxRails::Base.recorded_classes = {}
|
data/spec/support/models.rb
CHANGED
@@ -16,6 +16,7 @@ class Person < DatastaxRails::Base
|
|
16
16
|
before_create :set_variable2
|
17
17
|
before_save :set_nickname
|
18
18
|
after_save :set_variable
|
19
|
+
after_create :set_variable3
|
19
20
|
|
20
21
|
validates :name, :presence => true, :uniqueness => :true
|
21
22
|
|
@@ -30,6 +31,10 @@ class Person < DatastaxRails::Base
|
|
30
31
|
def set_variable2
|
31
32
|
@before_create_ran = "yup"
|
32
33
|
end
|
34
|
+
|
35
|
+
def set_variable3
|
36
|
+
@after_create_ran = 'yup'
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
40
|
class Car < DatastaxRails::Base
|
@@ -103,9 +108,9 @@ class Hobby < DatastaxRails::Base
|
|
103
108
|
end
|
104
109
|
|
105
110
|
class CoreMetadata < DatastaxRails::DynamicModel
|
106
|
-
self.
|
111
|
+
self.grouping = 'core'
|
107
112
|
end
|
108
113
|
|
109
114
|
class TeamMetadata < DatastaxRails::DynamicModel
|
110
|
-
self.
|
115
|
+
self.grouping = 'team'
|
111
116
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datastax_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason M. Kusar
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -246,6 +246,7 @@ files:
|
|
246
246
|
- spec/datastax_rails/column_spec.rb
|
247
247
|
- spec/datastax_rails/cql/select_spec.rb
|
248
248
|
- spec/datastax_rails/cql/update_spec.rb
|
249
|
+
- spec/datastax_rails/dynamic_model_spec.rb
|
249
250
|
- spec/datastax_rails/inheritance_spec.rb
|
250
251
|
- spec/datastax_rails/persistence_spec.rb
|
251
252
|
- spec/datastax_rails/relation/batches_spec.rb
|
@@ -336,6 +337,7 @@ test_files:
|
|
336
337
|
- spec/support/models.rb
|
337
338
|
- spec/support/default_consistency_shared_examples.rb
|
338
339
|
- spec/support/datastax_test_hook.rb
|
340
|
+
- spec/datastax_rails/dynamic_model_spec.rb
|
339
341
|
- spec/datastax_rails/attribute_methods_spec.rb
|
340
342
|
- spec/datastax_rails/scoping/default_spec.rb
|
341
343
|
- spec/datastax_rails/associations_spec.rb
|