nobrainer 0.13.1 → 0.15.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 +20 -4
- data/lib/no_brainer/criteria/count.rb +1 -1
- data/lib/no_brainer/criteria/delete.rb +2 -2
- data/lib/no_brainer/criteria/first.rb +6 -0
- data/lib/no_brainer/criteria/order_by.rb +33 -10
- data/lib/no_brainer/criteria/update.rb +2 -2
- data/lib/no_brainer/criteria/where.rb +12 -6
- data/lib/no_brainer/decorated_symbol.rb +2 -1
- data/lib/no_brainer/document/association/belongs_to.rb +10 -5
- 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 -3
- data/lib/no_brainer/document/callbacks.rb +2 -10
- data/lib/no_brainer/document/core.rb +5 -3
- data/lib/no_brainer/document/criteria.rb +11 -11
- data/lib/no_brainer/document/dirty.rb +11 -0
- 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 +6 -2
- data/lib/no_brainer/document/persistance.rb +7 -6
- data/lib/no_brainer/document/readonly.rb +7 -0
- data/lib/no_brainer/document/serialization.rb +4 -0
- data/lib/no_brainer/document/types/boolean.rb +26 -0
- data/lib/no_brainer/document/types/date.rb +26 -0
- data/lib/no_brainer/document/types/float.rb +20 -0
- data/lib/no_brainer/document/types/integer.rb +18 -0
- data/lib/no_brainer/document/types/string.rb +14 -0
- data/lib/no_brainer/document/types/symbol.rb +21 -0
- data/lib/no_brainer/document/types/time.rb +41 -0
- data/lib/no_brainer/document/types.rb +64 -124
- data/lib/no_brainer/document/uniqueness.rb +4 -2
- data/lib/no_brainer/document/validation.rb +2 -1
- data/lib/no_brainer/document.rb +2 -0
- data/lib/no_brainer/error.rb +9 -9
- data/lib/no_brainer/query_runner/logger.rb +18 -12
- data/lib/no_brainer/query_runner/missing_index.rb +15 -3
- data/lib/no_brainer/query_runner/reconnect.rb +15 -4
- 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/rql.rb +25 -0
- data/lib/nobrainer.rb +9 -6
- data/lib/rails/generators/nobrainer/model/model_generator.rb +2 -1
- data/lib/rails/generators/nobrainer/model/templates/model.rb.tt +7 -2
- metadata +18 -11
- 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: e97dd4f563340a2c8551ffefb21f22fc628954b2
|
|
4
|
+
data.tar.gz: 1fca054e8f966be9e7518f8245c39987d40068c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1cbfebfa6ac2922e733d157522eda4e6c9edf9de9e51601f6892ac2ac2814593ed20807c68441a2ff7391398cf37896eeb739eaf2cdafa0da8e9fe91bb02fce2
|
|
7
|
+
data.tar.gz: ace1719ae85c7593145e81818fb1b93f715ad65d4634130c664b0fdd134c637aba0395066163b9fd3aee91f7ec0b27a53338e857ec2c089a4b425e02f2eba00b
|
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,
|
|
7
|
+
:max_reconnection_tries, :durability,
|
|
8
|
+
:user_timezone, :db_timezone, :colorize_logger,
|
|
8
9
|
:distributed_lock_class
|
|
9
10
|
|
|
10
11
|
def apply_defaults
|
|
@@ -15,6 +16,8 @@ module NoBrainer::Config
|
|
|
15
16
|
self.auto_create_tables = true
|
|
16
17
|
self.max_reconnection_tries = 10
|
|
17
18
|
self.durability = default_durability
|
|
19
|
+
self.user_timezone = :local
|
|
20
|
+
self.db_timezone = :utc
|
|
18
21
|
self.colorize_logger = true
|
|
19
22
|
self.distributed_lock_class = nil
|
|
20
23
|
end
|
|
@@ -27,6 +30,7 @@ module NoBrainer::Config
|
|
|
27
30
|
def configure(&block)
|
|
28
31
|
apply_defaults unless configured?
|
|
29
32
|
block.call(self) if block
|
|
33
|
+
assert_valid_options!
|
|
30
34
|
@configured = true
|
|
31
35
|
|
|
32
36
|
NoBrainer.disconnect_if_url_changed
|
|
@@ -36,9 +40,21 @@ module NoBrainer::Config
|
|
|
36
40
|
!!@configured
|
|
37
41
|
end
|
|
38
42
|
|
|
43
|
+
def assert_valid_options!
|
|
44
|
+
assert_array_in :durability, [:hard, :soft]
|
|
45
|
+
assert_array_in :user_timezone, [:unchanged, :utc, :local]
|
|
46
|
+
assert_array_in :db_timezone, [:unchanged, :utc, :local]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def assert_array_in(name, values)
|
|
50
|
+
unless __send__(name).in?(values)
|
|
51
|
+
raise ArgumentError.new("Unknown configuration for #{name}: #{__send__(name)}. Valid values are: #{values.inspect}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
39
55
|
def default_rethinkdb_url
|
|
40
56
|
db = ENV['RETHINKDB_DB'] || ENV['RDB_DB']
|
|
41
|
-
db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}"
|
|
57
|
+
db ||= "#{Rails.application.class.parent_name.underscore}_#{Rails.env}" rescue nil
|
|
42
58
|
host = ENV['RETHINKDB_HOST'] || ENV['RDB_HOST'] || 'localhost'
|
|
43
59
|
port = ENV['RETHINKDB_PORT'] || ENV['RDB_PORT']
|
|
44
60
|
auth = ENV['RETHINKDB_AUTH'] || ENV['RDB_AUTH']
|
|
@@ -48,11 +64,11 @@ module NoBrainer::Config
|
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
def default_logger
|
|
51
|
-
defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
|
|
67
|
+
defined?(Rails.logger) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
|
|
52
68
|
end
|
|
53
69
|
|
|
54
70
|
def default_durability
|
|
55
|
-
(defined?(Rails) && (Rails.env.test? || Rails.env.development?)) ? :soft : :hard
|
|
71
|
+
(defined?(Rails.env) && (Rails.env.test? || Rails.env.development?)) ? :soft : :hard
|
|
56
72
|
end
|
|
57
73
|
end
|
|
58
74
|
end
|
|
@@ -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
|
|
@@ -17,6 +17,12 @@ module NoBrainer::Criteria::First
|
|
|
17
17
|
last.tap { |doc| raise NoBrainer::Error::DocumentNotFound unless doc }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def sample(n=nil)
|
|
21
|
+
result = NoBrainer.run { self.without_ordering.to_rql.sample(n.nil? ? 1 : n) }
|
|
22
|
+
result = result.map(&method(:instantiate_doc))
|
|
23
|
+
n.nil? ? result.first : result
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
private
|
|
21
27
|
|
|
22
28
|
def get_one(criteria)
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -54,24 +54,26 @@ module NoBrainer::Criteria::Where
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
class BinaryOperator < Struct.new(:key, :op, :value, :criteria)
|
|
57
|
+
class BinaryOperator < Struct.new(:key, :op, :value, :criteria, :casted_values)
|
|
58
58
|
def simplify
|
|
59
59
|
key = cast_key(self.key)
|
|
60
60
|
case op
|
|
61
61
|
when :in then
|
|
62
62
|
case value
|
|
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)
|
|
63
|
+
when Range then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria, true)
|
|
64
|
+
when Array then BinaryOperator.new(key, :in, value.map(&method(:cast_value)).uniq, criteria, true)
|
|
65
65
|
else raise ArgumentError.new ":in takes an array/range, not #{value}"
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
when :between then BinaryOperator.new(key, :between, (cast_value(value.min)..cast_value(value.max)), criteria, true)
|
|
68
|
+
else BinaryOperator.new(key, op, cast_value(value), criteria, true)
|
|
68
69
|
end
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def to_rql(doc)
|
|
72
73
|
case op
|
|
74
|
+
when :defined then value ? doc.has_fields(key) : doc.has_fields(key).not
|
|
73
75
|
when :between then (doc[key] >= value.min) & (doc[key] <= value.max)
|
|
74
|
-
when :in
|
|
76
|
+
when :in then RethinkDB::RQL.new.expr(value).contains(doc[key])
|
|
75
77
|
else doc[key].__send__(op, value)
|
|
76
78
|
end
|
|
77
79
|
end
|
|
@@ -87,18 +89,22 @@ module NoBrainer::Criteria::Where
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def cast_value(value)
|
|
92
|
+
return value if casted_values
|
|
93
|
+
|
|
90
94
|
case association
|
|
91
95
|
when NoBrainer::Document::Association::BelongsTo::Metadata
|
|
92
96
|
target_klass = association.target_klass
|
|
93
97
|
opts = { :attr_name => key, :value => value, :type => target_klass}
|
|
94
98
|
raise NoBrainer::Error::InvalidType.new(opts) unless value.is_a?(target_klass)
|
|
95
|
-
value.
|
|
99
|
+
value.pk_value
|
|
96
100
|
else
|
|
97
101
|
criteria.klass.cast_user_to_db_for(key, value)
|
|
98
102
|
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def cast_key(key)
|
|
106
|
+
return key if casted_values
|
|
107
|
+
|
|
102
108
|
case association
|
|
103
109
|
when NoBrainer::Document::Association::BelongsTo::Metadata
|
|
104
110
|
association.foreign_key
|
|
@@ -2,7 +2,8 @@ class NoBrainer::DecoratedSymbol < Struct.new(:symbol, :modifier, :args)
|
|
|
2
2
|
MODIFIERS = { :in => :in, :nin => :nin,
|
|
3
3
|
:eq => :eq, :ne => :ne, :not => :ne,
|
|
4
4
|
:gt => :gt, :ge => :ge, :gte => :ge,
|
|
5
|
-
:lt => :lt, :le => :le, :lte => :le
|
|
5
|
+
:lt => :lt, :le => :le, :lte => :le,
|
|
6
|
+
:defined => :defined }
|
|
6
7
|
|
|
7
8
|
def self.hook
|
|
8
9
|
Symbol.class_eval do
|
|
@@ -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
|
|
@@ -32,7 +37,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
32
37
|
add_callback_for(:after_validation)
|
|
33
38
|
end
|
|
34
39
|
|
|
35
|
-
eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{
|
|
40
|
+
eager_load_with :owner_key => ->{ foreign_key }, :target_key => ->{ primary_key },
|
|
36
41
|
:unscoped => true
|
|
37
42
|
end
|
|
38
43
|
|
|
@@ -49,13 +54,13 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
49
54
|
return target if loaded?
|
|
50
55
|
|
|
51
56
|
if fk = owner.read_attribute(foreign_key)
|
|
52
|
-
preload(target_klass.
|
|
57
|
+
preload(target_klass.unscoped.where(primary_key => fk).first)
|
|
53
58
|
end
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def write(target)
|
|
57
62
|
assert_target_type(target)
|
|
58
|
-
owner.write_attribute(foreign_key, target.try(:
|
|
63
|
+
owner.write_attribute(foreign_key, target.try(:pk_value))
|
|
59
64
|
preload(target)
|
|
60
65
|
end
|
|
61
66
|
|
|
@@ -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,7 @@
|
|
|
1
1
|
module NoBrainer::Document::Attributes
|
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :
|
|
3
|
-
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations]
|
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :validates, :required, :unique, :in, :readonly, :primary_key]
|
|
3
|
+
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
|
|
4
|
+
+ NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
|
4
5
|
extend ActiveSupport::Concern
|
|
5
6
|
|
|
6
7
|
included do
|
|
@@ -15,8 +16,12 @@ module NoBrainer::Document::Attributes
|
|
|
15
16
|
assign_attributes(attrs, options.reverse_merge(:pristine => true))
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
def readable_attributes
|
|
20
|
+
@_attributes.keys & self.class.fields.keys.map(&:to_s)
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
def attributes
|
|
19
|
-
Hash[
|
|
24
|
+
Hash[readable_attributes.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
def read_attribute(name)
|
|
@@ -57,6 +62,10 @@ module NoBrainer::Document::Attributes
|
|
|
57
62
|
Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
|
|
58
63
|
end
|
|
59
64
|
|
|
65
|
+
def to_s
|
|
66
|
+
"#<#{self.class} #{self.class.pk_name}: #{self.pk_value.inspect}>"
|
|
67
|
+
end
|
|
68
|
+
|
|
60
69
|
def inspect
|
|
61
70
|
"#<#{self.class} #{inspectable_attributes.map { |k,v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
|
62
71
|
end
|
|
@@ -97,6 +106,23 @@ module NoBrainer::Document::Attributes
|
|
|
97
106
|
_field(attr, self.fields[attr])
|
|
98
107
|
end
|
|
99
108
|
|
|
109
|
+
def _remove_field(attr, options={})
|
|
110
|
+
inject_in_layer :attributes do
|
|
111
|
+
remove_method("#{attr}=")
|
|
112
|
+
remove_method("#{attr}")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def remove_field(attr, options={})
|
|
117
|
+
attr = attr.to_sym
|
|
118
|
+
|
|
119
|
+
_remove_field(attr, options)
|
|
120
|
+
|
|
121
|
+
([self] + descendants).each do |klass|
|
|
122
|
+
klass.fields.delete(attr)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
100
126
|
def has_field?(attr)
|
|
101
127
|
!!fields[attr.to_sym]
|
|
102
128
|
end
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
module NoBrainer::Document::Callbacks
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
def self.terminator
|
|
5
|
-
if Gem.loaded_specs['activesupport'].version.release >= Gem::Version.new('4.1')
|
|
6
|
-
proc { false }
|
|
7
|
-
else
|
|
8
|
-
'false'
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
4
|
included do
|
|
13
5
|
extend ActiveModel::Callbacks
|
|
14
6
|
|
|
15
|
-
define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator =>
|
|
16
|
-
define_model_callbacks :find, :only => [:after], :terminator =>
|
|
7
|
+
define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => proc { false }
|
|
8
|
+
define_model_callbacks :find, :only => [:after], :terminator => proc { false }
|
|
17
9
|
end
|
|
18
10
|
|
|
19
11
|
def initialize(*args, &block)
|
|
@@ -5,16 +5,18 @@ module NoBrainer::Document::Core
|
|
|
5
5
|
attr_accessor :_all
|
|
6
6
|
|
|
7
7
|
def all
|
|
8
|
-
Rails.application.eager_load! if defined?(Rails)
|
|
8
|
+
Rails.application.eager_load! if defined?(Rails.application.eager_load!)
|
|
9
9
|
@_all
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
self._all = []
|
|
13
13
|
|
|
14
|
-
# TODO This assume the primary key is id.
|
|
15
|
-
# RethinkDB can have a custom primary key. careful.
|
|
16
14
|
include ActiveModel::Conversion
|
|
17
15
|
|
|
16
|
+
def to_key
|
|
17
|
+
[pk_value]
|
|
18
|
+
end
|
|
19
|
+
|
|
18
20
|
included do
|
|
19
21
|
# TODO test these includes
|
|
20
22
|
extend ActiveModel::Naming
|
|
@@ -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 }
|
|
@@ -10,7 +10,7 @@ module NoBrainer::Document::Criteria
|
|
|
10
10
|
module ClassMethods
|
|
11
11
|
delegate :to_rql, # Core
|
|
12
12
|
:limit, :offset, :skip, # Limit
|
|
13
|
-
:order_by, :reverse_order,
|
|
13
|
+
:order_by, :reverse_order, :without_ordering, # OrderBy
|
|
14
14
|
:scoped, :unscoped, # Scope
|
|
15
15
|
:where, :with_index, :without_index, :used_index, :indexed?, # Where
|
|
16
16
|
:with_cache, :without_cache, # Cache
|
|
@@ -18,7 +18,7 @@ module NoBrainer::Document::Criteria
|
|
|
18
18
|
:delete_all, :destroy_all, # Delete
|
|
19
19
|
:includes, :preload, # Preload
|
|
20
20
|
:each, :to_a, # Enumerable
|
|
21
|
-
:first, :last, :first!, :last!, # First
|
|
21
|
+
:first, :last, :first!, :last!, :sample, # First
|
|
22
22
|
:update_all, :replace_all, # Update
|
|
23
23
|
:to => :all
|
|
24
24
|
|
|
@@ -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
|
|
@@ -79,5 +79,16 @@ module NoBrainer::Document::Dirty
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
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
|
|
82
93
|
end
|
|
83
94
|
end
|
|
@@ -5,17 +5,23 @@ require 'digest/md5'
|
|
|
5
5
|
module NoBrainer::Document::Id
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
DEFAULT_PK_NAME = :id
|
|
9
|
+
|
|
10
|
+
def pk_value
|
|
11
|
+
__send__(self.class.pk_name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pk_value=(value)
|
|
15
|
+
__send__("#{self.class.pk_name}=", value)
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def ==(other)
|
|
13
19
|
return super unless self.class == other.class
|
|
14
|
-
!
|
|
20
|
+
!pk_value.nil? && pk_value == other.pk_value
|
|
15
21
|
end
|
|
16
22
|
alias_method :eql?, :==
|
|
17
23
|
|
|
18
|
-
delegate :hash, :to => :
|
|
24
|
+
delegate :hash, :to => :pk_value
|
|
19
25
|
|
|
20
26
|
# The following code is inspired by the mongo-ruby-driver
|
|
21
27
|
|
|
@@ -46,4 +52,43 @@ module NoBrainer::Document::Id
|
|
|
46
52
|
|
|
47
53
|
oid.unpack("C12").map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
|
|
48
54
|
end
|
|
55
|
+
|
|
56
|
+
module ClassMethods
|
|
57
|
+
def define_default_pk
|
|
58
|
+
class_variable_set(:@@pk_name, nil)
|
|
59
|
+
field NoBrainer::Document::Id::DEFAULT_PK_NAME, :primary_key => :default,
|
|
60
|
+
:type => String, :default => ->{ NoBrainer::Document::Id.generate }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def define_pk(attr)
|
|
64
|
+
if fields[pk_name].try(:[], :primary_key) == :default
|
|
65
|
+
remove_field(pk_name, :set_default_pk => false)
|
|
66
|
+
end
|
|
67
|
+
class_variable_set(:@@pk_name, attr)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def pk_name
|
|
71
|
+
class_variable_get(:@@pk_name)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def _field(attr, options={})
|
|
75
|
+
super
|
|
76
|
+
define_pk(attr) if options[:primary_key]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def field(attr, options={})
|
|
80
|
+
if options[:primary_key]
|
|
81
|
+
options = options.merge(:readonly => true) if options[:readonly].nil?
|
|
82
|
+
options = options.merge(:index => true)
|
|
83
|
+
end
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def _remove_field(attr, options={})
|
|
88
|
+
super
|
|
89
|
+
if fields[attr][:primary_key] && options[:set_default_pk] != false
|
|
90
|
+
define_default_pk
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
49
94
|
end
|
|
@@ -5,7 +5,6 @@ module NoBrainer::Document::Index
|
|
|
5
5
|
included do
|
|
6
6
|
cattr_accessor :indexes, :instance_accessor => false
|
|
7
7
|
self.indexes = {}
|
|
8
|
-
self.index :id
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
module ClassMethods
|
|
@@ -57,6 +56,11 @@ module NoBrainer::Document::Index
|
|
|
57
56
|
end
|
|
58
57
|
end
|
|
59
58
|
|
|
59
|
+
def _remove_field(attr, options={})
|
|
60
|
+
super
|
|
61
|
+
remove_index(attr) if fields[attr][:index]
|
|
62
|
+
end
|
|
63
|
+
|
|
60
64
|
def perform_create_index(index_name, options={})
|
|
61
65
|
index_name = index_name.to_sym
|
|
62
66
|
index_args = self.indexes[index_name]
|
|
@@ -79,7 +83,7 @@ module NoBrainer::Document::Index
|
|
|
79
83
|
|
|
80
84
|
def perform_update_indexes(options={})
|
|
81
85
|
current_indexes = NoBrainer.run(self.rql_table.index_list).map(&:to_sym)
|
|
82
|
-
wanted_indexes = self.indexes.keys - [
|
|
86
|
+
wanted_indexes = self.indexes.keys - [self.pk_name]
|
|
83
87
|
|
|
84
88
|
(current_indexes - wanted_indexes).each do |index_name|
|
|
85
89
|
perform_drop_index(index_name, options)
|