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.
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