believer 0.2.5 → 0.2.6
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 +29 -2
- data/lib/believer.rb +5 -0
- data/lib/believer/base.rb +16 -4
- data/lib/believer/column.rb +74 -9
- data/lib/believer/columns.rb +18 -1
- data/lib/believer/command.rb +12 -1
- data/lib/believer/counter.rb +60 -0
- data/lib/believer/counting.rb +29 -0
- data/lib/believer/cql_helper.rb +22 -0
- data/lib/believer/create_table.rb +24 -0
- data/lib/believer/ddl.rb +2 -31
- data/lib/believer/drop_table.rb +9 -0
- data/lib/believer/environment/base_env.rb +5 -0
- data/lib/believer/insert.rb +3 -1
- data/lib/believer/persistence.rb +18 -2
- data/lib/believer/update.rb +52 -0
- data/lib/believer/values.rb +67 -0
- data/lib/believer/version.rb +1 -1
- data/spec/believer/base_spec.rb +9 -2
- data/spec/believer/callback_spec.rb +1 -1
- data/spec/believer/collection_columns_spec.rb +95 -0
- data/spec/believer/columns_spec.rb +21 -0
- data/spec/believer/counter_spec.rb +46 -0
- data/spec/believer/counting_spec.rb +31 -0
- data/spec/believer/delete_spec.rb +1 -1
- data/spec/believer/environment_spec.rb +4 -4
- data/spec/believer/finder_methods_spec.rb +9 -9
- data/spec/believer/insert_spec.rb +1 -1
- data/spec/believer/limit_spec.rb +1 -1
- data/spec/believer/order_by_spec.rb +2 -2
- data/spec/believer/query_spec.rb +2 -2
- data/spec/believer/querying_spec.rb +1 -1
- data/spec/believer/relation_spec.rb +6 -6
- data/spec/believer/test_run_life_cycle_spec.rb +2 -2
- data/spec/believer/time_series_spec.rb +23 -3
- data/spec/believer/update_spec.rb +71 -0
- data/spec/believer/where_spec.rb +4 -4
- data/spec/spec_helper.rb +6 -2
- data/spec/support/setup_database.rb +8 -8
- data/spec/support/test_classes.rb +22 -5
- metadata +17 -19
- data/lib/believer/extensions/will_paginate.rb +0 -173
data/README.md
CHANGED
@@ -47,9 +47,36 @@ This is the class you should extend from.
|
|
47
47
|
#### The column class method
|
48
48
|
Defines the mapping between a Ruby object attribute and a Cassandra column. Also defines a getter and setter attribute with the same name.
|
49
49
|
The second argument is a Hash, which support the following keys:
|
50
|
-
* type: the data type. Supported values are: :string, :integer, :float, :timestamp, :time
|
50
|
+
* type: the data type. Supported values are: :string, :integer, :float, :timestamp, :time, :array, :set, :map
|
51
51
|
* cql_type: the CQL data type.
|
52
|
-
|
52
|
+
* element_type: sets the type of elements in a collection if the type is a :set (CQL type SET) or an :array (CQL type LIST)
|
53
|
+
* key_type; set the type of the hash keys is the type is a :hash (CQL type MAP)
|
54
|
+
* value_type; set the type of the hash values is the type is a :hash (CQL type MAP)
|
55
|
+
|
56
|
+
Note: for correct type determination, you must include either or both the :type or the :cql_type options.
|
57
|
+
|
58
|
+
#### Counters
|
59
|
+
You can use CQL counters by setting the column type to :counter. The internal value is a Believer::Counter instance,
|
60
|
+
and can be manipulated in the following ways:
|
61
|
+
|
62
|
+
``` ruby
|
63
|
+
class AlbumSales < Believer::Base
|
64
|
+
column :artist_name
|
65
|
+
column :name
|
66
|
+
|
67
|
+
column :sales, :type => :counter
|
68
|
+
|
69
|
+
primary_key :artist_name, :name
|
70
|
+
end
|
71
|
+
|
72
|
+
album_sales = AlbumSales.new
|
73
|
+
album_sales.sales # Returns a Believer::Counter, with a value of 0
|
74
|
+
album_sales.sales.incr # Increment counter by 1, value is 1
|
75
|
+
album_sales.sales.incr(6) # Increment counter by 3, value is 7
|
76
|
+
album_sales.sales.decr # Decrement counter by 1, value is 6
|
77
|
+
album_sales.sales.decr(3) # Decrement counter by 3, value is 3
|
78
|
+
album_sales.sales.reset! # Reset it to the initial value, which is 0
|
79
|
+
```
|
53
80
|
|
54
81
|
#### The primary_key class method
|
55
82
|
Sets the primary key columns of the class.
|
data/lib/believer.rb
CHANGED
@@ -19,6 +19,7 @@ require 'believer/environment/rails_env'
|
|
19
19
|
require 'believer/environment/merb_env'
|
20
20
|
require 'believer/environment'
|
21
21
|
require 'believer/connection'
|
22
|
+
require 'believer/counter'
|
22
23
|
require 'believer/values'
|
23
24
|
require 'believer/column'
|
24
25
|
require 'believer/columns'
|
@@ -32,6 +33,7 @@ require 'believer/filter_command'
|
|
32
33
|
require 'believer/query'
|
33
34
|
require 'believer/empty_result'
|
34
35
|
require 'believer/delete'
|
36
|
+
require 'believer/update'
|
35
37
|
require 'believer/insert'
|
36
38
|
require 'believer/querying'
|
37
39
|
require 'believer/scoping'
|
@@ -44,6 +46,9 @@ require 'believer/log_subscriber'
|
|
44
46
|
require 'believer/observer'
|
45
47
|
require 'believer/relation'
|
46
48
|
|
49
|
+
require 'believer/create_table'
|
50
|
+
require 'believer/drop_table'
|
47
51
|
require 'believer/ddl'
|
52
|
+
require 'believer/counting'
|
48
53
|
require 'believer/base'
|
49
54
|
|
data/lib/believer/base.rb
CHANGED
@@ -12,6 +12,7 @@ module Believer
|
|
12
12
|
extend Querying
|
13
13
|
extend FinderMethods
|
14
14
|
include Callbacks
|
15
|
+
include Counting
|
15
16
|
include DDL
|
16
17
|
|
17
18
|
include ::ActiveModel::Observing
|
@@ -21,10 +22,7 @@ module Believer
|
|
21
22
|
|
22
23
|
def initialize(attrs = {})
|
23
24
|
@attributes = {}
|
24
|
-
attrs
|
25
|
-
send("#{name}=".to_sym, val)
|
26
|
-
end if attrs.present?
|
27
|
-
|
25
|
+
set_attributes(attrs)
|
28
26
|
yield self if block_given?
|
29
27
|
end
|
30
28
|
|
@@ -32,6 +30,20 @@ module Believer
|
|
32
30
|
new(row)
|
33
31
|
end
|
34
32
|
|
33
|
+
def reload!
|
34
|
+
persisted_object = self.class.scoped.where(key_values).first
|
35
|
+
unless persisted_object.nil?
|
36
|
+
set_attributes(persisted_object.attributes)
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_attributes(attrs)
|
42
|
+
attrs.each do |name, val|
|
43
|
+
send("#{name}=".to_sym, val)
|
44
|
+
end if attrs.present?
|
45
|
+
end
|
46
|
+
|
35
47
|
def ==(obj)
|
36
48
|
eql?(obj)
|
37
49
|
end
|
data/lib/believer/column.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Believer
|
2
4
|
|
3
5
|
# Represents a Cassandra table column
|
@@ -9,7 +11,7 @@ module Believer
|
|
9
11
|
:bigint => {:ruby_type => :integer}, # integers 64-bit signed long
|
10
12
|
:blob => {:ruby_type => :string}, # blobs Arbitrary bytes (no validation), expressed as hexadecimal
|
11
13
|
:boolean => {:ruby_type => :boolean}, # booleans true or false
|
12
|
-
:counter => {:ruby_type => :
|
14
|
+
:counter => {:ruby_type => :counter}, # integers Distributed counter value (64-bit long)
|
13
15
|
:decimal => {:ruby_type => :float}, # integers, floats Variable-precision decimal
|
14
16
|
:double => {:ruby_type => :float}, # integers 64-bit IEEE-754 floating point
|
15
17
|
:float => {:ruby_type => :float}, # integers, floats 32-bit IEEE-754 floating point
|
@@ -17,25 +19,35 @@ module Believer
|
|
17
19
|
:int => {:ruby_type => :integer}, # integers 32-bit signed integer
|
18
20
|
:list => {:ruby_type => :array}, # n/a A collection of one or more ordered elements
|
19
21
|
:map => {:ruby_type => :hash}, # n/a A JSON-style array of literals: { literal : literal, literal : literal ... }
|
20
|
-
:set => {:ruby_type => :
|
22
|
+
:set => {:ruby_type => :set}, # n/a A collection of one or more elements
|
21
23
|
:text => {:ruby_type => :string}, # strings UTF-8 encoded string
|
22
24
|
:timestamp => {:ruby_type => :time}, # integers, strings Date plus time, encoded as 8 bytes since epoch
|
23
25
|
:uuid => {:ruby_type => :string}, # uuids A UUID in standard UUID format
|
24
26
|
:timeuuid => {:ruby_type => :integer}, # uuids Type 1 UUID only (CQL 3)
|
25
27
|
:varchar => {:ruby_type => :string}, # strings UTF-8 encoded string
|
26
|
-
:varint => {:ruby_type => :integer}
|
28
|
+
:varint => {:ruby_type => :integer} # integers Arbitrary-precision integer
|
27
29
|
}
|
28
30
|
|
29
31
|
# Supported Ruby 'types'
|
30
32
|
RUBY_TYPES = {
|
33
|
+
:symbol => {:default_cql_type => :varchar},
|
31
34
|
:integer => {:default_cql_type => :int},
|
32
35
|
:string => {:default_cql_type => :varchar},
|
33
36
|
:time => {:default_cql_type => :timestamp},
|
34
37
|
:timestamp => {:default_cql_type => :timestamp},
|
35
|
-
:float => {:default_cql_type => :float}
|
38
|
+
:float => {:default_cql_type => :float},
|
39
|
+
:array => {:default_cql_type => :list},
|
40
|
+
:set => {:default_cql_type => :set},
|
41
|
+
:hash => {:default_cql_type => :map},
|
42
|
+
:counter => {:default_cql_type => :counter, :default_value => lambda { Counter.new } }
|
36
43
|
}
|
37
44
|
|
38
|
-
attr_reader :name,
|
45
|
+
attr_reader :name,
|
46
|
+
:ruby_type,
|
47
|
+
:cql_type,
|
48
|
+
:element_type,
|
49
|
+
:key_type,
|
50
|
+
:value_type
|
39
51
|
|
40
52
|
# Creates a new instance.
|
41
53
|
# @param opts [Hash] values options
|
@@ -48,16 +60,69 @@ module Believer
|
|
48
60
|
raise "Invalid type #{opts[:type]}" unless RUBY_TYPES.has_key?(opts[:type])
|
49
61
|
|
50
62
|
@name = opts[:name]
|
51
|
-
@
|
63
|
+
@ruby_type = opts[:type].nil? ? CQL_TYPES[opts[:cql_type]][:ruby_type] : opts[:type]
|
52
64
|
@cql_type = opts[:cql_type].nil? ? RUBY_TYPES[opts[:type]][:default_cql_type] : opts[:cql_type]
|
65
|
+
|
66
|
+
@element_type = opts[:element_type]
|
67
|
+
@key_type = opts[:key_type]
|
68
|
+
@value_type = opts[:value_type]
|
69
|
+
|
70
|
+
@default_value = opts[:default_value]
|
71
|
+
|
72
|
+
|
53
73
|
end
|
54
74
|
|
55
75
|
# Converts the value to a one that conforms to the type of this column
|
56
76
|
# @param v [Object] the value
|
57
77
|
def convert_to_type(v)
|
58
|
-
|
59
|
-
|
60
|
-
|
78
|
+
# TODO: kind of a dirty hack, this...
|
79
|
+
case @ruby_type
|
80
|
+
when :array
|
81
|
+
return convert_to_array(v, element_type)
|
82
|
+
when :set
|
83
|
+
return convert_to_set(v, element_type)
|
84
|
+
when :hash
|
85
|
+
return convert_to_hash(v, key_type, value_type)
|
86
|
+
end
|
87
|
+
convert_value_to_type(v, @ruby_type)
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_cql
|
91
|
+
col_stmt = "#{name} #{cql_type}"
|
92
|
+
if cql_type == :list
|
93
|
+
col_stmt << "<#{to_cql_type(element_type)}>"
|
94
|
+
elsif cql_type == :set
|
95
|
+
col_stmt << "<#{to_cql_type(element_type)}>"
|
96
|
+
elsif cql_type == :map
|
97
|
+
col_stmt << "<#{to_cql_type(key_type)},#{to_cql_type(value_type)}>"
|
98
|
+
end
|
99
|
+
col_stmt
|
100
|
+
end
|
101
|
+
|
102
|
+
def has_default_value?
|
103
|
+
(@default_value != nil) || RUBY_TYPES[ruby_type][:default_value] != nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def default_value
|
107
|
+
def_val = @default_value || RUBY_TYPES[ruby_type][:default_value]
|
108
|
+
unless def_val.nil?
|
109
|
+
return def_val.call if def_val.is_a?(Proc)
|
110
|
+
return def_val
|
111
|
+
end
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
def to_cql_type(t)
|
117
|
+
return t if CQL_TYPES.has_key?(t)
|
118
|
+
return RUBY_TYPES[t][:default_cql_type] if RUBY_TYPES.has_key?(t)
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_ruby_type(t)
|
123
|
+
return t if RUBY_TYPES.has_key?(t)
|
124
|
+
return CQL_TYPES[t][:ruby_type] if CQL_TYPES.has_key?(t)
|
125
|
+
nil
|
61
126
|
end
|
62
127
|
|
63
128
|
end
|
data/lib/believer/columns.rb
CHANGED
@@ -47,6 +47,10 @@ module Believer
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
def columns_with_type(t)
|
51
|
+
columns.values.find_all {|col| col.ruby_type == t}
|
52
|
+
end
|
53
|
+
|
50
54
|
def primary_key(*cols)
|
51
55
|
@primary_key = *cols
|
52
56
|
end
|
@@ -91,13 +95,26 @@ module Believer
|
|
91
95
|
end
|
92
96
|
|
93
97
|
def read_attribute(attr_name)
|
98
|
+
col = self.class.columns[attr_name]
|
99
|
+
if !@attributes.has_key?(attr_name) && col && col.has_default_value?
|
100
|
+
write_attribute(attr_name, col.default_value)
|
101
|
+
end
|
94
102
|
@attributes[attr_name]
|
95
103
|
end
|
96
104
|
|
97
105
|
def write_attribute(attr_name, value)
|
98
106
|
v = value
|
99
107
|
# Convert the value to the actual type
|
100
|
-
|
108
|
+
col = self.class.columns[attr_name]
|
109
|
+
unless col.nil?
|
110
|
+
cur_val = @attributes[attr_name]
|
111
|
+
if cur_val && cur_val.respond_to?(:adopt_value)
|
112
|
+
cur_val.adopt_value(value)
|
113
|
+
v = cur_val
|
114
|
+
else
|
115
|
+
v = col.convert_to_type(v)
|
116
|
+
end
|
117
|
+
end
|
101
118
|
@attributes[attr_name] = v
|
102
119
|
end
|
103
120
|
|
data/lib/believer/command.rb
CHANGED
@@ -28,7 +28,13 @@ module Believer
|
|
28
28
|
self.class.name.split('::').last.underscore
|
29
29
|
end
|
30
30
|
|
31
|
+
def can_execute?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
31
35
|
def execute(name = nil)
|
36
|
+
return false unless can_execute?
|
37
|
+
|
32
38
|
@record_class.connection_pool.with do |connection|
|
33
39
|
cql = to_cql
|
34
40
|
begin
|
@@ -36,7 +42,12 @@ module Believer
|
|
36
42
|
return ActiveSupport::Notifications.instrument('cql.believer', :cql => cql, :name => name) do
|
37
43
|
exec_opts = {}
|
38
44
|
exec_opts[:consistency] = consistency_level unless consistency_level.nil?
|
39
|
-
|
45
|
+
begin
|
46
|
+
return connection.execute(cql, exec_opts)
|
47
|
+
rescue Cql::NotConnectedError => not_connected
|
48
|
+
connection.connect
|
49
|
+
return connection.execute(cql, exec_opts)
|
50
|
+
end
|
40
51
|
end
|
41
52
|
rescue Cql::Protocol::DecodingError => e
|
42
53
|
# Decoding errors tend to #$%# up the connection, resulting in no more activity, so a reconnect is performed here.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
class Counter
|
4
|
+
|
5
|
+
def initialize(v = 0, initial_val = nil)
|
6
|
+
@value = v
|
7
|
+
@initial_value = initial_val.nil? ? @value : initial_val
|
8
|
+
end
|
9
|
+
|
10
|
+
def reset!
|
11
|
+
@value = initial_value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def adopt_value(v)
|
16
|
+
@value = 0 if v.nil?
|
17
|
+
@value = v.to_i
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def incr(val = 1)
|
22
|
+
@value = @value + val
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def incremented?
|
27
|
+
@value > initial_value
|
28
|
+
end
|
29
|
+
|
30
|
+
def decr(val = 1)
|
31
|
+
@value = @value - val
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def decremented?
|
36
|
+
initial_value > @value
|
37
|
+
end
|
38
|
+
|
39
|
+
def diff
|
40
|
+
(@value - initial_value).abs
|
41
|
+
end
|
42
|
+
|
43
|
+
def changed?
|
44
|
+
diff > 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_i
|
48
|
+
@value
|
49
|
+
end
|
50
|
+
|
51
|
+
def initial_value
|
52
|
+
if @initial_value.nil?
|
53
|
+
@initial_value = self.to_i
|
54
|
+
end
|
55
|
+
@initial_value
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Believer
|
2
|
+
module Counting
|
3
|
+
extend ::ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
def counter_columns
|
8
|
+
columns_with_type(:counter)
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_counter_table?
|
12
|
+
counter_columns.any?
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_counter_instance?
|
18
|
+
self.class.is_counter_table?
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_counter_diffs?
|
22
|
+
self.class.counter_columns.any? do |col|
|
23
|
+
counter = self.send(col.name)
|
24
|
+
counter && counter.diff > 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/believer/cql_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Believer
|
2
4
|
|
3
5
|
# Contains various methods for dealing with CQL statements
|
@@ -10,9 +12,29 @@ module Believer
|
|
10
12
|
def to_cql_literal(value)
|
11
13
|
return 'NULL' if value.nil?
|
12
14
|
return "'#{value}'" if value.is_a?(String)
|
15
|
+
return "'#{value}'" if value.is_a?(Symbol)
|
13
16
|
return "#{value}" if value.is_a?(Numeric)
|
14
17
|
return "'#{value.strftime(CQL_TIMESTAMP_FORMAT)}'" if value.is_a?(Time) || value.is_a?(DateTime)
|
15
18
|
#return "#{value.to_i * 1000}" if value.is_a?(Time) || value.is_a?(DateTime)
|
19
|
+
|
20
|
+
if value.is_a?(Counter)
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set
|
25
|
+
if value.is_a?(Set)
|
26
|
+
return "{#{value.map {|v| to_cql_literal(v)}.join(',')}}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Map
|
30
|
+
if value.is_a?(Hash)
|
31
|
+
keys = value.keys
|
32
|
+
return "{#{keys.map {|k| "#{to_cql_literal(k)} : #{to_cql_literal(value[k])}" }.join(',')} }"
|
33
|
+
end
|
34
|
+
|
35
|
+
# List
|
36
|
+
return "[#{value.map {|v| to_cql_literal(v)}.join(',')}]" if value.is_a?(Array)
|
37
|
+
|
16
38
|
return nil
|
17
39
|
end
|
18
40
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Believer
|
2
|
+
class CreateTable < Command
|
3
|
+
|
4
|
+
def to_cql
|
5
|
+
keys = []
|
6
|
+
record_class.get_primary_key.each do |key_part|
|
7
|
+
if key_part.is_a?(Enumerable)
|
8
|
+
keys << "(#{key_part.join(',')})"
|
9
|
+
else
|
10
|
+
keys << key_part
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
s = "CREATE TABLE #{record_class.table_name} (\n"
|
15
|
+
col_statement_parts = record_class.columns.keys.map {|col_name| record_class.columns[col_name].to_cql }
|
16
|
+
s << col_statement_parts.join(",\n")
|
17
|
+
s << ",\n"
|
18
|
+
s << "PRIMARY KEY (#{keys.join(',')})"
|
19
|
+
s << "\n)"
|
20
|
+
s
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|