nobrainer 0.18.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 +73 -39
- data/lib/no_brainer/connection.rb +2 -4
- data/lib/no_brainer/criteria/after_find.rb +3 -11
- data/lib/no_brainer/criteria/aggregate.rb +2 -2
- data/lib/no_brainer/criteria/cache.rb +15 -10
- data/lib/no_brainer/criteria/core.rb +46 -11
- data/lib/no_brainer/criteria/delete.rb +2 -2
- data/lib/no_brainer/criteria/eager_load.rb +51 -0
- data/lib/no_brainer/criteria/extend.rb +4 -16
- data/lib/no_brainer/criteria/find.rb +27 -0
- data/lib/no_brainer/criteria/index.rb +7 -13
- data/lib/no_brainer/criteria/limit.rb +5 -12
- data/lib/no_brainer/criteria/order_by.rb +20 -36
- data/lib/no_brainer/criteria/pluck.rb +16 -22
- data/lib/no_brainer/criteria/raw.rb +4 -10
- data/lib/no_brainer/criteria/scope.rb +6 -19
- data/lib/no_brainer/criteria/update.rb +8 -6
- data/lib/no_brainer/criteria/where.rb +252 -138
- data/lib/no_brainer/criteria.rb +3 -2
- data/lib/no_brainer/document/aliases.rb +3 -3
- data/lib/no_brainer/document/association/belongs_to.rb +9 -5
- data/lib/no_brainer/document/association/core.rb +6 -5
- data/lib/no_brainer/document/association/eager_loader.rb +9 -9
- data/lib/no_brainer/document/association/has_many.rb +23 -9
- data/lib/no_brainer/document/association/has_many_through.rb +12 -3
- data/lib/no_brainer/document/atomic_ops.rb +79 -78
- data/lib/no_brainer/document/attributes.rb +24 -20
- data/lib/no_brainer/document/callbacks.rb +1 -1
- data/lib/no_brainer/document/core.rb +5 -2
- data/lib/no_brainer/document/criteria.rb +14 -19
- data/lib/no_brainer/document/dirty.rb +11 -16
- data/lib/no_brainer/document/index/index.rb +2 -1
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/index.rb +14 -10
- data/lib/no_brainer/document/persistance.rb +24 -13
- data/lib/no_brainer/document/primary_key/generator.rb +83 -0
- data/lib/no_brainer/document/{id.rb → primary_key.rb} +9 -36
- data/lib/no_brainer/document/store_in.rb +2 -2
- data/lib/no_brainer/document/timestamps.rb +4 -2
- data/lib/no_brainer/document/types/binary.rb +2 -7
- data/lib/no_brainer/document/types/boolean.rb +2 -4
- 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 +17 -18
- data/lib/no_brainer/document/validation/not_null.rb +15 -0
- data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -11
- data/lib/no_brainer/document/validation.rb +35 -6
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/error.rb +21 -19
- data/lib/no_brainer/geo/base.rb +16 -0
- data/lib/no_brainer/geo/circle.rb +25 -0
- data/lib/no_brainer/geo/line_string.rb +11 -0
- data/lib/no_brainer/geo/point.rb +49 -0
- data/lib/no_brainer/geo/polygon.rb +11 -0
- data/lib/no_brainer/geo.rb +4 -0
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +114 -0
- data/lib/no_brainer/query_runner/connection_lock.rb +1 -1
- 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/reconnect.rb +9 -11
- data/lib/no_brainer/query_runner/run_options.rb +0 -3
- data/lib/no_brainer/query_runner/table_on_demand.rb +3 -4
- data/lib/no_brainer/railtie/database.rake +2 -2
- data/lib/no_brainer/rql.rb +1 -5
- data/lib/nobrainer.rb +2 -6
- data/lib/rails/generators/nobrainer.rb +1 -1
- metadata +34 -9
- data/lib/no_brainer/criteria/preload.rb +0 -50
- data/lib/no_brainer/decorated_symbol.rb +0 -17
|
@@ -24,7 +24,7 @@ class NoBrainer::Document::Index::MetaStore
|
|
|
24
24
|
def self.on(db_name, &block)
|
|
25
25
|
old_db_name = Thread.current[:nobrainer_meta_store_db]
|
|
26
26
|
Thread.current[:nobrainer_meta_store_db] = db_name
|
|
27
|
-
|
|
27
|
+
block.call
|
|
28
28
|
ensure
|
|
29
29
|
Thread.current[:nobrainer_meta_store_db] = old_db_name
|
|
30
30
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module NoBrainer::Document::Index
|
|
2
|
-
VALID_INDEX_OPTIONS = [:external, :geo, :multi, :
|
|
2
|
+
VALID_INDEX_OPTIONS = [:external, :geo, :multi, :store_as]
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
extend NoBrainer::Autoload
|
|
5
5
|
|
|
@@ -33,12 +33,16 @@ module NoBrainer::Document::Index
|
|
|
33
33
|
raise "Cannot reuse field name #{name}"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
if kind == :compound && what.size < 2
|
|
37
|
+
raise "Compound indexes only make sense with 2 or more fields"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
store_as = options.delete(:store_as)
|
|
41
|
+
store_as ||= fields[name][:store_as] if has_field?(name)
|
|
42
|
+
store_as ||= name
|
|
43
|
+
store_as = store_as.to_sym
|
|
40
44
|
|
|
41
|
-
indexes[name] = NoBrainer::Document::Index::Index.new(self.root_class, name,
|
|
45
|
+
indexes[name] = NoBrainer::Document::Index::Index.new(self.root_class, name, store_as,
|
|
42
46
|
kind, what, options[:external], options[:geo], options[:multi], nil)
|
|
43
47
|
end
|
|
44
48
|
|
|
@@ -57,12 +61,12 @@ module NoBrainer::Document::Index
|
|
|
57
61
|
|
|
58
62
|
super
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
store_as = {:store_as => options[:store_as]}
|
|
61
65
|
case options[:index]
|
|
62
66
|
when nil then
|
|
63
|
-
when Hash then index(attr,
|
|
64
|
-
when Symbol then index(attr,
|
|
65
|
-
when true then index(attr,
|
|
67
|
+
when Hash then index(attr, store_as.merge(options[:index]))
|
|
68
|
+
when Symbol then index(attr, store_as.merge(options[:index] => true))
|
|
69
|
+
when true then index(attr, store_as)
|
|
66
70
|
when false then remove_index(attr)
|
|
67
71
|
end
|
|
68
72
|
end
|
|
@@ -29,7 +29,7 @@ module NoBrainer::Document::Persistance
|
|
|
29
29
|
|
|
30
30
|
def _reload(options={})
|
|
31
31
|
attrs = NoBrainer.run { _reload_selector(options) }
|
|
32
|
-
raise NoBrainer::Error::DocumentNotFound, "#{self.class}
|
|
32
|
+
raise NoBrainer::Error::DocumentNotFound, "#{self.class} :#{self.class.pk_name}=>\"#{pk_value}\" not found" unless attrs
|
|
33
33
|
|
|
34
34
|
options = options.merge(:pristine => true, :from_db => true)
|
|
35
35
|
|
|
@@ -45,16 +45,16 @@ module NoBrainer::Document::Persistance
|
|
|
45
45
|
|
|
46
46
|
def reload(options={})
|
|
47
47
|
[:without, :pluck].each do |type|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
next unless v = options.delete(type)
|
|
49
|
+
|
|
50
|
+
v = Hash[v.flatten.map { |k| [k, true] }] if v.is_a?(Array)
|
|
51
|
+
v = {v => true} unless v.is_a?(Hash)
|
|
52
|
+
v = v.select { |k,_v| _v }
|
|
53
|
+
v = v.with_indifferent_access
|
|
54
|
+
next unless v.present?
|
|
55
|
+
|
|
56
|
+
options[:missing_attributes] ||= {}
|
|
57
|
+
options[:missing_attributes][type] = v
|
|
58
58
|
end
|
|
59
59
|
_reload(options)
|
|
60
60
|
end
|
|
@@ -113,13 +113,11 @@ module NoBrainer::Document::Persistance
|
|
|
113
113
|
assign_attributes(attrs, options)
|
|
114
114
|
save?(options)
|
|
115
115
|
end
|
|
116
|
-
alias_method :update_attributes?, :update?
|
|
117
116
|
|
|
118
117
|
def update(*args)
|
|
119
118
|
update?(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
|
120
119
|
nil
|
|
121
120
|
end
|
|
122
|
-
alias_method :update_attributes, :update
|
|
123
121
|
|
|
124
122
|
def update!(*args)
|
|
125
123
|
update(*args)
|
|
@@ -127,6 +125,18 @@ module NoBrainer::Document::Persistance
|
|
|
127
125
|
end
|
|
128
126
|
alias_method :update_attributes!, :update!
|
|
129
127
|
|
|
128
|
+
def update_attributes?(*args)
|
|
129
|
+
update?(*args).tap { STDERR.puts "[NoBrainer] update_attributes?() is deprecated. Please use update?() instead" }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def update_attributes(*args)
|
|
133
|
+
update(*args).tap { STDERR.puts "[NoBrainer] update_attributes() is deprecated. Please use update() instead" }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def update_attributes!(*args)
|
|
137
|
+
update!(*args).tap { STDERR.puts "[NoBrainer] update_attributes!() is deprecated. Please use update() instead" }
|
|
138
|
+
end
|
|
139
|
+
|
|
130
140
|
def delete
|
|
131
141
|
unless @destroyed
|
|
132
142
|
NoBrainer.run { selector.delete }
|
|
@@ -144,6 +154,7 @@ module NoBrainer::Document::Persistance
|
|
|
144
154
|
def create(attrs={}, options={})
|
|
145
155
|
new(attrs, options).tap { |doc| doc.save(options) }
|
|
146
156
|
end
|
|
157
|
+
alias_method :create!, :create
|
|
147
158
|
|
|
148
159
|
def insert_all(*args)
|
|
149
160
|
docs = args.shift
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module NoBrainer::Document::PrimaryKey::Generator
|
|
2
|
+
class Retry < RuntimeError; end
|
|
3
|
+
|
|
4
|
+
BASE_TABLE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".freeze
|
|
5
|
+
|
|
6
|
+
TIME_OFFSET = Time.parse('2014-01-01').to_i
|
|
7
|
+
|
|
8
|
+
# 30 bits timestamp with 1s resolution -> We overflow in year 2048. Good enough.
|
|
9
|
+
# Math.log(Time.parse('2048-01-01').to_f - TIME_OFFSET)/Math.log(2) = 29.999
|
|
10
|
+
TIMESTAMP_BITS = 30
|
|
11
|
+
|
|
12
|
+
# 14 bits of sequence number. max 16k values per 1s slices.
|
|
13
|
+
# We want something >10k because we want to be able to do high speed inserts
|
|
14
|
+
# on a single process for future benchmarks.
|
|
15
|
+
SEQUENCE_BITS = 14
|
|
16
|
+
|
|
17
|
+
# 24 bits of machine id
|
|
18
|
+
# 0.1% of chance to have a collision with 183 servers:
|
|
19
|
+
# Math.sqrt(-2*(2**24)*Math.log(0.999)) = 183.2
|
|
20
|
+
# 1% of chance to have a collision with ~580 servers.
|
|
21
|
+
# When using more than 500 machines, it's therefore a good
|
|
22
|
+
# idea to set the machine_id manually to avoid collisions.
|
|
23
|
+
MACHINE_ID_BITS = 24
|
|
24
|
+
|
|
25
|
+
# 15 bits for the current pid. We wouldn't need it if the sequence number was
|
|
26
|
+
# on a piece of shared memory :)
|
|
27
|
+
PID_BITS = 15
|
|
28
|
+
|
|
29
|
+
# Total: 83 bits
|
|
30
|
+
# We need at most 14 digits in [A-Za-z0-9] to represent 83 bits:
|
|
31
|
+
# Math.log(62**14)/Math.log(2) = 83.35
|
|
32
|
+
ID_STR_LENGTH = 14
|
|
33
|
+
|
|
34
|
+
TIMESTAMP_MASK = (1 << TIMESTAMP_BITS)-1
|
|
35
|
+
SEQUENCE_MASK = (1 << SEQUENCE_BITS)-1
|
|
36
|
+
MACHINE_ID_MASK = (1 << MACHINE_ID_BITS)-1
|
|
37
|
+
PID_MASK = (1 << PID_BITS)-1
|
|
38
|
+
|
|
39
|
+
PID_SHIFT = 0
|
|
40
|
+
MACHINE_ID_SHIFT = PID_SHIFT + PID_BITS
|
|
41
|
+
SEQUENCE_SHIFT = MACHINE_ID_SHIFT + MACHINE_ID_BITS
|
|
42
|
+
TIMESTAMP_SHIFT = SEQUENCE_SHIFT + SEQUENCE_BITS
|
|
43
|
+
|
|
44
|
+
def self._generate
|
|
45
|
+
timestamp = (Time.now.to_i - TIME_OFFSET) & TIMESTAMP_MASK
|
|
46
|
+
|
|
47
|
+
unless @last_timestamp == timestamp
|
|
48
|
+
# more noise is better in the ID, but we prefer to avoid
|
|
49
|
+
# wrapping the sequences so that Model.last on a single
|
|
50
|
+
# machine returns the latest created document.
|
|
51
|
+
@first_sequence = sequence = rand(SEQUENCE_MASK/2)
|
|
52
|
+
@last_timestamp = timestamp
|
|
53
|
+
else
|
|
54
|
+
sequence = (@sequence + 1) & SEQUENCE_MASK
|
|
55
|
+
raise Retry if @first_sequence == sequence
|
|
56
|
+
end
|
|
57
|
+
@sequence = sequence
|
|
58
|
+
|
|
59
|
+
machine_id = NoBrainer::Config.machine_id & MACHINE_ID_MASK
|
|
60
|
+
|
|
61
|
+
pid = Process.pid & PID_MASK
|
|
62
|
+
|
|
63
|
+
(timestamp << TIMESTAMP_SHIFT) | (sequence << SEQUENCE_SHIFT) |
|
|
64
|
+
(machine_id << MACHINE_ID_SHIFT) | (pid << PID_SHIFT)
|
|
65
|
+
rescue Retry
|
|
66
|
+
sleep 0.1
|
|
67
|
+
retry
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.convert_to_alphanum(id)
|
|
71
|
+
result = []
|
|
72
|
+
until id.zero?
|
|
73
|
+
id, r = id.divmod(BASE_TABLE.size)
|
|
74
|
+
result << BASE_TABLE[r]
|
|
75
|
+
end
|
|
76
|
+
result.reverse.join.rjust(ID_STR_LENGTH, BASE_TABLE[0])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@lock = Mutex.new
|
|
80
|
+
def self.generate
|
|
81
|
+
convert_to_alphanum(@lock.synchronize { _generate })
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
module NoBrainer::Document::PrimaryKey
|
|
2
|
+
extend NoBrainer::Autoload
|
|
3
|
+
autoload :Generator
|
|
4
4
|
|
|
5
|
-
module NoBrainer::Document::Id
|
|
6
5
|
extend ActiveSupport::Concern
|
|
7
6
|
|
|
8
7
|
DEFAULT_PK_NAME = :id
|
|
@@ -24,41 +23,10 @@ module NoBrainer::Document::Id
|
|
|
24
23
|
|
|
25
24
|
delegate :hash, :to => :pk_value
|
|
26
25
|
|
|
27
|
-
# The following code is inspired by the mongo-ruby-driver
|
|
28
|
-
|
|
29
|
-
@machine_id = Digest::MD5.digest(Socket.gethostname)[0, 3]
|
|
30
|
-
@lock = Mutex.new
|
|
31
|
-
@index = 0
|
|
32
|
-
|
|
33
|
-
def self.get_inc
|
|
34
|
-
@lock.synchronize do
|
|
35
|
-
@index = (@index + 1) % 0xFFFFFF
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# TODO Unit test that thing
|
|
40
|
-
def self.generate
|
|
41
|
-
oid = ''
|
|
42
|
-
# 4 bytes current time
|
|
43
|
-
oid += [Time.now.to_i].pack("N")
|
|
44
|
-
|
|
45
|
-
# 3 bytes machine
|
|
46
|
-
oid += @machine_id
|
|
47
|
-
|
|
48
|
-
# 2 bytes pid
|
|
49
|
-
oid += [Process.pid % 0xFFFF].pack("n")
|
|
50
|
-
|
|
51
|
-
# 3 bytes inc
|
|
52
|
-
oid += [get_inc].pack("N")[1, 3]
|
|
53
|
-
|
|
54
|
-
oid.unpack("C12").map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
|
|
55
|
-
end
|
|
56
|
-
|
|
57
26
|
module ClassMethods
|
|
58
27
|
def define_default_pk
|
|
59
28
|
class_variable_set(:@@pk_name, nil)
|
|
60
|
-
field NoBrainer::Document::
|
|
61
|
-
:type => String, :default => ->{ NoBrainer::Document::Id.generate }
|
|
29
|
+
field NoBrainer::Document::PrimaryKey::DEFAULT_PK_NAME, :primary_key => :default
|
|
62
30
|
end
|
|
63
31
|
|
|
64
32
|
def define_pk(attr)
|
|
@@ -81,6 +49,11 @@ module NoBrainer::Document::Id
|
|
|
81
49
|
if options[:primary_key]
|
|
82
50
|
options = options.merge(:readonly => true) if options[:readonly].nil?
|
|
83
51
|
options = options.merge(:index => true)
|
|
52
|
+
|
|
53
|
+
if options[:type].in?([String, nil]) && options[:default].nil?
|
|
54
|
+
options[:type] = String
|
|
55
|
+
options[:default] = ->{ NoBrainer::Document::PrimaryKey::Generator.generate }
|
|
56
|
+
end
|
|
84
57
|
end
|
|
85
58
|
super
|
|
86
59
|
end
|
|
@@ -16,13 +16,13 @@ module NoBrainer::Document::StoreIn
|
|
|
16
16
|
|
|
17
17
|
def database_name
|
|
18
18
|
db = self.store_in_options[:database]
|
|
19
|
-
db.is_a?(Proc) ? db.call : db
|
|
19
|
+
(db.is_a?(Proc) ? db.call : db).try(:to_s)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def table_name
|
|
23
23
|
table = store_in_options[:table]
|
|
24
24
|
table_name = table.is_a?(Proc) ? table.call : table
|
|
25
|
-
table_name || root_class.name.tableize.gsub('/', '__')
|
|
25
|
+
table_name.try(:to_s) || root_class.name.tableize.gsub('/', '__')
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def rql_table
|
|
@@ -7,12 +7,14 @@ module NoBrainer::Document::Timestamps
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def _create(options={})
|
|
10
|
-
|
|
10
|
+
now = Time.now
|
|
11
|
+
self.created_at = now unless created_at_changed?
|
|
12
|
+
self.updated_at = now unless updated_at_changed?
|
|
11
13
|
super
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def _update(attrs)
|
|
15
|
-
self.updated_at = Time.now
|
|
17
|
+
self.updated_at = Time.now unless updated_at_changed?
|
|
16
18
|
super(attrs.merge('updated_at' => @_attributes['updated_at']))
|
|
17
19
|
end
|
|
18
20
|
end
|
|
@@ -4,7 +4,7 @@ class NoBrainer::Binary
|
|
|
4
4
|
def self.to_s; inspect; end
|
|
5
5
|
def self.name; inspect; end
|
|
6
6
|
|
|
7
|
-
module
|
|
7
|
+
module NoBrainerExtensions
|
|
8
8
|
InvalidType = NoBrainer::Error::InvalidType
|
|
9
9
|
|
|
10
10
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -13,11 +13,6 @@ class NoBrainer::Binary
|
|
|
13
13
|
else raise InvalidType
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
-
|
|
17
|
-
def nobrainer_cast_db_to_model(value)
|
|
18
|
-
value.is_a?(String) ? RethinkDB::Binary.new(value) : value
|
|
19
|
-
end
|
|
20
16
|
end
|
|
21
|
-
extend
|
|
17
|
+
extend NoBrainerExtensions
|
|
22
18
|
end
|
|
23
|
-
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
# We namespace our fake Boolean class to avoid polluting the global namespace
|
|
2
1
|
class NoBrainer::Boolean
|
|
3
2
|
def initialize; raise; end
|
|
4
3
|
def self.inspect; 'Boolean'; end
|
|
5
4
|
def self.to_s; inspect; end
|
|
6
5
|
def self.name; inspect; end
|
|
7
6
|
|
|
8
|
-
module
|
|
7
|
+
module NoBrainerExtensions
|
|
9
8
|
InvalidType = NoBrainer::Error::InvalidType
|
|
10
9
|
|
|
11
10
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -21,6 +20,5 @@ class NoBrainer::Boolean
|
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
end
|
|
24
|
-
extend
|
|
23
|
+
extend NoBrainerExtensions
|
|
25
24
|
end
|
|
26
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Date
|
|
2
|
-
module
|
|
2
|
+
module NoBrainerExtensions
|
|
3
3
|
InvalidType = NoBrainer::Error::InvalidType
|
|
4
4
|
|
|
5
5
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -22,5 +22,5 @@ class Date
|
|
|
22
22
|
value.is_a?(Date) ? Time.utc(value.year, value.month, value.day) : value
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
|
-
extend
|
|
25
|
+
extend NoBrainerExtensions
|
|
26
26
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Float
|
|
2
|
-
module
|
|
2
|
+
module NoBrainerExtensions
|
|
3
3
|
InvalidType = NoBrainer::Error::InvalidType
|
|
4
4
|
|
|
5
5
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -16,5 +16,5 @@ class Float
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
-
extend
|
|
19
|
+
extend NoBrainerExtensions
|
|
20
20
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Look in lib/no_brainer/geo.rb instead
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Integer
|
|
2
|
-
module
|
|
2
|
+
module NoBrainerExtensions
|
|
3
3
|
InvalidType = NoBrainer::Error::InvalidType
|
|
4
4
|
|
|
5
5
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -14,5 +14,5 @@ class Integer
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
|
-
extend
|
|
17
|
+
extend NoBrainerExtensions
|
|
18
18
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Set
|
|
2
|
-
module
|
|
2
|
+
module NoBrainerExtensions
|
|
3
3
|
InvalidType = NoBrainer::Error::InvalidType
|
|
4
4
|
|
|
5
5
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -19,5 +19,5 @@ class Set
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
extend
|
|
22
|
+
extend NoBrainerExtensions
|
|
23
23
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class String
|
|
2
|
-
module
|
|
2
|
+
module NoBrainerExtensions
|
|
3
3
|
InvalidType = NoBrainer::Error::InvalidType
|
|
4
4
|
|
|
5
5
|
def nobrainer_cast_user_to_model(value)
|
|
@@ -7,8 +7,11 @@ class String
|
|
|
7
7
|
when String then value
|
|
8
8
|
when Symbol then value.to_s
|
|
9
9
|
else raise InvalidType
|
|
10
|
+
end.tap do |str|
|
|
11
|
+
max_length = NoBrainer::Config.max_string_length
|
|
12
|
+
raise InvalidType.new(:error => { :message => :too_long, :count => max_length }) if str.size > max_length
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
end
|
|
13
|
-
extend
|
|
16
|
+
extend NoBrainerExtensions
|
|
14
17
|
end
|
|
@@ -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,6 +63,13 @@ module NoBrainer::Document::Types
|
|
|
62
63
|
|
|
63
64
|
return unless options[:type]
|
|
64
65
|
|
|
66
|
+
raise "Please use a class for the type option" unless options[:type].is_a?(Class)
|
|
67
|
+
case options[:type].to_s
|
|
68
|
+
when "NoBrainer::Geo::Circle" then raise "Cannot store circles :("
|
|
69
|
+
when "NoBrainer::Geo::Polygon", "NoBrainer::Geo::LineString"
|
|
70
|
+
raise "Make a request on github if you'd like to store polygons/linestrings"
|
|
71
|
+
end
|
|
72
|
+
|
|
65
73
|
NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
|
|
66
74
|
|
|
67
75
|
inject_in_layer :types do
|
|
@@ -88,21 +96,12 @@ module NoBrainer::Document::Types
|
|
|
88
96
|
remove_method("#{attr}?") if method_defined?("#{attr}?")
|
|
89
97
|
end
|
|
90
98
|
end
|
|
91
|
-
|
|
92
|
-
def field(attr, options={})
|
|
93
|
-
options[:real_type] = options[:type]
|
|
94
|
-
if options[:type] == Array || options[:type] == Hash
|
|
95
|
-
# XXX For the moment, NoBrainer does not support these complex types
|
|
96
|
-
options.delete(:type)
|
|
97
|
-
end
|
|
98
|
-
super
|
|
99
|
-
end
|
|
100
99
|
end
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
106
105
|
|
|
107
106
|
class << self
|
|
108
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,14 +51,13 @@ 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)
|
|
58
58
|
subclass.unique_validators = self.unique_validators.dup
|
|
59
59
|
super
|
|
60
60
|
end
|
|
61
|
-
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
class UniquenessValidator < ActiveModel::EachValidator
|
|
@@ -73,19 +72,20 @@ module NoBrainer::Document::Uniqueness
|
|
|
73
72
|
end
|
|
74
73
|
end
|
|
75
74
|
|
|
76
|
-
def
|
|
77
|
-
(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?") }
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
def validate_each(doc, attr, value)
|
|
81
|
-
return true unless should_validate_uniquess_of?(doc, attr)
|
|
82
|
-
|
|
83
80
|
criteria = doc.root_class.unscoped.where(attr => value)
|
|
84
81
|
criteria = apply_scopes(criteria, doc)
|
|
85
82
|
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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.
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def apply_scopes(criteria, doc)
|