nobrainer 0.9.0 → 0.11.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/autoload.rb +18 -7
  3. data/lib/no_brainer/config.rb +2 -0
  4. data/lib/no_brainer/criteria/chainable/where.rb +42 -15
  5. data/lib/no_brainer/criteria/termination/cache.rb +1 -1
  6. data/lib/no_brainer/criteria/termination/preload.rb +55 -0
  7. data/lib/no_brainer/criteria.rb +1 -1
  8. data/lib/no_brainer/document/association/belongs_to.rb +4 -2
  9. data/lib/no_brainer/document/association/eager_loader.rb +10 -8
  10. data/lib/no_brainer/document/association.rb +2 -1
  11. data/lib/no_brainer/document/attributes.rb +21 -30
  12. data/lib/no_brainer/document/callbacks.rb +32 -0
  13. data/lib/no_brainer/document/core.rb +0 -2
  14. data/lib/no_brainer/document/criteria.rb +1 -1
  15. data/lib/no_brainer/document/dirty.rb +51 -36
  16. data/lib/no_brainer/document/dynamic_attributes.rb +11 -3
  17. data/lib/no_brainer/document/index.rb +0 -1
  18. data/lib/no_brainer/document/injection_layer.rb +1 -1
  19. data/lib/no_brainer/document/persistance.rb +24 -37
  20. data/lib/no_brainer/document/timestamps.rb +7 -4
  21. data/lib/no_brainer/document/types.rb +137 -0
  22. data/lib/no_brainer/document/validation.rb +5 -1
  23. data/lib/no_brainer/document.rb +3 -3
  24. data/lib/no_brainer/locale/en.yml +1 -0
  25. data/lib/no_brainer/railtie.rb +5 -5
  26. data/lib/nobrainer.rb +12 -8
  27. data/lib/rails/generators/nobrainer/model/model_generator.rb +17 -0
  28. data/lib/rails/generators/nobrainer/model/templates/model.rb.tt +13 -0
  29. data/lib/rails/generators/nobrainer.rb +18 -0
  30. metadata +27 -23
  31. data/lib/no_brainer/criteria/termination/eager_loading.rb +0 -50
  32. data/lib/no_brainer/version.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea32475119089388850bb55326aab70d4a3dbc16
4
- data.tar.gz: dfbef7c1a239b75cb3b45d32b593026ef4d8df93
3
+ metadata.gz: 56f5bfdef46fb94e140efe80af84031e02238d07
4
+ data.tar.gz: aa5965b806a43a8727be98ef2a7a9c370fe94cfc
5
5
  SHA512:
6
- metadata.gz: d83f20ced889fba1ba1048e6ad41f4c7a6a1ae02562c8bf230580a66e64306d9a064eb179a500b61b230f026bbc93a8bf8876f4b4edbf2e21c1a2140decb743d
7
- data.tar.gz: a9a671db853f43d369550c7b1031a3a9ad17b9d5307aee6aea659e3808364c6d3d77b0ecfe9d2354a08efb99df7f44f085566d36d2f787a3e1d7469ec915ba52
6
+ metadata.gz: 0285cc6834d21a4e973fe293adf54ada0bcce15424a605719a32db2445a909793ebab766549a9d8b31b42ced44e6d3ef5f1cfecd980b33b4c4c1792af94a74c4
7
+ data.tar.gz: 3ed81e4508bc4528142b7049db0e1464927f7dfcad9c459853593c70a9ebbed95c47b3bd634d03f0bc2e9ebcc358a2d72cbcd7b7c73b4c46ca37a518a23c7c56
@@ -1,14 +1,25 @@
1
1
  module NoBrainer::Autoload
2
2
  include ActiveSupport::Autoload
3
3
 
4
- def autoload(*args)
5
- args.each { |mod| super mod }
4
+ def self.extended(base)
5
+ ActiveSupport::Autoload.extended(base)
6
6
  end
7
7
 
8
- def autoload_and_include(*mods)
9
- mods.each do |mod|
10
- autoload mod
11
- include const_get mod
12
- end
8
+ def autoload(*constants)
9
+ constants.each { |constant| super(constant) }
10
+ end
11
+
12
+ def eager_autoload(*constants)
13
+ super() { autoload(*constants) }
14
+ end
15
+
16
+ def autoload_and_include(*constants)
17
+ constants.each { |constant| autoload constant }
18
+ constants.each { |constant| include const_get constant }
19
+ end
20
+
21
+ def eager_autoload_and_include(*constants)
22
+ eager_autoload(*constants)
23
+ constants.each { |constant| include const_get constant }
13
24
  end
14
25
  end
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  module NoBrainer::Config
2
4
  class << self
3
5
  mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
@@ -56,14 +56,21 @@ module NoBrainer::Criteria::Chainable::Where
56
56
 
57
57
  class BinaryOperator < Struct.new(:key, :op, :value)
58
58
  def simplify
59
- # TODO Simplify the in uniq
60
- self
59
+ case op
60
+ when :in then
61
+ case value
62
+ when Range then BinaryOperator.new(key, :between, value)
63
+ when Array then BinaryOperator.new(key, :in, value.uniq)
64
+ else raise ArgumentError.new ":in takes an array/range, not #{value}"
65
+ end
66
+ else self
67
+ end
61
68
  end
62
69
 
63
70
  def to_rql(doc)
64
71
  case op
65
72
  when :between then (doc[key] >= value.min) & (doc[key] <= value.max)
66
- when :in then value.map { |v| doc[key].eq(v) }.reduce { |a,b| a | b }
73
+ when :in then RethinkDB::RQL.new.expr(value).contains(doc[key])
67
74
  else doc[key].__send__(op, value)
68
75
  end
69
76
  end
@@ -130,7 +137,7 @@ module NoBrainer::Criteria::Chainable::Where
130
137
  self.with_index_name == false
131
138
  end
132
139
 
133
- class IndexFinder < Struct.new(:criteria, :index_name, :indexed_values, :ast)
140
+ class IndexFinder < Struct.new(:criteria, :index_name, :rql_proc, :ast)
134
141
  def initialize(*args)
135
142
  super
136
143
  find_index
@@ -143,9 +150,7 @@ module NoBrainer::Criteria::Chainable::Where
143
150
  private
144
151
 
145
152
  def get_candidate_clauses(*types)
146
- Hash[criteria.where_ast.clauses
147
- .select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
148
- .map { |c| [c.key, c] }]
153
+ criteria.where_ast.clauses.select { |c| c.is_a?(BinaryOperator) && types.include?(c.op) }
149
154
  end
150
155
 
151
156
  def get_usable_indexes(*types)
@@ -156,19 +161,24 @@ module NoBrainer::Criteria::Chainable::Where
156
161
  end
157
162
 
158
163
  def find_index_canonical
159
- clauses = get_candidate_clauses(:eq, :in)
164
+ clauses = Hash[get_candidate_clauses(:eq, :in, :between).map { |c| [c.key, c] }]
160
165
  return unless clauses.present?
161
166
 
162
167
  if index_name = (get_usable_indexes.keys & clauses.keys).first
163
168
  clause = clauses[index_name]
164
169
  self.index_name = index_name
165
- self.indexed_values = clause.op == :in ? clause.value : [clause.value]
166
170
  self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - [clause])
171
+ self.rql_proc = case clause.op
172
+ when :eq then ->(rql){ rql.get_all(clause.value, :index => index_name) }
173
+ when :in then ->(rql){ rql.get_all(*clause.value, :index => index_name) }
174
+ when :between then ->(rql){ rql.between(clause.value.min, clause.value.max, :index => index_name,
175
+ :left_bound => :closed, :right_bound => :closed) }
176
+ end
167
177
  end
168
178
  end
169
179
 
170
180
  def find_index_compound
171
- clauses = get_candidate_clauses(:eq)
181
+ clauses = Hash[get_candidate_clauses(:eq).map { |c| [c.key, c] }]
172
182
  return unless clauses.present?
173
183
 
174
184
  index_name, index_values = get_usable_indexes(:compound)
@@ -179,14 +189,33 @@ module NoBrainer::Criteria::Chainable::Where
179
189
  if index_name
180
190
  indexed_clauses = index_values.map { |field| clauses[field] }
181
191
  self.index_name = index_name
182
- self.indexed_values = [indexed_clauses.map { |c| c.value }]
183
192
  self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - indexed_clauses)
193
+ self.rql_proc = ->(rql){ rql.get_all(indexed_clauses.map { |c| c.value }, :index => index_name) }
194
+ end
195
+ end
196
+
197
+ def find_index_hidden_between
198
+ clauses = get_candidate_clauses(:gt, :ge, :lt, :le).group_by(&:key)
199
+ return unless clauses.present?
200
+
201
+ if index_name = (get_usable_indexes.keys & clauses.keys).first
202
+ op_clauses = Hash[clauses[index_name].map { |c| [c.op, c] }]
203
+ left_bound = op_clauses[:gt] || op_clauses[:ge]
204
+ right_bound = op_clauses[:lt] || op_clauses[:le]
205
+
206
+ self.index_name = index_name
207
+ self.ast = MultiOperator.new(:and, criteria.where_ast.clauses - [left_bound, right_bound].compact)
208
+
209
+ options = {:index => index_name}
210
+ options[:left_bound] = {:gt => :open, :ge => :closed}[left_bound.op] if left_bound
211
+ options[:right_bound] = {:lt => :open, :le => :closed}[right_bound.op] if right_bound
212
+ self.rql_proc = ->(rql){ rql.between(left_bound.try(:value), right_bound.try(:value), options) }
184
213
  end
185
214
  end
186
215
 
187
216
  def find_index
188
217
  return if criteria.__send__(:without_index?)
189
- find_index_canonical || find_index_compound
218
+ find_index_canonical || find_index_compound || find_index_hidden_between
190
219
  if criteria.with_index_name && !could_find_index?
191
220
  raise NoBrainer::Error::CannotUseIndex.new("Cannot use index #{criteria.with_index_name}")
192
221
  end
@@ -200,9 +229,7 @@ module NoBrainer::Criteria::Chainable::Where
200
229
 
201
230
  def compile_rql_pass1
202
231
  rql = super
203
- if index_finder.could_find_index?
204
- rql = rql.get_all(*index_finder.indexed_values, :index => index_finder.index_name)
205
- end
232
+ rql = index_finder.rql_proc.call(rql) if index_finder.could_find_index?
206
233
  rql
207
234
  end
208
235
 
@@ -45,7 +45,7 @@ module NoBrainer::Criteria::Termination::Cache
45
45
  block.call(instance)
46
46
  cache << instance
47
47
  end
48
- @cache = cache.freeze
48
+ @cache = cache
49
49
  self
50
50
  end
51
51
 
@@ -0,0 +1,55 @@
1
+ module NoBrainer::Criteria::Termination::Preload
2
+ extend ActiveSupport::Concern
3
+
4
+ included { attr_accessor :_preloads }
5
+
6
+ def initialize(options={})
7
+ super
8
+ self._preloads = []
9
+ end
10
+
11
+ def preload(*values)
12
+ chain(:keep_cache => true) { |criteria| criteria._preloads = values }
13
+ end
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
+ def merge!(criteria, options={})
21
+ super
22
+ self._preloads = self._preloads + criteria._preloads
23
+
24
+ # XXX Not pretty hack
25
+ if criteria._preloads.present? && cached?
26
+ perform_preloads(@cache)
27
+ end
28
+ end
29
+
30
+ def each(options={}, &block)
31
+ return super unless should_preloads? && !options[:no_preloadsing] && block
32
+
33
+ docs = []
34
+ super(options.merge(:no_preloadsing => true)) { |doc| docs << doc }
35
+ perform_preloads(docs)
36
+ docs.each(&block)
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ def should_preloads?
43
+ self._preloads.present? && !raw?
44
+ end
45
+
46
+ def get_one(criteria)
47
+ super.tap { |doc| perform_preloads([doc]) }
48
+ end
49
+
50
+ def perform_preloads(docs)
51
+ if should_preloads? && docs.present?
52
+ NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, self._preloads)
53
+ end
54
+ end
55
+ end
@@ -11,7 +11,7 @@ class NoBrainer::Criteria
11
11
  module Termination
12
12
  extend NoBrainer::Autoload
13
13
  extend ActiveSupport::Concern
14
- autoload_and_include :Count, :Delete, :Enumerable, :First, :EagerLoading,
14
+ autoload_and_include :Count, :Delete, :Enumerable, :First, :Preload,
15
15
  :Inc, :Update, :Cache
16
16
  end
17
17
 
@@ -2,7 +2,7 @@ class NoBrainer::Document::Association::BelongsTo
2
2
  include NoBrainer::Document::Association::Core
3
3
 
4
4
  class Metadata
5
- VALID_OPTIONS = [:foreign_key, :class_name, :index]
5
+ VALID_OPTIONS = [:foreign_key, :class_name, :index, :validates]
6
6
  include NoBrainer::Document::Association::Core::Metadata
7
7
  extend NoBrainer::Document::Association::EagerLoader::Generic
8
8
 
@@ -19,7 +19,9 @@ class NoBrainer::Document::Association::BelongsTo
19
19
  def hook
20
20
  super
21
21
 
22
- owner_klass.field foreign_key, :index => options[:index]
22
+ owner_klass.field(foreign_key, :index => options[:index])
23
+ owner_klass.validates(target_name, options[:validates]) if options[:validates]
24
+
23
25
  delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
24
26
  add_callback_for(:after_validation)
25
27
  # TODO test if we are not overstepping on another foreign_key
@@ -33,24 +33,26 @@ class NoBrainer::Document::Association::EagerLoader
33
33
  def eager_load_association(docs, association_name, criteria=nil)
34
34
  docs = docs.compact
35
35
  return [] if docs.empty?
36
- association = docs.first.root_class.association_metadata[association_name.to_sym]
36
+ meta = docs.first.root_class.association_metadata
37
+ # TODO test the singularize thingy.
38
+ association = meta[association_name.to_sym] || meta[association_name.to_s.singularize.to_sym]
37
39
  raise "Unknown association #{association_name}" unless association
38
40
  association.eager_load(docs, criteria)
39
41
  end
40
42
 
41
- def eager_load(docs, includes)
42
- case includes
43
- when Hash then includes.each do |k,v|
43
+ def eager_load(docs, preloads)
44
+ case preloads
45
+ when Hash then preloads.each do |k,v|
44
46
  if v.is_a?(NoBrainer::Criteria)
45
47
  v = v.dup
46
- nested_includes, v._includes = v._includes, []
47
- eager_load(eager_load_association(docs, k, v), nested_includes)
48
+ nested_preloads, v._preloads = v._preloads, []
49
+ eager_load(eager_load_association(docs, k, v), nested_preloads)
48
50
  else
49
51
  eager_load(eager_load_association(docs, k), v)
50
52
  end
51
53
  end
52
- when Array then includes.each { |v| eager_load(docs, v) }
53
- else eager_load_association(docs, includes)
54
+ when Array then preloads.each { |v| eager_load(docs, v) }
55
+ else eager_load_association(docs, preloads)
54
56
  end
55
57
  true
56
58
  end
@@ -1,6 +1,7 @@
1
1
  module NoBrainer::Document::Association
2
2
  extend NoBrainer::Autoload
3
3
  autoload :Core, :BelongsTo, :HasMany, :HasManyThrough, :HasOne, :HasOneThrough, :EagerLoader
4
+ METHODS = [:belongs_to, :has_many, :has_one]
4
5
 
5
6
  extend ActiveSupport::Concern
6
7
 
@@ -19,7 +20,7 @@ module NoBrainer::Document::Association
19
20
  subclass.association_metadata = self.association_metadata.dup
20
21
  end
21
22
 
22
- [:belongs_to, :has_many, :has_one].each do |association|
23
+ METHODS.each do |association|
23
24
  define_method(association) do |target, options={}|
24
25
  target = target.to_sym
25
26
 
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Document::Attributes
2
- VALID_FIELD_OPTIONS = [:index, :default]
2
+ VALID_FIELD_OPTIONS = [:index, :default, :type, :validates]
3
3
  RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
4
4
  extend ActiveSupport::Concern
5
5
 
@@ -10,14 +10,13 @@ module NoBrainer::Document::Attributes
10
10
  self.fields = {}
11
11
  end
12
12
 
13
- def initialize(attrs={}, options={})
14
- super
15
- @attributes = {}
13
+ def _initialize(attrs={}, options={})
14
+ @_attributes = {}.with_indifferent_access
16
15
  assign_attributes(attrs, options.reverse_merge(:pristine => true))
17
16
  end
18
17
 
19
18
  def attributes
20
- @attributes.dup.freeze
19
+ @_attributes.dup.freeze
21
20
  end
22
21
 
23
22
  def read_attribute(name)
@@ -32,7 +31,7 @@ module NoBrainer::Document::Attributes
32
31
 
33
32
  def assign_defaults
34
33
  self.class.fields.each do |name, field_options|
35
- if field_options.has_key?(:default) && !@attributes.has_key?(name.to_s)
34
+ if field_options.has_key?(:default) && !@_attributes.has_key?(name)
36
35
  default_value = field_options[:default]
37
36
  default_value = default_value.call if default_value.is_a?(Proc)
38
37
  self.write_attribute(name, default_value)
@@ -40,39 +39,31 @@ module NoBrainer::Document::Attributes
40
39
  end
41
40
  end
42
41
 
43
- def assign_attributes(attrs, options={})
44
- # XXX We don't save field that are not explicitly set. The row will
45
- # therefore not contain nil for unset attributes.
46
- @attributes.clear if options[:pristine]
47
-
48
- if options[:from_db]
49
- # TODO Should we reject undeclared fields ?
50
- #
51
- # TODO Not using the getter/setters, the dirty tracking won't notice it,
52
- # also we should start thinking about custom serializer/deserializer.
53
- @attributes.merge! attrs
54
- else
55
- attrs.each { |k,v| self.write_attribute(k, v) }
56
- end
42
+ def _assign_attributes(attrs, options={})
43
+ attrs.each { |k,v| self.write_attribute(k,v) }
44
+ end
57
45
 
58
- assign_defaults if options[:pristine] || options[:from_db]
46
+ def assign_attributes(attrs, options={})
47
+ @_attributes.clear if options[:pristine]
48
+ _assign_attributes(attrs, options)
49
+ assign_defaults if options[:pristine]
59
50
  self
60
51
  end
61
52
  def attributes=(*args); assign_attributes(*args); end
62
53
 
63
- # TODO test that thing
64
- def inspect
65
- attrs = self.class.fields.keys.map { |f| "#{f}: #{@attributes[f.to_s].inspect}" }
66
- "#<#{self.class} #{attrs.join(', ')}>"
54
+ def inspectable_attributes
55
+ # TODO test that thing
56
+ Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
67
57
  end
68
58
 
69
- def to_s
70
- inspect
59
+ def inspect
60
+ "#<#{self.class} #{inspectable_attributes.map { |k,v| "#{k}: #{v.inspect}" }.join(', ')}>"
71
61
  end
72
62
 
73
63
  module ClassMethods
74
64
  def new_from_db(attrs, options={})
75
- klass_from_attrs(attrs).new(attrs, options.reverse_merge(:from_db => true)) if attrs
65
+ options = options.reverse_merge(:pristine => true, :from_db => true)
66
+ klass_from_attrs(attrs).new(attrs, options) if attrs
76
67
  end
77
68
 
78
69
  def inherited(subclass)
@@ -96,11 +87,11 @@ module NoBrainer::Document::Attributes
96
87
  # Using a layer so the user can use super when overriding these methods
97
88
  inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
98
89
  def #{name}=(value)
99
- @attributes['#{name}'] = value
90
+ @_attributes['#{name}'] = value
100
91
  end
101
92
 
102
93
  def #{name}
103
- @attributes['#{name}']
94
+ @_attributes['#{name}']
104
95
  end
105
96
  RUBY
106
97
  end
@@ -0,0 +1,32 @@
1
+ module NoBrainer::Document::Callbacks
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ extend ActiveModel::Callbacks
6
+ define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => 'false'
7
+ end
8
+
9
+ def initialize(*args)
10
+ run_callbacks(:initialize) { _initialize(*args); true }
11
+ end
12
+
13
+ def _create(*args)
14
+ run_callbacks(:create) { super }
15
+ end
16
+
17
+ def update(*args, &block)
18
+ run_callbacks(:update) { super }
19
+ end
20
+
21
+ def replace(*args, &block)
22
+ run_callbacks(:update) { super }
23
+ end
24
+
25
+ def save(*args)
26
+ run_callbacks(:save) { super }
27
+ end
28
+
29
+ def destroy(*args)
30
+ run_callbacks(:destroy) { super }
31
+ end
32
+ end
@@ -15,6 +15,4 @@ module NoBrainer::Document::Core
15
15
 
16
16
  NoBrainer::Document::Core.all << self
17
17
  end
18
-
19
- def initialize(attrs={}, options={}); end
20
18
  end
@@ -16,7 +16,7 @@ module NoBrainer::Document::Criteria
16
16
  :with_cache, :without_cache, # Cache
17
17
  :count, :empty?, :any?, # Count
18
18
  :delete_all, :destroy_all, # Delete
19
- :includes, # EagerLoading
19
+ :includes, :preload, # Preload
20
20
  :each, :to_a, # Enumerable
21
21
  :first, :last, :first!, :last!, # First
22
22
  :inc_all, :dec_all, # Inc
@@ -5,73 +5,87 @@ module NoBrainer::Document::Dirty
5
5
  # ActiveModel::AttributeMethods which gives pretty violent method_missing()
6
6
  # capabilities, such as giving a getter/setter method for any keys within the
7
7
  # attributes keys. We don't want that.
8
+ # Also it doesn't work properly with array and hashes
8
9
 
9
- included do
10
- attr_accessor :previous_changes
11
- after_save { clear_dirtiness }
10
+ included { after_save { clear_dirtiness } }
11
+
12
+ def old_attributes_values
13
+ @old_attributes_values ||= {}.with_indifferent_access
12
14
  end
13
15
 
14
- def changed_attributes
15
- @changed_attributes ||= {}
16
+ def clear_dirtiness
17
+ @old_attributes_values.try(:clear)
16
18
  end
17
19
 
18
- def assign_attributes(attrs, options={})
19
- clear_dirtiness if options[:pristine] || options[:from_db]
20
- super
20
+ def attributes
21
+ # If we leak all the attributes (rare), we perform a copy to ensure that we
22
+ # don't have issues with modified hashes/array. Devs are crazy.
23
+ super.tap { |attrs| @old_attributes_values = attrs.deep_dup }
21
24
  end
22
25
 
23
- def clear_dirtiness
24
- self.previous_changes = changes
25
- self.changed_attributes.clear
26
+ def _assign_attributes(attrs, options={})
27
+ super
28
+ clear_dirtiness if options[:from_db]
26
29
  end
27
30
 
28
31
  def changed?
29
- changed_attributes.present?
32
+ changes.present?
30
33
  end
31
34
 
32
35
  def changed
33
- changed_attributes.keys
36
+ changes.keys
34
37
  end
35
38
 
36
39
  def changes
37
- Hash[changed_attributes.map { |k,v| [k, [v, read_attribute(k)]] }]
40
+ result = {}.with_indifferent_access
41
+ old_attributes_values.each do |attr, old_value|
42
+ current_value = read_attribute(attr)
43
+ result[attr] = [old_value, current_value] if current_value != old_value
44
+ end
45
+ result
38
46
  end
39
47
 
40
- def attribute_will_change!(attr, new_value)
41
- return if changed_attributes.include?(attr)
42
-
43
- # ActiveModel ignores TypeError and NoMethodError exception as if nothng
44
- # happened. Why is that?
45
- value = read_attribute(attr)
46
- value = value.clone if value.duplicable?
47
-
48
- return if value == new_value
49
-
50
- changed_attributes[attr] = value
48
+ def attribute_may_change(attr, current_value)
49
+ unless old_attributes_values.has_key?(attr)
50
+ old_attributes_values[attr] = current_value.deep_dup
51
+ end
51
52
  end
52
53
 
53
54
  module ClassMethods
54
55
  def field(name, options={})
55
56
  super
56
57
 
57
- inject_in_layer :dirty_tracking, <<-RUBY, __FILE__, __LINE__ + 1
58
- def #{name}_changed?
59
- changed_attributes.include?(:#{name})
58
+ inject_in_layer :dirty_tracking do
59
+ define_method("#{name}_change") do
60
+ if old_attributes_values.has_key?(name)
61
+ result = [old_attributes_values[name], read_attribute(name)]
62
+ result = nil if result.first == result.last
63
+ result
64
+ end
60
65
  end
61
66
 
62
- def #{name}_change
63
- [changed_attributes[:#{name}], #{name}] if #{name}_changed?
67
+ define_method("#{name}_changed?") do
68
+ !!__send__("#{name}_change")
64
69
  end
65
70
 
66
- def #{name}_was
67
- #{name}_changed? ? changed_attributes[:#{name}] : #{name}
71
+ define_method("#{name}_was") do
72
+ old_attributes_values.has_key?(name) ?
73
+ old_attributes_values[name] : read_attribute(name)
68
74
  end
69
75
 
70
- def #{name}=(value)
71
- attribute_will_change!(:#{name}, value)
72
- super
76
+ define_method("#{name}") do
77
+ super().tap do |value|
78
+ # This take care of string/arrays/hashes that could change without going
79
+ # through the setter.
80
+ attribute_may_change(name, value) if value.respond_to?(:size)
81
+ end
73
82
  end
74
- RUBY
83
+
84
+ define_method("#{name}=") do |value|
85
+ attribute_may_change(name, read_attribute(name))
86
+ super(value)
87
+ end
88
+ end
75
89
  end
76
90
 
77
91
  def remove_field(name)
@@ -81,6 +95,7 @@ module NoBrainer::Document::Dirty
81
95
  undef #{name}_changed?
82
96
  undef #{name}_change
83
97
  undef #{name}_was
98
+ undef #{name}
84
99
  undef #{name}=
85
100
  RUBY
86
101
  end
@@ -2,11 +2,19 @@ module NoBrainer::Document::DynamicAttributes
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def read_attribute(name)
5
- self.respond_to?("#{name}") ? super : @attributes[name.to_s]
5
+ if self.respond_to?("#{name}")
6
+ super
7
+ else
8
+ @_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
9
+ end
6
10
  end
7
11
 
8
12
  def write_attribute(name, value)
9
- attribute_will_change!(name, value)
10
- self.respond_to?("#{name}=") ? super : @attributes[name.to_s] = value
13
+ if self.respond_to?("#{name}=")
14
+ super
15
+ else
16
+ attribute_may_change(name, read_attribute(name))
17
+ @_attributes[name] = value
18
+ end
11
19
  end
12
20
  end
@@ -23,7 +23,6 @@ module NoBrainer::Document::Index
23
23
  else raise "Index argument must be a lambda or a list of fields"
24
24
  end
25
25
 
26
- # FIXME Primary key may not always be :id
27
26
  if name.in?(NoBrainer::Document::Attributes::RESERVED_FIELD_NAMES)
28
27
  raise "Cannot use a reserved field name: #{name}"
29
28
  end
@@ -3,7 +3,7 @@ module NoBrainer::Document::InjectionLayer
3
3
 
4
4
  module ClassMethods
5
5
  def inject_in_layer(name, code=nil, file=nil, line=nil, &block)
6
- mod = class_eval "module NoBrainer; module #{name.to_s.camelize}; self; end; end"
6
+ mod = class_eval "module NoBrainerLayer; module #{name.to_s.camelize}; self; end; end"
7
7
  mod.module_eval(code, file, line) if code
8
8
  mod.module_exec(&block) if block
9
9
  include mod
@@ -6,8 +6,7 @@ module NoBrainer::Document::Persistance
6
6
  define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
7
7
  end
8
8
 
9
- # TODO after_initialize, after_find callback
10
- def initialize(attrs={}, options={})
9
+ def _initialize(attrs={}, options={})
11
10
  super
12
11
  @new_record = !options[:from_db]
13
12
  end
@@ -25,56 +24,44 @@ module NoBrainer::Document::Persistance
25
24
  end
26
25
 
27
26
  def reload(options={})
28
- unless options[:keep_ivars]
29
- id = self.id
30
- instance_variables.each { |ivar| remove_instance_variable(ivar) }
31
- @attributes = {}
32
- self.id = id
33
- end
34
- assign_attributes(selector.raw.first!, :pristine => true, :from_db => true)
27
+ attrs = selector.raw.first!
28
+ instance_variables.each { |ivar| remove_instance_variable(ivar) } unless options[:keep_ivars]
29
+ initialize(attrs, :pristine => true, :from_db => true)
35
30
  self
36
31
  end
37
32
 
38
33
  def _create(options={})
39
- run_callbacks :create do
40
- if options[:validate] && !valid?
41
- false
42
- else
43
- keys = self.class.insert_all(attributes)
44
- self.id ||= keys.first
45
- @new_record = false
46
- true
47
- end
34
+ if options[:validate] && !valid?
35
+ false
36
+ else
37
+ keys = self.class.insert_all(attributes)
38
+ self.id ||= keys.first
39
+ @new_record = false
40
+ true
48
41
  end
49
42
  end
50
43
 
51
44
  def update(options={}, &block)
52
- run_callbacks :update do
53
- if options[:validate] && !valid?
54
- false
55
- else
56
- selector.update_all(&block)
57
- true
58
- end
45
+ if options[:validate] && !valid?
46
+ false
47
+ else
48
+ selector.update_all(&block)
49
+ true
59
50
  end
60
51
  end
61
52
 
62
53
  def replace(options={}, &block)
63
- run_callbacks :update do
64
- if options[:validate] && !valid?
65
- false
66
- else
67
- selector.replace_all(&block)
68
- true
69
- end
54
+ if options[:validate] && !valid?
55
+ false
56
+ else
57
+ selector.replace_all(&block)
58
+ true
70
59
  end
71
60
  end
72
61
 
73
62
  def save(options={})
74
63
  options = options.reverse_merge(:validate => true)
75
- run_callbacks :save do
76
- new_record? ? _create(options) : replace(options) { attributes }
77
- end
64
+ new_record? ? _create(options) : replace(options) { attributes }
78
65
  end
79
66
 
80
67
  def save!(*args)
@@ -95,12 +82,12 @@ module NoBrainer::Document::Persistance
95
82
  selector.delete_all
96
83
  @destroyed = true
97
84
  end
98
- # TODO freeze attributes
85
+ @_attributes.freeze
99
86
  true
100
87
  end
101
88
 
102
89
  def destroy
103
- run_callbacks(:destroy) { delete }
90
+ delete
104
91
  end
105
92
 
106
93
  module ClassMethods
@@ -2,15 +2,18 @@ module NoBrainer::Document::Timestamps
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- self.field :created_at
6
- self.field :updated_at
5
+ class_attribute :timestamps_disabled
7
6
 
8
- before_create { self.created_at = Time.now if self.respond_to?(:created_at=) }
9
- before_save { self.updated_at = Time.now if self.respond_to?(:updated_at=) }
7
+ self.field :created_at, :type => Time
8
+ self.field :updated_at, :type => Time
9
+
10
+ before_create { self.created_at = Time.now unless self.timestamps_disabled }
11
+ before_save { self.updated_at = Time.now unless self.timestamps_disabled }
10
12
  end
11
13
 
12
14
  module ClassMethods
13
15
  def disable_timestamps
16
+ self.timestamps_disabled = true
14
17
  self.remove_field :created_at
15
18
  self.remove_field :updated_at
16
19
  end
@@ -0,0 +1,137 @@
1
+ module NoBrainer::Document::Types
2
+ extend ActiveSupport::Concern
3
+
4
+ module CastingRules
5
+ extend self
6
+
7
+ def String(value)
8
+ case value
9
+ when Symbol then value.to_s
10
+ else raise InvalidType
11
+ end
12
+ end
13
+
14
+ def Integer(value)
15
+ case value
16
+ when String
17
+ value = value.strip.gsub(/^\+/, '')
18
+ value.to_i.tap { |new_value| new_value.to_s == value or raise InvalidType }
19
+ when Float
20
+ value.to_i.tap { |new_value| new_value.to_f == value or raise InvalidType }
21
+ else raise InvalidType
22
+ end
23
+ end
24
+
25
+ def Float(value)
26
+ case value
27
+ when Integer then value.to_f
28
+ when String
29
+ value = value.strip.gsub(/^\+/, '')
30
+ value = value.gsub(/0+$/, '') if value['.']
31
+ value = value.gsub(/\.$/, '')
32
+ value = "#{value}.0" unless value['.']
33
+ value.to_f.tap { |new_value| new_value.to_s == value or raise InvalidType }
34
+ else raise InvalidType
35
+ end
36
+ end
37
+
38
+ def Boolean(value)
39
+ case value
40
+ when TrueClass then true
41
+ when FalseClass then false
42
+ when String, Integer
43
+ value = value.to_s.strip.downcase
44
+ return true if value.in? %w(true yes t 1)
45
+ return false if value.in? %w(false no f 0)
46
+ raise InvalidType
47
+ else raise InvalidType
48
+ end
49
+ end
50
+
51
+ def Symbol(value)
52
+ case value
53
+ when String
54
+ value = value.strip
55
+ raise InvalidType if value.empty?
56
+ value.to_sym
57
+ else raise InvalidType
58
+ end
59
+ end
60
+
61
+ def lookup(type)
62
+ CastingRules.method(type.to_s)
63
+ rescue NameError
64
+ proc { raise InvalidType }
65
+ end
66
+
67
+ def cast(value, type, cast_method)
68
+ return value if value.nil? || type.nil? || value.is_a?(type)
69
+ cast_method.call(value)
70
+ end
71
+ end
72
+
73
+ included do
74
+ # We namespace our fake Boolean class to avoid polluting the global namespace
75
+ class_exec do
76
+ class Boolean
77
+ def initialize; raise; end
78
+ def self.inspect; 'Boolean'; end
79
+ def self.to_s; inspect; end
80
+ def self.name; inspect; end
81
+ end
82
+ end
83
+ before_validation :add_type_errors
84
+ end
85
+
86
+ class InvalidType < RuntimeError
87
+ attr_accessor :type
88
+ def initialize(type=nil)
89
+ @type = type
90
+ end
91
+
92
+ def validation_error_args
93
+ [:invalid_type, :type => type.to_s.underscore.humanize.downcase]
94
+ end
95
+ end
96
+
97
+ def add_type_errors
98
+ return unless @pending_type_errors
99
+ @pending_type_errors.each do |name, error|
100
+ errors.add(name, *error.validation_error_args)
101
+ end
102
+ end
103
+
104
+ module ClassMethods
105
+ def field(name, options={})
106
+ super
107
+ return unless options.has_key?(:type)
108
+ name = name.to_sym
109
+ type = options[:type]
110
+ cast_method = NoBrainer::Document::Types::CastingRules.lookup(type)
111
+
112
+ inject_in_layer :types do
113
+ define_method("#{name}=") do |value|
114
+ begin
115
+ value = NoBrainer::Document::Types::CastingRules.cast(value, type, cast_method)
116
+ @pending_type_errors.try(:delete, name)
117
+ rescue NoBrainer::Document::Types::InvalidType => error
118
+ error.type ||= type
119
+ @pending_type_errors ||= {}
120
+ @pending_type_errors[name] = error
121
+ end
122
+ super(value)
123
+ end
124
+
125
+ define_method("#{name}?") { !!read_attribute(name) } if type == Boolean
126
+ end
127
+ end
128
+
129
+ def remove_field(name)
130
+ super
131
+ # TODO remove the name? if we added it. Low priority though.
132
+ inject_in_layer :types, <<-RUBY, __FILE__, __LINE__ + 1
133
+ undef #{name}=
134
+ RUBY
135
+ end
136
+ end
137
+ end
@@ -3,12 +3,16 @@ module NoBrainer::Document::Validation
3
3
  include ActiveModel::Validations
4
4
  include ActiveModel::Validations::Callbacks
5
5
 
6
- # TODO Test that thing
7
6
  def valid?(context=nil)
8
7
  super(context || (new_record? ? :create : :update))
9
8
  end
10
9
 
11
10
  module ClassMethods
11
+ def field(name, options={})
12
+ super
13
+ validates(name.to_sym, options[:validates]) if options[:validates]
14
+ end
15
+
12
16
  def validates_uniqueness_of(*attr_names)
13
17
  validates_with UniquenessValidator, _merge_attributes(attr_names)
14
18
  end
@@ -4,9 +4,9 @@ module NoBrainer::Document
4
4
  extend ActiveSupport::Concern
5
5
  extend NoBrainer::Autoload
6
6
 
7
- autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Persistance, :Dirty,
8
- :Id, :Association, :Serialization, :Criteria, :Validation,
9
- :Polymorphic, :Index, :Timestamps
7
+ autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
8
+ :Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
9
+ :Criteria, :Polymorphic, :Index, :Timestamps
10
10
 
11
11
  autoload :DynamicAttributes
12
12
 
@@ -2,3 +2,4 @@ en:
2
2
  errors:
3
3
  messages:
4
4
  taken: "is already taken"
5
+ invalid_type: "should be a %{type}"
@@ -1,7 +1,9 @@
1
- require "nobrainer"
2
- require "rails"
1
+ require 'nobrainer'
3
2
 
4
3
  class NoBrainer::Railtie < Rails::Railtie
4
+ config.app_generators.orm :nobrainer
5
+ config.eager_load_namespaces << NoBrainer
6
+
5
7
  config.action_dispatch.rescue_responses.merge!(
6
8
  "NoBrainer::Errors::DocumentNotFound" => :not_found,
7
9
  "NoBrainer::Errors::DocumentInvalid" => :unprocessable_entity,
@@ -16,7 +18,7 @@ class NoBrainer::Railtie < Rails::Railtie
16
18
 
17
19
  if defined?(ActiveRecord) && NoBrainer::Config.warn_on_active_record
18
20
  STDERR.puts "[NoBrainer] WARNING: ActiveRecord is loaded which is probably not what you want."
19
- STDERR.puts "[NoBrainer] Follow the instructions on http://todo/ to learn how to remove ActiveRecord."
21
+ STDERR.puts "[NoBrainer] Follow the instructions on http://nobrainer.io/docs/configuration/#removing_activerecord"
20
22
  STDERR.puts "[NoBrainer] Configure NoBrainer with 'config.warn_on_active_record = false' to disable with warning."
21
23
  end
22
24
 
@@ -30,6 +32,4 @@ class NoBrainer::Railtie < Rails::Railtie
30
32
  NoBrainer::Loader.cleanup
31
33
  end
32
34
  end
33
-
34
- #config.eager_load_namespaces << NoBrainer
35
35
  end
data/lib/nobrainer.rb CHANGED
@@ -1,15 +1,19 @@
1
- require 'active_support/core_ext'
2
-
3
1
  if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9')
4
2
  raise 'Please use Ruby 1.9 or later'
5
3
  end
6
4
 
5
+ require 'active_support'
6
+ %w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion
7
+ object/deep_dup object/try hash/keys hash/indifferent_access hash/reverse_merge array/extract_options)
8
+ .each { |dep| require "active_support/core_ext/#{dep}" }
9
+
7
10
  module NoBrainer
8
11
  require 'no_brainer/autoload'
9
12
  extend NoBrainer::Autoload
10
13
 
11
- autoload :Config, :Document, :Connection, :Database, :Error, :QueryRunner,
12
- :Criteria, :DecoratedSymbol, :IndexManager, :Loader, :Logging, :Util, :Fork
14
+ # We eager load things that could be loaded for the first time during the web request
15
+ autoload :Document, :IndexManager, :Loader, :Fork, :DecoratedSymbol
16
+ eager_autoload :Config, :Connection, :Error, :QueryRunner, :Criteria, :Util
13
17
 
14
18
  class << self
15
19
  # Note: we always access the connection explicitly, so that in the future,
@@ -34,14 +38,14 @@ module NoBrainer
34
38
  end
35
39
  end
36
40
 
37
- # Not using modules to extend, it's nicer to see the NoBrainer module API here.
38
41
  delegate :db_create, :db_drop, :db_list,
39
42
  :table_create, :table_drop, :table_list,
40
43
  :drop!, :purge!, :to => :connection
41
- delegate :run, :to => 'NoBrainer::QueryRunner'
42
- delegate :update_indexes, :to => 'NoBrainer::IndexManager'
44
+
45
+ delegate :configure, :logger, :to => 'NoBrainer::Config'
46
+ delegate :run, :to => 'NoBrainer::QueryRunner'
47
+ delegate :update_indexes, :to => 'NoBrainer::IndexManager'
43
48
  delegate :with, :with_database, :to => 'NoBrainer::QueryRunner::RunOptions'
44
- delegate :configure, :logger, :to => 'NoBrainer::Config'
45
49
 
46
50
  def jruby?
47
51
  RUBY_PLATFORM == 'java'
@@ -0,0 +1,17 @@
1
+ require "rails/generators/nobrainer"
2
+
3
+ module NoBrainer::Generators
4
+ class ModelGenerator < Base
5
+ argument :attributes, :type => :array, default: [], banner: "field ... field"
6
+
7
+ check_class_collision
8
+
9
+ class_option :parent, :type => :string, :desc => "The parent class for the generated model"
10
+
11
+ def create_model_file
12
+ template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb")
13
+ end
14
+
15
+ hook_for :test_framework
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent] %>
3
+ <% unless options[:parent] -%>
4
+ include NoBrainer::Document
5
+ <% end -%>
6
+ <% attributes.reject(&:reference?).each do |attribute| -%>
7
+ field :<%= attribute.name %>
8
+ <% end -%>
9
+ <% attributes.select(&:reference?).each do |attribute| -%>
10
+ belongs_to :<%= attribute.name %>
11
+ <% end -%>
12
+ end
13
+ <% end -%>
@@ -0,0 +1,18 @@
1
+ require 'rails/generators/named_base'
2
+ require 'nobrainer'
3
+
4
+ module NoBrainer::Generators
5
+ class Base < Rails::Generators::NamedBase
6
+ def self.base_name
7
+ 'nobrainer'
8
+ end
9
+
10
+ def self.base_root
11
+ File.dirname(__FILE__)
12
+ end
13
+
14
+ def self.namespace
15
+ super.gsub(/no_brainer/,'nobrainer')
16
+ end
17
+ end
18
+ end
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.9.0
4
+ version: 0.11.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: 2014-01-05 00:00:00.000000000 Z
11
+ date: 2014-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rethinkdb
@@ -66,35 +66,37 @@ extensions: []
66
66
  extra_rdoc_files: []
67
67
  files:
68
68
  - lib/no_brainer/document/id.rb
69
- - lib/no_brainer/document/injection_layer.rb
70
- - lib/no_brainer/document/timestamps.rb
71
- - lib/no_brainer/document/association/belongs_to.rb
72
- - lib/no_brainer/document/association/core.rb
73
- - lib/no_brainer/document/association/eager_loader.rb
74
- - lib/no_brainer/document/association/has_many_through.rb
75
69
  - lib/no_brainer/document/association/has_many.rb
76
70
  - lib/no_brainer/document/association/has_one.rb
77
71
  - lib/no_brainer/document/association/has_one_through.rb
72
+ - lib/no_brainer/document/association/has_many_through.rb
73
+ - lib/no_brainer/document/association/core.rb
74
+ - lib/no_brainer/document/association/belongs_to.rb
75
+ - lib/no_brainer/document/association/eager_loader.rb
76
+ - lib/no_brainer/document/polymorphic.rb
77
+ - lib/no_brainer/document/store_in.rb
78
+ - lib/no_brainer/document/injection_layer.rb
79
+ - lib/no_brainer/document/index.rb
80
+ - lib/no_brainer/document/serialization.rb
81
+ - lib/no_brainer/document/association.rb
82
+ - lib/no_brainer/document/timestamps.rb
83
+ - lib/no_brainer/document/core.rb
84
+ - lib/no_brainer/document/callbacks.rb
85
+ - lib/no_brainer/document/validation.rb
78
86
  - lib/no_brainer/document/dirty.rb
87
+ - lib/no_brainer/document/attributes.rb
79
88
  - lib/no_brainer/document/dynamic_attributes.rb
80
89
  - lib/no_brainer/document/persistance.rb
81
- - lib/no_brainer/document/validation.rb
82
- - lib/no_brainer/document/serialization.rb
83
90
  - lib/no_brainer/document/criteria.rb
84
- - lib/no_brainer/document/index.rb
85
- - lib/no_brainer/document/polymorphic.rb
86
- - lib/no_brainer/document/store_in.rb
87
- - lib/no_brainer/document/attributes.rb
88
- - lib/no_brainer/document/core.rb
89
- - lib/no_brainer/document/association.rb
91
+ - lib/no_brainer/document/types.rb
90
92
  - lib/no_brainer/query_runner/connection.rb
91
93
  - lib/no_brainer/query_runner/database_on_demand.rb
92
- - lib/no_brainer/query_runner/logger.rb
93
94
  - lib/no_brainer/query_runner/table_on_demand.rb
94
95
  - lib/no_brainer/query_runner/write_error.rb
95
96
  - lib/no_brainer/query_runner/driver.rb
96
97
  - lib/no_brainer/query_runner/missing_index.rb
97
98
  - lib/no_brainer/query_runner/run_options.rb
99
+ - lib/no_brainer/query_runner/logger.rb
98
100
  - lib/no_brainer/railtie/database.rake
99
101
  - lib/no_brainer/criteria/chainable/limit.rb
100
102
  - lib/no_brainer/criteria/chainable/order_by.rb
@@ -108,23 +110,25 @@ files:
108
110
  - lib/no_brainer/criteria/termination/enumerable.rb
109
111
  - lib/no_brainer/criteria/termination/update.rb
110
112
  - lib/no_brainer/criteria/termination/delete.rb
111
- - lib/no_brainer/criteria/termination/eager_loading.rb
112
113
  - lib/no_brainer/criteria/termination/cache.rb
114
+ - lib/no_brainer/criteria/termination/preload.rb
113
115
  - lib/no_brainer/decorated_symbol.rb
114
116
  - lib/no_brainer/index_manager.rb
115
117
  - lib/no_brainer/loader.rb
116
118
  - lib/no_brainer/locale/en.yml
117
- - lib/no_brainer/util.rb
118
119
  - lib/no_brainer/fork.rb
119
- - lib/no_brainer/criteria.rb
120
120
  - lib/no_brainer/connection.rb
121
121
  - lib/no_brainer/query_runner.rb
122
122
  - lib/no_brainer/error.rb
123
- - lib/no_brainer/document.rb
124
- - lib/no_brainer/config.rb
125
123
  - lib/no_brainer/railtie.rb
124
+ - lib/no_brainer/config.rb
126
125
  - lib/no_brainer/autoload.rb
127
- - lib/no_brainer/version.rb
126
+ - lib/no_brainer/document.rb
127
+ - lib/no_brainer/util.rb
128
+ - lib/no_brainer/criteria.rb
129
+ - lib/rails/generators/nobrainer.rb
130
+ - lib/rails/generators/nobrainer/model/model_generator.rb
131
+ - lib/rails/generators/nobrainer/model/templates/model.rb.tt
128
132
  - lib/nobrainer.rb
129
133
  - README.md
130
134
  - LICENSE.md
@@ -1,50 +0,0 @@
1
- module NoBrainer::Criteria::Termination::EagerLoading
2
- extend ActiveSupport::Concern
3
-
4
- included { attr_accessor :_includes }
5
-
6
- def initialize(options={})
7
- super
8
- self._includes = []
9
- end
10
-
11
- def includes(*values)
12
- chain(:keep_cache => true) { |criteria| criteria._includes = values }
13
- end
14
-
15
- def merge!(criteria, options={})
16
- super
17
- self._includes = self._includes + criteria._includes
18
-
19
- # XXX Not pretty hack
20
- if criteria._includes.present? && cached?
21
- perform_eager_loading(@cache)
22
- end
23
- end
24
-
25
- def each(options={}, &block)
26
- return super unless should_eager_load? && !options[:no_eager_loading] && block
27
-
28
- docs = []
29
- super(options.merge(:no_eager_loading => true)) { |doc| docs << doc }
30
- perform_eager_loading(docs)
31
- docs.each(&block)
32
- self
33
- end
34
-
35
- private
36
-
37
- def should_eager_load?
38
- self._includes.present? && !raw?
39
- end
40
-
41
- def get_one(criteria)
42
- super.tap { |doc| perform_eager_loading([doc]) }
43
- end
44
-
45
- def perform_eager_loading(docs)
46
- if should_eager_load? && docs.present?
47
- NoBrainer::Document::Association::EagerLoader.new.eager_load(docs, self._includes)
48
- end
49
- end
50
- end
@@ -1,3 +0,0 @@
1
- module NoBrainer
2
- VERSION = '0.9.0'
3
- end