believer 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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