nobrainer 0.20.0 → 0.22.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/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/find.rb +27 -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/criteria.rb +3 -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 -17
- data/lib/no_brainer/document/dirty.rb +11 -16
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/index.rb +0 -6
- data/lib/no_brainer/document/persistance.rb +13 -3
- data/lib/no_brainer/document/store_in.rb +2 -2
- data/lib/no_brainer/document/types/binary.rb +2 -6
- data/lib/no_brainer/document/types/boolean.rb +2 -3
- data/lib/no_brainer/document/types/date.rb +2 -2
- data/lib/no_brainer/document/types/float.rb +2 -2
- data/lib/no_brainer/document/types/geo.rb +1 -0
- data/lib/no_brainer/document/types/integer.rb +2 -2
- data/lib/no_brainer/document/types/set.rb +2 -2
- data/lib/no_brainer/document/types/string.rb +5 -2
- data/lib/no_brainer/document/types/symbol.rb +2 -2
- data/lib/no_brainer/document/types/text.rb +18 -0
- data/lib/no_brainer/document/types/time.rb +2 -2
- data/lib/no_brainer/document/types.rb +13 -12
- 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/document/validation.rb +35 -6
- data/lib/no_brainer/document.rb +2 -2
- 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 +9 -4
- data/lib/no_brainer/criteria/preload.rb +0 -44
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Symbol
|
|
2
|
-
module
|
|
2
|
+
module NoBrainerExtensions
|
|
3
3
|
InvalidType = NoBrainer::Error::InvalidType
|
|
4
4
|
|
|
5
5
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -17,5 +17,5 @@ class Symbol
|
|
|
17
17
|
value.to_sym rescue (value.to_s.to_sym rescue value)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
-
extend
|
|
20
|
+
extend NoBrainerExtensions
|
|
21
21
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class NoBrainer::Text
|
|
2
|
+
def initialize; raise; end
|
|
3
|
+
def self.inspect; 'Text'; end
|
|
4
|
+
def self.to_s; inspect; end
|
|
5
|
+
def self.name; inspect; end
|
|
6
|
+
|
|
7
|
+
module NoBrainerExtensions
|
|
8
|
+
InvalidType = NoBrainer::Error::InvalidType
|
|
9
|
+
|
|
10
|
+
def nobrainer_cast_user_to_model(value)
|
|
11
|
+
case value
|
|
12
|
+
when String then value
|
|
13
|
+
else raise InvalidType
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
extend NoBrainerExtensions
|
|
18
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'time'
|
|
2
2
|
|
|
3
3
|
class Time
|
|
4
|
-
module
|
|
4
|
+
module NoBrainerExtensions
|
|
5
5
|
InvalidType = NoBrainer::Error::InvalidType
|
|
6
6
|
|
|
7
7
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -37,5 +37,5 @@ class Time
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
-
extend
|
|
40
|
+
extend NoBrainerExtensions
|
|
41
41
|
end
|
|
@@ -6,7 +6,7 @@ module NoBrainer::Document::Types
|
|
|
6
6
|
def add_type_errors
|
|
7
7
|
return unless @pending_type_errors
|
|
8
8
|
@pending_type_errors.each do |name, error|
|
|
9
|
-
errors.add(name, :invalid_type,
|
|
9
|
+
errors.add(name, :invalid_type, error.error)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -22,7 +22,10 @@ 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? ||
|
|
26
|
+
value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) ||
|
|
27
|
+
value.is_a?(RethinkDB::RQL)
|
|
28
|
+
|
|
26
29
|
if type.respond_to?(:nobrainer_cast_user_to_model)
|
|
27
30
|
type.nobrainer_cast_user_to_model(value)
|
|
28
31
|
else
|
|
@@ -30,9 +33,7 @@ module NoBrainer::Document::Types
|
|
|
30
33
|
value
|
|
31
34
|
end
|
|
32
35
|
rescue NoBrainer::Error::InvalidType => error
|
|
33
|
-
error.type
|
|
34
|
-
error.value = value
|
|
35
|
-
error.attr_name = attr
|
|
36
|
+
error.update(:model => self, :value => value, :attr_name => attr, :type => type)
|
|
36
37
|
raise error
|
|
37
38
|
end
|
|
38
39
|
|
|
@@ -62,14 +63,15 @@ module NoBrainer::Document::Types
|
|
|
62
63
|
|
|
63
64
|
return unless options[:type]
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
raise "Please use a class for the type option" unless options[:type].is_a?(Class)
|
|
67
67
|
case options[:type].to_s
|
|
68
68
|
when "NoBrainer::Geo::Circle" then raise "Cannot store circles :("
|
|
69
69
|
when "NoBrainer::Geo::Polygon", "NoBrainer::Geo::LineString"
|
|
70
70
|
raise "Make a request on github if you'd like to store polygons/linestrings"
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
|
|
74
|
+
|
|
73
75
|
inject_in_layer :types do
|
|
74
76
|
define_method("#{attr}=") do |value|
|
|
75
77
|
begin
|
|
@@ -96,11 +98,10 @@ module NoBrainer::Document::Types
|
|
|
96
98
|
end
|
|
97
99
|
end
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Geo = NoBrainer::Geo
|
|
101
|
+
%w(binary boolean text geo).each do |type|
|
|
102
|
+
require File.join(File.dirname(__FILE__), 'types', type)
|
|
103
|
+
const_set(type.camelize, NoBrainer.const_get(type.camelize))
|
|
104
|
+
end
|
|
104
105
|
|
|
105
106
|
class << self
|
|
106
107
|
mattr_accessor :loaded_extensions
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module NoBrainer::Document::Validation::NotNull
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def validates_not_null(*attr_names)
|
|
6
|
+
validates_with(NotNullValidator, _merge_attributes(attr_names))
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class NotNullValidator < ActiveModel::EachValidator
|
|
11
|
+
def validate_each(doc, attr, value)
|
|
12
|
+
doc.errors.add(attr, :undefined, options) if value.nil?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module NoBrainer::Document::Uniqueness
|
|
1
|
+
module NoBrainer::Document::Validation::Uniqueness
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
def _create(options={})
|
|
@@ -27,7 +27,7 @@ module NoBrainer::Document::Uniqueness
|
|
|
27
27
|
self.class.unique_validators
|
|
28
28
|
.map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
|
29
29
|
.flatten(1)
|
|
30
|
-
.select { |f, validator| validator.
|
|
30
|
+
.select { |f, validator| validator.should_validate_field?(self, f) }
|
|
31
31
|
.map { |f, options| _lock_key_from_field(f) }
|
|
32
32
|
.sort
|
|
33
33
|
.uniq
|
|
@@ -51,7 +51,7 @@ module NoBrainer::Document::Uniqueness
|
|
|
51
51
|
|
|
52
52
|
module ClassMethods
|
|
53
53
|
def validates_uniqueness_of(*attr_names)
|
|
54
|
-
validates_with
|
|
54
|
+
validates_with(UniquenessValidator, _merge_attributes(attr_names))
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def inherited(subclass)
|
|
@@ -72,19 +72,20 @@ module NoBrainer::Document::Uniqueness
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
def
|
|
76
|
-
(scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
|
|
75
|
+
def should_validate_field?(doc, field)
|
|
76
|
+
doc.new_record? || (scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def validate_each(doc, attr, value)
|
|
80
|
-
return true unless should_validate_uniquess_of?(doc, attr)
|
|
81
|
-
|
|
82
80
|
criteria = doc.root_class.unscoped.where(attr => value)
|
|
83
81
|
criteria = apply_scopes(criteria, doc)
|
|
84
82
|
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
|
|
84
|
+
rescue NoBrainer::Error::InvalidType
|
|
85
|
+
# We can't run the uniqueness validator: where() won't accept bad types
|
|
86
|
+
# and we have some values that don't have the right type.
|
|
87
|
+
# Note that it's fine to not add errors because the type validations will
|
|
88
|
+
# prevent the document from being saved.
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
def apply_scopes(criteria, doc)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
module NoBrainer::Document::Validation
|
|
2
|
+
extend NoBrainer::Autoload
|
|
2
3
|
extend ActiveSupport::Concern
|
|
3
4
|
include ActiveModel::Validations
|
|
4
5
|
include ActiveModel::Validations::Callbacks
|
|
5
6
|
|
|
7
|
+
autoload_and_include :Uniqueness, :NotNull
|
|
8
|
+
|
|
6
9
|
included do
|
|
7
10
|
# We don't want before_validation returning false to halt the chain.
|
|
8
11
|
define_callbacks :validation, :skip_after_callbacks_if_terminated => true,
|
|
@@ -12,7 +15,7 @@ module NoBrainer::Document::Validation
|
|
|
12
15
|
def valid?(context=nil, options={})
|
|
13
16
|
context ||= new_record? ? :create : :update
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
# XXX Monkey Patching, because we need to have control on errors.clear
|
|
16
19
|
current_context, self.validation_context = validation_context, context
|
|
17
20
|
errors.clear unless options[:clear_errors] == false
|
|
18
21
|
run_validations!
|
|
@@ -20,15 +23,41 @@ module NoBrainer::Document::Validation
|
|
|
20
23
|
self.validation_context = current_context
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
SHORTHANDS = { :format => :format, :length => :length, :required => :presence,
|
|
27
|
+
:uniq => :uniqueness, :unique => :uniqueness, :in => :inclusion }
|
|
28
|
+
|
|
23
29
|
module ClassMethods
|
|
24
30
|
def _field(attr, options={})
|
|
25
31
|
super
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
validates(attr,
|
|
30
|
-
|
|
32
|
+
|
|
33
|
+
shorthands = SHORTHANDS
|
|
34
|
+
shorthands = shorthands.merge(:required => :not_null) if options[:type] == NoBrainer::Boolean
|
|
35
|
+
shorthands.each { |k,v| validates(attr, v => options[k]) if options.has_key?(k) }
|
|
36
|
+
|
|
31
37
|
validates(attr, options[:validates]) if options[:validates]
|
|
38
|
+
validates(attr, :length => { :minimum => options[:min_length] }) if options[:min_length]
|
|
39
|
+
validates(attr, :length => { :maximum => options[:max_length] }) if options[:max_length]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class ActiveModel::EachValidator
|
|
45
|
+
def should_validate_field?(record, attribute)
|
|
46
|
+
return true unless record.is_a?(NoBrainer::Document)
|
|
47
|
+
return true if record.new_record?
|
|
48
|
+
|
|
49
|
+
attr_changed = "#{attribute}_changed?"
|
|
50
|
+
return record.respond_to?(attr_changed) ? record.__send__(attr_changed) : true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# XXX Monkey Patching :(
|
|
54
|
+
def validate(record)
|
|
55
|
+
attributes.each do |attribute|
|
|
56
|
+
next unless should_validate_field?(record, attribute) # <--- Added
|
|
57
|
+
value = record.read_attribute_for_validation(attribute)
|
|
58
|
+
next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- Added
|
|
59
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
|
60
|
+
validate_each(record, attribute, value)
|
|
32
61
|
end
|
|
33
62
|
end
|
|
34
63
|
end
|
data/lib/no_brainer/document.rb
CHANGED
|
@@ -4,8 +4,8 @@ module NoBrainer::Document
|
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
extend NoBrainer::Autoload
|
|
6
6
|
|
|
7
|
-
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
|
|
8
|
-
:Persistance, :
|
|
7
|
+
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
|
|
8
|
+
:Persistance, :Validation, :Types, :Callbacks, :Dirty, :PrimaryKey,
|
|
9
9
|
:Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
|
|
10
10
|
:MissingAttributes, :LazyFetch, :AtomicOps
|
|
11
11
|
|
data/lib/no_brainer/error.rb
CHANGED
|
@@ -5,25 +5,13 @@ module NoBrainer::Error
|
|
|
5
5
|
class ChildrenExist < RuntimeError; end
|
|
6
6
|
class CannotUseIndex < RuntimeError; end
|
|
7
7
|
class MissingIndex < RuntimeError; end
|
|
8
|
-
class InvalidType < RuntimeError; end
|
|
9
8
|
class AssociationNotPersisted < RuntimeError; end
|
|
10
9
|
class ReadonlyField < RuntimeError; end
|
|
11
10
|
class MissingAttribute < RuntimeError; end
|
|
12
11
|
class UnknownAttribute < RuntimeError; end
|
|
13
12
|
class AtomicBlock < RuntimeError; end
|
|
14
|
-
|
|
15
|
-
class
|
|
16
|
-
attr_accessor :instance, :field, :value
|
|
17
|
-
def initialize(instance, field, value)
|
|
18
|
-
@instance = instance
|
|
19
|
-
@field = field
|
|
20
|
-
@value = value
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def message
|
|
24
|
-
"Cannot read #{field}, atomic operations are pending"
|
|
25
|
-
end
|
|
26
|
-
end
|
|
13
|
+
class LostLock < RuntimeError; end
|
|
14
|
+
class LockUnavailable < RuntimeError; end
|
|
27
15
|
|
|
28
16
|
class DocumentInvalid < RuntimeError
|
|
29
17
|
attr_accessor :instance
|
|
@@ -37,23 +25,32 @@ module NoBrainer::Error
|
|
|
37
25
|
end
|
|
38
26
|
|
|
39
27
|
class InvalidType < RuntimeError
|
|
40
|
-
attr_accessor :attr_name, :value, :type
|
|
28
|
+
attr_accessor :model, :attr_name, :value, :type, :error
|
|
41
29
|
def initialize(options={})
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
update(options)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update(options={})
|
|
34
|
+
options.assert_valid_keys(:model, :attr_name, :type, :value, :error)
|
|
35
|
+
options.each { |k,v| instance_variable_set("@#{k}", v) }
|
|
45
36
|
end
|
|
46
37
|
|
|
47
38
|
def human_type_name
|
|
48
39
|
type.to_s.underscore.humanize.downcase
|
|
49
40
|
end
|
|
50
41
|
|
|
42
|
+
def error
|
|
43
|
+
# dup because errors.add eventually .delete() on our option.
|
|
44
|
+
@error.nil? ? (type && { :type => human_type_name }) : @error.dup
|
|
45
|
+
end
|
|
46
|
+
|
|
51
47
|
def message
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
return super unless model && attr_name && error
|
|
49
|
+
value = self.value
|
|
50
|
+
mock = model.allocate
|
|
51
|
+
mock.singleton_class.send(:define_method, :read_attribute_for_validation) { |_| value }
|
|
52
|
+
mock.errors.add(attr_name, :invalid_type, error)
|
|
53
|
+
mock.errors.full_messages.first
|
|
57
54
|
end
|
|
58
55
|
end
|
|
59
56
|
|
|
@@ -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.22.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-03-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rethinkdb
|
|
@@ -100,14 +100,15 @@ 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
|
|
106
|
+
- lib/no_brainer/criteria/find.rb
|
|
105
107
|
- lib/no_brainer/criteria/first.rb
|
|
106
108
|
- lib/no_brainer/criteria/index.rb
|
|
107
109
|
- lib/no_brainer/criteria/limit.rb
|
|
108
110
|
- lib/no_brainer/criteria/order_by.rb
|
|
109
111
|
- lib/no_brainer/criteria/pluck.rb
|
|
110
|
-
- lib/no_brainer/criteria/preload.rb
|
|
111
112
|
- lib/no_brainer/criteria/raw.rb
|
|
112
113
|
- lib/no_brainer/criteria/scope.rb
|
|
113
114
|
- lib/no_brainer/criteria/update.rb
|
|
@@ -149,13 +150,16 @@ files:
|
|
|
149
150
|
- lib/no_brainer/document/types/boolean.rb
|
|
150
151
|
- lib/no_brainer/document/types/date.rb
|
|
151
152
|
- lib/no_brainer/document/types/float.rb
|
|
153
|
+
- lib/no_brainer/document/types/geo.rb
|
|
152
154
|
- lib/no_brainer/document/types/integer.rb
|
|
153
155
|
- lib/no_brainer/document/types/set.rb
|
|
154
156
|
- lib/no_brainer/document/types/string.rb
|
|
155
157
|
- lib/no_brainer/document/types/symbol.rb
|
|
158
|
+
- lib/no_brainer/document/types/text.rb
|
|
156
159
|
- lib/no_brainer/document/types/time.rb
|
|
157
|
-
- lib/no_brainer/document/uniqueness.rb
|
|
158
160
|
- lib/no_brainer/document/validation.rb
|
|
161
|
+
- lib/no_brainer/document/validation/not_null.rb
|
|
162
|
+
- lib/no_brainer/document/validation/uniqueness.rb
|
|
159
163
|
- lib/no_brainer/error.rb
|
|
160
164
|
- lib/no_brainer/fork.rb
|
|
161
165
|
- lib/no_brainer/geo.rb
|
|
@@ -166,6 +170,7 @@ files:
|
|
|
166
170
|
- lib/no_brainer/geo/polygon.rb
|
|
167
171
|
- lib/no_brainer/loader.rb
|
|
168
172
|
- lib/no_brainer/locale/en.yml
|
|
173
|
+
- lib/no_brainer/lock.rb
|
|
169
174
|
- lib/no_brainer/query_runner.rb
|
|
170
175
|
- lib/no_brainer/query_runner/connection_lock.rb
|
|
171
176
|
- 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
|