nobrainer 0.12.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f9f0fb2d3cb21fba6fad94403706e6f5274f72f4
4
- data.tar.gz: 74bfea6252ccc4eee6e67c003ed3facc7bbffe69
3
+ metadata.gz: dd6046706e63442640e96473b737f37ad5a4fea7
4
+ data.tar.gz: e1634aaf6acfb3c3e5e4287c57151d54cce30ee1
5
5
  SHA512:
6
- metadata.gz: 8da93f62774128bc6bfc485c356fd1e27b68ae0f3c5ccd0e9e81545210856a9282dccf0936369d3b92da90904e598d1c16d264a36b82e050e328ad02bda2758c
7
- data.tar.gz: c8f6e9c112d89df7062d2ffde33952e688d6c7c47becde0d3391949abac41851149a3d3625025908f00429565152157366bf90d1147bf4d0e705e68e57b1c31a
6
+ metadata.gz: ac735666befc4a025536e5beb72a8c4d4d79259875a8ad956ae16e4399c17cf5c2dcc7eecf6bcfa4351d56aec14b1a83a7d68cb751723d5beade516011990915
7
+ data.tar.gz: b6f69cd4dfbd953d9acc06a1c06aa9a27851baa51b1e1a2d0fce0ff320a32131c49724e5ea6aaa8af3a2aaf74bdb661379eb0a4e97abdb98e8084e5fae0516c5
@@ -2,7 +2,7 @@ module NoBrainer::Autoload
2
2
  include ActiveSupport::Autoload
3
3
 
4
4
  def self.extended(base)
5
- ActiveSupport::Autoload.extended(base)
5
+ ActiveSupport::Autoload.send(:extended, base)
6
6
  end
7
7
 
8
8
  def autoload(*constants)
@@ -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, :Inc,
7
- :Update, :Cache
6
+ :Count, :Delete, :Enumerable, :First, :Preload, :Update, :Cache
8
7
  end
@@ -26,7 +26,7 @@ module NoBrainer::Criteria::Core
26
26
  end
27
27
 
28
28
  def run(rql=nil)
29
- NoBrainer.run(rql || to_rql)
29
+ NoBrainer.run(:criteria => self) { (rql || to_rql) }
30
30
  end
31
31
 
32
32
  def merge!(criteria, options={})
@@ -2,7 +2,7 @@ module NoBrainer::Criteria::Delete
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def delete_all
5
- run(to_rql.delete)['deleted']
5
+ run(to_rql.delete)
6
6
  end
7
7
 
8
8
  def destroy_all
@@ -1,13 +1,11 @@
1
1
  module NoBrainer::Criteria::Update
2
2
  extend ActiveSupport::Concern
3
3
 
4
- def update_all(attrs={}, &block)
5
- block = proc { attrs } unless block_given?
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(attrs={}, &block)
10
- block = proc { attrs } unless block_given?
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
@@ -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
- _assign_attributes(attrs, options)
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 field(name, options={})
75
- name = name.to_sym
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 name.in?(RESERVED_FIELD_NAMES)
79
- raise "Cannot use a reserved field name: #{name}"
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[name] ||= {}
84
- klass.fields[name].merge!(options)
92
+ klass.fields[attr] ||= {}
93
+ klass.fields[attr].deep_merge!(options)
85
94
  end
86
95
 
87
- # Using a layer so the user can use super when overriding these methods
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?(name)
100
- !!fields[name.to_sym]
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 update(*args, &block)
18
+ def _update_only_changed_attrs(*args, &block)
19
19
  run_callbacks(:update) { super }
20
20
  end
21
21
 
22
- def replace(*args, &block)
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
@@ -19,7 +19,6 @@ module NoBrainer::Document::Criteria
19
19
  :includes, :preload, # Preload
20
20
  :each, :to_a, # Enumerable
21
21
  :first, :last, :first!, :last!, # First
22
- :inc_all, :dec_all, # Inc
23
22
  :update_all, :replace_all, # Update
24
23
  :to => :all
25
24
 
@@ -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
- # We are not using ActiveModel::Dirty because it's using
5
- # ActiveModel::AttributeMethods which gives pretty violent method_missing()
6
- # capabilities, such as giving a getter/setter method for any keys within the
7
- # attributes keys. We don't want that.
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 old_attributes_values
13
- @old_attributes_values ||= {}.with_indifferent_access
14
+ def _create(*args)
15
+ super.tap { clear_dirtiness }
14
16
  end
15
17
 
16
- def clear_dirtiness
17
- @old_attributes_values.try(:clear)
18
+ def _update(*args)
19
+ super.tap { clear_dirtiness }
18
20
  end
19
21
 
20
- def _assign_attributes(attrs, options={})
21
- super
22
- clear_dirtiness if options[:from_db]
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
- old_attributes_values.each do |attr, old_value|
37
+ @_old_attributes.each do |attr, old_value|
36
38
  current_value = read_attribute(attr)
37
- result[attr] = [old_value, current_value] if current_value != old_value
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 old_attributes_values.has_key?(attr)
44
- old_attributes_values[attr] = current_value.deep_dup
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 field(name, options={})
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("#{name}_change") do
54
- if old_attributes_values.has_key?(name)
55
- result = [old_attributes_values[name], read_attribute(name)]
56
- result = nil if result.first == result.last
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("#{name}_changed?") do
62
- !!__send__("#{name}_change")
65
+ define_method("#{attr}_changed?") do
66
+ !!__send__("#{attr}_change")
63
67
  end
64
68
 
65
- define_method("#{name}_was") do
66
- old_attributes_values.has_key?(name) ?
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("#{name}") do
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(name, value) if value.respond_to?(:size)
77
+ attribute_may_change(attr, value) if value.respond_to?(:size)
75
78
  end
76
79
  end
77
80
 
78
- define_method("#{name}=") do |value|
79
- attribute_may_change(name, read_attribute(name))
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 field(name, options={})
45
- name = name.to_sym
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(name, options[:index])
56
- when Symbol then index(name, options[:index] => true)
57
- when true then index(name)
58
- when false then remove_index(name)
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, code=nil, file=nil, line=nil, &block)
5
+ def inject_in_layer(name, &block)
6
6
  mod = class_eval "module NoBrainerLayer; module #{name.to_s.camelize}; self; end; end"
7
- mod.module_eval(code, file, line) if code
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 update(options={}, &block)
42
- return false if options[:validate] && !valid?
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 _update_changed(options={})
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
- changed_attrs = Hash[self.changed.map { |k| [k, @_attributes[k]] }]
71
- _update_changed_attributes(changed_attrs) if changed_attrs.present?
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) : _update_changed(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
- before_create { self.created_at = self.updated_at = Time.now }
9
- # Not using the before_update callback as it would mess
10
- # with the dirty tracking. We want to bypass the database
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 _update_changed_attributes(changed_attrs)
14
+ def _update(attrs)
15
15
  self.updated_at = Time.now
16
- changed_attrs['updated_at'] = @_attributes['updated_at']
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(name, value)
96
- name = name.to_sym
97
- field_def = fields[name]
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 = name
103
+ error.attr_name = attr
104
104
  raise error
105
105
  end
106
106
 
107
- def field(name, options={})
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("#{name}=") do |value|
111
+ define_method("#{attr}=") do |value|
118
112
  begin
119
- value = self.class.cast_value_for(name, value)
120
- @pending_type_errors.try(:delete, name)
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[name] = error
117
+ @pending_type_errors[attr] = error
124
118
  end
125
119
  super(value)
126
120
  end
127
121
 
128
- define_method("#{name}?") { !!read_attribute(name) } if type == Boolean
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 field(name, options={})
11
+ def _field(attr, options={})
12
12
  super
13
- validates(name.to_sym, options[:validates]) if options[: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)
@@ -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 RuntimeError, NoBrainer::Error::DocumentNotSaved => e
5
- if e.message =~ /cannot perform (read|write): lost contact with master/
6
- env[:connection_retries] ||= 0
7
- # TODO sleep in between? timing out should be time based?
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
- # XXX Possibly dangerous, as we could reexecute a non idempotent operation
10
- # Check the semantics of the db
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
- # TODO Unit test
13
- retry if (env[:connection_retries] += 1) < NoBrainer::Config.max_reconnection_tries
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] = env[:options].reverse_merge(Thread.current[:nobrainer_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] = env[:options].reverse_merge(:durability => NoBrainer::Config.durability)
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, :DocumentWithTimestamps, :IndexManager, :Loader, :Fork, :DecoratedSymbol
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.12.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-09 00:00:00.000000000 Z
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: activemodel
28
+ name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '>='
32
32
  - !ruby/object:Gem::Version
33
- version: 3.2.0
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: 3.2.0
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: '5'
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/dirty.rb
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/query_runner/connection.rb
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/railtie.rb
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: '0'
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
@@ -1,6 +0,0 @@
1
- module NoBrainer::DocumentWithTimestamps
2
- extend ActiveSupport::Concern
3
-
4
- include NoBrainer::Document
5
- include NoBrainer::Document::Timestamps
6
- end