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,44 @@
1
+ module Believer
2
+ class Command
3
+
4
+ attr_accessor :record_class
5
+
6
+ def initialize(attrs = {})
7
+ attrs.each do |name, value|
8
+ send("#{name}=", value)
9
+ end if attrs.present?
10
+ #@instrumenter = ActiveSupport::Notifications.instrumenter
11
+ end
12
+
13
+ def clone
14
+ self.class.new(query_attributes)
15
+ end
16
+
17
+ def query_attributes
18
+ {:record_class => @record_class}
19
+ end
20
+
21
+ def connection
22
+ @record_class.connection
23
+ end
24
+
25
+ def execute(name = 'cql.cql_record')
26
+ cql = to_cql
27
+ begin
28
+ start = Time.now
29
+ puts "Executing #{cql}"
30
+ res = connection.execute(cql)
31
+ #Rails.logger.debug "#{name} #{sprintf "%.3f", (Time.now - start)*1000.0} ms: #{cql}"
32
+ return res
33
+ rescue Cql::Protocol::DecodingError => e
34
+ # Decoding errors tend to #$%# up the connection, resulting in no more activity, so a reconnect is performed here.
35
+ # This is a known issue in cql-rb, and will be fixed in version 1.10
36
+ @record_class.reset_connection
37
+ raise e
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,22 @@
1
+ module Believer
2
+ module Connection
3
+ extend ::ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def reset_connection
8
+ unless connection.nil?
9
+ connection.close
10
+ @client_connection = nil
11
+ end
12
+ end
13
+
14
+ def connection
15
+ @client_connection ||= environment.create_connection(:connect_to_keyspace => true)
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,36 @@
1
+ module Believer
2
+
3
+ module DDL
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def create_table
8
+ cql = create_table_cql
9
+ puts "Creating table #{table_name} using CQL:\n#{cql}"
10
+ connection.execute(cql)
11
+ puts "Created table #{table_name}"
12
+ end
13
+
14
+ def create_table_cql
15
+ s = "CREATE TABLE #{table_name} (\n"
16
+ col_statement_parts = columns.keys.map {|col| "#{col} #{columns[col].cql_column_type}"}
17
+ s << col_statement_parts.join(",\n")
18
+
19
+ keys = []
20
+ get_primary_key.each do |key_part|
21
+ if key_part.is_a?(Enumerable)
22
+ keys << "(#{key_part.join(',')})"
23
+ else
24
+ keys << key_part
25
+ end
26
+ end
27
+ s << ",\n"
28
+ s << "PRIMARY KEY (#{keys.join(',')})"
29
+ s << "\n)"
30
+ s
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,11 @@
1
+ module Believer
2
+ class Delete < ScopedCommand
3
+
4
+ def to_cql
5
+ cql = "DELETE FROM #{@record_class.table_name}"
6
+ cql << " WHERE #{@wheres.map { |wc| "#{wc.to_cql}" }.join(' AND ')}" if @wheres && @wheres.any?
7
+ cql
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ module Believer
2
+
3
+ class EmptyResult
4
+ DATA_METHODS = {
5
+ :execute => [],
6
+ :destroy_all => nil,
7
+ :delete_all => 0,
8
+ :to_a => [],
9
+ :size => 0,
10
+ :count => 0,
11
+ :each => nil,
12
+ :first => nil,
13
+ :last => nil,
14
+ :any? => false
15
+ }
16
+ QUERY_METHODS = [:select, :where, :order, :limit]
17
+
18
+ DATA_METHODS.each do |method_name, return_val|
19
+ define_method(method_name) do |*|
20
+ return_val
21
+ end
22
+ end
23
+
24
+ QUERY_METHODS.each do |method_name|
25
+ define_method(method_name) do |*|
26
+ self
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,51 @@
1
+ module Believer
2
+ module Environment
3
+ extend ::ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def environment
8
+ if @environment.nil?
9
+ if defined?(Rails)
10
+ @environment = RailsEnvironment.new
11
+ end
12
+ end
13
+ @environment
14
+ end
15
+
16
+ def environment=(env)
17
+ @environment = env
18
+ end
19
+
20
+ end
21
+
22
+ class Base
23
+ def create_connection(options = {})
24
+ cc = connection_configuration
25
+
26
+ if options[:connect_to_keyspace] && cc[:keyspace]
27
+ connection = Cql::Client.connect(cc)
28
+ connection.use(cc[:keyspace])
29
+ else
30
+ connection = Cql::Client.connect(cc.delete_if {|k,v|k==:keyspace})
31
+ end
32
+ connection
33
+ end
34
+
35
+ def create_keyspace(connection = nil)
36
+ conn = connection || create_connection
37
+ ks_def = <<-KSDEF
38
+ CREATE KEYSPACE #{connection_configuration[:keyspace]}
39
+ WITH replication = {
40
+ 'class': 'SimpleStrategy',
41
+ 'replication_factor': 1
42
+ }
43
+ KSDEF
44
+
45
+ conn.execute(ks_def)
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Believer
3
+ module Environment
4
+ class RailsEnvironment < Base
5
+
6
+ def connection_configuration
7
+ unless @connection_configuration
8
+ config_file = Rails.root + "config/cql.yml"
9
+ #puts "Using CQL connection config file: #{config_file}"
10
+ config = YAML::load(File.open(config_file.to_s))
11
+ env_config = config[Rails.env]
12
+ @connection_configuration = env_config.symbolize_keys
13
+ @connection_configuration[:logger] = Rails.logger
14
+ end
15
+ @connection_configuration
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Believer
2
+
3
+ class Insert < Command
4
+ include Values
5
+
6
+ attr_accessor :values
7
+
8
+ def values=(v)
9
+ @values = v.is_a?(Base) ? v.attributes : v.to_hash
10
+ end
11
+
12
+ def to_cql
13
+ attrs = @values.keys
14
+ "INSERT INTO #{@record_class.table_name} (#{attrs.join(', ')}) VALUES (#{attrs.map {|a| to_cql_literal(@values[a]) }.join(', ')})"
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,20 @@
1
+ module Believer
2
+
3
+ class Limit
4
+ attr_reader :size
5
+
6
+ def initialize(l)
7
+ @size = l.to_i
8
+ end
9
+
10
+ def to_cql
11
+ "LIMIT #{@size}"
12
+ end
13
+
14
+ def to_i
15
+ @size
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,176 @@
1
+ module Believer
2
+ module ModelSchema
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ ##
7
+ # :singleton-method:
8
+ # Accessor for the name of the prefix string to prepend to every table name. So if set
9
+ # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
10
+ # etc. This is a convenient way of creating a namespace for tables in a shared database.
11
+ # By default, the prefix is the empty string.
12
+ #
13
+ # If you are organising your models within modules you can add a prefix to the models within
14
+ # a namespace by defining a singleton method in the parent module called table_name_prefix which
15
+ # returns your chosen prefix.
16
+ class_attribute :table_name_prefix, :instance_writer => false
17
+ self.table_name_prefix = ""
18
+
19
+ ##
20
+ # :singleton-method:
21
+ # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
22
+ # "people_basecamp"). By default, the suffix is the empty string.
23
+ class_attribute :table_name_suffix, :instance_writer => false
24
+ self.table_name_suffix = ""
25
+
26
+ ##
27
+ # :singleton-method:
28
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
29
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
30
+ # See table_name for the full rules on table/class naming. This is true, by default.
31
+ class_attribute :pluralize_table_names, :instance_writer => false
32
+ self.pluralize_table_names = true
33
+ end
34
+
35
+ module ClassMethods
36
+ # Guesses the table name (in forced lower-case) based on the name of the class in the
37
+ # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
38
+ # looks like: Reply < Message < ActiveRecord::Base, then Message is used
39
+ # to guess the table name even when called on Reply. The rules used to do the guess
40
+ # are handled by the Inflector class in Active Support, which knows almost all common
41
+ # English inflections. You can add new inflections in config/initializers/inflections.rb.
42
+ #
43
+ # Nested classes are given table names prefixed by the singular form of
44
+ # the parent's table name. Enclosing modules are not considered.
45
+ #
46
+ # ==== Examples
47
+ #
48
+ # class Invoice < ActiveRecord::Base
49
+ # end
50
+ #
51
+ # file class table_name
52
+ # invoice.rb Invoice invoices
53
+ #
54
+ # class Invoice < ActiveRecord::Base
55
+ # class Lineitem < ActiveRecord::Base
56
+ # end
57
+ # end
58
+ #
59
+ # file class table_name
60
+ # invoice.rb Invoice::Lineitem invoice_lineitems
61
+ #
62
+ # module Invoice
63
+ # class Lineitem < ActiveRecord::Base
64
+ # end
65
+ # end
66
+ #
67
+ # file class table_name
68
+ # invoice/lineitem.rb Invoice::Lineitem lineitems
69
+ #
70
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
71
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
72
+ # the table name guess for an Invoice class becomes "myapp_invoices".
73
+ # Invoice::Lineitem becomes "myapp_invoice_lineitems".
74
+ #
75
+ # You can also set your own table name explicitly:
76
+ #
77
+ # class Mouse < ActiveRecord::Base
78
+ # self.table_name = "mice"
79
+ # end
80
+ #
81
+ # Alternatively, you can override the table_name method to define your
82
+ # own computation. (Possibly using <tt>super</tt> to manipulate the default
83
+ # table name.) Example:
84
+ #
85
+ # class Post < ActiveRecord::Base
86
+ # def self.table_name
87
+ # "special_" + super
88
+ # end
89
+ # end
90
+ # Post.table_name # => "special_posts"
91
+ def table_name
92
+ reset_table_name unless defined?(@table_name)
93
+ @table_name
94
+ end
95
+
96
+ def original_table_name #:nodoc:
97
+ deprecated_original_property_getter :table_name
98
+ end
99
+
100
+ # Sets the table name explicitly. Example:
101
+ #
102
+ # class Project < ActiveRecord::Base
103
+ # self.table_name = "project"
104
+ # end
105
+ #
106
+ # You can also just define your own <tt>self.table_name</tt> method; see
107
+ # the documentation for ActiveRecord::Base#table_name.
108
+ def table_name=(value)
109
+ @original_table_name = @table_name if defined?(@table_name)
110
+ @table_name = value && value.to_s
111
+ @quoted_table_name = nil
112
+ end
113
+
114
+ def set_table_name(value = nil, &block) #:nodoc:
115
+ deprecated_property_setter :table_name, value, block
116
+ @quoted_table_name = nil
117
+ end
118
+
119
+ # Returns a quoted version of the table name, used to construct SQL statements.
120
+ def quoted_table_name
121
+ @quoted_table_name ||= "`#{table_name}`"
122
+ end
123
+
124
+ # Computes the table name, (re)sets it internally, and returns it.
125
+ def reset_table_name #:nodoc:
126
+ self.table_name = compute_table_name
127
+ end
128
+
129
+ def full_table_name_prefix #:nodoc:
130
+ (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
131
+ end
132
+
133
+ private
134
+
135
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
136
+ def undecorated_table_name(class_name = base_class.name)
137
+ table_name = class_name.to_s.demodulize.underscore
138
+ table_name = table_name.pluralize if pluralize_table_names
139
+ table_name
140
+ end
141
+
142
+ # Computes and returns a table name according to default conventions.
143
+ def compute_table_name
144
+ "#{full_table_name_prefix}#{undecorated_table_name(name)}#{table_name_suffix}"
145
+ end
146
+
147
+ def deprecated_property_setter(property, value, block)
148
+ if block
149
+ ActiveSupport::Deprecation.warn(
150
+ "Calling set_#{property} is deprecated. If you need to lazily evaluate " \
151
+ "the #{property}, define your own `self.#{property}` class method. You can use `super` " \
152
+ "to get the default #{property} where you would have called `original_#{property}`."
153
+ )
154
+
155
+ define_attr_method property, value, false, &block
156
+ else
157
+ ActiveSupport::Deprecation.warn(
158
+ "Calling set_#{property} is deprecated. Please use `self.#{property} = 'the_name'` instead."
159
+ )
160
+
161
+ define_attr_method property, value, false
162
+ end
163
+ end
164
+
165
+ def deprecated_original_property_getter(property)
166
+ ActiveSupport::Deprecation.warn("original_#{property} is deprecated. Define self.#{property} and call super instead.")
167
+
168
+ if !instance_variable_defined?("@original_#{property}") && respond_to?("reset_#{property}")
169
+ send("reset_#{property}")
170
+ else
171
+ instance_variable_get("@original_#{property}")
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_model/observing'
2
+
3
+ module Believer
4
+
5
+ class Observer < ::ActiveModel::Observer
6
+
7
+ protected
8
+
9
+ def observed_classes
10
+ klasses = super
11
+ klasses + klasses.map { |klass| klass.descendants }.flatten
12
+ end
13
+
14
+ def add_observer!(klass)
15
+ super
16
+ define_callbacks klass
17
+ end
18
+
19
+ def define_callbacks(klass)
20
+ observer = self
21
+ observer_name = observer.class.name.underscore.gsub('/', '__')
22
+
23
+ Believer::Callbacks::CALLBACKS.each do |callback|
24
+ next unless respond_to?(callback)
25
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
26
+ unless klass.respond_to?(callback_meth)
27
+ klass.send(:define_method, callback_meth) do |&block|
28
+ observer.update(callback, self, &block)
29
+ end
30
+ klass.send(callback, callback_meth)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ end