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.
- 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
|