nobrainer 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/no_brainer/autoload.rb +0 -5
- data/lib/no_brainer/config.rb +14 -9
- data/lib/no_brainer/connection.rb +1 -3
- data/lib/no_brainer/criteria.rb +1 -1
- data/lib/no_brainer/criteria/aggregate.rb +2 -2
- data/lib/no_brainer/criteria/cache.rb +8 -3
- data/lib/no_brainer/criteria/core.rb +1 -5
- data/lib/no_brainer/criteria/delete.rb +1 -1
- data/lib/no_brainer/criteria/eager_load.rb +51 -0
- data/lib/no_brainer/criteria/order_by.rb +1 -1
- data/lib/no_brainer/criteria/scope.rb +3 -10
- data/lib/no_brainer/criteria/update.rb +8 -6
- data/lib/no_brainer/criteria/where.rb +50 -13
- data/lib/no_brainer/document.rb +2 -2
- data/lib/no_brainer/document/aliases.rb +0 -8
- data/lib/no_brainer/document/association/belongs_to.rb +6 -2
- data/lib/no_brainer/document/association/core.rb +5 -4
- data/lib/no_brainer/document/association/eager_loader.rb +7 -8
- data/lib/no_brainer/document/association/has_many.rb +22 -8
- data/lib/no_brainer/document/association/has_many_through.rb +12 -3
- data/lib/no_brainer/document/atomic_ops.rb +63 -61
- data/lib/no_brainer/document/attributes.rb +11 -3
- data/lib/no_brainer/document/core.rb +5 -2
- data/lib/no_brainer/document/criteria.rb +14 -5
- data/lib/no_brainer/document/dirty.rb +11 -16
- data/lib/no_brainer/document/index.rb +0 -6
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/persistance.rb +12 -2
- data/lib/no_brainer/document/types.rb +13 -12
- data/lib/no_brainer/document/types/binary.rb +0 -4
- data/lib/no_brainer/document/types/boolean.rb +0 -1
- data/lib/no_brainer/document/types/geo.rb +1 -0
- data/lib/no_brainer/document/types/string.rb +3 -0
- data/lib/no_brainer/document/types/text.rb +18 -0
- data/lib/no_brainer/document/validation.rb +31 -6
- data/lib/no_brainer/document/validation/not_null.rb +15 -0
- data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -10
- data/lib/no_brainer/error.rb +20 -23
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +114 -0
- data/lib/no_brainer/query_runner/database_on_demand.rb +0 -1
- data/lib/no_brainer/query_runner/missing_index.rb +1 -1
- data/lib/no_brainer/query_runner/run_options.rb +0 -3
- data/lib/no_brainer/query_runner/table_on_demand.rb +2 -3
- data/lib/no_brainer/rql.rb +0 -4
- data/lib/nobrainer.rb +1 -1
- metadata +8 -4
- data/lib/no_brainer/criteria/preload.rb +0 -44
@@ -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
|
21
|
-
model ||= NoBrainer::Document.
|
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."
|
data/lib/no_brainer/rql.rb
CHANGED
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.
|
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:
|
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
|