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,22 @@
1
+ module Believer
2
+ class OrderBy
3
+ attr_reader :field, :dir
4
+
5
+ def initialize(field, dir = :asc)
6
+ raise "Invalid field: #{field}" unless field.is_a?(Symbol) || field.is_a?(String)
7
+ raise "Direction must be one of (:asc|:desc): #{dir}" unless dir == :asc || dir == :desc
8
+ @field = field
9
+ @dir = dir
10
+ end
11
+
12
+ def to_cql
13
+ "ORDER BY #{@field} #{@dir.present? ? @dir.to_s.upcase : 'ASC'}"
14
+ end
15
+
16
+ def inverse
17
+ OrderBy.new(@field, @dir == :asc ? :desc : :asc)
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,48 @@
1
+ module Believer
2
+ module Owner
3
+ extend ::ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def cql_record_relations
7
+ @cql_record_relations ||= {}
8
+ end
9
+
10
+ def has_some(name, opts = {})
11
+ defaults = {
12
+ }
13
+ options = opts.merge(defaults)
14
+
15
+ relation_class = options[:class]
16
+ if relation_class.nil?
17
+ relation_class = Kernel.const_get(name.to_s.camelize[0, (name.to_s.size - 1)])
18
+ elsif relation_class.is_a?(String)
19
+ relation_class = Kernel.const_get(relation_type)
20
+ end
21
+
22
+ foreign_key = options[:foreign_key]
23
+ foreign_key = "#{self.name.underscore}_id" if foreign_key.nil?
24
+ foreign_key = foreign_key.to_sym
25
+
26
+ key_attribute = options[:key_attribute]
27
+ if key_attribute.nil?
28
+ key_attribute = foreign_key
29
+ end
30
+
31
+ cql_record_relations[name] = options
32
+
33
+ self.redefine_method(name) do
34
+ q = relation_class.where(foreign_key => self.send(key_attribute))
35
+ if options[:filter]
36
+ q = options[:filter].call(self, q)
37
+ return EmptyResult.new unless q
38
+ end
39
+ q
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,31 @@
1
+ module Believer
2
+
3
+ module Persistence
4
+
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+
9
+ def create(attributes = nil, &block)
10
+ if attributes.is_a?(Array)
11
+ attributes.collect { |attr| create(attr, &block) }
12
+ else
13
+ object = new(attributes, &block)
14
+ object.save
15
+ object
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ # Saves the model.
22
+ def save
23
+ Insert.new(:record_class => self.class, :values => self).execute
24
+ end
25
+
26
+ def destroy
27
+ Delete.new(:record_class => self.class).where(key_values).execute
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ module Believer
2
+ class PrimaryKey
3
+
4
+ end
5
+ end
@@ -0,0 +1,140 @@
1
+ module Believer
2
+ class Query < ScopedCommand
3
+
4
+ attr_accessor :record_class, :selects, :order_by, :limit_to
5
+
6
+ delegate *(Enumerable.instance_methods(false).map {|enum_method| enum_method.to_sym}), :to => :to_a
7
+
8
+ def initialize(attrs)
9
+ super
10
+ end
11
+
12
+ def query_attributes
13
+ attrs = super
14
+ attrs.merge(:order_by => @order_by, :selects => @selects, :limit_to => @limit_to)
15
+ end
16
+
17
+ def select(*fields)
18
+ q = clone
19
+ q.selects ||= []
20
+ q.selects += fields
21
+ q.selects.flatten!
22
+ #puts "New selects: #{q.selects}, added: #{fields}"
23
+ q
24
+ end
25
+
26
+ def order(field, order = :asc)
27
+ q = clone
28
+ q.order_by = OrderBy.new(field, order)
29
+ q
30
+ end
31
+
32
+ def limit(l)
33
+ q = clone
34
+ q.limit_to = Limit.new(l)
35
+ q
36
+ end
37
+
38
+ def limit_to=(l)
39
+ if l.nil?
40
+ @limit_to = nil
41
+ else
42
+ @limit_to = Limit.new(l.to_i)
43
+ end
44
+ end
45
+
46
+ def to_cql
47
+ cql = "SELECT "
48
+ if @selects && @selects.any?
49
+ cql << "#{@selects.join(', ')}"
50
+ else
51
+ cql << @record_class.columns.keys.join(',')
52
+ #cql << "*"
53
+ end
54
+
55
+ cql << " FROM #{@record_class.table_name}"
56
+ cql << " WHERE #{@wheres.map { |wc| "#{wc.to_cql}" }.join(' AND ')}" if @wheres && @wheres.any?
57
+ cql << " #{@order_by.to_cql}" unless @order_by.nil?
58
+ cql << " #{@limit_to.to_cql}" unless @limit_to.nil?
59
+ cql
60
+ end
61
+
62
+ def destroy_all
63
+ objects = to_a
64
+ objects.each do |obj|
65
+ obj.destroy
66
+ end
67
+ objects.size
68
+ end
69
+
70
+ def delete_all(options = {})
71
+ cnt = count
72
+ chunk_size = options[:delete_batch_chunk_size] || (self.limit_to.nil? ? nil : self.limit_to.size) || cnt
73
+ key_cols = self.record_class.primary_key_columns
74
+ deleted_count = 0
75
+ while deleted_count < cnt
76
+ batch = Batch.new(:record_class => @record_class)
77
+ rows_to_delete = clone.select(key_cols).limit(chunk_size).execute
78
+ rows_to_delete.each do |row_to_delete|
79
+ batch << Delete.new(:record_class => self.record_class).where(row_to_delete)
80
+ end
81
+ batch.execute
82
+ deleted_count += batch.commands.size
83
+ end
84
+ deleted_count
85
+ end
86
+
87
+ def to_a
88
+ unless @result_rows
89
+ result = execute
90
+ @result_rows = []
91
+ start = Time.now
92
+ result.each do |row|
93
+ @result_rows << @record_class.instantiate_from_result_rows(row)
94
+ end
95
+ puts "Took #{sprintf "%.3f", (Time.now - start)*1000.0} ms to deserialize #{@result_rows.size} object(s)"
96
+ end
97
+ @result_rows
98
+ end
99
+
100
+ def size
101
+ to_a.size
102
+ end
103
+
104
+ def count
105
+ count_q = clone
106
+ count_q.selects = ['COUNT(*)']
107
+ result = count_q.execute
108
+
109
+ cnt = -1
110
+ result.each do |row|
111
+ cnt = row['count']
112
+ end
113
+ cnt
114
+ end
115
+
116
+ def each
117
+ to_a.each do |r|
118
+ yield r
119
+ end
120
+ end
121
+
122
+ def [](index)
123
+ to_a[index]
124
+ end
125
+
126
+ def first
127
+ return @result_rows.first unless @result_rows.nil?
128
+ clone.limit(1)[0]
129
+ end
130
+
131
+ def last
132
+ return @result_rows.last unless @result_rows.nil?
133
+ raise "Cannot retrieve last of no order column is set" if @order_by.nil?
134
+ lq = clone.limit(1)
135
+ lq.order_by = @order_by.inverse
136
+ lq[0]
137
+ end
138
+ end
139
+
140
+ end
@@ -0,0 +1,6 @@
1
+ module Believer
2
+ module Querying
3
+ delegate :select, :where, :order, :limit, :to => :scoped
4
+
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ module Believer
2
+ class ScopedCommand < Command
3
+
4
+ attr_accessor :wheres
5
+
6
+ def query_attributes
7
+ attrs = super
8
+ attrs.merge(:wheres => @wheres)
9
+ end
10
+
11
+ def where(*args)
12
+ q = clone
13
+ q.wheres ||= []
14
+ q.wheres << WhereClause.new(*args)
15
+ q
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module Believer
2
+
3
+ module Scoping
4
+ extend ::ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ def scoped
9
+ Query.new(:record_class => self)
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,51 @@
1
+ module Believer
2
+ module Test
3
+ module RSpec
4
+ # Controls the life cycle for all objects created in a test
5
+ module TestRunLifeCycle
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ Believer::Base.observers = Destructor
10
+
11
+ after(:each) do
12
+ Destructor.instance.cleanup
13
+ end
14
+ end
15
+
16
+ def saved_models
17
+ @saved_models ||= []
18
+ end
19
+
20
+ def after_save(model)
21
+ saved_models << model
22
+ end
23
+
24
+ # Detroys all CqlRecord::Base instances created
25
+ class Destructor < Believer::Observer
26
+ observe Believer::Base
27
+
28
+ def cleanup
29
+ saved_models.each do |model|
30
+ begin
31
+ model.destroy
32
+ rescue Exception => e
33
+ puts "Could not destroy model #{model}: #{e}\n#{e.backtrace.join("\n")}"
34
+ end
35
+ end
36
+ end
37
+
38
+ def saved_models
39
+ @saved_models ||= []
40
+ end
41
+
42
+ def after_save(model)
43
+ saved_models << model
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,40 @@
1
+ module Believer
2
+
3
+ module Values
4
+
5
+ TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S%z'
6
+
7
+ def to_cql_literal(value)
8
+ return 'NULL' if value.nil?
9
+ return "'#{value}'" if value.is_a?(String)
10
+ return "#{value}" if value.is_a?(Numeric)
11
+ return "'#{value.strftime(TIMESTAMP_FORMAT)}'" if value.is_a?(Time) || value.is_a?(DateTime)
12
+ #return "#{value.to_i * 1000}" if value.is_a?(Time) || value.is_a?(DateTime)
13
+ return nil
14
+ end
15
+
16
+ def convert_to_integer(v)
17
+ return v.to_i unless v.nil?
18
+ nil
19
+ end
20
+
21
+ def convert_to_float(v)
22
+ return v.to_f unless v.nil?
23
+ nil
24
+ end
25
+
26
+ def convert_to_boolean(v)
27
+ return v.to_bool if v.respond_to?(:to_bool)
28
+ nil
29
+ end
30
+
31
+ def convert_to_time(v)
32
+ return nil if v.nil?
33
+ return v if v.is_a?(Time)
34
+ return Time.parse(v) if v.is_a?(String)
35
+ Time.at(v.to_i)
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,5 @@
1
+ module Believer
2
+ module Version
3
+ VERSION = '0.1'
4
+ end
5
+ end
@@ -0,0 +1,40 @@
1
+ module Believer
2
+
3
+ class WhereClause
4
+ include Values
5
+
6
+ def initialize(*args)
7
+ if args.any?
8
+ if args[0].is_a?(Hash)
9
+ @value_map = args[0]
10
+ else
11
+ @where_string = args[0]
12
+ @bindings = args.slice(1, args.length - 1)
13
+ end
14
+ end
15
+ end
16
+
17
+ def to_cql
18
+ if @value_map && @value_map.any?
19
+ return @value_map.keys.map {|k| create_expression(k, @value_map[k])}.join(' AND ')
20
+ end
21
+ binding_index = 0
22
+ ws = @where_string.gsub(/\?/) { |match|
23
+ rep_val = to_cql_literal(@bindings[binding_index])
24
+ binding_index += 1
25
+ rep_val
26
+ }
27
+ ws
28
+ end
29
+
30
+ private
31
+ def create_expression(key, value)
32
+ if value.is_a?(Enumerable)
33
+ values = value.map {|v| to_cql_literal(v)}.join(',')
34
+ return "#{key} IN (#{values})"
35
+ end
36
+ "#{key} = #{to_cql_literal(value)}"
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe Believer::Base do
4
+
5
+
6
+ end