nobrainer 0.13.0 → 0.14.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/no_brainer/config.rb +6 -4
  3. data/lib/no_brainer/connection.rb +14 -12
  4. data/lib/no_brainer/criteria/core.rb +1 -6
  5. data/lib/no_brainer/criteria/delete.rb +2 -2
  6. data/lib/no_brainer/criteria/order_by.rb +33 -10
  7. data/lib/no_brainer/criteria/preload.rb +0 -5
  8. data/lib/no_brainer/criteria/update.rb +2 -2
  9. data/lib/no_brainer/criteria/where.rb +34 -7
  10. data/lib/no_brainer/decorated_symbol.rb +2 -1
  11. data/lib/no_brainer/document/association/belongs_to.rb +19 -11
  12. data/lib/no_brainer/document/association/core.rb +1 -1
  13. data/lib/no_brainer/document/association/has_many.rb +10 -4
  14. data/lib/no_brainer/document/attributes.rb +29 -4
  15. data/lib/no_brainer/document/callbacks.rb +3 -2
  16. data/lib/no_brainer/document/core.rb +14 -5
  17. data/lib/no_brainer/document/criteria.rb +9 -9
  18. data/lib/no_brainer/document/dirty.rb +11 -5
  19. data/lib/no_brainer/document/dynamic_attributes.rb +4 -0
  20. data/lib/no_brainer/document/id.rb +49 -4
  21. data/lib/no_brainer/document/index.rb +12 -3
  22. data/lib/no_brainer/document/persistance.rb +5 -9
  23. data/lib/no_brainer/document/readonly.rb +7 -0
  24. data/lib/no_brainer/document/serialization.rb +4 -0
  25. data/lib/no_brainer/document/types.rb +62 -13
  26. data/lib/no_brainer/document/uniqueness.rb +99 -0
  27. data/lib/no_brainer/document/validation.rb +8 -24
  28. data/lib/no_brainer/document.rb +3 -1
  29. data/lib/no_brainer/error.rb +9 -9
  30. data/lib/no_brainer/index_manager.rb +4 -2
  31. data/lib/no_brainer/loader.rb +1 -1
  32. data/lib/no_brainer/query_runner/logger.rb +31 -19
  33. data/lib/no_brainer/query_runner/missing_index.rb +15 -3
  34. data/lib/no_brainer/query_runner/{connection.rb → reconnect.rb} +16 -5
  35. data/lib/no_brainer/query_runner/table_on_demand.rb +14 -25
  36. data/lib/no_brainer/query_runner/write_error.rb +5 -8
  37. data/lib/no_brainer/query_runner.rb +2 -2
  38. data/lib/no_brainer/rql.rb +25 -0
  39. data/lib/nobrainer.rb +5 -6
  40. metadata +70 -69
  41. data/lib/no_brainer/util.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dd6046706e63442640e96473b737f37ad5a4fea7
4
- data.tar.gz: e1634aaf6acfb3c3e5e4287c57151d54cce30ee1
3
+ metadata.gz: 43783bcf0d625af679de685ab771782ce9b141f3
4
+ data.tar.gz: 4b5e4ff205f55adb754133b681b6e71c71772790
5
5
  SHA512:
6
- metadata.gz: ac735666befc4a025536e5beb72a8c4d4d79259875a8ad956ae16e4399c17cf5c2dcc7eecf6bcfa4351d56aec14b1a83a7d68cb751723d5beade516011990915
7
- data.tar.gz: b6f69cd4dfbd953d9acc06a1c06aa9a27851baa51b1e1a2d0fce0ff320a32131c49724e5ea6aaa8af3a2aaf74bdb661379eb0a4e97abdb98e8084e5fae0516c5
6
+ metadata.gz: 1c182d056390dd2c7b51bac68999d79c72cfaca49837239c4eead56ce8d0ff474630cc7b24e9fa12a4ff8d36641a5f6cc46fb7c588c45e975af42e8d4b5dbebc
7
+ data.tar.gz: 5226adb01498de4e82f8b3a4729c818ed1a40dc07e3596061d64956407b9a62129108046e0625bbe60bcac1804f0b8111cab4ec3a736151b6cfdc3d5799f2d88
@@ -4,7 +4,8 @@ module NoBrainer::Config
4
4
  class << self
5
5
  mattr_accessor :rethinkdb_url, :logger, :warn_on_active_record,
6
6
  :auto_create_databases, :auto_create_tables,
7
- :max_reconnection_tries, :durability, :colorize_logger
7
+ :max_reconnection_tries, :durability, :colorize_logger,
8
+ :distributed_lock_class
8
9
 
9
10
  def apply_defaults
10
11
  self.rethinkdb_url = default_rethinkdb_url
@@ -15,6 +16,7 @@ module NoBrainer::Config
15
16
  self.max_reconnection_tries = 10
16
17
  self.durability = default_durability
17
18
  self.colorize_logger = true
19
+ self.distributed_lock_class = nil
18
20
  end
19
21
 
20
22
  def reset!
@@ -36,7 +38,7 @@ module NoBrainer::Config
36
38
 
37
39
  def default_rethinkdb_url
38
40
  db = ENV['RETHINKDB_DB'] || ENV['RDB_DB']
39
- db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}" if defined?(Rails)
41
+ db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}" rescue nil
40
42
  host = ENV['RETHINKDB_HOST'] || ENV['RDB_HOST'] || 'localhost'
41
43
  port = ENV['RETHINKDB_PORT'] || ENV['RDB_PORT']
42
44
  auth = ENV['RETHINKDB_AUTH'] || ENV['RDB_AUTH']
@@ -46,11 +48,11 @@ module NoBrainer::Config
46
48
  end
47
49
 
48
50
  def default_logger
49
- defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
51
+ defined?(Rails.logger) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
50
52
  end
51
53
 
52
54
  def default_durability
53
- (defined?(Rails) && (Rails.env.test? || Rails.env.development?)) ? :soft : :hard
55
+ (defined?(Rails.env) && (Rails.env.test? || Rails.env.development?)) ? :soft : :hard
54
56
  end
55
57
  end
56
58
  end
@@ -1,7 +1,7 @@
1
1
  require 'rethinkdb'
2
2
 
3
3
  class NoBrainer::Connection
4
- attr_accessor :uri
4
+ attr_accessor :uri, :parsed_uri
5
5
 
6
6
  def initialize(uri)
7
7
  self.uri = uri
@@ -9,19 +9,21 @@ class NoBrainer::Connection
9
9
  end
10
10
 
11
11
  def parsed_uri
12
- require 'uri'
13
- uri = URI.parse(self.uri)
12
+ @parsed_uri ||= begin
13
+ require 'uri'
14
+ uri = URI.parse(self.uri)
14
15
 
15
- if uri.scheme != 'rethinkdb'
16
- raise NoBrainer::Error::Connection,
17
- "Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
18
- end
16
+ if uri.scheme != 'rethinkdb'
17
+ raise NoBrainer::Error::Connection,
18
+ "Invalid URI. Expecting something like rethinkdb://host:port/database. Got #{uri}"
19
+ end
19
20
 
20
- { :auth_key => uri.password,
21
- :host => uri.host,
22
- :port => uri.port || 28015,
23
- :db => uri.path.gsub(/^\//, ''),
24
- }.tap { |result| raise "No database specified in #{uri}" unless result[:db].present? }
21
+ { :auth_key => uri.password,
22
+ :host => uri.host,
23
+ :port => uri.port || 28015,
24
+ :db => uri.path.gsub(/^\//, ''),
25
+ }.tap { |result| raise "No database specified in #{uri}" unless result[:db].present? }
26
+ end
25
27
  end
26
28
 
27
29
  def raw
@@ -17,12 +17,7 @@ module NoBrainer::Criteria::Core
17
17
 
18
18
  def inspect
19
19
  # rescue super because sometimes klass is not set.
20
- str = to_rql.inspect rescue super
21
- if str =~ /Erroneous_Portion_Constructed/
22
- # Need to fix the rethinkdb gem.
23
- str = "the rethinkdb gem is flipping out with Erroneous_Portion_Constructed"
24
- end
25
- str
20
+ to_rql.inspect rescue super
26
21
  end
27
22
 
28
23
  def run(rql=nil)
@@ -2,10 +2,10 @@ module NoBrainer::Criteria::Delete
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def delete_all
5
- run(to_rql.delete)
5
+ run(without_ordering.to_rql.delete)
6
6
  end
7
7
 
8
8
  def destroy_all
9
- to_a.each { |doc| doc.destroy }
9
+ without_ordering.to_a.each { |doc| doc.destroy }
10
10
  end
11
11
  end
@@ -1,7 +1,7 @@
1
1
  module NoBrainer::Criteria::OrderBy
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included { attr_accessor :order, :_reverse_order }
4
+ included { attr_accessor :order, :ordering_mode }
5
5
 
6
6
  def initialize(options={})
7
7
  super
@@ -24,36 +24,55 @@ module NoBrainer::Criteria::OrderBy
24
24
 
25
25
  chain do |criteria|
26
26
  criteria.order = rules
27
- criteria._reverse_order = false
27
+ criteria.ordering_mode = :normal
28
28
  end
29
29
  end
30
30
 
31
+ def without_ordering
32
+ chain { |criteria| criteria.ordering_mode = :disabled }
33
+ end
34
+
31
35
  def merge!(criteria, options={})
32
36
  super
33
37
  # The latest order_by() wins
34
38
  self.order = criteria.order if criteria.order.present?
35
- self._reverse_order = criteria._reverse_order unless criteria._reverse_order.nil?
39
+ self.ordering_mode = criteria.ordering_mode unless criteria.ordering_mode.nil?
36
40
  self
37
41
  end
38
42
 
39
43
  def reverse_order
40
- chain { |criteria| criteria._reverse_order = !self._reverse_order }
44
+ chain do |criteria|
45
+ criteria.ordering_mode =
46
+ case self.ordering_mode
47
+ when nil then :reversed
48
+ when :normal then :reversed
49
+ when :reversed then :normal
50
+ when :disabled then :disabled
51
+ end
52
+ end
41
53
  end
42
54
 
43
55
  private
44
56
 
45
57
  def effective_order
46
- self.order.presence || {:id => :asc}
58
+ self.order.presence || (klass ? {klass.pk_name => :asc} : {})
47
59
  end
48
60
 
49
61
  def reverse_order?
50
- !!self._reverse_order
62
+ self.ordering_mode == :reversed
63
+ end
64
+
65
+ def should_order?
66
+ self.ordering_mode != :disabled
51
67
  end
52
68
 
53
69
  def compile_rql_pass1
54
70
  rql = super
71
+ return rql unless should_order?
72
+ _effective_order = effective_order
73
+ return rql if _effective_order.empty?
55
74
 
56
- rql_rules = effective_order.map do |k,v|
75
+ rql_rules = _effective_order.map do |k,v|
57
76
  case v
58
77
  when :asc then reverse_order? ? RethinkDB::RQL.new.desc(k) : RethinkDB::RQL.new.asc(k)
59
78
  when :desc then reverse_order? ? RethinkDB::RQL.new.asc(k) : RethinkDB::RQL.new.desc(k)
@@ -64,9 +83,10 @@ module NoBrainer::Criteria::OrderBy
64
83
  # We are going to try to go so and if we cannot, we'll simply apply
65
84
  # the ordering in pass2, which will happen after a potential filter().
66
85
 
67
- if rql.body.type == Term::TermType::TABLE && !without_index?
86
+ NoBrainer::RQL.is_table?(rql)
87
+ if NoBrainer::RQL.is_table?(rql) && !without_index?
68
88
  options = {}
69
- first_key = effective_order.first[0]
89
+ first_key = _effective_order.first[0]
70
90
  if (first_key.is_a?(Symbol) || first_key.is_a?(String)) && klass.has_index?(first_key)
71
91
  options[:index] = rql_rules.shift
72
92
  end
@@ -83,7 +103,10 @@ module NoBrainer::Criteria::OrderBy
83
103
 
84
104
  def compile_rql_pass2
85
105
  rql = super
86
- rql = rql.order_by(*@rql_rules_pass2) if @rql_rules_pass2
106
+ if @rql_rules_pass2
107
+ rql = rql.order_by(*@rql_rules_pass2)
108
+ @rql_rules_pass2 = nil
109
+ end
87
110
  rql
88
111
  end
89
112
 
@@ -12,11 +12,6 @@ module NoBrainer::Criteria::Preload
12
12
  chain(:keep_cache => true) { |criteria| criteria._preloads = values }
13
13
  end
14
14
 
15
- def includes(*values)
16
- NoBrainer.logger.warn "[NoBrainer] includes() is deprecated and will be removed, use preload() instead."
17
- preload(*values)
18
- end
19
-
20
15
  def merge!(criteria, options={})
21
16
  super
22
17
  self._preloads = self._preloads + criteria._preloads
@@ -2,10 +2,10 @@ module NoBrainer::Criteria::Update
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def update_all(*args, &block)
5
- run(to_rql.update(*args, &block))
5
+ run(without_ordering.to_rql.update(*args, &block))
6
6
  end
7
7
 
8
8
  def replace_all(*args, &block)
9
- run(to_rql.replace(*args, &block))
9
+ run(without_ordering.to_rql.replace(*args, &block))
10
10
  end
11
11
  end
@@ -56,14 +56,15 @@ module NoBrainer::Criteria::Where
56
56
 
57
57
  class BinaryOperator < Struct.new(:key, :op, :value, :criteria)
58
58
  def simplify
59
+ key = cast_key(self.key)
59
60
  case op
60
61
  when :in then
61
62
  case value
62
- when Range then BinaryOperator.new(key, :between, (cast(value.min)..cast(value.max)), criteria)
63
- when Array then BinaryOperator.new(key, :in, value.map(&method(:cast)).uniq, criteria)
63
+ when Range then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria)
64
+ when Array then BinaryOperator.new(key, :in, value.map(&method(:cast_value)).uniq, criteria)
64
65
  else raise ArgumentError.new ":in takes an array/range, not #{value}"
65
66
  end
66
- else BinaryOperator.new(key, op, cast(value), criteria)
67
+ else BinaryOperator.new(key, op, cast_value(value), criteria)
67
68
  end
68
69
  end
69
70
 
@@ -77,8 +78,33 @@ module NoBrainer::Criteria::Where
77
78
 
78
79
  private
79
80
 
80
- def cast(value)
81
- criteria.klass.cast_value_for(key, value)
81
+ def association
82
+ # FIXME This leaks memory with dynamic attributes. The internals of type
83
+ # checking will convert the key to a symbol, and Ruby does not garbage
84
+ # collect symbols.
85
+ @association ||= [criteria.klass.association_metadata[key.to_sym]]
86
+ @association.first
87
+ end
88
+
89
+ def cast_value(value)
90
+ case association
91
+ when NoBrainer::Document::Association::BelongsTo::Metadata
92
+ target_klass = association.target_klass
93
+ opts = { :attr_name => key, :value => value, :type => target_klass}
94
+ raise NoBrainer::Error::InvalidType.new(opts) unless value.is_a?(target_klass)
95
+ value.pk_value
96
+ else
97
+ criteria.klass.cast_user_to_db_for(key, value)
98
+ end
99
+ end
100
+
101
+ def cast_key(key)
102
+ case association
103
+ when NoBrainer::Document::Association::BelongsTo::Metadata
104
+ association.foreign_key
105
+ else
106
+ key
107
+ end
82
108
  end
83
109
  end
84
110
 
@@ -126,8 +152,9 @@ module NoBrainer::Criteria::Where
126
152
  when String, Symbol then parse_clause_stub_eq(key, value)
127
153
  when NoBrainer::DecoratedSymbol then
128
154
  case key.modifier
129
- when :ne then parse_clause(:not => { key.symbol => value })
130
- when :eq then parse_clause_stub_eq(key.symbol, value)
155
+ when :nin then parse_clause(:not => { key.symbol.in => value })
156
+ when :ne then parse_clause(:not => { key.symbol.eq => value })
157
+ when :eq then parse_clause_stub_eq(key.symbol, value)
131
158
  else BinaryOperator.new(key.symbol, key.modifier, value, self)
132
159
  end
133
160
  else raise "Invalid key: #{key}"
@@ -1,5 +1,6 @@
1
1
  class NoBrainer::DecoratedSymbol < Struct.new(:symbol, :modifier, :args)
2
- MODIFIERS = { :ne => :ne, :not => :ne, :in => :in, :eq => :eq,
2
+ MODIFIERS = { :in => :in, :nin => :nin,
3
+ :eq => :eq, :ne => :ne, :not => :ne,
3
4
  :gt => :gt, :ge => :ge, :gte => :ge,
4
5
  :lt => :lt, :le => :le, :lte => :le}
5
6
 
@@ -2,13 +2,18 @@ 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, :validates, :required]
5
+ VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :index, :validates, :required]
6
6
  include NoBrainer::Document::Association::Core::Metadata
7
7
  extend NoBrainer::Document::Association::EagerLoader::Generic
8
8
 
9
9
  def foreign_key
10
10
  # TODO test :foreign_key
11
- options[:foreign_key].try(:to_sym) || :"#{target_name}_id"
11
+ options[:foreign_key].try(:to_sym) || :"#{target_name}_#{primary_key}"
12
+ end
13
+
14
+ def primary_key
15
+ # TODO test :primary_key
16
+ options[:primary_key].try(:to_sym) || target_klass.pk_name
12
17
  end
13
18
 
14
19
  def target_klass
@@ -30,10 +35,9 @@ class NoBrainer::Document::Association::BelongsTo
30
35
 
31
36
  delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
32
37
  add_callback_for(:after_validation)
33
- # TODO test if we are not overstepping on another foreign_key
34
38
  end
35
39
 
36
- eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ :id },
40
+ eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ primary_key },
37
41
  :unscoped => true
38
42
  end
39
43
 
@@ -47,21 +51,25 @@ class NoBrainer::Document::Association::BelongsTo
47
51
  end
48
52
 
49
53
  def read
50
- return @target_container.first if loaded?
54
+ return target if loaded?
51
55
 
52
56
  if fk = owner.read_attribute(foreign_key)
53
- preload(target_klass.find(fk))
57
+ preload(target_klass.unscoped.where(primary_key => fk).first)
54
58
  end
55
59
  end
56
60
 
57
61
  def write(target)
58
62
  assert_target_type(target)
59
- owner.write_attribute(foreign_key, target.try(:id))
63
+ owner.write_attribute(foreign_key, target.try(:pk_value))
60
64
  preload(target)
61
65
  end
62
66
 
63
- def preload(target)
64
- @target_container = [*target] # the * is for the generic eager loading code
67
+ def preload(targets)
68
+ @target_container = [*targets] # the * is for the generic eager loading code
69
+ target
70
+ end
71
+
72
+ def target
65
73
  @target_container.first
66
74
  end
67
75
 
@@ -70,8 +78,8 @@ class NoBrainer::Document::Association::BelongsTo
70
78
  end
71
79
 
72
80
  def after_validation_callback
73
- if loaded? && @target_container.first && !@target_container.first.persisted?
74
- raise NoBrainer::Error::AssociationNotSaved.new("#{target_name} must be saved first")
81
+ if loaded? && target && !target.persisted?
82
+ raise NoBrainer::Error::AssociationNotPersisted.new("#{target_name} must be saved first")
75
83
  end
76
84
  end
77
85
  end
@@ -49,7 +49,7 @@ module NoBrainer::Document::Association::Core
49
49
 
50
50
  included { attr_accessor :metadata, :owner }
51
51
 
52
- delegate :foreign_key, :target_name, :target_klass, :to => :metadata
52
+ delegate :primary_key, :foreign_key, :target_name, :target_klass, :to => :metadata
53
53
 
54
54
  def initialize(metadata, owner)
55
55
  @metadata, @owner = metadata, owner
@@ -2,13 +2,18 @@ class NoBrainer::Document::Association::HasMany
2
2
  include NoBrainer::Document::Association::Core
3
3
 
4
4
  class Metadata
5
- VALID_OPTIONS = [:foreign_key, :class_name, :dependent]
5
+ VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :dependent]
6
6
  include NoBrainer::Document::Association::Core::Metadata
7
7
  extend NoBrainer::Document::Association::EagerLoader::Generic
8
8
 
9
9
  def foreign_key
10
10
  # TODO test :foreign_key
11
- options[:foreign_key].try(:to_sym) || owner_klass.name.foreign_key.to_sym
11
+ options[:foreign_key].try(:to_sym) || :"#{owner_klass.name.underscore}_#{owner_klass.pk_name}"
12
+ end
13
+
14
+ def primary_key
15
+ # TODO test :primary_key
16
+ options[:primary_key].try(:to_sym) || target_klass.pk_name
12
17
  end
13
18
 
14
19
  def target_klass
@@ -25,6 +30,7 @@ class NoBrainer::Document::Association::HasMany
25
30
  target_klass.association_metadata.values.select do |assoc|
26
31
  assoc.is_a?(NoBrainer::Document::Association::BelongsTo::Metadata) and
27
32
  assoc.foreign_key == self.foreign_key and
33
+ assoc.primary_key == self.primary_key and
28
34
  assoc.target_klass.root_class == owner_klass.root_class
29
35
  end
30
36
  end
@@ -34,11 +40,11 @@ class NoBrainer::Document::Association::HasMany
34
40
  add_callback_for(:before_destroy) if options[:dependent]
35
41
  end
36
42
 
37
- eager_load_with :owner_key => ->{ :id }, :target_key => ->{ foreign_key }
43
+ eager_load_with :owner_key => ->{ primary_key }, :target_key => ->{ foreign_key }
38
44
  end
39
45
 
40
46
  def target_criteria
41
- @target_criteria ||= target_klass.where(foreign_key => owner.id)
47
+ @target_criteria ||= target_klass.where(foreign_key => owner.pk_value)
42
48
  .after_find(set_inverse_proc)
43
49
  end
44
50
 
@@ -1,6 +1,9 @@
1
1
  module NoBrainer::Document::Attributes
2
- VALID_FIELD_OPTIONS = [:index, :default, :type, :type_cast_method, :validates, :required, :readonly]
3
- RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
2
+ VALID_FIELD_OPTIONS = [:index, :default, :type, :cast_user_to_db,
3
+ :cast_db_to_user, :validates, :required, :unique,
4
+ :readonly, :primary_key]
5
+ RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
6
+ + NoBrainer::DecoratedSymbol::MODIFIERS.keys
4
7
  extend ActiveSupport::Concern
5
8
 
6
9
  included do
@@ -15,8 +18,12 @@ module NoBrainer::Document::Attributes
15
18
  assign_attributes(attrs, options.reverse_merge(:pristine => true))
16
19
  end
17
20
 
21
+ def readable_attributes
22
+ @_attributes.keys & self.class.fields.keys.map(&:to_s)
23
+ end
24
+
18
25
  def attributes
19
- Hash[@_attributes.keys.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
26
+ Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
20
27
  end
21
28
 
22
29
  def read_attribute(name)
@@ -43,13 +50,14 @@ module NoBrainer::Document::Attributes
43
50
  @_attributes.clear if options[:pristine]
44
51
  if options[:from_db]
45
52
  @_attributes.merge!(attrs)
53
+ clear_dirtiness
46
54
  else
55
+ clear_dirtiness if options[:pristine]
47
56
  attrs.each { |k,v| self.write_attribute(k,v) }
48
57
  end
49
58
  assign_defaults if options[:pristine]
50
59
  self
51
60
  end
52
- def attributes=(*args); assign_attributes(*args); end
53
61
 
54
62
  def inspectable_attributes
55
63
  # TODO test that thing
@@ -96,6 +104,23 @@ module NoBrainer::Document::Attributes
96
104
  _field(attr, self.fields[attr])
97
105
  end
98
106
 
107
+ def _remove_field(attr, options={})
108
+ inject_in_layer :attributes do
109
+ remove_method("#{attr}=")
110
+ remove_method("#{attr}")
111
+ end
112
+ end
113
+
114
+ def remove_field(attr, options={})
115
+ attr = attr.to_sym
116
+
117
+ _remove_field(attr, options)
118
+
119
+ ([self] + descendants).each do |klass|
120
+ klass.fields.delete(attr)
121
+ end
122
+ end
123
+
99
124
  def has_field?(attr)
100
125
  !!fields[attr.to_sym]
101
126
  end
@@ -3,8 +3,9 @@ module NoBrainer::Document::Callbacks
3
3
 
4
4
  included do
5
5
  extend ActiveModel::Callbacks
6
- define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => 'false'
7
- define_model_callbacks :find, :only => [:after], :terminator => 'false'
6
+
7
+ define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => proc { false }
8
+ define_model_callbacks :find, :only => [:after], :terminator => proc { false }
8
9
  end
9
10
 
10
11
  def initialize(*args, &block)
@@ -1,18 +1,27 @@
1
1
  module NoBrainer::Document::Core
2
2
  extend ActiveSupport::Concern
3
3
 
4
- singleton_class.send(:attr_accessor, :all)
5
- self.all = []
4
+ singleton_class.class_eval do
5
+ attr_accessor :_all
6
+
7
+ def all
8
+ Rails.application.eager_load! if defined?(Rails.application.eager_load!)
9
+ @_all
10
+ end
11
+ end
12
+ self._all = []
6
13
 
7
- # TODO This assume the primary key is id.
8
- # RethinkDB can have a custom primary key. careful.
9
14
  include ActiveModel::Conversion
10
15
 
16
+ def to_key
17
+ [pk_value]
18
+ end
19
+
11
20
  included do
12
21
  # TODO test these includes
13
22
  extend ActiveModel::Naming
14
23
  extend ActiveModel::Translation
15
24
 
16
- NoBrainer::Document::Core.all << self
25
+ NoBrainer::Document::Core._all << self
17
26
  end
18
27
  end
@@ -2,7 +2,7 @@ module NoBrainer::Document::Criteria
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def selector
5
- self.class.selector_for(id)
5
+ self.class.selector_for(pk_value)
6
6
  end
7
7
 
8
8
  included { cattr_accessor :default_scope_proc, :instance_accessor => false }
@@ -40,20 +40,20 @@ module NoBrainer::Document::Criteria
40
40
  self.default_scope_proc = criteria.is_a?(Proc) ? criteria : proc { criteria }
41
41
  end
42
42
 
43
- def selector_for(id)
44
- # TODO Pass primary key if not default
45
- unscoped.where(:id => id)
43
+ def selector_for(pk)
44
+ rql_table.get(pk)
46
45
  end
47
46
 
48
47
  # XXX this doesn't have the same semantics as
49
48
  # other ORMs. the equivalent is find!.
50
- def find(id)
51
- selector_for(id).first
49
+ def find(pk)
50
+ attrs = NoBrainer.run { selector_for(pk) }
51
+ new_from_db(attrs).tap { |doc| doc.run_callbacks(:find) } if attrs
52
52
  end
53
53
 
54
- def find!(id)
55
- find(id).tap do |doc|
56
- raise NoBrainer::Error::DocumentNotFound, "#{self.class} id #{id} not found" unless doc
54
+ def find!(pk)
55
+ find(pk).tap do |doc|
56
+ raise NoBrainer::Error::DocumentNotFound, "#{self} #{pk_name}: #{pk} not found" unless doc
57
57
  end
58
58
  end
59
59
  end
@@ -6,11 +6,6 @@ module NoBrainer::Document::Dirty
6
6
  # things like undefined -> nil. Going through the getters will
7
7
  # not give us that.
8
8
 
9
- def assign_attributes(attrs, options={})
10
- clear_dirtiness if options[:pristine]
11
- super
12
- end
13
-
14
9
  def _create(*args)
15
10
  super.tap { clear_dirtiness }
16
11
  end
@@ -84,5 +79,16 @@ module NoBrainer::Document::Dirty
84
79
  end
85
80
  end
86
81
  end
82
+
83
+ def _remove_field(attr, options={})
84
+ super
85
+ inject_in_layer :dirty_tracking do
86
+ remove_method("#{attr}_change")
87
+ remove_method("#{attr}_changed?")
88
+ remove_method("#{attr}_was")
89
+ remove_method("#{attr}=")
90
+ remove_method("#{attr}")
91
+ end
92
+ end
87
93
  end
88
94
  end
@@ -17,4 +17,8 @@ module NoBrainer::Document::DynamicAttributes
17
17
  @_attributes[name] = value
18
18
  end
19
19
  end
20
+
21
+ def readable_attributes
22
+ @_attributes.keys
23
+ end
20
24
  end