believer 0.1
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/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
|