datastax_rails 1.0.19.0 → 1.1.0.3
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 +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
|