nobrainer 0.13.0 → 0.14.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 +4 -4
- data/lib/no_brainer/config.rb +6 -4
- data/lib/no_brainer/connection.rb +14 -12
- data/lib/no_brainer/criteria/core.rb +1 -6
- data/lib/no_brainer/criteria/delete.rb +2 -2
- data/lib/no_brainer/criteria/order_by.rb +33 -10
- data/lib/no_brainer/criteria/preload.rb +0 -5
- data/lib/no_brainer/criteria/update.rb +2 -2
- data/lib/no_brainer/criteria/where.rb +34 -7
- data/lib/no_brainer/decorated_symbol.rb +2 -1
- data/lib/no_brainer/document/association/belongs_to.rb +19 -11
- data/lib/no_brainer/document/association/core.rb +1 -1
- data/lib/no_brainer/document/association/has_many.rb +10 -4
- data/lib/no_brainer/document/attributes.rb +29 -4
- data/lib/no_brainer/document/callbacks.rb +3 -2
- data/lib/no_brainer/document/core.rb +14 -5
- data/lib/no_brainer/document/criteria.rb +9 -9
- data/lib/no_brainer/document/dirty.rb +11 -5
- data/lib/no_brainer/document/dynamic_attributes.rb +4 -0
- data/lib/no_brainer/document/id.rb +49 -4
- data/lib/no_brainer/document/index.rb +12 -3
- data/lib/no_brainer/document/persistance.rb +5 -9
- data/lib/no_brainer/document/readonly.rb +7 -0
- data/lib/no_brainer/document/serialization.rb +4 -0
- data/lib/no_brainer/document/types.rb +62 -13
- data/lib/no_brainer/document/uniqueness.rb +99 -0
- data/lib/no_brainer/document/validation.rb +8 -24
- data/lib/no_brainer/document.rb +3 -1
- data/lib/no_brainer/error.rb +9 -9
- data/lib/no_brainer/index_manager.rb +4 -2
- data/lib/no_brainer/loader.rb +1 -1
- data/lib/no_brainer/query_runner/logger.rb +31 -19
- data/lib/no_brainer/query_runner/missing_index.rb +15 -3
- data/lib/no_brainer/query_runner/{connection.rb → reconnect.rb} +16 -5
- data/lib/no_brainer/query_runner/table_on_demand.rb +14 -25
- data/lib/no_brainer/query_runner/write_error.rb +5 -8
- data/lib/no_brainer/query_runner.rb +2 -2
- data/lib/no_brainer/rql.rb +25 -0
- data/lib/nobrainer.rb +5 -6
- metadata +70 -69
- data/lib/no_brainer/util.rb +0 -23
|
@@ -5,17 +5,23 @@ require 'digest/md5'
|
|
|
5
5
|
module NoBrainer::Document::Id
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
DEFAULT_PK_NAME = :id
|
|
9
|
+
|
|
10
|
+
def pk_value
|
|
11
|
+
__send__(self.class.pk_name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pk_value=(value)
|
|
15
|
+
__send__("#{self.class.pk_name}=", value)
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def ==(other)
|
|
13
19
|
return super unless self.class == other.class
|
|
14
|
-
!
|
|
20
|
+
!pk_value.nil? && pk_value == other.pk_value
|
|
15
21
|
end
|
|
16
22
|
alias_method :eql?, :==
|
|
17
23
|
|
|
18
|
-
delegate :hash, :to => :
|
|
24
|
+
delegate :hash, :to => :pk_value
|
|
19
25
|
|
|
20
26
|
# The following code is inspired by the mongo-ruby-driver
|
|
21
27
|
|
|
@@ -46,4 +52,43 @@ module NoBrainer::Document::Id
|
|
|
46
52
|
|
|
47
53
|
oid.unpack("C12").map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
|
|
48
54
|
end
|
|
55
|
+
|
|
56
|
+
module ClassMethods
|
|
57
|
+
def define_default_pk
|
|
58
|
+
class_variable_set(:@@pk_name, nil)
|
|
59
|
+
field NoBrainer::Document::Id::DEFAULT_PK_NAME, :primary_key => :default,
|
|
60
|
+
:type => String, :default => ->{ NoBrainer::Document::Id.generate }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def define_pk(attr)
|
|
64
|
+
if fields[pk_name].try(:[], :primary_key) == :default
|
|
65
|
+
remove_field(pk_name, :set_default_pk => false)
|
|
66
|
+
end
|
|
67
|
+
class_variable_set(:@@pk_name, attr)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def pk_name
|
|
71
|
+
class_variable_get(:@@pk_name)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def _field(attr, options={})
|
|
75
|
+
super
|
|
76
|
+
define_pk(attr) if options[:primary_key]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def field(attr, options={})
|
|
80
|
+
if options[:primary_key]
|
|
81
|
+
options = options.merge(:readonly => true) if options[:readonly].nil?
|
|
82
|
+
options = options.merge(:index => true)
|
|
83
|
+
end
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def _remove_field(attr, options={})
|
|
88
|
+
super
|
|
89
|
+
if fields[attr][:primary_key] && options[:set_default_pk] != false
|
|
90
|
+
define_default_pk
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
49
94
|
end
|
|
@@ -5,7 +5,6 @@ module NoBrainer::Document::Index
|
|
|
5
5
|
included do
|
|
6
6
|
cattr_accessor :indexes, :instance_accessor => false
|
|
7
7
|
self.indexes = {}
|
|
8
|
-
self.index :id
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
module ClassMethods
|
|
@@ -57,6 +56,11 @@ module NoBrainer::Document::Index
|
|
|
57
56
|
end
|
|
58
57
|
end
|
|
59
58
|
|
|
59
|
+
def _remove_field(attr, options={})
|
|
60
|
+
super
|
|
61
|
+
remove_index(attr) if fields[attr][:index]
|
|
62
|
+
end
|
|
63
|
+
|
|
60
64
|
def perform_create_index(index_name, options={})
|
|
61
65
|
index_name = index_name.to_sym
|
|
62
66
|
index_args = self.indexes[index_name]
|
|
@@ -68,7 +72,7 @@ module NoBrainer::Document::Index
|
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
NoBrainer.run(self.rql_table.index_create(index_name, index_args[:options], &index_proc))
|
|
71
|
-
|
|
75
|
+
wait_for_index(index_name) unless options[:wait] == false
|
|
72
76
|
STDERR.puts "Created index #{self}.#{index_name}" if options[:verbose]
|
|
73
77
|
end
|
|
74
78
|
|
|
@@ -79,7 +83,7 @@ module NoBrainer::Document::Index
|
|
|
79
83
|
|
|
80
84
|
def perform_update_indexes(options={})
|
|
81
85
|
current_indexes = NoBrainer.run(self.rql_table.index_list).map(&:to_sym)
|
|
82
|
-
wanted_indexes = self.indexes.keys - [
|
|
86
|
+
wanted_indexes = self.indexes.keys - [self.pk_name]
|
|
83
87
|
|
|
84
88
|
(current_indexes - wanted_indexes).each do |index_name|
|
|
85
89
|
perform_drop_index(index_name, options)
|
|
@@ -90,5 +94,10 @@ module NoBrainer::Document::Index
|
|
|
90
94
|
end
|
|
91
95
|
end
|
|
92
96
|
alias_method :update_indexes, :perform_update_indexes
|
|
97
|
+
|
|
98
|
+
def wait_for_index(index_name=nil, options={})
|
|
99
|
+
args = [index_name].compact
|
|
100
|
+
NoBrainer.run(self.rql_table.index_wait(*args))
|
|
101
|
+
end
|
|
93
102
|
end
|
|
94
103
|
end
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
module NoBrainer::Document::Persistance
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
included do
|
|
5
|
-
extend ActiveModel::Callbacks
|
|
6
|
-
define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
|
|
7
|
-
end
|
|
8
|
-
|
|
9
4
|
def _initialize(attrs={}, options={})
|
|
10
5
|
@new_record = !options[:from_db]
|
|
11
6
|
super
|
|
@@ -24,7 +19,8 @@ module NoBrainer::Document::Persistance
|
|
|
24
19
|
end
|
|
25
20
|
|
|
26
21
|
def reload(options={})
|
|
27
|
-
attrs =
|
|
22
|
+
attrs = NoBrainer.run { self.class.selector_for(pk_value) }
|
|
23
|
+
raise NoBrainer::Error::DocumentNotFound, "#{self.class} #{self.class.pk_name}: #{pk_value} not found" unless attrs
|
|
28
24
|
instance_variables.each { |ivar| remove_instance_variable(ivar) } unless options[:keep_ivars]
|
|
29
25
|
initialize(attrs, :pristine => true, :from_db => true)
|
|
30
26
|
self
|
|
@@ -33,13 +29,13 @@ module NoBrainer::Document::Persistance
|
|
|
33
29
|
def _create(options={})
|
|
34
30
|
return false if options[:validate] && !valid?
|
|
35
31
|
keys = self.class.insert_all(@_attributes)
|
|
36
|
-
self.
|
|
32
|
+
self.pk_value ||= keys.first
|
|
37
33
|
@new_record = false
|
|
38
34
|
true
|
|
39
35
|
end
|
|
40
36
|
|
|
41
37
|
def _update(attrs)
|
|
42
|
-
selector.
|
|
38
|
+
NoBrainer.run { selector.update(attrs) }
|
|
43
39
|
end
|
|
44
40
|
|
|
45
41
|
def _update_only_changed_attrs(options={})
|
|
@@ -78,7 +74,7 @@ module NoBrainer::Document::Persistance
|
|
|
78
74
|
|
|
79
75
|
def delete
|
|
80
76
|
unless @destroyed
|
|
81
|
-
selector.
|
|
77
|
+
NoBrainer.run { selector.delete }
|
|
82
78
|
@destroyed = true
|
|
83
79
|
end
|
|
84
80
|
@_attributes.freeze
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module NoBrainer::Document::Types
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
module
|
|
4
|
+
module CastUserToDB
|
|
5
5
|
extend self
|
|
6
6
|
InvalidType = NoBrainer::Error::InvalidType
|
|
7
7
|
|
|
@@ -60,14 +60,23 @@ module NoBrainer::Document::Types
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def lookup(type)
|
|
63
|
-
|
|
63
|
+
public_method(type.to_s)
|
|
64
64
|
rescue NameError
|
|
65
65
|
proc { raise InvalidType }
|
|
66
66
|
end
|
|
67
|
+
end
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
module CastDBToUser
|
|
70
|
+
extend self
|
|
71
|
+
|
|
72
|
+
def Symbol(value)
|
|
73
|
+
value.to_sym rescue value
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def lookup(type)
|
|
77
|
+
public_method(type.to_s)
|
|
78
|
+
rescue NameError
|
|
79
|
+
nil
|
|
71
80
|
end
|
|
72
81
|
end
|
|
73
82
|
|
|
@@ -82,6 +91,11 @@ module NoBrainer::Document::Types
|
|
|
82
91
|
end
|
|
83
92
|
end
|
|
84
93
|
before_validation :add_type_errors
|
|
94
|
+
|
|
95
|
+
# Fast access for db->user cast methods for performance when reading from
|
|
96
|
+
# the database.
|
|
97
|
+
singleton_class.send(:attr_accessor, :cast_db_to_user_fields)
|
|
98
|
+
self.cast_db_to_user_fields = Set.new
|
|
85
99
|
end
|
|
86
100
|
|
|
87
101
|
def add_type_errors
|
|
@@ -91,12 +105,27 @@ module NoBrainer::Document::Types
|
|
|
91
105
|
end
|
|
92
106
|
end
|
|
93
107
|
|
|
108
|
+
def assign_attributes(attrs, options={})
|
|
109
|
+
super
|
|
110
|
+
if options[:from_db]
|
|
111
|
+
self.class.cast_db_to_user_fields.each do |attr|
|
|
112
|
+
field_def = self.class.fields[attr]
|
|
113
|
+
type = field_def[:type]
|
|
114
|
+
value = @_attributes[attr.to_s]
|
|
115
|
+
unless value.nil? || value.is_a?(type)
|
|
116
|
+
@_attributes[attr.to_s] = field_def[:cast_db_to_user].call(value)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
94
122
|
module ClassMethods
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
123
|
+
def cast_user_to_db_for(attr, value)
|
|
124
|
+
field_def = fields[attr.to_sym]
|
|
125
|
+
return value if !field_def
|
|
126
|
+
type = field_def[:type]
|
|
127
|
+
return value if value.nil? || type.nil? || value.is_a?(type)
|
|
128
|
+
field_def[:cast_user_to_db].call(value)
|
|
100
129
|
rescue NoBrainer::Error::InvalidType => error
|
|
101
130
|
error.type = field_def[:type]
|
|
102
131
|
error.value = value
|
|
@@ -104,13 +133,24 @@ module NoBrainer::Document::Types
|
|
|
104
133
|
raise error
|
|
105
134
|
end
|
|
106
135
|
|
|
136
|
+
def inherited(subclass)
|
|
137
|
+
super
|
|
138
|
+
subclass.cast_db_to_user_fields = self.cast_db_to_user_fields.dup
|
|
139
|
+
end
|
|
140
|
+
|
|
107
141
|
def _field(attr, options={})
|
|
108
142
|
super
|
|
109
143
|
|
|
144
|
+
if options[:cast_db_to_user]
|
|
145
|
+
([self] + descendants).each do |klass|
|
|
146
|
+
klass.cast_db_to_user_fields << attr
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
110
150
|
inject_in_layer :types do
|
|
111
151
|
define_method("#{attr}=") do |value|
|
|
112
152
|
begin
|
|
113
|
-
value = self.class.
|
|
153
|
+
value = self.class.cast_user_to_db_for(attr, value)
|
|
114
154
|
@pending_type_errors.try(:delete, attr)
|
|
115
155
|
rescue NoBrainer::Error::InvalidType => error
|
|
116
156
|
@pending_type_errors ||= {}
|
|
@@ -125,10 +165,19 @@ module NoBrainer::Document::Types
|
|
|
125
165
|
|
|
126
166
|
def field(attr, options={})
|
|
127
167
|
if options[:type]
|
|
128
|
-
|
|
129
|
-
|
|
168
|
+
options = options.merge(
|
|
169
|
+
:cast_user_to_db => NoBrainer::Document::Types::CastUserToDB.lookup(options[:type]),
|
|
170
|
+
:cast_db_to_user => NoBrainer::Document::Types::CastDBToUser.lookup(options[:type]))
|
|
130
171
|
end
|
|
131
172
|
super
|
|
132
173
|
end
|
|
174
|
+
|
|
175
|
+
def _remove_field(attr, options={})
|
|
176
|
+
super
|
|
177
|
+
inject_in_layer :types do
|
|
178
|
+
remove_method("#{attr}=")
|
|
179
|
+
remove_method("#{attr}?") if method_defined?("#{attr}?")
|
|
180
|
+
end
|
|
181
|
+
end
|
|
133
182
|
end
|
|
134
183
|
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module NoBrainer::Document::Uniqueness
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
def _create(options={})
|
|
5
|
+
lock_unique_fields
|
|
6
|
+
super
|
|
7
|
+
ensure
|
|
8
|
+
unlock_unique_fields
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def _update_only_changed_attrs(options={})
|
|
12
|
+
lock_unique_fields
|
|
13
|
+
super
|
|
14
|
+
ensure
|
|
15
|
+
unlock_unique_fields
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def _lock_key_from_field(field)
|
|
19
|
+
value = read_attribute(field).to_s
|
|
20
|
+
['nobrainer', self.class.database_name || NoBrainer.connection.parsed_uri[:db],
|
|
21
|
+
self.class.table_name, field, value.empty? ? 'nil' : value].join(':')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def lock_unique_fields
|
|
25
|
+
return unless NoBrainer::Config.distributed_lock_class && !self.class.unique_validators.empty?
|
|
26
|
+
|
|
27
|
+
self.class.unique_validators
|
|
28
|
+
.map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
|
29
|
+
.flatten(1)
|
|
30
|
+
.select { |f, validator| validator.should_validate_uniquess_of?(self, f) }
|
|
31
|
+
.map { |f, options| _lock_key_from_field(f) }
|
|
32
|
+
.sort
|
|
33
|
+
.uniq
|
|
34
|
+
.each do |key|
|
|
35
|
+
lock = NoBrainer::Config.distributed_lock_class.new(key)
|
|
36
|
+
lock.lock
|
|
37
|
+
@locked_unique_fields ||= []
|
|
38
|
+
@locked_unique_fields << lock
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def unlock_unique_fields
|
|
43
|
+
return unless @locked_unique_fields
|
|
44
|
+
@locked_unique_fields.pop.unlock until @locked_unique_fields.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
included do
|
|
48
|
+
singleton_class.send(:attr_accessor, :unique_validators)
|
|
49
|
+
self.unique_validators = []
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module ClassMethods
|
|
53
|
+
def validates_uniqueness_of(*attr_names)
|
|
54
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def inherited(subclass)
|
|
58
|
+
super
|
|
59
|
+
subclass.unique_validators = self.unique_validators.dup
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
|
65
|
+
attr_accessor :scope
|
|
66
|
+
|
|
67
|
+
def initialize(options={})
|
|
68
|
+
super
|
|
69
|
+
klass = options[:class]
|
|
70
|
+
self.scope = [*options[:scope]]
|
|
71
|
+
([klass] + klass.descendants).each do |_klass|
|
|
72
|
+
_klass.unique_validators << self
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def should_validate_uniquess_of?(doc, field)
|
|
77
|
+
(scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def validate_each(doc, attr, value)
|
|
81
|
+
return true unless should_validate_uniquess_of?(doc, attr)
|
|
82
|
+
|
|
83
|
+
criteria = doc.root_class.unscoped.where(attr => value)
|
|
84
|
+
criteria = apply_scopes(criteria, doc)
|
|
85
|
+
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
|
86
|
+
is_unique = criteria.count == 0
|
|
87
|
+
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
|
|
88
|
+
is_unique
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def apply_scopes(criteria, doc)
|
|
92
|
+
criteria.where(scope.map { |k| {k => doc.read_attribute(k)} })
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def exclude_doc(criteria, doc)
|
|
96
|
+
criteria.where(doc.class.pk_name.ne => doc.pk_value)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -3,6 +3,12 @@ module NoBrainer::Document::Validation
|
|
|
3
3
|
include ActiveModel::Validations
|
|
4
4
|
include ActiveModel::Validations::Callbacks
|
|
5
5
|
|
|
6
|
+
included do
|
|
7
|
+
# We don't want before_validation returning false to halt the chain.
|
|
8
|
+
define_callbacks :validation, :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name],
|
|
9
|
+
:terminator => proc { false }
|
|
10
|
+
end
|
|
11
|
+
|
|
6
12
|
def valid?(context=nil)
|
|
7
13
|
super(context || (new_record? ? :create : :update))
|
|
8
14
|
end
|
|
@@ -10,31 +16,9 @@ module NoBrainer::Document::Validation
|
|
|
10
16
|
module ClassMethods
|
|
11
17
|
def _field(attr, options={})
|
|
12
18
|
super
|
|
13
|
-
validates(attr, { :presence =>
|
|
19
|
+
validates(attr, { :presence => options[:required] }) if options.has_key?(:required)
|
|
20
|
+
validates(attr, { :uniqueness => options[:unique] }) if options.has_key?(:unique)
|
|
14
21
|
validates(attr, options[:validates]) if options[:validates]
|
|
15
22
|
end
|
|
16
|
-
|
|
17
|
-
def validates_uniqueness_of(*attr_names)
|
|
18
|
-
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
class UniquenessValidator < ActiveModel::EachValidator
|
|
23
|
-
def validate_each(doc, attr, value)
|
|
24
|
-
criteria = doc.root_class.unscoped.where(attr => value)
|
|
25
|
-
criteria = apply_scopes(criteria, doc)
|
|
26
|
-
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
|
27
|
-
is_unique = criteria.count == 0
|
|
28
|
-
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
|
|
29
|
-
is_unique
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def apply_scopes(criteria, doc)
|
|
33
|
-
criteria.where([*options[:scope]].map { |k| {k => doc.read_attribute(k)} })
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def exclude_doc(criteria, doc)
|
|
37
|
-
criteria.where(:id.ne => doc.id)
|
|
38
|
-
end
|
|
39
23
|
end
|
|
40
24
|
end
|
data/lib/no_brainer/document.rb
CHANGED
|
@@ -5,10 +5,12 @@ module NoBrainer::Document
|
|
|
5
5
|
extend NoBrainer::Autoload
|
|
6
6
|
|
|
7
7
|
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly, :Validation, :Types,
|
|
8
|
-
:Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
|
8
|
+
:Persistance, :Uniqueness, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
|
9
9
|
:Criteria, :Polymorphic, :Index
|
|
10
10
|
|
|
11
11
|
autoload :DynamicAttributes, :Timestamps
|
|
12
12
|
|
|
13
|
+
included { define_default_pk }
|
|
14
|
+
|
|
13
15
|
singleton_class.delegate :all, :to => Core
|
|
14
16
|
end
|
data/lib/no_brainer/error.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
module NoBrainer::Error
|
|
2
|
-
class Connection
|
|
3
|
-
class DocumentNotFound
|
|
4
|
-
class
|
|
5
|
-
class ChildrenExist
|
|
6
|
-
class CannotUseIndex
|
|
7
|
-
class MissingIndex
|
|
8
|
-
class InvalidType
|
|
9
|
-
class
|
|
10
|
-
class ReadonlyField
|
|
2
|
+
class Connection < RuntimeError; end
|
|
3
|
+
class DocumentNotFound < RuntimeError; end
|
|
4
|
+
class DocumentNotPersisted < RuntimeError; end
|
|
5
|
+
class ChildrenExist < RuntimeError; end
|
|
6
|
+
class CannotUseIndex < RuntimeError; end
|
|
7
|
+
class MissingIndex < RuntimeError; end
|
|
8
|
+
class InvalidType < RuntimeError; end
|
|
9
|
+
class AssociationNotPersisted < RuntimeError; end
|
|
10
|
+
class ReadonlyField < RuntimeError; end
|
|
11
11
|
|
|
12
12
|
class DocumentInvalid < RuntimeError
|
|
13
13
|
attr_accessor :instance
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module NoBrainer::IndexManager
|
|
2
2
|
def self.update_indexes(options={})
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
NoBrainer::Document.all.each { |model| model.perform_update_indexes(options.merge(:wait => false)) }
|
|
4
|
+
unless options[:wait] == false
|
|
5
|
+
NoBrainer::Document.all.each { |model| model.wait_for_index(nil) }
|
|
6
|
+
end
|
|
5
7
|
end
|
|
6
8
|
end
|
data/lib/no_brainer/loader.rb
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
|
2
2
|
def call(env)
|
|
3
3
|
start_time = Time.now
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
@runner.call(env).tap { log_query(env, start_time) }
|
|
5
|
+
rescue Exception => e
|
|
6
|
+
log_query(env, start_time, e) rescue nil
|
|
7
|
+
raise e
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
msg = "r.the_rethinkdb_gem_is_flipping_out_with_Erroneous_Portion_Constructed"
|
|
12
|
-
end
|
|
12
|
+
def log_query(env, start_time, exception=nil)
|
|
13
|
+
return unless NoBrainer.logger.debug?
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
msg = "[#{(duration * 1000.0).round(1)}ms] #{msg}"
|
|
15
|
+
duration = Time.now - start_time
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
when :read then msg = "\e[1;32m#{msg}\e[0m" # green
|
|
21
|
-
when :management then msg = "\e[1;33m#{msg}\e[0m" # yellow
|
|
22
|
-
end
|
|
23
|
-
end
|
|
17
|
+
msg_duration = (duration * 1000.0).round(1).to_s
|
|
18
|
+
msg_duration = " " * [0, 5 - msg_duration.size].max + msg_duration
|
|
19
|
+
msg_duration = "[#{msg_duration}ms] "
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
msg_db = "[#{env[:db_name]}] " if env[:db_name]
|
|
22
|
+
msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
|
|
23
|
+
msg_exception = " #{exception.class} #{exception.message.split("\n").first}" if exception
|
|
24
|
+
msg_last = nil
|
|
25
|
+
|
|
26
|
+
if NoBrainer::Config.colorize_logger
|
|
27
|
+
query_color = case NoBrainer::RQL.type_of(env[:query])
|
|
28
|
+
when :write then "\e[1;31m" # red
|
|
29
|
+
when :read then "\e[1;32m" # green
|
|
30
|
+
when :management then "\e[1;33m" # yellow
|
|
31
|
+
end
|
|
32
|
+
msg_duration = [query_color, msg_duration].join
|
|
33
|
+
msg_db = ["\e[0;34m", msg_db, query_color].join if msg_db
|
|
34
|
+
msg_exception = ["\e[0;31m", msg_exception].join if msg_exception
|
|
35
|
+
msg_last = "\e[0m"
|
|
26
36
|
end
|
|
27
|
-
|
|
37
|
+
|
|
38
|
+
msg = [msg_duration, msg_db, msg_query, msg_exception, msg_last].join
|
|
39
|
+
NoBrainer.logger.debug(msg)
|
|
28
40
|
end
|
|
29
41
|
end
|
|
@@ -2,9 +2,21 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
|
|
|
2
2
|
def call(env)
|
|
3
3
|
@runner.call(env)
|
|
4
4
|
rescue RethinkDB::RqlRuntimeError => e
|
|
5
|
-
if e.message =~ /^Index `(.+)` was not found
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
if e.message =~ /^Index `(.+)` was not found on table `(.+)\.(.+)`\.$/
|
|
6
|
+
index_name = $1
|
|
7
|
+
database_name = $2
|
|
8
|
+
table_name = $3
|
|
9
|
+
|
|
10
|
+
klass = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
|
|
11
|
+
if klass && klass.pk_name.to_s == index_name
|
|
12
|
+
err_msg = "Please run update the primary key `#{index_name}` in the table `#{database_name}.#{table_name}`."
|
|
13
|
+
else
|
|
14
|
+
err_msg = "Please run \"rake db:update_indexes\" to create the index `#{index_name}`"
|
|
15
|
+
err_msg += " in the table `#{database_name}.#{table_name}`."
|
|
16
|
+
err_msg += "\n--> Read http://nobrainer.io/docs/indexes for more information."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
raise NoBrainer::Error::MissingIndex.new(err_msg)
|
|
8
20
|
end
|
|
9
21
|
raise
|
|
10
22
|
end
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
class NoBrainer::QueryRunner::
|
|
1
|
+
class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
|
|
2
2
|
def call(env)
|
|
3
3
|
@runner.call(env)
|
|
4
4
|
rescue StandardError => e
|
|
5
5
|
# TODO test that thing
|
|
6
6
|
if is_connection_error_exception?(e)
|
|
7
|
-
retry if reconnect
|
|
7
|
+
retry if reconnect(e)
|
|
8
8
|
end
|
|
9
9
|
raise
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
private
|
|
13
13
|
|
|
14
|
-
def reconnect
|
|
14
|
+
def reconnect(e)
|
|
15
15
|
# FIXME thread safety? perhaps we need to use a connection pool
|
|
16
16
|
# XXX Possibly dangerous, as we could reexecute a non idempotent operation
|
|
17
17
|
# Check the semantics of the db
|
|
18
18
|
NoBrainer::Config.max_reconnection_tries.times do
|
|
19
19
|
begin
|
|
20
|
-
|
|
20
|
+
warn_reconnect(e)
|
|
21
21
|
sleep 1
|
|
22
22
|
NoBrainer.connection.reconnect(:noreply_wait => false)
|
|
23
23
|
return true
|
|
@@ -35,10 +35,21 @@ class NoBrainer::QueryRunner::Connection < NoBrainer::QueryRunner::Middleware
|
|
|
35
35
|
Errno::ECONNRESET, Errno::ETIMEDOUT, IOError
|
|
36
36
|
true
|
|
37
37
|
when RethinkDB::RqlRuntimeError
|
|
38
|
-
e.message =~ /
|
|
38
|
+
e.message =~ /No master available/ ||
|
|
39
|
+
e.message =~ /Master .* not available/ ||
|
|
39
40
|
e.message =~ /Error: Connection Closed/
|
|
40
41
|
else
|
|
41
42
|
false
|
|
42
43
|
end
|
|
43
44
|
end
|
|
45
|
+
|
|
46
|
+
def warn_reconnect(e)
|
|
47
|
+
if e.is_a?(RethinkDB::RqlRuntimeError)
|
|
48
|
+
e_msg = e.message.split("\n").first
|
|
49
|
+
msg = "Server #{NoBrainer::Config.rethinkdb_url} not ready - #{e_msg}, retrying..."
|
|
50
|
+
else
|
|
51
|
+
msg = "Connection issue with #{NoBrainer::Config.rethinkdb_url} - #{e}, retrying..."
|
|
52
|
+
end
|
|
53
|
+
NoBrainer.logger.try(:warn, msg)
|
|
54
|
+
end
|
|
44
55
|
end
|