nobrainer 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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