believer 0.1.4 → 0.2
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 +168 -53
- data/lib/believer.rb +29 -26
- data/lib/believer/base.rb +2 -0
- data/lib/believer/column.rb +65 -0
- data/lib/believer/columns.rb +3 -62
- data/lib/believer/command.rb +14 -16
- data/lib/believer/connection.rb +25 -6
- data/lib/believer/cql_helper.rb +20 -0
- data/lib/believer/ddl.rb +7 -5
- data/lib/believer/delete.rb +4 -2
- data/lib/believer/environment.rb +43 -13
- data/lib/believer/environment/merb_env.rb +14 -0
- data/lib/believer/environment/rails_env.rb +3 -4
- data/lib/believer/insert.rb +1 -1
- data/lib/believer/model_schema.rb +10 -44
- data/lib/believer/order_by.rb +8 -1
- data/lib/believer/persistence.rb +6 -1
- data/lib/believer/query.rb +3 -3
- data/lib/believer/relation.rb +90 -0
- data/lib/believer/scoped_command.rb +11 -4
- data/lib/believer/test/test_run_life_cycle.rb +49 -0
- data/lib/believer/values.rb +2 -11
- data/lib/believer/version.rb +1 -1
- data/lib/believer/where_clause.rb +1 -1
- data/spec/believer/delete_spec.rb +2 -2
- data/spec/believer/environment_spec.rb +32 -0
- data/spec/believer/insert_spec.rb +2 -2
- data/spec/believer/query_spec.rb +14 -14
- data/spec/believer/relation_spec.rb +33 -0
- data/spec/spec_helper.rb +6 -20
- data/spec/support/setup_database.rb +1 -1
- data/spec/support/test_classes.rb +34 -21
- metadata +27 -7
- data/lib/believer/owner.rb +0 -48
- data/lib/believer/primary_key.rb +0 -5
- data/lib/believer/test/rspec/test_run_life_cycle.rb +0 -51
- data/spec/believer/environment/rails_env_spec.rb +0 -0
data/lib/believer/command.rb
CHANGED
@@ -18,23 +18,21 @@ module Believer
|
|
18
18
|
{:record_class => @record_class}
|
19
19
|
end
|
20
20
|
|
21
|
-
def connection
|
22
|
-
@record_class.connection
|
23
|
-
end
|
24
|
-
|
25
21
|
def execute(name = 'cql.cql_record')
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
22
|
+
@record_class.connection_pool.with do |connection|
|
23
|
+
cql = to_cql
|
24
|
+
begin
|
25
|
+
start = Time.now
|
26
|
+
puts "Executing #{cql}"
|
27
|
+
res = connection.execute(cql)
|
28
|
+
#Rails.logger.debug "#{name} #{sprintf "%.3f", (Time.now - start)*1000.0} ms: #{cql}"
|
29
|
+
return res
|
30
|
+
rescue Cql::Protocol::DecodingError => e
|
31
|
+
# Decoding errors tend to #$%# up the connection, resulting in no more activity, so a reconnect is performed here.
|
32
|
+
# This is a known issue in cql-rb, and will be fixed in version 1.10
|
33
|
+
@record_class.reset_connection(connection)
|
34
|
+
raise e
|
35
|
+
end
|
38
36
|
end
|
39
37
|
|
40
38
|
end
|
data/lib/believer/connection.rb
CHANGED
@@ -1,18 +1,37 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
|
1
3
|
module Believer
|
2
4
|
module Connection
|
3
5
|
extend ::ActiveSupport::Concern
|
4
6
|
|
5
7
|
module ClassMethods
|
6
8
|
|
7
|
-
def reset_connection
|
8
|
-
unless
|
9
|
-
|
10
|
-
@client_connection = nil
|
9
|
+
def reset_connection(conn)
|
10
|
+
unless conn.nil?
|
11
|
+
conn.close
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
-
|
15
|
+
def connection_pool
|
16
|
+
Believer::Connection::Pool.instance.connection(environment)
|
17
|
+
#@client_connection ||= environment.create_connection(:connect_to_keyspace => true)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Pool
|
23
|
+
include ::Singleton
|
24
|
+
|
25
|
+
# Retrieve a connection from the pool
|
26
|
+
# @param environment [Believer::Environment::BaseEnv] the environment with the connection configuration
|
27
|
+
def connection(environment)
|
28
|
+
unless @connection_pool
|
29
|
+
pool_config = environment.connection_pool_configuration
|
30
|
+
@connection_pool ||= ::ConnectionPool.new(pool_config) do
|
31
|
+
environment.create_connection(:connect_to_keyspace => true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@connection_pool
|
16
35
|
end
|
17
36
|
|
18
37
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
# Contains various methods for dealing with CQL statements
|
4
|
+
module CqlHelper
|
5
|
+
|
6
|
+
CQL_TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S%z'
|
7
|
+
|
8
|
+
# Converts a value to a CQL literal
|
9
|
+
# @param value [Object] the value to convert
|
10
|
+
def to_cql_literal(value)
|
11
|
+
return 'NULL' if value.nil?
|
12
|
+
return "'#{value}'" if value.is_a?(String)
|
13
|
+
return "#{value}" if value.is_a?(Numeric)
|
14
|
+
return "'#{value.strftime(CQL_TIMESTAMP_FORMAT)}'" if value.is_a?(Time) || value.is_a?(DateTime)
|
15
|
+
#return "#{value.to_i * 1000}" if value.is_a?(Time) || value.is_a?(DateTime)
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/believer/ddl.rb
CHANGED
@@ -5,15 +5,17 @@ module Believer
|
|
5
5
|
|
6
6
|
module ClassMethods
|
7
7
|
def create_table
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
connection_pool.with do |connection|
|
9
|
+
cql = create_table_cql
|
10
|
+
puts "Creating table #{table_name} using CQL:\n#{cql}"
|
11
|
+
connection.execute(cql)
|
12
|
+
puts "Created table #{table_name}"
|
13
|
+
end
|
12
14
|
end
|
13
15
|
|
14
16
|
def create_table_cql
|
15
17
|
s = "CREATE TABLE #{table_name} (\n"
|
16
|
-
col_statement_parts = columns.keys.map {|col| "#{col} #{columns[col].
|
18
|
+
col_statement_parts = columns.keys.map {|col| "#{col} #{columns[col].cql_type}"}
|
17
19
|
s << col_statement_parts.join(",\n")
|
18
20
|
|
19
21
|
keys = []
|
data/lib/believer/delete.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
module Believer
|
2
|
+
# Creates CQL DELETE statements
|
2
3
|
class Delete < ScopedCommand
|
3
4
|
|
5
|
+
# Creates the CQL
|
4
6
|
def to_cql
|
5
|
-
cql = "DELETE FROM #{
|
6
|
-
cql << " WHERE #{
|
7
|
+
cql = "DELETE FROM #{record_class.table_name}"
|
8
|
+
cql << " WHERE #{wheres.map { |wc| "#{wc.to_cql}" }.join(' AND ')}" if wheres.any?
|
7
9
|
cql
|
8
10
|
end
|
9
11
|
|
data/lib/believer/environment.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#require "believer/environment/rails_env"
|
2
|
+
#require "believer/environment/merb_env"
|
2
3
|
|
3
4
|
module Believer
|
4
5
|
module Environment
|
@@ -7,12 +8,13 @@ module Believer
|
|
7
8
|
module ClassMethods
|
8
9
|
|
9
10
|
def environment
|
10
|
-
|
11
11
|
if @environment.nil?
|
12
12
|
if self.superclass.respond_to?(:environment)
|
13
13
|
@environment = self.superclass.environment
|
14
14
|
elsif defined?(::Rails)
|
15
15
|
@environment = ::Believer::Environment::RailsEnv.new
|
16
|
+
elsif defined?(::Merb)
|
17
|
+
@environment = ::Believer::Environment::MerbEnv.new
|
16
18
|
end
|
17
19
|
end
|
18
20
|
@environment
|
@@ -25,29 +27,41 @@ module Believer
|
|
25
27
|
end
|
26
28
|
|
27
29
|
class BaseEnv
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
# Default pool configuration
|
31
|
+
DEFAULT_POOL_CONFIG = {
|
32
|
+
:size => 1,
|
33
|
+
:timeout => 10
|
34
|
+
}
|
35
|
+
|
36
|
+
# Creates a new environment using the provided configuration
|
37
|
+
# @param config [Hash] the configuration
|
32
38
|
def initialize(config = nil)
|
33
39
|
@configuration = config.dup unless config.nil?
|
34
40
|
end
|
35
41
|
|
42
|
+
# Returns the configuration. This configuration hash should contain the cql-rb client connection parameters.
|
43
|
+
# Optionally the connection_pool configuraton can be included in a :pool node.
|
36
44
|
def configuration
|
37
45
|
@configuration ||= load_configuration
|
38
46
|
end
|
39
47
|
|
48
|
+
# Sets the configuration
|
40
49
|
def configuration=(config)
|
41
50
|
@configuration = config
|
42
51
|
end
|
43
52
|
|
44
53
|
def connection_configuration
|
45
|
-
|
54
|
+
configuration.reject {|k, v| k == :pool}
|
55
|
+
end
|
56
|
+
|
57
|
+
# The connection_pool configuration, which should be a :pool node in the configuration.
|
58
|
+
def connection_pool_configuration
|
59
|
+
DEFAULT_POOL_CONFIG.merge(configuration[:pool].is_a?(Hash) ? configuration[:pool].symbolize_keys! : {})
|
46
60
|
end
|
47
61
|
|
62
|
+
# Creates a new connection
|
48
63
|
def create_connection(options = {})
|
49
64
|
cc = connection_configuration
|
50
|
-
|
51
65
|
if options[:connect_to_keyspace] && cc[:keyspace]
|
52
66
|
connection = Cql::Client.connect(cc)
|
53
67
|
connection.use(cc[:keyspace])
|
@@ -57,16 +71,32 @@ module Believer
|
|
57
71
|
connection
|
58
72
|
end
|
59
73
|
|
60
|
-
def create_keyspace(connection = nil)
|
61
|
-
conn = connection || create_connection
|
74
|
+
def create_keyspace(properties = {}, connection = nil)
|
75
|
+
conn = connection || create_connection(:connect_to_keyspace => false)
|
76
|
+
|
77
|
+
default_properties = {:replication => {:class => 'SimpleStrategy', :replication_factor => 1}}
|
78
|
+
ks_props = default_properties.merge(properties)
|
79
|
+
|
80
|
+
ks_props_s = ks_props.keys.map {|k|
|
81
|
+
v = ks_props[k]
|
82
|
+
v_s = nil
|
83
|
+
if v.is_a?(Hash)
|
84
|
+
v_s = v.to_json.gsub(/\"/) {|m| "'"}
|
85
|
+
elsif v.is_a?(String)
|
86
|
+
v_s = "'#{v}'"
|
87
|
+
else
|
88
|
+
v_s = v.to_s
|
89
|
+
end
|
90
|
+
"#{k} = #{v_s}"
|
91
|
+
}.join("\nAND ")
|
92
|
+
|
62
93
|
ks_def = <<-KS_DEF
|
63
94
|
CREATE KEYSPACE #{connection_configuration[:keyspace]}
|
64
|
-
WITH
|
65
|
-
'class': 'SimpleStrategy',
|
66
|
-
'replication_factor': 1
|
67
|
-
}
|
95
|
+
WITH #{ks_props_s}
|
68
96
|
KS_DEF
|
69
97
|
|
98
|
+
puts ks_def
|
99
|
+
|
70
100
|
conn.execute(ks_def)
|
71
101
|
end
|
72
102
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module Believer
|
3
|
+
module Environment
|
4
|
+
class MerbEnv < Believer::Environment::BaseEnv
|
5
|
+
|
6
|
+
def load_configuration
|
7
|
+
config_file = File.join(Merb.root, 'config', 'believer.yml')
|
8
|
+
config = HashWithIndifferentAccess.new(YAML::load(File.open(config_file.to_s)))
|
9
|
+
env_config = config[Merb.environment]
|
10
|
+
env_config
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -4,11 +4,10 @@ module Believer
|
|
4
4
|
class RailsEnv < Believer::Environment::BaseEnv
|
5
5
|
|
6
6
|
def load_configuration
|
7
|
-
config_file = Rails.root
|
8
|
-
config = YAML::load(File.open(config_file.to_s))
|
7
|
+
config_file = File.join(Rails.root, 'config', 'believer.yml')
|
8
|
+
config = HashWithIndifferentAccess.new(YAML::load(File.open(config_file.to_s)))
|
9
9
|
env_config = config[Rails.env]
|
10
|
-
env_config =
|
11
|
-
env_config[:logger] = Rails.logger
|
10
|
+
env_config[:logger] = Rails.logger unless Rails.logger.nil?
|
12
11
|
env_config
|
13
12
|
end
|
14
13
|
end
|
data/lib/believer/insert.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Believer
|
2
|
+
|
3
|
+
# Mostly *borrowed* from ActiveRecord::ModelSchema ;)
|
2
4
|
module ModelSchema
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
@@ -34,8 +36,8 @@ module Believer
|
|
34
36
|
|
35
37
|
module ClassMethods
|
36
38
|
# Guesses the table name (in forced lower-case) based on the name of the class in the
|
37
|
-
# inheritance hierarchy descending directly from
|
38
|
-
# looks like: Reply < Message <
|
39
|
+
# inheritance hierarchy descending directly from Believer::Base. So if the hierarchy
|
40
|
+
# looks like: Reply < Message < Believer::Base, then Message is used
|
39
41
|
# to guess the table name even when called on Reply. The rules used to do the guess
|
40
42
|
# are handled by the Inflector class in Active Support, which knows almost all common
|
41
43
|
# English inflections. You can add new inflections in config/initializers/inflections.rb.
|
@@ -45,14 +47,14 @@ module Believer
|
|
45
47
|
#
|
46
48
|
# ==== Examples
|
47
49
|
#
|
48
|
-
# class Invoice <
|
50
|
+
# class Invoice < Believer::Base
|
49
51
|
# end
|
50
52
|
#
|
51
53
|
# file class table_name
|
52
54
|
# invoice.rb Invoice invoices
|
53
55
|
#
|
54
|
-
# class Invoice <
|
55
|
-
# class Lineitem <
|
56
|
+
# class Invoice < Believer::Base
|
57
|
+
# class Lineitem < Believer::Base
|
56
58
|
# end
|
57
59
|
# end
|
58
60
|
#
|
@@ -60,7 +62,7 @@ module Believer
|
|
60
62
|
# invoice.rb Invoice::Lineitem invoice_lineitems
|
61
63
|
#
|
62
64
|
# module Invoice
|
63
|
-
# class Lineitem <
|
65
|
+
# class Lineitem < Believer::Base
|
64
66
|
# end
|
65
67
|
# end
|
66
68
|
#
|
@@ -74,7 +76,7 @@ module Believer
|
|
74
76
|
#
|
75
77
|
# You can also set your own table name explicitly:
|
76
78
|
#
|
77
|
-
# class Mouse <
|
79
|
+
# class Mouse < Believer::Base
|
78
80
|
# self.table_name = "mice"
|
79
81
|
# end
|
80
82
|
#
|
@@ -82,7 +84,7 @@ module Believer
|
|
82
84
|
# own computation. (Possibly using <tt>super</tt> to manipulate the default
|
83
85
|
# table name.) Example:
|
84
86
|
#
|
85
|
-
# class Post <
|
87
|
+
# class Post < Believer::Base
|
86
88
|
# def self.table_name
|
87
89
|
# "special_" + super
|
88
90
|
# end
|
@@ -93,10 +95,6 @@ module Believer
|
|
93
95
|
@table_name
|
94
96
|
end
|
95
97
|
|
96
|
-
def original_table_name #:nodoc:
|
97
|
-
deprecated_original_property_getter :table_name
|
98
|
-
end
|
99
|
-
|
100
98
|
# Sets the table name explicitly. Example:
|
101
99
|
#
|
102
100
|
# class Project < ActiveRecord::Base
|
@@ -111,11 +109,6 @@ module Believer
|
|
111
109
|
@quoted_table_name = nil
|
112
110
|
end
|
113
111
|
|
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
112
|
# Returns a quoted version of the table name, used to construct SQL statements.
|
120
113
|
def quoted_table_name
|
121
114
|
@quoted_table_name ||= "`#{table_name}`"
|
@@ -144,33 +137,6 @@ module Believer
|
|
144
137
|
"#{full_table_name_prefix}#{undecorated_table_name(name)}#{table_name_suffix}"
|
145
138
|
end
|
146
139
|
|
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
140
|
end
|
175
141
|
end
|
176
142
|
end
|
data/lib/believer/order_by.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
module Believer
|
2
|
+
|
3
|
+
# Encapsulates the CQL ORDER BY clause
|
2
4
|
class OrderBy
|
5
|
+
|
3
6
|
attr_reader :field, :dir
|
4
7
|
|
8
|
+
# @param field [Symbol] the field to order by
|
9
|
+
# @param dir [Symbol] the order direction. Can be :asc or :desc. Default is :asc
|
5
10
|
def initialize(field, dir = :asc)
|
6
11
|
raise "Invalid field: #{field}" unless field.is_a?(Symbol) || field.is_a?(String)
|
7
12
|
raise "Direction must be one of (:asc|:desc): #{dir}" unless dir == :asc || dir == :desc
|
@@ -9,10 +14,12 @@ module Believer
|
|
9
14
|
@dir = dir
|
10
15
|
end
|
11
16
|
|
17
|
+
# Creates the CQL ORDER BY clause
|
12
18
|
def to_cql
|
13
|
-
"ORDER BY #{@field} #{@dir.
|
19
|
+
"ORDER BY #{@field} #{@dir.to_s.upcase}"
|
14
20
|
end
|
15
21
|
|
22
|
+
# Inverts the direction of the order
|
16
23
|
def inverse
|
17
24
|
OrderBy.new(@field, @dir == :asc ? :desc : :asc)
|
18
25
|
end
|
data/lib/believer/persistence.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
module Believer
|
2
2
|
|
3
|
+
# Defines persistence functionality for a class
|
3
4
|
module Persistence
|
4
|
-
|
5
5
|
extend ::ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
|
9
|
+
# Creates 1 or more new instances, and persists them to the database.
|
10
|
+
# An optional block can be provided which is called for each created model.
|
11
|
+
#
|
12
|
+
# @param attributes [Enumerable] the attributes. If this is an array, it is assumed multiple models should be created
|
9
13
|
def create(attributes = nil, &block)
|
10
14
|
if attributes.is_a?(Array)
|
11
15
|
attributes.collect { |attr| create(attr, &block) }
|
@@ -23,6 +27,7 @@ module Believer
|
|
23
27
|
Insert.new(:record_class => self.class, :values => self).execute
|
24
28
|
end
|
25
29
|
|
30
|
+
# Destroys the model.
|
26
31
|
def destroy
|
27
32
|
Delete.new(:record_class => self.class).where(key_values).execute
|
28
33
|
end
|