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
@@ -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,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,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,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,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
|