datastax_rails 2.0.16 → 2.0.18
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/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
|