nobrainer 0.29.0 → 0.30.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fbf539d3fd47eeec38c4ae88843297a7462efb5
4
- data.tar.gz: e02bde6490ffc0eb21bf3fde637dc6a4dea0350d
3
+ metadata.gz: ebe188d50e06d6ef907cdf043560143e3bd7d703
4
+ data.tar.gz: 15b1580f27a66b6670386cc117b4767f930e67c7
5
5
  SHA512:
6
- metadata.gz: 586f87afabedec2bc9716949298255c6456b7cbf9846a6b9e3090fe009cf481d68fd3eb852368a2658f1e4b59e432388f690cd2fce24606460229dbad1daf633
7
- data.tar.gz: d55889b76d3a1e6dd48338fca51eb1ad636baa00bc20c21a992cf8e5e6d02a4f958f535dc95398a89c3695ceab87cf5ededbd915ac3b0f07f041dda16148e913
6
+ metadata.gz: ff9b387f2045109c33efb88d36273325edf34573acb134bbb94d3d2e04c1efd6fdf65b761739eac61c185309cfa9deba5a07987fdf56becf3d78308b300838b9
7
+ data.tar.gz: acb0f33b44d27ac25599352b0d568dd563e13a81da91b0dfef6cf72596a1500026e739f2cc64c63e22edc72ddb57ee3b61a70acf72c139cd502f0b1d95e78c9b
@@ -2,8 +2,9 @@ require 'rethinkdb'
2
2
 
3
3
  class NoBrainer::Criteria
4
4
  extend NoBrainer::Autoload
5
- autoload_and_include :Core, :Run, :Raw, :Scope, :AfterFind, :Where, :OrderBy,
6
- :Limit, :Pluck, :Count, :Delete, :Enumerable, :Find,
7
- :First, :FirstOrCreate, :Changes, :Aggregate, :EagerLoad,
8
- :Update, :Cache, :Index, :Extend, :Join
5
+ autoload_and_include :Core, :Run, :Raw, :VirtualAttributes, :Scope,
6
+ :AfterFind, :Where, :OrderBy, :Limit, :Pluck, :Count,
7
+ :Delete, :Enumerable, :Find, :First, :FirstOrCreate,
8
+ :Changes, :Aggregate, :EagerLoad, :Update, :Cache,
9
+ :Index, :Extend, :Join
9
10
  end
@@ -38,7 +38,11 @@ module NoBrainer::Criteria::FirstOrCreate
38
38
 
39
39
  def _first_or_create(create_params, save_options, &block)
40
40
  raise "Cannot use .raw() with .first_or_create()" if raw?
41
- raise "Use first_or_create() on the root class `#{model.root_class}'" unless model.is_root_class?
41
+
42
+ if block && block.arity == 1
43
+ raise "When passing a block to first_or_create(), you must pass a block with no arguments.\n" +
44
+ "The passed block must return a hash of additional attributes for create()"
45
+ end
42
46
 
43
47
  save_method = save_options.delete(:save_method)
44
48
  should_update = save_options.delete(:update)
@@ -62,6 +66,25 @@ module NoBrainer::Criteria::FirstOrCreate
62
66
  "\nend"
63
67
  end
64
68
 
69
+ unless model.is_root_class? || (model.superclass.fields.keys & keys).empty?
70
+ # We can't allow the parent to share the keys we are matching on.
71
+ # Consider this case:
72
+ # - Base has the field :name, :uniq => true declared
73
+ # - A < Base
74
+ # - B < Base
75
+ # - A.create(:name => 'x'),
76
+ # - B.where(:name => 'x').first_or_create
77
+ # We are forced to return nil, or raise.
78
+ parent = model
79
+ parent = parent.superclass while parent.superclass < NoBrainer::Document &&
80
+ !(parent.superclass.fields.keys & keys).empty?
81
+ raise "A polymorphic problem has been detected: The fields `#{keys.inspect}' are defined on `#{parent}'.\n" +
82
+ "This is problematic as first_or_create() could return nil in some cases.\n" +
83
+ "Either 1) Only define `#{keys.inspect}' on `#{model}', \n" +
84
+ "or 2) Query the superclass, and pass :_type in first_or_create() as such:\n" +
85
+ " `#{parent}.where(...).first_or_create(:_type => \"#{model}\")'."
86
+ end
87
+
65
88
  # We don't want to access create_params yet, because invoking the block
66
89
  # might be costly (the user might be doing some API call or w/e), and
67
90
  # so we want to invoke the block only if necessary.
@@ -108,18 +131,22 @@ module NoBrainer::Criteria::FirstOrCreate
108
131
  where_clauses = finalized_criteria.options[:where_ast]
109
132
 
110
133
  unless where_clauses.is_a?(NoBrainer::Criteria::Where::MultiOperator) &&
111
- where_clauses.op == :and && where_clauses.clauses.size > 0 &&
112
- where_clauses.clauses.all? do |c|
113
- c.is_a?(NoBrainer::Criteria::Where::BinaryOperator) &&
114
- c.op == :eq && c.key_modifier == :scalar
115
- end
134
+ where_clauses.op == :and
116
135
  raise "Please use a query of the form `.where(...).first_or_create(...)'"
117
136
  end
118
137
 
119
138
  Hash[where_clauses.clauses.map do |c|
139
+ unless c.is_a?(NoBrainer::Criteria::Where::BinaryOperator) &&
140
+ c.op == :eq && c.key_modifier == :scalar
141
+ # Ignore params on the subclass type, we are handling this case directly
142
+ # in _first_or_create()
143
+ next if c.key_path == [:_type]
144
+ raise "Please only use equal constraints in your where() query when using first_or_create()"
145
+ end
146
+
120
147
  raise "You may not use nested hash queries with first_or.create()" if c.key_path.size > 1
121
148
  [c.key_path.first.to_sym, c.value]
122
- end]
149
+ end.compact].tap { |h| raise "Missing where() clauses for first_or_create()" if h.empty? }
123
150
  end
124
151
 
125
152
  def get_model_unique_fields
@@ -53,9 +53,10 @@ module NoBrainer::Criteria::Join
53
53
  join_ast.reduce(super) do |rql, (association, criteria)|
54
54
  rql.concat_map do |doc|
55
55
  key = doc[association.eager_load_owner_key]
56
- criteria.where(association.eager_load_target_key => key).to_rql.map do |assoc_doc|
57
- doc.merge(association.target_name => assoc_doc)
58
- end
56
+ RethinkDB::RQL.new.branch(key.eq(nil), [],
57
+ criteria.where(association.eager_load_target_key => key).to_rql.map do |assoc_doc|
58
+ doc.merge(association.target_name => assoc_doc)
59
+ end)
59
60
  end
60
61
  end
61
62
  end
@@ -0,0 +1,18 @@
1
+ module NoBrainer::Criteria::VirtualAttributes
2
+ extend ActiveSupport::Concern
3
+
4
+ def compile_rql_pass2
5
+ rql = super
6
+
7
+ if model.virtual_fields
8
+ rql = rql.map do |_doc|
9
+ model.virtual_fields.reduce(_doc) do |doc, field|
10
+ field_rql = model.fields[field][:virtual].call(doc, RethinkDB::RQL.new)
11
+ field_rql.nil? ? doc : doc.merge(field => field_rql)
12
+ end
13
+ end
14
+ end
15
+
16
+ rql
17
+ end
18
+ end
@@ -195,11 +195,12 @@ module NoBrainer::Criteria::Where
195
195
  else
196
196
  # 1) Box value in array if we have an any/all modifier
197
197
  # 2) Box value in hash if we have a nested query.
198
- value = [value] if key_modifier.in?([:any, :all])
198
+ box_value = key_modifier.in?([:any, :all]) || op == :include
199
+ value = [value] if box_value
199
200
  value_hash = key_path.reverse.reduce(value) { |v,k| {k => v} }
200
201
  value = model.cast_user_to_db_for(*value_hash.first)
201
202
  value = key_path[1..-1].reduce(value) { |h,k| h[k] }
202
- value = value.first if key_modifier.in?([:any, :all])
203
+ value = value.first if box_value
203
204
  value
204
205
  end
205
206
  end
@@ -7,7 +7,7 @@ module NoBrainer::Document
7
7
  autoload_and_include :Core, :TableConfig, :InjectionLayer, :Attributes, :Readonly,
8
8
  :Persistance, :Callbacks, :Validation, :Types, :Dirty, :PrimaryKey,
9
9
  :Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
10
- :MissingAttributes, :LazyFetch, :AtomicOps
10
+ :MissingAttributes, :LazyFetch, :AtomicOps, :VirtualAttributes
11
11
 
12
12
  autoload :DynamicAttributes, :Timestamps
13
13
 
@@ -2,7 +2,7 @@ module NoBrainer::Document::Attributes
2
2
  VALID_FIELD_OPTIONS = [:index, :default, :type, :readonly, :primary_key,
3
3
  :lazy_fetch, :store_as, :validates, :required, :unique,
4
4
  :uniq, :format, :in, :length, :min_length, :max_length,
5
- :prefix, :suffix]
5
+ :prefix, :suffix, :virtual]
6
6
  RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] +
7
7
  NoBrainer::SymbolDecoration::OPERATORS
8
8
 
@@ -15,6 +15,7 @@ module NoBrainer::Document::Index
15
15
  name = case name
16
16
  when String then name.to_sym
17
17
  when Symbol then name
18
+ when Array then args.unshift(name); name.map(&:to_s).join('_').to_sym
18
19
  else raise ArgumentError, "Incorrect index specification"
19
20
  end
20
21
 
@@ -18,18 +18,13 @@ module NoBrainer::Document::Persistance
18
18
  !new_record? && !destroyed?
19
19
  end
20
20
 
21
- def _reload_selector(options={})
22
- rql = selector
21
+ def _reload(options={})
22
+ criteria = root_class.raw
23
23
  if opt = options[:missing_attributes]
24
- rql = rql.pluck(self.class.with_fields_aliased(opt[:pluck])) if opt[:pluck]
25
- rql = rql.without(self.class.with_fields_aliased(opt[:without])) if opt[:without]
24
+ criteria = criteria.pluck(opt[:pluck]) if opt[:pluck]
25
+ criteria = criteria.without(opt[:without]) if opt[:without]
26
26
  end
27
- rql
28
- end
29
-
30
- def _reload(options={})
31
- attrs = NoBrainer.run { _reload_selector(options) }
32
- raise NoBrainer::Error::DocumentNotFound, "#{self.class} :#{self.class.pk_name}=>\"#{pk_value}\" not found" unless attrs
27
+ attrs = criteria.find(pk_value)
33
28
 
34
29
  options = options.merge(:pristine => true, :from_db => true)
35
30
 
@@ -60,7 +55,7 @@ module NoBrainer::Document::Persistance
60
55
  end
61
56
 
62
57
  def _create(options={})
63
- attrs = self.class.persistable_attributes(@_attributes, :instance => self)
58
+ attrs = self.class.persistable_attributes(@_attributes)
64
59
  result = NoBrainer.run(self.class.rql_table.insert(attrs))
65
60
  self.pk_value ||= result['generated_keys'].to_a.first
66
61
  @new_record = false
@@ -69,7 +64,7 @@ module NoBrainer::Document::Persistance
69
64
  end
70
65
 
71
66
  def _update(attrs)
72
- rql = ->(doc){ self.class.persistable_attributes(attrs, :instance => self, :rql_doc => doc) }
67
+ rql = ->(doc){ self.class.persistable_attributes(attrs, :rql_doc => doc) }
73
68
  NoBrainer.run { selector.update(&rql) }
74
69
  end
75
70
 
@@ -0,0 +1,52 @@
1
+ module NoBrainer::Document::VirtualAttributes
2
+ extend NoBrainer::Autoload
3
+ extend ActiveSupport::Concern
4
+
5
+ VALID_VIRTUAL_FIELD_OPTIONS = [:type, :lazy_fetch, :virtual]
6
+
7
+ included do
8
+ cattr_accessor :virtual_fields, :instance_accessor => false
9
+ end
10
+
11
+ module ClassMethods
12
+ def virtual_field(attr, rql=nil, options={}, &block)
13
+ rql, options = nil, rql if rql.is_a?(Hash)
14
+ rql ||= block
15
+ rql_proc = rql.is_a?(Proc) ? rql : proc { rql }
16
+ field(attr, options.merge(:virtual => rql_proc))
17
+ end
18
+
19
+ def field(attr, options={})
20
+ return super unless options.key?(:virtual)
21
+
22
+ raise "virtual attributes are limited to the root class `#{self.root_class}' for the moment.\n" +
23
+ "Ask on GitHub for polymorphic support." unless is_root_class?
24
+
25
+ raise "You cannot index a virtual attribute. Use an index with a lambda expression instead" if options[:index]
26
+ options.assert_valid_keys(*VALID_VIRTUAL_FIELD_OPTIONS)
27
+
28
+ self.virtual_fields ||= Set.new
29
+ virtual_fields << attr
30
+
31
+ inject_in_layer :virtual_attributes do
32
+ define_method("#{attr}=") do |value|
33
+ raise NoBrainer::Error::ReadonlyField.new("#{attr} is a virtual attribute and thus readonly.")
34
+ end
35
+ end
36
+
37
+ super
38
+ end
39
+
40
+ def remove_field(attr, options={})
41
+ super
42
+
43
+ if fields[:virtual]
44
+ virtual_fields.try(:delete, attr)
45
+
46
+ inject_in_layer :virtual_attributes do
47
+ remove_method("#{attr}=")
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -11,6 +11,7 @@ module NoBrainer::Error
11
11
  class UnknownAttribute < RuntimeError; end
12
12
  class AtomicBlock < RuntimeError; end
13
13
  class LostLock < RuntimeError; end
14
+ class LockInvalidOp < RuntimeError; end
14
15
  class LockUnavailable < RuntimeError; end
15
16
  class InvalidPolymorphicType < RuntimeError; end
16
17
 
@@ -7,27 +7,33 @@ class NoBrainer::Lock
7
7
 
8
8
  # Since PKs are limited to 127 characters, we can't use the user's key as a PK
9
9
  # as it could be arbitrarily long.
10
- field :key_hash, :type => String, :primary_key => true, :default => ->{ Digest::SHA1.base64digest(key) }
11
- field :key, :type => String
12
- field :token, :type => String
13
- field :expires_at, :type => Time
14
-
15
- # We always use a new token, even when reading from the DB, because that's
16
- # what represent our instance.
17
- after_initialize { self.token = NoBrainer::Document::PrimaryKey::Generator.generate }
10
+ field :key_hash, :type => String, :primary_key => true, :default => ->{ Digest::SHA1.base64digest(key.to_s) }
11
+ field :key, :type => String
12
+ field :instance_token, :type => String, :default => ->{ get_new_instance_token }
13
+ field :expires_at, :type => Time
18
14
 
19
15
  scope :expired, where(:expires_at.lt(RethinkDB::RQL.new.now))
20
16
 
21
- def initialize(key, options={})
22
- return super if options[:from_db]
17
+ def self.find(key)
18
+ super(Digest::SHA1.base64digest(key.to_s))
19
+ end
23
20
 
24
- key = case key
25
- when Symbol then key.to_s
26
- when String then key
27
- else raise ArgumentError
21
+ def initialize(key, options={})
22
+ if options[:from_db]
23
+ super
24
+ # We reset our instance_token to allow recoveries.
25
+ self.instance_token = get_new_instance_token
26
+ else
27
+ @default_options = options.slice(:expire, :timeout)
28
+ options.delete(:expire); options.delete(:timeout);
29
+
30
+ super(options.merge(:key => key))
31
+ raise ArgumentError unless valid?
28
32
  end
33
+ end
29
34
 
30
- super(options.merge(:key => key))
35
+ def get_new_instance_token
36
+ NoBrainer::Document::PrimaryKey::Generator.generate
31
37
  end
32
38
 
33
39
  def synchronize(options={}, &block)
@@ -41,22 +47,21 @@ class NoBrainer::Lock
41
47
 
42
48
  def lock(options={})
43
49
  options.assert_valid_keys(:expire, :timeout)
44
- timeout = NoBrainer::Config.lock_options.merge(options)[:timeout]
50
+ timeout = get_option_value(options, :timeout)
45
51
  sleep_amount = 0.1
46
52
 
47
53
  start_at = Time.now
48
- while Time.now - start_at < timeout
49
- return if try_lock(options.select { |k,_| k == :expire })
54
+ loop do
55
+ return if try_lock(options.slice(:expire))
56
+ raise_lock_unavailable! if Time.now - start_at + sleep_amount > timeout
50
57
  sleep(sleep_amount)
51
58
  sleep_amount = [1, sleep_amount * 2].min
52
59
  end
53
-
54
- raise NoBrainer::Error::LockUnavailable.new("Lock on `#{key}' unavailable")
55
60
  end
56
61
 
57
62
  def try_lock(options={})
58
63
  options.assert_valid_keys(:expire)
59
- raise "Lock instance `#{key}' already locked" if @locked
64
+ raise_if_locked!
60
65
 
61
66
  set_expiration(options)
62
67
 
@@ -71,28 +76,28 @@ class NoBrainer::Lock
71
76
  end
72
77
 
73
78
  def unlock
74
- raise "Lock instance `#{key}' not locked" unless @locked
79
+ raise_unless_locked!
75
80
 
76
81
  result = NoBrainer.run do |r|
77
82
  selector.replace do |doc|
78
- r.branch(doc[:token].eq(self.token),
83
+ r.branch(doc[:instance_token].default(nil).eq(self.instance_token),
79
84
  nil, doc)
80
85
  end
81
86
  end
82
87
 
83
88
  @locked = false
84
- raise NoBrainer::Error::LostLock.new("Lost lock on `#{key}'") unless result['deleted'] == 1
89
+ raise_lost_lock! unless result['deleted'] == 1
85
90
  end
86
91
 
87
92
  def refresh(options={})
88
93
  options.assert_valid_keys(:expire)
89
- raise "Lock instance `#{key}' not locked" unless @locked
94
+ raise_unless_locked!
90
95
 
91
- set_expiration(options)
96
+ set_expiration(options.merge(:use_previous_expire => true))
92
97
 
93
98
  result = NoBrainer.run do |r|
94
99
  selector.update do |doc|
95
- r.branch(doc[:token].eq(self.token),
100
+ r.branch(doc[:instance_token].eq(self.instance_token),
96
101
  { :expires_at => self.expires_at }, nil)
97
102
  end
98
103
  end
@@ -102,7 +107,7 @@ class NoBrainer::Lock
102
107
  # unlikely to happen and should not harmful.
103
108
  unless result['replaced'] == 1
104
109
  @locked = false
105
- raise NoBrainer::Error::LostLock.new("Lost lock on `#{key}'")
110
+ raise_lost_lock!
106
111
  end
107
112
  end
108
113
 
@@ -112,7 +117,29 @@ class NoBrainer::Lock
112
117
  private
113
118
 
114
119
  def set_expiration(options)
115
- expire = NoBrainer::Config.lock_options.merge(options)[:expire]
120
+ expire = @previous_expire if options[:use_previous_expire] && !options[:expire]
121
+ expire ||= get_option_value(options, :expire)
122
+ @previous_expire = expire
116
123
  self.expires_at = RethinkDB::RQL.new.now + expire
117
124
  end
125
+
126
+ def get_option_value(options, key)
127
+ NoBrainer::Config.lock_options.merge(@default_options || {}).merge(options)[key]
128
+ end
129
+
130
+ def raise_if_locked!
131
+ raise NoBrainer::Error::LockInvalidOp.new("Lock instance `#{key}' already locked") if @locked
132
+ end
133
+
134
+ def raise_unless_locked!
135
+ raise NoBrainer::Error::LockInvalidOp.new("Lock instance `#{key}' not locked") unless @locked
136
+ end
137
+
138
+ def raise_lost_lock!
139
+ raise NoBrainer::Error::LostLock.new("Lost lock on `#{key}'")
140
+ end
141
+
142
+ def raise_lock_unavailable!
143
+ raise NoBrainer::Error::LockUnavailable.new("Lock on `#{key}' unavailable")
144
+ end
118
145
  end
@@ -47,6 +47,8 @@ module NoBrainer::Profiler::ControllerRuntime
47
47
  end
48
48
 
49
49
  def cleanup_view_runtime
50
+ return super unless Profiler.current
51
+
50
52
  time_spent_in_db_before_views = Profiler.current.total_duration
51
53
  runtime = super
52
54
  time_spent_in_db_after_views = Profiler.current.total_duration
@@ -0,0 +1,36 @@
1
+ class NoBrainer::ReentrantLock < NoBrainer::Lock
2
+ field :lock_count, :type => Integer
3
+
4
+ def try_lock(options={})
5
+ options.assert_valid_keys(:expire)
6
+ set_expiration(options)
7
+
8
+ result = NoBrainer.run do |r|
9
+ selector.replace do |doc|
10
+ r.branch(doc[:instance_token].default(nil).eq(self.instance_token),
11
+ doc.merge(:expires_at => self.expires_at,
12
+ :lock_count => doc[:lock_count] + 1),
13
+ r.branch(doc.eq(nil).or(doc[:expires_at] < r.now),
14
+ self.attributes.merge(:lock_count => 1), doc))
15
+ end
16
+ end
17
+
18
+ @locked = true # to make refresh() and synchronize() happy, somewhat hacky
19
+ return (result['inserted'] + result['replaced']) == 1
20
+ end
21
+
22
+ def unlock
23
+ set_expiration(:use_previous_expire => true)
24
+
25
+ result = NoBrainer.run do |r|
26
+ selector.replace do |doc|
27
+ r.branch(doc[:instance_token].default(nil).eq(self.instance_token),
28
+ r.branch(doc[:lock_count] > 1,
29
+ doc.merge(:expires_at => self.expires_at,
30
+ :lock_count => doc[:lock_count] - 1), nil), doc)
31
+ end
32
+ end
33
+
34
+ raise_lost_lock! unless (result['deleted'] + result['replaced']) == 1
35
+ end
36
+ end
@@ -16,7 +16,7 @@ module NoBrainer
16
16
  # Code that is loaded through the DSL of NoBrainer should not be eager loaded.
17
17
  autoload :Document, :IndexManager, :Loader, :Fork, :Geo, :SymbolDecoration
18
18
  eager_autoload :Config, :Connection, :ConnectionManager, :Error,
19
- :QueryRunner, :Criteria, :RQL, :Lock, :Profiler, :System
19
+ :QueryRunner, :Criteria, :RQL, :Lock, :ReentrantLock, :Profiler, :System
20
20
 
21
21
  class << self
22
22
  delegate :connection, :disconnect, :to => 'NoBrainer::ConnectionManager'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nobrainer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.29.0
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Viennot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-19 00:00:00.000000000 Z
11
+ date: 2015-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -116,6 +116,7 @@ files:
116
116
  - lib/no_brainer/criteria/run.rb
117
117
  - lib/no_brainer/criteria/scope.rb
118
118
  - lib/no_brainer/criteria/update.rb
119
+ - lib/no_brainer/criteria/virtual_attributes.rb
119
120
  - lib/no_brainer/criteria/where.rb
120
121
  - lib/no_brainer/document.rb
121
122
  - lib/no_brainer/document/aliases.rb
@@ -167,6 +168,7 @@ files:
167
168
  - lib/no_brainer/document/validation/core.rb
168
169
  - lib/no_brainer/document/validation/not_null.rb
169
170
  - lib/no_brainer/document/validation/uniqueness.rb
171
+ - lib/no_brainer/document/virtual_attributes.rb
170
172
  - lib/no_brainer/error.rb
171
173
  - lib/no_brainer/fork.rb
172
174
  - lib/no_brainer/geo.rb
@@ -194,6 +196,7 @@ files:
194
196
  - lib/no_brainer/query_runner/write_error.rb
195
197
  - lib/no_brainer/railtie.rb
196
198
  - lib/no_brainer/railtie/database.rake
199
+ - lib/no_brainer/reentrant_lock.rb
197
200
  - lib/no_brainer/rql.rb
198
201
  - lib/no_brainer/symbol_decoration.rb
199
202
  - lib/no_brainer/system.rb