cequel 0.0.0 → 0.4.0
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.
- data/lib/cequel.rb +16 -0
- data/lib/cequel/batch.rb +58 -0
- data/lib/cequel/cql_row_specification.rb +22 -0
- data/lib/cequel/data_set.rb +346 -0
- data/lib/cequel/errors.rb +4 -0
- data/lib/cequel/keyspace.rb +106 -0
- data/lib/cequel/model.rb +95 -0
- data/lib/cequel/model/associations.rb +120 -0
- data/lib/cequel/model/callbacks.rb +32 -0
- data/lib/cequel/model/class_internals.rb +48 -0
- data/lib/cequel/model/column.rb +20 -0
- data/lib/cequel/model/dictionary.rb +202 -0
- data/lib/cequel/model/dirty.rb +53 -0
- data/lib/cequel/model/dynamic.rb +31 -0
- data/lib/cequel/model/errors.rb +13 -0
- data/lib/cequel/model/inheritable.rb +48 -0
- data/lib/cequel/model/instance_internals.rb +23 -0
- data/lib/cequel/model/local_association.rb +42 -0
- data/lib/cequel/model/magic.rb +79 -0
- data/lib/cequel/model/mass_assignment_security.rb +21 -0
- data/lib/cequel/model/naming.rb +17 -0
- data/lib/cequel/model/observer.rb +42 -0
- data/lib/cequel/model/persistence.rb +173 -0
- data/lib/cequel/model/properties.rb +143 -0
- data/lib/cequel/model/railtie.rb +33 -0
- data/lib/cequel/model/remote_association.rb +40 -0
- data/lib/cequel/model/scope.rb +362 -0
- data/lib/cequel/model/scoped.rb +50 -0
- data/lib/cequel/model/subclass_internals.rb +45 -0
- data/lib/cequel/model/timestamps.rb +52 -0
- data/lib/cequel/model/translation.rb +17 -0
- data/lib/cequel/model/validations.rb +50 -0
- data/lib/cequel/new_relic_instrumentation.rb +22 -0
- data/lib/cequel/row_specification.rb +63 -0
- data/lib/cequel/statement.rb +23 -0
- data/lib/cequel/version.rb +3 -0
- data/spec/environment.rb +3 -0
- data/spec/examples/data_set_spec.rb +382 -0
- data/spec/examples/keyspace_spec.rb +63 -0
- data/spec/examples/model/associations_spec.rb +109 -0
- data/spec/examples/model/callbacks_spec.rb +79 -0
- data/spec/examples/model/dictionary_spec.rb +413 -0
- data/spec/examples/model/dirty_spec.rb +39 -0
- data/spec/examples/model/dynamic_spec.rb +41 -0
- data/spec/examples/model/inheritable_spec.rb +45 -0
- data/spec/examples/model/magic_spec.rb +199 -0
- data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/model/naming_spec.rb +9 -0
- data/spec/examples/model/observer_spec.rb +86 -0
- data/spec/examples/model/persistence_spec.rb +201 -0
- data/spec/examples/model/properties_spec.rb +81 -0
- data/spec/examples/model/scope_spec.rb +677 -0
- data/spec/examples/model/serialization_spec.rb +20 -0
- data/spec/examples/model/spec_helper.rb +12 -0
- data/spec/examples/model/timestamps_spec.rb +52 -0
- data/spec/examples/model/translation_spec.rb +23 -0
- data/spec/examples/model/validations_spec.rb +86 -0
- data/spec/examples/spec_helper.rb +9 -0
- data/spec/models/asset.rb +21 -0
- data/spec/models/asset_observer.rb +5 -0
- data/spec/models/blog.rb +14 -0
- data/spec/models/blog_posts.rb +6 -0
- data/spec/models/category.rb +9 -0
- data/spec/models/comment.rb +12 -0
- data/spec/models/photo.rb +5 -0
- data/spec/models/post.rb +88 -0
- data/spec/models/post_comments.rb +14 -0
- data/spec/models/post_observer.rb +43 -0
- data/spec/support/helpers.rb +26 -0
- data/spec/support/result_stub.rb +27 -0
- metadata +125 -23
@@ -0,0 +1,106 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
#
|
4
|
+
# Handle to a Cassandra keyspace.
|
5
|
+
#
|
6
|
+
class Keyspace
|
7
|
+
|
8
|
+
#
|
9
|
+
# Set a logger for logging queries. Queries logged at INFO level
|
10
|
+
#
|
11
|
+
attr_writer :logger, :slowlog, :slowlog_threshold, :connection
|
12
|
+
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
# @see Cequel.connect
|
16
|
+
#
|
17
|
+
def initialize(configuration = {})
|
18
|
+
@name = configuration[:keyspace]
|
19
|
+
@hosts = configuration[:host] || configuration[:hosts]
|
20
|
+
@thrift_options = configuration[:thrift].try(:symbolize_keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection
|
24
|
+
@connection ||= CassandraCQL::Database.new(
|
25
|
+
@hosts, {:keyspace => @name}, @thrift_options
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Get DataSet encapsulating a column family in this keyspace
|
31
|
+
#
|
32
|
+
# @param column_family_name [Symbol] the name of the column family
|
33
|
+
# @return [DataSet] a column family
|
34
|
+
#
|
35
|
+
def [](column_family_name)
|
36
|
+
DataSet.new(column_family_name.to_sym, self)
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Execute a CQL query in this keyspace.
|
41
|
+
#
|
42
|
+
# @param statement [String] CQL string
|
43
|
+
# @param *bind_vars [Object] values for bind variables
|
44
|
+
#
|
45
|
+
def execute(statement, *bind_vars)
|
46
|
+
log('CQL', statement, *bind_vars) do
|
47
|
+
connection.execute(statement, *bind_vars)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Write data to this keyspace using a CQL query. Will be included the
|
53
|
+
# current batch operation if one is present.
|
54
|
+
#
|
55
|
+
# @param (see #execute)
|
56
|
+
#
|
57
|
+
def write(statement, *bind_vars)
|
58
|
+
if @batch
|
59
|
+
@batch.execute(statement, *bind_vars)
|
60
|
+
else
|
61
|
+
execute(statement, *bind_vars)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Execute write operations in a batch. Any inserts, updates, and deletes
|
67
|
+
# inside this method's block will be executed inside a CQL BATCH operation.
|
68
|
+
#
|
69
|
+
# @param options [Hash]
|
70
|
+
# @option options [Fixnum] :auto_apply Automatically send batch to Cassandra after this many statements
|
71
|
+
#
|
72
|
+
# @example Perform inserts in a batch
|
73
|
+
# DB.batch do
|
74
|
+
# DB[:posts].insert(:id => 1, :title => 'One')
|
75
|
+
# DB[:posts].insert(:id => 2, :title => 'Two')
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
def batch(options = {})
|
79
|
+
old_batch, @batch = @batch, Batch.new(self, options)
|
80
|
+
yield
|
81
|
+
@batch.apply
|
82
|
+
ensure
|
83
|
+
@batch = old_batch
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def log(label, statement, *bind_vars)
|
89
|
+
return yield unless @logger || @slowlog
|
90
|
+
response = nil
|
91
|
+
time = Benchmark.ms { response = yield }
|
92
|
+
generate_message = proc do
|
93
|
+
sprintf(
|
94
|
+
'%s (%dms) %s', label, time.to_i,
|
95
|
+
CassandraCQL::Statement.sanitize(statement, bind_vars)
|
96
|
+
)
|
97
|
+
end
|
98
|
+
@logger.debug(&generate_message) if @logger
|
99
|
+
threshold = @slowlog_threshold || 2000
|
100
|
+
@slowlog.warn(&generate_message) if @slowlog && time >= threshold
|
101
|
+
response
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
data/lib/cequel/model.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
require 'cequel'
|
4
|
+
require 'cequel/model/associations'
|
5
|
+
require 'cequel/model/callbacks'
|
6
|
+
require 'cequel/model/class_internals'
|
7
|
+
require 'cequel/model/column'
|
8
|
+
require 'cequel/model/dictionary'
|
9
|
+
require 'cequel/model/dirty'
|
10
|
+
require 'cequel/model/dynamic'
|
11
|
+
require 'cequel/model/errors'
|
12
|
+
require 'cequel/model/inheritable'
|
13
|
+
require 'cequel/model/instance_internals'
|
14
|
+
require 'cequel/model/local_association'
|
15
|
+
require 'cequel/model/mass_assignment_security'
|
16
|
+
require 'cequel/model/magic'
|
17
|
+
require 'cequel/model/naming'
|
18
|
+
require 'cequel/model/observer'
|
19
|
+
require 'cequel/model/persistence'
|
20
|
+
require 'cequel/model/properties'
|
21
|
+
require 'cequel/model/remote_association'
|
22
|
+
require 'cequel/model/scope'
|
23
|
+
require 'cequel/model/scoped'
|
24
|
+
require 'cequel/model/subclass_internals'
|
25
|
+
require 'cequel/model/timestamps'
|
26
|
+
require 'cequel/model/translation'
|
27
|
+
require 'cequel/model/validations'
|
28
|
+
|
29
|
+
if defined? Rails
|
30
|
+
require 'cequel/model/railtie'
|
31
|
+
end
|
32
|
+
|
33
|
+
module Cequel
|
34
|
+
|
35
|
+
#
|
36
|
+
# This module adds Cassandra persistence to a class using Cequel.
|
37
|
+
#
|
38
|
+
module Model
|
39
|
+
|
40
|
+
extend ActiveSupport::Concern
|
41
|
+
extend ActiveModel::Observing::ClassMethods
|
42
|
+
|
43
|
+
included do
|
44
|
+
@_cequel = ClassInternals.new(self)
|
45
|
+
|
46
|
+
include Properties
|
47
|
+
include Persistence
|
48
|
+
include Scoped
|
49
|
+
include Naming
|
50
|
+
include Callbacks
|
51
|
+
include Validations
|
52
|
+
include ActiveModel::Observing
|
53
|
+
include Dirty
|
54
|
+
include MassAssignmentSecurity
|
55
|
+
include Associations
|
56
|
+
extend Inheritable
|
57
|
+
extend Magic
|
58
|
+
|
59
|
+
include ActiveModel::Serializers::JSON
|
60
|
+
include ActiveModel::Serializers::Xml
|
61
|
+
|
62
|
+
extend Translation
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.keyspace
|
66
|
+
@keyspace ||= Cequel.connect(@configuration).tap do |keyspace|
|
67
|
+
keyspace.logger = @logger if @logger
|
68
|
+
keyspace.slowlog = @slowlog if @slowlog
|
69
|
+
keyspace.slowlog_threshold = @slowlog_threshold if @slowlog_threshold
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.configure(configuration)
|
74
|
+
@configuration = configuration
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.logger=(logger)
|
78
|
+
@logger = logger
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.slowlog=(slowlog)
|
82
|
+
@slowlog = slowlog
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.slowlog_threshold=(slowlog_threshold)
|
86
|
+
@slowlog_threshold = slowlog_threshold
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize
|
90
|
+
@_cequel = InstanceInternals.new(self)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module Associations
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def belongs_to(name, options = {})
|
12
|
+
name = name.to_sym
|
13
|
+
association = LocalAssociation.new(name, self, options.symbolize_keys)
|
14
|
+
@_cequel.associations[name] = association
|
15
|
+
column(association.foreign_key_name, association.primary_key.type)
|
16
|
+
|
17
|
+
module_eval <<-RUBY, __FILE__, __LINE__+1
|
18
|
+
def #{name}
|
19
|
+
if @_cequel.associations.key?(#{name.inspect})
|
20
|
+
return @_cequel.associations[#{name.inspect}]
|
21
|
+
end
|
22
|
+
key = __send__(:#{name}_id)
|
23
|
+
if key
|
24
|
+
@_cequel.associations[#{name.inspect}] =
|
25
|
+
self.class.reflect_on_association(#{name.inspect}).
|
26
|
+
scope(self).first
|
27
|
+
else
|
28
|
+
@_cequel.associations[#{name.inspect}] = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def #{name}=(instance)
|
33
|
+
@_cequel.associations[#{name.inspect}] = instance
|
34
|
+
if instance.nil?
|
35
|
+
key = nil
|
36
|
+
else
|
37
|
+
key = instance.__send__(instance.class.key_alias)
|
38
|
+
end
|
39
|
+
write_attribute(#{association.foreign_key_name.inspect}, key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def #{association.foreign_key_name}=(key)
|
43
|
+
@_cequel.associations.delete(#{name.inspect})
|
44
|
+
write_attribute(#{association.foreign_key_name.inspect}, key)
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_many(name, options = {})
|
50
|
+
name = name.to_sym
|
51
|
+
@_cequel.associations[name] =
|
52
|
+
RemoteAssociation.new(name, self, options.symbolize_keys)
|
53
|
+
|
54
|
+
module_eval <<-RUBY, __FILE__, __LINE__+1
|
55
|
+
def #{name}
|
56
|
+
self.class.reflect_on_association(#{name.inspect}).scope(self)
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
end
|
60
|
+
|
61
|
+
def has_one(name, options = {})
|
62
|
+
name = name.to_sym
|
63
|
+
@_cequel.associations[name] =
|
64
|
+
RemoteAssociation.new(name, self, options.symbolize_keys)
|
65
|
+
|
66
|
+
module_eval <<-RUBY, __FILE__, __LINE__+1
|
67
|
+
def #{name}
|
68
|
+
self.class.reflect_on_association(#{name.inspect}).scope(self).first
|
69
|
+
end
|
70
|
+
RUBY
|
71
|
+
end
|
72
|
+
|
73
|
+
def reflect_on_association(name)
|
74
|
+
@_cequel.association(name.to_sym)
|
75
|
+
end
|
76
|
+
|
77
|
+
def reflect_on_associations
|
78
|
+
@_cequel.associations.values
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def save(*args)
|
84
|
+
save_transient_associated
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def destroy(*args)
|
89
|
+
destroy_associated
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def save_transient_associated
|
96
|
+
self.class.reflect_on_associations.each do |association|
|
97
|
+
if LocalAssociation === association
|
98
|
+
associated = @_cequel.associations[association.name]
|
99
|
+
if associated && associated.transient?
|
100
|
+
associated.save
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def destroy_associated
|
107
|
+
self.class.reflect_on_associations.each do |association|
|
108
|
+
if association.dependent == :destroy
|
109
|
+
association.scope(self).each do |associated|
|
110
|
+
associated.destroy
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module Callbacks
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
HOOKS = [:save, :create, :update, :destroy, :validation]
|
10
|
+
CALLBACKS = HOOKS.map { |hook| [:"before_#{hook}", :"after_#{hook}"] }.
|
11
|
+
flatten
|
12
|
+
|
13
|
+
included do
|
14
|
+
extend ActiveModel::Callbacks
|
15
|
+
define_model_callbacks *HOOKS
|
16
|
+
end
|
17
|
+
|
18
|
+
def save(*args)
|
19
|
+
run_callbacks(:save) do
|
20
|
+
run_callbacks(persisted? ? :update : :create) { super }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy(*args)
|
25
|
+
run_callbacks(:destroy) { super }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
class ClassInternals
|
9
|
+
|
10
|
+
attr_accessor :key, :current_scope, :default_scope
|
11
|
+
attr_reader :columns, :associations, :index_preference
|
12
|
+
|
13
|
+
def initialize(clazz)
|
14
|
+
@clazz = clazz
|
15
|
+
@columns, @associations = {}, {}
|
16
|
+
@index_preference = []
|
17
|
+
@lock = Monitor.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_column(name, type, options = {})
|
21
|
+
@columns[name] = Column.new(name, type, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def type_column
|
25
|
+
@columns[:class_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def column_family_name
|
29
|
+
@column_family_name ||= @clazz.name.tableize
|
30
|
+
end
|
31
|
+
|
32
|
+
def base_class
|
33
|
+
@clazz
|
34
|
+
end
|
35
|
+
|
36
|
+
def association(name)
|
37
|
+
associations[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def synchronize(&block)
|
41
|
+
@lock.synchronize(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
#
|
6
|
+
# Encapsulates information about a column in a model's column family
|
7
|
+
#
|
8
|
+
class Column
|
9
|
+
attr_reader :name, :type, :default
|
10
|
+
|
11
|
+
def initialize(name, type, options = {})
|
12
|
+
@name, @type = name, type
|
13
|
+
@default = options[:default]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class Dictionary
|
6
|
+
|
7
|
+
class <<self
|
8
|
+
|
9
|
+
attr_writer :column_family, :default_batch_size
|
10
|
+
|
11
|
+
def key_alias
|
12
|
+
@key_alias ||= :KEY
|
13
|
+
end
|
14
|
+
|
15
|
+
def key_type
|
16
|
+
@key_type ||= :text
|
17
|
+
end
|
18
|
+
|
19
|
+
def comparator
|
20
|
+
@comparator ||= :text
|
21
|
+
end
|
22
|
+
|
23
|
+
def validation
|
24
|
+
@validation ||= :text
|
25
|
+
end
|
26
|
+
|
27
|
+
def key(key_alias, type)
|
28
|
+
@key_alias, @key_type = key_alias, type
|
29
|
+
|
30
|
+
module_eval(<<-RUBY)
|
31
|
+
def #{key_alias.downcase}
|
32
|
+
@key
|
33
|
+
end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
|
37
|
+
def maps(options)
|
38
|
+
@comparator, @validation = *options.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def column_family
|
42
|
+
return @column_family if @column_family
|
43
|
+
self.column_family_name = name.underscore.to_sym
|
44
|
+
@column_family
|
45
|
+
end
|
46
|
+
|
47
|
+
def column_family_name=(column_family_name)
|
48
|
+
self.column_family = Cequel::Model.keyspace[column_family_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_batch_size
|
52
|
+
@default_batch_size || 1000
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](key)
|
56
|
+
new(key)
|
57
|
+
end
|
58
|
+
private :new
|
59
|
+
end
|
60
|
+
|
61
|
+
include Enumerable
|
62
|
+
|
63
|
+
def initialize(key)
|
64
|
+
@key = key
|
65
|
+
setup
|
66
|
+
end
|
67
|
+
|
68
|
+
def []=(column, value)
|
69
|
+
if value.nil?
|
70
|
+
@deleted_columns << column
|
71
|
+
@changed_columns.delete(column)
|
72
|
+
else
|
73
|
+
@changed_columns << column
|
74
|
+
@deleted_columns.delete(column)
|
75
|
+
end
|
76
|
+
@row[column] = value
|
77
|
+
end
|
78
|
+
|
79
|
+
def [](column)
|
80
|
+
if @loaded || @changed_columns.include?(column)
|
81
|
+
@row[column]
|
82
|
+
elsif !@deleted_columns.include?(column)
|
83
|
+
value = scope.select(column).first[column]
|
84
|
+
deserialize_value(column, value) if value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def keys
|
89
|
+
@loaded ? @row.keys : each_pair.map { |key, value| key }
|
90
|
+
end
|
91
|
+
|
92
|
+
def values
|
93
|
+
@loaded ? @row.values : each_pair.map { |key, value| value }
|
94
|
+
end
|
95
|
+
|
96
|
+
def slice(*columns)
|
97
|
+
if @loaded
|
98
|
+
@row.slice(*columns)
|
99
|
+
else
|
100
|
+
{}.tap do |slice|
|
101
|
+
row = scope.select(*columns).first.except(self.class.key_alias)
|
102
|
+
row.each { |col, value| slice[col] = deserialize_value(col, value) }
|
103
|
+
slice.merge!(@row.slice(*columns))
|
104
|
+
@deleted_columns.each { |column| slice.delete(column) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def destroy
|
110
|
+
scope.delete
|
111
|
+
setup
|
112
|
+
end
|
113
|
+
|
114
|
+
def save
|
115
|
+
updates = {}
|
116
|
+
@changed_columns.each do |column|
|
117
|
+
updates[column] = serialize_value(@row[column])
|
118
|
+
end
|
119
|
+
scope.update(updates) if updates.any?
|
120
|
+
scope.delete(*@deleted_columns.to_a) if @deleted_columns.any?
|
121
|
+
@changed_columns.clear
|
122
|
+
@deleted_columns.clear
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def each_pair(options = {}, &block)
|
127
|
+
return Enumerator.new(self, :each_pair, options) unless block
|
128
|
+
return @row.each_pair(&block) if @loaded
|
129
|
+
batch_size = options[:batch_size] || self.class.default_batch_size
|
130
|
+
batch_scope = scope.select(:first => batch_size)
|
131
|
+
key_alias = self.class.key_alias
|
132
|
+
last_key = nil
|
133
|
+
new_columns = @changed_columns.dup
|
134
|
+
begin
|
135
|
+
batch_results = batch_scope.first
|
136
|
+
batch_results.delete(key_alias)
|
137
|
+
result_length = batch_results.length
|
138
|
+
batch_results.delete(last_key) unless last_key.nil?
|
139
|
+
batch_results.each_pair do |key, value|
|
140
|
+
if @changed_columns.include?(key)
|
141
|
+
new_columns.delete(key)
|
142
|
+
yield key, @row[key]
|
143
|
+
elsif !@deleted_columns.include?(key)
|
144
|
+
yield key, deserialize_value(key, value)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
last_key = batch_results.keys.last
|
148
|
+
batch_scope = batch_scope.select(:from => last_key)
|
149
|
+
end while result_length == batch_size
|
150
|
+
new_columns.each do |key|
|
151
|
+
yield key, @row[key]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def each(&block)
|
156
|
+
each_pair(&block)
|
157
|
+
end
|
158
|
+
|
159
|
+
def load
|
160
|
+
@row = {}
|
161
|
+
each_pair { |column, value| @row[column] = value }
|
162
|
+
@loaded = true
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
def loaded?
|
167
|
+
!!@loaded
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def setup
|
173
|
+
@row = {}
|
174
|
+
@changed_columns = Set[]
|
175
|
+
@deleted_columns = Set[]
|
176
|
+
end
|
177
|
+
|
178
|
+
def scope
|
179
|
+
self.class.column_family.where(self.class.key_alias => @key)
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# Subclasses may override this method to implement custom serialization
|
184
|
+
# strategies
|
185
|
+
#
|
186
|
+
def serialize_value(value)
|
187
|
+
value
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Subclasses may override this method to implement custom deserialization
|
192
|
+
# strategies
|
193
|
+
#
|
194
|
+
def deserialize_value(column, value)
|
195
|
+
value
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|