nobrainer 0.10.0 → 0.12.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +169 -0
  3. data/lib/no_brainer/criteria/after_find.rb +24 -0
  4. data/lib/no_brainer/criteria/{termination/cache.rb → cache.rb} +1 -1
  5. data/lib/no_brainer/criteria/{chainable/core.rb → core.rb} +1 -1
  6. data/lib/no_brainer/criteria/{termination/count.rb → count.rb} +1 -1
  7. data/lib/no_brainer/criteria/{termination/delete.rb → delete.rb} +1 -1
  8. data/lib/no_brainer/criteria/{termination/enumerable.rb → enumerable.rb} +1 -1
  9. data/lib/no_brainer/criteria/{termination/first.rb → first.rb} +1 -1
  10. data/lib/no_brainer/criteria/{termination/inc.rb → inc.rb} +1 -1
  11. data/lib/no_brainer/criteria/{chainable/limit.rb → limit.rb} +1 -1
  12. data/lib/no_brainer/criteria/{chainable/order_by.rb → order_by.rb} +1 -1
  13. data/lib/no_brainer/criteria/preload.rb +55 -0
  14. data/lib/no_brainer/criteria/raw.rb +25 -0
  15. data/lib/no_brainer/criteria/{chainable/scope.rb → scope.rb} +1 -1
  16. data/lib/no_brainer/criteria/{termination/update.rb → update.rb} +1 -1
  17. data/lib/no_brainer/criteria/{chainable/where.rb → where.rb} +61 -25
  18. data/lib/no_brainer/criteria.rb +4 -16
  19. data/lib/no_brainer/document/association/belongs_to.rb +9 -2
  20. data/lib/no_brainer/document/association/core.rb +2 -2
  21. data/lib/no_brainer/document/association/eager_loader.rb +7 -7
  22. data/lib/no_brainer/document/association/has_many.rb +1 -1
  23. data/lib/no_brainer/document/attributes.rb +9 -23
  24. data/lib/no_brainer/document/callbacks.rb +37 -0
  25. data/lib/no_brainer/document/core.rb +0 -2
  26. data/lib/no_brainer/document/criteria.rb +1 -1
  27. data/lib/no_brainer/document/dirty.rb +43 -46
  28. data/lib/no_brainer/document/dynamic_attributes.rb +11 -3
  29. data/lib/no_brainer/document/id.rb +1 -1
  30. data/lib/no_brainer/document/index.rb +0 -5
  31. data/lib/no_brainer/document/persistance.rb +39 -39
  32. data/lib/no_brainer/document/serialization.rb +0 -1
  33. data/lib/no_brainer/document/timestamps.rb +10 -9
  34. data/lib/no_brainer/document/types.rb +42 -39
  35. data/lib/no_brainer/document/validation.rb +5 -0
  36. data/lib/no_brainer/document.rb +3 -3
  37. data/lib/no_brainer/document_with_timestamps.rb +6 -0
  38. data/lib/no_brainer/error.rb +26 -9
  39. data/lib/no_brainer/query_runner/missing_index.rb +1 -1
  40. data/lib/no_brainer/railtie.rb +10 -0
  41. data/lib/nobrainer.rb +3 -3
  42. metadata +37 -34
  43. data/LICENSE.md +0 -7
  44. data/lib/no_brainer/criteria/chainable/raw.rb +0 -33
  45. data/lib/no_brainer/criteria/termination/eager_loading.rb +0 -50
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Document::Attributes
2
- VALID_FIELD_OPTIONS = [:index, :default, :type]
2
+ VALID_FIELD_OPTIONS = [:index, :default, :type, :type_cast_method, :validates]
3
3
  RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
4
4
  extend ActiveSupport::Concern
5
5
 
@@ -10,14 +10,13 @@ module NoBrainer::Document::Attributes
10
10
  self.fields = {}
11
11
  end
12
12
 
13
- def initialize(attrs={}, options={})
14
- super
15
- @attributes = {}
13
+ def _initialize(attrs={}, options={})
14
+ @_attributes = {}.with_indifferent_access
16
15
  assign_attributes(attrs, options.reverse_merge(:pristine => true))
17
16
  end
18
17
 
19
18
  def attributes
20
- @attributes.dup.freeze
19
+ Hash[@_attributes.keys.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
21
20
  end
22
21
 
23
22
  def read_attribute(name)
@@ -32,7 +31,7 @@ module NoBrainer::Document::Attributes
32
31
 
33
32
  def assign_defaults
34
33
  self.class.fields.each do |name, field_options|
35
- if field_options.has_key?(:default) && !@attributes.has_key?(name.to_s)
34
+ if field_options.has_key?(:default) && !@_attributes.has_key?(name)
36
35
  default_value = field_options[:default]
37
36
  default_value = default_value.call if default_value.is_a?(Proc)
38
37
  self.write_attribute(name, default_value)
@@ -45,7 +44,7 @@ module NoBrainer::Document::Attributes
45
44
  end
46
45
 
47
46
  def assign_attributes(attrs, options={})
48
- @attributes.clear if options[:pristine]
47
+ @_attributes.clear if options[:pristine]
49
48
  _assign_attributes(attrs, options)
50
49
  assign_defaults if options[:pristine]
51
50
  self
@@ -54,7 +53,7 @@ module NoBrainer::Document::Attributes
54
53
 
55
54
  def inspectable_attributes
56
55
  # TODO test that thing
57
- Hash[@attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
56
+ Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
58
57
  end
59
58
 
60
59
  def inspect
@@ -88,28 +87,15 @@ module NoBrainer::Document::Attributes
88
87
  # Using a layer so the user can use super when overriding these methods
89
88
  inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
90
89
  def #{name}=(value)
91
- @attributes['#{name}'] = value
90
+ @_attributes['#{name}'] = value
92
91
  end
93
92
 
94
93
  def #{name}
95
- @attributes['#{name}']
94
+ @_attributes['#{name}']
96
95
  end
97
96
  RUBY
98
97
  end
99
98
 
100
- def remove_field(name)
101
- name = name.to_sym
102
-
103
- ([self] + descendants).each do |klass|
104
- klass.fields.delete(name)
105
- end
106
-
107
- inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
108
- undef #{name}=
109
- undef #{name}
110
- RUBY
111
- end
112
-
113
99
  def has_field?(name)
114
100
  !!fields[name.to_sym]
115
101
  end
@@ -0,0 +1,37 @@
1
+ module NoBrainer::Document::Callbacks
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ extend ActiveModel::Callbacks
6
+ define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => 'false'
7
+ define_model_callbacks :find, :only => [:after], :terminator => 'false'
8
+ end
9
+
10
+ def initialize(*args)
11
+ run_callbacks(:initialize) { _initialize(*args); true }
12
+ end
13
+
14
+ def _create(*args)
15
+ run_callbacks(:create) { super }
16
+ end
17
+
18
+ def update(*args, &block)
19
+ run_callbacks(:update) { super }
20
+ end
21
+
22
+ def replace(*args, &block)
23
+ run_callbacks(:update) { super }
24
+ end
25
+
26
+ def _update_changed(*args)
27
+ run_callbacks(:update) { super }
28
+ end
29
+
30
+ def save(*args)
31
+ run_callbacks(:save) { super }
32
+ end
33
+
34
+ def destroy(*args)
35
+ run_callbacks(:destroy) { super }
36
+ end
37
+ end
@@ -15,6 +15,4 @@ module NoBrainer::Document::Core
15
15
 
16
16
  NoBrainer::Document::Core.all << self
17
17
  end
18
-
19
- def initialize(attrs={}, options={}); end
20
18
  end
@@ -16,7 +16,7 @@ module NoBrainer::Document::Criteria
16
16
  :with_cache, :without_cache, # Cache
17
17
  :count, :empty?, :any?, # Count
18
18
  :delete_all, :destroy_all, # Delete
19
- :includes, # EagerLoading
19
+ :includes, :preload, # Preload
20
20
  :each, :to_a, # Enumerable
21
21
  :first, :last, :first!, :last!, # First
22
22
  :inc_all, :dec_all, # Inc
@@ -5,84 +5,81 @@ module NoBrainer::Document::Dirty
5
5
  # ActiveModel::AttributeMethods which gives pretty violent method_missing()
6
6
  # capabilities, such as giving a getter/setter method for any keys within the
7
7
  # attributes keys. We don't want that.
8
+ # Also it doesn't work properly with array and hashes
8
9
 
9
- included do
10
- attr_accessor :previous_changes
11
- after_save { clear_dirtiness }
10
+ included { after_save { clear_dirtiness } }
11
+
12
+ def old_attributes_values
13
+ @old_attributes_values ||= {}.with_indifferent_access
12
14
  end
13
15
 
14
- def changed_attributes
15
- @changed_attributes ||= {}
16
+ def clear_dirtiness
17
+ @old_attributes_values.try(:clear)
16
18
  end
17
19
 
18
20
  def _assign_attributes(attrs, options={})
19
21
  super
20
- clear_dirtiness if options[:pristine]
21
- end
22
-
23
- def clear_dirtiness
24
- self.previous_changes = changes
25
- self.changed_attributes.clear
22
+ clear_dirtiness if options[:from_db]
26
23
  end
27
24
 
28
25
  def changed?
29
- changed_attributes.present?
26
+ changes.present?
30
27
  end
31
28
 
32
29
  def changed
33
- changed_attributes.keys
30
+ changes.keys
34
31
  end
35
32
 
36
33
  def changes
37
- Hash[changed_attributes.map { |k,v| [k, [v, read_attribute(k)]] }]
34
+ result = {}.with_indifferent_access
35
+ old_attributes_values.each do |attr, old_value|
36
+ current_value = read_attribute(attr)
37
+ result[attr] = [old_value, current_value] if current_value != old_value
38
+ end
39
+ result
38
40
  end
39
41
 
40
- def attribute_will_change!(attr, new_value)
41
- return if changed_attributes.include?(attr)
42
-
43
- # ActiveModel ignores TypeError and NoMethodError exception as if nothng
44
- # happened. Why is that?
45
- value = read_attribute(attr)
46
- value = value.clone if value.duplicable?
47
-
48
- return if value == new_value
49
-
50
- changed_attributes[attr] = value
42
+ def attribute_may_change(attr, current_value)
43
+ unless old_attributes_values.has_key?(attr)
44
+ old_attributes_values[attr] = current_value.deep_dup
45
+ end
51
46
  end
52
47
 
53
48
  module ClassMethods
54
49
  def field(name, options={})
55
50
  super
56
51
 
57
- inject_in_layer :dirty_tracking, <<-RUBY, __FILE__, __LINE__ + 1
58
- def #{name}_changed?
59
- changed_attributes.include?(:#{name})
52
+ inject_in_layer :dirty_tracking do
53
+ define_method("#{name}_change") do
54
+ if old_attributes_values.has_key?(name)
55
+ result = [old_attributes_values[name], read_attribute(name)]
56
+ result = nil if result.first == result.last
57
+ result
58
+ end
60
59
  end
61
60
 
62
- def #{name}_change
63
- [changed_attributes[:#{name}], #{name}] if #{name}_changed?
61
+ define_method("#{name}_changed?") do
62
+ !!__send__("#{name}_change")
64
63
  end
65
64
 
66
- def #{name}_was
67
- #{name}_changed? ? changed_attributes[:#{name}] : #{name}
65
+ define_method("#{name}_was") do
66
+ old_attributes_values.has_key?(name) ?
67
+ old_attributes_values[name] : read_attribute(name)
68
68
  end
69
69
 
70
- def #{name}=(value)
71
- attribute_will_change!(:#{name}, value)
72
- super
70
+ define_method("#{name}") do
71
+ super().tap do |value|
72
+ # This take care of string/arrays/hashes that could change without going
73
+ # through the setter.
74
+ attribute_may_change(name, value) if value.respond_to?(:size)
75
+ end
73
76
  end
74
- RUBY
75
- end
76
77
 
77
- def remove_field(name)
78
- super
79
-
80
- inject_in_layer :dirty_tracking, <<-RUBY, __FILE__, __LINE__ + 1
81
- undef #{name}_changed?
82
- undef #{name}_change
83
- undef #{name}_was
84
- undef #{name}=
85
- RUBY
78
+ define_method("#{name}=") do |value|
79
+ attribute_may_change(name, read_attribute(name))
80
+ super(value)
81
+ end
82
+ end
86
83
  end
87
84
  end
88
85
  end
@@ -2,11 +2,19 @@ module NoBrainer::Document::DynamicAttributes
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def read_attribute(name)
5
- self.respond_to?("#{name}") ? super : @attributes[name.to_s]
5
+ if self.respond_to?("#{name}")
6
+ super
7
+ else
8
+ @_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
9
+ end
6
10
  end
7
11
 
8
12
  def write_attribute(name, value)
9
- attribute_will_change!(name, value)
10
- self.respond_to?("#{name}=") ? super : @attributes[name.to_s] = value
13
+ if self.respond_to?("#{name}=")
14
+ super
15
+ else
16
+ attribute_may_change(name, read_attribute(name))
17
+ @_attributes[name] = value
18
+ end
11
19
  end
12
20
  end
@@ -6,7 +6,7 @@ module NoBrainer::Document::Id
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- self.field :id, :default => ->{ NoBrainer::Document::Id.generate }
9
+ self.field :id, :type => String, :default => ->{ NoBrainer::Document::Id.generate }
10
10
  end
11
11
 
12
12
  def ==(other)
@@ -59,11 +59,6 @@ module NoBrainer::Document::Index
59
59
  end
60
60
  end
61
61
 
62
- def remove_field(name)
63
- remove_index(name) if fields[name.to_sym][:index]
64
- super
65
- end
66
-
67
62
  def perform_create_index(index_name, options={})
68
63
  index_name = index_name.to_sym
69
64
  index_args = self.indexes[index_name]
@@ -6,8 +6,7 @@ module NoBrainer::Document::Persistance
6
6
  define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
7
7
  end
8
8
 
9
- # TODO after_initialize, after_find callback
10
- def initialize(attrs={}, options={})
9
+ def _initialize(attrs={}, options={})
11
10
  super
12
11
  @new_record = !options[:from_db]
13
12
  end
@@ -25,56 +24,57 @@ module NoBrainer::Document::Persistance
25
24
  end
26
25
 
27
26
  def reload(options={})
28
- unless options[:keep_ivars]
29
- id = self.id
30
- instance_variables.each { |ivar| remove_instance_variable(ivar) }
31
- @attributes = {}
32
- self.id = id
33
- end
34
- assign_attributes(selector.raw.first!, :pristine => true, :from_db => true)
27
+ attrs = selector.raw.first!
28
+ instance_variables.each { |ivar| remove_instance_variable(ivar) } unless options[:keep_ivars]
29
+ initialize(attrs, :pristine => true, :from_db => true)
35
30
  self
36
31
  end
37
32
 
38
33
  def _create(options={})
39
- run_callbacks :create do
40
- if options[:validate] && !valid?
41
- false
42
- else
43
- keys = self.class.insert_all(attributes)
44
- self.id ||= keys.first
45
- @new_record = false
46
- true
47
- end
48
- end
34
+ return false if options[:validate] && !valid?
35
+ keys = self.class.insert_all(@_attributes)
36
+ self.id ||= keys.first
37
+ @new_record = false
38
+ true
49
39
  end
50
40
 
51
41
  def update(options={}, &block)
52
- run_callbacks :update do
53
- if options[:validate] && !valid?
54
- false
55
- else
56
- selector.update_all(&block)
57
- true
58
- end
59
- end
42
+ return false if options[:validate] && !valid?
43
+ selector.update_all(&block)
44
+ true
60
45
  end
61
46
 
62
47
  def replace(options={}, &block)
63
- run_callbacks :update do
64
- if options[:validate] && !valid?
65
- false
66
- else
67
- selector.replace_all(&block)
68
- true
69
- end
48
+ return false if options[:validate] && !valid?
49
+ selector.replace_all(&block)
50
+ true
51
+ end
52
+
53
+ def _update_changed_attributes(changed_attrs)
54
+ # If we have a hash to save, we replace the entire document
55
+ # instead of doing some smart update. This is because RethinkDB
56
+ # will merge the existing hash with the given hash. If the
57
+ # user has deleted some keys, we won't remove them.
58
+ if changed_attrs.values.any? { |v| v.is_a?(Hash) }
59
+ selector.replace_all { @_attributes }
60
+ else
61
+ selector.update_all { changed_attrs }
70
62
  end
71
63
  end
72
64
 
65
+ def _update_changed(options={})
66
+ return false if options[:validate] && !valid?
67
+
68
+ # We won't be using the `changes` values, because they went through
69
+ # read_attribute(), and we want the raw values.
70
+ changed_attrs = Hash[self.changed.map { |k| [k, @_attributes[k]] }]
71
+ _update_changed_attributes(changed_attrs) if changed_attrs.present?
72
+ true
73
+ end
74
+
73
75
  def save(options={})
74
76
  options = options.reverse_merge(:validate => true)
75
- run_callbacks :save do
76
- new_record? ? _create(options) : replace(options) { attributes }
77
- end
77
+ new_record? ? _create(options) : _update_changed(options)
78
78
  end
79
79
 
80
80
  def save!(*args)
@@ -95,12 +95,12 @@ module NoBrainer::Document::Persistance
95
95
  selector.delete_all
96
96
  @destroyed = true
97
97
  end
98
- # TODO freeze attributes
98
+ @_attributes.freeze
99
99
  true
100
100
  end
101
101
 
102
102
  def destroy
103
- run_callbacks(:destroy) { delete }
103
+ delete
104
104
  end
105
105
 
106
106
  module ClassMethods
@@ -3,7 +3,6 @@ module NoBrainer::Document::Serialization
3
3
 
4
4
  include ActiveModel::Serialization
5
5
  include ActiveModel::Serializers::JSON
6
- include ActiveModel::Serializers::Xml
7
6
 
8
7
  included { self.include_root_in_json = false }
9
8
  end
@@ -2,17 +2,18 @@ module NoBrainer::Document::Timestamps
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- self.field :created_at, :type => Time
6
- self.field :updated_at, :type => Time
5
+ field :created_at, :type => Time
6
+ field :updated_at, :type => Time
7
7
 
8
- before_create { self.created_at = Time.now if self.respond_to?(:created_at=) }
9
- before_save { self.updated_at = Time.now if self.respond_to?(:updated_at=) }
8
+ before_create { self.created_at = self.updated_at = Time.now }
9
+ # Not using the before_update callback as it would mess
10
+ # with the dirty tracking. We want to bypass the database
11
+ # call if nothing has changed.
10
12
  end
11
13
 
12
- module ClassMethods
13
- def disable_timestamps
14
- self.remove_field :created_at
15
- self.remove_field :updated_at
16
- end
14
+ def _update_changed_attributes(changed_attrs)
15
+ self.updated_at = Time.now
16
+ changed_attrs['updated_at'] = @_attributes['updated_at']
17
+ super
17
18
  end
18
19
  end
@@ -1,14 +1,9 @@
1
1
  module NoBrainer::Document::Types
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included do
5
- # We namespace our fake Boolean class to avoid polluting the global namespace
6
- class_exec { class Boolean; def initialize; raise; end; end }
7
- before_validation :add_type_errors
8
- end
9
-
10
4
  module CastingRules
11
5
  extend self
6
+ InvalidType = NoBrainer::Error::InvalidType
12
7
 
13
8
  def String(value)
14
9
  case value
@@ -63,67 +58,75 @@ module NoBrainer::Document::Types
63
58
  else raise InvalidType
64
59
  end
65
60
  end
66
- end
67
61
 
68
- def self.cast(value, type, cast_method)
69
- return value if value.nil? || type.nil? || value.is_a?(type)
70
- cast_method.call(value)
71
- end
72
-
73
- def self.lookup_cast_method(type)
74
- type = type.to_s
75
- type = 'Boolean' if type == 'NoBrainer::Document::Types::Boolean'
76
- CastingRules.method(type)
77
- rescue NameError
78
- proc { raise InvalidType }
79
- end
62
+ def lookup(type)
63
+ CastingRules.method(type.to_s)
64
+ rescue NameError
65
+ proc { raise InvalidType }
66
+ end
80
67
 
81
- class InvalidType < RuntimeError
82
- attr_accessor :type
83
- def initialize(type=nil)
84
- @type = type
68
+ def cast(value, type, type_cast_method)
69
+ return value if value.nil? || type.nil? || value.is_a?(type)
70
+ type_cast_method.call(value)
85
71
  end
72
+ end
86
73
 
87
- def validation_error_args
88
- [:invalid_type, :type => type.to_s.underscore.humanize.downcase]
74
+ included do
75
+ # We namespace our fake Boolean class to avoid polluting the global namespace
76
+ class_exec do
77
+ class Boolean
78
+ def initialize; raise; end
79
+ def self.inspect; 'Boolean'; end
80
+ def self.to_s; inspect; end
81
+ def self.name; inspect; end
82
+ end
89
83
  end
84
+ before_validation :add_type_errors
90
85
  end
91
86
 
92
87
  def add_type_errors
93
88
  return unless @pending_type_errors
94
89
  @pending_type_errors.each do |name, error|
95
- errors.add(name, *error.validation_error_args)
90
+ errors.add(name, :invalid_type, :type => error.human_type_name)
96
91
  end
97
92
  end
98
93
 
99
94
  module ClassMethods
95
+ def cast_value_for(name, value)
96
+ name = name.to_sym
97
+ field_def = fields[name]
98
+ return value unless field_def && field_def[:type]
99
+ NoBrainer::Document::Types::CastingRules.cast(value, field_def[:type], field_def[:type_cast_method])
100
+ rescue NoBrainer::Error::InvalidType => error
101
+ error.type = field_def[:type]
102
+ error.value = value
103
+ error.attr_name = name
104
+ raise error
105
+ end
106
+
100
107
  def field(name, options={})
101
- super
102
- return unless options.has_key?(:type)
108
+ return super unless options.has_key?(:type)
109
+
103
110
  name = name.to_sym
104
111
  type = options[:type]
105
- cast_method = NoBrainer::Document::Types.lookup_cast_method(type)
112
+ options[:type_cast_method] = NoBrainer::Document::Types::CastingRules.lookup(type)
113
+
114
+ super
106
115
 
107
116
  inject_in_layer :types do
108
117
  define_method("#{name}=") do |value|
109
118
  begin
110
- value = NoBrainer::Document::Types.cast(value, type, cast_method)
119
+ value = self.class.cast_value_for(name, value)
111
120
  @pending_type_errors.try(:delete, name)
112
- rescue NoBrainer::Document::Types::InvalidType => error
113
- error.type ||= type
121
+ rescue NoBrainer::Error::InvalidType => error
114
122
  @pending_type_errors ||= {}
115
123
  @pending_type_errors[name] = error
116
124
  end
117
125
  super(value)
118
126
  end
119
- end
120
- end
121
127
 
122
- def remove_field(name)
123
- super
124
- inject_in_layer :types, <<-RUBY, __FILE__, __LINE__ + 1
125
- undef #{name}=
126
- RUBY
128
+ define_method("#{name}?") { !!read_attribute(name) } if type == Boolean
129
+ end
127
130
  end
128
131
  end
129
132
  end
@@ -8,6 +8,11 @@ module NoBrainer::Document::Validation
8
8
  end
9
9
 
10
10
  module ClassMethods
11
+ def field(name, options={})
12
+ super
13
+ validates(name.to_sym, options[:validates]) if options[:validates]
14
+ end
15
+
11
16
  def validates_uniqueness_of(*attr_names)
12
17
  validates_with UniquenessValidator, _merge_attributes(attr_names)
13
18
  end
@@ -5,10 +5,10 @@ module NoBrainer::Document
5
5
  extend NoBrainer::Autoload
6
6
 
7
7
  autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
8
- :Persistance, :Dirty, :Id, :Association, :Serialization, :Criteria,
9
- :Polymorphic, :Index, :Timestamps
8
+ :Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
9
+ :Criteria, :Polymorphic, :Index
10
10
 
11
- autoload :DynamicAttributes
11
+ autoload :DynamicAttributes, :Timestamps
12
12
 
13
13
  singleton_class.delegate :all, :to => Core
14
14
  end
@@ -0,0 +1,6 @@
1
+ module NoBrainer::DocumentWithTimestamps
2
+ extend ActiveSupport::Concern
3
+
4
+ include NoBrainer::Document
5
+ include NoBrainer::Document::Timestamps
6
+ end
@@ -1,11 +1,28 @@
1
1
  module NoBrainer::Error
2
- class Connection < StandardError; end
3
- class DocumentNotFound < StandardError; end
4
- class DocumentInvalid < StandardError; end
5
- class DocumentNotSaved < StandardError; end
6
- class ChildrenExist < StandardError; end
7
- class CannotUseIndex < StandardError; end
8
- class MissingIndex < StandardError; end
9
- class InvalidType < StandardError; end
10
- class AssociationNotSaved < StandardError; end
2
+ class Connection < RuntimeError; end
3
+ class DocumentNotFound < RuntimeError; end
4
+ class DocumentInvalid < RuntimeError; end
5
+ class DocumentNotSaved < RuntimeError; end
6
+ class ChildrenExist < RuntimeError; end
7
+ class CannotUseIndex < RuntimeError; end
8
+ class MissingIndex < RuntimeError; end
9
+ class InvalidType < RuntimeError; end
10
+ class AssociationNotSaved < RuntimeError; end
11
+
12
+ class InvalidType < RuntimeError
13
+ attr_accessor :attr_name, :value, :type
14
+ def initialize(options={})
15
+ @attr_name = options[:attr_name]
16
+ @value = options[:value]
17
+ @type = options[:type]
18
+ end
19
+
20
+ def human_type_name
21
+ type.to_s.underscore.humanize.downcase
22
+ end
23
+
24
+ def message
25
+ "#{attr_name} should be used with a #{human_type_name}. Got `#{value}`"
26
+ end
27
+ end
11
28
  end
@@ -3,7 +3,7 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
3
3
  @runner.call(env)
4
4
  rescue RethinkDB::RqlRuntimeError => e
5
5
  if e.message =~ /^Index `(.+)` was not found\.$/
6
- raise NoBrainer::Error::MissingIndex.new("Please run \"rake db:update_indexes\" to create index `#{$1}`\n" +
6
+ raise NoBrainer::Error::MissingIndex.new("Please run \"rake db:update_indexes\" to create the index `#{$1}`\n" +
7
7
  "--> Read http://nobrainer.io/docs/indexes for more information.")
8
8
  end
9
9
  raise