datastax_rails 2.0.16 → 2.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +7 -4
- data/config/solrconfig.xml.erb +1 -8
- data/lib/cql-rb_extensions.rb +1 -1
- data/lib/datastax_rails.rb +5 -0
- data/lib/datastax_rails/associations.rb +1 -1
- data/lib/datastax_rails/associations/association_scope.rb +2 -1
- data/lib/datastax_rails/associations/belongs_to_association.rb +12 -1
- data/lib/datastax_rails/associations/builder/association.rb +5 -2
- data/lib/datastax_rails/associations/has_one_association.rb +2 -1
- data/lib/datastax_rails/associations/singular_association.rb +2 -1
- data/lib/datastax_rails/autosave_association.rb +429 -0
- data/lib/datastax_rails/base.rb +5 -7
- data/lib/datastax_rails/connection.rb +65 -40
- data/lib/datastax_rails/cql.rb +4 -0
- data/lib/datastax_rails/cql/base.rb +17 -5
- data/lib/datastax_rails/locale/en.yml +47 -0
- data/lib/datastax_rails/persistence.rb +1 -2
- data/lib/datastax_rails/reflection.rb +10 -0
- data/lib/datastax_rails/schema/solr.rb +1 -1
- data/lib/datastax_rails/version.rb +1 -1
- data/spec/datastax_rails/autosave_association_spec.rb +17 -0
- data/spec/datastax_rails/relation/batches_spec.rb +1 -3
- data/spec/dummy/config/ca.crt +19 -0
- data/spec/dummy/config/datastax.yml +10 -2
- data/spec/dummy/log/test.log +1 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19e0c7c773fb3991c35ee3c5a4f76ec967d9041c
|
4
|
+
data.tar.gz: 4a5033007b3009ea785e38935f0d0a7c5a6eb410
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3ead232f919376053a15e813cec9d08630ff7ae4414300590ff2b8500077b9c6622a5347dec3d2ce5f25e22a528df05399843d8eff6587ef24df14ba1cc7be1
|
7
|
+
data.tar.gz: 4f620b9de99f047c114386357424cd1516432f81a45c1c3d446d31343712cd67c4396f46d2dab624b75361026dd58905982553540e8872c8bed9d06a3b98eac5
|
data/README.rdoc
CHANGED
@@ -15,10 +15,11 @@ Significant changes from SolandraObject:
|
|
15
15
|
|
16
16
|
=== Usage Note
|
17
17
|
|
18
|
-
Before using this gem, you should probably take a strong look at the type of problem you are trying to solve.
|
19
|
-
designed as a solution to Big Data problems.
|
20
|
-
We are using DSE to solve a replication problem more so than
|
21
|
-
it just might not be
|
18
|
+
Before using this gem, you should probably take a strong look at the type of problem you are trying to solve.
|
19
|
+
Cassandra is primarily designed as a solution to Big Data problems. This gem is not. You will notice that it
|
20
|
+
still carries a lot of relational logic with it. We are using DSE to solve a replication problem more so than
|
21
|
+
a Big Data problem. That's not to say that this gem won't work for Big Data problems, it just might not be
|
22
|
+
ideal. You've been warned...
|
22
23
|
|
23
24
|
=== Getting started
|
24
25
|
|
@@ -31,6 +32,7 @@ Configure the config/datastax.yml file:
|
|
31
32
|
development:
|
32
33
|
servers: ["127.0.0.1"]
|
33
34
|
port: 9042
|
35
|
+
ssl: true
|
34
36
|
keyspace: "<my_app>_development"
|
35
37
|
strategy_class: "org.apache.cassandra.locator.NetworkTopologyStrategy"
|
36
38
|
strategy_options: {"DC1": "1"}
|
@@ -48,6 +50,7 @@ For a more simple, single datacenter setup, something like this should probably
|
|
48
50
|
development:
|
49
51
|
servers: ["127.0.0.1"]
|
50
52
|
port: 9042
|
53
|
+
ssl: false
|
51
54
|
keyspace: "datastax_rails_development"
|
52
55
|
strategy_class: "org.apache.cassandra.locator.SimpleStrategy"
|
53
56
|
strategy_options: {"replication_factor": "1"}
|
data/config/solrconfig.xml.erb
CHANGED
@@ -70,20 +70,14 @@
|
|
70
70
|
the classpath, this is useful for including all jars in a
|
71
71
|
directory.
|
72
72
|
-->
|
73
|
-
<lib dir="../../contrib/extraction/lib" />
|
74
73
|
<!-- When a regex is specified in addition to a directory, only the
|
75
74
|
files in that directory which completely match the regex
|
76
75
|
(anchored on both ends) will be included.
|
77
76
|
-->
|
78
|
-
<lib dir="../../dist/" regex="apache-solr-cell-\d.*\.jar" />
|
79
|
-
<lib dir="../../dist/" regex="apache-solr-clustering-\d.*\.jar" />
|
80
|
-
<lib dir="../../dist/" regex="apache-solr-dataimporthandler-\d.*\.jar" />
|
81
77
|
|
82
78
|
<!-- If a dir option (with or without a regex) is used and nothing
|
83
79
|
is found that matches, it will be ignored
|
84
80
|
-->
|
85
|
-
<lib dir="../../contrib/clustering/lib/" />
|
86
|
-
<lib dir="/total/crap/dir/ignored" />
|
87
81
|
<!-- an exact path can be used to specify a specific file. This
|
88
82
|
will cause a serious error to be logged if it can't be loaded.
|
89
83
|
-->
|
@@ -219,8 +213,7 @@
|
|
219
213
|
triggering a new commit.
|
220
214
|
-->
|
221
215
|
<autoSoftCommit>
|
222
|
-
<maxTime
|
223
|
-
<maxDocs>1</maxDocs>
|
216
|
+
<maxTime><%= @solr_commit_time %></maxTime>
|
224
217
|
</autoSoftCommit>
|
225
218
|
|
226
219
|
<!-- Update Related Event Listeners
|
data/lib/cql-rb_extensions.rb
CHANGED
data/lib/datastax_rails.rb
CHANGED
@@ -9,6 +9,7 @@ module DatastaxRails
|
|
9
9
|
autoload :Associations
|
10
10
|
autoload :AttributeAssignment
|
11
11
|
autoload :AttributeMethods
|
12
|
+
autoload :AutosaveAssociation
|
12
13
|
autoload :Base
|
13
14
|
autoload :Batches
|
14
15
|
autoload :Callbacks
|
@@ -101,3 +102,7 @@ require 'datastax_rails/errors'
|
|
101
102
|
require 'cql-rb_extensions'
|
102
103
|
|
103
104
|
ActiveSupport.run_load_hooks(:datastax_rails, DatastaxRails::Base)
|
105
|
+
|
106
|
+
ActiveSupport.on_load(:i18n) do
|
107
|
+
I18n.load_path << File.dirname(__FILE__) + '/datastax_rails/locale/en.yml'
|
108
|
+
end
|
@@ -1,6 +1,17 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Associations
|
3
|
-
|
3
|
+
# belongs_to associations are the child side of a parent/child relationship
|
4
|
+
#
|
5
|
+
# class Car < DatastaxRails::Base
|
6
|
+
# uuid :id
|
7
|
+
# uuid :owner_id
|
8
|
+
# belongs_to :owner, class_name: 'Person'
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# Valid options:
|
12
|
+
# * class_name - The class on the other side (if different than what +classify+ returns)
|
13
|
+
# * foreign_key - The name of the foreign_key column if not just _id at the end of the association name
|
14
|
+
class BelongsToAssociation < SingularAssociation
|
4
15
|
attr_reader :updated
|
5
16
|
alias_method :updated?, :updated
|
6
17
|
def replace(record)
|
@@ -16,11 +16,14 @@ module DatastaxRails::Associations::Builder # rubocop:disable Style/ClassAndModu
|
|
16
16
|
@model, @name, @options = model, name, options
|
17
17
|
end
|
18
18
|
|
19
|
+
include Module.new { def build; end }
|
20
|
+
|
19
21
|
def build
|
20
22
|
validate_options
|
21
|
-
reflection = model.create_reflection(self.class.macro, name, options, model)
|
23
|
+
@reflection = model.create_reflection(self.class.macro, name, options, model)
|
22
24
|
define_accessors
|
23
|
-
|
25
|
+
super # provides an extension point
|
26
|
+
@reflection
|
24
27
|
end
|
25
28
|
|
26
29
|
def mixin
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Associations
|
3
|
-
|
3
|
+
# A has_one association is the parent of a one-to-one parent/child relation
|
4
|
+
class HasOneAssociation < SingularAssociation
|
4
5
|
def replace(record, save = true)
|
5
6
|
raise_on_type_mismatch(record) if record
|
6
7
|
load_target
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Associations
|
3
|
-
|
3
|
+
# Encapsulates the common functionality between belongs_to and has_one relationships
|
4
|
+
class SingularAssociation < Association
|
4
5
|
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
6
|
def reader(force_reload = false)
|
6
7
|
reload if force_reload || !loaded? || stale_target?
|
@@ -0,0 +1,429 @@
|
|
1
|
+
# rubocop:disable
|
2
|
+
module DatastaxRails
|
3
|
+
# = DatastaxRails Autosave Association
|
4
|
+
#
|
5
|
+
# +AutosaveAssociation+ is a module that takes care of automatically saving
|
6
|
+
# associated records when their parent is saved. In addition to saving, it
|
7
|
+
# also destroys any associated records that were marked for destruction.
|
8
|
+
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
|
9
|
+
#
|
10
|
+
# If validations for any of the associations fail, their error messages will
|
11
|
+
# be applied to the parent.
|
12
|
+
#
|
13
|
+
# Note that it also means that associations marked for destruction won't
|
14
|
+
# be destroyed directly. They will however still be marked for destruction.
|
15
|
+
#
|
16
|
+
# Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
|
17
|
+
# When the <tt>:autosave</tt> option is not present then new association records are
|
18
|
+
# saved but the updated association records are not saved.
|
19
|
+
#
|
20
|
+
# == Validation
|
21
|
+
#
|
22
|
+
# Children records are validated unless <tt>:validate</tt> is +false+.
|
23
|
+
#
|
24
|
+
# == Callbacks
|
25
|
+
#
|
26
|
+
# Association with autosave option defines several callbacks on your
|
27
|
+
# model (before_save, after_create, after_update). Please note that
|
28
|
+
# callbacks are executed in the order they were defined in the
|
29
|
+
# model. You should avoid modifying the association content, before
|
30
|
+
# autosave callbacks are executed. Placing your callbacks after
|
31
|
+
# associations is usually a good practice.
|
32
|
+
#
|
33
|
+
# === One-to-one Example
|
34
|
+
#
|
35
|
+
# class Post
|
36
|
+
# has_one :author, autosave: true
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Saving changes to the parent and its associated model can now be performed
|
40
|
+
# automatically:
|
41
|
+
#
|
42
|
+
# post = Post.find(1)
|
43
|
+
# post.title # => "The current global position of migrating ducks"
|
44
|
+
# post.author.name # => "alloy"
|
45
|
+
#
|
46
|
+
# post.title = "On the migration of ducks"
|
47
|
+
# post.author.name = "Eloy Duran"
|
48
|
+
#
|
49
|
+
# post.save
|
50
|
+
# post.reload
|
51
|
+
# post.title # => "On the migration of ducks"
|
52
|
+
# post.author.name # => "Eloy Duran"
|
53
|
+
#
|
54
|
+
# Destroying an associated model, as part of the parent's save action, is as
|
55
|
+
# simple as marking it for destruction:
|
56
|
+
#
|
57
|
+
# post.author.mark_for_destruction
|
58
|
+
# post.author.marked_for_destruction? # => true
|
59
|
+
#
|
60
|
+
# Note that the model is _not_ yet removed from the database:
|
61
|
+
#
|
62
|
+
# id = post.author.id
|
63
|
+
# Author.find_by(id: id).nil? # => false
|
64
|
+
#
|
65
|
+
# post.save
|
66
|
+
# post.reload.author # => nil
|
67
|
+
#
|
68
|
+
# Now it _is_ removed from the database:
|
69
|
+
#
|
70
|
+
# Author.find_by(id: id).nil? # => true
|
71
|
+
#
|
72
|
+
# === One-to-many Example
|
73
|
+
#
|
74
|
+
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
|
75
|
+
#
|
76
|
+
# class Post
|
77
|
+
# has_many :comments # :autosave option is not declared
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# post = Post.new(title: 'ruby rocks')
|
81
|
+
# post.comments.build(body: 'hello world')
|
82
|
+
# post.save # => saves both post and comment
|
83
|
+
#
|
84
|
+
# post = Post.create(title: 'ruby rocks')
|
85
|
+
# post.comments.build(body: 'hello world')
|
86
|
+
# post.save # => saves both post and comment
|
87
|
+
#
|
88
|
+
# post = Post.create(title: 'ruby rocks')
|
89
|
+
# post.comments.create(body: 'hello world')
|
90
|
+
# post.save # => saves both post and comment
|
91
|
+
#
|
92
|
+
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
|
93
|
+
# are new records or not:
|
94
|
+
#
|
95
|
+
# class Post
|
96
|
+
# has_many :comments, autosave: true
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# post = Post.create(title: 'ruby rocks')
|
100
|
+
# post.comments.create(body: 'hello world')
|
101
|
+
# post.comments[0].body = 'hi everyone'
|
102
|
+
# post.save # => saves both post and comment, with 'hi everyone' as body
|
103
|
+
#
|
104
|
+
# Destroying one of the associated models as part of the parent's save action
|
105
|
+
# is as simple as marking it for destruction:
|
106
|
+
#
|
107
|
+
# post.comments.last.mark_for_destruction
|
108
|
+
# post.comments.last.marked_for_destruction? # => true
|
109
|
+
# post.comments.length # => 2
|
110
|
+
#
|
111
|
+
# Note that the model is _not_ yet removed from the database:
|
112
|
+
#
|
113
|
+
# id = post.comments.last.id
|
114
|
+
# Comment.find_by(id: id).nil? # => false
|
115
|
+
#
|
116
|
+
# post.save
|
117
|
+
# post.reload.comments.length # => 1
|
118
|
+
#
|
119
|
+
# Now it _is_ removed from the database:
|
120
|
+
#
|
121
|
+
# Comment.find_by(id: id).nil? # => true
|
122
|
+
module AutosaveAssociation
|
123
|
+
extend ActiveSupport::Concern
|
124
|
+
|
125
|
+
# Extends the association builder to add autosave callbacks
|
126
|
+
module AssociationBuilderExtension
|
127
|
+
def build
|
128
|
+
model.send(:add_autosave_association_callbacks, reflection)
|
129
|
+
super
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
included do
|
134
|
+
require 'datastax_rails/associations/builder/association'
|
135
|
+
Associations::Builder::Association.class_eval do
|
136
|
+
valid_options << :autosave
|
137
|
+
include AssociationBuilderExtension
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
142
|
+
private
|
143
|
+
|
144
|
+
def define_non_cyclic_method(name, reflection, &block)
|
145
|
+
define_method(name) do |*_args|
|
146
|
+
result = true
|
147
|
+
@_already_called ||= {}
|
148
|
+
# Loop prevention for validation of associations
|
149
|
+
unless @_already_called[[name, reflection.name]]
|
150
|
+
begin
|
151
|
+
@_already_called[[name, reflection.name]] = true
|
152
|
+
result = instance_eval(&block)
|
153
|
+
ensure
|
154
|
+
@_already_called[[name, reflection.name]] = false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
result
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Adds validation and save callbacks for the association as specified by
|
163
|
+
# the +reflection+.
|
164
|
+
#
|
165
|
+
# For performance reasons, we don't check whether to validate at runtime.
|
166
|
+
# However the validation and callback methods are lazy and those methods
|
167
|
+
# get created when they are invoked for the very first time. However,
|
168
|
+
# this can change, for instance, when using nested attributes, which is
|
169
|
+
# called _after_ the association has been defined. Since we don't want
|
170
|
+
# the callbacks to get defined multiple times, there are guards that
|
171
|
+
# check if the save or validation methods have already been defined
|
172
|
+
# before actually defining them.
|
173
|
+
def add_autosave_association_callbacks(reflection)
|
174
|
+
save_method = :"autosave_associated_records_for_#{reflection.name}"
|
175
|
+
validation_method = :"validate_associated_records_for_#{reflection.name}"
|
176
|
+
collection = reflection.collection?
|
177
|
+
|
178
|
+
unless method_defined?(save_method)
|
179
|
+
if collection
|
180
|
+
before_save :before_save_collection_association
|
181
|
+
|
182
|
+
define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
|
183
|
+
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
184
|
+
after_create save_method
|
185
|
+
after_update save_method
|
186
|
+
elsif reflection.macro == :has_one
|
187
|
+
define_method(save_method) { save_has_one_association(reflection) }
|
188
|
+
# Configures two callbacks instead of a single after_save so that
|
189
|
+
# the model may rely on their execution order relative to its
|
190
|
+
# own callbacks.
|
191
|
+
#
|
192
|
+
# For example, given that after_creates run before after_saves, if
|
193
|
+
# we configured instead an after_save there would be no way to fire
|
194
|
+
# a custom after_create callback after the child association gets
|
195
|
+
# created.
|
196
|
+
after_create save_method
|
197
|
+
after_update save_method
|
198
|
+
else
|
199
|
+
define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
|
200
|
+
before_save save_method
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
if reflection.validate? && !method_defined?(validation_method) # rubocop:disable Style/GuardClause
|
205
|
+
method = (collection ? :validate_collection_association : :validate_single_association)
|
206
|
+
define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
|
207
|
+
validate validation_method
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
|
213
|
+
def reload(options = nil)
|
214
|
+
@marked_for_destruction = false
|
215
|
+
@destroyed_by_association = nil
|
216
|
+
super
|
217
|
+
end
|
218
|
+
|
219
|
+
# Marks this record to be destroyed as part of the parents save transaction.
|
220
|
+
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
|
221
|
+
# when <tt>parent.save</tt> is called.
|
222
|
+
#
|
223
|
+
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
224
|
+
def mark_for_destruction
|
225
|
+
@marked_for_destruction = true
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns whether or not this record will be destroyed as part of the parents save transaction.
|
229
|
+
#
|
230
|
+
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
231
|
+
attr_reader :marked_for_destruction
|
232
|
+
alias_method :marked_for_destruction?, :marked_for_destruction
|
233
|
+
|
234
|
+
# Records the association that is being destroyed and destroying this
|
235
|
+
# record in the process.
|
236
|
+
attr_writer :destroyed_by_association
|
237
|
+
|
238
|
+
# Returns the association for the parent being destroyed.
|
239
|
+
#
|
240
|
+
# Used to avoid updating the counter cache unnecessarily.
|
241
|
+
attr_reader :destroyed_by_association
|
242
|
+
|
243
|
+
# Returns whether or not this record has been changed in any way (including whether
|
244
|
+
# any of its nested autosave associations are likewise changed)
|
245
|
+
def changed_for_autosave?
|
246
|
+
new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
# Returns the record for an association collection that should be validated
|
252
|
+
# or saved. If +autosave+ is +false+ only new records will be returned,
|
253
|
+
# unless the parent is/was a new record itself.
|
254
|
+
def associated_records_to_validate_or_save(association, new_record, autosave)
|
255
|
+
if new_record
|
256
|
+
association && association.target
|
257
|
+
elsif autosave
|
258
|
+
association.target.select { |record| record.changed_for_autosave? }
|
259
|
+
else
|
260
|
+
association.target.select { |record| record.new_record? }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# go through nested autosave associations that are loaded in memory (without loading
|
265
|
+
# any new ones), and return true if is changed for autosave
|
266
|
+
def nested_records_changed_for_autosave?
|
267
|
+
self.class.reflect_on_all_autosave_associations.any? do |reflection|
|
268
|
+
association = association_instance_get(reflection.name)
|
269
|
+
association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
274
|
+
# turned on for the association.
|
275
|
+
def validate_single_association(reflection)
|
276
|
+
association = association_instance_get(reflection.name)
|
277
|
+
record = association && association.reader
|
278
|
+
association_valid?(reflection, record) if record
|
279
|
+
end
|
280
|
+
|
281
|
+
# Validate the associated records if <tt>:validate</tt> or
|
282
|
+
# <tt>:autosave</tt> is turned on for the association specified by
|
283
|
+
# +reflection+.
|
284
|
+
def validate_collection_association(reflection)
|
285
|
+
if (association = association_instance_get(reflection.name))
|
286
|
+
records = associated_records_to_validate_or_save(association,
|
287
|
+
new_record?,
|
288
|
+
reflection.options[:autosave])
|
289
|
+
if records
|
290
|
+
records.each { |record| association_valid?(reflection, record) }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Returns whether or not the association is valid and applies any errors to
|
296
|
+
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
297
|
+
# enabled records if they're marked_for_destruction? or destroyed.
|
298
|
+
def association_valid?(reflection, record)
|
299
|
+
return true if record.destroyed? || record.marked_for_destruction?
|
300
|
+
|
301
|
+
unless valid = record.valid?
|
302
|
+
if reflection.options[:autosave]
|
303
|
+
record.errors.each do |attribute, message|
|
304
|
+
attribute = "#{reflection.name}.#{attribute}"
|
305
|
+
errors[attribute] << message
|
306
|
+
errors[attribute].uniq!
|
307
|
+
end
|
308
|
+
else
|
309
|
+
errors.add(reflection.name)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
valid
|
313
|
+
end
|
314
|
+
|
315
|
+
# Is used as a before_save callback to check while saving a collection
|
316
|
+
# association whether or not the parent was a new record before saving.
|
317
|
+
def before_save_collection_association
|
318
|
+
@new_record_before_save = new_record?
|
319
|
+
true
|
320
|
+
end
|
321
|
+
|
322
|
+
# Saves any new associated records, or all loaded autosave associations if
|
323
|
+
# <tt>:autosave</tt> is enabled on the association.
|
324
|
+
#
|
325
|
+
# In addition, it destroys all children that were marked for destruction
|
326
|
+
# with mark_for_destruction.
|
327
|
+
def save_collection_association(reflection)
|
328
|
+
if association = association_instance_get(reflection.name)
|
329
|
+
autosave = reflection.options[:autosave]
|
330
|
+
|
331
|
+
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
332
|
+
|
333
|
+
if autosave
|
334
|
+
records_to_destroy = records.select(&:marked_for_destruction?)
|
335
|
+
records_to_destroy.each { |record| association.destroy(record) }
|
336
|
+
records -= records_to_destroy
|
337
|
+
end
|
338
|
+
|
339
|
+
records.each do |record|
|
340
|
+
next if record.destroyed?
|
341
|
+
|
342
|
+
saved = true
|
343
|
+
|
344
|
+
if autosave != false && (@new_record_before_save || record.new_record?)
|
345
|
+
if autosave
|
346
|
+
saved = association.insert_record(record, false)
|
347
|
+
else
|
348
|
+
association.insert_record(record) unless reflection.nested?
|
349
|
+
end
|
350
|
+
elsif autosave
|
351
|
+
saved = record.save(validate: false)
|
352
|
+
end
|
353
|
+
|
354
|
+
fail ActiveRecord::Rollback unless saved
|
355
|
+
end
|
356
|
+
@new_record_before_save = false unless reflection.macro == :has_and_belongs_to_many
|
357
|
+
end
|
358
|
+
|
359
|
+
# reconstruct the scope now that we know the owner's id
|
360
|
+
association.reset_scope if association.respond_to?(:reset_scope)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
|
365
|
+
# on the association.
|
366
|
+
#
|
367
|
+
# In addition, it will destroy the association if it was marked for
|
368
|
+
# destruction with mark_for_destruction.
|
369
|
+
#
|
370
|
+
# This all happens inside a transaction, _if_ the Transactions module is included into
|
371
|
+
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
372
|
+
def save_has_one_association(reflection)
|
373
|
+
association = association_instance_get(reflection.name)
|
374
|
+
record = association && association.load_target
|
375
|
+
if record && !record.destroyed?
|
376
|
+
autosave = reflection.options[:autosave]
|
377
|
+
|
378
|
+
if autosave && record.marked_for_destruction?
|
379
|
+
record.destroy
|
380
|
+
elsif autosave != false
|
381
|
+
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
382
|
+
|
383
|
+
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
|
384
|
+
unless reflection.through_reflection
|
385
|
+
record[reflection.foreign_key] = key
|
386
|
+
end
|
387
|
+
|
388
|
+
saved = record.save(validate: !autosave)
|
389
|
+
fail ActiveRecord::Rollback if !saved && autosave
|
390
|
+
saved
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# If the record is new or it has changed, returns true.
|
397
|
+
def record_changed?(reflection, record, key)
|
398
|
+
record.new_record? ||
|
399
|
+
record[reflection.foreign_key] != key ||
|
400
|
+
record.changed_attributes.include?(reflection.foreign_key)
|
401
|
+
end
|
402
|
+
|
403
|
+
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
404
|
+
#
|
405
|
+
# In addition, it will destroy the association if it was marked for destruction.
|
406
|
+
def save_belongs_to_association(reflection)
|
407
|
+
association = association_instance_get(reflection.name)
|
408
|
+
record = association && association.load_target
|
409
|
+
if record && !record.destroyed?
|
410
|
+
autosave = reflection.options[:autosave]
|
411
|
+
|
412
|
+
if autosave && record.marked_for_destruction?
|
413
|
+
self[reflection.foreign_key] = nil
|
414
|
+
record.destroy
|
415
|
+
elsif autosave != false
|
416
|
+
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
417
|
+
|
418
|
+
if association.updated?
|
419
|
+
association_id = record.send(reflection.options[:primary_key] || :id)
|
420
|
+
self[reflection.foreign_key] = association_id
|
421
|
+
association.loaded!
|
422
|
+
end
|
423
|
+
|
424
|
+
saved if autosave
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
data/lib/datastax_rails/base.rb
CHANGED
@@ -360,6 +360,7 @@ module DatastaxRails #:nodoc:
|
|
360
360
|
include Callbacks
|
361
361
|
include Reflection
|
362
362
|
include Associations
|
363
|
+
include AutosaveAssociation
|
363
364
|
include Scoping
|
364
365
|
include Timestamps
|
365
366
|
include Serialization
|
@@ -387,6 +388,10 @@ module DatastaxRails #:nodoc:
|
|
387
388
|
# See {DatastaxRails::WideStorageModel} or {DatastaxRails::Payload} model for an example
|
388
389
|
class_attribute :create_options
|
389
390
|
|
391
|
+
# Allows the setting of how frequently to commit data to solr. Default is 10s.
|
392
|
+
class_attribute :solr_commit_time
|
393
|
+
self.solr_commit_time = 10_000
|
394
|
+
|
390
395
|
# Stores the attribute that wide models should cluster on. Basically, this is the
|
391
396
|
# attribute that CQL uses to "group" columns into logical records even though they
|
392
397
|
# are stored on the same row.
|
@@ -401,9 +406,6 @@ module DatastaxRails #:nodoc:
|
|
401
406
|
class_attribute :serialized_attributes
|
402
407
|
self.serialized_attributes = {}
|
403
408
|
|
404
|
-
# Whether or not we are using solr legacy mappings
|
405
|
-
class_attribute :legacy_mapping
|
406
|
-
|
407
409
|
def initialize(attributes = {}, _options = {})
|
408
410
|
defaults = self.class.column_defaults.dup
|
409
411
|
defaults.each { |_k, v| v.duplicable? ? v.dup : v }
|
@@ -556,10 +558,6 @@ module DatastaxRails #:nodoc:
|
|
556
558
|
ancestors.include?(DatastaxRails::WideStorageModel)
|
557
559
|
end
|
558
560
|
|
559
|
-
def legacy_mapping?
|
560
|
-
legacy_mapping
|
561
|
-
end
|
562
|
-
|
563
561
|
def base_class
|
564
562
|
klass = self
|
565
563
|
klass = klass.superclass while klass.superclass != Base
|
@@ -15,11 +15,12 @@ module DatastaxRails
|
|
15
15
|
class_attribute :solr
|
16
16
|
end
|
17
17
|
|
18
|
-
module ClassMethods
|
18
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
19
19
|
DEFAULT_OPTIONS = {
|
20
|
-
servers:
|
21
|
-
|
22
|
-
|
20
|
+
servers: '127.0.0.1',
|
21
|
+
port: 9160,
|
22
|
+
connection_options: { timeout: 10 },
|
23
|
+
ssl: false
|
23
24
|
}
|
24
25
|
|
25
26
|
# Returns the current server that we are talking to. This is useful when you are talking to a
|
@@ -40,6 +41,11 @@ module DatastaxRails
|
|
40
41
|
#
|
41
42
|
# servers: ["10.1.2.5"]
|
42
43
|
# port: 9042
|
44
|
+
# ssl:
|
45
|
+
# cert: config/datastax_rails.crt
|
46
|
+
# key: config/datastax_rails.key
|
47
|
+
# ca_cert: config/ca.crt
|
48
|
+
# keypass: changeme
|
43
49
|
# keyspace: "datastax_rails_production"
|
44
50
|
# strategy_class: "org.apache.cassandra.locator.NetworkTopologyStrategy"
|
45
51
|
# strategy_options: {"DS1": "3", "DS2": "3", "DS3": "3"}
|
@@ -48,18 +54,13 @@ module DatastaxRails
|
|
48
54
|
# solr:
|
49
55
|
# port: 8983
|
50
56
|
# path: /solr
|
51
|
-
# ssl:
|
52
|
-
# use_ssl: true
|
53
|
-
# cert: config/datastax_rails.crt
|
54
|
-
# key: config/datastax_rails.key
|
55
|
-
# keypass: changeme
|
56
57
|
#
|
57
58
|
# The +servers+ entry should be a list of all seed nodes for servers you wish to connect to. DSR
|
58
59
|
# will automatically connect to all nodes in the cluster or in the datacenter if you are using multiple
|
59
60
|
# datacenters. You can safely just list all nodes in a particular datacenter if you would like.
|
60
61
|
#
|
61
|
-
# The port to connect to, this port will be used for all nodes. Because the `system.peers` table does
|
62
|
-
# the port that the nodes are listening on, the port must be the same for all nodes.
|
62
|
+
# The port to connect to, this port will be used for all nodes. Because the `system.peers` table does
|
63
|
+
# not contain the port that the nodes are listening on, the port must be the same for all nodes.
|
63
64
|
#
|
64
65
|
# Since we're using the NetworkTopologyStrategy for our locator, it is important that you configure
|
65
66
|
# cassandra-topology.properties. See the DSE documentation at http://www.datastax.com for more
|
@@ -73,36 +74,59 @@ module DatastaxRails
|
|
73
74
|
# * *retries* - Number of times a request will be retried. Should likely be the number of servers - 1.
|
74
75
|
# Defaults to 0.
|
75
76
|
# * *server_retry_period* - Amount of time to wait before retrying a down server. Defaults to 1.
|
76
|
-
# * *server_max_requests* - Number of requests to make to a server before moving to the next one (helps
|
77
|
-
# balanced). Default to nil which means cycling does not take place.
|
77
|
+
# * *server_max_requests* - Number of requests to make to a server before moving to the next one (helps
|
78
|
+
# keep load balanced). Default to nil which means cycling does not take place.
|
78
79
|
# * *retry_overrides* - Overrides retries option for individual exceptions.
|
79
80
|
# * *connect_timeout* - The connection timeout on the Thrift socket. Defaults to 0.1.
|
80
81
|
# * *timeout* - The timeout for the transport layer. Defaults to 1.
|
81
|
-
# * *timeout_overrides* - Overrides the timeout value for specific methods (advanced).
|
82
|
-
# * *exception_classes* - List of exceptions for which Thrift will automatically retry a new server in the cluster
|
83
|
-
# (up to retry limit).
|
84
|
-
# Defaults to [IOError, Thrift::Exception, Thrift::ApplicationException, Thrift::TransportException].
|
85
|
-
# * *exception_class_overrides* - List of exceptions which will never cause a retry.
|
86
|
-
# Defaults to [CassandraCQL::Thrift::InvalidRequestException].
|
87
|
-
# * *wrapped_exception_options* - List of exceptions that will be automatically wrapped in an exception provided
|
88
|
-
# by client class with the same name (advanced).
|
89
|
-
# Defaults to [Thrift::ApplicationException, Thrift::TransportException].
|
90
|
-
# * *raise* - Whether to raise exceptions or default calls that cause an error (advanced). Defaults to true
|
91
|
-
# (raise exceptions).
|
92
|
-
# * *defaults* - When raise is false and an error is encountered, these methods are called to default the return
|
93
|
-
# value (advanced). Should be a hash of method names to values.
|
94
|
-
# * *protocol* - The thrift protocol to use (advanced). Defaults to Thrift::BinaryProtocol.
|
95
|
-
# * *protocol_extra_params* - Any extra parameters to send to the protocol (advanced).
|
96
|
-
# * *transport* - The thrift transport to use (advanced). Defaults to Thrift::Socket.
|
97
|
-
# * *transport_wrapper* - The thrift transport wrapper to use (advanced). Defaults to Thrift::FramedTransport.
|
98
82
|
#
|
99
83
|
# See +solr_connection+ for a description of the solr options in datastax.yml
|
100
84
|
def establish_connection(spec)
|
101
85
|
DatastaxRails::Base.config = spec.with_indifferent_access
|
102
86
|
spec.reverse_merge!(DEFAULT_OPTIONS)
|
103
|
-
|
104
|
-
|
105
|
-
|
87
|
+
cql_options = { hosts: spec[:servers],
|
88
|
+
keyspace: spec[:keyspace],
|
89
|
+
connection_timeout: spec[:connection_options][:timeout] }
|
90
|
+
if ssl_type
|
91
|
+
ca_cert = Pathname.new(DatastaxRails::Base.config[:ssl][:ca_cert])
|
92
|
+
ca_cert = Rails.root.join(ca_cert) unless ca_cert.absolute?
|
93
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
94
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
95
|
+
ssl_context.ca_file = ca_cert.to_s
|
96
|
+
if ssl_type == :two_way_ssl
|
97
|
+
cert = Pathname.new(DatastaxRails::Base.config[:ssl][:cert])
|
98
|
+
key = Pathname.new(DatastaxRails::Base.config[:ssl][:key])
|
99
|
+
pass = DatastaxRails::Base.config[:ssl][:keypass]
|
100
|
+
cert = Rails.root.join(cert) unless cert.absolute?
|
101
|
+
key = Rails.root.join(key) unless key.absolute?
|
102
|
+
if pass
|
103
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(key.read, pass)
|
104
|
+
else
|
105
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(key.read)
|
106
|
+
end
|
107
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(cert.read)
|
108
|
+
end
|
109
|
+
cql_options[:ssl] = ssl_context
|
110
|
+
end
|
111
|
+
self.connection = ::Cql::Client.connect(cql_options)
|
112
|
+
end
|
113
|
+
|
114
|
+
def ssl_type
|
115
|
+
return false unless DatastaxRails::Base.config && DatastaxRails::Base.config[:ssl]
|
116
|
+
config = DatastaxRails::Base.config
|
117
|
+
if config[:ssl][:key] && config[:ssl][:cert]
|
118
|
+
:two_way_ssl
|
119
|
+
elsif config[:ssl][:ca_cert]
|
120
|
+
:one_way_ssl
|
121
|
+
else
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def reconnect
|
127
|
+
connection.close rescue true
|
128
|
+
self.connection = nil
|
129
|
+
establish_connection(DatastaxRails::Base.config)
|
106
130
|
end
|
107
131
|
|
108
132
|
# Returns the base portion of the URL for connecting to SOLR based on the current Cassandra server.
|
@@ -112,7 +136,7 @@ module DatastaxRails
|
|
112
136
|
DatastaxRails::Base.establish_connection unless connection
|
113
137
|
port = DatastaxRails::Base.config[:solr][:port]
|
114
138
|
path = DatastaxRails::Base.config[:solr][:path]
|
115
|
-
protocol =
|
139
|
+
protocol = ssl_type ? 'https' : 'http'
|
116
140
|
"#{protocol}://#{current_server}:#{port}#{path}"
|
117
141
|
end
|
118
142
|
|
@@ -135,17 +159,18 @@ module DatastaxRails
|
|
135
159
|
# @return [RSolr::Client] RSolr client object
|
136
160
|
def establish_solr_connection
|
137
161
|
opts = { url: "#{solr_base_url}/#{DatastaxRails::Base.connection.keyspace}.#{column_family}" }
|
138
|
-
if
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
162
|
+
if ssl_type == :two_way_ssl
|
163
|
+
ca_cert = Pathname.new(DatastaxRails::Base.config[:ssl][:ca_cert])
|
164
|
+
cert = Pathname.new(DatastaxRails::Base.config[:ssl][:cert])
|
165
|
+
key = Pathname.new(DatastaxRails::Base.config[:ssl][:key])
|
166
|
+
pass = DatastaxRails::Base.config[:ssl][:keypass]
|
167
|
+
ca_cert = Rails.root.join(ca_cert) unless ca_cert.absolute?
|
144
168
|
cert = Rails.root.join(cert) unless cert.absolute?
|
145
169
|
key = Rails.root.join(key) unless key.absolute?
|
146
170
|
opts[:ssl_cert_file] = cert.to_s
|
147
171
|
opts[:ssl_key_file] = key.to_s
|
148
172
|
opts[:ssl_key_pass] = pass if pass
|
173
|
+
opts[:ssl_ca_file] = ca_cert.to_s
|
149
174
|
|
150
175
|
RSolr::ClientCert.connect opts
|
151
176
|
else
|
data/lib/datastax_rails/cql.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
module DatastaxRails
|
2
|
+
# The Cql classes handle all of the generation of CQL. They are constructed in such a way that
|
3
|
+
# the statement can be built up over multiple calls before generating the actual CQL.
|
4
|
+
#
|
5
|
+
# TODO: Add examples
|
2
6
|
module Cql
|
3
7
|
extend ActiveSupport::Autoload
|
4
8
|
class << self
|
@@ -27,11 +27,23 @@ module DatastaxRails
|
|
27
27
|
puts cql if ENV['DEBUG_CQL'] == 'true'
|
28
28
|
pp @values if ENV['DEBUG_CQL'] == 'true'
|
29
29
|
digest = Digest::MD5.digest cql
|
30
|
-
|
31
|
-
|
32
|
-
stmt.
|
33
|
-
|
34
|
-
|
30
|
+
try_again = true
|
31
|
+
begin
|
32
|
+
stmt = DatastaxRails::Base.statement_cache[digest] ||= DatastaxRails::Base.connection.prepare(cql)
|
33
|
+
if @consistency
|
34
|
+
stmt.execute(*@values, consistency: @consistency)
|
35
|
+
else
|
36
|
+
stmt.execute(*@values)
|
37
|
+
end
|
38
|
+
rescue ::Cql::NotConnectedError
|
39
|
+
if try_again
|
40
|
+
Rails.logger.warn('Lost connection to Cassandra. Attempting to reconnect...')
|
41
|
+
try_again = false
|
42
|
+
DatastaxRails::Base.reconnect
|
43
|
+
retry
|
44
|
+
else
|
45
|
+
raise
|
46
|
+
end
|
35
47
|
end
|
36
48
|
end
|
37
49
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
en:
|
2
|
+
# Attributes names common to most models
|
3
|
+
#attributes:
|
4
|
+
#created_at: "Created at"
|
5
|
+
#updated_at: "Updated at"
|
6
|
+
|
7
|
+
# Default error messages
|
8
|
+
errors:
|
9
|
+
messages:
|
10
|
+
taken: "has already been taken"
|
11
|
+
|
12
|
+
# Active Record models configuration
|
13
|
+
activerecord:
|
14
|
+
errors:
|
15
|
+
messages:
|
16
|
+
record_invalid: "Validation failed: %{errors}"
|
17
|
+
restrict_dependent_destroy:
|
18
|
+
one: "Cannot delete record because a dependent %{record} exists"
|
19
|
+
many: "Cannot delete record because dependent %{record} exist"
|
20
|
+
# Append your own errors here or at the model/attributes scope.
|
21
|
+
|
22
|
+
# You can define own errors for models or model attributes.
|
23
|
+
# The values :model, :attribute and :value are always available for interpolation.
|
24
|
+
#
|
25
|
+
# For example,
|
26
|
+
# models:
|
27
|
+
# user:
|
28
|
+
# blank: "This is a custom blank message for %{model}: %{attribute}"
|
29
|
+
# attributes:
|
30
|
+
# login:
|
31
|
+
# blank: "This is a custom blank message for User login"
|
32
|
+
# Will define custom blank validation message for User model and
|
33
|
+
# custom blank validation message for login attribute of User model.
|
34
|
+
#models:
|
35
|
+
|
36
|
+
# Translate model names. Used in Model.human_name().
|
37
|
+
#models:
|
38
|
+
# For example,
|
39
|
+
# user: "Dude"
|
40
|
+
# will translate User model name to "Dude"
|
41
|
+
|
42
|
+
# Translate model attribute names. Used in Model.human_attribute_name(attribute).
|
43
|
+
#attributes:
|
44
|
+
# For example,
|
45
|
+
# user:
|
46
|
+
# login: "Handle"
|
47
|
+
# will translate User attribute "login" as "Handle"
|
@@ -9,8 +9,7 @@ module DatastaxRails
|
|
9
9
|
alias_method :new_record?, :new_record
|
10
10
|
end
|
11
11
|
|
12
|
-
# rubocop:disable Style/Documentation
|
13
|
-
module ClassMethods
|
12
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
14
13
|
# Removes one or more records with corresponding keys. Last parameter can be a hash
|
15
14
|
# specifying the consistency level. The keys should be in the form returned by
|
16
15
|
# +#id_for_update+
|
@@ -17,6 +17,7 @@ module DatastaxRails
|
|
17
17
|
self.reflections = {}
|
18
18
|
end
|
19
19
|
|
20
|
+
# rubocop:disable Style/Documentation
|
20
21
|
module ClassMethods
|
21
22
|
def create_reflection(macro, name, options, datastax_rails)
|
22
23
|
klass = options[:through] ? ThroughReflection : AssociationReflection
|
@@ -280,6 +281,10 @@ module DatastaxRails
|
|
280
281
|
end
|
281
282
|
end
|
282
283
|
|
284
|
+
def nested?
|
285
|
+
false
|
286
|
+
end
|
287
|
+
|
283
288
|
private
|
284
289
|
|
285
290
|
def derive_class_name
|
@@ -352,6 +357,11 @@ module DatastaxRails
|
|
352
357
|
end
|
353
358
|
end
|
354
359
|
|
360
|
+
# A through association is nested if there would be more than one join table
|
361
|
+
def nested?
|
362
|
+
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
|
363
|
+
end
|
364
|
+
|
355
365
|
# Consider the following example:
|
356
366
|
#
|
357
367
|
# class Person
|
@@ -65,7 +65,7 @@ module DatastaxRails
|
|
65
65
|
say 'Using custom solrconfig file', :subitem
|
66
66
|
solrconfig = Rails.root.join('config', 'solr', "#{model.column_family}-solrconfig.xml").read
|
67
67
|
else
|
68
|
-
@
|
68
|
+
@solr_commit_time = model.solr_commit_time
|
69
69
|
solrconfig = ERB.new(File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'solrconfig.xml.erb'))).result(binding)
|
70
70
|
end
|
71
71
|
if Rails.root.join('config', 'solr', "#{model.column_family}-stopwords.txt").exist?
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DatastaxRails::Base do
|
4
|
+
describe 'Autosave Associations' do
|
5
|
+
describe 'collections' do
|
6
|
+
it 'saves child records built via the association' do
|
7
|
+
p = Person.new(name: 'Jim')
|
8
|
+
c1 = p.cars.build(name: 'Jeep')
|
9
|
+
c2 = p.cars.build(name: 'Ford')
|
10
|
+
p.save
|
11
|
+
Person.commit_solr
|
12
|
+
expect(Car.find(c1.id).person).to eq(p)
|
13
|
+
expect(Car.find(c2.id).person).to eq(p)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,9 +3,8 @@ require 'spec_helper'
|
|
3
3
|
describe DatastaxRails::Relation do
|
4
4
|
before(:each) do
|
5
5
|
@relation = DatastaxRails::Relation.new(Hobby, 'hobbies')
|
6
|
-
('a'..'l').each_with_index do |letter,
|
6
|
+
('a'..'l').each_with_index do |letter, _idx|
|
7
7
|
Hobby.create(name: letter)
|
8
|
-
sleep(1) if idx % 5 == 4 # Performance hack
|
9
8
|
end
|
10
9
|
Hobby.commit_solr
|
11
10
|
end
|
@@ -13,7 +12,6 @@ describe DatastaxRails::Relation do
|
|
13
12
|
%w(cassandra solr).each do |access_method|
|
14
13
|
describe '#find_each' do
|
15
14
|
it "returns each record one at a time with #{access_method}" do
|
16
|
-
sleep(1)
|
17
15
|
missed_hobbies = ('a'..'l').to_a
|
18
16
|
@relation.send('with_' + access_method).find_each(batch_size: 5) do |hobby|
|
19
17
|
missed_hobbies.delete_if { |h| h == hobby.name }
|
@@ -0,0 +1,19 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDKDCCApGgAwIBAgIJAIgWnQPYsbWtMA0GCSqGSIb3DQEBBQUAMGwxCzAJBgNV
|
3
|
+
BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTEPMA0GA1UEBxMGUmVzdG9uMRswGQYD
|
4
|
+
VQQKExJPcGVuIFNvdXJjZSBDZW50ZXIxDTALBgNVBAsTBFNBREUxDTALBgNVBAMT
|
5
|
+
BFNBREUwHhcNMTAwODA2MTMzMzA1WhcNMjAwODAzMTMzMzA1WjBsMQswCQYDVQQG
|
6
|
+
EwJVUzERMA8GA1UECBMIVmlyZ2luaWExDzANBgNVBAcTBlJlc3RvbjEbMBkGA1UE
|
7
|
+
ChMST3BlbiBTb3VyY2UgQ2VudGVyMQ0wCwYDVQQLEwRTQURFMQ0wCwYDVQQDEwRT
|
8
|
+
QURFMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDxn2DOHCUMsJoD4MoUEzpW
|
9
|
+
1WFNag+ogebitEL1OwDeUuFuwZJAnOk3Gipw7ye27fU8qb+62BtHjg2EbnRa5NSG
|
10
|
+
teS4HnCIt149FzgB06A2E+6s10oX9ehqT1FpALLFEyf/KaP82KPVeDq4ki68LIVJ
|
11
|
+
+YrvBbgNpH7a1czlXeVNNQIDAQABo4HRMIHOMB0GA1UdDgQWBBREhQm645qfcEw3
|
12
|
+
uNXO6gaROCpSJzCBngYDVR0jBIGWMIGTgBREhQm645qfcEw3uNXO6gaROCpSJ6Fw
|
13
|
+
pG4wbDELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMQ8wDQYDVQQHEwZS
|
14
|
+
ZXN0b24xGzAZBgNVBAoTEk9wZW4gU291cmNlIENlbnRlcjENMAsGA1UECxMEU0FE
|
15
|
+
RTENMAsGA1UEAxMEU0FERYIJAIgWnQPYsbWtMAwGA1UdEwQFMAMBAf8wDQYJKoZI
|
16
|
+
hvcNAQEFBQADgYEA2ieesbXFL3jG3ebBmQLtHwjcWlQYcVGTq+wjxCBuBcZXEVFn
|
17
|
+
Qi6XxbCvRSm0lLWYRHEc80WL3FezfREY43yiewnT24S8oKYlRb2v7Yc2sUjE72Ue
|
18
|
+
nV7M6s4CPmqIYGkH+ukbxQVnAE0f1BNIoTsPx9XFr6Yx6kxZ4qSWc2IjuO4=
|
19
|
+
-----END CERTIFICATE-----
|
@@ -12,8 +12,12 @@ productiont:
|
|
12
12
|
path: /solr
|
13
13
|
|
14
14
|
development:
|
15
|
-
servers: ["
|
15
|
+
servers: ["sade-jasonk"]
|
16
16
|
port: 9042
|
17
|
+
ssl:
|
18
|
+
#cert: config/datastax_rails.crt
|
19
|
+
#key: config/datastax_rails.key
|
20
|
+
#ca_cert: config/ca.crt
|
17
21
|
keyspace: "datastax_rails_development"
|
18
22
|
strategy_class: "org.apache.cassandra.locator.SimpleStrategy"
|
19
23
|
strategy_options: {"replication_factor": "1"}
|
@@ -24,8 +28,12 @@ development:
|
|
24
28
|
path: /solr
|
25
29
|
|
26
30
|
test:
|
27
|
-
servers: ["
|
31
|
+
servers: ["sade-jasonk"]
|
28
32
|
port: 9042
|
33
|
+
ssl:
|
34
|
+
#cert: config/datastax_rails.crt
|
35
|
+
#key: config/datastax_rails.key
|
36
|
+
#ca_cert: config/ca.crt
|
29
37
|
keyspace: "datastax_rails_test"
|
30
38
|
strategy_class: "org.apache.cassandra.locator.SimpleStrategy"
|
31
39
|
strategy_options: {"replication_factor": "1"}
|
data/spec/dummy/log/test.log
CHANGED
@@ -15607,3 +15607,4 @@ Reconnecting and retrying
|
|
15607
15607
|
Error connecting to database
|
15608
15608
|
the scheme http does not accept registry part: :8983 (or bad hostname?)
|
15609
15609
|
Reconnecting and retrying
|
15610
|
+
Lost connection to Cassandra. Attempting to reconnect...
|
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.18
|
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-
|
11
|
+
date: 2014-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -202,6 +202,7 @@ files:
|
|
202
202
|
- lib/datastax_rails/attribute_methods/read.rb
|
203
203
|
- lib/datastax_rails/attribute_methods/typecasting.rb
|
204
204
|
- lib/datastax_rails/attribute_methods/write.rb
|
205
|
+
- lib/datastax_rails/autosave_association.rb
|
205
206
|
- lib/datastax_rails/base.rb
|
206
207
|
- lib/datastax_rails/callbacks.rb
|
207
208
|
- lib/datastax_rails/cassandra_only_model.rb
|
@@ -230,6 +231,7 @@ files:
|
|
230
231
|
- lib/datastax_rails/errors.rb
|
231
232
|
- lib/datastax_rails/grouped_collection.rb
|
232
233
|
- lib/datastax_rails/inheritance.rb
|
234
|
+
- lib/datastax_rails/locale/en.yml
|
233
235
|
- lib/datastax_rails/payload_model.rb
|
234
236
|
- lib/datastax_rails/persistence.rb
|
235
237
|
- lib/datastax_rails/railtie.rb
|
@@ -272,6 +274,7 @@ files:
|
|
272
274
|
- spec/datastax_rails/associations_spec.rb
|
273
275
|
- spec/datastax_rails/attribute_methods/typecasting_spec.rb
|
274
276
|
- spec/datastax_rails/attribute_methods_spec.rb
|
277
|
+
- spec/datastax_rails/autosave_association_spec.rb
|
275
278
|
- spec/datastax_rails/base_spec.rb
|
276
279
|
- spec/datastax_rails/callbacks_spec.rb
|
277
280
|
- spec/datastax_rails/column_spec.rb
|
@@ -309,6 +312,7 @@ files:
|
|
309
312
|
- spec/dummy/config.ru
|
310
313
|
- spec/dummy/config/application.rb
|
311
314
|
- spec/dummy/config/boot.rb
|
315
|
+
- spec/dummy/config/ca.crt
|
312
316
|
- spec/dummy/config/database.yml
|
313
317
|
- spec/dummy/config/datastax.yml
|
314
318
|
- spec/dummy/config/datastax_rails.crt
|
@@ -393,6 +397,7 @@ test_files:
|
|
393
397
|
- spec/datastax_rails/cql/update_spec.rb
|
394
398
|
- spec/datastax_rails/cql/base_spec.rb
|
395
399
|
- spec/datastax_rails/cql/select_spec.rb
|
400
|
+
- spec/datastax_rails/autosave_association_spec.rb
|
396
401
|
- spec/datastax_rails/persistence_spec.rb
|
397
402
|
- spec/datastax_rails/relation/stats_methods_spec.rb
|
398
403
|
- spec/datastax_rails/relation/spawn_methods_spec.rb
|
@@ -440,6 +445,7 @@ test_files:
|
|
440
445
|
- spec/dummy/config/application.rb
|
441
446
|
- spec/dummy/config/datastax.yml
|
442
447
|
- spec/dummy/config/routes.rb
|
448
|
+
- spec/dummy/config/ca.crt
|
443
449
|
- spec/dummy/config/boot.rb
|
444
450
|
- spec/dummy/config/datastax_rails.crt
|
445
451
|
- spec/dummy/script/rails
|