nobrainer 0.23.0 → 0.24.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: f77c9211fbf5a8c8fb7a695542b862cfd693ed82
4
- data.tar.gz: 72bd41593dfbb997eb1dd72a70c00c08f75f4124
3
+ metadata.gz: 56337a0aa87999793ca7bc6ed3e1de79ef76b427
4
+ data.tar.gz: 6740efd7304818a5a71638a639b48301d16eb602
5
5
  SHA512:
6
- metadata.gz: fd5ca8440015b18adfc34f61bb7862e15bc445a459cf1c2310843c3c7194862f1d1abecd72c4ce14a7501a6345704fea20f49b332331b227d8ee0309494a36bb
7
- data.tar.gz: cd4d1d5b1e8cb9ee69eed9b6a0956626e407fd3e1c1bf9e60bedd502def6c0adbe8684826afde573dde490ae6db0842cb602eb03b2025166780574bb17c474a5
6
+ metadata.gz: 69a4a851eb9e3a497a2bff26c65933437e3155b23f6573e1a9d7fcaa04980f59964b7d6f59383e3c7b9bb27014ab95eda52ca8be38d4b4f82144d2c389e211b6
7
+ data.tar.gz: 03968de9d8391cd208938420fa19d1aa1f2c76191bf938132f5c04dce33f673e725f5336390c010bb70cf3ff660a79a0b7dd72cc27bca994ba6411445f797948
@@ -32,11 +32,6 @@ module NoBrainer::Criteria::Core
32
32
  to_rql.inspect rescue super
33
33
  end
34
34
 
35
- def run(&block)
36
- block ||= proc { to_rql }
37
- NoBrainer.run(:criteria => self, &block)
38
- end
39
-
40
35
  def merge!(criteria, options={})
41
36
  criteria.options.each do |k,v|
42
37
  merge_proc = self.class.options_definitions[k]
@@ -61,6 +56,11 @@ module NoBrainer::Criteria::Core
61
56
  merge(self.class.new(options), merge_options)
62
57
  end
63
58
 
59
+ def run(&block)
60
+ block ||= proc { to_rql }
61
+ NoBrainer.run(:criteria => self, &block)
62
+ end
63
+
64
64
  def compile_rql_pass1
65
65
  # This method is overriden by other modules.
66
66
  raise "Criteria not bound to a model" unless model
@@ -3,7 +3,7 @@ module NoBrainer::Criteria::Enumerable
3
3
 
4
4
  def each(options={}, &block)
5
5
  return enum_for(:each, options) unless block
6
- self.run.each { |attrs| block.call(instantiate_doc(attrs)) }
6
+ run.each { |attrs| block.call(instantiate_doc(attrs)) }
7
7
  self
8
8
  end
9
9
 
@@ -26,6 +26,6 @@ module NoBrainer::Criteria::First
26
26
  private
27
27
 
28
28
  def get_one(criteria)
29
- instantiate_doc(criteria.limit(1).run.first)
29
+ instantiate_doc(criteria.limit(1).__send__(:run).first)
30
30
  end
31
31
  end
@@ -17,6 +17,8 @@ module NoBrainer::Criteria::OrderBy
17
17
  end
18
18
  end.reduce({}, :merge)
19
19
 
20
+ rules.keys.each { |k| model.ensure_valid_key!(k) unless k.is_a?(Proc) } if model
21
+
20
22
  chain(:order_by => rules, :ordering_mode => :normal)
21
23
  end
22
24
 
@@ -1,6 +1,6 @@
1
1
  module NoBrainer::Criteria::Where
2
- NON_CHAINABLE_OPERATORS = %w(in nin eq ne not gt ge gte lt le lte defined near intersects).map(&:to_sym)
3
- CHAINABLE_OPERATORS = %w(any all).map(&:to_sym)
2
+ NON_CHAINABLE_OPERATORS = %w(in eq gt ge gte lt le lte defined near intersects).map(&:to_sym)
3
+ CHAINABLE_OPERATORS = %w(not any all).map(&:to_sym)
4
4
  OPERATORS = CHAINABLE_OPERATORS + NON_CHAINABLE_OPERATORS
5
5
 
6
6
  require 'symbol_decoration'
@@ -134,12 +134,9 @@ module NoBrainer::Criteria::Where
134
134
  when :in then RethinkDB::RQL.new.expr(value).contains(lvalue)
135
135
  when :intersects then lvalue.intersects(value.to_rql)
136
136
  when :near
137
- options = value.dup
138
- point = options.delete(:point)
139
- max_dist = options.delete(:max_dist)
140
- # XXX max_results is not used, seems to be a workaround of rethinkdb index implemetnation.
141
- _ = options.delete(:max_results)
142
- RethinkDB::RQL.new.distance(lvalue, point.to_rql, options) <= max_dist
137
+ # XXX options[:max_results] is not used, seems to be a workaround of rethinkdb index implementation.
138
+ circle = value[:circle]
139
+ RethinkDB::RQL.new.distance(lvalue, circle.center.to_rql, circle.options) <= circle.radius
143
140
  else lvalue.__send__(op, value)
144
141
  end
145
142
  end
@@ -176,18 +173,23 @@ module NoBrainer::Criteria::Where
176
173
  raise "Use a geo object with `intersects`" unless value.is_a?(NoBrainer::Geo::Base)
177
174
  value
178
175
  when :near
179
- raise "Incorrect use of `near': rvalue must be a hash" unless value.is_a?(Hash)
180
- options = NoBrainer::Geo::Base.normalize_geo_options(value)
181
-
182
- unless options[:point] && options[:max_dist]
183
- raise "`near' takes something like {:point => P, :max_distance => d}"
184
- end
185
-
186
- unless options[:point].is_a?(NoBrainer::Geo::Point)
187
- options[:point] = NoBrainer::Geo::Point.nobrainer_cast_user_to_model(options[:point])
176
+ case value
177
+ when Hash
178
+ options = NoBrainer::Geo::Base.normalize_geo_options(value)
179
+
180
+ options[:radius] = options.delete(:max_distance) if options[:max_distance]
181
+ options[:radius] = options.delete(:max_dist) if options[:max_dist]
182
+ options[:center] = options.delete(:point) if options[:point]
183
+
184
+ unless options[:circle]
185
+ unless options[:center] && options[:radius]
186
+ raise "`near' takes something like {:center => P, :radius => d}"
187
+ end
188
+ { :circle => NoBrainer::Geo::Circle.new(options), :max_results => options[:max_results] }
189
+ end
190
+ when NoBrainer::Geo::Circle then { :circle => value }
191
+ else raise "Incorrect use of `near': rvalue must be a hash or a circle"
188
192
  end
189
-
190
- options
191
193
  else
192
194
  case key_modifier
193
195
  when :scalar then model.cast_user_to_db_for(key, value)
@@ -202,24 +204,28 @@ module NoBrainer::Criteria::Where
202
204
 
203
205
  case association
204
206
  when NoBrainer::Document::Association::BelongsTo::Metadata then association.foreign_key
205
- else ensure_valid_key!(key); key
207
+ else model.ensure_valid_key!(key); key
206
208
  end
207
209
  end
208
-
209
- def ensure_valid_key!(key)
210
- return if model.has_field?(key) || model.has_index?(key) || model < NoBrainer::Document::DynamicAttributes
211
- raise NoBrainer::Error::UnknownAttribute, "`#{key}' is not a declared attribute of #{model}"
212
- end
213
210
  end
214
211
 
215
- class UnaryOperator < Struct.new(:op, :value)
212
+ class UnaryOperator < Struct.new(:op, :clause)
216
213
  def simplify
217
- value.is_a?(UnaryOperator) && [self.op, value.op] == [:not, :not] ? value.value : self
214
+ simplified_clause = self.clause.simplify
215
+
216
+ case simplified_clause
217
+ when UnaryOperator then
218
+ case [self.op, simplified_clause.op]
219
+ when [:not, :not] then simplified_clause.clause
220
+ else self.class.new(op, simplified_clause)
221
+ end
222
+ else self.class.new(op, simplified_clause)
223
+ end
218
224
  end
219
225
 
220
226
  def to_rql(doc)
221
227
  case op
222
- when :not then value.to_rql(doc).not
228
+ when :not then clause.to_rql(doc).not
223
229
  end
224
230
  end
225
231
  end
@@ -256,15 +262,12 @@ module NoBrainer::Criteria::Where
256
262
  when :_and then parse_multi_value(:and, value, :safe => true)
257
263
  when :_or then parse_multi_value(:or, value, :safe => true)
258
264
  when :not then UnaryOperator.new(:not, parse_clause(value))
259
- when String, Symbol then parse_clause_stub_eq(key, value)
265
+ when String, Symbol then instantiate_binary_op(key, :eq, value)
260
266
  when Symbol::Decoration then
261
267
  case key.decorator
262
- when :any, :all then parse_clause_stub_eq(key, value)
263
- when :not, :ne then parse_clause(:not => { key.symbol.eq => value })
264
- when :nin then parse_clause(:not => { key.symbol.in => value })
265
- when :gte then parse_clause(key.symbol.ge => value)
266
- when :lte then parse_clause(key.symbol.le => value)
267
- when :eq then parse_clause_stub_eq(key.symbol, value)
268
+ when :any, :all, :not then instantiate_binary_op(key, :eq, value)
269
+ when :gte then instantiate_binary_op(key.symbol, :ge, value)
270
+ when :lte then instantiate_binary_op(key.symbol, :le, value)
268
271
  else instantiate_binary_op(key.symbol, key.decorator, value)
269
272
  end
270
273
  else raise "Invalid key: #{key}"
@@ -282,14 +285,6 @@ module NoBrainer::Criteria::Where
282
285
  MultiOperator.new(op, value.map { |v| parse_clause(v) })
283
286
  end
284
287
 
285
- def parse_clause_stub_eq(key, value)
286
- case value
287
- when Range then instantiate_binary_op(key, :between, value)
288
- when Regexp then instantiate_binary_op(key, :match, translate_regexp_to_re2_syntax(value))
289
- else instantiate_binary_op(key, :eq, value)
290
- end
291
- end
292
-
293
288
  def translate_regexp_to_re2_syntax(value)
294
289
  # Ruby always uses what RE2 calls "multiline mode" (the "m" flag),
295
290
  # meaning that "foo\nbar" matches /^bar$/.
@@ -304,8 +299,19 @@ module NoBrainer::Criteria::Where
304
299
  end
305
300
 
306
301
  def instantiate_binary_op(key, op, value)
302
+ op, value = case value
303
+ when Range then [:between, value]
304
+ when Regexp then [:match, translate_regexp_to_re2_syntax(value)]
305
+ else [:eq, value]
306
+ end if op == :eq
307
+
307
308
  case key
308
- when Symbol::Decoration then BinaryOperator.new(key.symbol, key.decorator, op, value, self.model)
309
+ when Symbol::Decoration
310
+ raise "Use only one .not, .all or .any modifiers in the query" if key.symbol.is_a?(Symbol::Decoration)
311
+ case key.decorator
312
+ when :any, :all then BinaryOperator.new(key.symbol, key.decorator, op, value, self.model)
313
+ when :not then UnaryOperator.new(:not, BinaryOperator.new(key.symbol, :scalar, op, value, self.model))
314
+ end
309
315
  else BinaryOperator.new(key, :scalar, op, value, self.model)
310
316
  end
311
317
  end
@@ -319,12 +325,12 @@ module NoBrainer::Criteria::Where
319
325
 
320
326
  def rql_proc
321
327
  lambda do |rql|
328
+ return RethinkDB::RQL.new.expr([]) if rql_op == :get_all && rql_args.empty?
329
+
322
330
  opt = (rql_options || {}).merge(:index => index.aliased_name)
323
331
  r = rql.__send__(rql_op, *rql_args, opt)
324
332
  r = r.map { |i| i['doc'] } if rql_op == :get_nearest
325
- # TODO distinct: waiting for issue #3345
326
- # TODO coerce_to: waiting for issue #3346
327
- r = r.coerce_to('array').distinct if index.multi && !index_finder.criteria.options[:without_distinct]
333
+ r = r.distinct if index.multi && !index_finder.criteria.options[:without_distinct]
328
334
  r
329
335
  end
330
336
  end
@@ -357,8 +363,10 @@ module NoBrainer::Criteria::Where
357
363
  when :intersects then [:get_intersecting, clause.value.to_rql]
358
364
  when :near
359
365
  options = clause.value.dup
360
- point = options.delete(:point)
361
- [:get_nearest, point.to_rql, options]
366
+ circle = options.delete(:circle)
367
+ options.delete(:max_results) if options[:max_results].nil?
368
+ options[:max_dist] = circle.radius
369
+ [:get_nearest, circle.center.to_rql, circle.options.merge(options)]
362
370
  when :eq then [:get_all, [clause.value]]
363
371
  when :in then [:get_all, clause.value]
364
372
  when :between then [:between, [clause.value.min, clause.value.max],
@@ -5,7 +5,7 @@ module NoBrainer::Document
5
5
  extend NoBrainer::Autoload
6
6
 
7
7
  autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
8
- :Persistance, :Validation, :Types, :Callbacks, :Dirty, :PrimaryKey,
8
+ :Persistance, :Callbacks, :Validation, :Types, :Dirty, :PrimaryKey,
9
9
  :Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
10
10
  :MissingAttributes, :LazyFetch, :AtomicOps
11
11
 
@@ -15,7 +15,7 @@ class NoBrainer::Document::Association::BelongsTo
15
15
  end
16
16
 
17
17
  def target_model
18
- (options[:class_name] || target_name.to_s.camelize).constantize
18
+ get_model_by_name(options[:class_name] || target_name.to_s.camelize)
19
19
  end
20
20
 
21
21
  def base_criteria
@@ -24,14 +24,18 @@ class NoBrainer::Document::Association::BelongsTo
24
24
 
25
25
  def hook
26
26
  super
27
- # XXX We are loading the target_model unless the primary_key is not
28
- # specified. This may eager load a part of the application.
29
- # Oh well.
27
+ # XXX We are loading the target_model unless the primary_key is specified.
28
+ # This may eager load a part of the application Oh well.
30
29
 
31
30
  # TODO if the primary key of the target_model changes, we need to revisit
32
31
  # our default foreign_key/primary_key value
33
32
 
34
33
  # TODO set the type of the foreign key to be the same as the target's primary key
34
+ if owner_model.association_metadata.values.any? { |assoc|
35
+ assoc.is_a?(self.class) && assoc != self && assoc.foreign_key == foreign_key }
36
+ raise "Cannot declare `#{target_name}' in #{owner_model}: the foreign_key `#{foreign_key}' is already used"
37
+ end
38
+
35
39
  owner_model.field(foreign_key, :store_as => options[:foreign_key_store_as], :index => options[:index])
36
40
 
37
41
  unless options[:validates] == false
@@ -46,6 +46,17 @@ module NoBrainer::Document::Association::Core
46
46
  end
47
47
  RUBY
48
48
  end
49
+
50
+ def get_model_by_name(model_name)
51
+ return model_name if model_name.is_a?(Module)
52
+
53
+ model_name = model_name.to_s
54
+ current_module = @owner_model.parent
55
+ return model_name.constantize if current_module == Object
56
+ return model_name.constantize if model_name =~ /^::/
57
+ return model_name.constantize if !current_module.const_defined?(model_name)
58
+ current_module.const_get(model_name)
59
+ end
49
60
  end
50
61
 
51
62
  included { attr_accessor :metadata, :owner }
@@ -7,7 +7,7 @@ class NoBrainer::Document::Association::HasMany
7
7
  extend NoBrainer::Document::Association::EagerLoader::Generic
8
8
 
9
9
  def foreign_key
10
- options[:foreign_key].try(:to_sym) || :"#{owner_model.name.underscore}_#{primary_key}"
10
+ options[:foreign_key].try(:to_sym) || :"#{owner_model.name.split('::').last.underscore}_#{primary_key}"
11
11
  end
12
12
 
13
13
  def primary_key
@@ -15,7 +15,7 @@ class NoBrainer::Document::Association::HasMany
15
15
  end
16
16
 
17
17
  def target_model
18
- (options[:class_name] || target_name.to_s.singularize.camelize).constantize
18
+ get_model_by_name(options[:class_name] || target_name.to_s.singularize.camelize)
19
19
  end
20
20
 
21
21
  def base_criteria
@@ -1,7 +1,7 @@
1
1
  class NoBrainer::Document::Association::HasOne < NoBrainer::Document::Association::HasMany
2
2
  class Metadata < NoBrainer::Document::Association::HasMany::Metadata
3
3
  def target_model
4
- (options[:class_name] || target_name.to_s.camelize).constantize
4
+ get_model_by_name(options[:class_name] || target_name.to_s.camelize)
5
5
  end
6
6
  end
7
7
 
@@ -157,5 +157,10 @@ module NoBrainer::Document::Attributes
157
157
  def has_field?(attr)
158
158
  !!fields[attr.to_sym]
159
159
  end
160
+
161
+ def ensure_valid_key!(key)
162
+ return if has_field?(key) || has_index?(key)
163
+ raise NoBrainer::Error::UnknownAttribute, "`#{key}' is not a declared attribute of #{self}"
164
+ end
160
165
  end
161
166
  end
@@ -20,7 +20,7 @@ module NoBrainer::Document::Callbacks
20
20
  run_callbacks(:update) { super }
21
21
  end
22
22
 
23
- def _save?(*args, &block)
23
+ def save?(*args, &block)
24
24
  run_callbacks(:save) { super }
25
25
  end
26
26
 
@@ -12,4 +12,10 @@ module NoBrainer::Document::DynamicAttributes
12
12
  def readable_attributes
13
13
  @_attributes.keys
14
14
  end
15
+
16
+ module ClassMethods
17
+ def ensure_valid_key!(key)
18
+ # we never raise
19
+ end
20
+ end
15
21
  end
@@ -60,12 +60,11 @@ module NoBrainer::Document::Persistance
60
60
  end
61
61
 
62
62
  def _create(options={})
63
- return false if options[:validate] != false && !valid?(nil, :clear_errors => false)
64
-
65
63
  attrs = self.class.persistable_attributes(@_attributes, :instance => self)
66
64
  result = NoBrainer.run(self.class.rql_table.insert(attrs))
67
65
  self.pk_value ||= result['generated_keys'].to_a.first
68
66
  @new_record = false
67
+ unlock_unique_fields # just an optimization for the uniquness validation
69
68
  true
70
69
  end
71
70
 
@@ -75,8 +74,6 @@ module NoBrainer::Document::Persistance
75
74
  end
76
75
 
77
76
  def _update_only_changed_attrs(options={})
78
- return false if options[:validate] != false && !valid?(nil, :clear_errors => false)
79
-
80
77
  # We won't be using the `changes` values, because they went through
81
78
  # read_attribute(), and we want the raw values.
82
79
  attrs = Hash[self.changed.map do |k|
@@ -87,26 +84,24 @@ module NoBrainer::Document::Persistance
87
84
  [k, attr]
88
85
  end]
89
86
  _update(attrs) if attrs.present?
87
+ unlock_unique_fields # just an optimization for the uniquness validation
90
88
  true
91
89
  end
92
90
 
93
- def _save?(options)
91
+ def _save?(options={})
94
92
  new_record? ? _create(options) : _update_only_changed_attrs(options)
95
93
  end
96
94
 
97
95
  def save?(options={})
98
- errors.clear
99
96
  _save?(options)
100
97
  end
101
98
 
102
- def save(*args)
99
+ def save!(*args)
103
100
  save?(*args) or raise NoBrainer::Error::DocumentInvalid, self
104
- nil
105
101
  end
106
102
 
107
- def save!(*args)
108
- save(*args)
109
- :you_should_be_using_the_non_bang_version_of_save
103
+ def save(*args)
104
+ save?(*args)
110
105
  end
111
106
 
112
107
  def update?(attrs, options={})
@@ -114,27 +109,13 @@ module NoBrainer::Document::Persistance
114
109
  save?(options)
115
110
  end
116
111
 
117
- def update(*args)
118
- update?(*args) or raise NoBrainer::Error::DocumentInvalid, self
119
- nil
120
- end
121
-
122
112
  def update!(*args)
123
- update(*args)
124
- :you_should_be_using_the_non_bang_version_of_update
113
+ update?(*args) or raise NoBrainer::Error::DocumentInvalid, self
125
114
  end
126
115
  alias_method :update_attributes!, :update!
127
116
 
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" }
117
+ def update(*args)
118
+ update?(*args)
138
119
  end
139
120
 
140
121
  def delete
@@ -152,9 +133,12 @@ module NoBrainer::Document::Persistance
152
133
 
153
134
  module ClassMethods
154
135
  def create(attrs={}, options={})
155
- new(attrs, options).tap { |doc| doc.save(options) }
136
+ new(attrs, options).tap { |doc| doc.save?(options) }
137
+ end
138
+
139
+ def create!(attrs={}, options={})
140
+ new(attrs, options).tap { |doc| doc.save!(options) }
156
141
  end
157
- alias_method :create!, :create
158
142
 
159
143
  def insert_all(*args)
160
144
  docs = args.shift
@@ -1,63 +1,6 @@
1
1
  module NoBrainer::Document::Validation
2
2
  extend NoBrainer::Autoload
3
3
  extend ActiveSupport::Concern
4
- include ActiveModel::Validations
5
- include ActiveModel::Validations::Callbacks
6
4
 
7
- autoload_and_include :Uniqueness, :NotNull
8
-
9
- included do
10
- # We don't want before_validation returning false to halt the chain.
11
- define_callbacks :validation, :skip_after_callbacks_if_terminated => true,
12
- :scope => [:kind, :name], :terminator => proc { false }
13
- end
14
-
15
- def valid?(context=nil, options={})
16
- context ||= new_record? ? :create : :update
17
-
18
- # XXX Monkey Patching, because we need to have control on errors.clear
19
- current_context, self.validation_context = validation_context, context
20
- errors.clear unless options[:clear_errors] == false
21
- run_validations!
22
- ensure
23
- self.validation_context = current_context
24
- end
25
-
26
- SHORTHANDS = { :format => :format, :length => :length, :required => :presence,
27
- :uniq => :uniqueness, :unique => :uniqueness, :in => :inclusion }
28
-
29
- module ClassMethods
30
- def _field(attr, options={})
31
- super
32
-
33
- shorthands = SHORTHANDS
34
- shorthands = shorthands.merge(:required => :not_null) if options[:type] == NoBrainer::Boolean
35
- shorthands.each { |k,v| validates(attr, v => options[k]) if options.has_key?(k) }
36
-
37
- validates(attr, options[:validates]) if options[:validates]
38
- validates(attr, :length => { :minimum => options[:min_length] }) if options[:min_length]
39
- validates(attr, :length => { :maximum => options[:max_length] }) if options[:max_length]
40
- end
41
- end
42
- end
43
-
44
- class ActiveModel::EachValidator
45
- def should_validate_field?(record, attribute)
46
- return true unless record.is_a?(NoBrainer::Document)
47
- return true if record.new_record?
48
-
49
- attr_changed = "#{attribute}_changed?"
50
- return record.respond_to?(attr_changed) ? record.__send__(attr_changed) : true
51
- end
52
-
53
- # XXX Monkey Patching :(
54
- def validate(record)
55
- attributes.each do |attribute|
56
- next unless should_validate_field?(record, attribute) # <--- Added
57
- value = record.read_attribute_for_validation(attribute)
58
- next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- Added
59
- next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
60
- validate_each(record, attribute, value)
61
- end
62
- end
5
+ autoload_and_include :Core, :Uniqueness, :NotNull
63
6
  end
@@ -0,0 +1,63 @@
1
+ module NoBrainer::Document::Validation::Core
2
+ extend ActiveSupport::Concern
3
+ include ActiveModel::Validations
4
+ include ActiveModel::Validations::Callbacks
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,
9
+ :scope => [:kind, :name], :terminator => proc { false }
10
+ end
11
+
12
+ def valid?(context=nil, options={})
13
+ super(context || (new_record? ? :create : :update))
14
+ end
15
+
16
+ def save?(options={})
17
+ options = { :validate => true }.merge(options)
18
+
19
+ if options[:validate]
20
+ valid? ? super : false
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ SHORTHANDS = { :format => :format, :length => :length, :required => :presence,
27
+ :uniq => :uniqueness, :unique => :uniqueness, :in => :inclusion }
28
+
29
+ module ClassMethods
30
+ def _field(attr, options={})
31
+ super
32
+
33
+ shorthands = SHORTHANDS
34
+ shorthands = shorthands.merge(:required => :not_null) if options[:type] == NoBrainer::Boolean
35
+ shorthands.each { |k,v| validates(attr, v => options[k]) if options.has_key?(k) }
36
+
37
+ validates(attr, options[:validates]) if options[:validates]
38
+ validates(attr, :length => { :minimum => options[:min_length] }) if options[:min_length]
39
+ validates(attr, :length => { :maximum => options[:max_length] }) if options[:max_length]
40
+ end
41
+ end
42
+ end
43
+
44
+ class ActiveModel::EachValidator
45
+ def should_validate_field?(record, attribute)
46
+ return true unless record.is_a?(NoBrainer::Document)
47
+ return true if record.new_record?
48
+
49
+ attr_changed = "#{attribute}_changed?"
50
+ return record.respond_to?(attr_changed) ? record.__send__(attr_changed) : true
51
+ end
52
+
53
+ # XXX Monkey Patching :(
54
+ def validate(record)
55
+ attributes.each do |attribute|
56
+ next unless should_validate_field?(record, attribute) # <--- Added
57
+ value = record.read_attribute_for_validation(attribute)
58
+ next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- Added
59
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
60
+ validate_each(record, attribute, value)
61
+ end
62
+ end
63
+ end
@@ -1,14 +1,7 @@
1
1
  module NoBrainer::Document::Validation::Uniqueness
2
2
  extend ActiveSupport::Concern
3
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={})
4
+ def save?(options={})
12
5
  lock_unique_fields
13
6
  super
14
7
  ensure
@@ -93,7 +86,7 @@ module NoBrainer::Document::Validation::Uniqueness
93
86
  end
94
87
 
95
88
  def exclude_doc(criteria, doc)
96
- criteria.where(doc.class.pk_name.ne => doc.pk_value)
89
+ criteria.where(doc.class.pk_name.not => doc.pk_value)
97
90
  end
98
91
  end
99
92
  end
@@ -9,7 +9,6 @@ module NoBrainer::Geo::Base
9
9
 
10
10
  options[:unit] = unit if unit && unit.to_s != 'm'
11
11
  options[:geo_system] = geo_system if geo_system && geo_system.to_s != 'WGS84'
12
- options[:max_dist] = options.delete(:max_distance) if options[:max_distance]
13
12
 
14
13
  options
15
14
  end
data/lib/nobrainer.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'set'
2
2
  require 'active_support'
3
+ require 'active_model'
3
4
  require 'thread'
4
5
  %w(module/delegation module/attribute_accessors module/introspection
5
6
  class/attribute object/blank object/inclusion object/deep_dup
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.23.0
4
+ version: 0.24.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-04-19 00:00:00.000000000 Z
11
+ date: 2015-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -158,6 +158,7 @@ files:
158
158
  - lib/no_brainer/document/types/text.rb
159
159
  - lib/no_brainer/document/types/time.rb
160
160
  - lib/no_brainer/document/validation.rb
161
+ - lib/no_brainer/document/validation/core.rb
161
162
  - lib/no_brainer/document/validation/not_null.rb
162
163
  - lib/no_brainer/document/validation/uniqueness.rb
163
164
  - lib/no_brainer/error.rb