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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ebe188d50e06d6ef907cdf043560143e3bd7d703
4
- data.tar.gz: 15b1580f27a66b6670386cc117b4767f930e67c7
3
+ metadata.gz: 657fd4dc2ef9b4dd9383ea99fa14b99d386e19ad
4
+ data.tar.gz: 63b01fce35c08ffed01ff23496a5cc7eb394e1ab
5
5
  SHA512:
6
- metadata.gz: ff9b387f2045109c33efb88d36273325edf34573acb134bbb94d3d2e04c1efd6fdf65b761739eac61c185309cfa9deba5a07987fdf56becf3d78308b300838b9
7
- data.tar.gz: acb0f33b44d27ac25599352b0d568dd563e13a81da91b0dfef6cf72596a1500026e739f2cc64c63e22edc72ddb57ee3b61a70acf72c139cd502f0b1d95e78c9b
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
- attrs = attrs.symbolize_keys
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? ? doc : doc.merge(field => field_rql)
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 association
163
- when NoBrainer::Document::Association::BelongsTo::Metadata
164
- target_model = association.target_model
165
- unless value.is_a?(target_model)
166
- opts = { :model => model, :attr_name => key_path.first, :type => target_model, :value => value }
167
- raise NoBrainer::Error::InvalidType.new(opts)
168
- end
169
- value.pk_value
170
- else
171
- case op
172
- when :defined, :undefined then NoBrainer::Boolean.nobrainer_cast_user_to_model(value)
173
- when :intersects
174
- raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base)
175
- value
176
- when :near
177
- # TODO enforce key is a geo type
178
- case value
179
- when Hash
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
- when NoBrainer::Geo::Circle then { :circle => value }
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
- else
196
- # 1) Box value in array if we have an any/all modifier
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(key)
210
- return key if casted_values
194
+ def cast_key_path(key_path)
195
+ return key_path if casted_values
211
196
 
212
- case association
213
- when NoBrainer::Document::Association::BelongsTo::Metadata then [association.foreign_key]
214
- else model.ensure_valid_key!(key_path.first); key_path
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, :index, :validates, :required]
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 validity of the foreign_key if not nil.
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
- NoBrainer::Document::Core._all << self unless name =~ /^NoBrainer::/
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={})
@@ -49,6 +49,7 @@ module NoBrainer::Document::LazyFetch
49
49
  raise e unless attr.in?(@lazy_fetch)
50
50
  reload(:pluck => attr, :keep_ivars => true)
51
51
  @lazy_fetch.delete(attr)
52
+ clear_missing_field(attr)
52
53
  retry
53
54
  end
54
55
  end
@@ -112,6 +112,7 @@ module NoBrainer::Document::Persistance
112
112
  def update(*args)
113
113
  update?(*args)
114
114
  end
115
+ alias_method :update_attributes, :update # for API compat like devise
115
116
 
116
117
  def delete
117
118
  unless @destroyed
@@ -3,7 +3,7 @@ module NoBrainer::Document::PrimaryKey::Generator
3
3
 
4
4
  BASE_TABLE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".freeze
5
5
 
6
- TIME_OFFSET = Time.parse('2014-01-01').to_i
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
- # XXX A bit funny since we might touch the lock table...
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.30.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: 2015-10-03 00:00:00.000000000 Z
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.4.6
240
+ rubygems_version: 2.5.1
241
241
  signing_key:
242
242
  specification_version: 4
243
243
  summary: ORM for RethinkDB