nobrainer 0.23.0 → 0.24.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: 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