nobrainer 0.13.0 → 0.13.1
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/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
|