believer 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -0
- data/lib/believer.rb +42 -0
- data/lib/believer/base.rb +45 -0
- data/lib/believer/batch.rb +37 -0
- data/lib/believer/batch_delete.rb +27 -0
- data/lib/believer/callbacks.rb +276 -0
- data/lib/believer/columns.rb +166 -0
- data/lib/believer/command.rb +44 -0
- data/lib/believer/connection.rb +22 -0
- data/lib/believer/ddl.rb +36 -0
- data/lib/believer/delete.rb +11 -0
- data/lib/believer/empty_result.rb +32 -0
- data/lib/believer/environment.rb +51 -0
- data/lib/believer/environment/rails_environment.rb +19 -0
- data/lib/believer/insert.rb +18 -0
- data/lib/believer/limit.rb +20 -0
- data/lib/believer/model_schema.rb +176 -0
- data/lib/believer/observer.rb +36 -0
- data/lib/believer/order_by.rb +22 -0
- data/lib/believer/owner.rb +48 -0
- data/lib/believer/persistence.rb +31 -0
- data/lib/believer/primary_key.rb +5 -0
- data/lib/believer/query.rb +140 -0
- data/lib/believer/querying.rb +6 -0
- data/lib/believer/scoped_command.rb +19 -0
- data/lib/believer/scoping.rb +16 -0
- data/lib/believer/test/rspec/test_run_life_cycle.rb +51 -0
- data/lib/believer/values.rb +40 -0
- data/lib/believer/version.rb +5 -0
- data/lib/believer/where_clause.rb +40 -0
- data/spec/believer/base_spec.rb +6 -0
- data/spec/believer/callback_spec.rb +49 -0
- data/spec/believer/delete_spec.rb +12 -0
- data/spec/believer/insert_spec.rb +9 -0
- data/spec/believer/limit_spec.rb +7 -0
- data/spec/believer/order_by_spec.rb +14 -0
- data/spec/believer/query_spec.rb +31 -0
- data/spec/believer/time_series_spec.rb +18 -0
- data/spec/believer/where_spec.rb +28 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/setup_database.rb +20 -0
- data/spec/support/test_classes.rb +60 -0
- metadata +180 -0
data/README.md
ADDED
data/lib/believer.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Believer
|
2
|
+
VERSION = '0.1'
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'active_model'
|
6
|
+
|
7
|
+
require 'active_support/concern'
|
8
|
+
require 'active_support/core_ext'
|
9
|
+
require 'active_support/core_ext/module/delegation'
|
10
|
+
|
11
|
+
require 'cql'
|
12
|
+
require 'cql/client'
|
13
|
+
|
14
|
+
require 'yaml'
|
15
|
+
|
16
|
+
require 'believer/environment.rb'
|
17
|
+
require 'believer/connection.rb'
|
18
|
+
require 'believer/values.rb'
|
19
|
+
require 'believer/columns.rb'
|
20
|
+
require 'believer/model_schema.rb'
|
21
|
+
require 'believer/persistence.rb'
|
22
|
+
require 'believer/command.rb'
|
23
|
+
require 'believer/querying.rb'
|
24
|
+
require 'believer/where_clause.rb'
|
25
|
+
require 'believer/empty_result.rb'
|
26
|
+
require 'believer/limit.rb'
|
27
|
+
require 'believer/order_by.rb'
|
28
|
+
require 'believer/scoped_command.rb'
|
29
|
+
require 'believer/query.rb'
|
30
|
+
require 'believer/delete.rb'
|
31
|
+
require 'believer/insert.rb'
|
32
|
+
require 'believer/scoping.rb'
|
33
|
+
require 'believer/batch.rb'
|
34
|
+
require 'believer/batch_delete.rb'
|
35
|
+
require 'believer/callbacks.rb'
|
36
|
+
|
37
|
+
require 'believer/observer.rb'
|
38
|
+
require 'believer/owner.rb'
|
39
|
+
|
40
|
+
require 'believer/ddl.rb'
|
41
|
+
require 'believer/base.rb'
|
42
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
module Believer
|
3
|
+
class Base
|
4
|
+
extend ::ActiveModel::Naming
|
5
|
+
|
6
|
+
include Environment
|
7
|
+
include Connection
|
8
|
+
include Columns
|
9
|
+
include ModelSchema
|
10
|
+
include Scoping
|
11
|
+
include Persistence
|
12
|
+
extend Querying
|
13
|
+
include Callbacks
|
14
|
+
include DDL
|
15
|
+
|
16
|
+
include ::ActiveModel::Observing
|
17
|
+
|
18
|
+
# The Cassandra row ID
|
19
|
+
attr_accessor :id
|
20
|
+
|
21
|
+
def initialize(attrs = {})
|
22
|
+
@attributes = {}
|
23
|
+
#puts "Attrs: #{attrs.to_json}"
|
24
|
+
#self.class.columns.each do |name, colum_definition|
|
25
|
+
# send("#{name}=".to_sym, attrs[name.to_s])
|
26
|
+
#end if attrs.present?
|
27
|
+
attrs.each do |name, val|
|
28
|
+
send("#{name}=".to_sym, val)
|
29
|
+
end if attrs.present?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.instantiate_from_result_rows(row)
|
33
|
+
new(row)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(obj)
|
37
|
+
return false if obj.nil?
|
38
|
+
return false unless obj.is_a?(self.class)
|
39
|
+
equal_key_values?(obj)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
# A command which issues 0 or more other commands in batch to the Cassandra server.
|
4
|
+
# This is achieved using the CQL BATCH command
|
5
|
+
class Batch < Command
|
6
|
+
|
7
|
+
# Yields the collection of commands
|
8
|
+
# @return [Array<Command>] the command collection
|
9
|
+
def commands
|
10
|
+
@commands ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds a command
|
14
|
+
# @param command [Command] a command
|
15
|
+
def <<(command)
|
16
|
+
add(command)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds a command
|
20
|
+
# @param command [Command] a command
|
21
|
+
def add(command)
|
22
|
+
commands << command
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Yields the CQL for this command
|
27
|
+
# @return [String] the CQL
|
28
|
+
def to_cql
|
29
|
+
cql = "BEGIN BATCH\n"
|
30
|
+
commands.each do |c|
|
31
|
+
cql += " #{c.to_cql}\n"
|
32
|
+
end
|
33
|
+
cql += "APPLY BATCH;\n"
|
34
|
+
cql
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Believer
|
2
|
+
class BatchDelete < ScopedCommand
|
3
|
+
|
4
|
+
DELETE_BATCH_CHUNK_SIZE = 100
|
5
|
+
|
6
|
+
def execute
|
7
|
+
#cql = "DELETE FROM #{@record_class.table_name}"
|
8
|
+
#cql << " WHERE #{@wheres.map { |wc| "#{wc.to_cql}" }.join(' AND ')}" if @wheres && @wheres.any?
|
9
|
+
#cql << " #{@limit_to.to_cql}" unless @limit_to.nil?
|
10
|
+
#cql
|
11
|
+
|
12
|
+
cnt = count
|
13
|
+
s = self.limit_to.size
|
14
|
+
key_cols = self.record_class.primary_key_columns
|
15
|
+
cql = "BEGIN BATCH\n"
|
16
|
+
rows = clone.select(self.record_class.primary_key_columns).execute
|
17
|
+
rows.each do |row_to_delete|
|
18
|
+
d = Delete.new(:record_class => self.record_class).where(row_to_delete)
|
19
|
+
cql += "#{d.to_cql}"
|
20
|
+
end
|
21
|
+
cql = "APPLY BATCH;\n"
|
22
|
+
#BatchDelete.new(:record_class => self.record_class, :wheres => self.wheres, :limit_to => self.limit_to).execute
|
23
|
+
cnt
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
require 'active_model/callbacks'
|
2
|
+
require 'active_model/validations/callbacks'
|
3
|
+
|
4
|
+
module Believer
|
5
|
+
# = Record Callbacks
|
6
|
+
#
|
7
|
+
# Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
|
8
|
+
# before or after an alteration of the object state. This can be used to make sure that associated and
|
9
|
+
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
|
10
|
+
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
|
11
|
+
# the <tt>Base#save</tt> call for a new record:
|
12
|
+
#
|
13
|
+
# * (-) <tt>save</tt>
|
14
|
+
# * (-) <tt>valid</tt>
|
15
|
+
# * (1) <tt>before_validation</tt>
|
16
|
+
# * (-) <tt>validate</tt>
|
17
|
+
# * (2) <tt>after_validation</tt>
|
18
|
+
# * (3) <tt>before_save</tt>
|
19
|
+
# * (4) <tt>before_create</tt>
|
20
|
+
# * (-) <tt>create</tt>
|
21
|
+
# * (5) <tt>after_create</tt>
|
22
|
+
# * (6) <tt>after_save</tt>
|
23
|
+
# * (7) <tt>after_commit</tt>
|
24
|
+
#
|
25
|
+
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
|
26
|
+
# Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
|
27
|
+
# <tt>after_rollback</tt>.
|
28
|
+
#
|
29
|
+
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
|
30
|
+
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
|
31
|
+
# are instantiated as well.
|
32
|
+
#
|
33
|
+
# That's a total of twelve callbacks, which gives you immense power to react and prepare for each state in the
|
34
|
+
# Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
|
35
|
+
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
|
36
|
+
#
|
37
|
+
# Examples:
|
38
|
+
# class CreditCard < ActiveRecord::Base
|
39
|
+
# # Strip everything but digits, so the user can specify "555 234 34" or
|
40
|
+
# # "5552-3434" or both will mean "55523434"
|
41
|
+
# before_validation(:on => :create) do
|
42
|
+
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# class Subscription < ActiveRecord::Base
|
47
|
+
# before_create :record_signup
|
48
|
+
#
|
49
|
+
# private
|
50
|
+
# def record_signup
|
51
|
+
# self.signed_up_on = Date.today
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# class Firm < ActiveRecord::Base
|
56
|
+
# # Destroys the associated clients and people when the firm is destroyed
|
57
|
+
# before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
|
58
|
+
# before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# == Inheritable callback queues
|
62
|
+
#
|
63
|
+
# Besides the overwritable callback methods, it's also possible to register callbacks through the
|
64
|
+
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
|
65
|
+
# queue that is kept intact down through an inheritance hierarchy.
|
66
|
+
#
|
67
|
+
# class Topic < ActiveRecord::Base
|
68
|
+
# before_destroy :destroy_author
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# class Reply < Topic
|
72
|
+
# before_destroy :destroy_readers
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
|
76
|
+
# run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
|
77
|
+
# where the +before_destroy+ method is overridden:
|
78
|
+
#
|
79
|
+
# class Topic < ActiveRecord::Base
|
80
|
+
# def before_destroy() destroy_author end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# class Reply < Topic
|
84
|
+
# def before_destroy() destroy_readers end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
|
88
|
+
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
|
89
|
+
# hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
|
90
|
+
# to decide whether they want to call +super+ and trigger the inherited callbacks.
|
91
|
+
#
|
92
|
+
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
|
93
|
+
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
|
94
|
+
# child before the parent has registered the callbacks and they won't be inherited.
|
95
|
+
#
|
96
|
+
# == Types of callbacks
|
97
|
+
#
|
98
|
+
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
|
99
|
+
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
|
100
|
+
# are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
|
101
|
+
# creating mix-ins), and inline eval methods are deprecated.
|
102
|
+
#
|
103
|
+
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
|
104
|
+
#
|
105
|
+
# class Topic < ActiveRecord::Base
|
106
|
+
# before_destroy :delete_parents
|
107
|
+
#
|
108
|
+
# private
|
109
|
+
# def delete_parents
|
110
|
+
# self.class.delete_all "parent_id = #{id}"
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
|
115
|
+
#
|
116
|
+
# class BankAccount < ActiveRecord::Base
|
117
|
+
# before_save EncryptionWrapper.new
|
118
|
+
# after_save EncryptionWrapper.new
|
119
|
+
# after_initialize EncryptionWrapper.new
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# class EncryptionWrapper
|
123
|
+
# def before_save(record)
|
124
|
+
# record.credit_card_number = encrypt(record.credit_card_number)
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# def after_save(record)
|
128
|
+
# record.credit_card_number = decrypt(record.credit_card_number)
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# alias_method :after_find, :after_save
|
132
|
+
#
|
133
|
+
# private
|
134
|
+
# def encrypt(value)
|
135
|
+
# # Secrecy is committed
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# def decrypt(value)
|
139
|
+
# # Secrecy is unveiled
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
144
|
+
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
|
145
|
+
# initialization data such as the name of the attribute to work with:
|
146
|
+
#
|
147
|
+
# class BankAccount < ActiveRecord::Base
|
148
|
+
# before_save EncryptionWrapper.new("credit_card_number")
|
149
|
+
# after_save EncryptionWrapper.new("credit_card_number")
|
150
|
+
# after_initialize EncryptionWrapper.new("credit_card_number")
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# class EncryptionWrapper
|
154
|
+
# def initialize(attribute)
|
155
|
+
# @attribute = attribute
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# def before_save(record)
|
159
|
+
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# def after_save(record)
|
163
|
+
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# alias_method :after_find, :after_save
|
167
|
+
#
|
168
|
+
# private
|
169
|
+
# def encrypt(value)
|
170
|
+
# # Secrecy is committed
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# def decrypt(value)
|
174
|
+
# # Secrecy is unveiled
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also
|
179
|
+
# pass a "method string", which will then be evaluated within the binding of the callback. Example:
|
180
|
+
#
|
181
|
+
# class Topic < ActiveRecord::Base
|
182
|
+
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
|
183
|
+
# end
|
184
|
+
#
|
185
|
+
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
|
186
|
+
# is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
|
187
|
+
#
|
188
|
+
# class Topic < ActiveRecord::Base
|
189
|
+
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
|
190
|
+
# 'puts "Evaluated after parents are destroyed"'
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# == <tt>before_validation*</tt> returning statements
|
194
|
+
#
|
195
|
+
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
|
196
|
+
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
|
197
|
+
# ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
|
198
|
+
#
|
199
|
+
# == Canceling callbacks
|
200
|
+
#
|
201
|
+
# If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
|
202
|
+
# cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
|
203
|
+
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
|
204
|
+
# methods on the model, which are called last.
|
205
|
+
#
|
206
|
+
# == Transactions
|
207
|
+
#
|
208
|
+
# The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
|
209
|
+
# within a transaction. That includes <tt>after_*</tt> hooks. If everything
|
210
|
+
# goes fine a COMMIT is executed once the chain has been completed.
|
211
|
+
#
|
212
|
+
# If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
|
213
|
+
# can also trigger a ROLLBACK raising an exception in any of the callbacks,
|
214
|
+
# including <tt>after_*</tt> hooks. Note, however, that in that case the client
|
215
|
+
# needs to be aware of it because an ordinary +save+ will raise such exception
|
216
|
+
# instead of quietly returning +false+.
|
217
|
+
#
|
218
|
+
# == Debugging callbacks
|
219
|
+
#
|
220
|
+
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
|
221
|
+
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
|
222
|
+
# defines what part of the chain the callback runs in.
|
223
|
+
#
|
224
|
+
# To find all callbacks in the before_save callback chain:
|
225
|
+
#
|
226
|
+
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
|
227
|
+
#
|
228
|
+
# Returns an array of callback objects that form the before_save chain.
|
229
|
+
#
|
230
|
+
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
|
231
|
+
#
|
232
|
+
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
|
233
|
+
#
|
234
|
+
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
|
235
|
+
#
|
236
|
+
module Callbacks
|
237
|
+
extend ActiveSupport::Concern
|
238
|
+
|
239
|
+
CALLBACKS = [
|
240
|
+
:after_initialize, :before_validation, :after_validation,
|
241
|
+
:before_save, :around_save, :after_save, :before_create, :around_create,
|
242
|
+
:after_create, :before_update, :around_update, :after_update,
|
243
|
+
:before_destroy, :around_destroy, :after_destroy
|
244
|
+
]
|
245
|
+
|
246
|
+
included do
|
247
|
+
extend ActiveModel::Callbacks
|
248
|
+
include ActiveModel::Validations::Callbacks
|
249
|
+
|
250
|
+
define_model_callbacks :initialize, :only => :after
|
251
|
+
define_model_callbacks :save, :create, :update, :destroy
|
252
|
+
end
|
253
|
+
|
254
|
+
def initialize#:nodoc:
|
255
|
+
run_callbacks(:initialize) { super }
|
256
|
+
end
|
257
|
+
|
258
|
+
def destroy #:nodoc:
|
259
|
+
run_callbacks(:destroy) { super }
|
260
|
+
end
|
261
|
+
|
262
|
+
def save #:nodoc:
|
263
|
+
run_callbacks(:save) { super }
|
264
|
+
end
|
265
|
+
|
266
|
+
def update(*) #:nodoc:
|
267
|
+
run_callbacks(:update) { super }
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def create #:nodoc:
|
273
|
+
run_callbacks(:create) { super }
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
# Defines methods for dealing with model attributes.
|
4
|
+
module Columns
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include Values
|
9
|
+
|
10
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt>.
|
11
|
+
def [](attr_name)
|
12
|
+
read_attribute(attr_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
16
|
+
def []=(attr_name, value)
|
17
|
+
write_attribute(attr_name, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Column
|
23
|
+
CQL_COL_TYPES = {
|
24
|
+
:integer => 'INT',
|
25
|
+
:string => 'VARCHAR',
|
26
|
+
:timestamp => 'TIMESTAMP',
|
27
|
+
:float => 'FlOAT'
|
28
|
+
}
|
29
|
+
|
30
|
+
attr_reader :name, :type
|
31
|
+
|
32
|
+
def initialize(opts)
|
33
|
+
@name = opts[:name]
|
34
|
+
@type = opts[:type]
|
35
|
+
raise "Invalid column type #{@type}" unless CQL_COL_TYPES.has_key?(@type)
|
36
|
+
@key = opts[:key] == true || !opts[:key].nil?
|
37
|
+
if @key && opts[:key].is_a?(Hash)
|
38
|
+
@partition_key = opts[:key][:partition_key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cql_column_type
|
43
|
+
CQL_COL_TYPES[@type]
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_key?
|
47
|
+
@key
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_partition_key?
|
51
|
+
@partition_key
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
|
58
|
+
# Returns all columns on the model
|
59
|
+
# @return []
|
60
|
+
def columns
|
61
|
+
@columns ||= {}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Defines a column on the model.
|
65
|
+
# The column name must correspond with the Cassandra column name
|
66
|
+
def column(name, opts = {})
|
67
|
+
defaults = {
|
68
|
+
:type => :string
|
69
|
+
}
|
70
|
+
options = defaults.merge(opts).merge(:name => name)
|
71
|
+
|
72
|
+
columns[name] = Column.new(options)
|
73
|
+
|
74
|
+
self.redefine_method(name) do
|
75
|
+
read_attribute(name)
|
76
|
+
end
|
77
|
+
|
78
|
+
self.redefine_method("#{name}=") do |val|
|
79
|
+
write_attribute(name, val)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def primary_key(*cols)
|
84
|
+
@primary_key = *cols
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_primary_key
|
88
|
+
@primary_key.dup
|
89
|
+
end
|
90
|
+
|
91
|
+
def primary_key_columns
|
92
|
+
@primary_key.flatten
|
93
|
+
end
|
94
|
+
|
95
|
+
def partition_key
|
96
|
+
@primary_key.first.dup
|
97
|
+
end
|
98
|
+
|
99
|
+
def partition_keys
|
100
|
+
part_key = partition_key
|
101
|
+
return part_key.dup if part_key.is_a?(Enumerable)
|
102
|
+
[part_key]
|
103
|
+
end
|
104
|
+
|
105
|
+
def has_compound_key?
|
106
|
+
@primary_key.size > 1
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
def equal_key_values?(obj)
|
113
|
+
self.class.primary_key_columns.all? do |key_col|
|
114
|
+
read_attribute(key_col) == obj.read_attribute(key_col)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def key_values
|
119
|
+
k = {}
|
120
|
+
self.class.primary_key_columns.each do |key_col|
|
121
|
+
k[key_col] = read_attribute(key_col)
|
122
|
+
end
|
123
|
+
k
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_attribute(attr_name)
|
127
|
+
@attributes[attr_name]
|
128
|
+
end
|
129
|
+
|
130
|
+
def write_attribute(attr_name, value)
|
131
|
+
v = value
|
132
|
+
# Convert the value to the actual type
|
133
|
+
unless v.nil? && self.class.columns[attr_name]
|
134
|
+
value_type = self.class.columns[attr_name].type
|
135
|
+
convert_method = "convert_to_#{value_type}".to_sym
|
136
|
+
v = self.send(convert_method, v) if respond_to?(convert_method)
|
137
|
+
end
|
138
|
+
@attributes[attr_name] = v
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns true if the given attribute is in the attributes hash
|
142
|
+
def has_attribute?(attr_name)
|
143
|
+
@attributes.has_key?(attr_name.to_s)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns an array of names for the attributes available on this object.
|
147
|
+
def attribute_names
|
148
|
+
@attributes.keys
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
152
|
+
def attributes
|
153
|
+
attrs = {}
|
154
|
+
attribute_names.each { |name| attrs[name] = read_attribute(name) }
|
155
|
+
attrs
|
156
|
+
end
|
157
|
+
|
158
|
+
def attributes=(attrs)
|
159
|
+
attrs.each do |name, value|
|
160
|
+
setter_method = "#{name}=".to_sym
|
161
|
+
self.send(setter_method, value) if respond_to?(setter_method)
|
162
|
+
end if attrs.present?
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|