nobrainer 0.13.1 → 0.15.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/config.rb +20 -4
  3. data/lib/no_brainer/criteria/count.rb +1 -1
  4. data/lib/no_brainer/criteria/delete.rb +2 -2
  5. data/lib/no_brainer/criteria/first.rb +6 -0
  6. data/lib/no_brainer/criteria/order_by.rb +33 -10
  7. data/lib/no_brainer/criteria/update.rb +2 -2
  8. data/lib/no_brainer/criteria/where.rb +12 -6
  9. data/lib/no_brainer/decorated_symbol.rb +2 -1
  10. data/lib/no_brainer/document/association/belongs_to.rb +10 -5
  11. data/lib/no_brainer/document/association/core.rb +1 -1
  12. data/lib/no_brainer/document/association/has_many.rb +10 -4
  13. data/lib/no_brainer/document/attributes.rb +29 -3
  14. data/lib/no_brainer/document/callbacks.rb +2 -10
  15. data/lib/no_brainer/document/core.rb +5 -3
  16. data/lib/no_brainer/document/criteria.rb +11 -11
  17. data/lib/no_brainer/document/dirty.rb +11 -0
  18. data/lib/no_brainer/document/dynamic_attributes.rb +4 -0
  19. data/lib/no_brainer/document/id.rb +49 -4
  20. data/lib/no_brainer/document/index.rb +6 -2
  21. data/lib/no_brainer/document/persistance.rb +7 -6
  22. data/lib/no_brainer/document/readonly.rb +7 -0
  23. data/lib/no_brainer/document/serialization.rb +4 -0
  24. data/lib/no_brainer/document/types/boolean.rb +26 -0
  25. data/lib/no_brainer/document/types/date.rb +26 -0
  26. data/lib/no_brainer/document/types/float.rb +20 -0
  27. data/lib/no_brainer/document/types/integer.rb +18 -0
  28. data/lib/no_brainer/document/types/string.rb +14 -0
  29. data/lib/no_brainer/document/types/symbol.rb +21 -0
  30. data/lib/no_brainer/document/types/time.rb +41 -0
  31. data/lib/no_brainer/document/types.rb +64 -124
  32. data/lib/no_brainer/document/uniqueness.rb +4 -2
  33. data/lib/no_brainer/document/validation.rb +2 -1
  34. data/lib/no_brainer/document.rb +2 -0
  35. data/lib/no_brainer/error.rb +9 -9
  36. data/lib/no_brainer/query_runner/logger.rb +18 -12
  37. data/lib/no_brainer/query_runner/missing_index.rb +15 -3
  38. data/lib/no_brainer/query_runner/reconnect.rb +15 -4
  39. data/lib/no_brainer/query_runner/table_on_demand.rb +14 -25
  40. data/lib/no_brainer/query_runner/write_error.rb +5 -8
  41. data/lib/no_brainer/rql.rb +25 -0
  42. data/lib/nobrainer.rb +9 -6
  43. data/lib/rails/generators/nobrainer/model/model_generator.rb +2 -1
  44. data/lib/rails/generators/nobrainer/model/templates/model.rb.tt +7 -2
  45. metadata +18 -11
  46. data/lib/no_brainer/util.rb +0 -23
@@ -19,7 +19,8 @@ module NoBrainer::Document::Persistance
19
19
  end
20
20
 
21
21
  def reload(options={})
22
- attrs = selector.raw.first!
22
+ attrs = NoBrainer.run { self.class.selector_for(pk_value) }
23
+ raise NoBrainer::Error::DocumentNotFound, "#{self.class} #{self.class.pk_name}: #{pk_value} not found" unless attrs
23
24
  instance_variables.each { |ivar| remove_instance_variable(ivar) } unless options[:keep_ivars]
24
25
  initialize(attrs, :pristine => true, :from_db => true)
25
26
  self
@@ -27,14 +28,14 @@ module NoBrainer::Document::Persistance
27
28
 
28
29
  def _create(options={})
29
30
  return false if options[:validate] && !valid?
30
- keys = self.class.insert_all(@_attributes)
31
- self.id ||= keys.first
31
+ keys = self.class.insert_all(self.class.persistable_attributes(@_attributes))
32
+ self.pk_value ||= keys.first
32
33
  @new_record = false
33
34
  true
34
35
  end
35
36
 
36
37
  def _update(attrs)
37
- selector.update_all(attrs)
38
+ NoBrainer.run { selector.update(attrs) }
38
39
  end
39
40
 
40
41
  def _update_only_changed_attrs(options={})
@@ -49,7 +50,7 @@ module NoBrainer::Document::Persistance
49
50
  attr = RethinkDB::RQL.new.literal(attr) if attr.is_a?(Hash)
50
51
  [k, attr]
51
52
  end]
52
- _update(attrs) if attrs.present?
53
+ _update(self.class.persistable_attributes(attrs)) if attrs.present?
53
54
  true
54
55
  end
55
56
 
@@ -73,7 +74,7 @@ module NoBrainer::Document::Persistance
73
74
 
74
75
  def delete
75
76
  unless @destroyed
76
- selector.delete_all
77
+ NoBrainer.run { selector.delete }
77
78
  @destroyed = true
78
79
  end
79
80
  @_attributes.freeze
@@ -15,5 +15,12 @@ module NoBrainer::Document::Readonly
15
15
  end
16
16
  end
17
17
  end
18
+
19
+ def _remove_field(attr, options={})
20
+ super
21
+ inject_in_layer :readonly do
22
+ remove_method("#{attr}=") if method_defined?("#{attr}=")
23
+ end
24
+ end
18
25
  end
19
26
  end
@@ -5,4 +5,8 @@ module NoBrainer::Document::Serialization
5
5
  include ActiveModel::Serializers::JSON
6
6
 
7
7
  included { self.include_root_in_json = false }
8
+
9
+ def read_attribute_for_serialization(*a, &b)
10
+ read_attribute(*a, &b)
11
+ end
8
12
  end
@@ -0,0 +1,26 @@
1
+ # We namespace our fake Boolean class to avoid polluting the global namespace
2
+ class NoBrainer::Boolean
3
+ def initialize; raise; end
4
+ def self.inspect; 'Boolean'; end
5
+ def self.to_s; inspect; end
6
+ def self.name; inspect; end
7
+
8
+ module NoBrainerExtentions
9
+ InvalidType = NoBrainer::Error::InvalidType
10
+
11
+ def nobrainer_cast_user_to_model(value)
12
+ case value
13
+ when TrueClass then true
14
+ when FalseClass then false
15
+ when String, Integer
16
+ value = value.to_s.strip.downcase
17
+ return true if value.in? %w(true yes t 1)
18
+ return false if value.in? %w(false no f 0)
19
+ raise InvalidType
20
+ else raise InvalidType
21
+ end
22
+ end
23
+ end
24
+ extend NoBrainerExtentions
25
+ end
26
+
@@ -0,0 +1,26 @@
1
+ class Date
2
+ module NoBrainerExtentions
3
+ InvalidType = NoBrainer::Error::InvalidType
4
+
5
+ def nobrainer_cast_user_to_model(value)
6
+ case value
7
+ when Date then value
8
+ when String
9
+ value = value.strip
10
+ date = Date.parse(value) rescue (raise InvalidType)
11
+ raise InvalidType unless date.iso8601 == value
12
+ date
13
+ else raise InvalidType
14
+ end
15
+ end
16
+
17
+ def nobrainer_cast_db_to_model(value)
18
+ value.is_a?(Time) ? value.to_date : value
19
+ end
20
+
21
+ def nobrainer_cast_model_to_db(value)
22
+ value.is_a?(Date) ? Time.utc(value.year, value.month, value.day) : value
23
+ end
24
+ end
25
+ extend NoBrainerExtentions
26
+ end
@@ -0,0 +1,20 @@
1
+ class Float
2
+ module NoBrainerExtentions
3
+ InvalidType = NoBrainer::Error::InvalidType
4
+
5
+ def nobrainer_cast_user_to_model(value)
6
+ case value
7
+ when Float then value
8
+ when Integer then value.to_f
9
+ when String
10
+ value = value.strip.gsub(/^\+/, '')
11
+ value = value.gsub(/0+$/, '') if value['.']
12
+ value = value.gsub(/\.$/, '')
13
+ value = "#{value}.0" unless value['.']
14
+ value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
15
+ else raise InvalidType
16
+ end
17
+ end
18
+ end
19
+ extend NoBrainerExtentions
20
+ end
@@ -0,0 +1,18 @@
1
+ class Integer
2
+ module NoBrainerExtentions
3
+ InvalidType = NoBrainer::Error::InvalidType
4
+
5
+ def nobrainer_cast_user_to_model(value)
6
+ case value
7
+ when Integer then value
8
+ when String
9
+ value = value.strip.gsub(/^\+/, '')
10
+ value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
11
+ when Float
12
+ value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
13
+ else raise InvalidType
14
+ end
15
+ end
16
+ end
17
+ extend NoBrainerExtentions
18
+ end
@@ -0,0 +1,14 @@
1
+ class String
2
+ module NoBrainerExtentions
3
+ InvalidType = NoBrainer::Error::InvalidType
4
+
5
+ def nobrainer_cast_user_to_model(value)
6
+ case value
7
+ when String then value
8
+ when Symbol then value.to_s
9
+ else raise InvalidType
10
+ end
11
+ end
12
+ end
13
+ extend NoBrainerExtentions
14
+ end
@@ -0,0 +1,21 @@
1
+ class Symbol
2
+ module NoBrainerExtentions
3
+ InvalidType = NoBrainer::Error::InvalidType
4
+
5
+ def nobrainer_cast_user_to_model(value)
6
+ case value
7
+ when Symbol then value
8
+ when String
9
+ value = value.strip
10
+ raise InvalidType if value.empty?
11
+ value.to_sym
12
+ else raise InvalidType
13
+ end
14
+ end
15
+
16
+ def nobrainer_cast_db_to_model(value)
17
+ value.to_sym rescue (value.to_s.to_sym rescue value)
18
+ end
19
+ end
20
+ extend NoBrainerExtentions
21
+ end
@@ -0,0 +1,41 @@
1
+ require 'time'
2
+
3
+ class Time
4
+ module NoBrainerExtentions
5
+ InvalidType = NoBrainer::Error::InvalidType
6
+
7
+ def nobrainer_cast_user_to_model(value)
8
+ case value
9
+ when Time then time = value
10
+ when String
11
+ value = value.strip.sub(/Z$/, '+00:00')
12
+ # Using DateTime to preserve the timezone offset
13
+ dt = DateTime.parse(value) rescue (raise InvalidType)
14
+ time = Time.new(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.zone)
15
+ raise InvalidType unless time.iso8601 == value
16
+ else raise InvalidType
17
+ end
18
+
19
+ nobrainer_timezoned(NoBrainer::Config.user_timezone, time)
20
+ end
21
+
22
+ def nobrainer_cast_db_to_model(value)
23
+ return value unless value.is_a?(Time)
24
+ nobrainer_timezoned(NoBrainer::Config.user_timezone, value)
25
+ end
26
+
27
+ def nobrainer_cast_model_to_db(value)
28
+ return value unless value.is_a?(Time)
29
+ nobrainer_timezoned(NoBrainer::Config.db_timezone, value)
30
+ end
31
+
32
+ def nobrainer_timezoned(tz, value)
33
+ case tz
34
+ when :local then value.getlocal
35
+ when :utc then value.getutc
36
+ when :unchanged then value
37
+ end
38
+ end
39
+ end
40
+ extend NoBrainerExtentions
41
+ end
@@ -1,102 +1,7 @@
1
1
  module NoBrainer::Document::Types
2
2
  extend ActiveSupport::Concern
3
3
 
4
- module CastUserToDB
5
- extend self
6
- InvalidType = NoBrainer::Error::InvalidType
7
-
8
- def String(value)
9
- case value
10
- when Symbol then value.to_s
11
- else raise InvalidType
12
- end
13
- end
14
-
15
- def Integer(value)
16
- case value
17
- when String
18
- value = value.strip.gsub(/^\+/, '')
19
- value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
20
- when Float
21
- value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
22
- else raise InvalidType
23
- end
24
- end
25
-
26
- def Float(value)
27
- case value
28
- when Integer then value.to_f
29
- when String
30
- value = value.strip.gsub(/^\+/, '')
31
- value = value.gsub(/0+$/, '') if value['.']
32
- value = value.gsub(/\.$/, '')
33
- value = "#{value}.0" unless value['.']
34
- value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
35
- else raise InvalidType
36
- end
37
- end
38
-
39
- def Boolean(value)
40
- case value
41
- when TrueClass then true
42
- when FalseClass then false
43
- when String, Integer
44
- value = value.to_s.strip.downcase
45
- return true if value.in? %w(true yes t 1)
46
- return false if value.in? %w(false no f 0)
47
- raise InvalidType
48
- else raise InvalidType
49
- end
50
- end
51
-
52
- def Symbol(value)
53
- case value
54
- when String
55
- value = value.strip
56
- raise InvalidType if value.empty?
57
- value.to_sym
58
- else raise InvalidType
59
- end
60
- end
61
-
62
- def lookup(type)
63
- public_method(type.to_s)
64
- rescue NameError
65
- proc { raise InvalidType }
66
- end
67
- end
68
-
69
- module CastDBToUser
70
- extend self
71
-
72
- def Symbol(value)
73
- value.to_sym rescue value
74
- end
75
-
76
- def lookup(type)
77
- public_method(type.to_s)
78
- rescue NameError
79
- nil
80
- end
81
- end
82
-
83
- included do
84
- # We namespace our fake Boolean class to avoid polluting the global namespace
85
- class_exec do
86
- class Boolean
87
- def initialize; raise; end
88
- def self.inspect; 'Boolean'; end
89
- def self.to_s; inspect; end
90
- def self.name; inspect; end
91
- end
92
- end
93
- before_validation :add_type_errors
94
-
95
- # Fast access for db->user cast methods for performance when reading from
96
- # the database.
97
- singleton_class.send(:attr_accessor, :cast_db_to_user_fields)
98
- self.cast_db_to_user_fields = Set.new
99
- end
4
+ included { before_validation :add_type_errors }
100
5
 
101
6
  def add_type_errors
102
7
  return unless @pending_type_errors
@@ -108,49 +13,59 @@ module NoBrainer::Document::Types
108
13
  def assign_attributes(attrs, options={})
109
14
  super
110
15
  if options[:from_db]
111
- self.class.cast_db_to_user_fields.each do |attr|
112
- field_def = self.class.fields[attr]
113
- type = field_def[:type]
114
- value = @_attributes[attr.to_s]
115
- unless value.nil? || value.is_a?(type)
116
- @_attributes[attr.to_s] = field_def[:cast_db_to_user].call(value)
117
- end
118
- end
16
+ @_attributes = Hash[@_attributes.map do |k,v|
17
+ [k, self.class.cast_db_to_model_for(k, v)]
18
+ end].with_indifferent_access
119
19
  end
120
20
  end
121
21
 
122
22
  module ClassMethods
123
- def cast_user_to_db_for(attr, value)
124
- field_def = fields[attr.to_sym]
125
- return value if !field_def
126
- type = field_def[:type]
127
- return value if value.nil? || type.nil? || value.is_a?(type)
128
- field_def[:cast_user_to_db].call(value)
23
+ def cast_user_to_model_for(attr, value)
24
+ type = fields[attr.to_sym].try(:[], :type)
25
+ return value if type.nil? || value.nil?
26
+ if type.respond_to?(:nobrainer_cast_user_to_model)
27
+ type.nobrainer_cast_user_to_model(value)
28
+ else
29
+ raise NoBrainer::Error::InvalidType unless value.is_a?(type)
30
+ value
31
+ end
129
32
  rescue NoBrainer::Error::InvalidType => error
130
- error.type = field_def[:type]
33
+ error.type = type
131
34
  error.value = value
132
35
  error.attr_name = attr
133
36
  raise error
134
37
  end
135
38
 
136
- def inherited(subclass)
137
- super
138
- subclass.cast_db_to_user_fields = self.cast_db_to_user_fields.dup
39
+ def cast_model_to_db_for(attr, value)
40
+ type = fields[attr.to_sym].try(:[], :type)
41
+ return value if type.nil? || value.nil? || !type.respond_to?(:nobrainer_cast_model_to_db)
42
+ type.nobrainer_cast_model_to_db(value)
43
+ end
44
+
45
+ def cast_db_to_model_for(attr, value)
46
+ type = fields[attr.to_sym].try(:[], :type)
47
+ return value if type.nil? || value.nil? || !type.respond_to?(:nobrainer_cast_db_to_model)
48
+ type.nobrainer_cast_db_to_model(value)
49
+ end
50
+
51
+ def cast_user_to_db_for(attr, value)
52
+ value = cast_user_to_model_for(attr, value)
53
+ cast_model_to_db_for(attr, value)
54
+ end
55
+
56
+ def persistable_attributes(attrs)
57
+ Hash[attrs.map { |k,v| [k, cast_model_to_db_for(k, v)] }]
139
58
  end
140
59
 
141
60
  def _field(attr, options={})
142
61
  super
143
62
 
144
- if options[:cast_db_to_user]
145
- ([self] + descendants).each do |klass|
146
- klass.cast_db_to_user_fields << attr
147
- end
148
- end
63
+ NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
149
64
 
150
65
  inject_in_layer :types do
151
66
  define_method("#{attr}=") do |value|
152
67
  begin
153
- value = self.class.cast_user_to_db_for(attr, value)
68
+ value = self.class.cast_user_to_model_for(attr, value)
154
69
  @pending_type_errors.try(:delete, attr)
155
70
  rescue NoBrainer::Error::InvalidType => error
156
71
  @pending_type_errors ||= {}
@@ -163,13 +78,38 @@ module NoBrainer::Document::Types
163
78
  end
164
79
  end
165
80
 
81
+ def _remove_field(attr, options={})
82
+ super
83
+
84
+ inject_in_layer :types do
85
+ remove_method("#{attr}=")
86
+ remove_method("#{attr}?") if method_defined?("#{attr}?")
87
+ end
88
+ end
89
+
166
90
  def field(attr, options={})
167
- if options[:type]
168
- options = options.merge(
169
- :cast_user_to_db => NoBrainer::Document::Types::CastUserToDB.lookup(options[:type]),
170
- :cast_db_to_user => NoBrainer::Document::Types::CastDBToUser.lookup(options[:type]))
91
+ if options[:type] == Array || options[:type] == Hash
92
+ # XXX For the moment, NoBrainer does not support these complex types
93
+ options.delete(:type)
171
94
  end
172
95
  super
173
96
  end
174
97
  end
98
+
99
+ require File.join(File.dirname(__FILE__), 'types', 'boolean')
100
+ Boolean = NoBrainer::Boolean
101
+
102
+ class << self
103
+ mattr_accessor :loaded_extensions
104
+ self.loaded_extensions = Set.new
105
+ def load_type_extensions(klass)
106
+ unless loaded_extensions.include?(klass)
107
+ begin
108
+ require File.join(File.dirname(__FILE__), 'types', klass.name.underscore)
109
+ rescue LoadError
110
+ end
111
+ loaded_extensions << klass
112
+ end
113
+ end
114
+ end
175
115
  end
@@ -64,7 +64,9 @@ module NoBrainer::Document::Uniqueness
64
64
  class UniquenessValidator < ActiveModel::EachValidator
65
65
  attr_accessor :scope
66
66
 
67
- def setup(klass)
67
+ def initialize(options={})
68
+ super
69
+ klass = options[:class]
68
70
  self.scope = [*options[:scope]]
69
71
  ([klass] + klass.descendants).each do |_klass|
70
72
  _klass.unique_validators << self
@@ -91,7 +93,7 @@ module NoBrainer::Document::Uniqueness
91
93
  end
92
94
 
93
95
  def exclude_doc(criteria, doc)
94
- criteria.where(:id.ne => doc.id)
96
+ criteria.where(doc.class.pk_name.ne => doc.pk_value)
95
97
  end
96
98
  end
97
99
  end
@@ -6,7 +6,7 @@ module NoBrainer::Document::Validation
6
6
  included do
7
7
  # We don't want before_validation returning false to halt the chain.
8
8
  define_callbacks :validation, :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name],
9
- :terminator => NoBrainer::Document::Callbacks.terminator
9
+ :terminator => proc { false }
10
10
  end
11
11
 
12
12
  def valid?(context=nil)
@@ -18,6 +18,7 @@ module NoBrainer::Document::Validation
18
18
  super
19
19
  validates(attr, { :presence => options[:required] }) if options.has_key?(:required)
20
20
  validates(attr, { :uniqueness => options[:unique] }) if options.has_key?(:unique)
21
+ validates(attr, { :inclusion => {:in => options[:in]} }) if options.has_key?(:in)
21
22
  validates(attr, options[:validates]) if options[:validates]
22
23
  end
23
24
  end
@@ -10,5 +10,7 @@ module NoBrainer::Document
10
10
 
11
11
  autoload :DynamicAttributes, :Timestamps
12
12
 
13
+ included { define_default_pk }
14
+
13
15
  singleton_class.delegate :all, :to => Core
14
16
  end
@@ -1,13 +1,13 @@
1
1
  module NoBrainer::Error
2
- class Connection < RuntimeError; end
3
- class DocumentNotFound < RuntimeError; end
4
- class DocumentNotSaved < RuntimeError; end
5
- class ChildrenExist < RuntimeError; end
6
- class CannotUseIndex < RuntimeError; end
7
- class MissingIndex < RuntimeError; end
8
- class InvalidType < RuntimeError; end
2
+ class Connection < RuntimeError; end
3
+ class DocumentNotFound < RuntimeError; end
4
+ class DocumentNotPersisted < RuntimeError; end
5
+ class ChildrenExist < RuntimeError; end
6
+ class CannotUseIndex < RuntimeError; end
7
+ class MissingIndex < RuntimeError; end
8
+ class InvalidType < RuntimeError; end
9
9
  class AssociationNotPersisted < RuntimeError; end
10
- class ReadonlyField < RuntimeError; end
10
+ class ReadonlyField < RuntimeError; end
11
11
 
12
12
  class DocumentInvalid < RuntimeError
13
13
  attr_accessor :instance
@@ -33,7 +33,7 @@ module NoBrainer::Error
33
33
  end
34
34
 
35
35
  def message
36
- "#{attr_name} should be used with a #{human_type_name}. Got `#{value}`"
36
+ "#{attr_name} should be used with a #{human_type_name}. Got `#{value}` (#{value.class})"
37
37
  end
38
38
  end
39
39
  end
@@ -13,23 +13,29 @@ class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
13
13
  return unless NoBrainer.logger.debug?
14
14
 
15
15
  duration = Time.now - start_time
16
- msg = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
17
16
 
18
- msg = "(#{env[:db_name]}) #{msg}" if env[:db_name]
19
- msg = "[#{(duration * 1000.0).round(1)}ms] #{msg}"
17
+ msg_duration = (duration * 1000.0).round(1).to_s
18
+ msg_duration = " " * [0, 5 - msg_duration.size].max + msg_duration
19
+ msg_duration = "[#{msg_duration}ms] "
20
+
21
+ msg_db = "[#{env[:db_name]}] " if env[:db_name]
22
+ msg_query = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
23
+ msg_exception = " #{exception.class} #{exception.message.split("\n").first}" if exception
24
+ msg_last = nil
20
25
 
21
26
  if NoBrainer::Config.colorize_logger
22
- if exception
23
- msg = "#{msg} \e[0;31m#{exception.class} #{exception.message.split("\n").first}\e[0m"
24
- else
25
- case NoBrainer::Util.rql_type(env[:query])
26
- when :write then msg = "\e[1;31m#{msg}\e[0m" # red
27
- when :read then msg = "\e[1;32m#{msg}\e[0m" # green
28
- when :management then msg = "\e[1;33m#{msg}\e[0m" # yellow
29
- end
30
- end
27
+ query_color = case NoBrainer::RQL.type_of(env[:query])
28
+ when :write then "\e[1;31m" # red
29
+ when :read then "\e[1;32m" # green
30
+ when :management then "\e[1;33m" # yellow
31
+ end
32
+ msg_duration = [query_color, msg_duration].join
33
+ msg_db = ["\e[0;34m", msg_db, query_color].join if msg_db
34
+ msg_exception = ["\e[0;31m", msg_exception].join if msg_exception
35
+ msg_last = "\e[0m"
31
36
  end
32
37
 
38
+ msg = [msg_duration, msg_db, msg_query, msg_exception, msg_last].join
33
39
  NoBrainer.logger.debug(msg)
34
40
  end
35
41
  end
@@ -2,9 +2,21 @@ class NoBrainer::QueryRunner::MissingIndex < NoBrainer::QueryRunner::Middleware
2
2
  def call(env)
3
3
  @runner.call(env)
4
4
  rescue RethinkDB::RqlRuntimeError => e
5
- if e.message =~ /^Index `(.+)` was not found\.$/
6
- raise NoBrainer::Error::MissingIndex.new("Please run \"rake db:update_indexes\" to create the index `#{$1}`\n" +
7
- "--> Read http://nobrainer.io/docs/indexes for more information.")
5
+ if e.message =~ /^Index `(.+)` was not found on table `(.+)\.(.+)`\.$/
6
+ index_name = $1
7
+ database_name = $2
8
+ table_name = $3
9
+
10
+ klass = NoBrainer::Document.all.select { |m| m.table_name == table_name }.first
11
+ if klass && klass.pk_name.to_s == index_name
12
+ err_msg = "Please run update the primary key `#{index_name}` in the table `#{database_name}.#{table_name}`."
13
+ else
14
+ err_msg = "Please run \"rake db:update_indexes\" to create the index `#{index_name}`"
15
+ err_msg += " in the table `#{database_name}.#{table_name}`."
16
+ err_msg += "\n--> Read http://nobrainer.io/docs/indexes for more information."
17
+ end
18
+
19
+ raise NoBrainer::Error::MissingIndex.new(err_msg)
8
20
  end
9
21
  raise
10
22
  end
@@ -4,20 +4,20 @@ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
4
4
  rescue StandardError => e
5
5
  # TODO test that thing
6
6
  if is_connection_error_exception?(e)
7
- retry if reconnect
7
+ retry if reconnect(e)
8
8
  end
9
9
  raise
10
10
  end
11
11
 
12
12
  private
13
13
 
14
- def reconnect
14
+ def reconnect(e)
15
15
  # FIXME thread safety? perhaps we need to use a connection pool
16
16
  # XXX Possibly dangerous, as we could reexecute a non idempotent operation
17
17
  # Check the semantics of the db
18
18
  NoBrainer::Config.max_reconnection_tries.times do
19
19
  begin
20
- NoBrainer.logger.try(:warn, "Lost connection to #{NoBrainer::Config.rethinkdb_url}, retrying...")
20
+ warn_reconnect(e)
21
21
  sleep 1
22
22
  NoBrainer.connection.reconnect(:noreply_wait => false)
23
23
  return true
@@ -35,10 +35,21 @@ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
35
35
  Errno::ECONNRESET, Errno::ETIMEDOUT, IOError
36
36
  true
37
37
  when RethinkDB::RqlRuntimeError
38
- e.message =~ /cannot perform (read|write): No master available/ ||
38
+ e.message =~ /No master available/ ||
39
+ e.message =~ /Master .* not available/ ||
39
40
  e.message =~ /Error: Connection Closed/
40
41
  else
41
42
  false
42
43
  end
43
44
  end
45
+
46
+ def warn_reconnect(e)
47
+ if e.is_a?(RethinkDB::RqlRuntimeError)
48
+ e_msg = e.message.split("\n").first
49
+ msg = "Server #{NoBrainer::Config.rethinkdb_url} not ready - #{e_msg}, retrying..."
50
+ else
51
+ msg = "Connection issue with #{NoBrainer::Config.rethinkdb_url} - #{e}, retrying..."
52
+ end
53
+ NoBrainer.logger.try(:warn, msg)
54
+ end
44
55
  end