nobrainer 0.30.0 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
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