datastax_rails 2.0.3 → 2.0.4
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.
- 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
|