nobrainer 0.16.0 → 0.17.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: fe36d41b56c9780a4d16f3085d31de0361d34cae
4
- data.tar.gz: 9f89b0da1f34d65c8861345307adc79f93a5593d
3
+ metadata.gz: 6cf2f24165387c78884eaffc90c04da535bc571f
4
+ data.tar.gz: b8d3b1bd887c454e3f017664a559c70bec9a6312
5
5
  SHA512:
6
- metadata.gz: 43e0cf6f767028a46068635713cb9c1d639a8e03de6503493a6af05179977dfe1ae01b5c6a49d93ec0f2e4012a24b0386b6f66d31c7dd274ec05f5f69b252ba3
7
- data.tar.gz: e728763fc6095d493db2ed93a9141f72d7cfc927f35300993f0ca6272996a5bac120f6f9cbfc9a985d80329a78da88ec5d827d5af31ccce93c833704b3a17f0b
6
+ metadata.gz: fa00190b18bbd8a498c855ef04fe7720c169c51edbb0d360939dc2ea5c4b76db47361615bc86153207e22be8096946bdce120c35d3b3da6fe99a9db4a4b58f94
7
+ data.tar.gz: 034d21dabdce9c900b11c899e20d5e69af34560ec2980bbfdba386974bc30828ac6fb9d590fc73a4ac9b568dbfdbdb9a9fb60760c4aeceac963396fd8128e679
@@ -6,7 +6,7 @@ module NoBrainer::Config
6
6
  :auto_create_databases, :auto_create_tables,
7
7
  :max_reconnection_tries, :durability,
8
8
  :user_timezone, :db_timezone, :colorize_logger,
9
- :distributed_lock_class
9
+ :distributed_lock_class, :per_thread_connection
10
10
 
11
11
  def apply_defaults
12
12
  self.rethinkdb_url = default_rethinkdb_url
@@ -20,6 +20,7 @@ module NoBrainer::Config
20
20
  self.db_timezone = :utc
21
21
  self.colorize_logger = true
22
22
  self.distributed_lock_class = nil
23
+ self.per_thread_connection = false
23
24
  end
24
25
 
25
26
  def reset!
@@ -33,7 +34,7 @@ module NoBrainer::Config
33
34
  assert_valid_options!
34
35
  @configured = true
35
36
 
36
- NoBrainer.disconnect_if_url_changed
37
+ NoBrainer::ConnectionManager.disconnect_if_url_changed
37
38
  end
38
39
 
39
40
  def configured?
@@ -0,0 +1,60 @@
1
+ module NoBrainer::ConnectionManager
2
+ extend self
3
+
4
+ @lock = Mutex.new
5
+
6
+ def synchronize(&block)
7
+ if NoBrainer::Config.per_thread_connection
8
+ block.call
9
+ else
10
+ @lock.synchronize { block.call }
11
+ end
12
+ end
13
+
14
+ def get_new_connection
15
+ url = NoBrainer::Config.rethinkdb_url
16
+ raise "Please specify a database connection to RethinkDB" unless url
17
+ NoBrainer::Connection.new(url)
18
+ end
19
+
20
+ def current_connection
21
+ if NoBrainer::Config.per_thread_connection
22
+ Thread.current[:nobrainer_connection]
23
+ else
24
+ @connection
25
+ end
26
+ end
27
+
28
+ def current_connection=(value)
29
+ if NoBrainer::Config.per_thread_connection
30
+ Thread.current[:nobrainer_connection] = value
31
+ else
32
+ @connection = value
33
+ end
34
+ end
35
+
36
+ def connection
37
+ c = self.current_connection
38
+ return c if c
39
+
40
+ synchronize do
41
+ self.current_connection ||= get_new_connection
42
+ end
43
+ end
44
+
45
+ def _disconnect
46
+ self.current_connection.try(:disconnect, :noreply_wait => true) rescue nil
47
+ self.current_connection = nil
48
+ end
49
+
50
+ def disconnect
51
+ synchronize { _disconnect }
52
+ end
53
+
54
+ def disconnect_if_url_changed
55
+ synchronize do
56
+ c = current_connection
57
+ _disconnect if c && c.uri != NoBrainer::Config.rethinkdb_url
58
+ end
59
+ end
60
+ end
@@ -7,7 +7,7 @@ module NoBrainer::Document
7
7
  autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
8
8
  :Validation, :Persistance, :Types, :Uniqueness, :Callbacks, :Dirty, :Id,
9
9
  :Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
10
- :MissingAttributes, :LazyFetch
10
+ :MissingAttributes, :LazyFetch, :AtomicOps
11
11
 
12
12
  autoload :DynamicAttributes, :Timestamps
13
13
 
@@ -48,7 +48,7 @@ module NoBrainer::Document::Aliases
48
48
  end
49
49
  end
50
50
 
51
- def persistable_key(k)
51
+ def persistable_key(k, options={})
52
52
  lookup_field_alias(super)
53
53
  end
54
54
  end
@@ -0,0 +1,205 @@
1
+ module NoBrainer::Document::AtomicOps
2
+ extend ActiveSupport::Concern
3
+
4
+ class PendingAtomic
5
+ def self._new(instance, field, user_value, options={})
6
+ klass = case user_value
7
+ when Array then PendingAtomicArray
8
+ when Set then PendingAtomicSet
9
+ else self
10
+ end
11
+ klass.new(instance, field, user_value, options)
12
+ end
13
+
14
+ def initialize(instance, field, user_value, options={})
15
+ @instance = instance
16
+ @field = field
17
+ @user_value = user_value
18
+ @value_tainted = instance._is_attribute_tainted?(field)
19
+ @options = options
20
+ @ops = []
21
+ end
22
+
23
+ def write_access?
24
+ @options[:write_access] == true
25
+ end
26
+
27
+ def ensure_writeable!
28
+ unless write_access?
29
+ @options[:write_access] = true
30
+ @instance.write_attribute(@field, self)
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ "<#{@field} with pending atomic operations>"
36
+ end
37
+ alias inspect to_s
38
+
39
+ def method_missing(method_name, *a, &b)
40
+ @ops << [method_name, a, b]
41
+ self
42
+ end
43
+
44
+ def compile_rql_value(rql_doc)
45
+ field = @instance.class.lookup_field_alias(@field)
46
+ value = @value_tainted ? RethinkDB::RQL.new.expr(@user_value) : rql_doc[field]
47
+ @ops.each { |method_name, a, b| value = value.__send__(method_name, *a, &b) }
48
+ value
49
+ end
50
+ end
51
+
52
+ class PendingAtomicArray < PendingAtomic
53
+ def -(value)
54
+ @ops << [:difference, [value.to_a]]
55
+ self
56
+ end
57
+ def difference(v); self - v; end
58
+
59
+ def delete(value)
60
+ difference([value])
61
+ end
62
+
63
+ def +(value)
64
+ @ops << [:+, [value.to_a]]
65
+ self
66
+ end
67
+ def add(v); self + v; end
68
+
69
+ def &(value)
70
+ @ops << [:set_intersection, [value.to_a]]
71
+ self
72
+ end
73
+ def intersection(v); self & v; end
74
+
75
+ def |(value)
76
+ @ops << [:set_union, [value.to_a]]
77
+ self
78
+ end
79
+ def union(v); self | v; end
80
+
81
+ def <<(value)
82
+ @ops << [:append, [value]]
83
+ ensure_writeable!
84
+ self
85
+ end
86
+ end
87
+
88
+ class PendingAtomicSet < PendingAtomicArray
89
+ def -(value)
90
+ @ops << [:set_difference, [value.to_a]]
91
+ self
92
+ end
93
+
94
+ def +(value)
95
+ @ops << [:set_union, [value.to_a]]
96
+ self
97
+ end
98
+
99
+ def <<(value)
100
+ @ops << [:set_union, [[value]]]
101
+ ensure_writeable!
102
+ self
103
+ end
104
+ end
105
+
106
+ def clear_dirtiness(options={})
107
+ super
108
+ @_tainted_attributes = Set.new
109
+ end
110
+
111
+ def _taint_attribute(name)
112
+ @_tainted_attributes << name
113
+ end
114
+
115
+ def _is_attribute_tainted?(name)
116
+ @_tainted_attributes.include?(name)
117
+ end
118
+
119
+ def in_atomic?
120
+ !!Thread.current[:nobrainer_atomic]
121
+ end
122
+
123
+ def in_other_atomic?
124
+ v = Thread.current[:nobrainer_atomic]
125
+ !v.nil? && !v.equal?(self)
126
+ end
127
+
128
+ def ensure_exclusive_atomic!
129
+ raise NoBrainer::Error::AtomicBlock.new('You may not access other documents within an atomic block') if in_other_atomic?
130
+ end
131
+
132
+ def queue_atomic(&block)
133
+ ensure_exclusive_atomic!
134
+
135
+ begin
136
+ old_atomic, Thread.current[:nobrainer_atomic] = Thread.current[:nobrainer_atomic], self
137
+ block.call(RethinkDB::RQL.new)
138
+ ensure
139
+ Thread.current[:nobrainer_atomic] = old_atomic
140
+ end
141
+ end
142
+
143
+ def _read_attribute(name)
144
+ ensure_exclusive_atomic!
145
+ value = super
146
+
147
+ case [in_atomic?, value.is_a?(PendingAtomic)]
148
+ when [true, true] then value
149
+ when [true, false] then PendingAtomic._new(self, name.to_s, value, :write_access => false)
150
+ when [false, true] then raise NoBrainer::Error::CannotReadAtomic.new(self, name, value)
151
+ when [false, false] then value
152
+ end
153
+ end
154
+
155
+ def _write_attribute(name, value)
156
+ ensure_exclusive_atomic!
157
+
158
+ case [in_atomic?, value.is_a?(PendingAtomic)]
159
+ when [true, true] then super
160
+ when [true, false] then raise NoBrainer::Error::AtomicBlock.new('Avoid the use of atomic blocks for non atomic operations')
161
+ when [false, true] then raise NoBrainer::Error::AtomicBlock.new('Use atomic blocks for atomic operations')
162
+ when [false, false] then super.tap { _taint_attribute(name.to_s) }
163
+ end
164
+ end
165
+
166
+ def assign_attributes(attrs, options={})
167
+ ensure_exclusive_atomic!
168
+ super
169
+ end
170
+
171
+ def save?(options={})
172
+ raise NoBrainer::Error::AtomicBlock.new('You may persist documents only outside of queue_atomic blocks') if in_atomic?
173
+ super
174
+ end
175
+
176
+ def read_attribute_for_change(attr)
177
+ super
178
+ rescue NoBrainer::Error::CannotReadAtomic => e
179
+ e.value
180
+ end
181
+
182
+ def read_attribute_for_validation(attr)
183
+ super
184
+ rescue NoBrainer::Error::CannotReadAtomic => e
185
+ e.value
186
+ end
187
+
188
+ module ClassMethods
189
+ def persistable_value(k, v, options={})
190
+ v.is_a?(PendingAtomic) ? v.compile_rql_value(options[:rql_doc]) : super
191
+ end
192
+ end
193
+ end
194
+
195
+ class ActiveModel::EachValidator
196
+ # XXX Monkey Patching :(
197
+ def validate(record)
198
+ attributes.each do |attribute|
199
+ value = record.read_attribute_for_validation(attribute)
200
+ next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- This is the added line
201
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
202
+ validate_each(record, attribute, value)
203
+ end
204
+ end
205
+ end
@@ -25,6 +25,14 @@ module NoBrainer::Document::Attributes
25
25
  Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
26
26
  end
27
27
 
28
+ def _read_attribute(name)
29
+ @_attributes[name]
30
+ end
31
+
32
+ def _write_attribute(name, value)
33
+ @_attributes[name] = value
34
+ end
35
+
28
36
  def read_attribute(name)
29
37
  __send__("#{name}")
30
38
  end
@@ -103,8 +111,8 @@ module NoBrainer::Document::Attributes
103
111
  # Using a layer so the user can use super when overriding these methods
104
112
  attr = attr.to_s
105
113
  inject_in_layer :attributes do
106
- define_method("#{attr}=") { |value| @_attributes[attr] = value }
107
- define_method("#{attr}") { @_attributes[attr] }
114
+ define_method("#{attr}=") { |value| _write_attribute(attr, value) }
115
+ define_method("#{attr}") { _read_attribute(attr) }
108
116
  end
109
117
  end
110
118
 
@@ -20,7 +20,7 @@ module NoBrainer::Document::Callbacks
20
20
  run_callbacks(:update) { super }
21
21
  end
22
22
 
23
- def save(*args, &block)
23
+ def save?(*args, &block)
24
24
  run_callbacks(:save) { super }
25
25
  end
26
26
 
@@ -33,10 +33,14 @@ module NoBrainer::Document::Dirty
33
33
  changes.keys
34
34
  end
35
35
 
36
+ def read_attribute_for_change(attr)
37
+ read_attribute(attr)
38
+ end
39
+
36
40
  def changes
37
41
  result = {}.with_indifferent_access
38
42
  @_old_attributes.each do |attr, old_value|
39
- current_value = read_attribute(attr)
43
+ current_value = read_attribute_for_change(attr)
40
44
  if current_value != old_value || !@_old_attributes_keys.include?(attr)
41
45
  result[attr] = [old_value, current_value]
42
46
  end
@@ -48,7 +52,7 @@ module NoBrainer::Document::Dirty
48
52
  attr = args.first
49
53
  current_value = begin
50
54
  case args.size
51
- when 1 then assert_access_field(attr); read_attribute(attr)
55
+ when 1 then assert_access_field(attr); read_attribute_for_change(attr)
52
56
  when 2 then args.last
53
57
  else raise
54
58
  end
@@ -61,6 +65,19 @@ module NoBrainer::Document::Dirty
61
65
  end
62
66
  end
63
67
 
68
+ def _read_attribute(name)
69
+ super.tap do |value|
70
+ # This take care of string/arrays/hashes that could change without going
71
+ # through the setter.
72
+ attribute_may_change(name, value) if value.respond_to?(:size)
73
+ end
74
+ end
75
+
76
+ def _write_attribute(name, value)
77
+ attribute_may_change(name)
78
+ super
79
+ end
80
+
64
81
  module ClassMethods
65
82
  def _field(attr, options={})
66
83
  super
@@ -69,7 +86,7 @@ module NoBrainer::Document::Dirty
69
86
  inject_in_layer :dirty_tracking do
70
87
  define_method("#{attr}_change") do
71
88
  if @_old_attributes.has_key?(attr)
72
- result = [@_old_attributes[attr], read_attribute(attr)]
89
+ result = [@_old_attributes[attr], read_attribute_for_change(attr)]
73
90
  result if result.first != result.last || !@_old_attributes_keys.include?(attr)
74
91
  end
75
92
  end
@@ -79,20 +96,7 @@ module NoBrainer::Document::Dirty
79
96
  end
80
97
 
81
98
  define_method("#{attr}_was") do
82
- @_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute(attr)
83
- end
84
-
85
- define_method("#{attr}") do
86
- super().tap do |value|
87
- # This take care of string/arrays/hashes that could change without going
88
- # through the setter.
89
- attribute_may_change(attr, value) if value.respond_to?(:size)
90
- end
91
- end
92
-
93
- define_method("#{attr}=") do |value|
94
- attribute_may_change(attr)
95
- super(value)
99
+ @_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute_for_change(attr)
96
100
  end
97
101
  end
98
102
  end
@@ -103,8 +107,6 @@ module NoBrainer::Document::Dirty
103
107
  remove_method("#{attr}_change")
104
108
  remove_method("#{attr}_changed?")
105
109
  remove_method("#{attr}_was")
106
- remove_method("#{attr}=")
107
- remove_method("#{attr}")
108
110
  end
109
111
  end
110
112
  end
@@ -2,23 +2,11 @@ module NoBrainer::Document::DynamicAttributes
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def read_attribute(name)
5
- if self.respond_to?("#{name}")
6
- super
7
- else
8
- assert_access_field(name)
9
- @_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
10
- end
5
+ self.respond_to?("#{name}") ? super : _read_attribute(name)
11
6
  end
12
7
 
13
8
  def write_attribute(name, value)
14
- if self.respond_to?("#{name}=")
15
- super
16
- else
17
- attribute_may_change(name)
18
- @_attributes[name] = value
19
- clear_missing_field(name)
20
- value
21
- end
9
+ self.respond_to?("#{name}=") ? super : _write_attribute(name, value)
22
10
  end
23
11
 
24
12
  def readable_attributes
@@ -17,6 +17,7 @@ module NoBrainer::Document::Id
17
17
 
18
18
  def ==(other)
19
19
  return super unless self.class == other.class
20
+ return self.equal?(other) if in_atomic?
20
21
  !pk_value.nil? && pk_value == other.pk_value
21
22
  end
22
23
  alias_method :eql?, :==
@@ -75,6 +75,7 @@ module NoBrainer::Document::Index
75
75
  def perform_create_index(index_name, options={})
76
76
  index_name = index_name.to_sym
77
77
  index_args = self.indexes[index_name]
78
+ aliased_name = index_args[:as]
78
79
 
79
80
  index_proc = case index_args[:kind]
80
81
  when :single then ->(doc) { doc[lookup_field_alias(index_name)] }
@@ -82,29 +83,33 @@ module NoBrainer::Document::Index
82
83
  when :proc then index_args[:what]
83
84
  end
84
85
 
85
- NoBrainer.run(self.rql_table.index_create(index_args[:as], index_args[:options], &index_proc))
86
+ NoBrainer.run(self.rql_table.index_create(aliased_name, index_args[:options], &index_proc))
86
87
  wait_for_index(index_name) unless options[:wait] == false
87
88
 
88
- if options[:verbose]
89
- if index_name == index_args[:as]
90
- STDERR.puts "Created index #{self}.#{index_name}"
91
- else
92
- STDERR.puts "Created index #{self}.#{index_name} as #{index_args[:as]}"
93
- end
94
- end
89
+ readable_index_name = "index #{self}.#{index_name}"
90
+ readable_index_name += " as #{aliased_name}" unless index_name == aliased_name
91
+
92
+ STDERR.puts "Created index #{readable_index_name}" if options[:verbose]
95
93
  end
96
94
 
97
95
  def perform_drop_index(index_name, options={})
96
+ index_name = index_name.to_sym
98
97
  aliased_name = self.indexes[index_name].try(:[], :as) || index_name
99
- NoBrainer.run(self.rql_table.index_drop(aliased_name))
100
98
 
101
- if options[:verbose]
102
- if index_name == index_args[:as]
103
- STDERR.puts "Dropped index #{self}.#{index_name}"
104
- else
105
- STDERR.puts "Dreated index #{self}.#{index_name} as #{index_args[:as]}"
99
+ readable_index_name = "index #{self}.#{index_name}"
100
+ readable_index_name += " as #{aliased_name}" unless index_name == aliased_name
101
+
102
+ if STDIN.stat.chardev? && STDERR.stat.chardev? && !options[:no_confirmation]
103
+ STDERR.print "Confirm dropping #{readable_index_name} [yna]: "
104
+ case STDIN.gets.strip.chomp
105
+ when 'y' then
106
+ when 'n' then return
107
+ when 'a' then options[:no_confirmation] = true
106
108
  end
107
109
  end
110
+
111
+ NoBrainer.run(self.rql_table.index_drop(aliased_name))
112
+ STDERR.puts "Dropped #{readable_index_name}" if options[:verbose]
108
113
  end
109
114
 
110
115
  def get_index_alias_reverse_map
@@ -120,6 +125,8 @@ module NoBrainer::Document::Index
120
125
  end
121
126
  wanted_indexes = self.indexes.keys - [self.pk_name]
122
127
 
128
+ # FIXME removing an aliased field is not going to work well with this method
129
+
123
130
  (current_indexes - wanted_indexes).each do |index_name|
124
131
  perform_drop_index(index_name, options)
125
132
  end
@@ -45,29 +45,12 @@ module NoBrainer::Document::MissingAttributes
45
45
  end
46
46
  end
47
47
 
48
+ def _read_attribute(name)
49
+ assert_access_field(name)
50
+ super
51
+ end
48
52
 
49
- module ClassMethods
50
- def _field(attr, options={})
51
- super
52
-
53
- inject_in_layer :missing_attributes do
54
- define_method("#{attr}") do
55
- assert_access_field(attr)
56
- super()
57
- end
58
-
59
- define_method("#{attr}=") do |value|
60
- super(value).tap { clear_missing_field(attr) }
61
- end
62
- end
63
- end
64
-
65
- def _remove_field(attr, options={})
66
- super
67
- inject_in_layer :missing_attributes do
68
- remove_method("#{attr}=")
69
- remove_method("#{attr}")
70
- end
71
- end
53
+ def _write_attribute(name, value)
54
+ super.tap { clear_missing_field(name) }
72
55
  end
73
56
  end
@@ -61,15 +61,16 @@ module NoBrainer::Document::Persistance
61
61
 
62
62
  def _create(options={})
63
63
  return false if options[:validate] && !valid?
64
- keys = self.class.insert_all(@_attributes)
65
- self.pk_value ||= keys.first
64
+ attrs = self.class.persistable_attributes(@_attributes, :instance => self)
65
+ result = NoBrainer.run(self.class.rql_table.insert(attrs))
66
+ self.pk_value ||= result['generated_keys'].to_a.first
66
67
  @new_record = false
67
68
  true
68
69
  end
69
70
 
70
71
  def _update(attrs)
71
- attrs = self.class.persistable_attributes(attrs)
72
- NoBrainer.run { selector.update(attrs) }
72
+ rql = ->(doc){ self.class.persistable_attributes(attrs, :instance => self, :rql_doc => doc) }
73
+ NoBrainer.run { selector.update(&rql) }
73
74
  end
74
75
 
75
76
  def _update_only_changed_attrs(options={})
@@ -88,22 +89,22 @@ module NoBrainer::Document::Persistance
88
89
  true
89
90
  end
90
91
 
91
- def save(options={})
92
+ def save?(options={})
92
93
  options = options.reverse_merge(:validate => true)
93
94
  new_record? ? _create(options) : _update_only_changed_attrs(options)
94
95
  end
95
96
 
96
- def save!(*args)
97
- save(*args) or raise NoBrainer::Error::DocumentInvalid, self
97
+ def save(*args)
98
+ save?(*args) or raise NoBrainer::Error::DocumentInvalid, self
98
99
  end
99
100
 
100
- def update_attributes(attrs, options={})
101
+ def update_attributes?(attrs, options={})
101
102
  assign_attributes(attrs, options)
102
- save(options)
103
+ save?(options)
103
104
  end
104
105
 
105
- def update_attributes!(*args)
106
- update_attributes(*args) or raise NoBrainer::Error::DocumentInvalid, self
106
+ def update_attributes(*args)
107
+ update_attributes?(*args) or raise NoBrainer::Error::DocumentInvalid, self
107
108
  end
108
109
 
109
110
  def delete
@@ -124,10 +125,6 @@ module NoBrainer::Document::Persistance
124
125
  new(attrs, options).tap { |doc| doc.save(options) }
125
126
  end
126
127
 
127
- def create!(attrs={}, options={})
128
- new(attrs, options).tap { |doc| doc.save!(options) }
129
- end
130
-
131
128
  def insert_all(*args)
132
129
  docs = args.shift
133
130
  docs = [docs] unless docs.is_a?(Array)
@@ -140,16 +137,16 @@ module NoBrainer::Document::Persistance
140
137
  NoBrainer.run(rql_table.sync)['synced'] == 1
141
138
  end
142
139
 
143
- def persistable_key(k)
140
+ def persistable_key(k, options={})
144
141
  k
145
142
  end
146
143
 
147
- def persistable_value(k, v)
144
+ def persistable_value(k, v, options={})
148
145
  v
149
146
  end
150
147
 
151
- def persistable_attributes(attrs)
152
- Hash[attrs.map { |k,v| [persistable_key(k), persistable_value(k, v)] }]
148
+ def persistable_attributes(attrs, options={})
149
+ Hash[attrs.map { |k,v| [persistable_key(k, options), persistable_value(k, v, options)] }]
153
150
  end
154
151
  end
155
152
  end
@@ -22,7 +22,7 @@ module NoBrainer::Document::Types
22
22
  module ClassMethods
23
23
  def cast_user_to_model_for(attr, value)
24
24
  type = fields[attr.to_sym].try(:[], :type)
25
- return value if type.nil? || value.nil?
25
+ return value if type.nil? || value.nil? || value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic)
26
26
  if type.respond_to?(:nobrainer_cast_user_to_model)
27
27
  type.nobrainer_cast_user_to_model(value)
28
28
  else
@@ -53,7 +53,7 @@ module NoBrainer::Document::Types
53
53
  cast_model_to_db_for(attr, value)
54
54
  end
55
55
 
56
- def persistable_value(k, v)
56
+ def persistable_value(k, v, options={})
57
57
  cast_model_to_db_for(k, super)
58
58
  end
59
59
 
@@ -0,0 +1,23 @@
1
+ class Set
2
+ module NoBrainerExtentions
3
+ InvalidType = NoBrainer::Error::InvalidType
4
+
5
+ def nobrainer_cast_user_to_model(value)
6
+ case value
7
+ when Set then value
8
+ when Array then Set.new(value)
9
+ else raise InvalidType
10
+ end
11
+ end
12
+
13
+ def nobrainer_cast_db_to_model(value)
14
+ value.is_a?(Array) ? Set.new(value) : value
15
+ end
16
+
17
+ def nobrainer_cast_model_to_db(value)
18
+ value.is_a?(Set) ? value.to_a : value
19
+ end
20
+ end
21
+
22
+ extend NoBrainerExtentions
23
+ end
@@ -9,6 +9,20 @@ module NoBrainer::Error
9
9
  class AssociationNotPersisted < RuntimeError; end
10
10
  class ReadonlyField < RuntimeError; end
11
11
  class MissingAttribute < RuntimeError; end
12
+ class AtomicBlock < RuntimeError; end
13
+
14
+ class CannotReadAtomic < RuntimeError
15
+ attr_accessor :instance, :field, :value
16
+ def initialize(instance, field, value)
17
+ @instance = instance
18
+ @field = field
19
+ @value = value
20
+ end
21
+
22
+ def message
23
+ "Cannot read #{field}, atomic operations are pending"
24
+ end
25
+ end
12
26
 
13
27
  class DocumentInvalid < RuntimeError
14
28
  attr_accessor :instance
@@ -2,6 +2,10 @@ class NoBrainer::QueryRunner::ConnectionLock < NoBrainer::QueryRunner::Middlewar
2
2
  @@lock = Mutex.new
3
3
 
4
4
  def call(env)
5
- @@lock.synchronize { @runner.call(env) }
5
+ if NoBrainer::Config.per_thread_connection
6
+ @runner.call(env)
7
+ else
8
+ @@lock.synchronize { @runner.call(env) }
9
+ end
6
10
  end
7
11
  end
data/lib/nobrainer.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'set'
1
2
  require 'active_support'
2
3
  %w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion object/deep_dup
3
4
  object/try hash/keys hash/indifferent_access hash/reverse_merge hash/deep_merge array/extract_options)
@@ -10,28 +11,11 @@ module NoBrainer
10
11
  # We eager load things that could be loaded when handling the first web request.
11
12
  # Code that is loaded through the DSL of NoBrainer should not be eager loaded.
12
13
  autoload :Document, :IndexManager, :Loader, :Fork, :DecoratedSymbol
13
- eager_autoload :Config, :Connection, :Error, :QueryRunner, :Criteria, :RQL
14
+ eager_autoload :Config, :Connection, :ConnectionManager, :Error,
15
+ :QueryRunner, :Criteria, :RQL
14
16
 
15
17
  class << self
16
- # A connection is tied to a database.
17
- def connection
18
- @connection ||= begin
19
- url = NoBrainer::Config.rethinkdb_url
20
- raise "Please specify a database connection to RethinkDB" unless url
21
- Connection.new(url)
22
- end
23
- end
24
-
25
- def disconnect
26
- @connection.try(:disconnect, :noreply_wait => true)
27
- @connection = nil
28
- end
29
-
30
- def disconnect_if_url_changed
31
- if @connection && @connection.uri != NoBrainer::Config.rethinkdb_url
32
- disconnect
33
- end
34
- end
18
+ delegate :connection, :disconnect, :to => 'NoBrainer::ConnectionManager'
35
19
 
36
20
  delegate :db_create, :db_drop, :db_list,
37
21
  :table_create, :table_drop, :table_list,
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.16.0
4
+ version: 0.17.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-08-28 00:00:00.000000000 Z
11
+ date: 2014-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -78,6 +78,7 @@ files:
78
78
  - lib/no_brainer/autoload.rb
79
79
  - lib/no_brainer/config.rb
80
80
  - lib/no_brainer/connection.rb
81
+ - lib/no_brainer/connection_manager.rb
81
82
  - lib/no_brainer/criteria.rb
82
83
  - lib/no_brainer/criteria/after_find.rb
83
84
  - lib/no_brainer/criteria/aggregate.rb
@@ -107,6 +108,7 @@ files:
107
108
  - lib/no_brainer/document/association/has_many_through.rb
108
109
  - lib/no_brainer/document/association/has_one.rb
109
110
  - lib/no_brainer/document/association/has_one_through.rb
111
+ - lib/no_brainer/document/atomic_ops.rb
110
112
  - lib/no_brainer/document/attributes.rb
111
113
  - lib/no_brainer/document/callbacks.rb
112
114
  - lib/no_brainer/document/core.rb
@@ -130,6 +132,7 @@ files:
130
132
  - lib/no_brainer/document/types/date.rb
131
133
  - lib/no_brainer/document/types/float.rb
132
134
  - lib/no_brainer/document/types/integer.rb
135
+ - lib/no_brainer/document/types/set.rb
133
136
  - lib/no_brainer/document/types/string.rb
134
137
  - lib/no_brainer/document/types/symbol.rb
135
138
  - lib/no_brainer/document/types/time.rb
@@ -161,7 +164,10 @@ homepage: http://nobrainer.io
161
164
  licenses:
162
165
  - LGPLv3
163
166
  metadata: {}
164
- post_install_message:
167
+ post_install_message: |2
168
+
169
+ WARNING [NoBrainer] API change: save() is now save?() and save!() is now save()
170
+ WARNING [NoBrainer] Same for update_attributes
165
171
  rdoc_options: []
166
172
  require_paths:
167
173
  - lib