nobrainer 0.13.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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