nobrainer 0.18.0 → 0.22.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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/autoload.rb +0 -5
  3. data/lib/no_brainer/config.rb +73 -39
  4. data/lib/no_brainer/connection.rb +2 -4
  5. data/lib/no_brainer/criteria/after_find.rb +3 -11
  6. data/lib/no_brainer/criteria/aggregate.rb +2 -2
  7. data/lib/no_brainer/criteria/cache.rb +15 -10
  8. data/lib/no_brainer/criteria/core.rb +46 -11
  9. data/lib/no_brainer/criteria/delete.rb +2 -2
  10. data/lib/no_brainer/criteria/eager_load.rb +51 -0
  11. data/lib/no_brainer/criteria/extend.rb +4 -16
  12. data/lib/no_brainer/criteria/find.rb +27 -0
  13. data/lib/no_brainer/criteria/index.rb +7 -13
  14. data/lib/no_brainer/criteria/limit.rb +5 -12
  15. data/lib/no_brainer/criteria/order_by.rb +20 -36
  16. data/lib/no_brainer/criteria/pluck.rb +16 -22
  17. data/lib/no_brainer/criteria/raw.rb +4 -10
  18. data/lib/no_brainer/criteria/scope.rb +6 -19
  19. data/lib/no_brainer/criteria/update.rb +8 -6
  20. data/lib/no_brainer/criteria/where.rb +252 -138
  21. data/lib/no_brainer/criteria.rb +3 -2
  22. data/lib/no_brainer/document/aliases.rb +3 -3
  23. data/lib/no_brainer/document/association/belongs_to.rb +9 -5
  24. data/lib/no_brainer/document/association/core.rb +6 -5
  25. data/lib/no_brainer/document/association/eager_loader.rb +9 -9
  26. data/lib/no_brainer/document/association/has_many.rb +23 -9
  27. data/lib/no_brainer/document/association/has_many_through.rb +12 -3
  28. data/lib/no_brainer/document/atomic_ops.rb +79 -78
  29. data/lib/no_brainer/document/attributes.rb +24 -20
  30. data/lib/no_brainer/document/callbacks.rb +1 -1
  31. data/lib/no_brainer/document/core.rb +5 -2
  32. data/lib/no_brainer/document/criteria.rb +14 -19
  33. data/lib/no_brainer/document/dirty.rb +11 -16
  34. data/lib/no_brainer/document/index/index.rb +2 -1
  35. data/lib/no_brainer/document/index/meta_store.rb +1 -1
  36. data/lib/no_brainer/document/index.rb +14 -10
  37. data/lib/no_brainer/document/persistance.rb +24 -13
  38. data/lib/no_brainer/document/primary_key/generator.rb +83 -0
  39. data/lib/no_brainer/document/{id.rb → primary_key.rb} +9 -36
  40. data/lib/no_brainer/document/store_in.rb +2 -2
  41. data/lib/no_brainer/document/timestamps.rb +4 -2
  42. data/lib/no_brainer/document/types/binary.rb +2 -7
  43. data/lib/no_brainer/document/types/boolean.rb +2 -4
  44. data/lib/no_brainer/document/types/date.rb +2 -2
  45. data/lib/no_brainer/document/types/float.rb +2 -2
  46. data/lib/no_brainer/document/types/geo.rb +1 -0
  47. data/lib/no_brainer/document/types/integer.rb +2 -2
  48. data/lib/no_brainer/document/types/set.rb +2 -2
  49. data/lib/no_brainer/document/types/string.rb +5 -2
  50. data/lib/no_brainer/document/types/symbol.rb +2 -2
  51. data/lib/no_brainer/document/types/text.rb +18 -0
  52. data/lib/no_brainer/document/types/time.rb +2 -2
  53. data/lib/no_brainer/document/types.rb +17 -18
  54. data/lib/no_brainer/document/validation/not_null.rb +15 -0
  55. data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -11
  56. data/lib/no_brainer/document/validation.rb +35 -6
  57. data/lib/no_brainer/document.rb +1 -1
  58. data/lib/no_brainer/error.rb +21 -19
  59. data/lib/no_brainer/geo/base.rb +16 -0
  60. data/lib/no_brainer/geo/circle.rb +25 -0
  61. data/lib/no_brainer/geo/line_string.rb +11 -0
  62. data/lib/no_brainer/geo/point.rb +49 -0
  63. data/lib/no_brainer/geo/polygon.rb +11 -0
  64. data/lib/no_brainer/geo.rb +4 -0
  65. data/lib/no_brainer/locale/en.yml +1 -0
  66. data/lib/no_brainer/lock.rb +114 -0
  67. data/lib/no_brainer/query_runner/connection_lock.rb +1 -1
  68. data/lib/no_brainer/query_runner/database_on_demand.rb +0 -1
  69. data/lib/no_brainer/query_runner/missing_index.rb +1 -1
  70. data/lib/no_brainer/query_runner/reconnect.rb +9 -11
  71. data/lib/no_brainer/query_runner/run_options.rb +0 -3
  72. data/lib/no_brainer/query_runner/table_on_demand.rb +3 -4
  73. data/lib/no_brainer/railtie/database.rake +2 -2
  74. data/lib/no_brainer/rql.rb +1 -5
  75. data/lib/nobrainer.rb +2 -6
  76. data/lib/rails/generators/nobrainer.rb +1 -1
  77. metadata +34 -9
  78. data/lib/no_brainer/criteria/preload.rb +0 -50
  79. data/lib/no_brainer/decorated_symbol.rb +0 -17
@@ -24,7 +24,7 @@ class NoBrainer::Document::Index::MetaStore
24
24
  def self.on(db_name, &block)
25
25
  old_db_name = Thread.current[:nobrainer_meta_store_db]
26
26
  Thread.current[:nobrainer_meta_store_db] = db_name
27
- NoBrainer.with(:auto_create_tables => true) { block.call }
27
+ block.call
28
28
  ensure
29
29
  Thread.current[:nobrainer_meta_store_db] = old_db_name
30
30
  end
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Document::Index
2
- VALID_INDEX_OPTIONS = [:external, :geo, :multi, :as]
2
+ VALID_INDEX_OPTIONS = [:external, :geo, :multi, :store_as]
3
3
  extend ActiveSupport::Concern
4
4
  extend NoBrainer::Autoload
5
5
 
@@ -33,12 +33,16 @@ module NoBrainer::Document::Index
33
33
  raise "Cannot reuse field name #{name}"
34
34
  end
35
35
 
36
- as = options.delete(:as)
37
- as ||= fields[name][:as] if has_field?(name)
38
- as ||= name
39
- as = as.to_sym
36
+ if kind == :compound && what.size < 2
37
+ raise "Compound indexes only make sense with 2 or more fields"
38
+ end
39
+
40
+ store_as = options.delete(:store_as)
41
+ store_as ||= fields[name][:store_as] if has_field?(name)
42
+ store_as ||= name
43
+ store_as = store_as.to_sym
40
44
 
41
- indexes[name] = NoBrainer::Document::Index::Index.new(self.root_class, name, as,
45
+ indexes[name] = NoBrainer::Document::Index::Index.new(self.root_class, name, store_as,
42
46
  kind, what, options[:external], options[:geo], options[:multi], nil)
43
47
  end
44
48
 
@@ -57,12 +61,12 @@ module NoBrainer::Document::Index
57
61
 
58
62
  super
59
63
 
60
- as = {:as => options[:as]}
64
+ store_as = {:store_as => options[:store_as]}
61
65
  case options[:index]
62
66
  when nil then
63
- when Hash then index(attr, as.merge(options[:index]))
64
- when Symbol then index(attr, as.merge(options[:index] => true))
65
- when true then index(attr, as)
67
+ when Hash then index(attr, store_as.merge(options[:index]))
68
+ when Symbol then index(attr, store_as.merge(options[:index] => true))
69
+ when true then index(attr, store_as)
66
70
  when false then remove_index(attr)
67
71
  end
68
72
  end
@@ -29,7 +29,7 @@ module NoBrainer::Document::Persistance
29
29
 
30
30
  def _reload(options={})
31
31
  attrs = NoBrainer.run { _reload_selector(options) }
32
- raise NoBrainer::Error::DocumentNotFound, "#{self.class} #{self.class.pk_name}: #{pk_value} not found" unless attrs
32
+ raise NoBrainer::Error::DocumentNotFound, "#{self.class} :#{self.class.pk_name}=>\"#{pk_value}\" not found" unless attrs
33
33
 
34
34
  options = options.merge(:pristine => true, :from_db => true)
35
35
 
@@ -45,16 +45,16 @@ module NoBrainer::Document::Persistance
45
45
 
46
46
  def reload(options={})
47
47
  [:without, :pluck].each do |type|
48
- if v = options.delete(type)
49
- v = Hash[v.flatten.map { |k| [k, true] }] if v.is_a?(Array)
50
- v = {v => true} if !v.is_a?(Hash)
51
- v = v.select { |k,_v| _v }
52
- v = v.with_indifferent_access
53
- next unless v.present?
54
-
55
- options[:missing_attributes] ||= {}
56
- options[:missing_attributes][type] = v
57
- end
48
+ next unless v = options.delete(type)
49
+
50
+ v = Hash[v.flatten.map { |k| [k, true] }] if v.is_a?(Array)
51
+ v = {v => true} unless v.is_a?(Hash)
52
+ v = v.select { |k,_v| _v }
53
+ v = v.with_indifferent_access
54
+ next unless v.present?
55
+
56
+ options[:missing_attributes] ||= {}
57
+ options[:missing_attributes][type] = v
58
58
  end
59
59
  _reload(options)
60
60
  end
@@ -113,13 +113,11 @@ module NoBrainer::Document::Persistance
113
113
  assign_attributes(attrs, options)
114
114
  save?(options)
115
115
  end
116
- alias_method :update_attributes?, :update?
117
116
 
118
117
  def update(*args)
119
118
  update?(*args) or raise NoBrainer::Error::DocumentInvalid, self
120
119
  nil
121
120
  end
122
- alias_method :update_attributes, :update
123
121
 
124
122
  def update!(*args)
125
123
  update(*args)
@@ -127,6 +125,18 @@ module NoBrainer::Document::Persistance
127
125
  end
128
126
  alias_method :update_attributes!, :update!
129
127
 
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" }
138
+ end
139
+
130
140
  def delete
131
141
  unless @destroyed
132
142
  NoBrainer.run { selector.delete }
@@ -144,6 +154,7 @@ module NoBrainer::Document::Persistance
144
154
  def create(attrs={}, options={})
145
155
  new(attrs, options).tap { |doc| doc.save(options) }
146
156
  end
157
+ alias_method :create!, :create
147
158
 
148
159
  def insert_all(*args)
149
160
  docs = args.shift
@@ -0,0 +1,83 @@
1
+ module NoBrainer::Document::PrimaryKey::Generator
2
+ class Retry < RuntimeError; end
3
+
4
+ BASE_TABLE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".freeze
5
+
6
+ TIME_OFFSET = Time.parse('2014-01-01').to_i
7
+
8
+ # 30 bits timestamp with 1s resolution -> We overflow in year 2048. Good enough.
9
+ # Math.log(Time.parse('2048-01-01').to_f - TIME_OFFSET)/Math.log(2) = 29.999
10
+ TIMESTAMP_BITS = 30
11
+
12
+ # 14 bits of sequence number. max 16k values per 1s slices.
13
+ # We want something >10k because we want to be able to do high speed inserts
14
+ # on a single process for future benchmarks.
15
+ SEQUENCE_BITS = 14
16
+
17
+ # 24 bits of machine id
18
+ # 0.1% of chance to have a collision with 183 servers:
19
+ # Math.sqrt(-2*(2**24)*Math.log(0.999)) = 183.2
20
+ # 1% of chance to have a collision with ~580 servers.
21
+ # When using more than 500 machines, it's therefore a good
22
+ # idea to set the machine_id manually to avoid collisions.
23
+ MACHINE_ID_BITS = 24
24
+
25
+ # 15 bits for the current pid. We wouldn't need it if the sequence number was
26
+ # on a piece of shared memory :)
27
+ PID_BITS = 15
28
+
29
+ # Total: 83 bits
30
+ # We need at most 14 digits in [A-Za-z0-9] to represent 83 bits:
31
+ # Math.log(62**14)/Math.log(2) = 83.35
32
+ ID_STR_LENGTH = 14
33
+
34
+ TIMESTAMP_MASK = (1 << TIMESTAMP_BITS)-1
35
+ SEQUENCE_MASK = (1 << SEQUENCE_BITS)-1
36
+ MACHINE_ID_MASK = (1 << MACHINE_ID_BITS)-1
37
+ PID_MASK = (1 << PID_BITS)-1
38
+
39
+ PID_SHIFT = 0
40
+ MACHINE_ID_SHIFT = PID_SHIFT + PID_BITS
41
+ SEQUENCE_SHIFT = MACHINE_ID_SHIFT + MACHINE_ID_BITS
42
+ TIMESTAMP_SHIFT = SEQUENCE_SHIFT + SEQUENCE_BITS
43
+
44
+ def self._generate
45
+ timestamp = (Time.now.to_i - TIME_OFFSET) & TIMESTAMP_MASK
46
+
47
+ unless @last_timestamp == timestamp
48
+ # more noise is better in the ID, but we prefer to avoid
49
+ # wrapping the sequences so that Model.last on a single
50
+ # machine returns the latest created document.
51
+ @first_sequence = sequence = rand(SEQUENCE_MASK/2)
52
+ @last_timestamp = timestamp
53
+ else
54
+ sequence = (@sequence + 1) & SEQUENCE_MASK
55
+ raise Retry if @first_sequence == sequence
56
+ end
57
+ @sequence = sequence
58
+
59
+ machine_id = NoBrainer::Config.machine_id & MACHINE_ID_MASK
60
+
61
+ pid = Process.pid & PID_MASK
62
+
63
+ (timestamp << TIMESTAMP_SHIFT) | (sequence << SEQUENCE_SHIFT) |
64
+ (machine_id << MACHINE_ID_SHIFT) | (pid << PID_SHIFT)
65
+ rescue Retry
66
+ sleep 0.1
67
+ retry
68
+ end
69
+
70
+ def self.convert_to_alphanum(id)
71
+ result = []
72
+ until id.zero?
73
+ id, r = id.divmod(BASE_TABLE.size)
74
+ result << BASE_TABLE[r]
75
+ end
76
+ result.reverse.join.rjust(ID_STR_LENGTH, BASE_TABLE[0])
77
+ end
78
+
79
+ @lock = Mutex.new
80
+ def self.generate
81
+ convert_to_alphanum(@lock.synchronize { _generate })
82
+ end
83
+ end
@@ -1,8 +1,7 @@
1
- require 'thread'
2
- require 'socket'
3
- require 'digest/md5'
1
+ module NoBrainer::Document::PrimaryKey
2
+ extend NoBrainer::Autoload
3
+ autoload :Generator
4
4
 
5
- module NoBrainer::Document::Id
6
5
  extend ActiveSupport::Concern
7
6
 
8
7
  DEFAULT_PK_NAME = :id
@@ -24,41 +23,10 @@ module NoBrainer::Document::Id
24
23
 
25
24
  delegate :hash, :to => :pk_value
26
25
 
27
- # The following code is inspired by the mongo-ruby-driver
28
-
29
- @machine_id = Digest::MD5.digest(Socket.gethostname)[0, 3]
30
- @lock = Mutex.new
31
- @index = 0
32
-
33
- def self.get_inc
34
- @lock.synchronize do
35
- @index = (@index + 1) % 0xFFFFFF
36
- end
37
- end
38
-
39
- # TODO Unit test that thing
40
- def self.generate
41
- oid = ''
42
- # 4 bytes current time
43
- oid += [Time.now.to_i].pack("N")
44
-
45
- # 3 bytes machine
46
- oid += @machine_id
47
-
48
- # 2 bytes pid
49
- oid += [Process.pid % 0xFFFF].pack("n")
50
-
51
- # 3 bytes inc
52
- oid += [get_inc].pack("N")[1, 3]
53
-
54
- oid.unpack("C12").map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
55
- end
56
-
57
26
  module ClassMethods
58
27
  def define_default_pk
59
28
  class_variable_set(:@@pk_name, nil)
60
- field NoBrainer::Document::Id::DEFAULT_PK_NAME, :primary_key => :default,
61
- :type => String, :default => ->{ NoBrainer::Document::Id.generate }
29
+ field NoBrainer::Document::PrimaryKey::DEFAULT_PK_NAME, :primary_key => :default
62
30
  end
63
31
 
64
32
  def define_pk(attr)
@@ -81,6 +49,11 @@ module NoBrainer::Document::Id
81
49
  if options[:primary_key]
82
50
  options = options.merge(:readonly => true) if options[:readonly].nil?
83
51
  options = options.merge(:index => true)
52
+
53
+ if options[:type].in?([String, nil]) && options[:default].nil?
54
+ options[:type] = String
55
+ options[:default] = ->{ NoBrainer::Document::PrimaryKey::Generator.generate }
56
+ end
84
57
  end
85
58
  super
86
59
  end
@@ -16,13 +16,13 @@ module NoBrainer::Document::StoreIn
16
16
 
17
17
  def database_name
18
18
  db = self.store_in_options[:database]
19
- db.is_a?(Proc) ? db.call : db
19
+ (db.is_a?(Proc) ? db.call : db).try(:to_s)
20
20
  end
21
21
 
22
22
  def table_name
23
23
  table = store_in_options[:table]
24
24
  table_name = table.is_a?(Proc) ? table.call : table
25
- table_name || root_class.name.tableize.gsub('/', '__')
25
+ table_name.try(:to_s) || root_class.name.tableize.gsub('/', '__')
26
26
  end
27
27
 
28
28
  def rql_table
@@ -7,12 +7,14 @@ module NoBrainer::Document::Timestamps
7
7
  end
8
8
 
9
9
  def _create(options={})
10
- self.created_at = self.updated_at = Time.now
10
+ now = Time.now
11
+ self.created_at = now unless created_at_changed?
12
+ self.updated_at = now unless updated_at_changed?
11
13
  super
12
14
  end
13
15
 
14
16
  def _update(attrs)
15
- self.updated_at = Time.now
17
+ self.updated_at = Time.now unless updated_at_changed?
16
18
  super(attrs.merge('updated_at' => @_attributes['updated_at']))
17
19
  end
18
20
  end
@@ -4,7 +4,7 @@ class NoBrainer::Binary
4
4
  def self.to_s; inspect; end
5
5
  def self.name; inspect; end
6
6
 
7
- module NoBrainerExtentions
7
+ module NoBrainerExtensions
8
8
  InvalidType = NoBrainer::Error::InvalidType
9
9
 
10
10
  def nobrainer_cast_user_to_model(value)
@@ -13,11 +13,6 @@ class NoBrainer::Binary
13
13
  else raise InvalidType
14
14
  end
15
15
  end
16
-
17
- def nobrainer_cast_db_to_model(value)
18
- value.is_a?(String) ? RethinkDB::Binary.new(value) : value
19
- end
20
16
  end
21
- extend NoBrainerExtentions
17
+ extend NoBrainerExtensions
22
18
  end
23
-
@@ -1,11 +1,10 @@
1
- # We namespace our fake Boolean class to avoid polluting the global namespace
2
1
  class NoBrainer::Boolean
3
2
  def initialize; raise; end
4
3
  def self.inspect; 'Boolean'; end
5
4
  def self.to_s; inspect; end
6
5
  def self.name; inspect; end
7
6
 
8
- module NoBrainerExtentions
7
+ module NoBrainerExtensions
9
8
  InvalidType = NoBrainer::Error::InvalidType
10
9
 
11
10
  def nobrainer_cast_user_to_model(value)
@@ -21,6 +20,5 @@ class NoBrainer::Boolean
21
20
  end
22
21
  end
23
22
  end
24
- extend NoBrainerExtentions
23
+ extend NoBrainerExtensions
25
24
  end
26
-
@@ -1,5 +1,5 @@
1
1
  class Date
2
- module NoBrainerExtentions
2
+ module NoBrainerExtensions
3
3
  InvalidType = NoBrainer::Error::InvalidType
4
4
 
5
5
  def nobrainer_cast_user_to_model(value)
@@ -22,5 +22,5 @@ class Date
22
22
  value.is_a?(Date) ? Time.utc(value.year, value.month, value.day) : value
23
23
  end
24
24
  end
25
- extend NoBrainerExtentions
25
+ extend NoBrainerExtensions
26
26
  end
@@ -1,5 +1,5 @@
1
1
  class Float
2
- module NoBrainerExtentions
2
+ module NoBrainerExtensions
3
3
  InvalidType = NoBrainer::Error::InvalidType
4
4
 
5
5
  def nobrainer_cast_user_to_model(value)
@@ -16,5 +16,5 @@ class Float
16
16
  end
17
17
  end
18
18
  end
19
- extend NoBrainerExtentions
19
+ extend NoBrainerExtensions
20
20
  end
@@ -0,0 +1 @@
1
+ # Look in lib/no_brainer/geo.rb instead
@@ -1,5 +1,5 @@
1
1
  class Integer
2
- module NoBrainerExtentions
2
+ module NoBrainerExtensions
3
3
  InvalidType = NoBrainer::Error::InvalidType
4
4
 
5
5
  def nobrainer_cast_user_to_model(value)
@@ -14,5 +14,5 @@ class Integer
14
14
  end
15
15
  end
16
16
  end
17
- extend NoBrainerExtentions
17
+ extend NoBrainerExtensions
18
18
  end
@@ -1,5 +1,5 @@
1
1
  class Set
2
- module NoBrainerExtentions
2
+ module NoBrainerExtensions
3
3
  InvalidType = NoBrainer::Error::InvalidType
4
4
 
5
5
  def nobrainer_cast_user_to_model(value)
@@ -19,5 +19,5 @@ class Set
19
19
  end
20
20
  end
21
21
 
22
- extend NoBrainerExtentions
22
+ extend NoBrainerExtensions
23
23
  end
@@ -1,5 +1,5 @@
1
1
  class String
2
- module NoBrainerExtentions
2
+ module NoBrainerExtensions
3
3
  InvalidType = NoBrainer::Error::InvalidType
4
4
 
5
5
  def nobrainer_cast_user_to_model(value)
@@ -7,8 +7,11 @@ class String
7
7
  when String then value
8
8
  when Symbol then value.to_s
9
9
  else raise InvalidType
10
+ end.tap do |str|
11
+ max_length = NoBrainer::Config.max_string_length
12
+ raise InvalidType.new(:error => { :message => :too_long, :count => max_length }) if str.size > max_length
10
13
  end
11
14
  end
12
15
  end
13
- extend NoBrainerExtentions
16
+ extend NoBrainerExtensions
14
17
  end
@@ -1,5 +1,5 @@
1
1
  class Symbol
2
- module NoBrainerExtentions
2
+ module NoBrainerExtensions
3
3
  InvalidType = NoBrainer::Error::InvalidType
4
4
 
5
5
  def nobrainer_cast_user_to_model(value)
@@ -17,5 +17,5 @@ class Symbol
17
17
  value.to_sym rescue (value.to_s.to_sym rescue value)
18
18
  end
19
19
  end
20
- extend NoBrainerExtentions
20
+ extend NoBrainerExtensions
21
21
  end
@@ -0,0 +1,18 @@
1
+ class NoBrainer::Text
2
+ def initialize; raise; end
3
+ def self.inspect; 'Text'; end
4
+ def self.to_s; inspect; end
5
+ def self.name; inspect; end
6
+
7
+ module NoBrainerExtensions
8
+ InvalidType = NoBrainer::Error::InvalidType
9
+
10
+ def nobrainer_cast_user_to_model(value)
11
+ case value
12
+ when String then value
13
+ else raise InvalidType
14
+ end
15
+ end
16
+ end
17
+ extend NoBrainerExtensions
18
+ end
@@ -1,7 +1,7 @@
1
1
  require 'time'
2
2
 
3
3
  class Time
4
- module NoBrainerExtentions
4
+ module NoBrainerExtensions
5
5
  InvalidType = NoBrainer::Error::InvalidType
6
6
 
7
7
  def nobrainer_cast_user_to_model(value)
@@ -37,5 +37,5 @@ class Time
37
37
  end
38
38
  end
39
39
  end
40
- extend NoBrainerExtentions
40
+ extend NoBrainerExtensions
41
41
  end
@@ -6,7 +6,7 @@ module NoBrainer::Document::Types
6
6
  def add_type_errors
7
7
  return unless @pending_type_errors
8
8
  @pending_type_errors.each do |name, error|
9
- errors.add(name, :invalid_type, :type => error.human_type_name)
9
+ errors.add(name, :invalid_type, error.error)
10
10
  end
11
11
  end
12
12
 
@@ -22,7 +22,10 @@ module NoBrainer::Document::Types
22
22
  module ClassMethods
23
23
  def cast_user_to_model_for(attr, value)
24
24
  type = fields[attr.to_sym].try(:[], :type)
25
- return value if type.nil? || value.nil? || value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic)
25
+ return value if type.nil? || value.nil? ||
26
+ value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) ||
27
+ value.is_a?(RethinkDB::RQL)
28
+
26
29
  if type.respond_to?(:nobrainer_cast_user_to_model)
27
30
  type.nobrainer_cast_user_to_model(value)
28
31
  else
@@ -30,9 +33,7 @@ module NoBrainer::Document::Types
30
33
  value
31
34
  end
32
35
  rescue NoBrainer::Error::InvalidType => error
33
- error.type = type
34
- error.value = value
35
- error.attr_name = attr
36
+ error.update(:model => self, :value => value, :attr_name => attr, :type => type)
36
37
  raise error
37
38
  end
38
39
 
@@ -62,6 +63,13 @@ module NoBrainer::Document::Types
62
63
 
63
64
  return unless options[:type]
64
65
 
66
+ raise "Please use a class for the type option" unless options[:type].is_a?(Class)
67
+ case options[:type].to_s
68
+ when "NoBrainer::Geo::Circle" then raise "Cannot store circles :("
69
+ when "NoBrainer::Geo::Polygon", "NoBrainer::Geo::LineString"
70
+ raise "Make a request on github if you'd like to store polygons/linestrings"
71
+ end
72
+
65
73
  NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
66
74
 
67
75
  inject_in_layer :types do
@@ -88,21 +96,12 @@ module NoBrainer::Document::Types
88
96
  remove_method("#{attr}?") if method_defined?("#{attr}?")
89
97
  end
90
98
  end
91
-
92
- def field(attr, options={})
93
- options[:real_type] = options[:type]
94
- if options[:type] == Array || options[:type] == Hash
95
- # XXX For the moment, NoBrainer does not support these complex types
96
- options.delete(:type)
97
- end
98
- super
99
- end
100
99
  end
101
100
 
102
- require File.join(File.dirname(__FILE__), 'types', 'binary')
103
- require File.join(File.dirname(__FILE__), 'types', 'boolean')
104
- Binary = NoBrainer::Binary
105
- Boolean = NoBrainer::Boolean
101
+ %w(binary boolean text geo).each do |type|
102
+ require File.join(File.dirname(__FILE__), 'types', type)
103
+ const_set(type.camelize, NoBrainer.const_get(type.camelize))
104
+ end
106
105
 
107
106
  class << self
108
107
  mattr_accessor :loaded_extensions
@@ -0,0 +1,15 @@
1
+ module NoBrainer::Document::Validation::NotNull
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def validates_not_null(*attr_names)
6
+ validates_with(NotNullValidator, _merge_attributes(attr_names))
7
+ end
8
+ end
9
+
10
+ class NotNullValidator < ActiveModel::EachValidator
11
+ def validate_each(doc, attr, value)
12
+ doc.errors.add(attr, :undefined, options) if value.nil?
13
+ end
14
+ end
15
+ end
@@ -1,4 +1,4 @@
1
- module NoBrainer::Document::Uniqueness
1
+ module NoBrainer::Document::Validation::Uniqueness
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def _create(options={})
@@ -27,7 +27,7 @@ module NoBrainer::Document::Uniqueness
27
27
  self.class.unique_validators
28
28
  .map { |validator| validator.attributes.map { |attr| [attr, validator] } }
29
29
  .flatten(1)
30
- .select { |f, validator| validator.should_validate_uniquess_of?(self, f) }
30
+ .select { |f, validator| validator.should_validate_field?(self, f) }
31
31
  .map { |f, options| _lock_key_from_field(f) }
32
32
  .sort
33
33
  .uniq
@@ -51,14 +51,13 @@ module NoBrainer::Document::Uniqueness
51
51
 
52
52
  module ClassMethods
53
53
  def validates_uniqueness_of(*attr_names)
54
- validates_with UniquenessValidator, _merge_attributes(attr_names)
54
+ validates_with(UniquenessValidator, _merge_attributes(attr_names))
55
55
  end
56
56
 
57
57
  def inherited(subclass)
58
58
  subclass.unique_validators = self.unique_validators.dup
59
59
  super
60
60
  end
61
-
62
61
  end
63
62
 
64
63
  class UniquenessValidator < ActiveModel::EachValidator
@@ -73,19 +72,20 @@ module NoBrainer::Document::Uniqueness
73
72
  end
74
73
  end
75
74
 
76
- def should_validate_uniquess_of?(doc, field)
77
- (scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
75
+ def should_validate_field?(doc, field)
76
+ doc.new_record? || (scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
78
77
  end
79
78
 
80
79
  def validate_each(doc, attr, value)
81
- return true unless should_validate_uniquess_of?(doc, attr)
82
-
83
80
  criteria = doc.root_class.unscoped.where(attr => value)
84
81
  criteria = apply_scopes(criteria, doc)
85
82
  criteria = exclude_doc(criteria, doc) if doc.persisted?
86
- is_unique = criteria.count == 0
87
- doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
88
- is_unique
83
+ doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
84
+ rescue NoBrainer::Error::InvalidType
85
+ # We can't run the uniqueness validator: where() won't accept bad types
86
+ # and we have some values that don't have the right type.
87
+ # Note that it's fine to not add errors because the type validations will
88
+ # prevent the document from being saved.
89
89
  end
90
90
 
91
91
  def apply_scopes(criteria, doc)