nobrainer 0.10.0 → 0.11.0

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: 0fa1301301888d07f8c607cfd1102555aad40a92
4
- data.tar.gz: f05a189ae40c4e6b1f2d420dd84a4928a6f65203
3
+ metadata.gz: 56f5bfdef46fb94e140efe80af84031e02238d07
4
+ data.tar.gz: aa5965b806a43a8727be98ef2a7a9c370fe94cfc
5
5
  SHA512:
6
- metadata.gz: 1bfa4718cb043f4847f972d46b2728c772475b8d07683a591d38d6ec039d06c09508d2df855791e935bbaa85f5d11780a367a383e9ebae0c19dce278dab7c83d
7
- data.tar.gz: 4584d38e6646c426672cea00c0f9aa58f05a6df4778586ae2d699662d927984b1f32ba3b87ebc3b787d4ad9181b19e9e4ecf3f4cfafdbc116f85920566534ed0
6
+ metadata.gz: 0285cc6834d21a4e973fe293adf54ada0bcce15424a605719a32db2445a909793ebab766549a9d8b31b42ced44e6d3ef5f1cfecd980b33b4c4c1792af94a74c4
7
+ data.tar.gz: 3ed81e4508bc4528142b7049db0e1464927f7dfcad9c459853593c70a9ebbed95c47b3bd634d03f0bc2e9ebcc358a2d72cbcd7b7c73b4c46ca37a518a23c7c56
@@ -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
 
@@ -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
 
@@ -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
@@ -5,8 +5,8 @@ module NoBrainer::Document
5
5
  extend NoBrainer::Autoload
6
6
 
7
7
  autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Validation, :Types,
8
- :Persistance, :Dirty, :Id, :Association, :Serialization, :Criteria,
9
- :Polymorphic, :Index, :Timestamps
8
+ :Persistance, :Callbacks, :Dirty, :Id, :Association, :Serialization,
9
+ :Criteria, :Polymorphic, :Index, :Timestamps
10
10
 
11
11
  autoload :DynamicAttributes
12
12
 
@@ -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
@@ -40,19 +40,19 @@ class NoBrainer::Document::Association::EagerLoader
40
40
  association.eager_load(docs, criteria)
41
41
  end
42
42
 
43
- def eager_load(docs, includes)
44
- case includes
45
- 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|
46
46
  if v.is_a?(NoBrainer::Criteria)
47
47
  v = v.dup
48
- nested_includes, v._includes = v._includes, []
49
- 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)
50
50
  else
51
51
  eager_load(eager_load_association(docs, k), v)
52
52
  end
53
53
  end
54
- when Array then includes.each { |v| eager_load(docs, v) }
55
- else eager_load_association(docs, includes)
54
+ when Array then preloads.each { |v| eager_load(docs, v) }
55
+ else eager_load_association(docs, preloads)
56
56
  end
57
57
  true
58
58
  end
@@ -1,5 +1,5 @@
1
1
  module NoBrainer::Document::Attributes
2
- VALID_FIELD_OPTIONS = [:index, :default, :type]
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)
@@ -45,7 +44,7 @@ module NoBrainer::Document::Attributes
45
44
  end
46
45
 
47
46
  def assign_attributes(attrs, options={})
48
- @attributes.clear if options[:pristine]
47
+ @_attributes.clear if options[:pristine]
49
48
  _assign_attributes(attrs, options)
50
49
  assign_defaults if options[:pristine]
51
50
  self
@@ -54,7 +53,7 @@ module NoBrainer::Document::Attributes
54
53
 
55
54
  def inspectable_attributes
56
55
  # TODO test that thing
57
- Hash[@attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
56
+ Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
58
57
  end
59
58
 
60
59
  def inspect
@@ -88,11 +87,11 @@ module NoBrainer::Document::Attributes
88
87
  # Using a layer so the user can use super when overriding these methods
89
88
  inject_in_layer :attributes, <<-RUBY, __FILE__, __LINE__ + 1
90
89
  def #{name}=(value)
91
- @attributes['#{name}'] = value
90
+ @_attributes['#{name}'] = value
92
91
  end
93
92
 
94
93
  def #{name}
95
- @attributes['#{name}']
94
+ @_attributes['#{name}']
96
95
  end
97
96
  RUBY
98
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
- super
20
- clear_dirtiness if options[:pristine]
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
@@ -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
+ class_attribute :timestamps_disabled
6
+
5
7
  self.field :created_at, :type => Time
6
8
  self.field :updated_at, :type => Time
7
9
 
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=) }
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
@@ -1,12 +1,6 @@
1
1
  module NoBrainer::Document::Types
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included do
5
- # We namespace our fake Boolean class to avoid polluting the global namespace
6
- class_exec { class Boolean; def initialize; raise; end; end }
7
- before_validation :add_type_errors
8
- end
9
-
10
4
  module CastingRules
11
5
  extend self
12
6
 
@@ -63,19 +57,30 @@ module NoBrainer::Document::Types
63
57
  else raise InvalidType
64
58
  end
65
59
  end
66
- end
67
60
 
68
- def self.cast(value, type, cast_method)
69
- return value if value.nil? || type.nil? || value.is_a?(type)
70
- cast_method.call(value)
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
71
  end
72
72
 
73
- def self.lookup_cast_method(type)
74
- type = type.to_s
75
- type = 'Boolean' if type == 'NoBrainer::Document::Types::Boolean'
76
- CastingRules.method(type)
77
- rescue NameError
78
- proc { raise InvalidType }
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
79
84
  end
80
85
 
81
86
  class InvalidType < RuntimeError
@@ -102,12 +107,12 @@ module NoBrainer::Document::Types
102
107
  return unless options.has_key?(:type)
103
108
  name = name.to_sym
104
109
  type = options[:type]
105
- cast_method = NoBrainer::Document::Types.lookup_cast_method(type)
110
+ cast_method = NoBrainer::Document::Types::CastingRules.lookup(type)
106
111
 
107
112
  inject_in_layer :types do
108
113
  define_method("#{name}=") do |value|
109
114
  begin
110
- value = NoBrainer::Document::Types.cast(value, type, cast_method)
115
+ value = NoBrainer::Document::Types::CastingRules.cast(value, type, cast_method)
111
116
  @pending_type_errors.try(:delete, name)
112
117
  rescue NoBrainer::Document::Types::InvalidType => error
113
118
  error.type ||= type
@@ -116,11 +121,14 @@ module NoBrainer::Document::Types
116
121
  end
117
122
  super(value)
118
123
  end
124
+
125
+ define_method("#{name}?") { !!read_attribute(name) } if type == Boolean
119
126
  end
120
127
  end
121
128
 
122
129
  def remove_field(name)
123
130
  super
131
+ # TODO remove the name? if we added it. Low priority though.
124
132
  inject_in_layer :types, <<-RUBY, __FILE__, __LINE__ + 1
125
133
  undef #{name}=
126
134
  RUBY
@@ -8,6 +8,11 @@ module NoBrainer::Document::Validation
8
8
  end
9
9
 
10
10
  module ClassMethods
11
+ def field(name, options={})
12
+ super
13
+ validates(name.to_sym, options[:validates]) if options[:validates]
14
+ end
15
+
11
16
  def validates_uniqueness_of(*attr_names)
12
17
  validates_with UniquenessValidator, _merge_attributes(attr_names)
13
18
  end
data/lib/nobrainer.rb CHANGED
@@ -4,7 +4,7 @@ end
4
4
 
5
5
  require 'active_support'
6
6
  %w(module/delegation module/attribute_accessors class/attribute object/blank object/inclusion
7
- object/duplicable object/try hash/keys hash/reverse_merge array/extract_options)
7
+ object/deep_dup object/try hash/keys hash/indifferent_access hash/reverse_merge array/extract_options)
8
8
  .each { |dep| require "active_support/core_ext/#{dep}" }
9
9
 
10
10
  module NoBrainer
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.10.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-07 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,36 +66,37 @@ extensions: []
66
66
  extra_rdoc_files: []
67
67
  files:
68
68
  - lib/no_brainer/document/id.rb
69
- - lib/no_brainer/document/association/belongs_to.rb
70
69
  - lib/no_brainer/document/association/has_many.rb
71
70
  - lib/no_brainer/document/association/has_one.rb
72
71
  - lib/no_brainer/document/association/has_one_through.rb
73
72
  - lib/no_brainer/document/association/has_many_through.rb
74
- - lib/no_brainer/document/association/eager_loader.rb
75
73
  - lib/no_brainer/document/association/core.rb
76
- - lib/no_brainer/document/dynamic_attributes.rb
77
- - lib/no_brainer/document/criteria.rb
74
+ - lib/no_brainer/document/association/belongs_to.rb
75
+ - lib/no_brainer/document/association/eager_loader.rb
78
76
  - lib/no_brainer/document/polymorphic.rb
79
77
  - lib/no_brainer/document/store_in.rb
80
- - lib/no_brainer/document/core.rb
81
78
  - lib/no_brainer/document/injection_layer.rb
82
79
  - lib/no_brainer/document/index.rb
83
80
  - lib/no_brainer/document/serialization.rb
84
81
  - lib/no_brainer/document/association.rb
85
- - lib/no_brainer/document/validation.rb
86
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
87
86
  - lib/no_brainer/document/dirty.rb
88
- - lib/no_brainer/document/persistance.rb
89
87
  - lib/no_brainer/document/attributes.rb
88
+ - lib/no_brainer/document/dynamic_attributes.rb
89
+ - lib/no_brainer/document/persistance.rb
90
+ - lib/no_brainer/document/criteria.rb
90
91
  - lib/no_brainer/document/types.rb
91
92
  - lib/no_brainer/query_runner/connection.rb
92
93
  - lib/no_brainer/query_runner/database_on_demand.rb
93
- - lib/no_brainer/query_runner/logger.rb
94
94
  - lib/no_brainer/query_runner/table_on_demand.rb
95
95
  - lib/no_brainer/query_runner/write_error.rb
96
96
  - lib/no_brainer/query_runner/driver.rb
97
97
  - lib/no_brainer/query_runner/missing_index.rb
98
98
  - lib/no_brainer/query_runner/run_options.rb
99
+ - lib/no_brainer/query_runner/logger.rb
99
100
  - lib/no_brainer/railtie/database.rake
100
101
  - lib/no_brainer/criteria/chainable/limit.rb
101
102
  - lib/no_brainer/criteria/chainable/order_by.rb
@@ -109,15 +110,13 @@ files:
109
110
  - lib/no_brainer/criteria/termination/enumerable.rb
110
111
  - lib/no_brainer/criteria/termination/update.rb
111
112
  - lib/no_brainer/criteria/termination/delete.rb
112
- - lib/no_brainer/criteria/termination/eager_loading.rb
113
113
  - lib/no_brainer/criteria/termination/cache.rb
114
+ - lib/no_brainer/criteria/termination/preload.rb
114
115
  - lib/no_brainer/decorated_symbol.rb
115
116
  - lib/no_brainer/index_manager.rb
116
117
  - lib/no_brainer/loader.rb
117
118
  - lib/no_brainer/locale/en.yml
118
- - lib/no_brainer/util.rb
119
119
  - lib/no_brainer/fork.rb
120
- - lib/no_brainer/criteria.rb
121
120
  - lib/no_brainer/connection.rb
122
121
  - lib/no_brainer/query_runner.rb
123
122
  - lib/no_brainer/error.rb
@@ -125,6 +124,8 @@ files:
125
124
  - lib/no_brainer/config.rb
126
125
  - lib/no_brainer/autoload.rb
127
126
  - lib/no_brainer/document.rb
127
+ - lib/no_brainer/util.rb
128
+ - lib/no_brainer/criteria.rb
128
129
  - lib/rails/generators/nobrainer.rb
129
130
  - lib/rails/generators/nobrainer/model/model_generator.rb
130
131
  - lib/rails/generators/nobrainer/model/templates/model.rb.tt
@@ -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