believer 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,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
|
data/lib/believer/ddl.rb
ADDED
@@ -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,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,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
|