nobrainer 0.13.0 → 0.13.1
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/config.rb +3 -1
- data/lib/no_brainer/connection.rb +14 -12
- data/lib/no_brainer/criteria/core.rb +1 -6
- data/lib/no_brainer/criteria/preload.rb +0 -5
- data/lib/no_brainer/criteria/where.rb +34 -7
- data/lib/no_brainer/decorated_symbol.rb +2 -1
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/document/association/belongs_to.rb +9 -6
- data/lib/no_brainer/document/attributes.rb +3 -2
- data/lib/no_brainer/document/callbacks.rb +11 -2
- data/lib/no_brainer/document/core.rb +10 -3
- data/lib/no_brainer/document/dirty.rb +0 -5
- data/lib/no_brainer/document/index.rb +6 -1
- data/lib/no_brainer/document/persistance.rb +0 -5
- data/lib/no_brainer/document/types.rb +54 -13
- data/lib/no_brainer/document/uniqueness.rb +97 -0
- data/lib/no_brainer/document/validation.rb +8 -24
- data/lib/no_brainer/error.rb +1 -1
- data/lib/no_brainer/index_manager.rb +4 -2
- data/lib/no_brainer/loader.rb +1 -1
- data/lib/no_brainer/query_runner.rb +2 -2
- data/lib/no_brainer/query_runner/logger.rb +20 -14
- data/lib/no_brainer/query_runner/{connection.rb → reconnect.rb} +1 -1
- metadata +65 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5ca498dfa914c0c0178f339485d78ba804c5b95
|
4
|
+
data.tar.gz: c0e0c6f35d8927bb6281f860aac769753df59b88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed914e1eee020658197d596245404a2a088acbf033f5fc3a44cb490620cdc5ef110d62e4da927e7579e5e3383bc90e16dd499269a9bd8952dfa2011d3e2f2a12
|
7
|
+
data.tar.gz: f3204081b4ef7617706ee3c037830bdacbde973ae79e914b782b39fb41f1ec36b0125f78b427d0f21078bdae543ee426f2936ba69a1327bc36176acc6997765e
|
data/lib/no_brainer/config.rb
CHANGED
@@ -4,7 +4,8 @@ module NoBrainer::Config
|
|
4
4
|
class << self
|
5
5
|
mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
|
6
6
|
:auto_create_databases, :auto_create_tables,
|
7
|
-
:max_reconnection_tries, :durability, :colorize_logger
|
7
|
+
:max_reconnection_tries, :durability, :colorize_logger,
|
8
|
+
:distributed_lock_class
|
8
9
|
|
9
10
|
def apply_defaults
|
10
11
|
self.rethinkdb_url = default_rethinkdb_url
|
@@ -15,6 +16,7 @@ module NoBrainer::Config
|
|
15
16
|
self.max_reconnection_tries = 10
|
16
17
|
self.durability = default_durability
|
17
18
|
self.colorize_logger = true
|
19
|
+
self.distributed_lock_class = nil
|
18
20
|
end
|
19
21
|
|
20
22
|
def reset!
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rethinkdb'
|
2
2
|
|
3
3
|
class NoBrainer::Connection
|
4
|
-
attr_accessor :uri
|
4
|
+
attr_accessor :uri, :parsed_uri
|
5
5
|
|
6
6
|
def initialize(uri)
|
7
7
|
self.uri = uri
|
@@ -9,19 +9,21 @@ class NoBrainer::Connection
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def parsed_uri
|
12
|
-
|
13
|
-
|
12
|
+
@parsed_uri ||= begin
|
13
|
+
require 'uri'
|
14
|
+
uri = URI.parse(self.uri)
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
if uri.scheme != 'rethinkdb'
|
17
|
+
raise NoBrainer::Error::Connection,
|
18
|
+
"Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
{ :auth_key => uri.password,
|
22
|
+
:host => uri.host,
|
23
|
+
:port => uri.port || 28015,
|
24
|
+
:db => uri.path.gsub(/^\//, ''),
|
25
|
+
}.tap { |result| raise "No database specified in #{uri}" unless result[:db].present? }
|
26
|
+
end
|
25
27
|
end
|
26
28
|
|
27
29
|
def raw
|
@@ -17,12 +17,7 @@ module NoBrainer::Criteria::Core
|
|
17
17
|
|
18
18
|
def inspect
|
19
19
|
# rescue super because sometimes klass is not set.
|
20
|
-
|
21
|
-
if str =~ /Erroneous_Portion_Constructed/
|
22
|
-
# Need to fix the rethinkdb gem.
|
23
|
-
str = "the rethinkdb gem is flipping out with Erroneous_Portion_Constructed"
|
24
|
-
end
|
25
|
-
str
|
20
|
+
to_rql.inspect rescue super
|
26
21
|
end
|
27
22
|
|
28
23
|
def run(rql=nil)
|
@@ -12,11 +12,6 @@ module NoBrainer::Criteria::Preload
|
|
12
12
|
chain(:keep_cache => true) { |criteria| criteria._preloads = values }
|
13
13
|
end
|
14
14
|
|
15
|
-
def includes(*values)
|
16
|
-
NoBrainer.logger.warn "[NoBrainer] includes() is deprecated and will be removed, use preload() instead."
|
17
|
-
preload(*values)
|
18
|
-
end
|
19
|
-
|
20
15
|
def merge!(criteria, options={})
|
21
16
|
super
|
22
17
|
self._preloads = self._preloads + criteria._preloads
|
@@ -56,14 +56,15 @@ module NoBrainer::Criteria::Where
|
|
56
56
|
|
57
57
|
class BinaryOperator < Struct.new(:key, :op, :value, :criteria)
|
58
58
|
def simplify
|
59
|
+
key = cast_key(self.key)
|
59
60
|
case op
|
60
61
|
when :in then
|
61
62
|
case value
|
62
|
-
when Range then BinaryOperator.new(key, :between, (
|
63
|
-
when Array then BinaryOperator.new(key, :in, value.map(&method(:
|
63
|
+
when Range then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria)
|
64
|
+
when Array then BinaryOperator.new(key, :in, value.map(&method(:cast_value)).uniq, criteria)
|
64
65
|
else raise ArgumentError.new ":in takes an array/range, not #{value}"
|
65
66
|
end
|
66
|
-
else BinaryOperator.new(key, op,
|
67
|
+
else BinaryOperator.new(key, op, cast_value(value), criteria)
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
@@ -77,8 +78,33 @@ module NoBrainer::Criteria::Where
|
|
77
78
|
|
78
79
|
private
|
79
80
|
|
80
|
-
def
|
81
|
-
|
81
|
+
def association
|
82
|
+
# FIXME This leaks memory with dynamic attributes. The internals of type
|
83
|
+
# checking will convert the key to a symbol, and Ruby does not garbage
|
84
|
+
# collect symbols.
|
85
|
+
@association ||= [criteria.klass.association_metadata[key.to_sym]]
|
86
|
+
@association.first
|
87
|
+
end
|
88
|
+
|
89
|
+
def cast_value(value)
|
90
|
+
case association
|
91
|
+
when NoBrainer::Document::Association::BelongsTo::Metadata
|
92
|
+
target_klass = association.target_klass
|
93
|
+
opts = { :attr_name => key, :value => value, :type => target_klass}
|
94
|
+
raise NoBrainer::Error::InvalidType.new(opts) unless value.is_a?(target_klass)
|
95
|
+
value.id
|
96
|
+
else
|
97
|
+
criteria.klass.cast_user_to_db_for(key, value)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def cast_key(key)
|
102
|
+
case association
|
103
|
+
when NoBrainer::Document::Association::BelongsTo::Metadata
|
104
|
+
association.foreign_key
|
105
|
+
else
|
106
|
+
key
|
107
|
+
end
|
82
108
|
end
|
83
109
|
end
|
84
110
|
|
@@ -126,8 +152,9 @@ module NoBrainer::Criteria::Where
|
|
126
152
|
when String, Symbol then parse_clause_stub_eq(key, value)
|
127
153
|
when NoBrainer::DecoratedSymbol then
|
128
154
|
case key.modifier
|
129
|
-
when :
|
130
|
-
when :
|
155
|
+
when :nin then parse_clause(:not => { key.symbol.in => value })
|
156
|
+
when :ne then parse_clause(:not => { key.symbol.eq => value })
|
157
|
+
when :eq then parse_clause_stub_eq(key.symbol, value)
|
131
158
|
else BinaryOperator.new(key.symbol, key.modifier, value, self)
|
132
159
|
end
|
133
160
|
else raise "Invalid key: #{key}"
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class NoBrainer::DecoratedSymbol < Struct.new(:symbol, :modifier, :args)
|
2
|
-
MODIFIERS = { :
|
2
|
+
MODIFIERS = { :in => :in, :nin => :nin,
|
3
|
+
:eq => :eq, :ne => :ne, :not => :ne,
|
3
4
|
:gt => :gt, :ge => :ge, :gte => :ge,
|
4
5
|
:lt => :lt, :le => :le, :lte => :le}
|
5
6
|
|
data/lib/no_brainer/document.rb
CHANGED
@@ -5,7 +5,7 @@ module NoBrainer::Document
|
|
5
5
|
extend NoBrainer::Autoload
|
6
6
|
|
7
7
|
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly, :Validation, :Types,
|
8
|
-
:Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
8
|
+
:Persistance, :Uniqueness, :Callbacks, :Dirty, :Id, :Association, :Serialization,
|
9
9
|
:Criteria, :Polymorphic, :Index
|
10
10
|
|
11
11
|
autoload :DynamicAttributes, :Timestamps
|
@@ -30,7 +30,6 @@ class NoBrainer::Document::Association::BelongsTo
|
|
30
30
|
|
31
31
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
32
32
|
add_callback_for(:after_validation)
|
33
|
-
# TODO test if we are not overstepping on another foreign_key
|
34
33
|
end
|
35
34
|
|
36
35
|
eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ :id },
|
@@ -47,7 +46,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def read
|
50
|
-
return
|
49
|
+
return target if loaded?
|
51
50
|
|
52
51
|
if fk = owner.read_attribute(foreign_key)
|
53
52
|
preload(target_klass.find(fk))
|
@@ -60,8 +59,12 @@ class NoBrainer::Document::Association::BelongsTo
|
|
60
59
|
preload(target)
|
61
60
|
end
|
62
61
|
|
63
|
-
def preload(
|
64
|
-
@target_container = [*
|
62
|
+
def preload(targets)
|
63
|
+
@target_container = [*targets] # the * is for the generic eager loading code
|
64
|
+
target
|
65
|
+
end
|
66
|
+
|
67
|
+
def target
|
65
68
|
@target_container.first
|
66
69
|
end
|
67
70
|
|
@@ -70,8 +73,8 @@ class NoBrainer::Document::Association::BelongsTo
|
|
70
73
|
end
|
71
74
|
|
72
75
|
def after_validation_callback
|
73
|
-
if loaded? &&
|
74
|
-
raise NoBrainer::Error::
|
76
|
+
if loaded? && target && !target.persisted?
|
77
|
+
raise NoBrainer::Error::AssociationNotPersisted.new("#{target_name} must be saved first")
|
75
78
|
end
|
76
79
|
end
|
77
80
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module NoBrainer::Document::Attributes
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :cast_user_to_db, :cast_db_to_user, :validates, :required, :unique, :readonly]
|
3
3
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
@@ -43,13 +43,14 @@ module NoBrainer::Document::Attributes
|
|
43
43
|
@_attributes.clear if options[:pristine]
|
44
44
|
if options[:from_db]
|
45
45
|
@_attributes.merge!(attrs)
|
46
|
+
clear_dirtiness
|
46
47
|
else
|
48
|
+
clear_dirtiness if options[:pristine]
|
47
49
|
attrs.each { |k,v| self.write_attribute(k,v) }
|
48
50
|
end
|
49
51
|
assign_defaults if options[:pristine]
|
50
52
|
self
|
51
53
|
end
|
52
|
-
def attributes=(*args); assign_attributes(*args); end
|
53
54
|
|
54
55
|
def inspectable_attributes
|
55
56
|
# TODO test that thing
|
@@ -1,10 +1,19 @@
|
|
1
1
|
module NoBrainer::Document::Callbacks
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
+
def self.terminator
|
5
|
+
if Gem.loaded_specs['activesupport'].version.release >= Gem::Version.new('4.1')
|
6
|
+
proc { false }
|
7
|
+
else
|
8
|
+
'false'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
4
12
|
included do
|
5
13
|
extend ActiveModel::Callbacks
|
6
|
-
|
7
|
-
define_model_callbacks :
|
14
|
+
|
15
|
+
define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => NoBrainer::Document::Callbacks.terminator
|
16
|
+
define_model_callbacks :find, :only => [:after], :terminator => NoBrainer::Document::Callbacks.terminator
|
8
17
|
end
|
9
18
|
|
10
19
|
def initialize(*args, &block)
|
@@ -1,8 +1,15 @@
|
|
1
1
|
module NoBrainer::Document::Core
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
singleton_class.
|
5
|
-
|
4
|
+
singleton_class.class_eval do
|
5
|
+
attr_accessor :_all
|
6
|
+
|
7
|
+
def all
|
8
|
+
Rails.application.eager_load! if defined?(Rails)
|
9
|
+
@_all
|
10
|
+
end
|
11
|
+
end
|
12
|
+
self._all = []
|
6
13
|
|
7
14
|
# TODO This assume the primary key is id.
|
8
15
|
# RethinkDB can have a custom primary key. careful.
|
@@ -13,6 +20,6 @@ module NoBrainer::Document::Core
|
|
13
20
|
extend ActiveModel::Naming
|
14
21
|
extend ActiveModel::Translation
|
15
22
|
|
16
|
-
NoBrainer::Document::Core.
|
23
|
+
NoBrainer::Document::Core._all << self
|
17
24
|
end
|
18
25
|
end
|
@@ -6,11 +6,6 @@ module NoBrainer::Document::Dirty
|
|
6
6
|
# things like undefined -> nil. Going through the getters will
|
7
7
|
# not give us that.
|
8
8
|
|
9
|
-
def assign_attributes(attrs, options={})
|
10
|
-
clear_dirtiness if options[:pristine]
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
9
|
def _create(*args)
|
15
10
|
super.tap { clear_dirtiness }
|
16
11
|
end
|
@@ -68,7 +68,7 @@ module NoBrainer::Document::Index
|
|
68
68
|
end
|
69
69
|
|
70
70
|
NoBrainer.run(self.rql_table.index_create(index_name, index_args[:options], &index_proc))
|
71
|
-
|
71
|
+
wait_for_index(index_name) unless options[:wait] == false
|
72
72
|
STDERR.puts "Created index #{self}.#{index_name}" if options[:verbose]
|
73
73
|
end
|
74
74
|
|
@@ -90,5 +90,10 @@ module NoBrainer::Document::Index
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
alias_method :update_indexes, :perform_update_indexes
|
93
|
+
|
94
|
+
def wait_for_index(index_name=nil, options={})
|
95
|
+
args = [index_name].compact
|
96
|
+
NoBrainer.run(self.rql_table.index_wait(*args))
|
97
|
+
end
|
93
98
|
end
|
94
99
|
end
|
@@ -1,11 +1,6 @@
|
|
1
1
|
module NoBrainer::Document::Persistance
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
included do
|
5
|
-
extend ActiveModel::Callbacks
|
6
|
-
define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
|
7
|
-
end
|
8
|
-
|
9
4
|
def _initialize(attrs={}, options={})
|
10
5
|
@new_record = !options[:from_db]
|
11
6
|
super
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module NoBrainer::Document::Types
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
module
|
4
|
+
module CastUserToDB
|
5
5
|
extend self
|
6
6
|
InvalidType = NoBrainer::Error::InvalidType
|
7
7
|
|
@@ -60,14 +60,23 @@ module NoBrainer::Document::Types
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def lookup(type)
|
63
|
-
|
63
|
+
public_method(type.to_s)
|
64
64
|
rescue NameError
|
65
65
|
proc { raise InvalidType }
|
66
66
|
end
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
module CastDBToUser
|
70
|
+
extend self
|
71
|
+
|
72
|
+
def Symbol(value)
|
73
|
+
value.to_sym rescue value
|
74
|
+
end
|
75
|
+
|
76
|
+
def lookup(type)
|
77
|
+
public_method(type.to_s)
|
78
|
+
rescue NameError
|
79
|
+
nil
|
71
80
|
end
|
72
81
|
end
|
73
82
|
|
@@ -82,6 +91,11 @@ module NoBrainer::Document::Types
|
|
82
91
|
end
|
83
92
|
end
|
84
93
|
before_validation :add_type_errors
|
94
|
+
|
95
|
+
# Fast access for db->user cast methods for performance when reading from
|
96
|
+
# the database.
|
97
|
+
singleton_class.send(:attr_accessor, :cast_db_to_user_fields)
|
98
|
+
self.cast_db_to_user_fields = Set.new
|
85
99
|
end
|
86
100
|
|
87
101
|
def add_type_errors
|
@@ -91,12 +105,27 @@ module NoBrainer::Document::Types
|
|
91
105
|
end
|
92
106
|
end
|
93
107
|
|
108
|
+
def assign_attributes(attrs, options={})
|
109
|
+
super
|
110
|
+
if options[:from_db]
|
111
|
+
self.class.cast_db_to_user_fields.each do |attr|
|
112
|
+
field_def = self.class.fields[attr]
|
113
|
+
type = field_def[:type]
|
114
|
+
value = @_attributes[attr.to_s]
|
115
|
+
unless value.nil? || value.is_a?(type)
|
116
|
+
@_attributes[attr.to_s] = field_def[:cast_db_to_user].call(value)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
94
122
|
module ClassMethods
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
123
|
+
def cast_user_to_db_for(attr, value)
|
124
|
+
field_def = fields[attr.to_sym]
|
125
|
+
return value if !field_def
|
126
|
+
type = field_def[:type]
|
127
|
+
return value if value.nil? || type.nil? || value.is_a?(type)
|
128
|
+
field_def[:cast_user_to_db].call(value)
|
100
129
|
rescue NoBrainer::Error::InvalidType => error
|
101
130
|
error.type = field_def[:type]
|
102
131
|
error.value = value
|
@@ -104,13 +133,24 @@ module NoBrainer::Document::Types
|
|
104
133
|
raise error
|
105
134
|
end
|
106
135
|
|
136
|
+
def inherited(subclass)
|
137
|
+
super
|
138
|
+
subclass.cast_db_to_user_fields = self.cast_db_to_user_fields.dup
|
139
|
+
end
|
140
|
+
|
107
141
|
def _field(attr, options={})
|
108
142
|
super
|
109
143
|
|
144
|
+
if options[:cast_db_to_user]
|
145
|
+
([self] + descendants).each do |klass|
|
146
|
+
klass.cast_db_to_user_fields << attr
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
110
150
|
inject_in_layer :types do
|
111
151
|
define_method("#{attr}=") do |value|
|
112
152
|
begin
|
113
|
-
value = self.class.
|
153
|
+
value = self.class.cast_user_to_db_for(attr, value)
|
114
154
|
@pending_type_errors.try(:delete, attr)
|
115
155
|
rescue NoBrainer::Error::InvalidType => error
|
116
156
|
@pending_type_errors ||= {}
|
@@ -125,8 +165,9 @@ module NoBrainer::Document::Types
|
|
125
165
|
|
126
166
|
def field(attr, options={})
|
127
167
|
if options[:type]
|
128
|
-
|
129
|
-
|
168
|
+
options = options.merge(
|
169
|
+
:cast_user_to_db => NoBrainer::Document::Types::CastUserToDB.lookup(options[:type]),
|
170
|
+
:cast_db_to_user => NoBrainer::Document::Types::CastDBToUser.lookup(options[:type]))
|
130
171
|
end
|
131
172
|
super
|
132
173
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module NoBrainer::Document::Uniqueness
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def _create(options={})
|
5
|
+
lock_unique_fields
|
6
|
+
super
|
7
|
+
ensure
|
8
|
+
unlock_unique_fields
|
9
|
+
end
|
10
|
+
|
11
|
+
def _update_only_changed_attrs(options={})
|
12
|
+
lock_unique_fields
|
13
|
+
super
|
14
|
+
ensure
|
15
|
+
unlock_unique_fields
|
16
|
+
end
|
17
|
+
|
18
|
+
def _lock_key_from_field(field)
|
19
|
+
value = read_attribute(field).to_s
|
20
|
+
['nobrainer', self.class.database_name || NoBrainer.connection.parsed_uri[:db],
|
21
|
+
self.class.table_name, field, value.empty? ? 'nil' : value].join(':')
|
22
|
+
end
|
23
|
+
|
24
|
+
def lock_unique_fields
|
25
|
+
return unless NoBrainer::Config.distributed_lock_class && !self.class.unique_validators.empty?
|
26
|
+
|
27
|
+
self.class.unique_validators
|
28
|
+
.map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
29
|
+
.flatten(1)
|
30
|
+
.select { |f, validator| validator.should_validate_uniquess_of?(self, f) }
|
31
|
+
.map { |f, options| _lock_key_from_field(f) }
|
32
|
+
.sort
|
33
|
+
.uniq
|
34
|
+
.each do |key|
|
35
|
+
lock = NoBrainer::Config.distributed_lock_class.new(key)
|
36
|
+
lock.lock
|
37
|
+
@locked_unique_fields ||= []
|
38
|
+
@locked_unique_fields << lock
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unlock_unique_fields
|
43
|
+
return unless @locked_unique_fields
|
44
|
+
@locked_unique_fields.pop.unlock until @locked_unique_fields.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
included do
|
48
|
+
singleton_class.send(:attr_accessor, :unique_validators)
|
49
|
+
self.unique_validators = []
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def validates_uniqueness_of(*attr_names)
|
54
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
55
|
+
end
|
56
|
+
|
57
|
+
def inherited(subclass)
|
58
|
+
super
|
59
|
+
subclass.unique_validators = self.unique_validators.dup
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
65
|
+
attr_accessor :scope
|
66
|
+
|
67
|
+
def setup(klass)
|
68
|
+
self.scope = [*options[:scope]]
|
69
|
+
([klass] + klass.descendants).each do |_klass|
|
70
|
+
_klass.unique_validators << self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def should_validate_uniquess_of?(doc, field)
|
75
|
+
(scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_each(doc, attr, value)
|
79
|
+
return true unless should_validate_uniquess_of?(doc, attr)
|
80
|
+
|
81
|
+
criteria = doc.root_class.unscoped.where(attr => value)
|
82
|
+
criteria = apply_scopes(criteria, doc)
|
83
|
+
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
84
|
+
is_unique = criteria.count == 0
|
85
|
+
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
|
86
|
+
is_unique
|
87
|
+
end
|
88
|
+
|
89
|
+
def apply_scopes(criteria, doc)
|
90
|
+
criteria.where(scope.map { |k| {k => doc.read_attribute(k)} })
|
91
|
+
end
|
92
|
+
|
93
|
+
def exclude_doc(criteria, doc)
|
94
|
+
criteria.where(:id.ne => doc.id)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -3,6 +3,12 @@ module NoBrainer::Document::Validation
|
|
3
3
|
include ActiveModel::Validations
|
4
4
|
include ActiveModel::Validations::Callbacks
|
5
5
|
|
6
|
+
included do
|
7
|
+
# We don't want before_validation returning false to halt the chain.
|
8
|
+
define_callbacks :validation, :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name],
|
9
|
+
:terminator => NoBrainer::Document::Callbacks.terminator
|
10
|
+
end
|
11
|
+
|
6
12
|
def valid?(context=nil)
|
7
13
|
super(context || (new_record? ? :create : :update))
|
8
14
|
end
|
@@ -10,31 +16,9 @@ module NoBrainer::Document::Validation
|
|
10
16
|
module ClassMethods
|
11
17
|
def _field(attr, options={})
|
12
18
|
super
|
13
|
-
validates(attr, { :presence =>
|
19
|
+
validates(attr, { :presence => options[:required] }) if options.has_key?(:required)
|
20
|
+
validates(attr, { :uniqueness => options[:unique] }) if options.has_key?(:unique)
|
14
21
|
validates(attr, options[:validates]) if options[:validates]
|
15
22
|
end
|
16
|
-
|
17
|
-
def validates_uniqueness_of(*attr_names)
|
18
|
-
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class UniquenessValidator < ActiveModel::EachValidator
|
23
|
-
def validate_each(doc, attr, value)
|
24
|
-
criteria = doc.root_class.unscoped.where(attr => value)
|
25
|
-
criteria = apply_scopes(criteria, doc)
|
26
|
-
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
27
|
-
is_unique = criteria.count == 0
|
28
|
-
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
|
29
|
-
is_unique
|
30
|
-
end
|
31
|
-
|
32
|
-
def apply_scopes(criteria, doc)
|
33
|
-
criteria.where([*options[:scope]].map { |k| {k => doc.read_attribute(k)} })
|
34
|
-
end
|
35
|
-
|
36
|
-
def exclude_doc(criteria, doc)
|
37
|
-
criteria.where(:id.ne => doc.id)
|
38
|
-
end
|
39
23
|
end
|
40
24
|
end
|
data/lib/no_brainer/error.rb
CHANGED
@@ -6,7 +6,7 @@ module NoBrainer::Error
|
|
6
6
|
class CannotUseIndex < RuntimeError; end
|
7
7
|
class MissingIndex < RuntimeError; end
|
8
8
|
class InvalidType < RuntimeError; end
|
9
|
-
class
|
9
|
+
class AssociationNotPersisted < RuntimeError; end
|
10
10
|
class ReadonlyField < RuntimeError; end
|
11
11
|
|
12
12
|
class DocumentInvalid < RuntimeError
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module NoBrainer::IndexManager
|
2
2
|
def self.update_indexes(options={})
|
3
|
-
|
4
|
-
|
3
|
+
NoBrainer::Document.all.each { |model| model.perform_update_indexes(options.merge(:wait => false)) }
|
4
|
+
unless options[:wait] == false
|
5
|
+
NoBrainer::Document.all.each { |model| model.wait_for_index(nil) }
|
6
|
+
end
|
5
7
|
end
|
6
8
|
end
|
data/lib/no_brainer/loader.rb
CHANGED
@@ -11,7 +11,7 @@ module NoBrainer::QueryRunner
|
|
11
11
|
end
|
12
12
|
|
13
13
|
autoload :Driver, :DatabaseOnDemand, :TableOnDemand, :WriteError,
|
14
|
-
:
|
14
|
+
:Reconnect, :Selection, :RunOptions, :Logger, :MissingIndex
|
15
15
|
|
16
16
|
class << self
|
17
17
|
attr_accessor :stack
|
@@ -27,12 +27,12 @@ module NoBrainer::QueryRunner
|
|
27
27
|
# thread-safe, since require() is ran with a mutex.
|
28
28
|
self.stack = ::Middleware::Builder.new do
|
29
29
|
use RunOptions
|
30
|
-
use Connection
|
31
30
|
use WriteError
|
32
31
|
use MissingIndex
|
33
32
|
use DatabaseOnDemand
|
34
33
|
use TableOnDemand
|
35
34
|
use Logger
|
35
|
+
use Reconnect
|
36
36
|
use Driver
|
37
37
|
end
|
38
38
|
end
|
@@ -1,29 +1,35 @@
|
|
1
1
|
class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
|
2
2
|
def call(env)
|
3
3
|
start_time = Time.now
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
@runner.call(env).tap { log_query(env, start_time) }
|
5
|
+
rescue Exception => e
|
6
|
+
log_query(env, start_time, e) rescue nil
|
7
|
+
raise e
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
private
|
11
|
+
|
12
|
+
def log_query(env, start_time, exception=nil)
|
13
|
+
return unless NoBrainer.logger.debug?
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
duration = Time.now - start_time
|
16
|
+
msg = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
|
16
17
|
|
17
|
-
|
18
|
+
msg = "(#{env[:db_name]}) #{msg}" if env[:db_name]
|
19
|
+
msg = "[#{(duration * 1000.0).round(1)}ms] #{msg}"
|
20
|
+
|
21
|
+
if NoBrainer::Config.colorize_logger
|
22
|
+
if exception
|
23
|
+
msg = "#{msg} \e[0;31m#{exception.class} #{exception.message.split("\n").first}\e[0m"
|
24
|
+
else
|
18
25
|
case NoBrainer::Util.rql_type(env[:query])
|
19
26
|
when :write then msg = "\e[1;31m#{msg}\e[0m" # red
|
20
27
|
when :read then msg = "\e[1;32m#{msg}\e[0m" # green
|
21
28
|
when :management then msg = "\e[1;33m#{msg}\e[0m" # yellow
|
22
29
|
end
|
23
30
|
end
|
24
|
-
|
25
|
-
NoBrainer.logger.debug(msg)
|
26
31
|
end
|
27
|
-
|
32
|
+
|
33
|
+
NoBrainer.logger.debug(msg)
|
28
34
|
end
|
29
35
|
end
|
metadata
CHANGED
@@ -1,69 +1,69 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.1
|
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-
|
11
|
+
date: 2014-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rethinkdb
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.12.0.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.12.0.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 4.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 4.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: activemodel
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 4.0.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 4.0.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: middleware
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ~>
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 0.1.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - ~>
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.1.0
|
69
69
|
description: ORM for RethinkDB
|
@@ -73,74 +73,75 @@ executables: []
|
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
|
+
- LICENSE
|
77
|
+
- README.md
|
78
|
+
- lib/no_brainer/autoload.rb
|
79
|
+
- lib/no_brainer/config.rb
|
80
|
+
- lib/no_brainer/connection.rb
|
81
|
+
- lib/no_brainer/criteria.rb
|
82
|
+
- lib/no_brainer/criteria/after_find.rb
|
83
|
+
- lib/no_brainer/criteria/cache.rb
|
84
|
+
- lib/no_brainer/criteria/core.rb
|
85
|
+
- lib/no_brainer/criteria/count.rb
|
86
|
+
- lib/no_brainer/criteria/delete.rb
|
87
|
+
- lib/no_brainer/criteria/enumerable.rb
|
88
|
+
- lib/no_brainer/criteria/first.rb
|
89
|
+
- lib/no_brainer/criteria/limit.rb
|
90
|
+
- lib/no_brainer/criteria/order_by.rb
|
91
|
+
- lib/no_brainer/criteria/preload.rb
|
92
|
+
- lib/no_brainer/criteria/raw.rb
|
93
|
+
- lib/no_brainer/criteria/scope.rb
|
94
|
+
- lib/no_brainer/criteria/update.rb
|
95
|
+
- lib/no_brainer/criteria/where.rb
|
96
|
+
- lib/no_brainer/decorated_symbol.rb
|
97
|
+
- lib/no_brainer/document.rb
|
98
|
+
- lib/no_brainer/document/association.rb
|
99
|
+
- lib/no_brainer/document/association/belongs_to.rb
|
100
|
+
- lib/no_brainer/document/association/core.rb
|
101
|
+
- lib/no_brainer/document/association/eager_loader.rb
|
102
|
+
- lib/no_brainer/document/association/has_many.rb
|
103
|
+
- lib/no_brainer/document/association/has_many_through.rb
|
76
104
|
- lib/no_brainer/document/association/has_one.rb
|
77
105
|
- lib/no_brainer/document/association/has_one_through.rb
|
78
|
-
- lib/no_brainer/document/
|
79
|
-
- lib/no_brainer/document/association/has_many.rb
|
80
|
-
- lib/no_brainer/document/association/eager_loader.rb
|
81
|
-
- lib/no_brainer/document/association/core.rb
|
82
|
-
- lib/no_brainer/document/association/belongs_to.rb
|
83
|
-
- lib/no_brainer/document/polymorphic.rb
|
84
|
-
- lib/no_brainer/document/store_in.rb
|
85
|
-
- lib/no_brainer/document/core.rb
|
86
|
-
- lib/no_brainer/document/serialization.rb
|
87
|
-
- lib/no_brainer/document/association.rb
|
106
|
+
- lib/no_brainer/document/attributes.rb
|
88
107
|
- lib/no_brainer/document/callbacks.rb
|
89
|
-
- lib/no_brainer/document/
|
90
|
-
- lib/no_brainer/document/
|
108
|
+
- lib/no_brainer/document/core.rb
|
109
|
+
- lib/no_brainer/document/criteria.rb
|
91
110
|
- lib/no_brainer/document/dirty.rb
|
92
|
-
- lib/no_brainer/document/
|
93
|
-
- lib/no_brainer/document/validation.rb
|
94
|
-
- lib/no_brainer/document/attributes.rb
|
111
|
+
- lib/no_brainer/document/dynamic_attributes.rb
|
95
112
|
- lib/no_brainer/document/id.rb
|
113
|
+
- lib/no_brainer/document/index.rb
|
96
114
|
- lib/no_brainer/document/injection_layer.rb
|
97
115
|
- lib/no_brainer/document/persistance.rb
|
116
|
+
- lib/no_brainer/document/polymorphic.rb
|
98
117
|
- lib/no_brainer/document/readonly.rb
|
118
|
+
- lib/no_brainer/document/serialization.rb
|
119
|
+
- lib/no_brainer/document/store_in.rb
|
120
|
+
- lib/no_brainer/document/timestamps.rb
|
99
121
|
- lib/no_brainer/document/types.rb
|
100
|
-
- lib/no_brainer/document/
|
122
|
+
- lib/no_brainer/document/uniqueness.rb
|
123
|
+
- lib/no_brainer/document/validation.rb
|
124
|
+
- lib/no_brainer/error.rb
|
125
|
+
- lib/no_brainer/fork.rb
|
126
|
+
- lib/no_brainer/index_manager.rb
|
127
|
+
- lib/no_brainer/loader.rb
|
128
|
+
- lib/no_brainer/locale/en.yml
|
129
|
+
- lib/no_brainer/query_runner.rb
|
101
130
|
- lib/no_brainer/query_runner/database_on_demand.rb
|
102
|
-
- lib/no_brainer/query_runner/table_on_demand.rb
|
103
|
-
- lib/no_brainer/query_runner/write_error.rb
|
104
131
|
- lib/no_brainer/query_runner/driver.rb
|
105
132
|
- lib/no_brainer/query_runner/logger.rb
|
106
133
|
- lib/no_brainer/query_runner/missing_index.rb
|
134
|
+
- lib/no_brainer/query_runner/reconnect.rb
|
107
135
|
- lib/no_brainer/query_runner/run_options.rb
|
108
|
-
- lib/no_brainer/query_runner/
|
136
|
+
- lib/no_brainer/query_runner/table_on_demand.rb
|
137
|
+
- lib/no_brainer/query_runner/write_error.rb
|
138
|
+
- lib/no_brainer/railtie.rb
|
109
139
|
- lib/no_brainer/railtie/database.rake
|
110
|
-
- lib/no_brainer/index_manager.rb
|
111
|
-
- lib/no_brainer/loader.rb
|
112
|
-
- lib/no_brainer/locale/en.yml
|
113
|
-
- lib/no_brainer/fork.rb
|
114
|
-
- lib/no_brainer/connection.rb
|
115
|
-
- lib/no_brainer/query_runner.rb
|
116
140
|
- lib/no_brainer/util.rb
|
117
|
-
- lib/
|
118
|
-
- lib/no_brainer/criteria/scope.rb
|
119
|
-
- lib/no_brainer/criteria/raw.rb
|
120
|
-
- lib/no_brainer/criteria/preload.rb
|
121
|
-
- lib/no_brainer/criteria/order_by.rb
|
122
|
-
- lib/no_brainer/criteria/limit.rb
|
123
|
-
- lib/no_brainer/criteria/first.rb
|
124
|
-
- lib/no_brainer/criteria/enumerable.rb
|
125
|
-
- lib/no_brainer/criteria/count.rb
|
126
|
-
- lib/no_brainer/criteria/cache.rb
|
127
|
-
- lib/no_brainer/criteria/after_find.rb
|
128
|
-
- lib/no_brainer/criteria/where.rb
|
129
|
-
- lib/no_brainer/criteria/update.rb
|
130
|
-
- lib/no_brainer/criteria/delete.rb
|
131
|
-
- lib/no_brainer/criteria/core.rb
|
132
|
-
- lib/no_brainer/railtie.rb
|
133
|
-
- lib/no_brainer/config.rb
|
134
|
-
- lib/no_brainer/document.rb
|
135
|
-
- lib/no_brainer/criteria.rb
|
136
|
-
- lib/no_brainer/error.rb
|
137
|
-
- lib/no_brainer/autoload.rb
|
141
|
+
- lib/nobrainer.rb
|
138
142
|
- lib/rails/generators/nobrainer.rb
|
139
143
|
- lib/rails/generators/nobrainer/model/model_generator.rb
|
140
144
|
- lib/rails/generators/nobrainer/model/templates/model.rb.tt
|
141
|
-
- lib/nobrainer.rb
|
142
|
-
- README.md
|
143
|
-
- LICENSE
|
144
145
|
homepage: http://nobrainer.io
|
145
146
|
licenses:
|
146
147
|
- LGPLv3
|
@@ -151,17 +152,17 @@ require_paths:
|
|
151
152
|
- lib
|
152
153
|
required_ruby_version: !ruby/object:Gem::Requirement
|
153
154
|
requirements:
|
154
|
-
- -
|
155
|
+
- - ">="
|
155
156
|
- !ruby/object:Gem::Version
|
156
157
|
version: 1.9.0
|
157
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
159
|
requirements:
|
159
|
-
- -
|
160
|
+
- - ">="
|
160
161
|
- !ruby/object:Gem::Version
|
161
162
|
version: '0'
|
162
163
|
requirements: []
|
163
164
|
rubyforge_project:
|
164
|
-
rubygems_version: 2.
|
165
|
+
rubygems_version: 2.2.2
|
165
166
|
signing_key:
|
166
167
|
specification_version: 4
|
167
168
|
summary: ORM for RethinkDB
|