nobrainer 0.30.0 → 0.31.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/criteria/first_or_create.rb +4 -1
- data/lib/no_brainer/criteria/join.rb +2 -1
- data/lib/no_brainer/criteria/virtual_attributes.rb +8 -1
- data/lib/no_brainer/criteria/where.rb +46 -53
- data/lib/no_brainer/document/association.rb +8 -0
- data/lib/no_brainer/document/association/belongs_to.rb +20 -4
- data/lib/no_brainer/document/core.rb +10 -1
- data/lib/no_brainer/document/lazy_fetch.rb +1 -0
- data/lib/no_brainer/document/persistance.rb +1 -0
- data/lib/no_brainer/document/primary_key/generator.rb +1 -1
- data/lib/no_brainer/document/table_config/synchronizer.rb +1 -6
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 657fd4dc2ef9b4dd9383ea99fa14b99d386e19ad
|
|
4
|
+
data.tar.gz: 63b01fce35c08ffed01ff23496a5cc7eb394e1ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc92ed2d5c2cbdfee45b3f32504090412820b205f1cdd45f3deca3ed5a98754c23ffafef30f32c2aa2fe3b96d7cecd4a88165a7d014a7c3205efebe81c016501
|
|
7
|
+
data.tar.gz: 45428c47e16c508b1845314d01221b749b14588ee20c0ca76b9cf981216f6c1728fccd396072c3935eded21491d78596ccc76167f59cb0fbbff7fbfd4ca5fda1
|
|
@@ -20,7 +20,10 @@ module NoBrainer::Criteria::FirstOrCreate
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def _upsert(attrs, save_options)
|
|
23
|
-
|
|
23
|
+
# Note: we don't do a full cast of user_to_cast because where() takes care
|
|
24
|
+
# of it. We just need to locate a suitable uniqueness validator, for which
|
|
25
|
+
# we just need to translate keys.
|
|
26
|
+
attrs = Hash[attrs.map { |k,v| model.association_user_to_model_cast(k.to_sym, v) }]
|
|
24
27
|
unique_keys = get_model_unique_fields.detect { |keys| keys & attrs.keys == keys }
|
|
25
28
|
return where(attrs.slice(*unique_keys)).__send__(:_first_or_create, attrs, save_options) if unique_keys
|
|
26
29
|
|
|
@@ -36,6 +36,7 @@ module NoBrainer::Criteria::Join
|
|
|
36
36
|
|
|
37
37
|
def _instantiate_model(attrs, options={})
|
|
38
38
|
return super unless @options[:join] && !raw?
|
|
39
|
+
return super if attrs.nil?
|
|
39
40
|
|
|
40
41
|
associated_instances = join_ast.map do |association, criteria|
|
|
41
42
|
[association, criteria.send(:_instantiate_model, attrs.delete(association.target_name.to_s))]
|
|
@@ -53,7 +54,7 @@ module NoBrainer::Criteria::Join
|
|
|
53
54
|
join_ast.reduce(super) do |rql, (association, criteria)|
|
|
54
55
|
rql.concat_map do |doc|
|
|
55
56
|
key = doc[association.eager_load_owner_key]
|
|
56
|
-
RethinkDB::RQL.new.branch(key.eq(nil), [],
|
|
57
|
+
RethinkDB::RQL.new.branch(key.default(nil).eq(nil), [],
|
|
57
58
|
criteria.where(association.eager_load_target_key => key).to_rql.map do |assoc_doc|
|
|
58
59
|
doc.merge(association.target_name => assoc_doc)
|
|
59
60
|
end)
|
|
@@ -8,7 +8,14 @@ module NoBrainer::Criteria::VirtualAttributes
|
|
|
8
8
|
rql = rql.map do |_doc|
|
|
9
9
|
model.virtual_fields.reduce(_doc) do |doc, field|
|
|
10
10
|
field_rql = model.fields[field][:virtual].call(doc, RethinkDB::RQL.new)
|
|
11
|
-
field_rql.nil?
|
|
11
|
+
if field_rql.nil?
|
|
12
|
+
doc
|
|
13
|
+
else
|
|
14
|
+
unless field_rql.is_a?(RethinkDB::RQL)
|
|
15
|
+
raise "The virtual attribute `#{model}.#{field}' should return a RQL expression"
|
|
16
|
+
end
|
|
17
|
+
doc.merge(field => field_rql)
|
|
18
|
+
end
|
|
12
19
|
end
|
|
13
20
|
end
|
|
14
21
|
end
|
|
@@ -150,69 +150,57 @@ module NoBrainer::Criteria::Where
|
|
|
150
150
|
raise "Incorrect use of `#{op}' and `#{key_modifier}'" if key_modifier != :scalar
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
def association
|
|
154
|
-
return nil if key_path.size > 1
|
|
155
|
-
@association ||= [model.association_metadata[key_path.first]]
|
|
156
|
-
@association.first
|
|
157
|
-
end
|
|
158
|
-
|
|
159
153
|
def cast_value(value)
|
|
160
154
|
return value if casted_values
|
|
161
155
|
|
|
162
|
-
case
|
|
163
|
-
when NoBrainer::
|
|
164
|
-
|
|
165
|
-
unless value.is_a?(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
value
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
options = NoBrainer::Geo::Base.normalize_geo_options(value)
|
|
181
|
-
|
|
182
|
-
options[:radius] = options.delete(:max_distance) if options[:max_distance]
|
|
183
|
-
options[:radius] = options.delete(:max_dist) if options[:max_dist]
|
|
184
|
-
options[:center] = options.delete(:point) if options[:point]
|
|
185
|
-
|
|
186
|
-
unless options[:circle]
|
|
187
|
-
unless options[:center] && options[:radius]
|
|
188
|
-
raise "`near' takes something like {:center => P, :radius => d}"
|
|
189
|
-
end
|
|
190
|
-
{ :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
|
|
156
|
+
case op
|
|
157
|
+
when :defined, :undefined then NoBrainer::Boolean.nobrainer_cast_user_to_model(value)
|
|
158
|
+
when :intersects
|
|
159
|
+
raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base)
|
|
160
|
+
value
|
|
161
|
+
when :near
|
|
162
|
+
# TODO enforce key is a geo type
|
|
163
|
+
case value
|
|
164
|
+
when Hash
|
|
165
|
+
options = NoBrainer::Geo::Base.normalize_geo_options(value)
|
|
166
|
+
|
|
167
|
+
options[:radius] = options.delete(:max_distance) if options[:max_distance]
|
|
168
|
+
options[:radius] = options.delete(:max_dist) if options[:max_dist]
|
|
169
|
+
options[:center] = options.delete(:point) if options[:point]
|
|
170
|
+
|
|
171
|
+
unless options[:circle]
|
|
172
|
+
unless options[:center] && options[:radius]
|
|
173
|
+
raise "`near' takes something like {:center => P, :radius => d}"
|
|
191
174
|
end
|
|
192
|
-
|
|
193
|
-
else raise "Incorrect use of `near': rvalue must be a hash or a circle"
|
|
175
|
+
{ :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
|
|
194
176
|
end
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# 2) Box value in hash if we have a nested query.
|
|
198
|
-
box_value = key_modifier.in?([:any, :all]) || op == :include
|
|
199
|
-
value = [value] if box_value
|
|
200
|
-
value_hash = key_path.reverse.reduce(value) { |v,k| {k => v} }
|
|
201
|
-
value = model.cast_user_to_db_for(*value_hash.first)
|
|
202
|
-
value = key_path[1..-1].reduce(value) { |h,k| h[k] }
|
|
203
|
-
value = value.first if box_value
|
|
204
|
-
value
|
|
177
|
+
when NoBrainer::Geo::Circle then { :circle => value }
|
|
178
|
+
else raise "Incorrect use of `near': rvalue must be a hash or a circle"
|
|
205
179
|
end
|
|
180
|
+
else
|
|
181
|
+
# 1) Box value in array if we have an any/all modifier
|
|
182
|
+
# 2) Box value in hash if we have a nested query.
|
|
183
|
+
box_value = key_modifier.in?([:any, :all]) || op == :include
|
|
184
|
+
value = [value] if box_value
|
|
185
|
+
k_v = key_path.reverse.reduce(value) { |v,k| {k => v} }.first
|
|
186
|
+
k_v = model.association_user_to_model_cast(*k_v)
|
|
187
|
+
value = model.cast_user_to_db_for(*k_v)
|
|
188
|
+
value = key_path[1..-1].reduce(value) { |h,k| h[k] }
|
|
189
|
+
value = value.first if box_value
|
|
190
|
+
value
|
|
206
191
|
end
|
|
207
192
|
end
|
|
208
193
|
|
|
209
|
-
def cast_key_path(
|
|
210
|
-
return
|
|
194
|
+
def cast_key_path(key_path)
|
|
195
|
+
return key_path if casted_values
|
|
211
196
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
197
|
+
if key_path.size == 1
|
|
198
|
+
k, _v = model.association_user_to_model_cast(key_path.first, nil)
|
|
199
|
+
key_path = [k]
|
|
215
200
|
end
|
|
201
|
+
|
|
202
|
+
model.ensure_valid_key!(key_path.first)
|
|
203
|
+
key_path
|
|
216
204
|
end
|
|
217
205
|
end
|
|
218
206
|
|
|
@@ -365,7 +353,7 @@ module NoBrainer::Criteria::Where
|
|
|
365
353
|
end
|
|
366
354
|
|
|
367
355
|
def find_strategy_canonical
|
|
368
|
-
clauses = get_candidate_clauses(:eq, :in, :between, :near, :intersects)
|
|
356
|
+
clauses = get_candidate_clauses(:eq, :in, :between, :near, :intersects, :defined)
|
|
369
357
|
return nil unless clauses.present?
|
|
370
358
|
|
|
371
359
|
usable_indexes = Hash[get_usable_indexes.map { |i| [[i.name], i] }]
|
|
@@ -384,6 +372,11 @@ module NoBrainer::Criteria::Where
|
|
|
384
372
|
[:get_nearest, circle.center.to_rql, circle.options.merge(options)]
|
|
385
373
|
when :eq then [:get_all, [clause.value]]
|
|
386
374
|
when :in then [:get_all, clause.value]
|
|
375
|
+
when :defined then
|
|
376
|
+
next unless clause.value == true
|
|
377
|
+
next unless clause.key_modifier == :scalar && index.multi == false
|
|
378
|
+
[:between, [RethinkDB::RQL.new.minval, RethinkDB::RQL.new.maxval],
|
|
379
|
+
:left_bound => :open, :right_bound => :open]
|
|
387
380
|
when :between then [:between, [clause.value.min, clause.value.max],
|
|
388
381
|
:left_bound => :closed, :right_bound => :closed]
|
|
389
382
|
end
|
|
@@ -21,6 +21,14 @@ module NoBrainer::Document::Association
|
|
|
21
21
|
super
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def association_user_to_model_cast(k,v)
|
|
25
|
+
association = association_metadata[k]
|
|
26
|
+
case association
|
|
27
|
+
when NoBrainer::Document::Association::BelongsTo::Metadata then association.cast_attr(k,v)
|
|
28
|
+
else [k,v]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
METHODS.each do |association|
|
|
25
33
|
define_method(association) do |target, options={}|
|
|
26
34
|
target = target.to_sym
|
|
@@ -2,7 +2,8 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
|
3
3
|
|
|
4
4
|
class Metadata
|
|
5
|
-
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :foreign_key_store_as,
|
|
5
|
+
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :foreign_key_store_as,
|
|
6
|
+
:index, :validates, :required, :uniq, :unique]
|
|
6
7
|
include NoBrainer::Document::Association::Core::Metadata
|
|
7
8
|
include NoBrainer::Document::Association::EagerLoader::Generic
|
|
8
9
|
|
|
@@ -51,12 +52,17 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
51
52
|
unless options[:validates] == false
|
|
52
53
|
owner_model.validates(target_name, options[:validates]) if options[:validates]
|
|
53
54
|
|
|
55
|
+
uniq = options[:uniq] || options[:unique]
|
|
56
|
+
if uniq
|
|
57
|
+
owner_model.validates(foreign_key, :uniqueness => uniq)
|
|
58
|
+
end
|
|
59
|
+
|
|
54
60
|
if options[:required]
|
|
55
61
|
owner_model.validates(target_name, :presence => options[:required])
|
|
56
62
|
else
|
|
57
|
-
# Always validate the
|
|
63
|
+
# Always validate the foreign_key if not nil.
|
|
58
64
|
owner_model.validates_each(foreign_key) do |doc, attr, value|
|
|
59
|
-
if !value.nil? && doc.read_attribute_for_validation(target_name).nil?
|
|
65
|
+
if !value.nil? && value != doc.pk_value && doc.read_attribute_for_validation(target_name).nil?
|
|
60
66
|
doc.errors.add(attr, :invalid_foreign_key, :target_model => target_model, :primary_key => primary_key)
|
|
61
67
|
end
|
|
62
68
|
end
|
|
@@ -68,6 +74,16 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
68
74
|
add_callback_for(:after_validation)
|
|
69
75
|
end
|
|
70
76
|
|
|
77
|
+
def cast_attr(k, v)
|
|
78
|
+
case v
|
|
79
|
+
when target_model then [foreign_key, v.__send__(primary_key)]
|
|
80
|
+
when nil then [foreign_key, nil]
|
|
81
|
+
else
|
|
82
|
+
opts = { :model => owner_model, :attr_name => k, :type => target_model, :value => v }
|
|
83
|
+
raise NoBrainer::Error::InvalidType.new(opts)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
71
87
|
def eager_load_owner_key; foreign_key; end
|
|
72
88
|
def eager_load_target_key; primary_key; end
|
|
73
89
|
end
|
|
@@ -109,7 +125,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
109
125
|
end
|
|
110
126
|
|
|
111
127
|
def after_validation_callback
|
|
112
|
-
if loaded? && target && !target.persisted?
|
|
128
|
+
if loaded? && target && !target.persisted? && target != owner
|
|
113
129
|
raise NoBrainer::Error::AssociationNotPersisted.new("#{target_name} must be saved first")
|
|
114
130
|
end
|
|
115
131
|
end
|
|
@@ -9,7 +9,16 @@ module NoBrainer::Document::Core
|
|
|
9
9
|
extend ActiveModel::Naming
|
|
10
10
|
extend ActiveModel::Translation
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
unless name =~ /^NoBrainer::/
|
|
13
|
+
if NoBrainer::Document::Core._all.map(&:name).include?(name)
|
|
14
|
+
raise "Fatal: The model `#{name}' is already registered and partially loaded.\n" +
|
|
15
|
+
"This may happen when an exception occured while loading the model definitions\n" +
|
|
16
|
+
"(e.g. calling a missing class method on another model, having circular dependencies).\n" +
|
|
17
|
+
"In this situation, ActiveSupport autoloader may retry loading the model.\n" +
|
|
18
|
+
"Try moving all class methods declaration at the top of the model."
|
|
19
|
+
end
|
|
20
|
+
NoBrainer::Document::Core._all << self
|
|
21
|
+
end
|
|
13
22
|
end
|
|
14
23
|
|
|
15
24
|
def self.all(options={})
|
|
@@ -3,7 +3,7 @@ module NoBrainer::Document::PrimaryKey::Generator
|
|
|
3
3
|
|
|
4
4
|
BASE_TABLE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".freeze
|
|
5
5
|
|
|
6
|
-
TIME_OFFSET = Time.
|
|
6
|
+
TIME_OFFSET = Time.utc(2014, 01, 01).to_i
|
|
7
7
|
|
|
8
8
|
# 30 bits timestamp with 1s resolution -> We overflow in year 2048. Good enough.
|
|
9
9
|
# Math.log(Time.parse('2048-01-01').to_f - TIME_OFFSET)/Math.log(2) = 29.999
|
|
@@ -4,12 +4,7 @@ class NoBrainer::Document::TableConfig::Synchronizer
|
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
def sync_table_config(options={})
|
|
7
|
-
|
|
8
|
-
lock = NoBrainer::Lock.new('nobrainer:sync_table_config')
|
|
9
|
-
|
|
10
|
-
lock.synchronize do
|
|
11
|
-
@models.each(&:sync_table_config)
|
|
12
|
-
end
|
|
7
|
+
@models.each(&:sync_table_config)
|
|
13
8
|
|
|
14
9
|
unless options[:wait] == false
|
|
15
10
|
# Waiting on all models due to possible races
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nobrainer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.31.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nicolas Viennot
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2016-02-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rethinkdb
|
|
@@ -237,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
237
237
|
version: '0'
|
|
238
238
|
requirements: []
|
|
239
239
|
rubyforge_project:
|
|
240
|
-
rubygems_version: 2.
|
|
240
|
+
rubygems_version: 2.5.1
|
|
241
241
|
signing_key:
|
|
242
242
|
specification_version: 4
|
|
243
243
|
summary: ORM for RethinkDB
|