superstore 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +11 -0
- data/CHANGELOG +0 -0
- data/Gemfile +19 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.md +100 -0
- data/Rakefile +12 -0
- data/lib/superstore/adapters/abstract_adapter.rb +49 -0
- data/lib/superstore/adapters/cassandra_adapter.rb +181 -0
- data/lib/superstore/adapters/hstore_adapter.rb +163 -0
- data/lib/superstore/attribute_methods/definition.rb +22 -0
- data/lib/superstore/attribute_methods/dirty.rb +36 -0
- data/lib/superstore/attribute_methods/primary_key.rb +25 -0
- data/lib/superstore/attribute_methods/typecasting.rb +59 -0
- data/lib/superstore/attribute_methods.rb +96 -0
- data/lib/superstore/base.rb +33 -0
- data/lib/superstore/belongs_to/association.rb +48 -0
- data/lib/superstore/belongs_to/builder.rb +40 -0
- data/lib/superstore/belongs_to/reflection.rb +30 -0
- data/lib/superstore/belongs_to.rb +63 -0
- data/lib/superstore/callbacks.rb +29 -0
- data/lib/superstore/cassandra_schema/statements.rb +52 -0
- data/lib/superstore/cassandra_schema/tasks.rb +47 -0
- data/lib/superstore/cassandra_schema.rb +9 -0
- data/lib/superstore/connection.rb +39 -0
- data/lib/superstore/core.rb +59 -0
- data/lib/superstore/errors.rb +10 -0
- data/lib/superstore/identity.rb +26 -0
- data/lib/superstore/inspect.rb +25 -0
- data/lib/superstore/log_subscriber.rb +44 -0
- data/lib/superstore/model.rb +37 -0
- data/lib/superstore/persistence.rb +153 -0
- data/lib/superstore/railtie.rb +30 -0
- data/lib/superstore/railties/controller_runtime.rb +45 -0
- data/lib/superstore/schema.rb +20 -0
- data/lib/superstore/scope/batches.rb +32 -0
- data/lib/superstore/scope/finder_methods.rb +48 -0
- data/lib/superstore/scope/query_methods.rb +49 -0
- data/lib/superstore/scope.rb +49 -0
- data/lib/superstore/scoping.rb +27 -0
- data/lib/superstore/tasks/ks.rake +54 -0
- data/lib/superstore/timestamps.rb +19 -0
- data/lib/superstore/type.rb +16 -0
- data/lib/superstore/types/array_type.rb +20 -0
- data/lib/superstore/types/base_type.rb +26 -0
- data/lib/superstore/types/boolean_type.rb +20 -0
- data/lib/superstore/types/date_type.rb +22 -0
- data/lib/superstore/types/float_type.rb +16 -0
- data/lib/superstore/types/integer_type.rb +20 -0
- data/lib/superstore/types/json_type.rb +13 -0
- data/lib/superstore/types/string_type.rb +19 -0
- data/lib/superstore/types/time_type.rb +16 -0
- data/lib/superstore/types.rb +8 -0
- data/lib/superstore/validations.rb +44 -0
- data/lib/superstore.rb +69 -0
- data/superstore.gemspec +23 -0
- data/test/support/cassandra.rb +44 -0
- data/test/support/hstore.rb +40 -0
- data/test/support/issue.rb +10 -0
- data/test/test_helper.rb +42 -0
- data/test/unit/active_model_test.rb +18 -0
- data/test/unit/adapters/adapter_test.rb +6 -0
- data/test/unit/attribute_methods/definition_test.rb +13 -0
- data/test/unit/attribute_methods/dirty_test.rb +72 -0
- data/test/unit/attribute_methods/primary_key_test.rb +26 -0
- data/test/unit/attribute_methods/typecasting_test.rb +118 -0
- data/test/unit/attribute_methods_test.rb +51 -0
- data/test/unit/base_test.rb +20 -0
- data/test/unit/belongs_to/reflection_test.rb +12 -0
- data/test/unit/belongs_to_test.rb +62 -0
- data/test/unit/callbacks_test.rb +46 -0
- data/test/unit/cassandra_schema/statements_test.rb +47 -0
- data/test/unit/cassandra_schema/tasks_test.rb +31 -0
- data/test/unit/connection_test.rb +10 -0
- data/test/unit/core_test.rb +55 -0
- data/test/unit/identity_test.rb +26 -0
- data/test/unit/inspect_test.rb +26 -0
- data/test/unit/log_subscriber_test.rb +26 -0
- data/test/unit/persistence_test.rb +213 -0
- data/test/unit/railties/controller_runtime_test.rb +48 -0
- data/test/unit/schema_test.rb +27 -0
- data/test/unit/scope/batches_test.rb +30 -0
- data/test/unit/scope/finder_methods_test.rb +51 -0
- data/test/unit/scope/query_methods_test.rb +27 -0
- data/test/unit/scoping_test.rb +7 -0
- data/test/unit/serialization_test.rb +10 -0
- data/test/unit/timestamps_test.rb +27 -0
- data/test/unit/types/array_type_test.rb +21 -0
- data/test/unit/types/base_type_test.rb +19 -0
- data/test/unit/types/boolean_type_test.rb +24 -0
- data/test/unit/types/date_type_test.rb +15 -0
- data/test/unit/types/float_type_test.rb +17 -0
- data/test/unit/types/integer_type_test.rb +19 -0
- data/test/unit/types/json_type_test.rb +23 -0
- data/test/unit/types/string_type_test.rb +30 -0
- data/test/unit/types/time_type_test.rb +19 -0
- data/test/unit/validations_test.rb +27 -0
- metadata +170 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
3
|
+
def self.runtime=(value)
|
|
4
|
+
Thread.current["cassandra_object_request_runtime"] = value
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.runtime
|
|
8
|
+
Thread.current["cassandra_object_request_runtime"] ||= 0
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.reset_runtime
|
|
12
|
+
rt, self.runtime = runtime, 0
|
|
13
|
+
rt
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
super
|
|
18
|
+
@odd_or_even = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def cql(event)
|
|
22
|
+
self.class.runtime += event.duration
|
|
23
|
+
|
|
24
|
+
payload = event.payload
|
|
25
|
+
name = '%s (%.1fms)' % [payload[:name], event.duration]
|
|
26
|
+
cql = payload[:cql].squeeze(' ')
|
|
27
|
+
|
|
28
|
+
if odd?
|
|
29
|
+
name = color(name, CYAN, true)
|
|
30
|
+
cql = color(cql, nil, true)
|
|
31
|
+
else
|
|
32
|
+
name = color(name, MAGENTA, true)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
debug " #{name} #{cql}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def odd?
|
|
39
|
+
@odd_or_even = !@odd_or_even
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Superstore::LogSubscriber.attach_to :cassandra_object
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Model
|
|
3
|
+
def column_family=(column_family)
|
|
4
|
+
@column_family = column_family
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def column_family
|
|
8
|
+
@column_family ||= base_class.name.pluralize
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def base_class
|
|
12
|
+
class_of_active_record_descendant(self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def config=(config)
|
|
16
|
+
@@config = config.deep_symbolize_keys
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def config
|
|
20
|
+
@@config
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Returns the class descending directly from ActiveRecord::Base or an
|
|
26
|
+
# abstract class, if any, in the inheritance hierarchy.
|
|
27
|
+
def class_of_active_record_descendant(klass)
|
|
28
|
+
if klass == Base || klass.superclass == Base
|
|
29
|
+
klass
|
|
30
|
+
elsif klass.superclass.nil?
|
|
31
|
+
raise "#{name} doesn't belong in a hierarchy descending from Superstore"
|
|
32
|
+
else
|
|
33
|
+
class_of_active_record_descendant(klass.superclass)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Persistence
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
class_attribute :batch_statements
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def remove(ids)
|
|
11
|
+
adapter.delete column_family, ids
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def delete_all
|
|
15
|
+
adapter.execute "TRUNCATE #{column_family}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create(attributes = {}, &block)
|
|
19
|
+
new(attributes, &block).tap do |object|
|
|
20
|
+
object.save
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def insert_record(id, attributes)
|
|
25
|
+
adapter.insert column_family, id, encode_attributes(attributes)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update_record(id, attributes)
|
|
29
|
+
adapter.update column_family, id, encode_attributes(attributes)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def batching?
|
|
33
|
+
adapter.batching?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def batch(&block)
|
|
37
|
+
adapter.batch(&block)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def instantiate(id, attributes)
|
|
41
|
+
allocate.tap do |object|
|
|
42
|
+
object.instance_variable_set("@id", id) if id
|
|
43
|
+
object.instance_variable_set("@new_record", false)
|
|
44
|
+
object.instance_variable_set("@destroyed", false)
|
|
45
|
+
object.instance_variable_set("@attributes", typecast_persisted_attributes(object, attributes))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def encode_attributes(attributes)
|
|
50
|
+
encoded = {}
|
|
51
|
+
attributes.each do |column_name, value|
|
|
52
|
+
if value.nil?
|
|
53
|
+
encoded[column_name] = nil
|
|
54
|
+
else
|
|
55
|
+
encoded[column_name] = attribute_definitions[column_name].coder.encode(value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
encoded
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def quote_columns(column_names)
|
|
64
|
+
column_names.map { |name| "'#{name}'" }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def typecast_persisted_attributes(object, attributes)
|
|
68
|
+
attributes.each do |key, value|
|
|
69
|
+
if definition = attribute_definitions[key]
|
|
70
|
+
attributes[key] = definition.instantiate(object, value)
|
|
71
|
+
else
|
|
72
|
+
attributes.delete(key)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
attribute_definitions.each_value do |definition|
|
|
77
|
+
unless definition.default.nil? || attributes.has_key?(definition.name)
|
|
78
|
+
attributes[definition.name] = definition.default
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
attributes
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def new_record?
|
|
87
|
+
@new_record
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def destroyed?
|
|
91
|
+
@destroyed
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def persisted?
|
|
95
|
+
!(new_record? || destroyed?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def save(*)
|
|
99
|
+
new_record? ? create : update
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def destroy
|
|
103
|
+
self.class.remove(id)
|
|
104
|
+
@destroyed = true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def update_attribute(name, value)
|
|
108
|
+
name = name.to_s
|
|
109
|
+
send("#{name}=", value)
|
|
110
|
+
save(validate: false)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def update_attributes(attributes)
|
|
114
|
+
self.attributes = attributes
|
|
115
|
+
save
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def update_attributes!(attributes)
|
|
119
|
+
self.attributes = attributes
|
|
120
|
+
save!
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def becomes(klass)
|
|
124
|
+
became = klass.new
|
|
125
|
+
became.instance_variable_set("@attributes", @attributes)
|
|
126
|
+
became.instance_variable_set("@new_record", new_record?)
|
|
127
|
+
became.instance_variable_set("@destroyed", destroyed?)
|
|
128
|
+
became
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def reload
|
|
132
|
+
clear_belongs_to_cache
|
|
133
|
+
@attributes = self.class.find(id).instance_variable_get('@attributes')
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def create
|
|
140
|
+
@new_record = false
|
|
141
|
+
write :insert_record
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def update
|
|
145
|
+
write :update_record
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def write(method)
|
|
149
|
+
changed_attributes = Hash[changed.map { |attr| [attr, read_attribute(attr)] }]
|
|
150
|
+
self.class.send(method, id, changed_attributes)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class Railtie < Rails::Railtie
|
|
3
|
+
rake_tasks do
|
|
4
|
+
load 'superstore/tasks/ks.rake'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
initializer "superstore.config" do |app|
|
|
8
|
+
ActiveSupport.on_load :superstore do
|
|
9
|
+
pathname = Rails.root.join('config', 'superstore.yml')
|
|
10
|
+
if pathname.exist?
|
|
11
|
+
config = YAML.load(pathname.read)
|
|
12
|
+
|
|
13
|
+
if config = config[Rails.env]
|
|
14
|
+
self.config = config.symbolize_keys!
|
|
15
|
+
else
|
|
16
|
+
raise "Missing environment #{Rails.env} in superstore.yml"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Expose database runtime to controller for logging.
|
|
23
|
+
initializer "superstore.log_runtime" do |app|
|
|
24
|
+
require "superstore/railties/controller_runtime"
|
|
25
|
+
ActiveSupport.on_load(:action_controller) do
|
|
26
|
+
include Superstore::Railties::ControllerRuntime
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'active_support/core_ext/module/attr_internal'
|
|
2
|
+
require 'superstore/log_subscriber'
|
|
3
|
+
|
|
4
|
+
module Superstore
|
|
5
|
+
module Railties # :nodoc:
|
|
6
|
+
module ControllerRuntime #:nodoc:
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods # :nodoc:
|
|
10
|
+
def log_process_action(payload)
|
|
11
|
+
messages, cassandra_object_runtime = super, payload[:cassandra_object_runtime]
|
|
12
|
+
if cassandra_object_runtime.to_i > 0
|
|
13
|
+
messages << ("Superstore: %.1fms" % cassandra_object_runtime.to_f)
|
|
14
|
+
end
|
|
15
|
+
messages
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#private
|
|
20
|
+
|
|
21
|
+
attr_internal :cassandra_object_runtime
|
|
22
|
+
|
|
23
|
+
def process_action(action, *args)
|
|
24
|
+
# We also need to reset the runtime before each action
|
|
25
|
+
# because of queries in middleware or in cases we are streaming
|
|
26
|
+
# and it won't be cleaned up by the method below.
|
|
27
|
+
Superstore::LogSubscriber.reset_runtime
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cleanup_view_runtime
|
|
32
|
+
runtime_before_render = Superstore::LogSubscriber.reset_runtime
|
|
33
|
+
runtime = super
|
|
34
|
+
runtime_after_render = Superstore::LogSubscriber.reset_runtime
|
|
35
|
+
self.cassandra_object_runtime = runtime_before_render + runtime_after_render
|
|
36
|
+
runtime - runtime_after_render
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def append_info_to_payload(payload)
|
|
40
|
+
super
|
|
41
|
+
payload[:cassandra_object_runtime] = (cassandra_object_runtime || 0) + Superstore::LogSubscriber.reset_runtime
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class Schema
|
|
3
|
+
class << self
|
|
4
|
+
def create_table(table_name, options = {})
|
|
5
|
+
adapter.create_table table_name, options
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def drop_table(table_name)
|
|
9
|
+
adapter.drop_table table_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def adapter
|
|
15
|
+
Superstore::Base.adapter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class Scope
|
|
3
|
+
module Batches
|
|
4
|
+
def find_each(options = {})
|
|
5
|
+
find_in_batches(options) do |records|
|
|
6
|
+
records.each { |record| yield record }
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find_in_batches(options = {})
|
|
11
|
+
batch_size = options.delete(:batch_size) || 1000
|
|
12
|
+
start_key = nil
|
|
13
|
+
|
|
14
|
+
scope = limit(batch_size + 1)
|
|
15
|
+
records = scope.to_a
|
|
16
|
+
|
|
17
|
+
while records.any?
|
|
18
|
+
if records.size > batch_size
|
|
19
|
+
next_record = records.pop
|
|
20
|
+
else
|
|
21
|
+
next_record = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
yield records
|
|
25
|
+
break if next_record.nil?
|
|
26
|
+
|
|
27
|
+
records = scope.where("#{adapter.primary_key_column} >= '#{next_record.id}'").to_a
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class Scope
|
|
3
|
+
module FinderMethods
|
|
4
|
+
def find(ids)
|
|
5
|
+
if ids.is_a?(Array)
|
|
6
|
+
find_some(ids)
|
|
7
|
+
else
|
|
8
|
+
find_one(ids)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def find_by_id(ids)
|
|
13
|
+
find(ids)
|
|
14
|
+
rescue Superstore::RecordNotFound
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def all
|
|
19
|
+
to_a
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def first
|
|
23
|
+
limit(1).to_a.first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def find_one(id)
|
|
29
|
+
if id.blank?
|
|
30
|
+
raise Superstore::RecordNotFound, "Couldn't find #{self.name} with key #{id.inspect}"
|
|
31
|
+
elsif record = where_ids(id).first
|
|
32
|
+
record
|
|
33
|
+
else
|
|
34
|
+
raise Superstore::RecordNotFound
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def find_some(ids)
|
|
39
|
+
ids = ids.flatten
|
|
40
|
+
return [] if ids.empty?
|
|
41
|
+
|
|
42
|
+
ids = ids.compact.map(&:to_s).uniq
|
|
43
|
+
|
|
44
|
+
where_ids(ids).to_a
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class Scope
|
|
3
|
+
module QueryMethods
|
|
4
|
+
def select!(*values)
|
|
5
|
+
self.select_values += values.flatten
|
|
6
|
+
self
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def select(*values, &block)
|
|
10
|
+
if block_given?
|
|
11
|
+
to_a.select(&block)
|
|
12
|
+
else
|
|
13
|
+
clone.select! *values
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def where!(*values)
|
|
18
|
+
self.where_values += values.flatten
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def where(*values)
|
|
23
|
+
clone.where! values
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def where_ids!(*ids)
|
|
27
|
+
self.id_values += ids.flatten
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def where_ids(*ids)
|
|
32
|
+
clone.where_ids! ids
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def limit!(value)
|
|
36
|
+
self.limit_value = value
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def limit(value)
|
|
41
|
+
clone.limit! value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_a
|
|
45
|
+
select_records
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'superstore/scope/batches'
|
|
2
|
+
require 'superstore/scope/finder_methods'
|
|
3
|
+
require 'superstore/scope/query_methods'
|
|
4
|
+
|
|
5
|
+
module Superstore
|
|
6
|
+
class Scope
|
|
7
|
+
include Batches, FinderMethods, QueryMethods
|
|
8
|
+
|
|
9
|
+
attr_accessor :klass
|
|
10
|
+
attr_accessor :limit_value, :select_values, :where_values, :id_values
|
|
11
|
+
|
|
12
|
+
def initialize(klass)
|
|
13
|
+
@klass = klass
|
|
14
|
+
|
|
15
|
+
@limit_value = nil
|
|
16
|
+
@select_values = []
|
|
17
|
+
@where_values = []
|
|
18
|
+
@id_values = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def scoping
|
|
24
|
+
previous, klass.current_scope = klass.current_scope, self
|
|
25
|
+
yield
|
|
26
|
+
ensure
|
|
27
|
+
klass.current_scope = previous
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def method_missing(method_name, *args, &block)
|
|
31
|
+
if klass.respond_to?(method_name)
|
|
32
|
+
scoping { klass.send(method_name, *args, &block) }
|
|
33
|
+
elsif Array.method_defined?(method_name)
|
|
34
|
+
to_a.send(method_name, *args, &block)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def select_records
|
|
41
|
+
results = []
|
|
42
|
+
klass.adapter.select(self) do |key, attributes|
|
|
43
|
+
results << klass.instantiate(key, attributes)
|
|
44
|
+
end
|
|
45
|
+
results.compact!
|
|
46
|
+
results
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Scoping
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
singleton_class.class_eval do
|
|
7
|
+
delegate :find, :find_by_id, :first, :all, to: :scope
|
|
8
|
+
delegate :find_each, :find_in_batches, to: :scope
|
|
9
|
+
delegate :select, :where, :where_ids, to: :scope
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
def scope
|
|
15
|
+
self.current_scope ||= Scope.new(self)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def current_scope
|
|
19
|
+
Thread.current["#{self}_current_scope"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def current_scope=(new_scope)
|
|
23
|
+
Thread.current["#{self}_current_scope"] = new_scope
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
ks_namespace = namespace :ks do
|
|
2
|
+
desc 'Create the keyspace in config/superstore.yml for the current environment'
|
|
3
|
+
task create: :environment do
|
|
4
|
+
begin
|
|
5
|
+
Superstore::CassandraSchema.create_keyspace Superstore::Base.config[:keyspace], Superstore::Base.config[:keyspace_options]
|
|
6
|
+
rescue Exception => e
|
|
7
|
+
if e.message =~ /conflicts/
|
|
8
|
+
p "Keyspace #{Superstore::Base.config[:keyspace]} already exists"
|
|
9
|
+
else
|
|
10
|
+
raise e
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
task drop: :environment do
|
|
16
|
+
begin
|
|
17
|
+
Superstore::CassandraSchema.drop_keyspace Superstore::Base.config[:keyspace]
|
|
18
|
+
rescue Exception => e
|
|
19
|
+
if e.message =~ /non existing keyspace/
|
|
20
|
+
p "Keyspace #{Superstore::Base.config[:keyspace]} does not exist"
|
|
21
|
+
else
|
|
22
|
+
raise e
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
task reset: [:drop, :setup]
|
|
28
|
+
|
|
29
|
+
task setup: [:create, :_load]
|
|
30
|
+
|
|
31
|
+
namespace :structure do
|
|
32
|
+
task dump: :environment do
|
|
33
|
+
filename = ENV['SCHEMA'] || "#{Rails.root}/ks/structure.cql"
|
|
34
|
+
File.open(filename, "w:utf-8") do |file|
|
|
35
|
+
Superstore::CassandraSchema.dump(file)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
task load: :environment do
|
|
40
|
+
filename = ENV['SCHEMA'] || "#{Rails.root}/ks/structure.cql"
|
|
41
|
+
File.open(filename) do |file|
|
|
42
|
+
Superstore::CassandraSchema.load(file)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
task :_dump do
|
|
48
|
+
ks_namespace["structure:dump"].invoke
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
task :_load do
|
|
52
|
+
ks_namespace["structure:load"].invoke
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Timestamps
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
attribute :created_at, type: :time
|
|
7
|
+
attribute :updated_at, type: :time
|
|
8
|
+
|
|
9
|
+
before_create do
|
|
10
|
+
self.created_at ||= Time.current
|
|
11
|
+
self.updated_at ||= Time.current
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
before_update if: :changed? do
|
|
15
|
+
self.updated_at = Time.current
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
class Type
|
|
3
|
+
cattr_accessor :attribute_types
|
|
4
|
+
self.attribute_types = {}.with_indifferent_access
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def register(name, coder)
|
|
8
|
+
attribute_types[name] = coder
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get_coder(name)
|
|
12
|
+
attribute_types[name]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Types
|
|
3
|
+
class ArrayType < BaseType
|
|
4
|
+
def encode(array)
|
|
5
|
+
raise ArgumentError.new("#{array.inspect} is not an Array") unless array.kind_of?(Array)
|
|
6
|
+
array.to_a.to_json
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def decode(str)
|
|
10
|
+
return nil if str.blank?
|
|
11
|
+
|
|
12
|
+
ActiveSupport::JSON.decode(str)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def typecast(value)
|
|
16
|
+
value.to_a
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Types
|
|
3
|
+
class BaseType
|
|
4
|
+
attr_accessor :options
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
@options = options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def default
|
|
10
|
+
options[:default].duplicable? ? options[:default].dup : options[:default]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def encode(value)
|
|
14
|
+
value.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def decode(str)
|
|
18
|
+
str
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def typecast(value)
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Superstore
|
|
2
|
+
module Types
|
|
3
|
+
class BooleanType < BaseType
|
|
4
|
+
TRUE_VALS = [true, 'true', '1']
|
|
5
|
+
FALSE_VALS = [false, 'false', '0', '', nil]
|
|
6
|
+
VALID_VALS = TRUE_VALS + FALSE_VALS
|
|
7
|
+
|
|
8
|
+
def encode(bool)
|
|
9
|
+
unless VALID_VALS.include?(bool)
|
|
10
|
+
raise ArgumentError.new("#{bool.inspect} is not a Boolean")
|
|
11
|
+
end
|
|
12
|
+
TRUE_VALS.include?(bool) ? '1' : '0'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def decode(str)
|
|
16
|
+
TRUE_VALS.include?(str)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|