nobrainer 0.16.0 → 0.17.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: 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