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 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