nobrainer 0.13.0 → 0.13.1

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: dd6046706e63442640e96473b737f37ad5a4fea7
4
- data.tar.gz: e1634aaf6acfb3c3e5e4287c57151d54cce30ee1
3
+ metadata.gz: b5ca498dfa914c0c0178f339485d78ba804c5b95
4
+ data.tar.gz: c0e0c6f35d8927bb6281f860aac769753df59b88
5
5
  SHA512:
6
- metadata.gz: ac735666befc4a025536e5beb72a8c4d4d79259875a8ad956ae16e4399c17cf5c2dcc7eecf6bcfa4351d56aec14b1a83a7d68cb751723d5beade516011990915
7
- data.tar.gz: b6f69cd4dfbd953d9acc06a1c06aa9a27851baa51b1e1a2d0fce0ff320a32131c49724e5ea6aaa8af3a2aaf74bdb661379eb0a4e97abdb98e8084e5fae0516c5
6
+ metadata.gz: ed914e1eee020658197d596245404a2a088acbf033f5fc3a44cb490620cdc5ef110d62e4da927e7579e5e3383bc90e16dd499269a9bd8952dfa2011d3e2f2a12
7
+ data.tar.gz: f3204081b4ef7617706ee3c037830bdacbde973ae79e914b782b39fb41f1ec36b0125f78b427d0f21078bdae543ee426f2936ba69a1327bc36176acc6997765e
@@ -4,7 +4,8 @@ module NoBrainer::Config
4
4
  class << self
5
5
  mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
6
6
  :auto_create_databases, :auto_create_tables,
7
- :max_reconnection_tries, :durability, :colorize_logger
7
+ :max_reconnection_tries, :durability, :colorize_logger,
8
+ :distributed_lock_class
8
9
 
9
10
  def apply_defaults
10
11
  self.rethinkdb_url = default_rethinkdb_url
@@ -15,6 +16,7 @@ module NoBrainer::Config
15
16
  self.max_reconnection_tries = 10
16
17
  self.durability = default_durability
17
18
  self.colorize_logger = true
19
+ self.distributed_lock_class = nil
18
20
  end
19
21
 
20
22
  def reset!
@@ -1,7 +1,7 @@
1
1
  require 'rethinkdb'
2
2
 
3
3
  class NoBrainer::Connection
4
- attr_accessor :uri
4
+ attr_accessor :uri, :parsed_uri
5
5
 
6
6
  def initialize(uri)
7
7
  self.uri = uri
@@ -9,19 +9,21 @@ class NoBrainer::Connection
9
9
  end
10
10
 
11
11
  def parsed_uri
12
- require 'uri'
13
- uri = URI.parse(self.uri)
12
+ @parsed_uri ||= begin
13
+ require 'uri'
14
+ uri = URI.parse(self.uri)
14
15
 
15
- if uri.scheme != 'rethinkdb'
16
- raise NoBrainer::Error::Connection,
17
- "Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
18
- end
16
+ if uri.scheme != 'rethinkdb'
17
+ raise NoBrainer::Error::Connection,
18
+ "Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
19
+ end
19
20
 
20
- { :auth_key => uri.password,
21
- :host => uri.host,
22
- :port => uri.port || 28015,
23
- :db => uri.path.gsub(/^\//, ''),
24
- }.tap { |result| raise "No database specified in #{uri}" unless result[:db].present? }
21
+ { :auth_key => uri.password,
22
+ :host => uri.host,
23
+ :port => uri.port || 28015,
24
+ :db => uri.path.gsub(/^\//, ''),
25
+ }.tap { |result| raise "No database specified in #{uri}" unless result[:db].present? }
26
+ end
25
27
  end
26
28
 
27
29
  def raw
@@ -17,12 +17,7 @@ module NoBrainer::Criteria::Core
17
17
 
18
18
  def inspect
19
19
  # rescue super because sometimes klass is not set.
20
- str = to_rql.inspect rescue super
21
- if str =~ /Erroneous_Portion_Constructed/
22
- # Need to fix the rethinkdb gem.
23
- str = "the rethinkdb gem is flipping out with Erroneous_Portion_Constructed"
24
- end
25
- str
20
+ to_rql.inspect rescue super
26
21
  end
27
22
 
28
23
  def run(rql=nil)
@@ -12,11 +12,6 @@ module NoBrainer::Criteria::Preload
12
12
  chain(:keep_cache => true) { |criteria| criteria._preloads = values }
13
13
  end
14
14
 
15
- def includes(*values)
16
- NoBrainer.logger.warn "[NoBrainer] includes() is deprecated and will be removed, use preload() instead."
17
- preload(*values)
18
- end
19
-
20
15
  def merge!(criteria, options={})
21
16
  super
22
17
  self._preloads = self._preloads + criteria._preloads
@@ -56,14 +56,15 @@ module NoBrainer::Criteria::Where
56
56
 
57
57
  class BinaryOperator < Struct.new(:key, :op, :value, :criteria)
58
58
  def simplify
59
+ key = cast_key(self.key)
59
60
  case op
60
61
  when :in then
61
62
  case value
62
- when Range then BinaryOperator.new(key, :between, (cast(value.min)..cast(value.max)), criteria)
63
- when Array then BinaryOperator.new(key, :in, value.map(&method(:cast)).uniq, criteria)
63
+ when Range then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria)
64
+ when Array then BinaryOperator.new(key, :in, value.map(&method(:cast_value)).uniq, criteria)
64
65
  else raise ArgumentError.new ":in takes an array/range, not #{value}"
65
66
  end
66
- else BinaryOperator.new(key, op, cast(value), criteria)
67
+ else BinaryOperator.new(key, op, cast_value(value), criteria)
67
68
  end
68
69
  end
69
70
 
@@ -77,8 +78,33 @@ module NoBrainer::Criteria::Where
77
78
 
78
79
  private
79
80
 
80
- def cast(value)
81
- criteria.klass.cast_value_for(key, value)
81
+ def association
82
+ # FIXME This leaks memory with dynamic attributes. The internals of type
83
+ # checking will convert the key to a symbol, and Ruby does not garbage
84
+ # collect symbols.
85
+ @association ||= [criteria.klass.association_metadata[key.to_sym]]
86
+ @association.first
87
+ end
88
+
89
+ def cast_value(value)
90
+ case association
91
+ when NoBrainer::Document::Association::BelongsTo::Metadata
92
+ target_klass = association.target_klass
93
+ opts = { :attr_name => key, :value => value, :type => target_klass}
94
+ raise NoBrainer::Error::InvalidType.new(opts) unless value.is_a?(target_klass)
95
+ value.id
96
+ else
97
+ criteria.klass.cast_user_to_db_for(key, value)
98
+ end
99
+ end
100
+
101
+ def cast_key(key)
102
+ case association
103
+ when NoBrainer::Document::Association::BelongsTo::Metadata
104
+ association.foreign_key
105
+ else
106
+ key
107
+ end
82
108
  end
83
109
  end
84
110
 
@@ -126,8 +152,9 @@ module NoBrainer::Criteria::Where
126
152
  when String, Symbol then parse_clause_stub_eq(key, value)
127
153
  when NoBrainer::DecoratedSymbol then
128
154
  case key.modifier
129
- when :ne then parse_clause(:not => { key.symbol => value })
130
- when :eq then parse_clause_stub_eq(key.symbol, value)
155
+ when :nin then parse_clause(:not => { key.symbol.in => value })
156
+ when :ne then parse_clause(:not => { key.symbol.eq => value })
157
+ when :eq then parse_clause_stub_eq(key.symbol, value)
131
158
  else BinaryOperator.new(key.symbol, key.modifier, value, self)
132
159
  end
133
160
  else raise "Invalid key: #{key}"
@@ -1,5 +1,6 @@
1
1
  class NoBrainer::DecoratedSymbol < Struct.new(:symbol, :modifier, :args)
2
- MODIFIERS = { :ne => :ne, :not => :ne, :in => :in, :eq => :eq,
2
+ MODIFIERS = { :in => :in, :nin => :nin,
3
+ :eq => :eq, :ne => :ne, :not => :ne,
3
4
  :gt => :gt, :ge => :ge, :gte => :ge,
4
5
  :lt => :lt, :le => :le, :lte => :le}
5
6
 
@@ -5,7 +5,7 @@ module NoBrainer::Document
5
5
  extend NoBrainer::Autoload
6
6
 
7
7
  autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly, :Validation, :Types,
8
- :Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
8
+ :Persistance, :Uniqueness, :Callbacks, :Dirty, :Id, :Association, :Serialization,
9
9
  :Criteria, :Polymorphic, :Index
10
10
 
11
11
  autoload :DynamicAttributes, :Timestamps
@@ -30,7 +30,6 @@ class NoBrainer::Document::Association::BelongsTo
30
30
 
31
31
  delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
32
32
  add_callback_for(:after_validation)
33
- # TODO test if we are not overstepping on another foreign_key
34
33
  end
35
34
 
36
35
  eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ :id },
@@ -47,7 +46,7 @@ class NoBrainer::Document::Association::BelongsTo
47
46
  end
48
47
 
49
48
  def read
50
- return @target_container.first if loaded?
49
+ return target if loaded?
51
50
 
52
51
  if fk = owner.read_attribute(foreign_key)
53
52
  preload(target_klass.find(fk))
@@ -60,8 +59,12 @@ class NoBrainer::Document::Association::BelongsTo
60
59
  preload(target)
61
60
  end
62
61
 
63
- def preload(target)
64
- @target_container = [*target] # the * is for the generic eager loading code
62
+ def preload(targets)
63
+ @target_container = [*targets] # the * is for the generic eager loading code
64
+ target
65
+ end
66
+
67
+ def target
65
68
  @target_container.first
66
69
  end
67
70
 
@@ -70,8 +73,8 @@ class NoBrainer::Document::Association::BelongsTo
70
73
  end
71
74
 
72
75
  def after_validation_callback
73
- if loaded? && @target_container.first && !@target_container.first.persisted?
74
- raise NoBrainer::Error::AssociationNotSaved.new("#{target_name} must be saved first")
76
+ if loaded? && target && !target.persisted?
77
+ raise NoBrainer::Error::AssociationNotPersisted.new("#{target_name} must be saved first")
75
78
  end
76
79
  end
77
80
  end
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Document::Attributes
2
- VALID_FIELD_OPTIONS = [:index, :default, :type, :type_cast_method, :validates, :required, :readonly]
2
+ VALID_FIELD_OPTIONS = [:index, :default, :type, :cast_user_to_db, :cast_db_to_user, :validates, :required, :unique, :readonly]
3
3
  RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
4
4
  extend ActiveSupport::Concern
5
5
 
@@ -43,13 +43,14 @@ module NoBrainer::Document::Attributes
43
43
  @_attributes.clear if options[:pristine]
44
44
  if options[:from_db]
45
45
  @_attributes.merge!(attrs)
46
+ clear_dirtiness
46
47
  else
48
+ clear_dirtiness if options[:pristine]
47
49
  attrs.each { |k,v| self.write_attribute(k,v) }
48
50
  end
49
51
  assign_defaults if options[:pristine]
50
52
  self
51
53
  end
52
- def attributes=(*args); assign_attributes(*args); end
53
54
 
54
55
  def inspectable_attributes
55
56
  # TODO test that thing
@@ -1,10 +1,19 @@
1
1
  module NoBrainer::Document::Callbacks
2
2
  extend ActiveSupport::Concern
3
3
 
4
+ def self.terminator
5
+ if Gem.loaded_specs['activesupport'].version.release >= Gem::Version.new('4.1')
6
+ proc { false }
7
+ else
8
+ 'false'
9
+ end
10
+ end
11
+
4
12
  included do
5
13
  extend ActiveModel::Callbacks
6
- define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => 'false'
7
- define_model_callbacks :find, :only => [:after], :terminator => 'false'
14
+
15
+ define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => NoBrainer::Document::Callbacks.terminator
16
+ define_model_callbacks :find, :only => [:after], :terminator => NoBrainer::Document::Callbacks.terminator
8
17
  end
9
18
 
10
19
  def initialize(*args, &block)
@@ -1,8 +1,15 @@
1
1
  module NoBrainer::Document::Core
2
2
  extend ActiveSupport::Concern
3
3
 
4
- singleton_class.send(:attr_accessor, :all)
5
- self.all = []
4
+ singleton_class.class_eval do
5
+ attr_accessor :_all
6
+
7
+ def all
8
+ Rails.application.eager_load! if defined?(Rails)
9
+ @_all
10
+ end
11
+ end
12
+ self._all = []
6
13
 
7
14
  # TODO This assume the primary key is id.
8
15
  # RethinkDB can have a custom primary key. careful.
@@ -13,6 +20,6 @@ module NoBrainer::Document::Core
13
20
  extend ActiveModel::Naming
14
21
  extend ActiveModel::Translation
15
22
 
16
- NoBrainer::Document::Core.all << self
23
+ NoBrainer::Document::Core._all << self
17
24
  end
18
25
  end
@@ -6,11 +6,6 @@ module NoBrainer::Document::Dirty
6
6
  # things like undefined -> nil. Going through the getters will
7
7
  # not give us that.
8
8
 
9
- def assign_attributes(attrs, options={})
10
- clear_dirtiness if options[:pristine]
11
- super
12
- end
13
-
14
9
  def _create(*args)
15
10
  super.tap { clear_dirtiness }
16
11
  end
@@ -68,7 +68,7 @@ module NoBrainer::Document::Index
68
68
  end
69
69
 
70
70
  NoBrainer.run(self.rql_table.index_create(index_name, index_args[:options], &index_proc))
71
- NoBrainer.run(self.rql_table.index_wait(index_name)) if options[:wait]
71
+ wait_for_index(index_name) unless options[:wait] == false
72
72
  STDERR.puts "Created index #{self}.#{index_name}" if options[:verbose]
73
73
  end
74
74
 
@@ -90,5 +90,10 @@ module NoBrainer::Document::Index
90
90
  end
91
91
  end
92
92
  alias_method :update_indexes, :perform_update_indexes
93
+
94
+ def wait_for_index(index_name=nil, options={})
95
+ args = [index_name].compact
96
+ NoBrainer.run(self.rql_table.index_wait(*args))
97
+ end
93
98
  end
94
99
  end
@@ -1,11 +1,6 @@
1
1
  module NoBrainer::Document::Persistance
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included do
5
- extend ActiveModel::Callbacks
6
- define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
7
- end
8
-
9
4
  def _initialize(attrs={}, options={})
10
5
  @new_record = !options[:from_db]
11
6
  super
@@ -1,7 +1,7 @@
1
1
  module NoBrainer::Document::Types
2
2
  extend ActiveSupport::Concern
3
3
 
4
- module CastingRules
4
+ module CastUserToDB
5
5
  extend self
6
6
  InvalidType = NoBrainer::Error::InvalidType
7
7
 
@@ -60,14 +60,23 @@ module NoBrainer::Document::Types
60
60
  end
61
61
 
62
62
  def lookup(type)
63
- CastingRules.method(type.to_s)
63
+ public_method(type.to_s)
64
64
  rescue NameError
65
65
  proc { raise InvalidType }
66
66
  end
67
+ end
67
68
 
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)
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
71
80
  end
72
81
  end
73
82
 
@@ -82,6 +91,11 @@ module NoBrainer::Document::Types
82
91
  end
83
92
  end
84
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
85
99
  end
86
100
 
87
101
  def add_type_errors
@@ -91,12 +105,27 @@ module NoBrainer::Document::Types
91
105
  end
92
106
  end
93
107
 
108
+ def assign_attributes(attrs, options={})
109
+ super
110
+ 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
119
+ end
120
+ end
121
+
94
122
  module ClassMethods
95
- def cast_value_for(attr, value)
96
- attr = attr.to_sym
97
- field_def = fields[attr]
98
- return value unless field_def && field_def[:type]
99
- NoBrainer::Document::Types::CastingRules.cast(value, field_def[:type], field_def[:type_cast_method])
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)
100
129
  rescue NoBrainer::Error::InvalidType => error
101
130
  error.type = field_def[:type]
102
131
  error.value = value
@@ -104,13 +133,24 @@ module NoBrainer::Document::Types
104
133
  raise error
105
134
  end
106
135
 
136
+ def inherited(subclass)
137
+ super
138
+ subclass.cast_db_to_user_fields = self.cast_db_to_user_fields.dup
139
+ end
140
+
107
141
  def _field(attr, options={})
108
142
  super
109
143
 
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
149
+
110
150
  inject_in_layer :types do
111
151
  define_method("#{attr}=") do |value|
112
152
  begin
113
- value = self.class.cast_value_for(attr, value)
153
+ value = self.class.cast_user_to_db_for(attr, value)
114
154
  @pending_type_errors.try(:delete, attr)
115
155
  rescue NoBrainer::Error::InvalidType => error
116
156
  @pending_type_errors ||= {}
@@ -125,8 +165,9 @@ module NoBrainer::Document::Types
125
165
 
126
166
  def field(attr, options={})
127
167
  if options[:type]
128
- type_cast_method = NoBrainer::Document::Types::CastingRules.lookup(options[:type])
129
- options = options.merge(:type_cast_method => type_cast_method)
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]))
130
171
  end
131
172
  super
132
173
  end
@@ -0,0 +1,97 @@
1
+ module NoBrainer::Document::Uniqueness
2
+ extend ActiveSupport::Concern
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={})
12
+ lock_unique_fields
13
+ super
14
+ ensure
15
+ unlock_unique_fields
16
+ end
17
+
18
+ def _lock_key_from_field(field)
19
+ value = read_attribute(field).to_s
20
+ ['nobrainer', self.class.database_name || NoBrainer.connection.parsed_uri[:db],
21
+ self.class.table_name, field, value.empty? ? 'nil' : value].join(':')
22
+ end
23
+
24
+ def lock_unique_fields
25
+ return unless NoBrainer::Config.distributed_lock_class && !self.class.unique_validators.empty?
26
+
27
+ self.class.unique_validators
28
+ .map { |validator| validator.attributes.map { |attr| [attr, validator] } }
29
+ .flatten(1)
30
+ .select { |f, validator| validator.should_validate_uniquess_of?(self, f) }
31
+ .map { |f, options| _lock_key_from_field(f) }
32
+ .sort
33
+ .uniq
34
+ .each do |key|
35
+ lock = NoBrainer::Config.distributed_lock_class.new(key)
36
+ lock.lock
37
+ @locked_unique_fields ||= []
38
+ @locked_unique_fields << lock
39
+ end
40
+ end
41
+
42
+ def unlock_unique_fields
43
+ return unless @locked_unique_fields
44
+ @locked_unique_fields.pop.unlock until @locked_unique_fields.empty?
45
+ end
46
+
47
+ included do
48
+ singleton_class.send(:attr_accessor, :unique_validators)
49
+ self.unique_validators = []
50
+ end
51
+
52
+ module ClassMethods
53
+ def validates_uniqueness_of(*attr_names)
54
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
55
+ end
56
+
57
+ def inherited(subclass)
58
+ super
59
+ subclass.unique_validators = self.unique_validators.dup
60
+ end
61
+
62
+ end
63
+
64
+ class UniquenessValidator < ActiveModel::EachValidator
65
+ attr_accessor :scope
66
+
67
+ def setup(klass)
68
+ self.scope = [*options[:scope]]
69
+ ([klass] + klass.descendants).each do |_klass|
70
+ _klass.unique_validators << self
71
+ end
72
+ end
73
+
74
+ def should_validate_uniquess_of?(doc, field)
75
+ (scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
76
+ end
77
+
78
+ def validate_each(doc, attr, value)
79
+ return true unless should_validate_uniquess_of?(doc, attr)
80
+
81
+ criteria = doc.root_class.unscoped.where(attr => value)
82
+ criteria = apply_scopes(criteria, doc)
83
+ criteria = exclude_doc(criteria, doc) if doc.persisted?
84
+ is_unique = criteria.count == 0
85
+ doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
86
+ is_unique
87
+ end
88
+
89
+ def apply_scopes(criteria, doc)
90
+ criteria.where(scope.map { |k| {k => doc.read_attribute(k)} })
91
+ end
92
+
93
+ def exclude_doc(criteria, doc)
94
+ criteria.where(:id.ne => doc.id)
95
+ end
96
+ end
97
+ end
@@ -3,6 +3,12 @@ module NoBrainer::Document::Validation
3
3
  include ActiveModel::Validations
4
4
  include ActiveModel::Validations::Callbacks
5
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, :scope => [:kind, :name],
9
+ :terminator => NoBrainer::Document::Callbacks.terminator
10
+ end
11
+
6
12
  def valid?(context=nil)
7
13
  super(context || (new_record? ? :create : :update))
8
14
  end
@@ -10,31 +16,9 @@ module NoBrainer::Document::Validation
10
16
  module ClassMethods
11
17
  def _field(attr, options={})
12
18
  super
13
- validates(attr, { :presence => true }) if options[:required]
19
+ validates(attr, { :presence => options[:required] }) if options.has_key?(:required)
20
+ validates(attr, { :uniqueness => options[:unique] }) if options.has_key?(:unique)
14
21
  validates(attr, options[:validates]) if options[:validates]
15
22
  end
16
-
17
- def validates_uniqueness_of(*attr_names)
18
- validates_with UniquenessValidator, _merge_attributes(attr_names)
19
- end
20
- end
21
-
22
- class UniquenessValidator < ActiveModel::EachValidator
23
- def validate_each(doc, attr, value)
24
- criteria = doc.root_class.unscoped.where(attr => value)
25
- criteria = apply_scopes(criteria, doc)
26
- criteria = exclude_doc(criteria, doc) if doc.persisted?
27
- is_unique = criteria.count == 0
28
- doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless is_unique
29
- is_unique
30
- end
31
-
32
- def apply_scopes(criteria, doc)
33
- criteria.where([*options[:scope]].map { |k| {k => doc.read_attribute(k)} })
34
- end
35
-
36
- def exclude_doc(criteria, doc)
37
- criteria.where(:id.ne => doc.id)
38
- end
39
23
  end
40
24
  end
@@ -6,7 +6,7 @@ module NoBrainer::Error
6
6
  class CannotUseIndex < RuntimeError; end
7
7
  class MissingIndex < RuntimeError; end
8
8
  class InvalidType < RuntimeError; end
9
- class AssociationNotSaved < RuntimeError; end
9
+ class AssociationNotPersisted < RuntimeError; end
10
10
  class ReadonlyField < RuntimeError; end
11
11
 
12
12
  class DocumentInvalid < RuntimeError
@@ -1,6 +1,8 @@
1
1
  module NoBrainer::IndexManager
2
2
  def self.update_indexes(options={})
3
- Rails.application.eager_load! if defined?(Rails)
4
- NoBrainer::Document.all.each { |model| model.perform_update_indexes(options) }
3
+ NoBrainer::Document.all.each { |model| model.perform_update_indexes(options.merge(:wait => false)) }
4
+ unless options[:wait] == false
5
+ NoBrainer::Document.all.each { |model| model.wait_for_index(nil) }
6
+ end
5
7
  end
6
8
  end
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Loader
2
2
  def self.cleanup
3
- NoBrainer::Document.all.clear
3
+ NoBrainer::Document::Core._all.clear
4
4
  end
5
5
  end
@@ -11,7 +11,7 @@ module NoBrainer::QueryRunner
11
11
  end
12
12
 
13
13
  autoload :Driver, :DatabaseOnDemand, :TableOnDemand, :WriteError,
14
- :Connection, :Selection, :RunOptions, :Logger, :MissingIndex
14
+ :Reconnect, :Selection, :RunOptions, :Logger, :MissingIndex
15
15
 
16
16
  class << self
17
17
  attr_accessor :stack
@@ -27,12 +27,12 @@ module NoBrainer::QueryRunner
27
27
  # thread-safe, since require() is ran with a mutex.
28
28
  self.stack = ::Middleware::Builder.new do
29
29
  use RunOptions
30
- use Connection
31
30
  use WriteError
32
31
  use MissingIndex
33
32
  use DatabaseOnDemand
34
33
  use TableOnDemand
35
34
  use Logger
35
+ use Reconnect
36
36
  use Driver
37
37
  end
38
38
  end
@@ -1,29 +1,35 @@
1
1
  class NoBrainer::QueryRunner::Logger < NoBrainer::QueryRunner::Middleware
2
2
  def call(env)
3
3
  start_time = Time.now
4
- res = @runner.call(env)
5
- if NoBrainer.logger and NoBrainer.logger.debug?
6
- duration = Time.now - start_time
7
- msg = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
4
+ @runner.call(env).tap { log_query(env, start_time) }
5
+ rescue Exception => e
6
+ log_query(env, start_time, e) rescue nil
7
+ raise e
8
+ end
8
9
 
9
- # Need to fix the rethinkdb gem.
10
- if msg =~ /Erroneous_Portion_Constructed/
11
- msg = "r.the_rethinkdb_gem_is_flipping_out_with_Erroneous_Portion_Constructed"
12
- end
10
+ private
11
+
12
+ def log_query(env, start_time, exception=nil)
13
+ return unless NoBrainer.logger.debug?
13
14
 
14
- msg = "(#{env[:db_name]}) #{msg}" if env[:db_name]
15
- msg = "[#{(duration * 1000.0).round(1)}ms] #{msg}"
15
+ duration = Time.now - start_time
16
+ msg = env[:query].inspect.gsub(/\n/, '').gsub(/ +/, ' ')
16
17
 
17
- if NoBrainer::Config.colorize_logger
18
+ msg = "(#{env[:db_name]}) #{msg}" if env[:db_name]
19
+ msg = "[#{(duration * 1000.0).round(1)}ms] #{msg}"
20
+
21
+ 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
18
25
  case NoBrainer::Util.rql_type(env[:query])
19
26
  when :write then msg = "\e[1;31m#{msg}\e[0m" # red
20
27
  when :read then msg = "\e[1;32m#{msg}\e[0m" # green
21
28
  when :management then msg = "\e[1;33m#{msg}\e[0m" # yellow
22
29
  end
23
30
  end
24
-
25
- NoBrainer.logger.debug(msg)
26
31
  end
27
- res
32
+
33
+ NoBrainer.logger.debug(msg)
28
34
  end
29
35
  end
@@ -1,4 +1,4 @@
1
- class NoBrainer::QueryRunner::Connection < NoBrainer::QueryRunner::Middleware
1
+ class NoBrainer::QueryRunner::Reconnect < NoBrainer::QueryRunner::Middleware
2
2
  def call(env)
3
3
  @runner.call(env)
4
4
  rescue StandardError => e
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nobrainer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Viennot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-12 00:00:00.000000000 Z
11
+ date: 2014-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.11.0.1
19
+ version: 1.12.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.11.0.1
26
+ version: 1.12.0.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 4.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 4.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activemodel
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: 4.0.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 4.0.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: middleware
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 0.1.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.1.0
69
69
  description: ORM for RethinkDB
@@ -73,74 +73,75 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - LICENSE
77
+ - README.md
78
+ - lib/no_brainer/autoload.rb
79
+ - lib/no_brainer/config.rb
80
+ - lib/no_brainer/connection.rb
81
+ - lib/no_brainer/criteria.rb
82
+ - lib/no_brainer/criteria/after_find.rb
83
+ - lib/no_brainer/criteria/cache.rb
84
+ - lib/no_brainer/criteria/core.rb
85
+ - lib/no_brainer/criteria/count.rb
86
+ - lib/no_brainer/criteria/delete.rb
87
+ - lib/no_brainer/criteria/enumerable.rb
88
+ - lib/no_brainer/criteria/first.rb
89
+ - lib/no_brainer/criteria/limit.rb
90
+ - lib/no_brainer/criteria/order_by.rb
91
+ - lib/no_brainer/criteria/preload.rb
92
+ - lib/no_brainer/criteria/raw.rb
93
+ - lib/no_brainer/criteria/scope.rb
94
+ - lib/no_brainer/criteria/update.rb
95
+ - lib/no_brainer/criteria/where.rb
96
+ - lib/no_brainer/decorated_symbol.rb
97
+ - lib/no_brainer/document.rb
98
+ - lib/no_brainer/document/association.rb
99
+ - lib/no_brainer/document/association/belongs_to.rb
100
+ - lib/no_brainer/document/association/core.rb
101
+ - lib/no_brainer/document/association/eager_loader.rb
102
+ - lib/no_brainer/document/association/has_many.rb
103
+ - lib/no_brainer/document/association/has_many_through.rb
76
104
  - lib/no_brainer/document/association/has_one.rb
77
105
  - lib/no_brainer/document/association/has_one_through.rb
78
- - lib/no_brainer/document/association/has_many_through.rb
79
- - lib/no_brainer/document/association/has_many.rb
80
- - lib/no_brainer/document/association/eager_loader.rb
81
- - lib/no_brainer/document/association/core.rb
82
- - lib/no_brainer/document/association/belongs_to.rb
83
- - lib/no_brainer/document/polymorphic.rb
84
- - lib/no_brainer/document/store_in.rb
85
- - lib/no_brainer/document/core.rb
86
- - lib/no_brainer/document/serialization.rb
87
- - lib/no_brainer/document/association.rb
106
+ - lib/no_brainer/document/attributes.rb
88
107
  - lib/no_brainer/document/callbacks.rb
89
- - lib/no_brainer/document/dynamic_attributes.rb
90
- - lib/no_brainer/document/timestamps.rb
108
+ - lib/no_brainer/document/core.rb
109
+ - lib/no_brainer/document/criteria.rb
91
110
  - lib/no_brainer/document/dirty.rb
92
- - lib/no_brainer/document/index.rb
93
- - lib/no_brainer/document/validation.rb
94
- - lib/no_brainer/document/attributes.rb
111
+ - lib/no_brainer/document/dynamic_attributes.rb
95
112
  - lib/no_brainer/document/id.rb
113
+ - lib/no_brainer/document/index.rb
96
114
  - lib/no_brainer/document/injection_layer.rb
97
115
  - lib/no_brainer/document/persistance.rb
116
+ - lib/no_brainer/document/polymorphic.rb
98
117
  - lib/no_brainer/document/readonly.rb
118
+ - lib/no_brainer/document/serialization.rb
119
+ - lib/no_brainer/document/store_in.rb
120
+ - lib/no_brainer/document/timestamps.rb
99
121
  - lib/no_brainer/document/types.rb
100
- - lib/no_brainer/document/criteria.rb
122
+ - lib/no_brainer/document/uniqueness.rb
123
+ - lib/no_brainer/document/validation.rb
124
+ - lib/no_brainer/error.rb
125
+ - lib/no_brainer/fork.rb
126
+ - lib/no_brainer/index_manager.rb
127
+ - lib/no_brainer/loader.rb
128
+ - lib/no_brainer/locale/en.yml
129
+ - lib/no_brainer/query_runner.rb
101
130
  - lib/no_brainer/query_runner/database_on_demand.rb
102
- - lib/no_brainer/query_runner/table_on_demand.rb
103
- - lib/no_brainer/query_runner/write_error.rb
104
131
  - lib/no_brainer/query_runner/driver.rb
105
132
  - lib/no_brainer/query_runner/logger.rb
106
133
  - lib/no_brainer/query_runner/missing_index.rb
134
+ - lib/no_brainer/query_runner/reconnect.rb
107
135
  - lib/no_brainer/query_runner/run_options.rb
108
- - lib/no_brainer/query_runner/connection.rb
136
+ - lib/no_brainer/query_runner/table_on_demand.rb
137
+ - lib/no_brainer/query_runner/write_error.rb
138
+ - lib/no_brainer/railtie.rb
109
139
  - lib/no_brainer/railtie/database.rake
110
- - lib/no_brainer/index_manager.rb
111
- - lib/no_brainer/loader.rb
112
- - lib/no_brainer/locale/en.yml
113
- - lib/no_brainer/fork.rb
114
- - lib/no_brainer/connection.rb
115
- - lib/no_brainer/query_runner.rb
116
140
  - lib/no_brainer/util.rb
117
- - lib/no_brainer/decorated_symbol.rb
118
- - lib/no_brainer/criteria/scope.rb
119
- - lib/no_brainer/criteria/raw.rb
120
- - lib/no_brainer/criteria/preload.rb
121
- - lib/no_brainer/criteria/order_by.rb
122
- - lib/no_brainer/criteria/limit.rb
123
- - lib/no_brainer/criteria/first.rb
124
- - lib/no_brainer/criteria/enumerable.rb
125
- - lib/no_brainer/criteria/count.rb
126
- - lib/no_brainer/criteria/cache.rb
127
- - lib/no_brainer/criteria/after_find.rb
128
- - lib/no_brainer/criteria/where.rb
129
- - lib/no_brainer/criteria/update.rb
130
- - lib/no_brainer/criteria/delete.rb
131
- - lib/no_brainer/criteria/core.rb
132
- - lib/no_brainer/railtie.rb
133
- - lib/no_brainer/config.rb
134
- - lib/no_brainer/document.rb
135
- - lib/no_brainer/criteria.rb
136
- - lib/no_brainer/error.rb
137
- - lib/no_brainer/autoload.rb
141
+ - lib/nobrainer.rb
138
142
  - lib/rails/generators/nobrainer.rb
139
143
  - lib/rails/generators/nobrainer/model/model_generator.rb
140
144
  - lib/rails/generators/nobrainer/model/templates/model.rb.tt
141
- - lib/nobrainer.rb
142
- - README.md
143
- - LICENSE
144
145
  homepage: http://nobrainer.io
145
146
  licenses:
146
147
  - LGPLv3
@@ -151,17 +152,17 @@ require_paths:
151
152
  - lib
152
153
  required_ruby_version: !ruby/object:Gem::Requirement
153
154
  requirements:
154
- - - '>='
155
+ - - ">="
155
156
  - !ruby/object:Gem::Version
156
157
  version: 1.9.0
157
158
  required_rubygems_version: !ruby/object:Gem::Requirement
158
159
  requirements:
159
- - - '>='
160
+ - - ">="
160
161
  - !ruby/object:Gem::Version
161
162
  version: '0'
162
163
  requirements: []
163
164
  rubyforge_project:
164
- rubygems_version: 2.0.8
165
+ rubygems_version: 2.2.2
165
166
  signing_key:
166
167
  specification_version: 4
167
168
  summary: ORM for RethinkDB