datastax_rails 1.0.19.0 → 1.1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +13 -2
- data/config/solrconfig.xml +3 -0
- data/lib/datastax_rails/associations/collection_association.rb +31 -0
- data/lib/datastax_rails/attribute_methods/definition.rb +2 -2
- data/lib/datastax_rails/base.rb +3 -7
- data/lib/datastax_rails/connection.rb +1 -0
- data/lib/datastax_rails/cql/alter_column_family.rb +9 -0
- data/lib/datastax_rails/cql/base.rb +2 -1
- data/lib/datastax_rails/cql/create_column_family.rb +3 -3
- data/lib/datastax_rails/cql/create_index.rb +25 -0
- data/lib/datastax_rails/cql/create_keyspace.rb +3 -3
- data/lib/datastax_rails/cql/delete.rb +3 -3
- data/lib/datastax_rails/cql/drop_index.rb +13 -0
- data/lib/datastax_rails/cql/insert.rb +2 -2
- data/lib/datastax_rails/cql/select.rb +2 -2
- data/lib/datastax_rails/cql/update.rb +20 -20
- data/lib/datastax_rails/cql.rb +2 -0
- data/lib/datastax_rails/persistence.rb +2 -10
- data/lib/datastax_rails/railtie.rb +7 -0
- data/lib/datastax_rails/relation/batches.rb +23 -10
- data/lib/datastax_rails/relation/facet_methods.rb +17 -0
- data/lib/datastax_rails/relation/finder_methods.rb +2 -2
- data/lib/datastax_rails/relation/search_methods.rb +1 -1
- data/lib/datastax_rails/relation.rb +14 -6
- data/lib/datastax_rails/tasks/column_family.rb +97 -18
- data/lib/datastax_rails/tasks/ds.rake +11 -0
- data/lib/datastax_rails/types/array_type.rb +1 -1
- data/lib/datastax_rails/types/boolean_type.rb +1 -1
- data/lib/datastax_rails/types/date_type.rb +1 -1
- data/lib/datastax_rails/types/float_type.rb +1 -1
- data/lib/datastax_rails/types/integer_type.rb +1 -1
- data/lib/datastax_rails/types/string_type.rb +2 -2
- data/lib/datastax_rails/types/text_type.rb +3 -4
- data/lib/datastax_rails/types/time_type.rb +1 -1
- data/lib/datastax_rails/validations/associated.rb +43 -0
- data/lib/datastax_rails/validations.rb +14 -2
- data/lib/datastax_rails/version.rb +1 -1
- data/lib/datastax_rails.rb +14 -14
- data/spec/datastax_rails/associations/has_many_association_spec.rb +1 -0
- data/spec/datastax_rails/base_spec.rb +6 -0
- data/spec/datastax_rails/cql/select_spec.rb +3 -3
- data/spec/datastax_rails/cql/update_spec.rb +2 -2
- data/spec/datastax_rails/persistence_spec.rb +16 -12
- data/spec/datastax_rails/relation/batches_spec.rb +20 -16
- data/spec/datastax_rails/relation/finder_methods_spec.rb +2 -2
- data/spec/dummy/log/test.log +3316 -0
- data/spec/spec.opts +0 -1
- data/spec/support/connection_double.rb +6 -0
- data/spec/support/default_consistency_shared_examples.rb +4 -2
- metadata +86 -107
@@ -46,7 +46,7 @@ module DatastaxRails
|
|
46
46
|
if coder.options[:solr_type]
|
47
47
|
@fields.push({ :name => attr.name,
|
48
48
|
:type => coder.options[:solr_type].to_s,
|
49
|
-
:indexed => coder.options[:indexed].to_s,
|
49
|
+
:indexed => (coder.options[:indexed] == :solr).to_s,
|
50
50
|
:stored => coder.options[:stored].to_s,
|
51
51
|
:multi_valued => coder.options[:multi_valued].to_s })
|
52
52
|
end
|
@@ -72,7 +72,10 @@ module DatastaxRails
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def reindex_solr(model)
|
75
|
-
if model == '
|
75
|
+
if model == 'all'
|
76
|
+
Dir[Rails.root.join("app","models",'*.rb').to_s].each do |file|
|
77
|
+
require File.basename(file, File.extname(file))
|
78
|
+
end
|
76
79
|
models_to_index = DatastaxRails::Base.models
|
77
80
|
else
|
78
81
|
models_to_index = [model.constantize]
|
@@ -90,14 +93,38 @@ module DatastaxRails
|
|
90
93
|
end
|
91
94
|
end
|
92
95
|
|
96
|
+
def create_solr_core(model)
|
97
|
+
if model == 'all'
|
98
|
+
Dir[Rails.root.join("app","models",'*.rb').to_s].each do |file|
|
99
|
+
require File.basename(file, File.extname(file))
|
100
|
+
end
|
101
|
+
cores_to_create = DatastaxRails::Base.models
|
102
|
+
else
|
103
|
+
cores_to_create = [model.constantize]
|
104
|
+
end
|
105
|
+
cores_to_create.each do |m|
|
106
|
+
next if m.payload_model?
|
107
|
+
# Create the SOLR Core
|
108
|
+
url = "#{DatastaxRails::Base.solr_base_url}/admin/cores?action=CREATE&name=#{DatastaxRails::Base.config[:keyspace]}.#{m.column_family}&recovery=true"
|
109
|
+
puts "Posting create command to '#{url}'"
|
110
|
+
`curl -s -X POST '#{url}'`
|
111
|
+
if Rails.env.production?
|
112
|
+
sleep(5)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
93
117
|
def upload_solr_schemas(column_family)
|
94
118
|
force = !column_family.nil?
|
95
119
|
column_family ||= :all
|
96
120
|
# Ensure schema migrations CF exists
|
97
|
-
unless
|
121
|
+
unless column_family_exists?('schema_migrations')
|
122
|
+
puts "Creating schema_migrations column family"
|
98
123
|
connection.execute_cql_query(DatastaxRails::Cql::CreateColumnFamily.new('schema_migrations').key_type(:text).columns(:digest => :text, :solrconfig => :text, :stopwords => :text).to_cql)
|
99
124
|
end
|
100
125
|
|
126
|
+
check_key_name('schema_migrations')
|
127
|
+
|
101
128
|
solrconfig = File.read(File.join(File.dirname(__FILE__),"..","..","..","config","solrconfig.xml"))
|
102
129
|
stopwords = File.read(File.join(File.dirname(__FILE__),"..","..","..","config","stopwords.txt"))
|
103
130
|
solrconfig_digest = Digest::SHA1.hexdigest(solrconfig)
|
@@ -121,28 +148,23 @@ module DatastaxRails
|
|
121
148
|
models_to_upload.each do |model|
|
122
149
|
if model.payload_model?
|
123
150
|
next if model == DatastaxRails::PayloadModel
|
124
|
-
unless
|
151
|
+
unless column_family_exists?(model.column_family.to_s)
|
125
152
|
puts "Creating payload model #{model.column_family}"
|
126
153
|
columns = {:chunk => :int, :payload => :text}
|
127
|
-
cql = DatastaxRails::Cql::CreateColumnFamily.new(model.column_family).key_name(:digest).key_columns("digest
|
154
|
+
cql = DatastaxRails::Cql::CreateColumnFamily.new(model.column_family).key_name(:digest).key_columns("digest, chunk").key_type(:text).columns(columns).with("COMPACT STORAGE").to_cql
|
128
155
|
puts cql
|
129
156
|
connection.execute_cql_query(cql)
|
130
157
|
end
|
131
158
|
else
|
132
159
|
newcf = false
|
133
|
-
|
134
|
-
unless connection.schema.column_families[model.column_family.to_s]
|
160
|
+
unless column_family_exists?(model.column_family.to_s)
|
135
161
|
newcf = true
|
136
162
|
puts "Creating normal model #{model.column_family}"
|
137
|
-
cql = DatastaxRails::Cql::CreateColumnFamily.new(model.column_family).key_type(:text).columns(:updated_at => :text, :created_at => :text).to_cql
|
138
|
-
puts cql
|
139
|
-
connection.execute_cql_query(cql)
|
140
|
-
sleep(5) if Rails.env.production?
|
141
163
|
end
|
142
164
|
schema = generate_solr_schema(model)
|
143
165
|
schema_digest = Digest::SHA1.hexdigest(schema)
|
144
166
|
|
145
|
-
results = DatastaxRails::Cql::Select.new(SchemaMigration, ['*']).conditions(:
|
167
|
+
results = DatastaxRails::Cql::Select.new(SchemaMigration, ['*']).conditions(:key => model.column_family).execute
|
146
168
|
sm_digests = CassandraCQL::Result.new(results).fetch.try(:to_hash) || {}
|
147
169
|
|
148
170
|
solr_url = "#{DatastaxRails::Base.solr_base_url}/resource/#{DatastaxRails::Base.config[:keyspace]}.#{model.column_family}"
|
@@ -194,7 +216,7 @@ module DatastaxRails
|
|
194
216
|
break
|
195
217
|
end
|
196
218
|
DatastaxRails::Cql::Update.new(SchemaMigration, model.column_family).columns(:digest => schema_digest).execute
|
197
|
-
|
219
|
+
reindex_solr(model.to_s) unless newcf
|
198
220
|
end
|
199
221
|
|
200
222
|
if newcf
|
@@ -207,21 +229,41 @@ module DatastaxRails
|
|
207
229
|
end
|
208
230
|
end
|
209
231
|
|
210
|
-
|
232
|
+
check_key_name(model.column_family)
|
233
|
+
|
234
|
+
# Check for missing columns or columns needing cassandra indexes
|
211
235
|
model.attribute_definitions.each do |attribute, definition|
|
212
|
-
|
213
|
-
#!definition.coder.options[:stored] &&
|
214
|
-
#!definition.coder.options[:indexed]
|
215
|
-
|
236
|
+
unless column_exists?(model.column_family.to_s, attribute.to_s)
|
216
237
|
puts "Adding column '#{attribute}' to '#{model.column_family}'"
|
217
238
|
DatastaxRails::Cql::AlterColumnFamily.new(model.column_family).add(attribute => :text).execute
|
218
239
|
end
|
240
|
+
if(definition.coder.options[:indexed] == :cassandra)
|
241
|
+
unless index_exists?(model.column_family.to_s, attribute.to_s)
|
242
|
+
if index_exists?(model.column_family.to_s, attribute.to_s)
|
243
|
+
puts "Dropping solr index on #{model.column_family.to_s}.#{attribute.to_s}"
|
244
|
+
DatastaxRails::Cql::DropIndex.new(solr_index_cql_name(model.column_family.to_s, attribute.to_s)).execute
|
245
|
+
end
|
246
|
+
puts "Creating cassandra index on #{model.column_family.to_s}.#{attribute.to_s}"
|
247
|
+
DatastaxRails::Cql::CreateIndex.new(cassandra_index_cql_name(model.column_family.to_s, attribute.to_s)).on(model.column_family.to_s).column(attribute.to_s).execute
|
248
|
+
end
|
249
|
+
end
|
219
250
|
end
|
220
251
|
end
|
221
252
|
end
|
222
253
|
end
|
223
254
|
|
224
255
|
private
|
256
|
+
|
257
|
+
def check_key_name(cf)
|
258
|
+
klass = OpenStruct.new(:column_family => 'system.schema_columnfamilies', :default_consistency => 'QUORUM')
|
259
|
+
cql = DatastaxRails::Cql::ColumnFamily.new(klass)
|
260
|
+
results = CassandraCQL::Result.new(cql.select("key_alias, key_aliases").conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf).execute)
|
261
|
+
result = results.fetch
|
262
|
+
if(result['key_alias'] == 'KEY' && (result['key_aliases'].blank? || !result['key_aliases'].include?('key')))
|
263
|
+
puts "Renaming KEY column for #{cf}"
|
264
|
+
DatastaxRails::Cql::AlterColumnFamily.new(cf).rename("KEY",'key').execute
|
265
|
+
end
|
266
|
+
end
|
225
267
|
|
226
268
|
def connection
|
227
269
|
DatastaxRails::Base.connection
|
@@ -245,6 +287,43 @@ module DatastaxRails
|
|
245
287
|
|
246
288
|
cf
|
247
289
|
end
|
290
|
+
|
291
|
+
# def solr_index_system_name(cf, column)
|
292
|
+
# "#{@keyspace}.#{cf.to_s}#{column.to_s}"
|
293
|
+
# end
|
294
|
+
|
295
|
+
def solr_index_cql_name(cf, column)
|
296
|
+
"#{@keyspace}_#{cf.to_s}_#{column.to_s}_index"
|
297
|
+
end
|
298
|
+
|
299
|
+
def cassandra_index_cql_name(cf, column)
|
300
|
+
"#{cf.to_s}_#{column.to_s}_idx"
|
301
|
+
end
|
302
|
+
|
303
|
+
# def cassandra_index_system_name(cf, column)
|
304
|
+
# "#{cf.to_s}.#{cf.to_s}_#{column.to_s}_idx"
|
305
|
+
# end
|
306
|
+
|
307
|
+
def column_family_exists?(cf)
|
308
|
+
klass = OpenStruct.new(:column_family => 'system.schema_columnfamilies', :default_consistency => 'QUORUM')
|
309
|
+
cql = DatastaxRails::Cql::ColumnFamily.new(klass)
|
310
|
+
results = CassandraCQL::Result.new(cql.select("count(*)").conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf).execute)
|
311
|
+
results.fetch['count'] > 0
|
312
|
+
end
|
313
|
+
|
314
|
+
def column_exists?(cf, col)
|
315
|
+
klass = OpenStruct.new(:column_family => 'system.schema_columns', :default_consistency => 'QUORUM')
|
316
|
+
cql = DatastaxRails::Cql::ColumnFamily.new(klass)
|
317
|
+
results = CassandraCQL::Result.new(cql.select("count(*)").conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf, 'column_name' => col).execute)
|
318
|
+
results.fetch['count'] > 0
|
319
|
+
end
|
320
|
+
|
321
|
+
def index_exists?(cf, col)
|
322
|
+
klass = OpenStruct.new(:column_family => 'system.schema_columns', :default_consistency => 'QUORUM')
|
323
|
+
cql = DatastaxRails::Cql::ColumnFamily.new(klass)
|
324
|
+
results = CassandraCQL::Result.new(cql.select("index_name").conditions('keyspace_name' => @keyspace, 'columnfamily_name' => cf, 'column_name' => col).execute)
|
325
|
+
results.fetch['index_name'] != nil
|
326
|
+
end
|
248
327
|
end
|
249
328
|
end
|
250
329
|
end
|
@@ -60,6 +60,17 @@ namespace :ds do
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
desc 'Create SOLR Core (Normally not needed) -- pass in a model name (:all creates everything)'
|
64
|
+
task :create_core, [:model] => :configure do |t, args|
|
65
|
+
if args[:model].blank?
|
66
|
+
puts "\nUSAGE: rake ds:create_core[Model]"
|
67
|
+
else
|
68
|
+
cf = DatastaxRails::Tasks::ColumnFamily.new(@config['keyspace'])
|
69
|
+
puts "Creating core #{args[:model]}"
|
70
|
+
cf.create_solr_core(args[:model])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
63
74
|
desc 'Load the seed data from ds/seeds.rb'
|
64
75
|
task :seed => :environment do
|
65
76
|
seed_file = Rails.root.join("ks","seeds.rb")
|
@@ -7,7 +7,7 @@ module DatastaxRails
|
|
7
7
|
#
|
8
8
|
# That would give you all the posts that have Technology somewhere in the tags array.
|
9
9
|
class ArrayType < BaseType
|
10
|
-
DEFAULTS = {:solr_type => 'array', :indexed =>
|
10
|
+
DEFAULTS = {:solr_type => 'array', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => false, :tokenized => true, :fulltext => true}
|
11
11
|
|
12
12
|
# An extension to normal arrays that allow for tracking of dirty values. This is
|
13
13
|
# used by ActiveModel's change tracking framework.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class BooleanType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'boolean', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'boolean', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => true, :tokenized => false, :fulltext => false}
|
5
5
|
TRUE_VALS = [true, 'true', '1', 'Y']
|
6
6
|
FALSE_VALS = [false, 'false', '0', '', 'N', nil, 'null']
|
7
7
|
VALID_VALS = TRUE_VALS + FALSE_VALS
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class DateType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'date', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'date', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => true, :tokenized => false, :fulltext => false}
|
5
5
|
FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
6
6
|
|
7
7
|
def encode(value)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class FloatType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'float', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'float', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => true, :tokenized => false, :fulltext => false}
|
5
5
|
REGEX = /\A[-+]?(\d+(\.\d+)?|\.\d+)\Z/
|
6
6
|
def encode(float)
|
7
7
|
return -10191980.0 if float.blank?
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class IntegerType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'int', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'int', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => true, :tokenized => false, :fulltext => false}
|
5
5
|
REGEX = /\A[-+]?\d+\Z/
|
6
6
|
def encode(int)
|
7
7
|
return -10191980 if int.blank?
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class StringType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'string', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'string', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => true, :tokenized => false, :fulltext => true}
|
5
5
|
def encode(str)
|
6
6
|
str = "" unless str
|
7
7
|
str.to_s
|
8
8
|
end
|
9
9
|
|
10
10
|
def wrap(record, name, value)
|
11
|
-
txt = (value.frozen? ? value.dup : value)
|
11
|
+
txt = (value.frozen? ? value.to_s.dup : value)
|
12
12
|
txt.respond_to?(:force_encoding) ? txt.force_encoding('UTF-8') : txt
|
13
13
|
end
|
14
14
|
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class TextType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'text', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'text', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => false, :tokenized => true, :fulltext => true}
|
5
5
|
def encode(str)
|
6
|
-
|
7
|
-
str.dup
|
6
|
+
str.to_s.dup
|
8
7
|
end
|
9
8
|
|
10
9
|
def wrap(record, name, value)
|
@@ -13,4 +12,4 @@ module DatastaxRails
|
|
13
12
|
end
|
14
13
|
end
|
15
14
|
end
|
16
|
-
end
|
15
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DatastaxRails
|
2
2
|
module Types
|
3
3
|
class TimeType < BaseType
|
4
|
-
DEFAULTS = {:solr_type => 'date', :indexed =>
|
4
|
+
DEFAULTS = {:solr_type => 'date', :indexed => :solr, :stored => true, :multi_valued => false, :sortable => true, :tokenized => false, :fulltext => false}
|
5
5
|
FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
6
6
|
|
7
7
|
def encode(time)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module DatastaxRails
|
2
|
+
module Validations
|
3
|
+
class AssociatedValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
if Array.wrap(value).reject {|r| r.destroyed? || r.valid?}.any?
|
6
|
+
record.errors.add(attribute, :invalid, options.merge(:value => value))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
|
13
|
+
#
|
14
|
+
# class Book < DatastaxRails::Base
|
15
|
+
# has_many :pages
|
16
|
+
# belongs_to :library
|
17
|
+
#
|
18
|
+
# validates_associated :pages, :library
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion.
|
22
|
+
#
|
23
|
+
# NOTE: This validation will not fail if the association hasn't been assigned. If you want to
|
24
|
+
# ensure that the association is both present and guaranteed to be valid, you also need to
|
25
|
+
# use +validates_presence_of+.
|
26
|
+
#
|
27
|
+
# Configuration options:
|
28
|
+
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
|
29
|
+
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
|
30
|
+
# validation contexts by default (+nil+), other options are <tt>:create</tt>
|
31
|
+
# and <tt>:update</tt>.
|
32
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
33
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
34
|
+
# method, proc or string should return or evaluate to a true or false value.
|
35
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
36
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
37
|
+
# method, proc or string should return or evaluate to a true or false value.
|
38
|
+
def validates_associated(*attr_names)
|
39
|
+
validates_with AssociatedValidator, _merge_attributes(attr_names)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -24,9 +24,20 @@ module DatastaxRails
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
|
28
|
+
# Runs all the validations within the specified context. Returns true if no errors are found,
|
29
|
+
# false otherwise.
|
30
|
+
#
|
31
|
+
# If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
|
32
|
+
# <tt>new_record?</tt> is true, and to <tt>:update</tt> if it is not.
|
33
|
+
#
|
34
|
+
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
35
|
+
# some <tt>:on</tt> option will only run in the specified context.
|
36
|
+
def valid?(context = nil)
|
28
37
|
run_callbacks :validation do
|
29
|
-
|
38
|
+
context ||= (new_record? ? :create : :update)
|
39
|
+
output = super(context)
|
40
|
+
errors.empty? && output
|
30
41
|
end
|
31
42
|
end
|
32
43
|
|
@@ -46,3 +57,4 @@ module DatastaxRails
|
|
46
57
|
end
|
47
58
|
|
48
59
|
require 'datastax_rails/validations/uniqueness'
|
60
|
+
require 'datastax_rails/validations/associated'
|
data/lib/datastax_rails.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'active_support/all'
|
2
|
-
require 'cassandra-cql/1.
|
2
|
+
require 'cassandra-cql/1.2'
|
3
3
|
require 'blankslate'
|
4
4
|
require 'schema_migration'
|
5
5
|
|
@@ -80,19 +80,19 @@ end
|
|
80
80
|
require "thrift"
|
81
81
|
# Thrift is how we communicate with Cassandra. We need to do a little fixup
|
82
82
|
# work to handle UTF-8 properly in Ruby 1.8.6.
|
83
|
-
module Thrift
|
84
|
-
class BinaryProtocol
|
85
|
-
def write_string(str)
|
86
|
-
if(str.respond_to?(:bytesize))
|
87
|
-
size = str.bytesize
|
88
|
-
else
|
89
|
-
size = str.size
|
90
|
-
end
|
91
|
-
write_i32(size)
|
92
|
-
trans.write(str)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
83
|
+
# module Thrift
|
84
|
+
# class BinaryProtocol
|
85
|
+
# def write_string(str)
|
86
|
+
# if(str.respond_to?(:bytesize))
|
87
|
+
# size = str.bytesize
|
88
|
+
# else
|
89
|
+
# size = str.size
|
90
|
+
# end
|
91
|
+
# write_i32(size)
|
92
|
+
# trans.write(str)
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
# end
|
96
96
|
|
97
97
|
require 'datastax_rails/railtie' if defined?(Rails)
|
98
98
|
require 'datastax_rails/errors'
|
@@ -16,4 +16,10 @@ describe DatastaxRails::Base do
|
|
16
16
|
it "should raise RecordNotFound when finding a bogus ID" do
|
17
17
|
lambda { Person.find("xyzzy") }.should raise_exception(DatastaxRails::RecordNotFound)
|
18
18
|
end
|
19
|
+
|
20
|
+
xit "should skip records that are missing dsr in cassandra" do
|
21
|
+
p = Person.create(:name => 'Jason')
|
22
|
+
Person.cql.delete(p.id).columns(['dsr']).execute
|
23
|
+
Person.find_by_name('Jason').should be_nil
|
24
|
+
end
|
19
25
|
end
|
@@ -2,13 +2,13 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe DatastaxRails::Cql::Select do
|
4
4
|
before(:each) do
|
5
|
-
@model_class =
|
5
|
+
@model_class = double("Model Class", :column_family => 'users', :default_consistency => DatastaxRails::Cql::Consistency::QUORUM)
|
6
6
|
end
|
7
7
|
|
8
8
|
it "should generate valid CQL" do
|
9
9
|
cql = DatastaxRails::Cql::Select.new(@model_class, ["*"])
|
10
|
-
cql.using(DatastaxRails::Cql::Consistency::QUORUM).conditions(:
|
11
|
-
cql.to_cql.should == "SELECT * FROM users
|
10
|
+
cql.using(DatastaxRails::Cql::Consistency::QUORUM).conditions(:key => '12345').limit(1)
|
11
|
+
cql.to_cql.should == "SELECT * FROM users WHERE \"key\" = '12345' LIMIT 1 "
|
12
12
|
end
|
13
13
|
|
14
14
|
it_has_behavior "default_consistency"
|
@@ -2,13 +2,13 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe DatastaxRails::Cql::Update do
|
4
4
|
before(:each) do
|
5
|
-
@model_class =
|
5
|
+
@model_class = double("Model Class", :column_family => 'users', :default_consistency => DatastaxRails::Cql::Consistency::QUORUM)
|
6
6
|
end
|
7
7
|
|
8
8
|
it "should generate valid CQL" do
|
9
9
|
cql = DatastaxRails::Cql::Update.new(@model_class, "12345")
|
10
10
|
cql.using(DatastaxRails::Cql::Consistency::QUORUM).columns(:name => 'John', :age => '23')
|
11
|
-
cql.to_cql.should match(/update users
|
11
|
+
cql.to_cql.should match(/update users SET ("name" = 'John', "age" = '23'|"age" = '23', "name" = 'John') WHERE key IN \('12345'\)/)
|
12
12
|
end
|
13
13
|
|
14
14
|
it_has_behavior "default_consistency"
|
@@ -5,16 +5,16 @@ describe "DatastaxRails::Base" do
|
|
5
5
|
describe "with cql" do
|
6
6
|
describe "#create" do
|
7
7
|
it "should persist at the given consistency level" do
|
8
|
-
DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(/USING CONSISTENCY LOCAL_QUORUM/i).and_return(true)
|
9
8
|
Person.storage_method = :cql
|
9
|
+
DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(an_instance_of(String), :consistency => CassandraCQL::Thrift::ConsistencyLevel::LOCAL_QUORUM)
|
10
10
|
Person.create({:name => 'Steven'},{:consistency => 'LOCAL_QUORUM'})
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
describe "#save" do
|
15
15
|
it "should persist at the given consistency level" do
|
16
|
-
DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(/USING CONSISTENCY LOCAL_QUORUM/i).and_return(true)
|
17
16
|
Person.storage_method = :cql
|
17
|
+
DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(an_instance_of(String), :consistency => CassandraCQL::Thrift::ConsistencyLevel::LOCAL_QUORUM)
|
18
18
|
p=Person.new(:name => 'Steven')
|
19
19
|
p.save(:consistency => 'LOCAL_QUORUM')
|
20
20
|
end
|
@@ -24,7 +24,7 @@ describe "DatastaxRails::Base" do
|
|
24
24
|
describe "with solr" do
|
25
25
|
describe "#create" do
|
26
26
|
it "should persist at the given consistency level" do
|
27
|
-
Person.solr_connection.should_receive(:update).with(hash_including(:params => {:
|
27
|
+
Person.solr_connection.should_receive(:update).with(hash_including(:params => hash_including({:cl => 'LOCAL_QUORUM'}))).and_return(true)
|
28
28
|
Person.storage_method = :solr
|
29
29
|
Person.create({:name => 'Steven'},{:consistency => 'LOCAL_QUORUM'})
|
30
30
|
end
|
@@ -32,19 +32,23 @@ describe "DatastaxRails::Base" do
|
|
32
32
|
|
33
33
|
describe "#save" do
|
34
34
|
it "should persist at the given consistency level" do
|
35
|
-
Person.solr_connection.should_receive(:update).with(hash_including(:params => {:
|
35
|
+
Person.solr_connection.should_receive(:update).with(hash_including(:params => hash_including({:cl => 'LOCAL_QUORUM'}))).and_return(true)
|
36
36
|
Person.storage_method = :solr
|
37
37
|
p=Person.new(:name => 'Steven')
|
38
38
|
p.save(:consistency => 'LOCAL_QUORUM')
|
39
39
|
end
|
40
40
|
|
41
41
|
it "should successfully remove columns that are set to nil" do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
pending do
|
43
|
+
Person.storage_method = :solr
|
44
|
+
p = Person.create!(:name => 'Steven', :birthdate => Date.today)
|
45
|
+
Person.commit_solr
|
46
|
+
p = Person.find_by_name('Steven')
|
47
|
+
p.birthdate = nil
|
48
|
+
p.save
|
49
|
+
Person.commit_solr
|
50
|
+
Person.find by_name('Steven').birthdate.should be_nil
|
51
|
+
end
|
48
52
|
end
|
49
53
|
end
|
50
54
|
end
|
@@ -52,7 +56,7 @@ describe "DatastaxRails::Base" do
|
|
52
56
|
describe "#remove" do
|
53
57
|
it "should remove at the given consistency level" do
|
54
58
|
p=Person.create(:name => 'Steven')
|
55
|
-
DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(
|
59
|
+
DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(an_instance_of(String), :consistency => CassandraCQL::Thrift::ConsistencyLevel::LOCAL_QUORUM)
|
56
60
|
p.destroy(:consistency => :local_quorum)
|
57
61
|
end
|
58
62
|
end
|
@@ -65,7 +69,7 @@ describe "DatastaxRails::Base" do
|
|
65
69
|
end
|
66
70
|
|
67
71
|
it "should store really large files" do
|
68
|
-
file = IO.read("/dev/
|
72
|
+
file = IO.read("/dev/zero", 25.megabyte)
|
69
73
|
CarPayload.create(:digest => 'limo', :payload => file)
|
70
74
|
CarPayload.find('limo').payload.should == file
|
71
75
|
end
|
@@ -3,30 +3,34 @@ 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').
|
6
|
+
('a'..'l').each_with_index do |letter, idx|
|
7
7
|
Hobby.create(:name => letter)
|
8
|
+
sleep(1) if idx % 5 == 4 # Performance hack
|
8
9
|
end
|
9
10
|
Hobby.commit_solr
|
11
|
+
Hobby.commit_solr
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
['cassandra', 'solr'].each do |method|
|
15
|
+
describe "#find_each" do
|
16
|
+
it "returns each record one at a time with #{method}" do
|
17
|
+
missed_hobbies = ('a'..'l').to_a
|
18
|
+
@relation.send('with_'+method).find_each(:batch_size => 5) do |hobby|
|
19
|
+
missed_hobbies.delete_if {|h| h == hobby.name}
|
20
|
+
end
|
21
|
+
missed_hobbies.should be_empty
|
17
22
|
end
|
18
|
-
missed_hobbies.should be_empty
|
19
23
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
|
25
|
+
describe "#find_in_batches" do
|
26
|
+
it "returns records in batches of the given size with #{method}" do
|
27
|
+
count = 12
|
28
|
+
@relation.send('with_'+method).find_in_batches(:batch_size => 5) do |batch|
|
29
|
+
batch.size.should <= 5
|
30
|
+
count -= batch.size
|
31
|
+
end
|
32
|
+
count.should == 0
|
28
33
|
end
|
29
|
-
count.should == 0
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -16,7 +16,7 @@ describe DatastaxRails::Relation do
|
|
16
16
|
it "should look up the first result if records are not already loaded" do
|
17
17
|
a_record = mock_model(Hobby)
|
18
18
|
@relation.stub(:loaded? => false)
|
19
|
-
mock_relation =
|
19
|
+
mock_relation = double(DatastaxRails::Relation, :to_a => [a_record])
|
20
20
|
@relation.should_receive(:limit).with(1).and_return(mock_relation)
|
21
21
|
@relation.first.should == a_record
|
22
22
|
end
|
@@ -39,7 +39,7 @@ describe DatastaxRails::Relation do
|
|
39
39
|
it "should look up the last result if records are not already loaded" do
|
40
40
|
a_record = mock_model(Hobby)
|
41
41
|
@relation.stub(:loaded? => false)
|
42
|
-
mock_relation =
|
42
|
+
mock_relation = double(DatastaxRails::Relation, :to_a => [a_record])
|
43
43
|
@relation.should_receive(:reverse_order).and_return(mock_relation)
|
44
44
|
mock_relation.should_receive(:limit).with(1).and_return(mock_relation)
|
45
45
|
@relation.last.should == a_record
|