believer 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.md +2 -0
  2. data/lib/believer.rb +42 -0
  3. data/lib/believer/base.rb +45 -0
  4. data/lib/believer/batch.rb +37 -0
  5. data/lib/believer/batch_delete.rb +27 -0
  6. data/lib/believer/callbacks.rb +276 -0
  7. data/lib/believer/columns.rb +166 -0
  8. data/lib/believer/command.rb +44 -0
  9. data/lib/believer/connection.rb +22 -0
  10. data/lib/believer/ddl.rb +36 -0
  11. data/lib/believer/delete.rb +11 -0
  12. data/lib/believer/empty_result.rb +32 -0
  13. data/lib/believer/environment.rb +51 -0
  14. data/lib/believer/environment/rails_environment.rb +19 -0
  15. data/lib/believer/insert.rb +18 -0
  16. data/lib/believer/limit.rb +20 -0
  17. data/lib/believer/model_schema.rb +176 -0
  18. data/lib/believer/observer.rb +36 -0
  19. data/lib/believer/order_by.rb +22 -0
  20. data/lib/believer/owner.rb +48 -0
  21. data/lib/believer/persistence.rb +31 -0
  22. data/lib/believer/primary_key.rb +5 -0
  23. data/lib/believer/query.rb +140 -0
  24. data/lib/believer/querying.rb +6 -0
  25. data/lib/believer/scoped_command.rb +19 -0
  26. data/lib/believer/scoping.rb +16 -0
  27. data/lib/believer/test/rspec/test_run_life_cycle.rb +51 -0
  28. data/lib/believer/values.rb +40 -0
  29. data/lib/believer/version.rb +5 -0
  30. data/lib/believer/where_clause.rb +40 -0
  31. data/spec/believer/base_spec.rb +6 -0
  32. data/spec/believer/callback_spec.rb +49 -0
  33. data/spec/believer/delete_spec.rb +12 -0
  34. data/spec/believer/insert_spec.rb +9 -0
  35. data/spec/believer/limit_spec.rb +7 -0
  36. data/spec/believer/order_by_spec.rb +14 -0
  37. data/spec/believer/query_spec.rb +31 -0
  38. data/spec/believer/time_series_spec.rb +18 -0
  39. data/spec/believer/where_spec.rb +28 -0
  40. data/spec/spec_helper.rb +37 -0
  41. data/spec/support/setup_database.rb +20 -0
  42. data/spec/support/test_classes.rb +60 -0
  43. metadata +180 -0
@@ -0,0 +1,2 @@
1
+
2
+
@@ -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