nobrainer 0.20.0 → 0.21.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/autoload.rb +0 -5
  3. data/lib/no_brainer/config.rb +14 -9
  4. data/lib/no_brainer/connection.rb +1 -3
  5. data/lib/no_brainer/criteria.rb +1 -1
  6. data/lib/no_brainer/criteria/aggregate.rb +2 -2
  7. data/lib/no_brainer/criteria/cache.rb +8 -3
  8. data/lib/no_brainer/criteria/core.rb +1 -5
  9. data/lib/no_brainer/criteria/delete.rb +1 -1
  10. data/lib/no_brainer/criteria/eager_load.rb +51 -0
  11. data/lib/no_brainer/criteria/order_by.rb +1 -1
  12. data/lib/no_brainer/criteria/scope.rb +3 -10
  13. data/lib/no_brainer/criteria/update.rb +8 -6
  14. data/lib/no_brainer/criteria/where.rb +50 -13
  15. data/lib/no_brainer/document.rb +2 -2
  16. data/lib/no_brainer/document/aliases.rb +0 -8
  17. data/lib/no_brainer/document/association/belongs_to.rb +6 -2
  18. data/lib/no_brainer/document/association/core.rb +5 -4
  19. data/lib/no_brainer/document/association/eager_loader.rb +7 -8
  20. data/lib/no_brainer/document/association/has_many.rb +22 -8
  21. data/lib/no_brainer/document/association/has_many_through.rb +12 -3
  22. data/lib/no_brainer/document/atomic_ops.rb +63 -61
  23. data/lib/no_brainer/document/attributes.rb +11 -3
  24. data/lib/no_brainer/document/core.rb +5 -2
  25. data/lib/no_brainer/document/criteria.rb +14 -5
  26. data/lib/no_brainer/document/dirty.rb +11 -16
  27. data/lib/no_brainer/document/index.rb +0 -6
  28. data/lib/no_brainer/document/index/meta_store.rb +1 -1
  29. data/lib/no_brainer/document/persistance.rb +12 -2
  30. data/lib/no_brainer/document/types.rb +13 -12
  31. data/lib/no_brainer/document/types/binary.rb +0 -4
  32. data/lib/no_brainer/document/types/boolean.rb +0 -1
  33. data/lib/no_brainer/document/types/geo.rb +1 -0
  34. data/lib/no_brainer/document/types/string.rb +3 -0
  35. data/lib/no_brainer/document/types/text.rb +18 -0
  36. data/lib/no_brainer/document/validation.rb +31 -6
  37. data/lib/no_brainer/document/validation/not_null.rb +15 -0
  38. data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -10
  39. data/lib/no_brainer/error.rb +20 -23
  40. data/lib/no_brainer/locale/en.yml +1 -0
  41. data/lib/no_brainer/lock.rb +114 -0
  42. data/lib/no_brainer/query_runner/database_on_demand.rb +0 -1
  43. data/lib/no_brainer/query_runner/missing_index.rb +1 -1
  44. data/lib/no_brainer/query_runner/run_options.rb +0 -3
  45. data/lib/no_brainer/query_runner/table_on_demand.rb +2 -3
  46. data/lib/no_brainer/rql.rb +0 -4
  47. data/lib/nobrainer.rb +1 -1
  48. metadata +8 -4
  49. data/lib/no_brainer/criteria/preload.rb +0 -44
@@ -3,3 +3,4 @@ en:
3
3
  messages:
4
4
  taken: "is already taken"
5
5
  invalid_type: "should be a %{type}"
6
+ undefined: "must be defined"
@@ -0,0 +1,114 @@
1
+ require 'digest/sha1'
2
+
3
+ class NoBrainer::Lock
4
+ include NoBrainer::Document
5
+
6
+ store_in :table => 'nobrainer_locks'
7
+
8
+ # Since PKs are limited to 127 characters, we can't use the user's key as a PK
9
+ # as it could be arbitrarily long.
10
+ field :key_hash, :type => String, :primary_key => true, :default => ->{ Digest::SHA1.base64digest(key) }
11
+ field :key, :type => String
12
+ field :token, :type => String
13
+ field :expires_at, :type => Time
14
+
15
+ # We always use a new token, even when reading from the DB, because that's
16
+ # what represent our instance.
17
+ after_initialize { self.token = NoBrainer::Document::PrimaryKey::Generator.generate }
18
+
19
+ scope :expired, where(:expires_at.lt(RethinkDB::RQL.new.now))
20
+
21
+ def initialize(key, options={})
22
+ return super if options[:from_db]
23
+
24
+ key = case key
25
+ when Symbol then key.to_s
26
+ when String then key
27
+ else raise ArgumentError
28
+ end
29
+
30
+ super(options.merge(:key => key))
31
+ end
32
+
33
+ def lock(options={}, &block)
34
+ if block
35
+ lock(options)
36
+ return block.call.tap { unlock }
37
+ end
38
+
39
+ options.assert_valid_keys(:expire, :timeout)
40
+ timeout = NoBrainer::Config.lock_options.merge(options)[:timeout]
41
+ sleep_amount = 0.1
42
+
43
+ start_at = Time.now
44
+ while Time.now - start_at < timeout
45
+ return if try_lock(options.select { |k,_| k == :expire })
46
+ sleep(sleep_amount)
47
+ sleep_amount = [1, sleep_amount * 2].min
48
+ end
49
+
50
+ raise NoBrainer::Error::LockUnavailable.new("Lock on `#{key}' unavailable")
51
+ end
52
+
53
+ def try_lock(options={})
54
+ options.assert_valid_keys(:expire)
55
+ raise "Lock instance `#{key}' already locked" if @locked
56
+
57
+ set_expiration(options)
58
+
59
+ result = NoBrainer.run do |r|
60
+ selector.replace do |doc|
61
+ r.branch(doc.eq(nil).or(doc[:expires_at] < r.now),
62
+ self.attributes, doc)
63
+ end
64
+ end
65
+
66
+ return @locked = (result['inserted'] + result['replaced']) == 1
67
+ end
68
+
69
+ def unlock
70
+ raise "Lock instance `#{key}' not locked" unless @locked
71
+
72
+ result = NoBrainer.run do |r|
73
+ selector.replace do |doc|
74
+ r.branch(doc[:token].eq(self.token),
75
+ nil, doc)
76
+ end
77
+ end
78
+
79
+ @locked = false
80
+ raise NoBrainer::Error::LostLock.new("Lost lock on `#{key}'") unless result['deleted'] == 1
81
+ end
82
+
83
+ def refresh(options={})
84
+ options.assert_valid_keys(:expire)
85
+ raise "Lock instance `#{key}' not locked" unless @locked
86
+
87
+ set_expiration(options)
88
+
89
+ result = NoBrainer.run do |r|
90
+ selector.update do |doc|
91
+ r.branch(doc[:token].eq(self.token),
92
+ { :expires_at => self.expires_at }, nil)
93
+ end
94
+ end
95
+
96
+ # Note: If we are too quick, expires_at may not change, and the returned
97
+ # 'replaced' won't be 1. We'll generate a spurious error. This is very
98
+ # unlikely to happen and should not harmful.
99
+ unless result['replaced'] == 1
100
+ @locked = false
101
+ raise NoBrainer::Error::LostLock.new("Lost lock on `#{key}'")
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def set_expiration(options)
108
+ expire = NoBrainer::Config.lock_options.merge(options)[:expire]
109
+ self.expires_at = RethinkDB::RQL.new.now + expire
110
+ end
111
+
112
+ def save?; raise; end
113
+ def delete; raise; end
114
+ end
@@ -10,7 +10,6 @@ class NoBrainer::QueryRunner::DatabaseOnDemand < NoBrainer::QueryRunner::Middlew
10
10
  end
11
11
 
12
12
  def handle_database_on_demand_exception?(env, e)
13
- (NoBrainer::Config.auto_create_databases || env[:auto_create_databases]) &&
14
13
  e.message =~ /^Database `(.+)` does not exist\.$/ && $1
15
14
  end
16
15
 
@@ -11,7 +11,7 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
11
11
  index = model.indexes.values.select { |i| i.aliased_name == index_name.to_sym }.first if model
12
12
  index_name = index.name if index
13
13
 
14
- if model.try(:pk_name).try(:to_s) == index_name
14
+ if model.try(:pk_name).try(:to_s) == index_name.to_s
15
15
  err_msg = "Please update the primary key `#{index_name}` in the table `#{database_name}.#{table_name}`."
16
16
  else
17
17
  err_msg = "Please run `NoBrainer.sync_indexes' or `rake nobrainer:sync_indexes' to create the index `#{index_name}`"
@@ -30,9 +30,6 @@ class NoBrainer::QueryRunner::RunOptions < NoBrainer::QueryRunner::Middleware
30
30
 
31
31
  env[:criteria] = env[:options].delete(:criteria)
32
32
 
33
- env[:auto_create_tables] = env[:options].delete(:auto_create_tables)
34
- env[:auto_create_databases] = env[:options].delete(:auto_create_databases)
35
-
36
33
  @runner.call(env)
37
34
  end
38
35
  end
@@ -10,15 +10,14 @@ class NoBrainer::QueryRunner::TableOnDemand < NoBrainer::QueryRunner::Middleware
10
10
  end
11
11
 
12
12
  def handle_table_on_demand_exception?(env, e)
13
- (NoBrainer::Config.auto_create_tables || env[:auto_create_tables]) &&
14
13
  e.message =~ /^Table `(.+)\.(.+)` does not exist\.$/ && [$1, $2]
15
14
  end
16
15
 
17
16
  private
18
17
 
19
18
  def auto_create_table(env, database_name, table_name)
20
- model = NoBrainer::Document::Index::MetaStore if table_name == 'nobrainer_index_meta'
21
- model ||= NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
19
+ model ||= NoBrainer::Document::Core._all.select { |m| m.table_name == table_name }.first
20
+ model ||= NoBrainer::Document::Core._all_nobrainer.select { |m| m.table_name == table_name }.first
22
21
 
23
22
  if model.nil?
24
23
  raise "Auto table creation is not working for `#{database_name}.#{table_name}` -- Can't find the corresponding model."
@@ -27,8 +27,4 @@ module NoBrainer::RQL
27
27
  :read
28
28
  end
29
29
  end
30
-
31
- def is_table?(rql)
32
- rql.body.first == TABLE
33
- end
34
30
  end
data/lib/nobrainer.rb CHANGED
@@ -13,7 +13,7 @@ module NoBrainer
13
13
 
14
14
  # We eager load things that could be loaded when handling the first web request.
15
15
  # Code that is loaded through the DSL of NoBrainer should not be eager loaded.
16
- autoload :Document, :IndexManager, :Loader, :Fork, :Geo
16
+ autoload :Document, :IndexManager, :Loader, :Fork, :Geo, :Lock
17
17
  eager_autoload :Config, :Connection, :ConnectionManager, :Error,
18
18
  :QueryRunner, :Criteria, :RQL
19
19
 
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.20.0
4
+ version: 0.21.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-12-03 00:00:00.000000000 Z
11
+ date: 2015-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -100,6 +100,7 @@ files:
100
100
  - lib/no_brainer/criteria/core.rb
101
101
  - lib/no_brainer/criteria/count.rb
102
102
  - lib/no_brainer/criteria/delete.rb
103
+ - lib/no_brainer/criteria/eager_load.rb
103
104
  - lib/no_brainer/criteria/enumerable.rb
104
105
  - lib/no_brainer/criteria/extend.rb
105
106
  - lib/no_brainer/criteria/first.rb
@@ -107,7 +108,6 @@ files:
107
108
  - lib/no_brainer/criteria/limit.rb
108
109
  - lib/no_brainer/criteria/order_by.rb
109
110
  - lib/no_brainer/criteria/pluck.rb
110
- - lib/no_brainer/criteria/preload.rb
111
111
  - lib/no_brainer/criteria/raw.rb
112
112
  - lib/no_brainer/criteria/scope.rb
113
113
  - lib/no_brainer/criteria/update.rb
@@ -149,13 +149,16 @@ files:
149
149
  - lib/no_brainer/document/types/boolean.rb
150
150
  - lib/no_brainer/document/types/date.rb
151
151
  - lib/no_brainer/document/types/float.rb
152
+ - lib/no_brainer/document/types/geo.rb
152
153
  - lib/no_brainer/document/types/integer.rb
153
154
  - lib/no_brainer/document/types/set.rb
154
155
  - lib/no_brainer/document/types/string.rb
155
156
  - lib/no_brainer/document/types/symbol.rb
157
+ - lib/no_brainer/document/types/text.rb
156
158
  - lib/no_brainer/document/types/time.rb
157
- - lib/no_brainer/document/uniqueness.rb
158
159
  - lib/no_brainer/document/validation.rb
160
+ - lib/no_brainer/document/validation/not_null.rb
161
+ - lib/no_brainer/document/validation/uniqueness.rb
159
162
  - lib/no_brainer/error.rb
160
163
  - lib/no_brainer/fork.rb
161
164
  - lib/no_brainer/geo.rb
@@ -166,6 +169,7 @@ files:
166
169
  - lib/no_brainer/geo/polygon.rb
167
170
  - lib/no_brainer/loader.rb
168
171
  - lib/no_brainer/locale/en.yml
172
+ - lib/no_brainer/lock.rb
169
173
  - lib/no_brainer/query_runner.rb
170
174
  - lib/no_brainer/query_runner/connection_lock.rb
171
175
  - lib/no_brainer/query_runner/database_on_demand.rb
@@ -1,44 +0,0 @@
1
- module NoBrainer::Criteria::Preload
2
- extend ActiveSupport::Concern
3
-
4
- included { criteria_option :preload, :merge_with => :append_array }
5
-
6
- def preload(*values)
7
- chain({:preload => values}, :copy_cache_from => self)
8
- end
9
-
10
- def merge!(criteria, options={})
11
- super.tap do
12
- # XXX Not pretty hack
13
- if criteria.options[:preload].present? && criteria.cached?
14
- perform_preloads(@cache)
15
- end
16
- end
17
- end
18
-
19
- def each(options={}, &block)
20
- return super unless should_preloads? && !options[:no_preloading] && block
21
-
22
- docs = []
23
- super(options.merge(:no_preloading => true)) { |doc| docs << doc }
24
- perform_preloads(docs)
25
- docs.each(&block)
26
- self
27
- end
28
-
29
- private
30
-
31
- def should_preloads?
32
- @options[:preload].present? && !raw?
33
- end
34
-
35
- def get_one(criteria)
36
- super.tap { |doc| perform_preloads([doc]) }
37
- end
38
-
39
- def perform_preloads(docs)
40
- if should_preloads? && docs.present?
41
- NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, @options[:preload])
42
- end
43
- end
44
- end