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.
Files changed (42) hide show
  1. data/README.md +29 -2
  2. data/lib/believer.rb +5 -0
  3. data/lib/believer/base.rb +16 -4
  4. data/lib/believer/column.rb +74 -9
  5. data/lib/believer/columns.rb +18 -1
  6. data/lib/believer/command.rb +12 -1
  7. data/lib/believer/counter.rb +60 -0
  8. data/lib/believer/counting.rb +29 -0
  9. data/lib/believer/cql_helper.rb +22 -0
  10. data/lib/believer/create_table.rb +24 -0
  11. data/lib/believer/ddl.rb +2 -31
  12. data/lib/believer/drop_table.rb +9 -0
  13. data/lib/believer/environment/base_env.rb +5 -0
  14. data/lib/believer/insert.rb +3 -1
  15. data/lib/believer/persistence.rb +18 -2
  16. data/lib/believer/update.rb +52 -0
  17. data/lib/believer/values.rb +67 -0
  18. data/lib/believer/version.rb +1 -1
  19. data/spec/believer/base_spec.rb +9 -2
  20. data/spec/believer/callback_spec.rb +1 -1
  21. data/spec/believer/collection_columns_spec.rb +95 -0
  22. data/spec/believer/columns_spec.rb +21 -0
  23. data/spec/believer/counter_spec.rb +46 -0
  24. data/spec/believer/counting_spec.rb +31 -0
  25. data/spec/believer/delete_spec.rb +1 -1
  26. data/spec/believer/environment_spec.rb +4 -4
  27. data/spec/believer/finder_methods_spec.rb +9 -9
  28. data/spec/believer/insert_spec.rb +1 -1
  29. data/spec/believer/limit_spec.rb +1 -1
  30. data/spec/believer/order_by_spec.rb +2 -2
  31. data/spec/believer/query_spec.rb +2 -2
  32. data/spec/believer/querying_spec.rb +1 -1
  33. data/spec/believer/relation_spec.rb +6 -6
  34. data/spec/believer/test_run_life_cycle_spec.rb +2 -2
  35. data/spec/believer/time_series_spec.rb +23 -3
  36. data/spec/believer/update_spec.rb +71 -0
  37. data/spec/believer/where_spec.rb +4 -4
  38. data/spec/spec_helper.rb +6 -2
  39. data/spec/support/setup_database.rb +8 -8
  40. data/spec/support/test_classes.rb +22 -5
  41. metadata +17 -19
  42. 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
- For type determination, you must include either or both the :type or the :cql_type options.
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.
@@ -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
 
@@ -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.each do |name, val|
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
@@ -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 => :integer}, # integers Distributed counter value (64-bit long)
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 => :array}, # n/a A collection of one or more elements
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}, # integers Arbitrary-precision 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, :type, :cql_type
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
- @type = opts[:type].nil? ? CQL_TYPES[opts[:cql_type]][:ruby_type] : opts[:type]
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
- convert_method = "convert_to_#{@type}".to_sym
59
- return self.send(convert_method, v) if respond_to?(convert_method)
60
- v
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
@@ -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
- v = self.class.columns[attr_name].convert_to_type(v) unless self.class.columns[attr_name].nil?
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
 
@@ -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
- return connection.execute(cql, exec_opts)
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
@@ -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