nobrainer 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/no_brainer/autoload.rb +1 -1
- data/lib/no_brainer/criteria.rb +1 -2
- data/lib/no_brainer/criteria/core.rb +1 -1
- data/lib/no_brainer/criteria/delete.rb +1 -1
- data/lib/no_brainer/criteria/update.rb +4 -6
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/document/association/belongs_to.rb +2 -1
- data/lib/no_brainer/document/attributes.rb +24 -24
- data/lib/no_brainer/document/callbacks.rb +5 -13
- data/lib/no_brainer/document/criteria.rb +0 -1
- data/lib/no_brainer/document/dirty.rb +36 -33
- data/lib/no_brainer/document/id.rb +1 -1
- data/lib/no_brainer/document/index.rb +7 -9
- data/lib/no_brainer/document/injection_layer.rb +2 -3
- data/lib/no_brainer/document/persistance.rb +13 -27
- data/lib/no_brainer/document/readonly.rb +19 -0
- data/lib/no_brainer/document/timestamps.rb +6 -7
- data/lib/no_brainer/document/types.rb +18 -16
- data/lib/no_brainer/document/validation.rb +3 -2
- data/lib/no_brainer/error.rb +12 -1
- data/lib/no_brainer/query_runner/connection.rb +36 -9
- data/lib/no_brainer/query_runner/run_options.rb +4 -2
- data/lib/nobrainer.rb +2 -6
- metadata +37 -30
- data/lib/no_brainer/criteria/inc.rb +0 -14
- data/lib/no_brainer/document_with_timestamps.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd6046706e63442640e96473b737f37ad5a4fea7
|
4
|
+
data.tar.gz: e1634aaf6acfb3c3e5e4287c57151d54cce30ee1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac735666befc4a025536e5beb72a8c4d4d79259875a8ad956ae16e4399c17cf5c2dcc7eecf6bcfa4351d56aec14b1a83a7d68cb751723d5beade516011990915
|
7
|
+
data.tar.gz: b6f69cd4dfbd953d9acc06a1c06aa9a27851baa51b1e1a2d0fce0ff320a32131c49724e5ea6aaa8af3a2aaf74bdb661379eb0a4e97abdb98e8084e5fae0516c5
|
data/lib/no_brainer/autoload.rb
CHANGED
data/lib/no_brainer/criteria.rb
CHANGED
@@ -3,6 +3,5 @@ require 'rethinkdb'
|
|
3
3
|
class NoBrainer::Criteria
|
4
4
|
extend NoBrainer::Autoload
|
5
5
|
autoload_and_include :Core, :Scope, :Raw, :AfterFind, :Where, :OrderBy, :Limit,
|
6
|
-
:Count, :Delete, :Enumerable, :First, :Preload, :
|
7
|
-
:Update, :Cache
|
6
|
+
:Count, :Delete, :Enumerable, :First, :Preload, :Update, :Cache
|
8
7
|
end
|
@@ -1,13 +1,11 @@
|
|
1
1
|
module NoBrainer::Criteria::Update
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
def update_all(
|
5
|
-
block
|
6
|
-
run(to_rql.update(&block))['replaced']
|
4
|
+
def update_all(*args, &block)
|
5
|
+
run(to_rql.update(*args, &block))
|
7
6
|
end
|
8
7
|
|
9
|
-
def replace_all(
|
10
|
-
block
|
11
|
-
run(to_rql.replace(&block))['replaced']
|
8
|
+
def replace_all(*args, &block)
|
9
|
+
run(to_rql.replace(*args, &block))
|
12
10
|
end
|
13
11
|
end
|
data/lib/no_brainer/document.rb
CHANGED
@@ -4,7 +4,7 @@ module NoBrainer::Document
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
extend NoBrainer::Autoload
|
6
6
|
|
7
|
-
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
|
7
|
+
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly, :Validation, :Types,
|
8
8
|
:Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
9
9
|
:Criteria, :Polymorphic, :Index
|
10
10
|
|
@@ -2,7 +2,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
3
3
|
|
4
4
|
class Metadata
|
5
|
-
VALID_OPTIONS = [:foreign_key, :class_name, :index, :validates]
|
5
|
+
VALID_OPTIONS = [:foreign_key, :class_name, :index, :validates, :required]
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
7
7
|
extend NoBrainer::Document::Association::EagerLoader::Generic
|
8
8
|
|
@@ -25,6 +25,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
25
25
|
# are likely to be related to each other. So we don't know the type
|
26
26
|
# of the primary key of the target.
|
27
27
|
owner_klass.field(foreign_key, :index => options[:index])
|
28
|
+
owner_klass.validates(target_name, { :presence => true }) if options[:required]
|
28
29
|
owner_klass.validates(target_name, options[:validates]) if options[:validates]
|
29
30
|
|
30
31
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module NoBrainer::Document::Attributes
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :type_cast_method, :validates]
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :type_cast_method, :validates, :required, :readonly]
|
3
3
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
@@ -39,13 +39,13 @@ module NoBrainer::Document::Attributes
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
def _assign_attributes(attrs, options={})
|
43
|
-
attrs.each { |k,v| self.write_attribute(k,v) }
|
44
|
-
end
|
45
|
-
|
46
42
|
def assign_attributes(attrs, options={})
|
47
43
|
@_attributes.clear if options[:pristine]
|
48
|
-
|
44
|
+
if options[:from_db]
|
45
|
+
@_attributes.merge!(attrs)
|
46
|
+
else
|
47
|
+
attrs.each { |k,v| self.write_attribute(k,v) }
|
48
|
+
end
|
49
49
|
assign_defaults if options[:pristine]
|
50
50
|
self
|
51
51
|
end
|
@@ -71,33 +71,33 @@ module NoBrainer::Document::Attributes
|
|
71
71
|
subclass.fields = self.fields.dup
|
72
72
|
end
|
73
73
|
|
74
|
-
def
|
75
|
-
|
74
|
+
def _field(attr, options={})
|
75
|
+
# Using a layer so the user can use super when overriding these methods
|
76
|
+
attr = attr.to_s
|
77
|
+
inject_in_layer :attributes do
|
78
|
+
define_method("#{attr}=") { |value| @_attributes[attr] = value }
|
79
|
+
define_method("#{attr}") { @_attributes[attr] }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def field(attr, options={})
|
84
|
+
attr = attr.to_sym
|
76
85
|
|
77
86
|
options.assert_valid_keys(*VALID_FIELD_OPTIONS)
|
78
|
-
if
|
79
|
-
raise "Cannot use a reserved field
|
87
|
+
if attr.in?(RESERVED_FIELD_NAMES)
|
88
|
+
raise "Cannot use a reserved field attr: #{attr}"
|
80
89
|
end
|
81
90
|
|
82
91
|
([self] + descendants).each do |klass|
|
83
|
-
klass.fields[
|
84
|
-
klass.fields[
|
92
|
+
klass.fields[attr] ||= {}
|
93
|
+
klass.fields[attr].deep_merge!(options)
|
85
94
|
end
|
86
95
|
|
87
|
-
|
88
|
-
inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
|
89
|
-
def #{name}=(value)
|
90
|
-
@_attributes['#{name}'] = value
|
91
|
-
end
|
92
|
-
|
93
|
-
def #{name}
|
94
|
-
@_attributes['#{name}']
|
95
|
-
end
|
96
|
-
RUBY
|
96
|
+
_field(attr, self.fields[attr])
|
97
97
|
end
|
98
98
|
|
99
|
-
def has_field?(
|
100
|
-
!!fields[
|
99
|
+
def has_field?(attr)
|
100
|
+
!!fields[attr.to_sym]
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
@@ -7,31 +7,23 @@ module NoBrainer::Document::Callbacks
|
|
7
7
|
define_model_callbacks :find, :only => [:after], :terminator => 'false'
|
8
8
|
end
|
9
9
|
|
10
|
-
def initialize(*args)
|
10
|
+
def initialize(*args, &block)
|
11
11
|
run_callbacks(:initialize) { _initialize(*args); true }
|
12
12
|
end
|
13
13
|
|
14
|
-
def _create(*args)
|
14
|
+
def _create(*args, &block)
|
15
15
|
run_callbacks(:create) { super }
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
18
|
+
def _update_only_changed_attrs(*args, &block)
|
19
19
|
run_callbacks(:update) { super }
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
run_callbacks(:update) { super }
|
24
|
-
end
|
25
|
-
|
26
|
-
def _update_changed(*args)
|
27
|
-
run_callbacks(:update) { super }
|
28
|
-
end
|
29
|
-
|
30
|
-
def save(*args)
|
22
|
+
def save(*args, &block)
|
31
23
|
run_callbacks(:save) { super }
|
32
24
|
end
|
33
25
|
|
34
|
-
def destroy(*args)
|
26
|
+
def destroy(*args, &block)
|
35
27
|
run_callbacks(:destroy) { super }
|
36
28
|
end
|
37
29
|
end
|
@@ -1,25 +1,27 @@
|
|
1
1
|
module NoBrainer::Document::Dirty
|
2
2
|
extend ActiveSupport::Concern
|
3
|
+
# We need to save the changes as seen through read_attribute because
|
4
|
+
# the user sees attributes through the read_attribute getters.
|
5
|
+
# But we want to detect changes based on @_attributes to track
|
6
|
+
# things like undefined -> nil. Going through the getters will
|
7
|
+
# not give us that.
|
3
8
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# Also it doesn't work properly with array and hashes
|
9
|
-
|
10
|
-
included { after_save { clear_dirtiness } }
|
9
|
+
def assign_attributes(attrs, options={})
|
10
|
+
clear_dirtiness if options[:pristine]
|
11
|
+
super
|
12
|
+
end
|
11
13
|
|
12
|
-
def
|
13
|
-
|
14
|
+
def _create(*args)
|
15
|
+
super.tap { clear_dirtiness }
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
|
18
|
+
def _update(*args)
|
19
|
+
super.tap { clear_dirtiness }
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
22
|
+
def clear_dirtiness
|
23
|
+
@_old_attributes = {}.with_indifferent_access
|
24
|
+
@_old_attributes_keys = @_attributes.keys # to track undefined -> nil changes
|
23
25
|
end
|
24
26
|
|
25
27
|
def changed?
|
@@ -32,51 +34,52 @@ module NoBrainer::Document::Dirty
|
|
32
34
|
|
33
35
|
def changes
|
34
36
|
result = {}.with_indifferent_access
|
35
|
-
|
37
|
+
@_old_attributes.each do |attr, old_value|
|
36
38
|
current_value = read_attribute(attr)
|
37
|
-
|
39
|
+
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
40
|
+
result[attr] = [old_value, current_value]
|
41
|
+
end
|
38
42
|
end
|
39
43
|
result
|
40
44
|
end
|
41
45
|
|
42
46
|
def attribute_may_change(attr, current_value)
|
43
|
-
unless
|
44
|
-
|
47
|
+
unless @_old_attributes.has_key?(attr)
|
48
|
+
@_old_attributes[attr] = current_value.deep_dup
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
52
|
module ClassMethods
|
49
|
-
def
|
53
|
+
def _field(attr, options={})
|
50
54
|
super
|
55
|
+
attr = attr.to_s
|
51
56
|
|
52
57
|
inject_in_layer :dirty_tracking do
|
53
|
-
define_method("#{
|
54
|
-
if
|
55
|
-
result = [
|
56
|
-
result
|
57
|
-
result
|
58
|
+
define_method("#{attr}_change") do
|
59
|
+
if @_old_attributes.has_key?(attr)
|
60
|
+
result = [@_old_attributes[attr], read_attribute(attr)]
|
61
|
+
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
61
|
-
define_method("#{
|
62
|
-
!!__send__("#{
|
65
|
+
define_method("#{attr}_changed?") do
|
66
|
+
!!__send__("#{attr}_change")
|
63
67
|
end
|
64
68
|
|
65
|
-
define_method("#{
|
66
|
-
|
67
|
-
old_attributes_values[name] : read_attribute(name)
|
69
|
+
define_method("#{attr}_was") do
|
70
|
+
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute(attr)
|
68
71
|
end
|
69
72
|
|
70
|
-
define_method("#{
|
73
|
+
define_method("#{attr}") do
|
71
74
|
super().tap do |value|
|
72
75
|
# This take care of string/arrays/hashes that could change without going
|
73
76
|
# through the setter.
|
74
|
-
attribute_may_change(
|
77
|
+
attribute_may_change(attr, value) if value.respond_to?(:size)
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
78
|
-
define_method("#{
|
79
|
-
attribute_may_change(
|
81
|
+
define_method("#{attr}=") do |value|
|
82
|
+
attribute_may_change(attr, read_attribute(attr))
|
80
83
|
super(value)
|
81
84
|
end
|
82
85
|
end
|
@@ -6,7 +6,7 @@ module NoBrainer::Document::Id
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
self.field :id, :type => String, :default => ->{ NoBrainer::Document::Id.generate }
|
9
|
+
self.field :id, :type => String, :default => ->{ NoBrainer::Document::Id.generate }, :readonly => true
|
10
10
|
end
|
11
11
|
|
12
12
|
def ==(other)
|
@@ -41,21 +41,19 @@ module NoBrainer::Document::Index
|
|
41
41
|
!!indexes[name.to_sym]
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
if has_index?(name) && indexes[name][:kind] != :single
|
48
|
-
raise "Cannot reuse index name #{name}"
|
44
|
+
def _field(attr, options={})
|
45
|
+
if has_index?(attr) && indexes[attr][:kind] != :single
|
46
|
+
raise "Cannot reuse index attr #{attr}"
|
49
47
|
end
|
50
48
|
|
51
49
|
super
|
52
50
|
|
53
51
|
case options[:index]
|
54
52
|
when nil then
|
55
|
-
when Hash then index(
|
56
|
-
when Symbol then index(
|
57
|
-
when true then index(
|
58
|
-
when false then remove_index(
|
53
|
+
when Hash then index(attr, options[:index])
|
54
|
+
when Symbol then index(attr, options[:index] => true)
|
55
|
+
when true then index(attr)
|
56
|
+
when false then remove_index(attr)
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
@@ -2,10 +2,9 @@ module NoBrainer::Document::InjectionLayer
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
module ClassMethods
|
5
|
-
def inject_in_layer(name,
|
5
|
+
def inject_in_layer(name, &block)
|
6
6
|
mod = class_eval "module NoBrainerLayer; module #{name.to_s.camelize}; self; end; end"
|
7
|
-
mod.
|
8
|
-
mod.module_exec(&block) if block
|
7
|
+
mod.module_exec(&block)
|
9
8
|
include mod
|
10
9
|
end
|
11
10
|
end
|
@@ -7,8 +7,8 @@ module NoBrainer::Document::Persistance
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def _initialize(attrs={}, options={})
|
10
|
-
super
|
11
10
|
@new_record = !options[:from_db]
|
11
|
+
super
|
12
12
|
end
|
13
13
|
|
14
14
|
def new_record?
|
@@ -38,43 +38,29 @@ module NoBrainer::Document::Persistance
|
|
38
38
|
true
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
|
43
|
-
selector.update_all(&block)
|
44
|
-
true
|
45
|
-
end
|
46
|
-
|
47
|
-
def replace(options={}, &block)
|
48
|
-
return false if options[:validate] && !valid?
|
49
|
-
selector.replace_all(&block)
|
50
|
-
true
|
51
|
-
end
|
52
|
-
|
53
|
-
def _update_changed_attributes(changed_attrs)
|
54
|
-
# If we have a hash to save, we replace the entire document
|
55
|
-
# instead of doing some smart update. This is because RethinkDB
|
56
|
-
# will merge the existing hash with the given hash. If the
|
57
|
-
# user has deleted some keys, we won't remove them.
|
58
|
-
if changed_attrs.values.any? { |v| v.is_a?(Hash) }
|
59
|
-
selector.replace_all { @_attributes }
|
60
|
-
else
|
61
|
-
selector.update_all { changed_attrs }
|
62
|
-
end
|
41
|
+
def _update(attrs)
|
42
|
+
selector.update_all(attrs)
|
63
43
|
end
|
64
44
|
|
65
|
-
def
|
45
|
+
def _update_only_changed_attrs(options={})
|
66
46
|
return false if options[:validate] && !valid?
|
67
47
|
|
68
48
|
# We won't be using the `changes` values, because they went through
|
69
49
|
# read_attribute(), and we want the raw values.
|
70
|
-
|
71
|
-
|
50
|
+
attrs = Hash[self.changed.map do |k|
|
51
|
+
attr = @_attributes[k]
|
52
|
+
# If we have a hash to save, we need to specify r.literal(),
|
53
|
+
# otherwise, the hash would just get merged with the existing one.
|
54
|
+
attr = RethinkDB::RQL.new.literal(attr) if attr.is_a?(Hash)
|
55
|
+
[k, attr]
|
56
|
+
end]
|
57
|
+
_update(attrs) if attrs.present?
|
72
58
|
true
|
73
59
|
end
|
74
60
|
|
75
61
|
def save(options={})
|
76
62
|
options = options.reverse_merge(:validate => true)
|
77
|
-
new_record? ? _create(options) :
|
63
|
+
new_record? ? _create(options) : _update_only_changed_attrs(options)
|
78
64
|
end
|
79
65
|
|
80
66
|
def save!(*args)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module NoBrainer::Document::Readonly
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def _field(attr, options={})
|
6
|
+
super
|
7
|
+
inject_in_layer :readonly do
|
8
|
+
if options[:readonly]
|
9
|
+
define_method("#{attr}=") do |value|
|
10
|
+
raise NoBrainer::Error::ReadonlyField.new("#{attr} is readonly") unless new_record?
|
11
|
+
super(value)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
remove_method("#{attr}=") if method_defined?("#{attr}=")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -4,16 +4,15 @@ module NoBrainer::Document::Timestamps
|
|
4
4
|
included do
|
5
5
|
field :created_at, :type => Time
|
6
6
|
field :updated_at, :type => Time
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# call if nothing has changed.
|
9
|
+
def _create(options={})
|
10
|
+
self.created_at = self.updated_at = Time.now
|
11
|
+
super
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def _update(attrs)
|
15
15
|
self.updated_at = Time.now
|
16
|
-
|
17
|
-
super
|
16
|
+
super(attrs.merge('updated_at' => @_attributes['updated_at']))
|
18
17
|
end
|
19
18
|
end
|
@@ -92,41 +92,43 @@ module NoBrainer::Document::Types
|
|
92
92
|
end
|
93
93
|
|
94
94
|
module ClassMethods
|
95
|
-
def cast_value_for(
|
96
|
-
|
97
|
-
field_def = fields[
|
95
|
+
def cast_value_for(attr, value)
|
96
|
+
attr = attr.to_sym
|
97
|
+
field_def = fields[attr]
|
98
98
|
return value unless field_def && field_def[:type]
|
99
99
|
NoBrainer::Document::Types::CastingRules.cast(value, field_def[:type], field_def[:type_cast_method])
|
100
100
|
rescue NoBrainer::Error::InvalidType => error
|
101
101
|
error.type = field_def[:type]
|
102
102
|
error.value = value
|
103
|
-
error.attr_name =
|
103
|
+
error.attr_name = attr
|
104
104
|
raise error
|
105
105
|
end
|
106
106
|
|
107
|
-
def
|
108
|
-
return super unless options.has_key?(:type)
|
109
|
-
|
110
|
-
name = name.to_sym
|
111
|
-
type = options[:type]
|
112
|
-
options[:type_cast_method] = NoBrainer::Document::Types::CastingRules.lookup(type)
|
113
|
-
|
107
|
+
def _field(attr, options={})
|
114
108
|
super
|
115
109
|
|
116
110
|
inject_in_layer :types do
|
117
|
-
define_method("#{
|
111
|
+
define_method("#{attr}=") do |value|
|
118
112
|
begin
|
119
|
-
value = self.class.cast_value_for(
|
120
|
-
@pending_type_errors.try(:delete,
|
113
|
+
value = self.class.cast_value_for(attr, value)
|
114
|
+
@pending_type_errors.try(:delete, attr)
|
121
115
|
rescue NoBrainer::Error::InvalidType => error
|
122
116
|
@pending_type_errors ||= {}
|
123
|
-
@pending_type_errors[
|
117
|
+
@pending_type_errors[attr] = error
|
124
118
|
end
|
125
119
|
super(value)
|
126
120
|
end
|
127
121
|
|
128
|
-
define_method("#{
|
122
|
+
define_method("#{attr}?") { !!read_attribute(attr) } if options[:type] == Boolean
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def field(attr, options={})
|
127
|
+
if options[:type]
|
128
|
+
type_cast_method = NoBrainer::Document::Types::CastingRules.lookup(options[:type])
|
129
|
+
options = options.merge(:type_cast_method => type_cast_method)
|
129
130
|
end
|
131
|
+
super
|
130
132
|
end
|
131
133
|
end
|
132
134
|
end
|
@@ -8,9 +8,10 @@ module NoBrainer::Document::Validation
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
-
def
|
11
|
+
def _field(attr, options={})
|
12
12
|
super
|
13
|
-
validates(
|
13
|
+
validates(attr, { :presence => true }) if options[:required]
|
14
|
+
validates(attr, options[:validates]) if options[:validates]
|
14
15
|
end
|
15
16
|
|
16
17
|
def validates_uniqueness_of(*attr_names)
|
data/lib/no_brainer/error.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
module NoBrainer::Error
|
2
2
|
class Connection < RuntimeError; end
|
3
3
|
class DocumentNotFound < RuntimeError; end
|
4
|
-
class DocumentInvalid < RuntimeError; end
|
5
4
|
class DocumentNotSaved < RuntimeError; end
|
6
5
|
class ChildrenExist < RuntimeError; end
|
7
6
|
class CannotUseIndex < RuntimeError; end
|
8
7
|
class MissingIndex < RuntimeError; end
|
9
8
|
class InvalidType < RuntimeError; end
|
10
9
|
class AssociationNotSaved < RuntimeError; end
|
10
|
+
class ReadonlyField < RuntimeError; end
|
11
|
+
|
12
|
+
class DocumentInvalid < RuntimeError
|
13
|
+
attr_accessor :instance
|
14
|
+
def initialize(instance)
|
15
|
+
@instance = instance
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
"#{instance} is invalid: #{instance.errors.full_messages.join(", ")}"
|
20
|
+
end
|
21
|
+
end
|
11
22
|
|
12
23
|
class InvalidType < RuntimeError
|
13
24
|
attr_accessor :attr_name, :value, :type
|
@@ -1,17 +1,44 @@
|
|
1
1
|
class NoBrainer::QueryRunner::Connection < NoBrainer::QueryRunner::Middleware
|
2
2
|
def call(env)
|
3
3
|
@runner.call(env)
|
4
|
-
rescue
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
rescue StandardError => e
|
5
|
+
# TODO test that thing
|
6
|
+
if is_connection_error_exception?(e)
|
7
|
+
retry if reconnect
|
8
|
+
end
|
9
|
+
raise
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
8
13
|
|
9
|
-
|
10
|
-
|
14
|
+
def reconnect
|
15
|
+
# FIXME thread safety? perhaps we need to use a connection pool
|
16
|
+
# XXX Possibly dangerous, as we could reexecute a non idempotent operation
|
17
|
+
# Check the semantics of the db
|
18
|
+
NoBrainer::Config.max_reconnection_tries.times do
|
19
|
+
begin
|
20
|
+
NoBrainer.logger.try(:warn, "Lost connection to #{NoBrainer::Config.rethinkdb_url}, retrying...")
|
21
|
+
sleep 1
|
22
|
+
NoBrainer.connection.reconnect(:noreply_wait => false)
|
23
|
+
return true
|
24
|
+
rescue StandardError => e
|
25
|
+
retry if is_connection_error_exception?(e)
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
end
|
29
|
+
false
|
30
|
+
end
|
11
31
|
|
12
|
-
|
13
|
-
|
32
|
+
def is_connection_error_exception?(e)
|
33
|
+
case e
|
34
|
+
when Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE,
|
35
|
+
Errno::ECONNRESET, Errno::ETIMEDOUT, IOError
|
36
|
+
true
|
37
|
+
when RethinkDB::RqlRuntimeError
|
38
|
+
e.message =~ /cannot perform (read|write): No master available/ ||
|
39
|
+
e.message =~ /Error: Connection Closed/
|
40
|
+
else
|
41
|
+
false
|
14
42
|
end
|
15
|
-
raise e
|
16
43
|
end
|
17
44
|
end
|
@@ -16,11 +16,11 @@ class NoBrainer::QueryRunner::RunOptions < NoBrainer::QueryRunner::Middleware
|
|
16
16
|
def call(env)
|
17
17
|
env[:options].symbolize_keys!
|
18
18
|
if Thread.current[:nobrainer_options]
|
19
|
-
env[:options]
|
19
|
+
env[:options].reverse_merge!(Thread.current[:nobrainer_options])
|
20
20
|
end
|
21
21
|
|
22
22
|
if NoBrainer::Config.durability.to_s != 'hard'
|
23
|
-
env[:options]
|
23
|
+
env[:options].reverse_merge!(:durability => NoBrainer::Config.durability)
|
24
24
|
end
|
25
25
|
|
26
26
|
if env[:options][:db] && !env[:options][:db].is_a?(RethinkDB::RQL)
|
@@ -28,6 +28,8 @@ class NoBrainer::QueryRunner::RunOptions < NoBrainer::QueryRunner::Middleware
|
|
28
28
|
env[:options][:db] = RethinkDB::RQL.new.db(env[:db_name])
|
29
29
|
end
|
30
30
|
|
31
|
+
env[:criteria] = env[:options].delete(:criteria)
|
32
|
+
|
31
33
|
@runner.call(env)
|
32
34
|
end
|
33
35
|
end
|
data/lib/nobrainer.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9')
|
2
|
-
raise 'Please use Ruby 1.9 or later'
|
3
|
-
end
|
4
|
-
|
5
1
|
require 'active_support'
|
6
2
|
%w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion object/deep_dup
|
7
3
|
object/try hash/keys hash/indifferent_access hash/reverse_merge hash/deep_merge array/extract_options)
|
@@ -12,7 +8,7 @@ module NoBrainer
|
|
12
8
|
extend NoBrainer::Autoload
|
13
9
|
|
14
10
|
# We eager load things that could be loaded for the first time during the web request
|
15
|
-
autoload :Document, :
|
11
|
+
autoload :Document, :IndexManager, :Loader, :Fork, :DecoratedSymbol
|
16
12
|
eager_autoload :Config, :Connection, :Error, :QueryRunner, :Criteria, :Util
|
17
13
|
|
18
14
|
class << self
|
@@ -28,7 +24,7 @@ module NoBrainer
|
|
28
24
|
end
|
29
25
|
|
30
26
|
def disconnect
|
31
|
-
@connection.try(:disconnect)
|
27
|
+
@connection.try(:disconnect, :noreply_wait => true)
|
32
28
|
@connection = nil
|
33
29
|
end
|
34
30
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
@@ -25,25 +25,33 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.11.0.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
- - <
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '5'
|
33
|
+
version: 4.0.0
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
38
|
- - '>='
|
42
39
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
44
|
-
|
40
|
+
version: 4.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
45
53
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
54
|
+
version: 4.0.0
|
47
55
|
- !ruby/object:Gem::Dependency
|
48
56
|
name: middleware
|
49
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,33 +78,34 @@ files:
|
|
70
78
|
- lib/no_brainer/document/association/has_many_through.rb
|
71
79
|
- lib/no_brainer/document/association/has_many.rb
|
72
80
|
- lib/no_brainer/document/association/eager_loader.rb
|
73
|
-
- lib/no_brainer/document/association/belongs_to.rb
|
74
81
|
- lib/no_brainer/document/association/core.rb
|
82
|
+
- lib/no_brainer/document/association/belongs_to.rb
|
75
83
|
- lib/no_brainer/document/polymorphic.rb
|
76
84
|
- lib/no_brainer/document/store_in.rb
|
77
|
-
- lib/no_brainer/document/injection_layer.rb
|
78
85
|
- lib/no_brainer/document/core.rb
|
79
|
-
- lib/no_brainer/document/criteria.rb
|
80
|
-
- lib/no_brainer/document/dynamic_attributes.rb
|
81
86
|
- lib/no_brainer/document/serialization.rb
|
82
87
|
- lib/no_brainer/document/association.rb
|
83
|
-
- lib/no_brainer/document/index.rb
|
84
|
-
- lib/no_brainer/document/validation.rb
|
85
88
|
- lib/no_brainer/document/callbacks.rb
|
86
|
-
- lib/no_brainer/document/
|
87
|
-
- lib/no_brainer/document/persistance.rb
|
89
|
+
- lib/no_brainer/document/dynamic_attributes.rb
|
88
90
|
- lib/no_brainer/document/timestamps.rb
|
91
|
+
- lib/no_brainer/document/dirty.rb
|
92
|
+
- lib/no_brainer/document/index.rb
|
93
|
+
- lib/no_brainer/document/validation.rb
|
89
94
|
- lib/no_brainer/document/attributes.rb
|
90
95
|
- lib/no_brainer/document/id.rb
|
96
|
+
- lib/no_brainer/document/injection_layer.rb
|
97
|
+
- lib/no_brainer/document/persistance.rb
|
98
|
+
- lib/no_brainer/document/readonly.rb
|
91
99
|
- lib/no_brainer/document/types.rb
|
92
|
-
- lib/no_brainer/
|
100
|
+
- lib/no_brainer/document/criteria.rb
|
93
101
|
- lib/no_brainer/query_runner/database_on_demand.rb
|
94
102
|
- lib/no_brainer/query_runner/table_on_demand.rb
|
95
103
|
- lib/no_brainer/query_runner/write_error.rb
|
96
104
|
- lib/no_brainer/query_runner/driver.rb
|
97
|
-
- lib/no_brainer/query_runner/run_options.rb
|
98
105
|
- lib/no_brainer/query_runner/logger.rb
|
99
106
|
- lib/no_brainer/query_runner/missing_index.rb
|
107
|
+
- lib/no_brainer/query_runner/run_options.rb
|
108
|
+
- lib/no_brainer/query_runner/connection.rb
|
100
109
|
- lib/no_brainer/railtie/database.rake
|
101
110
|
- lib/no_brainer/index_manager.rb
|
102
111
|
- lib/no_brainer/loader.rb
|
@@ -104,30 +113,28 @@ files:
|
|
104
113
|
- lib/no_brainer/fork.rb
|
105
114
|
- lib/no_brainer/connection.rb
|
106
115
|
- lib/no_brainer/query_runner.rb
|
107
|
-
- lib/no_brainer/config.rb
|
108
|
-
- lib/no_brainer/autoload.rb
|
109
116
|
- lib/no_brainer/util.rb
|
110
|
-
- lib/no_brainer/document.rb
|
111
|
-
- lib/no_brainer/document_with_timestamps.rb
|
112
117
|
- lib/no_brainer/decorated_symbol.rb
|
113
|
-
- lib/no_brainer/criteria/update.rb
|
114
118
|
- lib/no_brainer/criteria/scope.rb
|
115
119
|
- lib/no_brainer/criteria/raw.rb
|
116
120
|
- lib/no_brainer/criteria/preload.rb
|
117
121
|
- lib/no_brainer/criteria/order_by.rb
|
118
122
|
- lib/no_brainer/criteria/limit.rb
|
119
|
-
- lib/no_brainer/criteria/inc.rb
|
120
123
|
- lib/no_brainer/criteria/first.rb
|
121
124
|
- lib/no_brainer/criteria/enumerable.rb
|
122
|
-
- lib/no_brainer/criteria/delete.rb
|
123
125
|
- lib/no_brainer/criteria/count.rb
|
124
|
-
- lib/no_brainer/criteria/core.rb
|
125
126
|
- lib/no_brainer/criteria/cache.rb
|
126
127
|
- lib/no_brainer/criteria/after_find.rb
|
127
128
|
- lib/no_brainer/criteria/where.rb
|
129
|
+
- lib/no_brainer/criteria/update.rb
|
130
|
+
- lib/no_brainer/criteria/delete.rb
|
131
|
+
- lib/no_brainer/criteria/core.rb
|
132
|
+
- lib/no_brainer/railtie.rb
|
133
|
+
- lib/no_brainer/config.rb
|
134
|
+
- lib/no_brainer/document.rb
|
128
135
|
- lib/no_brainer/criteria.rb
|
129
136
|
- lib/no_brainer/error.rb
|
130
|
-
- lib/no_brainer/
|
137
|
+
- lib/no_brainer/autoload.rb
|
131
138
|
- lib/rails/generators/nobrainer.rb
|
132
139
|
- lib/rails/generators/nobrainer/model/model_generator.rb
|
133
140
|
- lib/rails/generators/nobrainer/model/templates/model.rb.tt
|
@@ -146,7 +153,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
153
|
requirements:
|
147
154
|
- - '>='
|
148
155
|
- !ruby/object:Gem::Version
|
149
|
-
version:
|
156
|
+
version: 1.9.0
|
150
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
158
|
requirements:
|
152
159
|
- - '>='
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module NoBrainer::Criteria::Inc
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
|
4
|
-
def inc_all(field, value=1)
|
5
|
-
# TODO The useful inc() is on a model instance.
|
6
|
-
# But then do we want to postpone the inc() to the next save?
|
7
|
-
# It might make sense (because we don't have transactions).
|
8
|
-
update_all { |doc| { field => doc[field] + value } }
|
9
|
-
end
|
10
|
-
|
11
|
-
def dec_all(field, value=1)
|
12
|
-
inc_all(field, -value)
|
13
|
-
end
|
14
|
-
end
|