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.
- checksums.yaml +4 -4
- data/lib/no_brainer/config.rb +6 -4
- data/lib/no_brainer/connection.rb +14 -12
- data/lib/no_brainer/criteria/core.rb +1 -6
- data/lib/no_brainer/criteria/delete.rb +2 -2
- data/lib/no_brainer/criteria/order_by.rb +33 -10
- data/lib/no_brainer/criteria/preload.rb +0 -5
- data/lib/no_brainer/criteria/update.rb +2 -2
- data/lib/no_brainer/criteria/where.rb +34 -7
- data/lib/no_brainer/decorated_symbol.rb +2 -1
- data/lib/no_brainer/document/association/belongs_to.rb +19 -11
- data/lib/no_brainer/document/association/core.rb +1 -1
- data/lib/no_brainer/document/association/has_many.rb +10 -4
- data/lib/no_brainer/document/attributes.rb +29 -4
- data/lib/no_brainer/document/callbacks.rb +3 -2
- data/lib/no_brainer/document/core.rb +14 -5
- data/lib/no_brainer/document/criteria.rb +9 -9
- data/lib/no_brainer/document/dirty.rb +11 -5
- data/lib/no_brainer/document/dynamic_attributes.rb +4 -0
- data/lib/no_brainer/document/id.rb +49 -4
- data/lib/no_brainer/document/index.rb +12 -3
- data/lib/no_brainer/document/persistance.rb +5 -9
- data/lib/no_brainer/document/readonly.rb +7 -0
- data/lib/no_brainer/document/serialization.rb +4 -0
- data/lib/no_brainer/document/types.rb +62 -13
- data/lib/no_brainer/document/uniqueness.rb +99 -0
- data/lib/no_brainer/document/validation.rb +8 -24
- data/lib/no_brainer/document.rb +3 -1
- data/lib/no_brainer/error.rb +9 -9
- data/lib/no_brainer/index_manager.rb +4 -2
- data/lib/no_brainer/loader.rb +1 -1
- data/lib/no_brainer/query_runner/logger.rb +31 -19
- data/lib/no_brainer/query_runner/missing_index.rb +15 -3
- data/lib/no_brainer/query_runner/{connection.rb → reconnect.rb} +16 -5
- data/lib/no_brainer/query_runner/table_on_demand.rb +14 -25
- data/lib/no_brainer/query_runner/write_error.rb +5 -8
- data/lib/no_brainer/query_runner.rb +2 -2
- data/lib/no_brainer/rql.rb +25 -0
- data/lib/nobrainer.rb +5 -6
- metadata +70 -69
- data/lib/no_brainer/util.rb +0 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43783bcf0d625af679de685ab771782ce9b141f3
|
|
4
|
+
data.tar.gz: 4b5e4ff205f55adb754133b681b6e71c71772790
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c182d056390dd2c7b51bac68999d79c72cfaca49837239c4eead56ce8d0ff474630cc7b24e9fa12a4ff8d36641a5f6cc46fb7c588c45e975af42e8d4b5dbebc
|
|
7
|
+
data.tar.gz: 5226adb01498de4e82f8b3a4729c818ed1a40dc07e3596061d64956407b9a62129108046e0625bbe60bcac1804f0b8111cab4ec3a736151b6cfdc3d5799f2d88
|
data/lib/no_brainer/config.rb
CHANGED
|
@@ -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}"
|
|
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
|
-
|
|
13
|
-
|
|
12
|
+
@parsed_uri ||= begin
|
|
13
|
+
require 'uri'
|
|
14
|
+
uri = URI.parse(self.uri)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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, :
|
|
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.
|
|
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.
|
|
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
|
|
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 || {
|
|
58
|
+
self.order.presence || (klass ? {klass.pk_name => :asc} : {})
|
|
47
59
|
end
|
|
48
60
|
|
|
49
61
|
def reverse_order?
|
|
50
|
-
|
|
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 =
|
|
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
|
-
|
|
86
|
+
NoBrainer::RQL.is_table?(rql)
|
|
87
|
+
if NoBrainer::RQL.is_table?(rql) && !without_index?
|
|
68
88
|
options = {}
|
|
69
|
-
first_key =
|
|
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
|
-
|
|
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, (
|
|
63
|
-
when Array then BinaryOperator.new(key, :in, value.map(&method(:
|
|
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,
|
|
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
|
|
81
|
-
|
|
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 :
|
|
130
|
-
when :
|
|
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 = { :
|
|
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}
|
|
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 => ->{
|
|
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
|
|
54
|
+
return target if loaded?
|
|
51
55
|
|
|
52
56
|
if fk = owner.read_attribute(foreign_key)
|
|
53
|
-
preload(target_klass.
|
|
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(:
|
|
63
|
+
owner.write_attribute(foreign_key, target.try(:pk_value))
|
|
60
64
|
preload(target)
|
|
61
65
|
end
|
|
62
66
|
|
|
63
|
-
def preload(
|
|
64
|
-
@target_container = [*
|
|
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? &&
|
|
74
|
-
raise NoBrainer::Error::
|
|
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.
|
|
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 => ->{
|
|
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.
|
|
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, :
|
|
3
|
-
|
|
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[
|
|
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
|
-
|
|
7
|
-
define_model_callbacks :
|
|
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.
|
|
5
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
44
|
-
|
|
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(
|
|
51
|
-
selector_for(
|
|
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!(
|
|
55
|
-
find(
|
|
56
|
-
raise NoBrainer::Error::DocumentNotFound, "#{self
|
|
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
|